Skip to main content

Phase A0 — Entity Type Reclassification Compatibility Layer

Date: 2026-02-14 Status: COMPLETE — implemented and verified (207 unit + 96 integration tests pass) Parent plan: Implementation Plan — Entity Reclassification + Execution Chains + OAA Export ADRs: ADR-006, ADR-007


Context

The architecture docs and ADRs (006-009) have been completed. The platform code needs to catch up. This is Phase A0 of Workstream A from the implementation plan — the compatibility layer that makes the platform accept new entity types without changing behavior for existing data.

What's already done:

  • A0.1 (ADRs 006-009): COMPLETE
  • A4.3 (Architecture docs 00-07 + glossary): COMPLETE
  • Third-party review fixes: COMPLETE (ownership_ambiguous, OAA mapping, evidence signing gap, pat fix)

Goal: After this phase, the platform accepts identity, automation, connection as NormalizedNodeType values alongside the legacy autonomous_identity / human_identity. New edge types CALLS, INVOKES, USES, AUTHENTICATES_AS are accepted. ENTITY_TYPES expands from 6 to 9. All 205 existing unit tests + 84 integration tests still pass. Zero behavior change.


Files to Modify (6 files modified, 1 explicitly deferred)

1. sv0-platform/src/domain/entities/types.ts — Expand EntityType

Current: 6 types: identity, owner, role, permission, resource, credential Target: 9 types: add automation, connection, execution_evidence

export const ENTITY_TYPES = [
"identity",
"automation",
"connection",
"credential",
"owner",
"role",
"permission",
"resource",
"execution_evidence"
] as const;

Why execution_evidence? It's already handled as a special case in mapNodeType() (returns "execution_evidence" but cast away from EntityType). Making it a proper EntityType aligns the type system with the data model. The existing ExecutionEvidenceDoc goes to a separate collection, so no storage behavior changes.

2. sv0-platform/src/ingestion/types.ts — Expand NormalizedNodeType + NormalizedEdgeType

NormalizedNodeType — add 3 new canonical types, keep 2 legacy aliases:

export type NormalizedNodeType =
| "identity" // NEW: direct mapping (SP, OAuth app, machine account)
| "automation" // NEW: execution logic (BR, SI, Flow, Scheduled Job)
| "connection" // NEW: outbound configs (REST Message, SOAP, HTTP)
| "autonomous_identity" // LEGACY: accepted, maps to "identity" in transformer
| "human_identity" // LEGACY: accepted, maps to "owner" in transformer
| "role"
| "permission"
| "resource"
| "credential"
| "execution_evidence";

NormalizedEdgeType — add 4 new types:

export type NormalizedEdgeType =
| "CALLS" // NEW: automation → automation (BR→SI)
| "INVOKES" // NEW: automation → connection (SI→REST)
| "USES" // NEW: connection → credential (REST→OAuth)
| "AUTHENTICATES_AS" // NEW: credential → identity (OAuth→SP)
| "OWNED_BY"
| "BELONGS_TO"
| "HAS_ROLE"
| "GRANTS"
| "APPLIES_TO"
| "AUTHENTICATES_TO"
| "AUTHENTICATES_VIA" // LEGACY: still accepted
| "EXECUTES_ON"
| "RUNS_AS"
| "TRIGGERS_ON"
| "CREATED_BY"
| "DELEGATES_TO"
| "APPROVED_BY"
| "MEMBER_OF";

3. sv0-platform/src/ingestion/graph-transformer.ts — Expand mapNodeType()

Add cases for new NormalizedNodeType values. Keep all existing cases.

function mapNodeType(nodeType: NormalizedNodeType): EntityType {
switch (nodeType) {
// New canonical types (from updated connectors)
case "identity": return "identity";
case "automation": return "automation";
case "connection": return "connection";
// Legacy types (from existing connectors — still accepted)
case "autonomous_identity": return "identity";
case "human_identity": return "owner";
// Unchanged
case "role": return "role";
case "permission": return "permission";
case "resource": return "resource";
case "credential": return "credential";
case "execution_evidence": return "execution_evidence";
}
}

Note: Return type changes from EntityType | "execution_evidence" to just EntityType since execution_evidence is now in ENTITY_TYPES. The existing if (mappedType === "execution_evidence") guard at line 97 still works.

4. sv0-platform/src/api/routes/ingest.ts — Expand validators

NODE_TYPES (line 10-18): Add "identity", "automation", "connection" alongside legacy values.

const NODE_TYPES = [
// New canonical types
"identity",
"automation",
"connection",
// Legacy aliases (still accepted during migration)
"autonomous_identity",
"human_identity",
// Unchanged
"role",
"permission",
"resource",
"credential",
"execution_evidence"
] as const;

EDGE_TYPES (line 20-35): Add "CALLS", "INVOKES", "USES", "AUTHENTICATES_AS".

const EDGE_TYPES = [
// New canonical types
"CALLS",
"INVOKES",
"USES",
"AUTHENTICATES_AS",
// Preserved types
"OWNED_BY",
"BELONGS_TO",
"HAS_ROLE",
"GRANTS",
"APPLIES_TO",
"AUTHENTICATES_TO",
"AUTHENTICATES_VIA", // Legacy, still accepted
"EXECUTES_ON",
"RUNS_AS",
"TRIGGERS_ON",
"CREATED_BY",
"DELEGATES_TO",
"APPROVED_BY",
"MEMBER_OF"
] as const;

5. sv0-platform/src/domain/graph/identity-subtypes.ts — Split into per-entity-type arrays

Current: Single flat array of 11 subtypes (includes automation subtypes mixed with identity subtypes + pat).

Target: Separate canonical arrays per entity type, matching 01-data-model.md exactly.

// Identity subtypes — things that authenticate
export const IDENTITY_SUBTYPES = [
"service_principal",
"oauth_app",
"github_app",
"machine_account",
"agent",
"bot",
"integration_user",
] as const;

// Automation subtypes — execution logic artifacts
export const AUTOMATION_SUBTYPES = [
"business_rule",
"script_include",
"flow_designer_flow",
"scheduled_job",
"event_script",
"transform_map",
"system_execution",
] as const;

// Connection subtypes — outbound communication configs
export const CONNECTION_SUBTYPES = [
"rest_message",
"rest_method",
"soap_message",
"http_connection",
] as const;

// Credential subtypes — authentication material
export const CREDENTIAL_SUBTYPES = [
"oauth_provider",
"oauth_profile",
"api_key",
"certificate",
"client_secret",
"pat",
] as const;

export type IdentitySubtype = (typeof IDENTITY_SUBTYPES)[number];
export type AutomationSubtype = (typeof AUTOMATION_SUBTYPES)[number];
export type ConnectionSubtype = (typeof CONNECTION_SUBTYPES)[number];
export type CredentialSubtype = (typeof CREDENTIAL_SUBTYPES)[number];

// Combined — for backward compatibility in filters that accept any subtype
export const ALL_NHI_SUBTYPES = [
...IDENTITY_SUBTYPES,
...AUTOMATION_SUBTYPES,
...CONNECTION_SUBTYPES,
...CREDENTIAL_SUBTYPES,
] as const;

Key changes:

  • pat removed from identity subtypes → added to credential subtypes (per ADR-006 + docs fix)
  • script_include, event_script, transform_map added to automation subtypes (per 01-data-model.md)
  • New CONNECTION_SUBTYPES and CREDENTIAL_SUBTYPES arrays
  • system_execution stays in AUTOMATION_SUBTYPES (it's a catch-all for unclassified automation artifacts)

6. sv0-platform/src/storage/mongo/adapter.ts — Update AUTOMATION_SUBTYPES reference

Line 582-584: Private static AUTOMATION_SUBTYPES set used in execution_flow traversal.

Update to import from the canonical source instead of hardcoding:

import { AUTOMATION_SUBTYPES } from "../../domain/graph/identity-subtypes.js";

// Replace static hardcoded set:
private static readonly AUTOMATION_SUBTYPE_SET = new Set(AUTOMATION_SUBTYPES);

Also update the reference at line 600 from MongoStorageAdapter.AUTOMATION_SUBTYPES to MongoStorageAdapter.AUTOMATION_SUBTYPE_SET.

7. sv0-platform/ui/src/hooks/use-automations.ts — Keep entity_type: "identity" for now

No change in Phase A0. The UI currently queries entity_type: "identity" with automation subtypes, which still works because autonomous_identity still maps to entity_type: "identity" for existing connector data. This changes in Phase A1 when the connector emits automation type directly.


What This Does NOT Change (Deferred to Phase A1+)

  • Path materializer: Still filters entity_type === "identity" — no new entity types enter path computation
  • Evaluator: Still only evaluates entity_type === "identity" entities
  • Sync ingestion handler: Still filters to identity before path materialization
  • Evidence pack builder: Still assumes identity-type entities
  • API path routes: blast-radius/accessible-by still gate on entity_type
  • Normalizer: No autonomous_identity → automation reclassification normalizer yet
  • UI: No automation/connection/credential entity type pages yet
  • Connector: Still emits autonomous_identity (Phase A2)

Why this is safe: Old connector data flows through unchanged. The only new behavior: if a future connector sends nodeType: "automation", it will be accepted, stored as entity_type: "automation", but won't get execution paths computed (path materializer skips it). That's correct — path materializer update is Phase A1.


Verification

  1. TypeScript compilation: cd sv0-platform && npm run typecheck — must pass
  2. Unit tests: npm test — all 205 tests pass, zero failures
  3. Integration tests: npm run test:integration — all 84 tests pass
  4. Manual ingest test: POST a NormalizedGraph with nodeType: "automation" to verify it's accepted and stored correctly
  5. Regression: Existing seed data (npx tsx scripts/seed-demo.ts) still works, UI shows same entities
  6. Grep verification: grep -r "autonomous_identity" src/ — should still appear in types.ts and ingest.ts (legacy alias), plus graph-transformer.ts (legacy mapping case)

Risk Assessment

RiskLikelihoodMitigation
Existing tests reference hardcoded IDENTITY_SUBTYPES array lengthMediumSearch for .length assertions on IDENTITY_SUBTYPES before modifying
execution_evidence in ENTITY_TYPES causes storage adapter to try storing it in entities collectionLowThe if (mappedType === "execution_evidence") guard in transformer runs before entity creation — no behavior change
pat removal from IDENTITY_SUBTYPES breaks existing subtype validationMediumCheck all files that import from identity-subtypes.ts before modifying
mongo adapter AUTOMATION_SUBTYPES Set size changeLowAdding script_include adds a value that may already exist in data; no harm