Skip to main content

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

  1. Executive Summary
  2. OAA Data Model Baseline
  3. The Mapping Problem
  4. Approach A: Automation as Application
  5. Approach B: Automation as Resource
  6. Approach C: Hybrid Model
  7. The Trigger/Data Question
  8. OAA Extension Strategy
  9. Compatibility Matrix
  10. Concrete Mapping: AzureGraphRouter
  11. Reconciliation with Round 3
  12. Recommendation
  13. Appendix A: OAA Entity Reference
  14. 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:

  1. 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).

  2. 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.

  3. 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_chains collection.

  4. 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_chains collection 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)

PermissionSemanticRelevant to Execution Chains?
DataReadRead data contentYes — trigger reads (incident table)
DataWriteModify data contentYes — automation updates (incident, HR case)
DataDeleteRemove dataYes — cleanup automations
DataCreateCreate new data recordsYes — automation creates records
MetadataReadRead system configuration/schemaMarginally — reading ACLs, configs
MetadataWriteModify system configurationYes — automations that modify ACLs, configs
NonDataActions that aren't data operationsYes — "execute" semantics (run script, trigger flow)
OwnershipAssignmentAssign ownership of resourcesRarely — delegation automations
ResourceAdminAdminister resourcesYes — admin-level automation operations
AccountAdminAdminister user accountsRarely — 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:

ConnectorApplicationResourcesSub-ResourcesIdentitiesPattern
GitHub (oaa_github.py)Github - {org_name}Repositories (type: repository)NoneOrg members + collaboratorsOne app per org; repos as resources; teams as groups
Jira (oaa_jira.py)Jira - {instance}Projects (type: project)NoneJira usersOne app per instance; projects as resources; groups mapped to roles per project
Slack (oaa_slack.py)Slack - {team_name}NoneNoneSlack users + botsOne app per workspace; no resources; permissions at app level only

Pattern observations:

  1. 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.

  2. Resources represent domain objects — GitHub repos, Jira projects. These are the "things" that identities interact with, not intermediate processing steps.

  3. 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).

  4. 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.

  5. Custom properties are the extension mechanism — All connectors use property_definitions to add domain-specific metadata (e.g., GitHub's OutsideCollaborator, 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

StrengthExplanation
Container semanticsThe 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 graphAll 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 nestingBR → SI → RESTMessage maps to resource → sub-resource → sub-sub-resource, which OAA supports natively.
Custom propertiesEach chain-application can carry execution_mode, security_relevance, trigger_type, egress_category as custom properties.
IdP identity correlationThe 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

WeaknessSeverityExplanation
Application proliferationHIGHA 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 duplicationHIGHThe 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 duplicationHIGHsp-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 distortionMEDIUMAn 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 impossibleHIGH"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 flowMEDIUMEven 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

StrengthExplanation
Follows existing patternsExactly how GitHub models repos (resources within org-app) and Jira models projects (resources within instance-app). One application per ServiceNow instance.
No resource duplicationThe incident table is one resource within the ServiceNow app. All automations that reference it point to the same resource.
No identity duplicationsn-integration-user is one local user. All automations that run as this user share the same identity object.
Sub-resource nesting worksBR → SI → RESTMessage maps to resource → sub-resource → sub-sub-resource, capturing the containment hierarchy.
Custom properties carry metadatatrigger_table, execution_mode, security_relevance, egress_category all fit naturally as custom properties on the resource.
Veza-consumableVeza 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

WeaknessSeverityExplanation
No container semanticsHIGHA 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 backwardHIGHIn 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 invisibleHIGHThe 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 missingMEDIUMResources 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 ambiguityMEDIUMThe 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 modelMEDIUMThe 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:

  1. ServiceNow remains one Application (matching OAA patterns)
  2. Automations are Resources within ServiceNow (with sub-resource nesting for chain components)
  3. Execution chains are modeled as a special resource type with a custom property chain_type: "execution_chain" that groups related automation resources
  4. Cross-system linking happens via IdP identity correlation (SP in ServiceNow linked to SP in Entra via shared identity)
  5. 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

StrengthExplanation
Best of both worldsServiceNow and Entra are standard OAA applications (Veza-consumable). The chain registry is a SecurityV0-specific extension (SecurityV0-consumable).
No resource duplicationTables 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 IdPsp-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 metadatacomposition_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 appsServiceNow and Entra OAA payloads are completely standard. The chain registry is additive — it doesn't modify or conflict with the source application payloads.
Graceful degradationIf 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

WeaknessSeverityExplanation
Three OAA payloads requiredLOWServiceNow 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 liveLOWThe 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 flowMEDIUMOAA'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 modelHIGHOAA 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 novelMEDIUMapplication_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.

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:

  1. The incident creation is a DataCreate operation by the creating user — this is a standard OAA permission: User → DataCreate → incident table.

  2. 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 ConceptOAA ApproximationFidelity
BR triggers on incident insertexecute (NonData) permission on BR resource, with custom property trigger_table: "incident"LOW — loses causality
BR reads incident data during executionDataRead permission on incident resource for the run-as userHIGH — correct semantic
SI calls RESTMessageexecute (NonData) permission on RESTMessage resource, or sub-resource containmentLOW — containment is not invocation
RESTMessage authenticates via OAuthNot expressible in OAA (credential chain)NONE
SP reads/writes Graph APIDataRead/DataWrite permission on Graph API resourceHIGH — correct semantic
Automation updates incident tableDataWrite permission on incident resource for the run-as userHIGH — 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:

  1. Trigger resource: An INSERT event on this table causes the automation to fire
  2. Read resource: The BR reads the new incident record during execution
  3. 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:

  1. In OAA export: Use DataRead for trigger resources and DataWrite for modified resources. Add a custom property trigger: true on the automation resource to indicate it is event-driven, and trigger_table: "incident", trigger_condition: "on insert" as custom properties.

  2. In the internal data model: Use the existing TRIGGERS_ON relationship (Identity → Resource) with trigger_type and schedule properties. This is already in the SecurityV0 data model and correctly captures the semantics that OAA cannot.

  3. Do NOT attempt to force trigger semantics into OAA permission types. Creating a custom permission called "trigger" with NonData type 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 TypePurposeStandard OAA?
ServiceNowServiceNow instance authorizationYes (follows Jira/GitHub pattern)
EntraIDEntra ID tenant authorizationYes
GitHubGitHub org authorizationYes (existing community connector)
execution_chain_registrySecurityV0 chain metadataExtension (valid OAA, novel type)

8.4 Custom Permission Types

PermissionCanonical MappingPurpose
execute_automationNonDataIdentity can execute an automation artifact
trigger_readDataReadIdentity reads data from a trigger source
outbound_api_callNonDataIdentity makes external API calls
read_tableDataReadIdentity reads ServiceNow table data
write_tableDataWriteIdentity writes ServiceNow table data
admin_tableResourceAdminIdentity administers table configuration
executes_asNonDataIdentity 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

PropertyTypeOn Resource TypePurpose
chain_idSTRINGbusiness_rule, flow, scheduled_jobStable chain identifier
chain_roleSTRINGall automation typesRole in chain (entry_point, executor, outbound_connector, auth_credential, destination_identity)
execution_modeSTRINGbusiness_rule, flow, scheduled_jobautonomous, operator_assisted, human_triggered
security_relevanceSTRINGbusiness_rule, flow, scheduled_jobactive_external, dormant_authority, internal_inventory
trigger_tableSTRINGbusiness_ruleWhich table triggers the automation
trigger_conditionSTRINGbusiness_ruleTrigger condition (on insert, on update, etc.)
egress_categorySTRINGall automation typesinternal, external, none
destination_endpointSTRINGrest_messageTarget URL for outbound calls
auth_typeSTRINGrest_message, oauth_profileAuthentication mechanism
composition_hashSTRINGexecution_chainSHA256 of chain composition
ownership_statusSTRINGexecution_chainowned, degraded, orphaned
blast_radius_domainsSTRING_LISTexecution_chainAffected 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:

ConceptWhy OAA Cannot Express It
Execution flow orderOAA models static authorization, not invocation sequences
Credential chainsOAA has no credential entity type — auth is implicit in identity correlation
AUTHENTICATES_TO relationshipOAA links identities via IdP correlation, not explicit auth chain edges
Temporal stateOAA is snapshot-based — no history, no drift, no temporal comparison
Execution evidenceOAA has no concept of "proof this identity exercised this permission"
FindingsOAA is descriptive (what exists), not evaluative (what is wrong)
Ownership hierarchyOAA has groups but not ownership with decay semantics
Evidence completenessOAA 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

CriterionA: Automation-as-ApplicationB: Automation-as-ResourceC: Hybrid Model
OAA standard complianceValid but non-idiomaticFully idiomaticStandard source apps + valid extension
Veza consumable?Yes, but UI problems at scaleYes, clean displayYes, chain registry may lack optimized UI
Semantic correctnessOverpromotes automations to "systems"Underpromotes automations to "data objects"Correctly separates roles: source apps for auth, registry for chains
Resource duplicationHIGH — shared resources duplicatedNONENONE
Identity duplicationHIGH — shared identities duplicatedNONELOW — IdP correlation links them
Cross-system chainsPOOR — no cross-app linkingPOOR — different apps, no linkingGOOD — IdP identity correlation
Chain listabilityYES — list applications of type execution_chainPARTIAL — filter resources by typeYES — list resources in chain registry
Chain detail viewYES — application detail pagePARTIAL — resource + sub-resourcesYES — chain resource + sub-resources
Neo4j portabilityGOOD — apps become nodesGOOD — resources become nodesGOOD — all entities become nodes
Temporal diffNOT POSSIBLE in OAANOT POSSIBLE in OAANOT POSSIBLE in OAA (use execution_chains collection)
Query: all chains for SPPOOR — scan all chain-appsPOOR — scan all resourcesGOOD — IdP identity correlation on chain registry
Query: all chains touching incident tablePOOR — scan all chain-appsGOOD — filter by trigger_table propertyGOOD — custom property filter on chain resources
Effort to implementHIGH — custom connector + refactoringLOW — add to existing connectorMEDIUM — existing connectors + chain registry connector
Rollback riskHIGH — must deprecate application typeLOW — remove resourcesLOW — drop chain registry, source apps unaffected
Round 3 compatibilityPARTIAL — replaces execution_chains with OAA appsPOOR — no chain conceptHIGH — chain registry IS the OAA projection of execution_chains

9.2 CISO Use Case Evaluation

The CISO's primary questions from Round 3:

QuestionA: AppB: ResourceC: Hybrid
"List all automations"List apps of type execution_chainFilter resources by automation typesList chain registry resources
"Show this automation's chain"App detail → resources + sub-resourcesResource → sub-resourcesChain resource → sub-resources
"What SP does this chain use?"App → local usersResource → find identity with execute permissionChain → local users (linked via IdP)
"What data does this chain touch?"App → resources (trigger + target)Resource custom properties → linked resourcesChain custom properties (trigger_table, destination)
"Who owns this chain?"Custom property (not native to OAA)Custom propertyCustom property (ownership_status)
"Diff chain over time"NOT POSSIBLE in OAANOT POSSIBLE in OAANOT POSSIBLE in OAA — use execution_chains collection
"Show evidence of execution"NOT POSSIBLE in OAANOT POSSIBLE in OAANOT 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 fieldOAA chain registry equivalent
nameResource name
anchor_entity_idCustom property: anchor_entity_source_id
entity_refsSub-resources (name, resource_type=role)
entity_refs[].roleSub-resource resource_type (entry_point, executor, etc.)
summary.triggerCustom property: trigger_table + trigger_condition
summary.destinationCustom property: destination_endpoint
summary.egress_categoryCustom property: egress_category
summary.blast_radius_domainsCustom property: blast_radius_domains
summary.ownership_statusCustom property: ownership_status
composition_hashCustom property: composition_hash
first_detected_atCustom property: first_detected_at
last_seen_atCustom property: last_seen_at

11.2 What execution_chains Provides That OAA Cannot

Capabilityexecution_chainsOAA chain registry
Stable chain ID_id (UUID)Resource name (string)
Temporal versioningexecution_chain_versions collectionNOT POSSIBLE — snapshot only
Chain-level eventschain_created, chain_blast_radius_changedNOT POSSIBLE
Chain-level findingsFinding with entity_type: execution_chainNOT POSSIBLE
Chain diff over timeCompare two chain versionsNOT POSSIBLE
Entity references (live)entity_refs with UUIDs linking to entities collectionSub-resources (symbolic names, not live references)
Composition fingerprint change detectionCompare composition_hash across sync versionsCustom property comparison (manual, no built-in diff)
Integration with sync pipelineBuilt into sync-ingestion.ts after path materializationSeparate OAA push step after sync
                                     ┌─────────────────────┐
│ 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:

  1. Respects OAA semantics — Source applications follow existing connector patterns (one app per system instance, resources for domain objects, permissions for authorization bindings).

  2. 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.

  3. Maps directly to execution_chains collection — Every field in the Round 3 schema has a corresponding OAA representation via custom properties and sub-resources.

  4. Scales correctly — Chains are resources within a single registry application, not separate applications. 100 chains = 100 resources (same pattern as GitHub repos).

  5. 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

PhaseWhatOAA Impact
Phase 1 (Round 3)execution_chains collection + chain builder + API + UIInternal — no OAA export yet
Phase 2Source application OAA export (ServiceNow, Entra)Standard OAA payloads
Phase 3Chain registry OAA exportSecurityV0 extension payloads
Phase 4Veza 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

  1. 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_chains collection is sufficient.

  2. 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.

  3. Do we need Veza compatibility at all? The execution_chains collection + 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 EntityOAA SemanticSecurityV0 EquivalentMapping Fidelity
ApplicationSoftware system containerNot a direct equivalent — closest is source_systemLOW
Local UserIdentity native to the appIdentity (NHI entity)MEDIUM — OAA users are broader than NHIs
Local GroupCollection of usersNot used directly — closest is Owner (team)LOW
Local RolePermission bundleRole (entity_type: "role")HIGH
Custom PermissionApp-specific permission mapped to canonicalPermission (entity_type: "permission")HIGH
ResourceDomain object within appResource (entity_type: "resource")HIGH
Sub-ResourceNested domain objectResource with parent referenceHIGH
IdP IdentityExternal identity referenceAUTHENTICATES_TO relationshipMEDIUM — OAA links by email, SV0 by credential chain
Custom PropertyApp-specific metadataproperties on entity documentsHIGH

OAA Permission Types and SecurityV0 Mapping

OAA PermissionSecurityV0 Normalized ActionMapping
DataReadreadDirect
DataWriteupdateDirect
DataDeletedeleteDirect
DataCreatecreateDirect
MetadataReadread (on config scope)Approximate
MetadataWriteadminApproximate
NonDataexecuteApproximate — OAA catch-all vs. SV0 specific action
OwnershipAssignmentdelegateApproximate
ResourceAdminadminDirect
AccountAdminadmin (on identity scope)Approximate

Key Gaps

SecurityV0 ConceptOAA EquivalentGap Description
ExecutionEvidenceNoneOAA does not model proof-of-execution
CredentialNoneOAA has no credential entity type
AUTHENTICATES_TOIdP identity correlationOAA links by email; SV0 links by credential chain with evidence_references
RUNS_ASNoneOAA has no "runs as" relationship type
TRIGGERS_ONNoneOAA has no trigger/event model
FindingNoneOAA is descriptive, not evaluative
Evidence PackNoneOAA has no sealed evidence artifact concept
Temporal stateNoneOAA is snapshot-based, no history
execution_modeCustom propertyCan be carried as metadata, but OAA has no execution model
security_relevanceCustom propertyCan 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_id property

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.