Skip to content

MOD-096 — Multi-entity Party Graph Manager (design)

System: SD02 (Customer Identity & KYC Platform) Repo: bank-kyc Phase: 2 of the Expense Intelligence Platform

1. What it does

MOD-096 maintains the directed graph of party entities for a single customer login: the individual, any sole-trader personas they operate, companies they direct, trusts they trustee, and property-context envelopes they own. Nodes are typed (NATURAL_PERSON / ORGANISATION / ARRANGEMENT top-level via the ADR-064 party.parties taxonomy; INDIVIDUAL / SOLE_TRADER / LIMITED_COMPANY / TRUST / PROPERTY_CONTEXT operational subtype via the V002 column landed in commit 8bba2d1). Edges are typed relationships (IS_DIRECTOR_OF / IS_TRUSTEE_OF / IS_BENEFICIAL_OWNER_OF / OPERATES_AS).

Adding a node: 1. POST /v1/graph/nodes — validates the request, creates the party row, creates the edge, and either (a) PROPERTY_CONTEXT: lands the edge ACTIVE immediately and emits bank.kyc.party_graph_edge_activated; or (b) anything else: lands the edge PENDING_CDD and emits only bank.kyc.party_graph_node_added — activation comes asynchronously.

Activation: - MOD-009 publishes bank.kyc.identity_verified when eIDV completes for a natural-person target. MOD-096's EventBridge consumer rule activates the edge after enforcing the AML-002 GATE (a PASS row must exist in kyc.kyc_checks). - MOD-010 publishes bank.kyc.cdd_tier_assigned when CDD tier is set for a business-entity target. MOD-096's other consumer rule activates that edge. The cdd_tier is itself evidence that EDD has run upstream — AML-004 GATE is satisfied implicitly by the presence of a PASS tier (SIMPLIFIED / STANDARD / ENHANCED).

Reading the graph: - GET /v1/graph/{root_party_id} returns the full set of nodes + edges reachable from the login-rooted natural person. EXITED edges are filtered out. Used by SD08 (bank-app, MOD-094) to render the customer context switcher (FR-798).

2. FRs covered

FR Implementation
FR-796 validateEdge() enforces five subtypes × four edge types; DDL
CHECKs back-stop. Negative integration cases in fr-796.
FR-797 add-node.ts parks edges PENDING_CDD; activate-edge.ts flips
them ACTIVE only when the inbound event carries / can resolve
a PASS kyc_check_id (AML-002 GATE).
FR-798 list-graph.ts traverses bidirectional edges from a NATURAL_PERSON
root.

3. Policy satisfaction

Policy Mode Where
AML-002 GATE activateEdgesForParty() throws COMPLIANCE_BLOCK if no PASS
kyc_check_id is available on the identity_verified path.
Negative test: tests/integration/policy/aml-002-gate.test.ts
AML-004 GATE Edge activation refuses to flip on status=FAILED. EDD
tier (ENHANCED) is treated as evidence that EDD ran upstream
in MOD-010 (per the EDD workflow contract).
Negative test: tests/integration/policy/aml-004-gate.test.ts

4. Tables

Schema.table DDL owner MOD-096 access
party.parties MOD-009 V001/V002 INSERT, SELECT
party.party_relationships MOD-009 V003 INSERT, UPDATE, SELECT
kyc.kyc_checks MOD-009 V001 SELECT (AML-002 evidence)
kyc.idempotency_records MOD-009 V002 INSERT, SELECT (shared idempotency, keyPrefix=MOD-096)

ADR-064 routing: all party.* DDL is centralised in MOD-009 (party_migrate_user owns the schema; kyc_migrate_user has no CREATE on party). MOD-096 ships no migrations of its own.

5. Events

Published (both registered into MOD-043's per-env Schema Registry): - bank.kyc.party_graph_node_added v1 - bank.kyc.party_graph_edge_activated v1

Consumed (via EventBridge rules on the bank-kyc bus): - bank.kyc.identity_verified (from MOD-009) → activate natural-person edges - bank.kyc.cdd_tier_assigned (from MOD-010) → activate business-entity edges

6. SSM outputs

Path Consumer
/bank/{env}/kyc/party-graph/api-endpoint MOD-094 (SD08)
/bank/{env}/kyc/party-graph/function-arn infra tests, MOD-082 dashboards
/bank/{env}/kyc/party-graph/function-name CloudWatch ops
/bank/{env}/kyc/events/party-graph-node-added/schema-arn downstream consumers
/bank/{env}/kyc/events/party-graph-edge-activated/schema-arn downstream consumers
/bank/{env}/kyc/tables/party-relationships/name MOD-072 risk views

Smoke check: node tests/verify-deployment.mjs (STAGE env var).

7. Upstream SSM inputs

Path Owner Required at deploy
/bank/{env}/iam/lambda/bank-kyc/arn MOD-104 yes
/bank/{env}/eventbridge/bank-kyc/arn MOD-104 yes
/bank/{env}/kms/pii/arn MOD-104 yes
/bank/{env}/mod043/schema-registry/name MOD-043 yes
/bank/{env}/observability/adot-layer-arn MOD-076 yes
/bank/{env}/observability/parameters-extension-layer-arn MOD-076 yes
/bank/{env}/kyc/eidv/api-endpoint MOD-009 yes
/bank/{env}/kyc/cdd/function-arn MOD-010 optional
Secret bank-neon/{env}/kyc_app_user MOD-103 yes (Pulumi reads pooled_url at deploy)

8. Orchestrator rulings (build-scope confirmation, 2026-05-18)

  1. party.party_relationships — normalised table with (from_party_id, to_party_id, relationship_type) tuple, UNIQUE on idempotency_key, immutability trigger refusing DELETE + mutation of identity columns, governed lifecycle (no ACTIVE→PENDING_CDD regression, no leaving EXITED), is_ubo auto-flip trigger at ≥25%, CHECK constraint requiring ownership_percentage on IS_BENEFICIAL_OWNER_OF.
  2. No PENDING_CDD timeout — edges sit indefinitely; an EMF CloudWatch alarm (mod-096-{stage}-pending-cdd-stale) fires at the configurable PENDING_CDD_ALERT_HOURS threshold (default 336h = 14 days). Operator surfaces the stale edge via dashboards; no automatic state change.
  3. PROPERTY_CONTEXT bypasses CDD — MOD-094 calls POST /v1/graph/nodes with party_subtype=PROPERTY_CONTEXT; the handler skips the CDD trigger and lands the edge ACTIVE immediately (NO_CDD_SUBTYPES.has(party_subtype)).
  4. Shared idempotency@bank-kyc/shared PostgresIdempotencyStore against kyc.idempotency_records with keyPrefix="MOD-096", plus DB-level UNIQUE on party_relationships.idempotency_key as belt-and-braces (replay across cold containers falls through to the UNIQUE-violation path, which surfaces as 409 CONFLICT at the API).
  5. IS_BENEFICIAL_OWNER_OF — direct-only for v1ownership_percentage is required (CHECK + handler validation); is_ubo flips TRUE at ≥25%. Chain traversal (UBOs of UBOs) is a read-time concern handled by MOD-072.

9. Test plan

Unit (≥80% coverage): tests/unit/ - graph/{validate-edges, add-node, activate-edge, list-graph} - handler, events, db, idempotency, errors, observability, config - lib/{schema-validator, eventbridge-client}

Integration (RUN_INTEGRATION=1, against deployed dev): - fr/fr-796-graph-structure - fr/fr-797-cdd-gate - fr/fr-798-context-switcher - policy/aml-002-gate (NEGATIVE) - policy/aml-004-gate (NEGATIVE) - infra/ssm-outputs - infra/iam-binds-bankkycrole - infra/eventbridge-rules - infra/eventbridge-schema-registry - infra/party-relationships-immutability - nfr/idempotency

Smoke: tests/verify-deployment.mjs.