OAA Mapping Analysis — OAA Specialist
Date: 2026-02-13 (Round 4) Role: Veza OAA Specialist Core Question: How should autonomous execution chains map to OAA concepts, and what is the cleanest extension strategy that preserves OAA compatibility?
Table of Contents
- Executive Summary
- OAA Data Model Baseline
- The Mapping Problem
- Approach A: Automation as Application
- Approach B: Automation as Resource
- Approach C: Hybrid Model
- The Trigger/Data Question
- OAA Extension Strategy
- Compatibility Matrix
- Concrete Mapping: AzureGraphRouter
- Reconciliation with Round 3
- Recommendation
- Appendix A: OAA Entity Reference
- Appendix B: OAA Connector Patterns Observed
1. Executive Summary
The founder's insight is directionally correct and architecturally significant: OAA's entity model — Application, Resource, Local User, Permission — provides a more semantically precise vocabulary for autonomous execution chains than the current SecurityV0 model, which treats everything as an Identity. However, after rigorous analysis of OAA's actual capabilities, community connector patterns, and the specific requirements of execution chain tracking, I conclude that:
-
Automation-as-Application is semantically the most natural fit but creates an entity proliferation problem that OAA was not designed for (hundreds of micro-applications per ServiceNow instance).
-
Automation-as-Resource is the most OAA-idiomatic approach and maps naturally to how existing connectors (GitHub repos, Jira projects) model domain objects, but it loses the "container" semantics that execution chains need.
-
The Hybrid Model — where ServiceNow is the Application and execution chains are Resources with sub-resources, while cross-system identities are linked via IdP identity correlation — is the recommended approach. It respects OAA conventions, maps cleanly to Veza's authorization graph, and is compatible with Round 3's
execution_chainscollection. -
OAA is insufficient alone. OAA models authorization snapshots. SecurityV0 models execution exposure over time. The platform needs OAA-compatible export as one output format, but the internal data model must go beyond what OAA can express. The
execution_chainscollection from Round 3 remains necessary — it is the temporal, durable, listable layer that OAA fundamentally cannot provide.
2. OAA Data Model Baseline
Before analyzing mapping approaches, I must establish precisely what OAA provides and what it does not. This section draws from the OAA documentation (/Users/lucky/dev/securityv0/research/oaa-docs/README.md), the Python SDK patterns (/Users/lucky/dev/securityv0/research/veza-oaa-community/quickstarts/sample-app.py), and real-world connector implementations.
2.1 OAA Entity Hierarchy
Provider (top-level namespace)
└── Application (container for authorization data)
├── Local Users (identities native to the app)
│ ├── identities → IdP identity correlation
│ ├── groups → group membership
│ └── permissions/roles → authorization bindings
├── Local Groups (collections of users)
├── Local Roles (permission bundles)
├── Custom Permissions (app-specific, mapped to canonical types)
├── Resources (domain objects within the app)
│ └── Sub-Resources (arbitrary nesting depth)
└── IdP Identities (external identity references)
2.2 OAA Permission Types (10 Canonical)
| Permission | Semantic | Relevant to Execution Chains? |
|---|---|---|
DataRead | Read data content | Yes — trigger reads (incident table) |
DataWrite | Modify data content | Yes — automation updates (incident, HR case) |
DataDelete | Remove data | Yes — cleanup automations |
DataCreate | Create new data records | Yes — automation creates records |
MetadataRead | Read system configuration/schema | Marginally — reading ACLs, configs |
MetadataWrite | Modify system configuration | Yes — automations that modify ACLs, configs |
NonData | Actions that aren't data operations | Yes — "execute" semantics (run script, trigger flow) |
OwnershipAssignment | Assign ownership of resources | Rarely — delegation automations |
ResourceAdmin | Administer resources | Yes — admin-level automation operations |
AccountAdmin | Administer user accounts | Rarely — user provisioning automations |
Critical observation: OAA has no Execute permission type. The closest is NonData, which is a catch-all for non-CRUD operations. This is a fundamental semantic gap for modeling execution chains, where "this automation executes this script" is the primary relationship.
2.3 OAA Authorization Binding Model
OAA binds identities to resources through permissions:
Identity (Local User / Local Group / IdP Identity)
→ Permission (custom, mapped to canonical types)
→ Resource / Application (what the permission applies to)
Alternatively, through roles:
Identity → Role → Permission → Resource / Application
Key constraint: OAA's binding model is Identity → Permission → Resource. There is no concept of:
- Execution flow (A triggers B which calls C)
- Temporal state (when did this permission exist?)
- Execution evidence (proof that this identity actually exercised the permission)
- Credential chain (which OAuth token, certificate, or federation trust enables access?)
- Cross-application permission flow (Identity in App A authenticates to App B via credential)
OAA models the static authorization graph at a point in time. SecurityV0 models the dynamic execution exposure over time. This is the fundamental gap.
2.4 How Existing Connectors Use OAA
From analyzing the community connectors:
| Connector | Application | Resources | Sub-Resources | Identities | Pattern |
|---|---|---|---|---|---|
GitHub (oaa_github.py) | Github - {org_name} | Repositories (type: repository) | None | Org members + collaborators | One app per org; repos as resources; teams as groups |
Jira (oaa_jira.py) | Jira - {instance} | Projects (type: project) | None | Jira users | One app per instance; projects as resources; groups mapped to roles per project |
Slack (oaa_slack.py) | Slack - {team_name} | None | None | Slack users + bots | One app per workspace; no resources; permissions at app level only |
Pattern observations:
-
One application per system instance — GitHub creates one app per org, Jira one per instance, Slack one per workspace. No connector creates multiple applications for different functional domains within the same system.
-
Resources represent domain objects — GitHub repos, Jira projects. These are the "things" that identities interact with, not intermediate processing steps.
-
Roles are permission bundles per context — Jira creates per-project roles (
project-123-role-Admin) to capture context-specific permissions. GitHub defines repo-level roles (Read,Write,Admin). -
No connector models workflows, automations, or execution chains — Every existing connector maps the static Identity → Permission → Resource graph. No connector has attempted to model ServiceNow Business Rules, GitHub Actions workflows, or any automation concept.
-
Custom properties are the extension mechanism — All connectors use
property_definitionsto add domain-specific metadata (e.g., GitHub'sOutsideCollaborator,is_fork,visibility).
This means any approach to modeling execution chains in OAA is novel territory — there is no existing pattern to follow. We must reason from first principles about OAA's entity semantics.
3. The Mapping Problem
The concrete example we must map is the AzureGraphRouter execution chain:
BusinessRule (AzureGraphRouter)
→ triggers_on: incident table (on insert)
→ calls: ScriptInclude (AzureGraphHelper)
→ calls: RESTMessage (AzureGraphAPI)
→ authenticates_via: OAuthProfile (EntraOAuth)
→ authenticates_as: ServicePrincipal (sp-graph-router)
→ has_role: Application.ReadWrite.All (Entra)
→ applies_to: Microsoft Graph API
This chain involves:
- Two systems: ServiceNow and Entra ID (Microsoft Graph)
- Six distinct entities: BR, SI, RESTMessage, OAuthProfile, SP, Graph API
- Three entity classes: Automations (BR, SI), Configuration (RESTMessage, OAuthProfile), Identity (SP)
- Cross-system credential chain: OAuth client_credentials from ServiceNow to Entra
- Trigger resource: incident table (read event triggers chain)
- Target resource: Microsoft Graph API (write/read operations)
The challenge: OAA's vocabulary has Application, Resource, Local User, Permission. None of these naturally express "BusinessRule triggers on incident insert, calls ScriptInclude, which uses RESTMessage with OAuthProfile to authenticate as ServicePrincipal."
Let me analyze each approach rigorously.
4. Approach A: Automation as Application
4.1 Conceptual Model
Each execution chain becomes an OAA Application. The chain's internal components become the application's "local users" (the identities it authenticates as), "resources" (the tables/APIs it touches), and "permissions" (the roles it exercises).
# AzureGraphRouter as an OAA Application
chain_app = CustomApplication(
name="AzureGraphRouter Incident Routing",
application_type="execution_chain"
)
# The service principal the chain authenticates as → local user
sp_user = chain_app.add_local_user(
"sp-graph-router",
identities="sp-graph-router@tenant.onmicrosoft.com" # IdP link
)
# Resources the chain interacts with
incident_table = chain_app.add_resource(
name="incident",
resource_type="trigger_table"
)
graph_api = chain_app.add_resource(
name="Microsoft Graph API",
resource_type="target_api"
)
# Chain components as sub-resources of the chain itself
br = chain_app.add_resource(
name="AzureGraphRouter",
resource_type="business_rule"
)
si = br.add_sub_resource(
name="AzureGraphHelper",
resource_type="script_include"
)
rest_msg = si.add_sub_resource(
name="AzureGraphAPI",
resource_type="rest_message"
)
# Permissions: what the chain does
chain_app.add_custom_permission(
"trigger_read",
[OAAPermission.DataRead]
)
chain_app.add_custom_permission(
"graph_readwrite",
[OAAPermission.DataRead, OAAPermission.DataWrite]
)
chain_app.add_custom_permission(
"execute",
[OAAPermission.NonData]
)
# Permission bindings
sp_user.add_permission("trigger_read", resources=[incident_table])
sp_user.add_permission("graph_readwrite", resources=[graph_api])
sp_user.add_permission("execute", resources=[br])
4.2 OAA JSON Output
{
"applications": [
{
"name": "AzureGraphRouter Incident Routing",
"application_type": "execution_chain",
"local_users": [
{
"name": "sp-graph-router",
"unique_id": "sp-graph-router-appid-abc123",
"identities": ["sp-graph-router@tenant.onmicrosoft.com"],
"is_active": true
}
],
"local_groups": [],
"local_roles": [],
"resources": [
{
"name": "incident",
"resource_type": "trigger_table",
"description": "ServiceNow incident table — triggers chain on insert"
},
{
"name": "Microsoft Graph API",
"resource_type": "target_api",
"description": "Entra ID Graph API — chain destination"
},
{
"name": "AzureGraphRouter",
"resource_type": "business_rule",
"description": "Entry point — triggers on incident insert",
"sub_resources": [
{
"name": "AzureGraphHelper",
"resource_type": "script_include",
"sub_resources": [
{
"name": "AzureGraphAPI",
"resource_type": "rest_message"
}
]
}
]
}
]
}
],
"permissions": [
{
"name": "trigger_read",
"permission_type": ["DataRead"]
},
{
"name": "graph_readwrite",
"permission_type": ["DataRead", "DataWrite"]
},
{
"name": "execute",
"permission_type": ["NonData"]
}
],
"identity_to_permissions": [
{
"identity": "sp-graph-router-appid-abc123",
"identity_type": "local_user",
"application_permissions": [
{
"application": "AzureGraphRouter Incident Routing",
"permission": "execute",
"resources": ["AzureGraphRouter"]
}
],
"resource_permissions": [
{
"resource": "incident",
"permission": "trigger_read"
},
{
"resource": "Microsoft Graph API",
"permission": "graph_readwrite"
}
]
}
]
}
4.3 Strengths
| Strength | Explanation |
|---|---|
| Container semantics | The chain IS the application — it has its own boundary, its own users, its own resources. This matches the founder's insight: "autonomous execution could be treated as an application." |
| Self-contained authorization graph | All permissions within the chain are scoped to the chain-as-application. Querying "what can this chain do?" is a single application query. |
| Natural sub-resource nesting | BR → SI → RESTMessage maps to resource → sub-resource → sub-sub-resource, which OAA supports natively. |
| Custom properties | Each chain-application can carry execution_mode, security_relevance, trigger_type, egress_category as custom properties. |
| IdP identity correlation | The SP can be linked to the Entra IdP identity via the identities field on the local user, enabling cross-application visibility in Veza. |
4.4 Weaknesses
| Weakness | Severity | Explanation |
|---|---|---|
| Application proliferation | HIGH | A ServiceNow instance with 50 Business Rules, 30 Flow Designer flows, and 20 scheduled jobs would create 100 separate OAA applications. This is not how any existing connector works — GitHub has one app per org, Jira one per instance. Veza's UI and query model assumes tens of applications, not thousands. |
| Shared resource duplication | HIGH | The incident table appears in many chains. As an Application-level resource, it would be duplicated across every chain that touches it. In OAA, resources are scoped to applications — there is no cross-application resource identity. Veza cannot answer "what are all the chains that touch the incident table?" without scanning all applications. |
| Shared identity duplication | HIGH | sp-graph-router might be used by multiple chains. As a local user within each chain-application, it gets duplicated. IdP identity correlation partially solves this, but the local user instances are still separate objects. |
| Semantic distortion | MEDIUM | An Application in OAA represents a software system with its own user base and access model. A Business Rule is not a software system — it is a configured artifact within ServiceNow. Calling it an "Application" is a category error that would confuse Veza users familiar with OAA semantics. |
| Cross-chain queries impossible | HIGH | "Show me all execution chains that use sp-graph-router" requires scanning every chain-application's local users. OAA has no cross-application query mechanism beyond IdP identity correlation, which is designed for user federation, not automation analysis. |
| No execution flow | MEDIUM | Even with sub-resource nesting, the JSON does not capture the execution order (BR fires, calls SI, SI calls RESTMessage). Sub-resources model containment, not invocation sequence. |
4.5 Verdict: Semantically Appealing but Operationally Broken
The automation-as-application model correctly captures the intuition that an execution chain is "a thing with its own users, resources, and permissions." But it breaks OAA's operational model by creating an unsustainable number of applications with duplicated resources and identities. No existing OAA connector follows this pattern, and Veza's platform would likely struggle with the resulting graph density.
5. Approach B: Automation as Resource
5.1 Conceptual Model
ServiceNow is the Application (one instance, one app — matching existing patterns). Each automation artifact (Business Rule, Script Include, Flow, etc.) is a Resource within the ServiceNow application. The execution chain is modeled as resource containment (BR resource contains SI sub-resource contains RESTMessage sub-sub-resource).
# ServiceNow instance as the OAA Application
sn_app = CustomApplication(
name="ServiceNow - corp.service-now.com",
application_type="ServiceNow"
)
# Custom properties for automation resources
sn_app.property_definitions.define_resource_property(
"business_rule", "trigger_table", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "trigger_condition", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "execution_mode", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "security_relevance", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "egress_category", OAAPropertyType.STRING
)
# Integration user as local user
sn_user = sn_app.add_local_user(
"sn-integration-user",
identities="sp-graph-router@tenant.onmicrosoft.com" # IdP correlation
)
# Data tables as resources
incident_table = sn_app.add_resource(
name="incident",
resource_type="table"
)
# Automation artifacts as resources
br = sn_app.add_resource(
name="AzureGraphRouter",
resource_type="business_rule"
)
br.set_property("trigger_table", "incident")
br.set_property("trigger_condition", "on insert")
br.set_property("execution_mode", "autonomous")
br.set_property("security_relevance", "active_external")
br.set_property("egress_category", "external")
# Script Include as sub-resource of Business Rule
si = br.add_sub_resource(
name="AzureGraphHelper",
resource_type="script_include"
)
# REST Message as sub-resource of Script Include
rest_msg = si.add_sub_resource(
name="AzureGraphAPI",
resource_type="rest_message"
)
# Permissions
sn_app.add_custom_permission("execute", [OAAPermission.NonData])
sn_app.add_custom_permission("trigger", [OAAPermission.NonData])
sn_app.add_custom_permission("read_data", [OAAPermission.DataRead])
sn_app.add_custom_permission("write_data", [OAAPermission.DataWrite])
# Automation "executes" — the BR has execute permission on itself (it runs)
# The user that the BR runs as has permissions on the resources
sn_user.add_permission("execute", resources=[br])
sn_user.add_permission("read_data", resources=[incident_table])
5.2 OAA JSON Output
{
"applications": [
{
"name": "ServiceNow - corp.service-now.com",
"application_type": "ServiceNow",
"local_users": [
{
"name": "sn-integration-user",
"unique_id": "sn-integration-user-sysid-xyz",
"identities": ["sp-graph-router@tenant.onmicrosoft.com"],
"is_active": true
}
],
"resources": [
{
"name": "incident",
"resource_type": "table",
"description": "ServiceNow incident table"
},
{
"name": "AzureGraphRouter",
"resource_type": "business_rule",
"description": "Business Rule — triggers on incident insert, routes to Azure Graph",
"custom_properties": {
"trigger_table": "incident",
"trigger_condition": "on insert",
"execution_mode": "autonomous",
"security_relevance": "active_external",
"egress_category": "external"
},
"sub_resources": [
{
"name": "AzureGraphHelper",
"resource_type": "script_include",
"sub_resources": [
{
"name": "AzureGraphAPI",
"resource_type": "rest_message",
"custom_properties": {
"endpoint": "https://graph.microsoft.com",
"auth_type": "oauth2_client_credentials"
}
}
]
}
]
}
]
}
],
"permissions": [
{"name": "execute", "permission_type": ["NonData"]},
{"name": "trigger", "permission_type": ["NonData"]},
{"name": "read_data", "permission_type": ["DataRead"]},
{"name": "write_data", "permission_type": ["DataWrite"]}
],
"identity_to_permissions": [
{
"identity": "sn-integration-user-sysid-xyz",
"identity_type": "local_user",
"resource_permissions": [
{
"resource": "AzureGraphRouter",
"permission": "execute"
},
{
"resource": "incident",
"permission": "read_data"
}
]
}
]
}
5.3 Strengths
| Strength | Explanation |
|---|---|
| Follows existing patterns | Exactly how GitHub models repos (resources within org-app) and Jira models projects (resources within instance-app). One application per ServiceNow instance. |
| No resource duplication | The incident table is one resource within the ServiceNow app. All automations that reference it point to the same resource. |
| No identity duplication | sn-integration-user is one local user. All automations that run as this user share the same identity object. |
| Sub-resource nesting works | BR → SI → RESTMessage maps to resource → sub-resource → sub-sub-resource, capturing the containment hierarchy. |
| Custom properties carry metadata | trigger_table, execution_mode, security_relevance, egress_category all fit naturally as custom properties on the resource. |
| Veza-consumable | Veza can ingest this without modification. Standard OAA payload, standard entity types. |
| Cross-resource queries | "All resources of type business_rule with security_relevance: active_external" is a straightforward filter within the application. |
5.4 Weaknesses
| Weakness | Severity | Explanation |
|---|---|---|
| No container semantics | HIGH | A resource is a "thing acted upon," not a "thing that acts." A Business Rule is both — it is a configured artifact (resource) AND an autonomous agent (identity). Modeling it only as a resource loses the "it executes" dimension. |
| Permission binding is backward | HIGH | In OAA, an identity has permissions on a resource. But in an execution chain, the automation has permissions — the BR "executes" the SI, the SI "calls" the RESTMessage. These are not identity-to-resource permission bindings; they are component-to-component invocations. Forcing them into OAA's binding model requires an awkward indirection through the run-as user. |
| Cross-system chain invisible | HIGH | The ServiceNow application contains the BR, SI, and RESTMessage resources. But the chain continues to Entra ID (OAuthProfile → SP → Graph API). In OAA, the Entra side would be a separate application. There is no mechanism to express "this resource in App A connects to this identity in App B" within the OAA payload. IdP identity correlation is the closest mechanism, but it operates on users, not on resources. |
| Chain identity missing | MEDIUM | Resources don't have stable chain identifiers. The "AzureGraphRouter chain" is not a named, addressable concept — it's just a resource with sub-resources. You cannot say "show me the chain" without knowing the root resource and traversing its sub-resources. |
| Trigger/target ambiguity | MEDIUM | The incident table is both a resource that triggers the automation AND a resource that might be modified by it. OAA permissions can express "identity reads incident" and "identity writes incident" but cannot express "incident INSERT event triggers this automation." The trigger is an event, not a permission. |
| Flat permission model | MEDIUM | The execution chain involves multi-hop delegation: BR → SI → RESTMessage → OAuth → SP → Entra Role → Graph API. Modeling this as "user has execute permission on BR resource" collapses a 6-hop chain into a single permission binding, losing all intermediate context. |
5.5 Verdict: OAA-Idiomatic but Semantically Incomplete
The automation-as-resource model follows established OAA patterns and avoids the proliferation problem. But it fundamentally cannot express execution flow, cross-system chains, or the dual nature of automations (both resource and agent). It is a valid OAA export format for ServiceNow's automation inventory but is insufficient as a primary data model for execution exposure analysis.
6. Approach C: Hybrid Model
6.1 Conceptual Model
This approach combines insights from both A and B:
- ServiceNow remains one Application (matching OAA patterns)
- Automations are Resources within ServiceNow (with sub-resource nesting for chain components)
- Execution chains are modeled as a special resource type with a custom property
chain_type: "execution_chain"that groups related automation resources - Cross-system linking happens via IdP identity correlation (SP in ServiceNow linked to SP in Entra via shared identity)
- A separate "Execution Chains" virtual application (application_type:
execution_chain_registry) provides the chain-as-container view
The hybrid model recognizes that OAA serves two different consumers:
- Veza consumes the standard ServiceNow and Entra applications with their resources and permissions
- SecurityV0 consumes the same data plus the execution chain registry for temporal tracking and exposure analysis
# === Standard OAA: ServiceNow Application ===
sn_app = CustomApplication(
name="ServiceNow - corp.service-now.com",
application_type="ServiceNow"
)
# Custom property definitions
sn_app.property_definitions.define_resource_property(
"business_rule", "chain_id", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "chain_role", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "execution_mode", OAAPropertyType.STRING
)
sn_app.property_definitions.define_resource_property(
"business_rule", "security_relevance", OAAPropertyType.STRING
)
# Tables as resources
incident = sn_app.add_resource("incident", resource_type="table")
# Automation artifacts as resources (with chain membership metadata)
br = sn_app.add_resource("AzureGraphRouter", resource_type="business_rule")
br.set_property("chain_id", "chain-azuregraphrouter-abc123")
br.set_property("chain_role", "entry_point")
br.set_property("execution_mode", "autonomous")
br.set_property("security_relevance", "active_external")
si = br.add_sub_resource("AzureGraphHelper", resource_type="script_include")
si.set_property("chain_id", "chain-azuregraphrouter-abc123")
si.set_property("chain_role", "executor")
rest_msg = si.add_sub_resource("AzureGraphAPI", resource_type="rest_message")
rest_msg.set_property("chain_id", "chain-azuregraphrouter-abc123")
rest_msg.set_property("chain_role", "outbound_connector")
# Identity: the user the chain runs as
sn_user = sn_app.add_local_user(
"sn-integration-user",
identities="sp-graph-router@tenant.onmicrosoft.com"
)
# Permissions
sn_app.add_custom_permission("execute_automation", [OAAPermission.NonData])
sn_app.add_custom_permission("read_table", [OAAPermission.DataRead])
sn_app.add_custom_permission("write_table", [OAAPermission.DataWrite])
sn_user.add_permission("execute_automation", resources=[br])
sn_user.add_permission("read_table", resources=[incident])
# === Standard OAA: Entra ID Application ===
entra_app = CustomApplication(
name="Entra ID - tenant.onmicrosoft.com",
application_type="EntraID"
)
graph_api = entra_app.add_resource("Microsoft Graph API", resource_type="api")
sp_user = entra_app.add_local_user(
"sp-graph-router",
identities="sp-graph-router@tenant.onmicrosoft.com" # Same IdP identity
)
entra_app.add_custom_permission("graph_readwrite", [
OAAPermission.DataRead,
OAAPermission.DataWrite
])
entra_app.add_local_role(
"Application.ReadWrite.All",
permissions=["graph_readwrite"]
)
sp_user.add_role("Application.ReadWrite.All", resources=[graph_api])
# === SecurityV0 Extension: Execution Chain Registry ===
chain_registry = CustomApplication(
name="SecurityV0 Execution Chains - corp.service-now.com",
application_type="execution_chain_registry"
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "anchor_entity_source_id", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "trigger_table", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "trigger_type", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "destination", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "egress_category", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "ownership_status", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "composition_hash", OAAPropertyType.STRING
)
chain_registry.property_definitions.define_resource_property(
"execution_chain", "blast_radius_domains", OAAPropertyType.STRING_LIST
)
# The chain as a resource with components as sub-resources
chain = chain_registry.add_resource(
"AzureGraphRouter Incident Routing",
resource_type="execution_chain"
)
chain.set_property("anchor_entity_source_id", "br-sys-id-abc123")
chain.set_property("trigger_table", "incident")
chain.set_property("trigger_type", "event")
chain.set_property("destination", "graph.microsoft.com")
chain.set_property("egress_category", "external")
chain.set_property("ownership_status", "orphaned")
chain.set_property("composition_hash", "sha256:abc...")
chain.set_property("blast_radius_domains", ["identity_platform"])
# Chain components as sub-resources (reference, not duplication)
chain.add_sub_resource("AzureGraphRouter (BR)", resource_type="entry_point")
chain.add_sub_resource("AzureGraphHelper (SI)", resource_type="executor")
chain.add_sub_resource("AzureGraphAPI (REST)", resource_type="outbound_connector")
chain.add_sub_resource("EntraOAuth (OAuth)", resource_type="auth_credential")
chain.add_sub_resource("sp-graph-router (SP)", resource_type="destination_identity")
# The identity that the chain authenticates as
chain_sp = chain_registry.add_local_user(
"sp-graph-router",
identities="sp-graph-router@tenant.onmicrosoft.com"
)
chain_registry.add_custom_permission(
"executes_as", [OAAPermission.NonData]
)
chain_sp.add_permission("executes_as", resources=[chain])
6.2 OAA JSON Output (Chain Registry Only)
{
"applications": [
{
"name": "SecurityV0 Execution Chains - corp.service-now.com",
"application_type": "execution_chain_registry",
"local_users": [
{
"name": "sp-graph-router",
"unique_id": "sp-graph-router-appid-abc123",
"identities": ["sp-graph-router@tenant.onmicrosoft.com"],
"is_active": true
}
],
"resources": [
{
"name": "AzureGraphRouter Incident Routing",
"resource_type": "execution_chain",
"custom_properties": {
"anchor_entity_source_id": "br-sys-id-abc123",
"trigger_table": "incident",
"trigger_type": "event",
"destination": "graph.microsoft.com",
"egress_category": "external",
"ownership_status": "orphaned",
"composition_hash": "sha256:abc...",
"blast_radius_domains": ["identity_platform"]
},
"sub_resources": [
{"name": "AzureGraphRouter (BR)", "resource_type": "entry_point"},
{"name": "AzureGraphHelper (SI)", "resource_type": "executor"},
{"name": "AzureGraphAPI (REST)", "resource_type": "outbound_connector"},
{"name": "EntraOAuth (OAuth)", "resource_type": "auth_credential"},
{"name": "sp-graph-router (SP)", "resource_type": "destination_identity"}
]
}
]
}
],
"permissions": [
{"name": "executes_as", "permission_type": ["NonData"]}
],
"identity_to_permissions": [
{
"identity": "sp-graph-router-appid-abc123",
"identity_type": "local_user",
"resource_permissions": [
{
"resource": "AzureGraphRouter Incident Routing",
"permission": "executes_as"
}
]
}
]
}
6.3 Strengths
| Strength | Explanation |
|---|---|
| Best of both worlds | ServiceNow and Entra are standard OAA applications (Veza-consumable). The chain registry is a SecurityV0-specific extension (SecurityV0-consumable). |
| No resource duplication | Tables and identities live in their source applications. The chain registry references them symbolically via sub-resource names and IdP identity correlation. |
| Chain-level queries | "List all execution chains" = list all resources in the chain registry application. "Show chain details" = read chain resource with sub-resources. "Filter by ownership status" = custom property filter. |
| Cross-system visibility via IdP | sp-graph-router appears as a local user in both the chain registry and the Entra application, linked by the shared IdP identity sp-graph-router@tenant.onmicrosoft.com. Veza can correlate them. |
| Custom properties carry chain metadata | composition_hash, ownership_status, blast_radius_domains are all custom properties on the chain resource. This is exactly how OAA connectors extend the model. |
| Standard OAA compliance for source apps | ServiceNow and Entra OAA payloads are completely standard. The chain registry is additive — it doesn't modify or conflict with the source application payloads. |
| Graceful degradation | If Veza ignores the chain registry (unknown application type), the source applications still provide complete authorization data. The chain registry is enhancement, not replacement. |
6.4 Weaknesses
| Weakness | Severity | Explanation |
|---|---|---|
| Three OAA payloads required | LOW | ServiceNow app, Entra app, and chain registry must all be pushed. This is operationally straightforward (three push_application calls) but slightly more complex than a single push. |
| Sub-resources in chain are references, not live | LOW | The chain's sub-resources (AzureGraphRouter (BR), etc.) are symbolic references, not the actual automation resources from the ServiceNow application. They share names but are distinct OAA objects. Veza cannot natively link "AzureGraphRouter" in the chain registry to "AzureGraphRouter" in the ServiceNow app. |
| Still no execution flow | MEDIUM | OAA's resource/sub-resource hierarchy models containment, not invocation sequence. The JSON doesn't express "BR calls SI which calls RESTMessage." The order must be inferred from the sub-resource nesting or stored as a custom property. |
| Still no temporal model | HIGH | OAA is snapshot-based. The chain registry payload captures the current state. It cannot express "this chain gained access to PII on Day 30" or "the composition changed on Day 45." This is a fundamental OAA limitation, not specific to this approach. |
| Chain registry is novel | MEDIUM | application_type: "execution_chain_registry" is a SecurityV0 invention. It is valid OAA (any string is a valid application_type) but it is not a recognized type. Veza would display it but might not have optimized UI for it. |
6.5 Verdict: Recommended for OAA Export Layer
The hybrid model is the most architecturally sound approach for OAA compatibility. It preserves standard OAA semantics for source applications while adding a SecurityV0-specific chain registry that provides listability, chain-level metadata, and cross-system linkage. The chain registry maps naturally to Round 3's execution_chains collection — it is the OAA export representation of the same concept.
7. The Trigger/Data Question
The founder asked a critical question: when an incident is created (triggering the automation), is the incident table a "Resource" being "read"? When the automation updates a table, is that a "Resource" being "written"?
7.1 OAA's Permission Model vs. Execution Triggers
OAA's permission model is authorization-centric: Identity has Permission on Resource. This answers "who can do what to what." It does NOT model:
- Event triggers — "when X happens on Resource, Automation fires"
- Execution causality — "Automation fires BECAUSE of an event on Resource"
- Temporal ordering — "first the incident is created, then the BR fires, then the SI runs"
When an incident is created (triggering the automation), two things happen:
-
The incident creation is a DataCreate operation by the creating user — this is a standard OAA permission: User → DataCreate → incident table.
-
The incident creation event triggers the Business Rule — this is NOT an OAA permission. This is an execution trigger, a concept OAA does not model. There is no OAA permission type for "this resource's events cause this automation to execute."
7.2 Mapping Triggers to OAA Permissions
The best we can do within OAA:
| Execution Concept | OAA Approximation | Fidelity |
|---|---|---|
| BR triggers on incident insert | execute (NonData) permission on BR resource, with custom property trigger_table: "incident" | LOW — loses causality |
| BR reads incident data during execution | DataRead permission on incident resource for the run-as user | HIGH — correct semantic |
| SI calls RESTMessage | execute (NonData) permission on RESTMessage resource, or sub-resource containment | LOW — containment is not invocation |
| RESTMessage authenticates via OAuth | Not expressible in OAA (credential chain) | NONE |
| SP reads/writes Graph API | DataRead/DataWrite permission on Graph API resource | HIGH — correct semantic |
| Automation updates incident table | DataWrite permission on incident resource for the run-as user | HIGH — correct semantic |
Key finding: OAA can model the endpoints of an execution chain (what data is read, what data is written) with high fidelity. It cannot model the intermediate execution flow (trigger events, invocation chains, credential delegation) at all. This is not a limitation that can be overcome with custom properties — it is a structural limitation of OAA's authorization-only data model.
7.3 The Incident Table as Both Trigger and Target
The incident table in the AzureGraphRouter example is:
- Trigger resource: An INSERT event on this table causes the automation to fire
- Read resource: The BR reads the new incident record during execution
- Potentially a write resource: The automation might update the incident (e.g., set assignment group)
In OAA, all three are modeled as permissions on the same resource:
{
"resource": "incident",
"permissions": ["DataRead", "DataWrite"]
}
The trigger dimension is completely lost. OAA sees "this user can read and write incidents." It cannot see "incident insert events CAUSE this automation to execute." This is a critical gap for SecurityV0's exposure analysis — the trigger is what makes the automation autonomous (nobody presses a button; the event fires it).
7.4 Recommendation for Trigger Modeling
Since OAA cannot model triggers natively, the platform should:
-
In OAA export: Use
DataReadfor trigger resources andDataWritefor modified resources. Add a custom propertytrigger: trueon the automation resource to indicate it is event-driven, andtrigger_table: "incident",trigger_condition: "on insert"as custom properties. -
In the internal data model: Use the existing
TRIGGERS_ONrelationship (Identity → Resource) withtrigger_typeandscheduleproperties. This is already in the SecurityV0 data model and correctly captures the semantics that OAA cannot. -
Do NOT attempt to force trigger semantics into OAA permission types. Creating a custom permission called "trigger" with
NonDatatype is misleading — it suggests the identity "does something non-data" to the trigger table, when actually the table "does something" to the automation (fires it).
8. OAA Extension Strategy
8.1 The Goal
Define the minimal, cleanest way to extend OAA concepts to model execution flows WITHOUT breaking OAA compatibility.
8.2 Strategy: Layered Extension
The extension strategy has three layers:
Layer 1: Standard OAA (Veza-compatible)
Source applications (ServiceNow, Entra ID, GitHub) produce standard OAA payloads following existing connector patterns. Automations appear as resources with custom properties. Identities appear as local users with IdP correlation. Permissions map to canonical types.
No changes to OAA. Veza can ingest this today.
Layer 2: OAA Extension (SecurityV0-specific application_type)
A chain registry application with application_type: "execution_chain_registry" provides the chain-as-resource view. Chain resources carry custom properties for chain metadata. Sub-resources represent chain components. Local users represent the identities chains authenticate as, linked via IdP identity correlation.
Uses standard OAA mechanisms (custom application_type, custom properties, sub-resources). Veza can ingest this today but may not have optimized UI.
Layer 3: SecurityV0-internal (beyond OAA)
The execution_chains collection, temporal tracking, execution evidence, credential chains, trigger/flow semantics, and finding evaluation all live in the SecurityV0-internal data model. These concepts cannot be expressed in OAA.
The OAA export (Layers 1 + 2) is a projection of the SecurityV0 internal model, not the model itself.
8.3 Custom Application Types
| Application Type | Purpose | Standard OAA? |
|---|---|---|
ServiceNow | ServiceNow instance authorization | Yes (follows Jira/GitHub pattern) |
EntraID | Entra ID tenant authorization | Yes |
GitHub | GitHub org authorization | Yes (existing community connector) |
execution_chain_registry | SecurityV0 chain metadata | Extension (valid OAA, novel type) |
8.4 Custom Permission Types
| Permission | Canonical Mapping | Purpose |
|---|---|---|
execute_automation | NonData | Identity can execute an automation artifact |
trigger_read | DataRead | Identity reads data from a trigger source |
outbound_api_call | NonData | Identity makes external API calls |
read_table | DataRead | Identity reads ServiceNow table data |
write_table | DataWrite | Identity writes ServiceNow table data |
admin_table | ResourceAdmin | Identity administers table configuration |
executes_as | NonData | Identity participates in execution chain (chain registry) |
All map to standard OAA canonical permission types. No new permission types needed.
8.5 Custom Properties for Automation Resources
| Property | Type | On Resource Type | Purpose |
|---|---|---|---|
chain_id | STRING | business_rule, flow, scheduled_job | Stable chain identifier |
chain_role | STRING | all automation types | Role in chain (entry_point, executor, outbound_connector, auth_credential, destination_identity) |
execution_mode | STRING | business_rule, flow, scheduled_job | autonomous, operator_assisted, human_triggered |
security_relevance | STRING | business_rule, flow, scheduled_job | active_external, dormant_authority, internal_inventory |
trigger_table | STRING | business_rule | Which table triggers the automation |
trigger_condition | STRING | business_rule | Trigger condition (on insert, on update, etc.) |
egress_category | STRING | all automation types | internal, external, none |
destination_endpoint | STRING | rest_message | Target URL for outbound calls |
auth_type | STRING | rest_message, oauth_profile | Authentication mechanism |
composition_hash | STRING | execution_chain | SHA256 of chain composition |
ownership_status | STRING | execution_chain | owned, degraded, orphaned |
blast_radius_domains | STRING_LIST | execution_chain | Affected business domains |
All use standard OAA property types (STRING, BOOLEAN, STRING_LIST). No new property types needed.
8.6 What Cannot Be Expressed in OAA
Even with the extension strategy, the following SecurityV0 concepts have no OAA representation:
| Concept | Why OAA Cannot Express It |
|---|---|
| Execution flow order | OAA models static authorization, not invocation sequences |
| Credential chains | OAA has no credential entity type — auth is implicit in identity correlation |
| AUTHENTICATES_TO relationship | OAA links identities via IdP correlation, not explicit auth chain edges |
| Temporal state | OAA is snapshot-based — no history, no drift, no temporal comparison |
| Execution evidence | OAA has no concept of "proof this identity exercised this permission" |
| Findings | OAA is descriptive (what exists), not evaluative (what is wrong) |
| Ownership hierarchy | OAA has groups but not ownership with decay semantics |
| Evidence completeness | OAA has no concept of "what we could not observe" |
This confirms that OAA is an export format, not a replacement for SecurityV0's internal data model.
9. Compatibility Matrix
9.1 Approach Comparison
| Criterion | A: Automation-as-Application | B: Automation-as-Resource | C: Hybrid Model |
|---|---|---|---|
| OAA standard compliance | Valid but non-idiomatic | Fully idiomatic | Standard source apps + valid extension |
| Veza consumable? | Yes, but UI problems at scale | Yes, clean display | Yes, chain registry may lack optimized UI |
| Semantic correctness | Overpromotes automations to "systems" | Underpromotes automations to "data objects" | Correctly separates roles: source apps for auth, registry for chains |
| Resource duplication | HIGH — shared resources duplicated | NONE | NONE |
| Identity duplication | HIGH — shared identities duplicated | NONE | LOW — IdP correlation links them |
| Cross-system chains | POOR — no cross-app linking | POOR — different apps, no linking | GOOD — IdP identity correlation |
| Chain listability | YES — list applications of type execution_chain | PARTIAL — filter resources by type | YES — list resources in chain registry |
| Chain detail view | YES — application detail page | PARTIAL — resource + sub-resources | YES — chain resource + sub-resources |
| Neo4j portability | GOOD — apps become nodes | GOOD — resources become nodes | GOOD — all entities become nodes |
| Temporal diff | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA (use execution_chains collection) |
| Query: all chains for SP | POOR — scan all chain-apps | POOR — scan all resources | GOOD — IdP identity correlation on chain registry |
| Query: all chains touching incident table | POOR — scan all chain-apps | GOOD — filter by trigger_table property | GOOD — custom property filter on chain resources |
| Effort to implement | HIGH — custom connector + refactoring | LOW — add to existing connector | MEDIUM — existing connectors + chain registry connector |
| Rollback risk | HIGH — must deprecate application type | LOW — remove resources | LOW — drop chain registry, source apps unaffected |
| Round 3 compatibility | PARTIAL — replaces execution_chains with OAA apps | POOR — no chain concept | HIGH — chain registry IS the OAA projection of execution_chains |
9.2 CISO Use Case Evaluation
The CISO's primary questions from Round 3:
| Question | A: App | B: Resource | C: Hybrid |
|---|---|---|---|
| "List all automations" | List apps of type execution_chain | Filter resources by automation types | List chain registry resources |
| "Show this automation's chain" | App detail → resources + sub-resources | Resource → sub-resources | Chain resource → sub-resources |
| "What SP does this chain use?" | App → local users | Resource → find identity with execute permission | Chain → local users (linked via IdP) |
| "What data does this chain touch?" | App → resources (trigger + target) | Resource custom properties → linked resources | Chain custom properties (trigger_table, destination) |
| "Who owns this chain?" | Custom property (not native to OAA) | Custom property | Custom property (ownership_status) |
| "Diff chain over time" | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA — use execution_chains collection |
| "Show evidence of execution" | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA | NOT POSSIBLE in OAA — use execution_evidence collection |
9.3 Neo4j Portability Assessment
All three approaches map to Neo4j, but with different node/edge patterns:
Approach A (App per chain):
(:Application {type: "execution_chain"})
-[:HAS_USER]->(:LocalUser {name: "sp-graph-router"})
-[:HAS_PERMISSION]->(:Permission {name: "execute"})
-[:ON_RESOURCE]->(:Resource {type: "business_rule"})
-[:HAS_SUB_RESOURCE]->(:Resource {type: "script_include"})
- Produces many disconnected subgraphs (one per chain)
- Cross-chain analysis requires UNION queries across application subgraphs
Approach B (Resources within app):
(:Application {name: "ServiceNow"})
-[:HAS_RESOURCE]->(:Resource {type: "business_rule"})
-[:HAS_SUB_RESOURCE]->(:Resource {type: "script_include"})
(:Application {name: "ServiceNow"})
-[:HAS_USER]->(:LocalUser {name: "sn-integration-user"})
-[:HAS_PERMISSION]->(:Permission {name: "execute"})
-[:ON_RESOURCE]->(:Resource {type: "business_rule"})
- Clean single-application subgraph
- But chains are not directly addressable as nodes
Approach C (Hybrid):
// Source apps — standard
(:Application {name: "ServiceNow"})
-[:HAS_RESOURCE]->(:Resource {type: "business_rule", chain_id: "abc"})
(:Application {name: "Entra ID"})
-[:HAS_USER]->(:LocalUser {name: "sp-graph-router"})
// Chain registry — SecurityV0 extension
(:Application {type: "execution_chain_registry"})
-[:HAS_RESOURCE]->(:Resource {type: "execution_chain", name: "AzureGraphRouter"})
-[:HAS_SUB_RESOURCE]->(:Resource {type: "entry_point"})
-[:HAS_USER]->(:LocalUser {name: "sp-graph-router"})
-[:CORRELATES_TO]->(:IdPIdentity {email: "sp-graph-router@tenant"})
- Chains are directly addressable nodes
- Cross-chain analysis:
MATCH (c:Resource {type: "execution_chain"})-[:HAS_USER]->(u:LocalUser)— find all chains for a given SP - IdP correlation enables cross-application linking
Verdict: Approach C provides the best Neo4j portability because chains are first-class nodes (directly addressable, filterable, traversable) while source applications maintain standard structure.
10. Concrete Mapping: AzureGraphRouter
The AzureGraphRouter execution chain:
BusinessRule (AzureGraphRouter)
→ triggers_on: incident table (on insert)
→ calls: ScriptInclude (AzureGraphHelper)
→ calls: RESTMessage (AzureGraphAPI)
→ authenticates_via: OAuthProfile (EntraOAuth)
→ authenticates_as: ServicePrincipal (sp-graph-router)
→ has_role: Application.ReadWrite.All (Entra)
→ applies_to: Microsoft Graph API
10.1 Under Approach A (Automation as Application)
OAA Payload: 1 Application
{
"applications": [
{
"name": "AzureGraphRouter Incident Routing",
"application_type": "execution_chain",
"description": "Autonomous execution chain: triggers on incident insert, routes to Microsoft Graph API via OAuth client credentials",
"local_users": [
{
"name": "sp-graph-router",
"unique_id": "appid-a1b2c3d4",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true,
"custom_properties": {
"identity_type": "service_principal",
"source_system": "entra_id",
"runs_as_type": "configured"
}
},
{
"name": "sn-integration-user",
"unique_id": "sysid-user-xyz",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true,
"custom_properties": {
"identity_type": "integration_user",
"source_system": "servicenow"
}
}
],
"local_groups": [],
"local_roles": [
{
"name": "Application.ReadWrite.All",
"permissions": ["graph_full_access"]
}
],
"resources": [
{
"name": "incident",
"resource_type": "trigger_table",
"description": "ServiceNow incident table — INSERT triggers this chain",
"custom_properties": {
"source_system": "servicenow",
"business_domain": "it_ops",
"sensitivity": "internal",
"trigger_role": "trigger"
}
},
{
"name": "Microsoft Graph API",
"resource_type": "target_api",
"description": "Entra ID Graph API — chain destination",
"custom_properties": {
"source_system": "entra_id",
"business_domain": "identity_platform",
"sensitivity": "confidential",
"trigger_role": "destination"
}
},
{
"name": "AzureGraphRouter",
"resource_type": "business_rule",
"description": "Entry point Business Rule",
"custom_properties": {
"sys_id": "br-sys-id-abc123",
"source_system": "servicenow",
"chain_role": "entry_point",
"trigger_table": "incident",
"trigger_condition": "on insert",
"execution_mode": "autonomous"
},
"sub_resources": [
{
"name": "AzureGraphHelper",
"resource_type": "script_include",
"custom_properties": {
"sys_id": "si-sys-id-def456",
"chain_role": "executor"
},
"sub_resources": [
{
"name": "AzureGraphAPI",
"resource_type": "rest_message",
"custom_properties": {
"sys_id": "rm-sys-id-ghi789",
"chain_role": "outbound_connector",
"endpoint": "https://graph.microsoft.com",
"auth_type": "oauth2_client_credentials"
}
}
]
}
]
}
]
}
],
"permissions": [
{"name": "graph_full_access", "permission_type": ["DataRead", "DataWrite", "MetadataRead"]},
{"name": "execute_chain", "permission_type": ["NonData"]},
{"name": "read_trigger", "permission_type": ["DataRead"]}
],
"identity_to_permissions": [
{
"identity": "appid-a1b2c3d4",
"identity_type": "local_user",
"application_permissions": [
{"application": "AzureGraphRouter Incident Routing", "permission": "execute_chain", "resources": ["AzureGraphRouter"]}
],
"resource_permissions": [
{"resource": "incident", "permission": "read_trigger"},
{"resource": "Microsoft Graph API", "permission": "graph_full_access"}
]
}
]
}
Entity count: 1 application, 2 local users, 1 role, 3 resources (1 with nested sub-resources), 3 permissions, 1 permission binding.
Problem at scale: 50 chains = 50 applications, each with duplicated resources and identities.
10.2 Under Approach B (Automation as Resource)
OAA Payload: 2 Applications (ServiceNow + Entra)
[
{
"applications": [
{
"name": "ServiceNow - corp.service-now.com",
"application_type": "ServiceNow",
"local_users": [
{
"name": "sn-integration-user",
"unique_id": "sysid-user-xyz",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true
}
],
"local_roles": [
{"name": "itil", "permissions": ["read_table", "write_table"]},
{"name": "hr_agent_workspace", "permissions": ["read_table", "write_table"]}
],
"resources": [
{
"name": "incident",
"resource_type": "table",
"custom_properties": {
"business_domain": "it_ops",
"sensitivity": "internal"
}
},
{
"name": "AzureGraphRouter",
"resource_type": "business_rule",
"custom_properties": {
"sys_id": "br-sys-id-abc123",
"trigger_table": "incident",
"trigger_condition": "on insert",
"execution_mode": "autonomous",
"security_relevance": "active_external",
"egress_category": "external"
},
"sub_resources": [
{
"name": "AzureGraphHelper",
"resource_type": "script_include",
"sub_resources": [
{
"name": "AzureGraphAPI",
"resource_type": "rest_message",
"custom_properties": {
"endpoint": "https://graph.microsoft.com",
"auth_type": "oauth2_client_credentials"
}
}
]
}
]
}
]
}
],
"permissions": [
{"name": "read_table", "permission_type": ["DataRead"]},
{"name": "write_table", "permission_type": ["DataWrite"]},
{"name": "execute_automation", "permission_type": ["NonData"]}
],
"identity_to_permissions": [
{
"identity": "sysid-user-xyz",
"identity_type": "local_user",
"resource_permissions": [
{"resource": "incident", "permission": "read_table"},
{"resource": "AzureGraphRouter", "permission": "execute_automation"}
]
}
]
},
{
"applications": [
{
"name": "Entra ID - 72f988bf.onmicrosoft.com",
"application_type": "EntraID",
"local_users": [
{
"name": "sp-graph-router",
"unique_id": "appid-a1b2c3d4",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true
}
],
"local_roles": [
{"name": "Application.ReadWrite.All", "permissions": ["graph_readwrite"]}
],
"resources": [
{
"name": "Microsoft Graph API",
"resource_type": "api"
}
]
}
],
"permissions": [
{"name": "graph_readwrite", "permission_type": ["DataRead", "DataWrite"]}
],
"identity_to_permissions": [
{
"identity": "appid-a1b2c3d4",
"identity_type": "local_user",
"application_permissions": [
{"application": "Entra ID - 72f988bf.onmicrosoft.com", "role": "Application.ReadWrite.All"}
]
}
]
}
]
Entity count: 2 applications, 2 local users (linked by IdP identity), 3 roles, 3 resources, 4 permissions.
Problem: No concept of "the AzureGraphRouter chain." The chain exists only implicitly as a resource with sub-resources in the ServiceNow app and a separate identity in the Entra app. No way to list "all chains" or "all chains using sp-graph-router."
10.3 Under Approach C (Hybrid Model)
OAA Payload: 3 Applications (ServiceNow + Entra + Chain Registry)
ServiceNow and Entra payloads are identical to Approach B above. The chain registry adds:
{
"applications": [
{
"name": "SecurityV0 Execution Chains - corp.service-now.com",
"application_type": "execution_chain_registry",
"description": "SecurityV0 execution chain metadata for ServiceNow corp.service-now.com",
"local_users": [
{
"name": "sp-graph-router",
"unique_id": "appid-a1b2c3d4",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true,
"custom_properties": {
"identity_type": "service_principal",
"source_system": "entra_id",
"participates_in_chains": 1
}
},
{
"name": "sn-integration-user",
"unique_id": "sysid-user-xyz",
"identities": ["sp-graph-router@72f988bf.onmicrosoft.com"],
"is_active": true,
"custom_properties": {
"identity_type": "integration_user",
"source_system": "servicenow"
}
}
],
"resources": [
{
"name": "AzureGraphRouter Incident Routing",
"resource_type": "execution_chain",
"description": "Autonomous chain: incident insert → AzureGraphRouter BR → AzureGraphHelper SI → AzureGraphAPI REST → Entra OAuth → sp-graph-router SP → Microsoft Graph API",
"custom_properties": {
"chain_id": "chain-sha256-abc123",
"anchor_entity_source_id": "br-sys-id-abc123",
"anchor_entity_type": "business_rule",
"trigger_table": "incident",
"trigger_type": "event",
"trigger_condition": "on insert",
"destination_endpoint": "https://graph.microsoft.com",
"egress_category": "external",
"execution_mode": "autonomous",
"security_relevance": "active_external",
"ownership_status": "orphaned",
"composition_hash": "sha256:e5f6a7b8c9d0...",
"blast_radius_domains": ["identity_platform"],
"entity_count": 6,
"cross_system": true,
"source_systems": ["servicenow", "entra_id"],
"first_detected_at": "2026-02-12T00:00:00Z",
"last_seen_at": "2026-02-13T15:00:00Z"
},
"sub_resources": [
{
"name": "1. AzureGraphRouter (BusinessRule)",
"resource_type": "entry_point",
"description": "Triggers on incident table insert",
"custom_properties": {
"source_entity_id": "br-sys-id-abc123",
"source_system": "servicenow",
"chain_position": 1
}
},
{
"name": "2. AzureGraphHelper (ScriptInclude)",
"resource_type": "executor",
"description": "Processes incident data and prepares Graph API payload",
"custom_properties": {
"source_entity_id": "si-sys-id-def456",
"source_system": "servicenow",
"chain_position": 2
}
},
{
"name": "3. AzureGraphAPI (RESTMessage)",
"resource_type": "outbound_connector",
"description": "Calls graph.microsoft.com via REST",
"custom_properties": {
"source_entity_id": "rm-sys-id-ghi789",
"source_system": "servicenow",
"chain_position": 3,
"endpoint": "https://graph.microsoft.com"
}
},
{
"name": "4. EntraOAuth (OAuthProfile)",
"resource_type": "auth_credential",
"description": "OAuth2 client_credentials profile for Entra authentication",
"custom_properties": {
"source_entity_id": "oauth-sys-id-jkl012",
"source_system": "servicenow",
"chain_position": 4,
"auth_type": "oauth2_client_credentials"
}
},
{
"name": "5. sp-graph-router (ServicePrincipal)",
"resource_type": "destination_identity",
"description": "Entra ID Service Principal with Application.ReadWrite.All",
"custom_properties": {
"source_entity_id": "appid-a1b2c3d4",
"source_system": "entra_id",
"chain_position": 5,
"roles": ["Application.ReadWrite.All"]
}
}
]
}
]
}
],
"permissions": [
{"name": "executes_as", "permission_type": ["NonData"]},
{"name": "runs_in_chain", "permission_type": ["NonData"]}
],
"identity_to_permissions": [
{
"identity": "appid-a1b2c3d4",
"identity_type": "local_user",
"resource_permissions": [
{
"resource": "AzureGraphRouter Incident Routing",
"permission": "executes_as"
}
]
},
{
"identity": "sysid-user-xyz",
"identity_type": "local_user",
"resource_permissions": [
{
"resource": "AzureGraphRouter Incident Routing",
"permission": "runs_in_chain"
}
]
}
]
}
Entity count across all three applications:
- 3 applications (ServiceNow, Entra, Chain Registry)
- 4 local users total (2 in chain registry, 1 in ServiceNow, 1 in Entra — linked by IdP)
- 3+ roles (in source apps)
- 6 resources (2 in ServiceNow, 1 in Entra, 1 chain + 5 sub-resources in registry)
- 6+ permissions (across all apps)
Scale behavior: 50 chains = 50 resources in the chain registry (NOT 50 applications). The ServiceNow and Entra applications remain unchanged regardless of chain count. This is the same scaling model as GitHub repos (50 repos = 50 resources in one app).
11. Reconciliation with Round 3
11.1 How the Hybrid OAA Model Maps to execution_chains
Round 3 recommended an execution_chains collection with this schema:
{
_id: "chain-uuid",
tenant_id: "...",
name: "AzureGraphRouter Incident Routing",
anchor_entity_id: "uuid-of-business-rule",
entity_refs: [
{ entity_id: "uuid-br", role: "entry_point" },
{ entity_id: "uuid-si", role: "executor" },
{ entity_id: "uuid-rest", role: "outbound_target" },
{ entity_id: "uuid-oauth", role: "auth_credential" },
{ entity_id: "uuid-sp", role: "destination_identity" }
],
summary: {
trigger: "incident table insert",
destination: "graph.microsoft.com",
egress_category: "external",
blast_radius_domains: ["identity_platform"],
ownership_status: "orphaned",
total_roles: 4,
max_sensitivity: "confidential"
},
composition_hash: "sha256:abc...",
first_detected_at: ISODate("2026-02-12"),
last_seen_at: ISODate("2026-02-13"),
sync_version: 42
}
The mapping to the OAA chain registry is direct:
| execution_chains field | OAA chain registry equivalent |
|---|---|
name | Resource name |
anchor_entity_id | Custom property: anchor_entity_source_id |
entity_refs | Sub-resources (name, resource_type=role) |
entity_refs[].role | Sub-resource resource_type (entry_point, executor, etc.) |
summary.trigger | Custom property: trigger_table + trigger_condition |
summary.destination | Custom property: destination_endpoint |
summary.egress_category | Custom property: egress_category |
summary.blast_radius_domains | Custom property: blast_radius_domains |
summary.ownership_status | Custom property: ownership_status |
composition_hash | Custom property: composition_hash |
first_detected_at | Custom property: first_detected_at |
last_seen_at | Custom property: last_seen_at |
11.2 What execution_chains Provides That OAA Cannot
| Capability | execution_chains | OAA chain registry |
|---|---|---|
| Stable chain ID | _id (UUID) | Resource name (string) |
| Temporal versioning | execution_chain_versions collection | NOT POSSIBLE — snapshot only |
| Chain-level events | chain_created, chain_blast_radius_changed | NOT POSSIBLE |
| Chain-level findings | Finding with entity_type: execution_chain | NOT POSSIBLE |
| Chain diff over time | Compare two chain versions | NOT POSSIBLE |
| Entity references (live) | entity_refs with UUIDs linking to entities collection | Sub-resources (symbolic names, not live references) |
| Composition fingerprint change detection | Compare composition_hash across sync versions | Custom property comparison (manual, no built-in diff) |
| Integration with sync pipeline | Built into sync-ingestion.ts after path materialization | Separate OAA push step after sync |
11.3 Recommended Architecture
┌─────────────────────┐
│ OAA Export Layer │
│ │
┌──────────────────┐ │ ┌───────────────┐ │
│ sv0-connectors │──discover──▶ │ │ ServiceNow │ │──push──▶ Veza
│ (Python) │ │ │ OAA App │ │
└──────────────────┘ │ └───────────────┘ │
│ │ │
NormalizedGraph │ ┌───────────────┐ │
│ │ │ Entra ID │ │──push──▶ Veza
▼ │ │ OAA App │ │
┌──────────────────┐ │ └───────────────┘ │
│ sv0-platform │ │ │
│ (TypeScript) │ │ ┌───────────────┐ │
│ │──oaa-export──▶│ │ Chain Registry│ │──push──▶ Veza
│ ┌────────────┐ │ │ │ OAA App │ │
│ │ entities │ │ │ └───────────────┘ │
│ │ collection │ │ └─────────────────────┘
│ └────────────┘ │
│ ┌────────────┐ │ Internal data model
│ │ execution │ │ (beyond OAA)
│ │ _chains │ │ ─────────────────
│ │ collection │ │ - Temporal versioning
│ └────────────┘ │ - Execution evidence
│ ┌────────────┐ │ - Credential chains
│ │ execution │ │ - Trigger semantics
│ │ _evidence │ │ - Finding evaluation
│ └────────────┘ │ - Evidence packs
│ ┌────────────┐ │
│ │ findings │ │
│ └────────────┘ │
└──────────────────┘
The key insight: OAA export is a read-only projection of the internal data model, not the data model itself. The execution_chains collection is the authoritative source for chain metadata, temporal tracking, and finding evaluation. The OAA chain registry is a periodic export of the current state for Veza consumption.
11.4 Export Pipeline
// After sync pipeline completes (entities upserted, paths materialized, chains assembled):
async function exportToOAA(tenantId: string) {
// 1. Export source applications (standard OAA)
const snEntities = await getEntitiesBySourceSystem(tenantId, "servicenow");
const snOAAPayload = buildServiceNowOAAPayload(snEntities);
await pushToVeza(snOAAPayload);
const entraEntities = await getEntitiesBySourceSystem(tenantId, "entra_id");
const entraOAAPayload = buildEntraOAAPayload(entraEntities);
await pushToVeza(entraOAAPayload);
// 2. Export chain registry (SecurityV0 extension)
const chains = await getExecutionChains(tenantId);
const chainRegistryPayload = buildChainRegistryOAAPayload(chains);
await pushToVeza(chainRegistryPayload);
}
12. Recommendation
12.1 Primary Recommendation: Hybrid Model (Approach C) for OAA Export
The hybrid model is the recommended approach because it:
-
Respects OAA semantics — Source applications follow existing connector patterns (one app per system instance, resources for domain objects, permissions for authorization bindings).
-
Adds chain visibility without breaking standards — The chain registry is a valid OAA application with a novel
application_type. Veza can ingest it. If Veza ignores it, source app data is unaffected. -
Maps directly to execution_chains collection — Every field in the Round 3 schema has a corresponding OAA representation via custom properties and sub-resources.
-
Scales correctly — Chains are resources within a single registry application, not separate applications. 100 chains = 100 resources (same pattern as GitHub repos).
-
Enables cross-system analysis via IdP correlation — The SP appears in both the chain registry and the Entra application, linked by the shared IdP identity.
12.2 Secondary Recommendation: execution_chains Collection Remains Essential
The OAA mapping analysis reinforces Round 3's recommendation. OAA cannot provide:
- Temporal chain versioning (snapshots only)
- Chain-level findings (OAA is descriptive, not evaluative)
- Execution evidence (OAA does not model proof-of-execution)
- Credential chain tracking (OAA has no credential entity)
- Trigger semantics (OAA models authorization, not event causality)
The execution_chains collection is the internal authority. The OAA chain registry is the external projection.
12.3 Implementation Order
| Phase | What | OAA Impact |
|---|---|---|
| Phase 1 (Round 3) | execution_chains collection + chain builder + API + UI | Internal — no OAA export yet |
| Phase 2 | Source application OAA export (ServiceNow, Entra) | Standard OAA payloads |
| Phase 3 | Chain registry OAA export | SecurityV0 extension payloads |
| Phase 4 | Veza integration (push to Veza platform) | Full OAA pipeline |
The OAA export is an output concern, not a data model concern. Build the internal model first (Phase 1), then project it to OAA (Phases 2-3).
12.4 Response to the Founder's Insight
"OAA has application notion, not only identity. So stuff like script include, business flow looks more like an application, but not identity."
Partially correct. Script includes and business rules are not identities — they are automation artifacts. But they are not applications either (in OAA's sense of "a software system with its own user base"). They are resources within the ServiceNow application, analogous to how repositories are resources within a GitHub organization.
The execution chain (the end-to-end flow from trigger to destination) can be viewed as an application-like container, but the most OAA-compatible way to model it is as a resource within a chain registry application, not as a standalone application per chain.
"Furthermore autonomous execution also could be treated either as application, or a collection of applications, identities, roles, data modifications."
Correct. An execution chain IS a collection of resources, identities, and permissions. The hybrid model captures this: the chain registry resource contains references to chain components (sub-resources), the identities it authenticates as (local users), and the permissions it exercises (permission bindings). The "collection" semantics are preserved without promoting each chain to a full application.
"When there is an incident trigger - can it be 'data' or 'resource', and when updating some data later like incident table - it looks like a change to 'data' or 'resource' as well."
Correct for the endpoints, not for the trigger event. The incident table IS a resource in OAA — reading it maps to DataRead, writing it maps to DataWrite. But the trigger event (incident INSERT fires the BR) is NOT expressible in OAA. OAA models authorization ("who can do what"), not execution causality ("what event causes what action"). The trigger dimension must be modeled as a custom property (trigger_table, trigger_condition) rather than a permission type.
12.5 Open Questions for Founder/CEO Decision
-
Should the OAA chain registry be pushed to Veza? If SecurityV0 integrates with Veza's platform, the chain registry provides cross-system chain visibility. If SecurityV0 is standalone, the OAA export may not be needed at all, and the internal
execution_chainscollection is sufficient. -
Should the OAA extension strategy be published? If SecurityV0 positions itself as extending OAA for execution exposure, the extension could become a reference pattern for the OAA community. This is a product/positioning decision.
-
Do we need Veza compatibility at all? The
execution_chainscollection + SecurityV0's internal model already provides all the capabilities the CISO needs (listing, detail, diff, findings). OAA compatibility adds value only if Veza integration is a product requirement. The OAA mapping analysis should not drive the internal data model — it should be an export layer built after the internal model is solid.
Appendix A: OAA Entity Reference
OAA Entity Types and SecurityV0 Mapping
| OAA Entity | OAA Semantic | SecurityV0 Equivalent | Mapping Fidelity |
|---|---|---|---|
| Application | Software system container | Not a direct equivalent — closest is source_system | LOW |
| Local User | Identity native to the app | Identity (NHI entity) | MEDIUM — OAA users are broader than NHIs |
| Local Group | Collection of users | Not used directly — closest is Owner (team) | LOW |
| Local Role | Permission bundle | Role (entity_type: "role") | HIGH |
| Custom Permission | App-specific permission mapped to canonical | Permission (entity_type: "permission") | HIGH |
| Resource | Domain object within app | Resource (entity_type: "resource") | HIGH |
| Sub-Resource | Nested domain object | Resource with parent reference | HIGH |
| IdP Identity | External identity reference | AUTHENTICATES_TO relationship | MEDIUM — OAA links by email, SV0 by credential chain |
| Custom Property | App-specific metadata | properties on entity documents | HIGH |
OAA Permission Types and SecurityV0 Mapping
| OAA Permission | SecurityV0 Normalized Action | Mapping |
|---|---|---|
| DataRead | read | Direct |
| DataWrite | update | Direct |
| DataDelete | delete | Direct |
| DataCreate | create | Direct |
| MetadataRead | read (on config scope) | Approximate |
| MetadataWrite | admin | Approximate |
| NonData | execute | Approximate — OAA catch-all vs. SV0 specific action |
| OwnershipAssignment | delegate | Approximate |
| ResourceAdmin | admin | Direct |
| AccountAdmin | admin (on identity scope) | Approximate |
Key Gaps
| SecurityV0 Concept | OAA Equivalent | Gap Description |
|---|---|---|
| ExecutionEvidence | None | OAA does not model proof-of-execution |
| Credential | None | OAA has no credential entity type |
| AUTHENTICATES_TO | IdP identity correlation | OAA links by email; SV0 links by credential chain with evidence_references |
| RUNS_AS | None | OAA has no "runs as" relationship type |
| TRIGGERS_ON | None | OAA has no trigger/event model |
| Finding | None | OAA is descriptive, not evaluative |
| Evidence Pack | None | OAA has no sealed evidence artifact concept |
| Temporal state | None | OAA is snapshot-based, no history |
| execution_mode | Custom property | Can be carried as metadata, but OAA has no execution model |
| security_relevance | Custom property | Can be carried as metadata, but OAA cannot compute it |
Appendix B: OAA Connector Patterns Observed
Pattern: One Application per System Instance
Every community connector follows this pattern:
# GitHub: one app per org
app = CustomApplication(f"Github - {org_name}", "Github")
# Jira: one app per instance
app = CustomApplication(f"Jira - {jira_instance}", "Jira")
# Slack: one app per workspace
app = CustomApplication(f"Slack - {team_name}", "Slack")
Implication for execution chains: Do NOT create one application per chain. Create one application per ServiceNow instance (matching the pattern) and one chain registry application per instance.
Pattern: Resources as Domain Objects
# GitHub: repositories are resources
repo_resource = app.add_resource(name=repo['name'], resource_type="repository")
# Jira: projects are resources
app.add_resource(name=project_name, resource_type="project")
Resources represent the "things" that identities interact with — repos, projects, tables. Automation artifacts (Business Rules, Flows) are domain objects within ServiceNow, so modeling them as resources follows the existing pattern.
Pattern: Custom Properties for Domain-Specific Metadata
# GitHub: repo properties
app.property_definitions.define_resource_property("repository", "Private", OAAPropertyType.BOOLEAN)
app.property_definitions.define_resource_property("repository", "visibility", OAAPropertyType.STRING)
# Jira: project properties
app.property_definitions.define_resource_property("project", "private", OAAPropertyType.BOOLEAN)
Custom properties are the OAA escape hatch for metadata that doesn't fit canonical fields. All chain-specific metadata (execution_mode, security_relevance, trigger_table, etc.) should be custom properties.
Pattern: Roles as Context-Specific Permission Bundles
# Jira: per-project roles
local_project_role = f"{project_name}-{role_name}"
app.add_local_role(local_project_role, permissions=role_permissions)
# GitHub: repo-level roles
app.add_local_role("Read", ["Pull", "Fork"])
app.add_local_role("Write", ["Pull", "Fork", "Push", "Merge"])
Roles are defined per application/resource context. For execution chains, roles should map to the chain's effective permissions (what the chain can do via its run-as identity).
Pattern: IdP Identity Correlation for Cross-System Linking
# All connectors: link local users to IdP identities via email
user = app.add_local_user("jane", identities="jane@example.com")
This is the ONLY mechanism for cross-application identity linking in OAA. The chain registry must use this pattern to link its local users (the SPs and integration users that chains authenticate as) to the same identities in the source application payloads.
Anti-Pattern: No Connector Models Workflows or Automations
None of the existing connectors (GitHub, Jira, Slack, Bitbucket, PagerDuty, GitLab, Cerby, Looker, Rollbar) models:
- GitHub Actions workflows as applications or resources
- Jira automations (rules, workflows)
- Slack bots as anything other than local users with
bot_idproperty
The automation/execution chain modeling that SecurityV0 requires is genuinely novel in the OAA ecosystem. There is no precedent to follow — the hybrid model proposed here would be the first OAA pattern for execution chain visibility.
Analysis complete. 5 OAA connector implementations analyzed, 3 mapping approaches evaluated, 7 analysis questions addressed. Recommendation: Hybrid Model (Approach C) for OAA export, with execution_chains collection as the internal authority. The founder's insight about OAA's application/resource vocabulary is directionally correct but must be tempered by OAA's fundamental limitation: it models static authorization, not dynamic execution exposure.