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:
patremoved from identity subtypes → added to credential subtypes (per ADR-006 + docs fix)script_include,event_script,transform_mapadded to automation subtypes (per 01-data-model.md)- New CONNECTION_SUBTYPES and CREDENTIAL_SUBTYPES arrays
system_executionstays 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
- TypeScript compilation:
cd sv0-platform && npm run typecheck— must pass - Unit tests:
npm test— all 205 tests pass, zero failures - Integration tests:
npm run test:integration— all 84 tests pass - Manual ingest test: POST a NormalizedGraph with
nodeType: "automation"to verify it's accepted and stored correctly - Regression: Existing seed data (
npx tsx scripts/seed-demo.ts) still works, UI shows same entities - 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
| Risk | Likelihood | Mitigation |
|---|---|---|
Existing tests reference hardcoded IDENTITY_SUBTYPES array length | Medium | Search for .length assertions on IDENTITY_SUBTYPES before modifying |
execution_evidence in ENTITY_TYPES causes storage adapter to try storing it in entities collection | Low | The if (mappedType === "execution_evidence") guard in transformer runs before entity creation — no behavior change |
pat removal from IDENTITY_SUBTYPES breaks existing subtype validation | Medium | Check all files that import from identity-subtypes.ts before modifying |
| mongo adapter AUTOMATION_SUBTYPES Set size change | Low | Adding script_include adds a value that may already exist in data; no harm |