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"withidentitySubtypediscriminator - 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 EXECUTE →
autonomous_identity(BR, Scheduled Job, Flow) - Code libraries → NEW type
code_artifact(Script Include) - API configuration →
resourcewith subtypeapi_endpoint(REST Message) - Authentication bindings →
credential(OAuth Entity/Profile) - Service Principals →
autonomous_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:
| Artifact | Source Table | What It Stores | Can Execute? | Has Identity Context? |
|---|---|---|---|---|
| Business Rule | sys_script | Server-side script triggered by table events (insert/update/delete) | YES — runs on event | YES — runs as current.user or configured user |
| Script Include | sys_script_include | Reusable JavaScript library/function | NO — called by other code | NO — executes in caller's context |
| REST Message | sys_rest_message | HTTP endpoint configuration (URL, headers, auth) | NO — invoked by scripts | NO — config data only |
| OAuth Entity | oauth_entity | OAuth 2.0 client credentials binding (client_id, secret ref, user binding) | NO — authentication material | YES — binds to sys_user for execution context |
| Flow Designer Flow | sys_hub_flow | Visual workflow definition (triggers, actions, conditions) | YES — runs on trigger | YES — runs as flow owner or configured user |
| Scheduled Job | sysauto_script | Cron-style scheduled execution of script | YES — runs on schedule | YES — runs as configured user |
Key observations:
- Business Rules, Flows, Scheduled Jobs can execute autonomously (triggered by event/schedule).
- Script Includes are passive code — they do NOT execute on their own. They are CALLED BY Business Rules/Flows/Jobs.
- 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.
- 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
| Artifact | Graph API Resource | What It Stores | Can Execute? | Has Identity Context? |
|---|---|---|---|---|
| Service Principal | /servicePrincipals/{id} | Application instance in tenant (can authenticate, has roles, owns resources) | YES — authenticates via client credentials | YES — IS an identity |
| App Registration | /applications/{id} | Application definition (redirect URIs, API permissions, secrets) | NO — template for SPs | NO — config data |
Key observations:
- Service Principal IS an identity — it can authenticate, it has roles, it executes actions.
- 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?
- Incident table — RESOURCE (trigger source)
- Business Rule — IDENTITY (executes autonomously on trigger)
- Script Include — CODE ARTIFACT (library called by BR)
- REST Message — RESOURCE (API endpoint config)
- OAuth Entity — CREDENTIAL (authentication binding)
- ServiceNow user — IDENTITY (execution context)
- Service Principal — IDENTITY (cross-system identity)
- 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
| Artifact | Current Type | Current Subtype | Problem |
|---|---|---|---|
| Business Rule | autonomous_identity | business_rule | Semantically OK — does execute autonomously |
| Script Include | autonomous_identity | system_execution | WRONG — it's a library, not an executor. system_execution falsely implies it executes. |
| Scheduled Job | autonomous_identity | scheduled_job | Semantically OK — does execute autonomously |
| Flow | autonomous_identity | flow_designer_flow | Semantically OK — does execute autonomously |
| REST Message | resource | (via resourceType property) | PARTIALLY CORRECT — is a resource, but should use resourceSubtype per schema |
| OAuth Entity | autonomous_identity | oauth_app | WRONG — 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:
- Application — A software system with its own authorization model (users, groups, resources, permissions)
- Local User — An identity local to the application (equivalent to SecurityV0's
autonomous_identityorhuman_identity) - 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 System | Artifact | OAA Mapping |
|---|---|---|
| GitHub | Repository | Resource (with subtype repository) |
| GitHub | Workflow (Actions) | NOT modeled (execution semantics lost) |
| GitHub | Personal Access Token | Local User with custom properties |
| Jira | Project | Resource (with subtype project) |
| Jira | Automation Rule | NOT modeled |
| Snowflake | Stored Procedure | NOT modeled (or modeled as Resource) |
| AWS | Lambda Function | Resource (with subtype function) |
| ServiceNow | Business Rule | NOT 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:
-
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)
-
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.
-
api_configuration— HTTP endpoint definitions (URL, method, headers). Examples:- ServiceNow REST Messages
- AWS API Gateway endpoint configs
- GitHub webhook configs
-
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:
| Approach | Pros | Cons |
|---|---|---|
| New top-level types | Clear 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 only | No 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 Artifact | Source Table | Proposed Type | Proposed Subtype | Rationale |
|---|---|---|---|---|
| Business Rule | sys_script | autonomous_identity | business_rule | Executes autonomously on event trigger; has execution context (run_as user) |
| Script Include | sys_script_include | code_artifact | script_include | Passive library; executed IN CONTEXT of caller (BR, Flow, Job) |
| Flow Designer Flow | sys_hub_flow | autonomous_identity | flow_designer_flow | Executes autonomously on trigger; has execution context (flow owner or configured user) |
| Scheduled Job | sysauto_script | autonomous_identity | scheduled_job | Executes autonomously on schedule; has execution context (run_as user) |
| REST Message | sys_rest_message | resource | api_endpoint | API configuration data; invoked by identities (BR, Flow, Job) |
| OAuth Entity | oauth_entity | credential | authentication_binding | Maps client_id to ServiceNow user context; IS NOT an identity itself |
| ServiceNow User (integration account) | sys_user | autonomous_identity | machine_account | CAN authenticate; CAN execute; IS an identity |
| Incident Table | incident | resource | table | Data acted upon |
5.2 Microsoft Entra Artifacts → NormalizedNodeTypes
| Entra Artifact | Graph API Resource | Proposed Type | Proposed Subtype | Rationale |
|---|---|---|---|---|
| Service Principal | /servicePrincipals/{id} | autonomous_identity | service_principal | Executes autonomously; authenticates via client credentials; has roles |
| App Registration | /applications/{id} | resource | application_template | Configuration template for creating SPs; does NOT execute |
| App Role | appRoles property | role | application_role | Permission grouping |
| OAuth2 Permission Grant | /oauth2PermissionGrants | permission | delegated_permission | Individual capability grant |
| Client Secret | passwordCredentials | credential | oauth_client_secret | Authentication 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 Type | From → To | Meaning | Example |
|---|---|---|---|
CALLS | identity → code_artifact | "This identity invokes this library/function" | BR → Script Include |
INVOKES | code_artifact → resource | "This code calls this API" | Script Include → REST Message |
BINDS_TO | credential → 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_identitytocode_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_chainscollection).
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:
- Trigger source — emits INSERT event that fires the BR →
TRIGGERS_ONedge - Data input — BR reads incident fields →
EXECUTES_ONedge withaction: "read" - Data output — BR writes to incident fields →
EXECUTES_ONedge withaction: "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 →
resourcewithresourceSubtype: "api_endpoint" - OAuth Entity →
credentialwithcredentialSubtype: "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:
-
/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
- Add new entity type descriptions for
-
/Users/lucky/dev/securityv0/sv0-documentation/docs/architecture/05-connectors.md- Update NormalizedNodeType enum documentation
- Update permission normalization guide
- Add code artifact mapping examples
-
/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
- Update TypeScript types (sv0-platform/src/ingestion/types.ts) — 2h
- Update connector transformer (sv0-connectors/.../transformer.py) — 6h
- Update documentation (sv0-documentation/docs/architecture/) — 3h
- Update UI filters (if entity type is exposed in UI) — 2h
- Run full test suite — 2h
- 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_identitynodes withidentitySubtype: "system_execution"→code_artifactnodes - 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 Type | OAA Type | OAA Properties |
|---|---|---|
code_artifact | Resource | resource_type: "code", custom_properties: {sv0_type: "code_artifact", artifactSubtype: "script_include"} |
resource (REST Message) | Resource | resource_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. Recommended Decision
10.1 Minimal Breaking Change Approach
Add ONLY ONE new type: code_artifact
Use existing types for the rest:
- REST Message →
resourcewithresourceSubtype: "api_endpoint" - OAuth Entity →
credentialwithcredentialSubtype: "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_artifactautomation_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
| Artifact | Source System | Proposed Type | Cross-System Role |
|---|---|---|---|
| Business Rule | ServiceNow | autonomous_identity | Executor (initiates chain) |
| Script Include | ServiceNow | code_artifact | Library (called by executor) |
| REST Message | ServiceNow | resource | API config (invoked by code) |
| OAuth Entity | ServiceNow | credential | Auth binding (maps client_id → user) |
| ServiceNow User | ServiceNow | autonomous_identity | Execution context |
| Service Principal | Entra | autonomous_identity | Cross-system identity |
| App Registration | Entra | resource | Template (not executable) |
| Client Secret | Entra | credential | Auth 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:
- Add new type
code_artifactfor passive code libraries (Script Includes, Lambda code, stored procedures) - Change Script Include from
autonomous_identity→code_artifact - Change OAuth Entity from
autonomous_identity→credentialwith subtypeauthentication_binding - Keep Business Rules, Flows, Scheduled Jobs as
autonomous_identity(they DO execute autonomously) - 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_useris 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
-
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)
-
Correct execution chain modeling:
- BR --[CALLS]--> SI --[INVOKES]--> REST Message
- Clear distinction between "executes" (identity) and "is executed" (code artifact)
-
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
-
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)
- Script Includes export as Resources (with
12.3 Approval Request
Question for Founder:
Do you approve the following schema changes?
- Add
code_artifactas a new NormalizedNodeType - Add
CALLS,INVOKES,BINDS_TOas new NormalizedEdgeTypes - Reclassify Script Include:
autonomous_identity→code_artifact - Reclassify OAuth Entity:
autonomous_identity→credential - Keep Business Rules, Flows, Scheduled Jobs as
autonomous_identity - Keep REST Messages as
resource(withresourceSubtype: "api_endpoint")
If approved, implementation can begin immediately with ~2-day turnaround.
13. Open Questions
-
Should App Registration (Entra) be a separate type? Currently proposed as
resourcewith subtypeapplication_template. Alternative: new typeapplication_definition. -
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.
-
How to handle Lambda Functions (AWS)? The function CODE is a
code_artifact, but the function INVOCATION is anautonomous_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
-
Should GitHub Actions workflows be
code_artifactorautonomous_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.