Where money/time leaks: DSO trapped capital · vendor list-price leakage · bench under-utilisation · NRR erosion · inventory aging · late-stage rework · key-person risk · process bottleneck · hidden sub-group · going-concern · Eisenberg-Noe contagion.
POST /api/control/holding/audit → /ui/holding-audit?audit_id=…
Money-laundering typologies: shell cluster + weak-ID BO · OFAC 50% aggregation · roundtrip ring · structuring · crypto mixer (INDETERMINATE post-3-hop) · TBML misinvoicing · commingled withdrawal · phantom subcontractor · offshore layering · sanctions evasion. Pattern warranting review, NOT a SAR determination.
POST /api/control/forensic/cases/fixture-or-id/fire → /ui/forensic-case?case_id=…
Both lanes on the same graph: typed-dollar diagnostic findings + AML typologies embedded in legitimate IC flow + contagion non-summed. The ATLAS-OMEGA capstone pattern, applied to real client data. Heavier — only when both layers matter.
runs both pipelines sequentially · two audit IDs returned
internal/service/phase525_holding_audit.go::HoldingPayload · 16 top-level arrays · 9 are optional. Cheat-sheet in section ③ below.Minimal mandatory: holding_id, holding_name, consolidation_ccy, entities[]. Findings activate only when their input arrays are populated — empty array → finding routes to INDETERMINATE with the required input named. Honest gap, not a false negative.
| Array | Required for | Schema source |
|---|---|---|
entities · vendors · intercompany | diagnostic + forensic baseline (IC graph, vendor leakage P3, ring detection T3) | HoldingEntity / HoldingVendor / HoldingIntercompany |
ar_aging | DSO trapped capital P2a + annual carry P2b | HoldingARAging (KOK-F-17/21 sanity floor) |
inventory | inventory aging excess P6 (LGD per industry tier) | HoldingInventoryLot |
function_headcount + process_log | bench under-utilisation P4 + process dwell P9 | HoldingFunctionHeadcount + HoldingProcessEvent |
saas_metrics | NRR erosion P5 (forward contingent exposure) | HoldingSaaSMetric |
derivatives + debt_covenants + quarterly_financials | hidden risk layer (Level-3 / going-concern / Eisenberg-Noe contagion) | HoldingDerivative + HoldingDebtCovenant + HoldingQuarterlyFinancial |
input_files | chain-of-custody (every figure traceable to input SHA-256) | InputFileFingerprint — name + sha256 + bytes |
acknowledged_decoys | pre-disclosed legit-twins (regulated sole-source supplier etc.) so detectors don't double-flag | string slice of {entity_id|vendor|finding_id} |
Forensic lane requires additional CSVs (bank_transactions, contracts, ownership, pep_list, sdn_list, crypto_flows, sdn_crypto) — see internal/service/phase552_* for the case-file schema. The forensic intake handles these directly via the case fixture API.
Audit ID (deterministic). Computed at submit-time from sha256(canonical_payload + engine_version + scorer_version). Same client bundle → same audit_id (even across deploys, as long as engine version unchanged). Re-submission is idempotent: the saved scorecard is overwritten with byte-identical content.
Sealed fingerprint. Every figure in the verdict is HMAC-SHA256-hashed: claim → evidence → sealed_fingerprint. HMAC key from KOKON_CAPSTONE_HMAC_KEY / KOKON_PERSONA_HMAC_KEY in .env.prod (set permanently after CAP-FIX 2026-06-29). Hash survives restarts.
Chain of custody. Each report's chain_of_custody panel lists: input file SHAs → engine version → scorer version → per-finding claim hashes → per-decoy/gap evidence hashes → sealed fingerprint. The reader can re-derive the fingerprint from those parts and a published HMAC key (after the audit is closed).
Replay verify. Each report page has a ↻ verify replay bundle button — re-runs the engine on the same input bytes, compares seed SHA and sealed hash. Green = byte-deterministic; amber = HMAC key rotated (still deterministic, just different key).
Where reports land.
Diagnostic: /ui/holding-audit?audit_id=… (same as Meridian / Atlas-V3 / NovaBank)
Forensic: /ui/forensic-case?case_id=… (same as OPERATION TIDEWAY)
Combined: one of each, plus the cross-domain reconciliation if both bundles share the same holding_id.