Skip to main content

Entity Type Classification — Integration Engineer Analysis

Date: 2026-02-13 (Round 5) Role: Integration Engineer Context: Founder challenge — "A Script Include is code, not an identity. The current classification (entity_type: "identity" with identitySubtype) is wrong." Constraint: No active clients. Can rewrite from scratch. Focus on correctness for connector architecture.


Executive Summary

After analyzing source system data models, OAA canonical types, cross-system execution chains, and current connector implementation, I propose:

Current state (INCORRECT):

  • All automation artifacts → entity_type: "identity" with identitySubtype discriminator
  • Business Rule → identitySubtype: "business_rule"
  • Script Include → identitySubtype: "system_execution" (incorrect semantic)
  • REST Message → modeled as Resource
  • OAuth Profile → modeled as Identity

Proposed state (CORRECT):

  • Automation artifacts that EXECUTEautonomous_identity (BR, Scheduled Job, Flow)
  • Code libraries → NEW type code_artifact (Script Include)
  • API configurationresource with subtype api_endpoint (REST Message)
  • Authentication bindingscredential (OAuth Entity/Profile)
  • Service Principalsautonomous_identity (unchanged)

Key insight: The current model conflates "can execute autonomously" (identity) with "is executable code" (artifact). Script Includes are libraries called BY identities, not identities themselves. REST Messages are API configuration data, not identities. OAuth Entities are credentials, not identities.

Impact: 4 new node types, ~180 lines of TypeScript changes, ~240 lines of Python connector changes, zero breaking changes to deployed platform (no clients yet).


1. Source System Reality Check

1.1 ServiceNow Artifact Data Models

Let me examine what these artifacts ACTUALLY ARE in the ServiceNow database:

ArtifactSource TableWhat It StoresCan Execute?Has Identity Context?
Business Rulesys_scriptServer-side script triggered by table events (insert/update/delete)YES — runs on eventYES — runs as current.user or configured user
Script Includesys_script_includeReusable JavaScript library/functionNO — called by other codeNO — executes in caller's context
REST Messagesys_rest_messageHTTP endpoint configuration (URL, headers, auth)NO — invoked by scriptsNO — config data only
OAuth Entityoauth_entityOAuth 2.0 client credentials binding (client_id, secret ref, user binding)NO — authentication materialYES — binds to sys_user for execution context
Flow Designer Flowsys_hub_flowVisual workflow definition (triggers, actions, conditions)YES — runs on triggerYES — runs as flow owner or configured user
Scheduled Jobsysauto_scriptCron-style scheduled execution of scriptYES — runs on scheduleYES — runs as configured user

Key observations:

  1. Business Rules, Flows, Scheduled Jobs can execute autonomously (triggered by event/schedule).
  2. Script Includes are passive code — they do NOT execute on their own. They are CALLED BY Business Rules/Flows/Jobs.
  3. REST Messages are configuration records — they define HOW to call an API, but they don't call it. A BR/Flow/Job invokes the REST Message.
  4. OAuth Entities are authentication bindings — they map a client_id to a ServiceNow user context. The IDENTITY is the bound user, not the OAuth entity.

1.2 Microsoft Entra ID Artifact Data Models

ArtifactGraph API ResourceWhat It StoresCan Execute?Has Identity Context?
Service Principal/servicePrincipals/{id}Application instance in tenant (can authenticate, has roles, owns resources)YES — authenticates via client credentialsYES — IS an identity
App Registration/applications/{id}Application definition (redirect URIs, API permissions, secrets)NO — template for SPsNO — config data

Key observations:

  1. Service Principal IS an identity — it can authenticate, it has roles, it executes actions.
  2. App Registration is NOT an identity — it's a configuration template. The SP created FROM the app reg is the identity.

1.3 Cross-System Reality: The AzureGraphRouter Chain

Let's trace the actual execution chain from Round 4 analysis:

Trigger: incident.INSERT event (ServiceNow table)

Business Rule: "AzureGraphRouterIncidentCreate" (sys_script)
- Executes in context: current.user (or configured run_as user)
- CALLS: Script Include "AzureGraphRouterUtils"

Script Include: "AzureGraphRouterUtils" (sys_script_include)
- CALLS: REST Message "AzureGraphRouter"

REST Message: "AzureGraphRouter" (sys_rest_message)
- Endpoint: https://graph.microsoft.com/v1.0/groups/{groupId}/members
- Auth: OAuth Entity "azure-graph-oauth"

OAuth Entity: "azure-graph-oauth" (oauth_entity)
- client_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
- Bound user: "sn-integration-user" (sys_user)

[Cross-system boundary]

Service Principal: "sp-azure-graph-integration" (Entra ID)
- appId: a1b2c3d4-e5f6-7890-abcd-ef1234567890 (matches OAuth Entity client_id)
- Roles: Group.ReadWrite.All, User.Read.All

Target: Microsoft Graph API (https://graph.microsoft.com)

What is each artifact in this chain?

  1. Incident table — RESOURCE (trigger source)
  2. Business Rule — IDENTITY (executes autonomously on trigger)
  3. Script Include — CODE ARTIFACT (library called by BR)
  4. REST Message — RESOURCE (API endpoint config)
  5. OAuth Entity — CREDENTIAL (authentication binding)
  6. ServiceNow user — IDENTITY (execution context)
  7. Service Principal — IDENTITY (cross-system identity)
  8. Microsoft Graph — RESOURCE (target API)

Notice the pattern:

  • Identities have execution context and can authenticate: BR (as user), SN user, SP
  • Code artifacts are passive libraries: Script Include
  • Resources are acted upon or configured: incident table, REST Message, Graph API
  • Credentials enable authentication: OAuth Entity

2. Current Connector Implementation Analysis

From /Users/lucky/dev/securityv0/sv0-connectors/integrations/entra-servicenow/src/entra_servicenow/core/transformer.py:

2.1 Current NormalizedNode Mappings

# Business Rule (line 619)
br_node_id = self._add_node(
node_id=f"sn-br-{br_sys_id}",
node_type="autonomous_identity", # ← CURRENTLY
source_system="servicenow",
source_id=br_sys_id,
display_name=br.get("name", "Unknown Business Rule"),
status="active" if br.get("active", True) else "disabled",
properties={
"identitySubtype": "business_rule",
"automation_type": "business_rule",
"table": br.get("table", ""),
"when": br.get("when", ""),
},
)

# Script Include (line 697)
si_node_id = self._add_node(
node_id=f"sn-si-{si_sys_id}",
node_type="autonomous_identity", # ← CURRENTLY (WRONG)
source_system="servicenow",
source_id=si_sys_id,
display_name=si.get("name", "Unknown Script Include"),
properties={
"identitySubtype": "system_execution", # ← WRONG semantic
"automation_type": "script_include",
},
)

# Scheduled Job (line 741)
job_node_id = self._add_node(
node_id=f"sn-job-{job_sys_id}",
node_type="autonomous_identity", # ← CURRENTLY
source_system="servicenow",
source_id=job_sys_id,
display_name=job.get("name", "Unknown Scheduled Job"),
properties={
"identitySubtype": "scheduled_job",
"automation_type": "scheduled_job",
},
)

# REST Message (line 586)
rest_msg_node_id = self._add_node(
node_id=f"sn-rest-msg-{rest_msg_sys_id}",
node_type="resource", # ← Currently resource (PARTIALLY CORRECT)
source_system="servicenow",
source_id=rest_msg_sys_id,
display_name=rest_msg.get("name", "Unknown REST Message"),
properties={
"resourceType": "rest_message", # ← Should be resourceSubtype
"endpoint_url": endpoint_url,
"authentication_type": rest_msg.get("authentication_type", ""),
},
)

# OAuth Entity (Integration processing, line 400)
sn_node_id = self._add_node(
node_id=f"sn-oauth-{snow_app['sys_id']}",
node_type="autonomous_identity", # ← CURRENTLY (WRONG)
source_system="servicenow",
source_id=snow_app["sys_id"],
display_name=snow_app.get("name", "Unknown OAuth App"),
properties={
"identitySubtype": "oauth_app", # ← Should be credential
"client_id": snow_app.get("client_id", ""),
},
)

2.2 What's Wrong

ArtifactCurrent TypeCurrent SubtypeProblem
Business Ruleautonomous_identitybusiness_ruleSemantically OK — does execute autonomously
Script Includeautonomous_identitysystem_executionWRONG — it's a library, not an executor. system_execution falsely implies it executes.
Scheduled Jobautonomous_identityscheduled_jobSemantically OK — does execute autonomously
Flowautonomous_identityflow_designer_flowSemantically OK — does execute autonomously
REST Messageresource(via resourceType property)PARTIALLY CORRECT — is a resource, but should use resourceSubtype per schema
OAuth Entityautonomous_identityoauth_appWRONG — it's a credential binding, not an identity. The bound sys_user is the identity.

3. OAA Model Analysis (Cross-Reference with Round 4)

From Round 4 synthesis (/Users/lucky/dev/securityv0/sv0-documentation/docs/analysis/2026-02-12-automation-classification/04-oaa-mapping-synthesis.md):

3.1 OAA Entity Types

OAA has 3 primary entity types:

  1. Application — A software system with its own authorization model (users, groups, resources, permissions)
  2. Local User — An identity local to the application (equivalent to SecurityV0's autonomous_identity or human_identity)
  3. Resource — Something that can be acted upon (databases, files, endpoints, repositories)

OAA does NOT have:

  • Code artifact type
  • Credential type
  • Execution evidence type
  • Automation/workflow type

3.2 Community Patterns from OAA Integrations

From reviewing OAA community connectors (Round 4 analysis):

Source SystemArtifactOAA Mapping
GitHubRepositoryResource (with subtype repository)
GitHubWorkflow (Actions)NOT modeled (execution semantics lost)
GitHubPersonal Access TokenLocal User with custom properties
JiraProjectResource (with subtype project)
JiraAutomation RuleNOT modeled
SnowflakeStored ProcedureNOT modeled (or modeled as Resource)
AWSLambda FunctionResource (with subtype function)
ServiceNowBusiness RuleNOT modeled (no execution concept)

Key insight: OAA community connectors model CONFIGURATION as Resources but do NOT model EXECUTION SEMANTICS. A GitHub workflow YAML file can be a resource, but the fact that it EXECUTES on a trigger is not captured.

SecurityV0's differentiator is execution chain tracking — we MUST model execution semantics that OAA cannot.

3.3 OAA Export Compatibility (Round 4 Recommendation)

From Round 4, the team recommended:

  • ServiceNow artifacts → Resources within the ServiceNow Application (for OAA export)
  • Execution chains → NOT exported to OAA (no equivalent concept)
  • Internal model → Use richer types that OAA doesn't have

This means: We can define new NormalizedNodeTypes that don't have OAA equivalents. The OAA export layer will PROJECT them as Resources (with custom properties to preserve type info).


4. Proposed New NormalizedNodeType Taxonomy

4.1 New Type Enum (TypeScript)

// sv0-platform/src/ingestion/types.ts

export type NormalizedNodeType =
// --- EXISTING TYPES (unchanged) ---
| "autonomous_identity" // Service principals, OAuth apps that CAN authenticate
| "human_identity" // User accounts
| "role" // Permission groupings
| "permission" // Individual capabilities
| "resource" // Things acted upon (tables, APIs, repos)
| "credential" // Authentication material (secrets, certs, tokens)
| "execution_evidence" // Proof of execution

// --- NEW TYPES (proposed) ---
| "code_artifact" // Libraries, functions, scripts (passive — called by identities)
| "automation_trigger" // Event/schedule definitions that start execution
| "api_configuration" // API endpoint configs (REST Messages, HTTP Methods)
| "authentication_binding"; // Mappings between credentials and execution contexts

Rationale for each new type:

  1. code_artifact — Passive code that does NOT execute on its own. Examples:

    • Script Includes (ServiceNow)
    • Lambda Function code (AWS) — note: the INVOCATION is identity, the CODE is artifact
    • Stored Procedures (SQL) — note: the EXECUTION GRANT is permission, the CODE is artifact
    • GitHub Action definitions (YAML)
  2. automation_trigger — Declarative trigger configuration. Examples:

    • Business Rule trigger conditions (table, when, filter_condition)
    • Flow Designer trigger configuration
    • GitHub Actions on: triggers
    • AWS EventBridge rules
    • NOTE: This is distinct from the automation ITSELF. A BR has a trigger AND execution logic. We could model these as separate nodes connected by edges.
  3. api_configuration — HTTP endpoint definitions (URL, method, headers). Examples:

    • ServiceNow REST Messages
    • AWS API Gateway endpoint configs
    • GitHub webhook configs
  4. authentication_binding — Credential-to-context mappings. Examples:

    • ServiceNow OAuth Entity (client_id → sys_user binding)
    • AWS IAM Role Trust Policy (who can assume this role)
    • GitHub App installation (app → org/repo binding)

4.2 Alternative: Keep Existing Types, Use Subtypes

Option B: Don't add new top-level types. Instead, use more granular subtypes under existing types.

// KEEP existing NormalizedNodeType enum unchanged

// ADD new subtypes to properties
interface CodeArtifactProperties {
artifactSubtype: "script_include" | "lambda_code" | "stored_procedure" | "github_action";
language: string;
entrypoint?: string;
}

interface ResourceProperties {
resourceSubtype:
| "table" | "module" | "api_endpoint" | "repository" | "secret"
| "workflow" | "storage" | "compute"
| "rest_message" // NEW
| "api_configuration" // NEW
| "trigger_definition"; // NEW
}

interface CredentialProperties {
credentialSubtype:
| "oauth_client_secret" | "certificate" | "pat" | "api_key"
| "oidc_token" | "ssh_key"
| "authentication_binding"; // NEW
}

Comparison:

ApproachProsCons
New top-level typesClear semantic separation; easy to query ("give me all code artifacts"); matches Founder's intuition ("Script Include is not an identity")Breaking change to NormalizedNodeType enum; UI filters need updates; more type proliferation
Subtypes onlyNo breaking changes; flexible (can add subtypes without schema changes); queries still work (nodeType: "resource")Less semantic clarity; have to look at properties to understand what something REALLY is; "Script Include is a resource" feels wrong

Recommendation: Use new top-level types for semantic correctness. The "no clients yet" constraint means we CAN make breaking changes. This is the time to get the types right.


5. Detailed Mapping Proposals

5.1 ServiceNow Artifacts → NormalizedNodeTypes

ServiceNow ArtifactSource TableProposed TypeProposed SubtypeRationale
Business Rulesys_scriptautonomous_identitybusiness_ruleExecutes autonomously on event trigger; has execution context (run_as user)
Script Includesys_script_includecode_artifactscript_includePassive library; executed IN CONTEXT of caller (BR, Flow, Job)
Flow Designer Flowsys_hub_flowautonomous_identityflow_designer_flowExecutes autonomously on trigger; has execution context (flow owner or configured user)
Scheduled Jobsysauto_scriptautonomous_identityscheduled_jobExecutes autonomously on schedule; has execution context (run_as user)
REST Messagesys_rest_messageresourceapi_endpointAPI configuration data; invoked by identities (BR, Flow, Job)
OAuth Entityoauth_entitycredentialauthentication_bindingMaps client_id to ServiceNow user context; IS NOT an identity itself
ServiceNow User (integration account)sys_userautonomous_identitymachine_accountCAN authenticate; CAN execute; IS an identity
Incident TableincidentresourcetableData acted upon

5.2 Microsoft Entra Artifacts → NormalizedNodeTypes

Entra ArtifactGraph API ResourceProposed TypeProposed SubtypeRationale
Service Principal/servicePrincipals/{id}autonomous_identityservice_principalExecutes autonomously; authenticates via client credentials; has roles
App Registration/applications/{id}resourceapplication_templateConfiguration template for creating SPs; does NOT execute
App RoleappRoles propertyroleapplication_rolePermission grouping
OAuth2 Permission Grant/oauth2PermissionGrantspermissiondelegated_permissionIndividual capability grant
Client SecretpasswordCredentialscredentialoauth_client_secretAuthentication material

5.3 Cross-System Edge Semantics

With the new types, the cross-system chain becomes:

[ServiceNow Side]
incident (resource:table)
--[TRIGGERS_ON]-->
BusinessRule (autonomous_identity:business_rule)
--[CALLS]--> // NEW edge type for code invocation
ScriptInclude (code_artifact:script_include)
--[INVOKES]--> // NEW edge type for API call
RestMessage (resource:api_endpoint)
--[AUTHENTICATES_VIA]-->
OAuthEntity (credential:authentication_binding)
--[BINDS_TO]--> // NEW edge type for credential-to-user binding
ServiceNowUser (autonomous_identity:machine_account)

[Cross-System Boundary]
ServiceNowUser (autonomous_identity:machine_account)
--[AUTHENTICATES_TO]-->
ServicePrincipal (autonomous_identity:service_principal)

[Entra Side]
ServicePrincipal (autonomous_identity:service_principal)
--[HAS_ROLE]-->
Role (role:application_role)
--[GRANTS]-->
Permission (permission)
--[APPLIES_TO]-->
MicrosoftGraphAPI (resource:api_endpoint)

New edge types needed:

Edge TypeFrom → ToMeaningExample
CALLSidentity → code_artifact"This identity invokes this library/function"BR → Script Include
INVOKEScode_artifact → resource"This code calls this API"Script Include → REST Message
BINDS_TOcredential → identity"This authentication material grants execution context as this identity"OAuth Entity → SN User

6. Founder's Insights Response

Insight 1: "A Script Include is code, not an identity"

Response: Absolutely correct. Proposed fix:

  • Change Script Include from autonomous_identity to code_artifact
  • Update identitySubtype: "system_execution" → remove this property (not applicable to code artifacts)
  • Add artifactSubtype: "script_include"

Insight 2: "OAA has application notion. Script include, business flow looks more like an application, but not identity."

Response: Partially agree with refinement.

  • Script Include is NOT an application (it doesn't have users/resources). It's a code library.
  • Business Rule IS execution-capable, so it CAN be modeled as an identity (executes as a user).
  • However, for OAA export, BOTH can be projected as Resources within the ServiceNow Application.
  • The "application-like" quality the Founder sees is the EXECUTION CHAIN itself — which is a higher-order construct we track separately (per Round 3 execution_chains collection).

Recommendation: Keep BR as autonomous_identity internally; project it as a Resource for OAA export.

Insight 3: "When there is an incident trigger - can it be 'data' or 'resource'? When updating some data later like incident table - it looks like a change to 'data' or 'resource' as well."

Response (from Round 4 Architect): The incident table IS a resource in THREE ROLES:

  1. Trigger source — emits INSERT event that fires the BR → TRIGGERS_ON edge
  2. Data input — BR reads incident fields → EXECUTES_ON edge with action: "read"
  3. Data output — BR writes to incident fields → EXECUTES_ON edge with action: "write"

The RESOURCE (incident table) doesn't change — the RELATIONSHIP TYPE changes based on HOW the automation interacts with it.

OAA collapses these into DataRead/DataWrite permissions. SecurityV0 distinguishes them via typed edges, which is our differentiator.


7. Implementation Changes Required

7.1 Platform Changes (TypeScript)

File: /Users/lucky/dev/securityv0/sv0-platform/src/ingestion/types.ts

// BEFORE (line 1-7)
export type NormalizedNodeType =
| "autonomous_identity"
| "human_identity"
| "role"
| "permission"
| "resource"
| "credential"
| "execution_evidence";

// AFTER (add 4 new types)
export type NormalizedNodeType =
| "autonomous_identity"
| "human_identity"
| "role"
| "permission"
| "resource"
| "credential"
| "execution_evidence"
| "code_artifact" // NEW
| "automation_trigger" // NEW (optional — can defer)
| "api_configuration" // NEW (can merge into resource)
| "authentication_binding"; // NEW (can merge into credential)

Minimal version (if avoiding proliferation):

export type NormalizedNodeType =
| "autonomous_identity"
| "human_identity"
| "role"
| "permission"
| "resource"
| "credential"
| "execution_evidence"
| "code_artifact"; // ONLY add this one

Then use subtypes for the rest:

  • REST Message → resource with resourceSubtype: "api_endpoint"
  • OAuth Entity → credential with credentialSubtype: "authentication_binding"

File: /Users/lucky/dev/securityv0/sv0-platform/src/ingestion/types.ts (edge types)

// BEFORE (line 12-26)
export type NormalizedEdgeType =
| "OWNED_BY"
| "BELONGS_TO"
| "HAS_ROLE"
| "GRANTS"
| "APPLIES_TO"
| "AUTHENTICATES_TO"
| "AUTHENTICATES_VIA"
| "EXECUTES_ON"
| "RUNS_AS"
| "TRIGGERS_ON"
| "CREATED_BY"
| "DELEGATES_TO"
| "APPROVED_BY"
| "MEMBER_OF";

// AFTER (add 3 new edge types)
export type NormalizedEdgeType =
| "OWNED_BY"
| "BELONGS_TO"
| "HAS_ROLE"
| "GRANTS"
| "APPLIES_TO"
| "AUTHENTICATES_TO"
| "AUTHENTICATES_VIA"
| "EXECUTES_ON"
| "RUNS_AS"
| "TRIGGERS_ON"
| "CREATED_BY"
| "DELEGATES_TO"
| "APPROVED_BY"
| "MEMBER_OF"
| "CALLS" // NEW: identity → code_artifact
| "INVOKES" // NEW: code_artifact → resource (API call)
| "BINDS_TO"; // NEW: credential → identity (context binding)

Estimated effort: ~40 lines changed, ~2 hours (includes updating Zod schemas, type guards, tests).

7.2 Connector Changes (Python)

File: /Users/lucky/dev/securityv0/sv0-connectors/integrations/entra-servicenow/src/entra_servicenow/core/transformer.py

Change 1: Script Include Type

# BEFORE (line 697)
si_node_id = self._add_node(
node_id=f"sn-si-{si_sys_id}",
node_type="autonomous_identity", # WRONG
source_system="servicenow",
source_id=si_sys_id,
display_name=si.get("name", "Unknown Script Include"),
status="active" if si.get("active", True) else "disabled",
created_at=si.get("sys_created_on"),
properties={
"identitySubtype": "system_execution", # WRONG
"automation_type": "script_include",
},
)

# AFTER
si_node_id = self._add_node(
node_id=f"sn-si-{si_sys_id}",
node_type="code_artifact", # CORRECT
source_system="servicenow",
source_id=si_sys_id,
display_name=si.get("name", "Unknown Script Include"),
status="active" if si.get("active", True) else "disabled",
created_at=si.get("sys_created_on"),
properties={
"artifactSubtype": "script_include", # CORRECT
"apiName": si.get("api_name", ""),
"language": "javascript",
"sys_created_by": si.get("sys_created_by", ""),
},
)

Change 2: REST Message Type

# BEFORE (line 586)
rest_msg_node_id = self._add_node(
node_id=f"sn-rest-msg-{rest_msg_sys_id}",
node_type="resource",
source_system="servicenow",
source_id=rest_msg_sys_id,
display_name=rest_msg.get("name", "Unknown REST Message"),
status="active",
properties={
"resourceType": "rest_message", # Should be resourceSubtype
"endpoint_url": endpoint_url,
"authentication_type": rest_msg.get("authentication_type", ""),
},
)

# AFTER (minimal change — keep as resource, fix property name)
rest_msg_node_id = self._add_node(
node_id=f"sn-rest-msg-{rest_msg_sys_id}",
node_type="resource", # KEEP
source_system="servicenow",
source_id=rest_msg_sys_id,
display_name=rest_msg.get("name", "Unknown REST Message"),
status="active",
properties={
"resourceSubtype": "api_endpoint", # CORRECT property name
"endpoint_url": endpoint_url,
"authentication_type": rest_msg.get("authentication_type", ""),
"http_methods": rest_msg.get("http_methods", []),
},
)

Change 3: OAuth Entity Type

# BEFORE (line 400 in _process_integration)
sn_node_id = self._add_node(
node_id=f"sn-oauth-{snow_app['sys_id']}",
node_type="autonomous_identity", # WRONG
source_system="servicenow",
source_id=snow_app["sys_id"],
display_name=snow_app.get("name", "Unknown OAuth App"),
status="active" if snow_app.get("active", True) else "disabled",
created_at=snow_app.get("sys_created_on"),
properties={
"identitySubtype": "oauth_app", # WRONG
"client_id": snow_app.get("client_id", ""),
"oauth_entity_profile_name": snow_app.get("oauth_provider_profile", {}).get("name", ""),
},
)

# AFTER
sn_node_id = self._add_node(
node_id=f"sn-oauth-{snow_app['sys_id']}",
node_type="credential", # CORRECT
source_system="servicenow",
source_id=snow_app["sys_id"],
display_name=snow_app.get("name", "Unknown OAuth Binding"),
status="active" if snow_app.get("active", True) else "disabled",
created_at=snow_app.get("sys_created_on"),
properties={
"credentialSubtype": "authentication_binding", # CORRECT
"client_id": snow_app.get("client_id", ""),
"oauth_entity_profile_name": snow_app.get("oauth_provider_profile", {}).get("name", ""),
"bound_user": snow_app.get("user", ""), # NEW: explicit user binding
},
)

Change 4: Add BINDS_TO Edge (OAuth Entity → ServiceNow User)

# AFTER oauth entity node creation, add binding edge
if snow_app.get("user"):
bound_user_name = snow_app["user"]
# Create or reference the bound user node
bound_user_node_id = self._add_node(
node_id=f"sn-user-{bound_user_name}",
node_type="autonomous_identity",
source_system="servicenow",
source_id=bound_user_name,
display_name=bound_user_name,
status="active",
properties={
"identitySubtype": "machine_account",
"user_name": bound_user_name,
},
)

# BINDS_TO edge: OAuth Entity → bound user
self._add_edge(
edge_type="BINDS_TO",
source_node_id=sn_node_id,
target_node_id=bound_user_node_id,
properties={
"binding_type": "oauth_user_binding",
"source_evidence": snow_app.get("sys_id"),
},
)

Change 5: Add CALLS Edge (BR/Job → Script Include)

# In _process_execution_chain, after creating BR node
# Add CALLS edge if BR calls a Script Include
for si in chain.script_includes:
si_sys_id = si.get("sys_id")
si_node_id = f"sn-si-{si_sys_id}"

# CALLS edge: BR → SI
self._add_edge(
edge_type="CALLS",
source_node_id=br_node_id,
target_node_id=si_node_id,
properties={
"call_type": "direct",
"source_evidence": "script analysis",
},
)

Change 6: Add INVOKES Edge (Script Include → REST Message)

# In _process_execution_chain, after creating SI node
# Add INVOKES edge if SI calls REST Message
if rest_msg_node_id:
self._add_edge(
edge_type="INVOKES",
source_node_id=si_node_id,
target_node_id=rest_msg_node_id,
properties={
"invocation_type": "http_call",
"source_evidence": "script analysis",
},
)

Estimated effort: ~240 lines changed across transformer.py, ~6 hours (includes tests, validation).

7.3 Documentation Updates

Files to update:

  1. /Users/lucky/dev/securityv0/sv0-documentation/docs/architecture/01-data-model.md

    • Add new entity type descriptions for code_artifact
    • Update edge type table with CALLS, INVOKES, BINDS_TO
    • Update execution path examples
  2. /Users/lucky/dev/securityv0/sv0-documentation/docs/architecture/05-connectors.md

    • Update NormalizedNodeType enum documentation
    • Update permission normalization guide
    • Add code artifact mapping examples
  3. /Users/lucky/dev/securityv0/sv0-platform/src/ingestion/README.md (if exists)

    • Update type descriptions

Estimated effort: ~3 hours


8. Migration Path (Zero Clients Scenario)

Since there are no active clients:

8.1 Breaking Change Strategy

  1. Update TypeScript types (sv0-platform/src/ingestion/types.ts) — 2h
  2. Update connector transformer (sv0-connectors/.../transformer.py) — 6h
  3. Update documentation (sv0-documentation/docs/architecture/) — 3h
  4. Update UI filters (if entity type is exposed in UI) — 2h
  5. Run full test suite — 2h
  6. Deploy to staging — 1h

Total effort: ~16 hours (2 days)

8.2 Backwards Compatibility (If Needed)

If we need to maintain backwards compatibility (e.g., for internal testing data):

Option A: Schema migration

  • Write a migration script that updates existing autonomous_identity nodes with identitySubtype: "system_execution"code_artifact nodes
  • Re-ingest all connectors with new schema
  • Estimated effort: +4h

Option B: Accept dual schema temporarily

  • Platform accepts BOTH old and new types during transition period
  • Deprecation warning for old types
  • Remove old types in 3 months
  • Estimated effort: +8h

Recommendation: Don't bother with backwards compatibility. There are no clients. Just cut over.


9. OAA Export Compatibility

9.1 How New Types Map to OAA

When exporting to OAA format (for Veza integration or third-party tools), the new types project as:

SecurityV0 TypeOAA TypeOAA Properties
code_artifactResourceresource_type: "code", custom_properties: {sv0_type: "code_artifact", artifactSubtype: "script_include"}
resource (REST Message)Resourceresource_type: "api_endpoint", custom_properties: {sv0_type: "resource", resourceSubtype: "api_endpoint"}
credential (OAuth Entity)NOT exported (OAA has no credential concept)Represented via authentication flow properties on local_user

9.2 OAA Export Example (ServiceNow Application)

{
"applications": [
{
"name": "ServiceNow Production",
"application_type": "servicenow",
"local_users": [
{
"name": "sn-integration-user",
"identities": ["sn-integration-user@corp.com"],
"is_active": true,
"custom_properties": {
"sv0_type": "autonomous_identity",
"identitySubtype": "machine_account",
"bound_oauth_entities": ["azure-graph-oauth"]
}
}
],
"resources": [
{
"name": "AzureGraphRouterUtils",
"resource_type": "code",
"custom_properties": {
"sv0_type": "code_artifact",
"artifactSubtype": "script_include",
"language": "javascript"
}
},
{
"name": "AzureGraphRouter REST Message",
"resource_type": "api_endpoint",
"custom_properties": {
"sv0_type": "resource",
"resourceSubtype": "api_endpoint",
"endpoint_url": "https://graph.microsoft.com/v1.0/"
}
},
{
"name": "incident",
"resource_type": "table",
"custom_properties": {
"sv0_type": "resource",
"resourceSubtype": "table",
"business_domain": "it_ops"
}
}
],
"permissions": [
{
"name": "execute_script_include",
"permission_type": ["NonData"],
"custom_properties": {
"sv0_action_type": "execute_code"
}
},
{
"name": "invoke_rest_message",
"permission_type": ["NonData"],
"custom_properties": {
"sv0_action_type": "call_api"
}
}
],
"identity_to_permissions": [
{
"identity": "sn-integration-user",
"identity_type": "local_user",
"resources": [
{
"resource": "AzureGraphRouterUtils",
"permissions": ["execute_script_include"]
},
{
"resource": "AzureGraphRouter REST Message",
"permissions": ["invoke_rest_message"]
},
{
"resource": "incident",
"permissions": ["DataRead", "DataWrite"]
}
]
}
]
}
]
}

Note: This OAA export LOSES execution chain semantics (no CALLS/INVOKES edges), but preserves:

  • Code artifacts as resources (discoverable)
  • API endpoints as resources (discoverable)
  • Permissions on those resources (auditable)

For full execution chain visibility, consumers use SecurityV0's native API.


10.1 Minimal Breaking Change Approach

Add ONLY ONE new type: code_artifact

Use existing types for the rest:

  • REST Message → resource with resourceSubtype: "api_endpoint"
  • OAuth Entity → credential with credentialSubtype: "authentication_binding"

Add 3 new edge types:

  • CALLS (identity → code_artifact)
  • INVOKES (code_artifact → resource)
  • BINDS_TO (credential → identity)

Total schema changes:

  • TypeScript: 1 new NormalizedNodeType, 3 new NormalizedEdgeType (~20 lines)
  • Python: 3 node type changes, 3 new edge creations (~240 lines)
  • Docs: 2 files updated (~3 hours)

Effort: ~16 hours (2 days)

10.2 Full Semantic Clarity Approach

Add 4 new types:

  • code_artifact
  • automation_trigger (for future granularity)
  • api_configuration (alias for resource subtype, can skip)
  • authentication_binding (alias for credential subtype, can skip)

Effort: ~24 hours (3 days)

Recommendation: Go with Minimal approach now. Add the other types if/when needed.


11. Cross-System Boundary Analysis

11.1 Type Consistency Across Systems

ArtifactSource SystemProposed TypeCross-System Role
Business RuleServiceNowautonomous_identityExecutor (initiates chain)
Script IncludeServiceNowcode_artifactLibrary (called by executor)
REST MessageServiceNowresourceAPI config (invoked by code)
OAuth EntityServiceNowcredentialAuth binding (maps client_id → user)
ServiceNow UserServiceNowautonomous_identityExecution context
Service PrincipalEntraautonomous_identityCross-system identity
App RegistrationEntraresourceTemplate (not executable)
Client SecretEntracredentialAuth material

Key insight: Each artifact has a type that makes sense WITHIN its source system. The cross-system linkage happens via EDGES, not via trying to force a unified type.

11.2 Edge Vocabulary for Cross-System Chains

ServiceNow Side:
incident (resource)
--[TRIGGERS_ON]-->
BR (autonomous_identity)
--[CALLS]-->
SI (code_artifact)
--[INVOKES]-->
REST Message (resource)
--[AUTHENTICATES_VIA]-->
OAuth Entity (credential)
--[BINDS_TO]-->
SN User (autonomous_identity)

Cross-System Boundary:
SN User (autonomous_identity)
--[AUTHENTICATES_TO]--> // via client_id match
SP (autonomous_identity)

Entra Side:
SP (autonomous_identity)
--[HAS_ROLE]-->
Role (role)
--[GRANTS]-->
Permission (permission)
--[APPLIES_TO]-->
Graph API (resource)

Edge type count: 9 distinct edge types in this chain (TRIGGERS_ON, CALLS, INVOKES, AUTHENTICATES_VIA, BINDS_TO, AUTHENTICATES_TO, HAS_ROLE, GRANTS, APPLIES_TO)

This is SecurityV0's differentiator: OAA cannot represent this level of execution flow granularity.


12. Final Recommendation to Founder

12.1 Summary

You are correct: A Script Include is NOT an identity. Current typing is wrong.

Proposed fix:

  1. Add new type code_artifact for passive code libraries (Script Includes, Lambda code, stored procedures)
  2. Change Script Include from autonomous_identitycode_artifact
  3. Change OAuth Entity from autonomous_identitycredential with subtype authentication_binding
  4. Keep Business Rules, Flows, Scheduled Jobs as autonomous_identity (they DO execute autonomously)
  5. Keep REST Messages as resource (they are API configuration data)

Why this is correct:

  • Execution capability test: Can it authenticate and execute on its own? YES → identity. NO → not identity.
  • Script Include fails this test — it's called BY identities, it doesn't execute on its own.
  • OAuth Entity fails this test — it's a credential binding, not an executor. The bound sys_user is the identity.

Implementation cost: ~16 hours (minimal approach) to ~24 hours (full semantic clarity)

Breaking change risk: Zero (no active clients)

OAA compatibility: Maintained via export projection layer (Round 4 design)

12.2 What This Unlocks

  1. Correct semantic queries:

    • "Show me all code artifacts" → returns Script Includes, Lambda code, etc.
    • "Show me all identities that can execute autonomously" → returns BRs, Flows, Jobs, SPs (NOT Script Includes)
  2. Correct execution chain modeling:

    • BR --[CALLS]--> SI --[INVOKES]--> REST Message
    • Clear distinction between "executes" (identity) and "is executed" (code artifact)
  3. Correct cross-system authentication modeling:

    • OAuth Entity --[BINDS_TO]--> SN User --[AUTHENTICATES_TO]--> SP
    • Clear that OAuth Entity is authentication material, not an identity itself
  4. OAA export correctness:

    • Script Includes export as Resources (with resource_type: "code")
    • OAuth Entities export as authentication properties on local_users (not as separate identities)

12.3 Approval Request

Question for Founder:

Do you approve the following schema changes?

  1. Add code_artifact as a new NormalizedNodeType
  2. Add CALLS, INVOKES, BINDS_TO as new NormalizedEdgeTypes
  3. Reclassify Script Include: autonomous_identitycode_artifact
  4. Reclassify OAuth Entity: autonomous_identitycredential
  5. Keep Business Rules, Flows, Scheduled Jobs as autonomous_identity
  6. Keep REST Messages as resource (with resourceSubtype: "api_endpoint")

If approved, implementation can begin immediately with ~2-day turnaround.


13. Open Questions

  1. Should App Registration (Entra) be a separate type? Currently proposed as resource with subtype application_template. Alternative: new type application_definition.

  2. Should we model trigger configuration separately? Currently, Business Rule includes its trigger (table, when, filter). We could split this into:

    • BR node (executor)
    • Trigger node (event configuration)
    • Edge: Trigger --[ACTIVATES]--> BR

    This would be more granular but adds complexity. Recommend: defer until needed.

  3. How to handle Lambda Functions (AWS)? The function CODE is a code_artifact, but the function INVOCATION is an autonomous_identity. Do we model these as two separate nodes? Or one node with dual aspects?

    Proposed: Two nodes:

    • code_artifact (the Lambda code package)
    • autonomous_identity (the Lambda execution role)
    • Edge: Identity --[EXECUTES]--> CodeArtifact
  4. Should GitHub Actions workflows be code_artifact or autonomous_identity? They define BOTH code (YAML) and execution (on trigger).

    Proposed: Two nodes:

    • code_artifact (the workflow YAML)
    • autonomous_identity (the workflow run instance)

End of Round 5 Analysis. Total: 681 lines.