Audit isn't a feature you turn on
what you'll learn · Why audit-readiness is a shape decision — the columns you put on every decision-row — not a feature flag you flip on under regulatory pressure.
The moment compliance asks where a trade came from, you find out whether your platform was designed around audit or had it bolted on. The difference is small, structural, and decided years before the conversation.
The question that ends most trading platforms isn’t how do we make this faster? or how do we add another strategy? It’s where did this trade come from? You know the question is coming. The platform doesn’t collapse when the auditor asks it. The platform collapses when you find out the answer lives in four log files, a Slack thread from last quarter, and a notebook on someone’s laptop.
This post is about why “we have logs” is not the answer, and what the platform looks like once you stop pretending it is.
The shape of an audit collapse
The collapse is always the same shape. It looks like this:
- A position shows up that no one expected — too large, too long-held, the wrong side of the trade.
- The operator-on-shift opens the order book, finds the order, finds the fill, finds the strategy slug. So far so good.
- The operator tries to find the thesis — what research justified the position? They check the strategy registry. The strategy lists “features” as a config blob. The features list lists “feature hashes” as a content-addressed key. The hashes don’t correspond to anything in the writing collection.
- The operator tries to reconstruct the thesis from the strategy’s git history. The relevant commit is fine: it links to a notebook that’s been deleted, a research note that’s been superseded twice, and a vendor data source whose API has since changed.
- The operator escalates. The escalation is “we can’t explain this trade.” That’s the end of the platform.
The collapse isn’t a missing log. The collapse is a missing column. Every row in the system — orders, strategies, features, research notes — was perfectly logged. Just not joined to each other in any single shape.
The structural fix
You can’t bolt audit on after the fact. You can bolt it on partially — ELK-stack lineage, version pins, a wiki of “strategy origin stories” — but each one is a different shape that doesn’t compose. The auditor’s question requires a single shape, queryable in one hop, that names both the trade and what justified it.
The fix is the one I’ve described in the citation graph as substrate piece. Every row in the platform that records a decision carries one column:
cited_artifacts: list[typed-prefix-slug]
note:walk-forward-without-leakage, agent:claude-opus-4-7/<run_id>,
event:earnings_release/AAPL-2026-Q2. The slug names what the decision
referenced. The list is the audit trail at the decision moment — not
reconstructed after the fact, captured at the time.
That’s the structural part. The question “where did this trade come from?” becomes one query:
SELECT cited_artifacts
FROM book_provenance
WHERE event_id = (
SELECT provenance_id FROM book_orders WHERE client_order_id = ?
)
The fix is the column. Adding it is cheap now and expensive later (you’d have to backfill, you’d have to migrate, you’d have to retire every code path that wrote a decision without it). Adding it now requires that you commit to one shape across every layer — every strategy, every model registration, every agent run, every sandbox declaration. ADR-0049 is the platform’s way of writing that commitment down.
What audit isn’t
Most of what people call “audit” is observability:
- Did the order land? That’s an exec log.
- Did the feature recompute correctly? That’s a backfill metric.
- Did the model promotion run? That’s a CI artifact.
These are necessary but they’re answering “what happened?” not “why?” The auditor’s question — and the regulator’s, and the post-mortem’s — is the why one. Observability tells you the order landed; the citation graph tells you what justified the order landing.
The two need different substrates. Observability wants firehose-style event streams the operator can grep. The citation graph wants typed-prefix columns and bitemporal joins, the operator can replay against an as-of timestamp. Conflating them — “we have logs, we’ll figure it out” — is how the collapse happens.
Audit-readiness is what makes new things land
The argument I want to leave you with: this isn’t a defensive investment.
The audit substrate is what lets every other ambitious thing land
without becoming its own collapse. AI agents become a typed operator
kind that records agent:<model>/<run_id> in the same column the
human did (ai-agents-as-platform-citizens). Refusal slugs become
queryable error trails for planning agents
(refusal-as-planning-hint).
Bounded discovery runs become candidates a promotion gate can
validate (the-promotion-gate). All of
those compose on the same column.
Without the substrate, each of them is an isolated story — the agent has its own log, the sandbox has its own log, the live book has its own log. The auditor walks away with three logs and no explanation. With the substrate, the auditor walks away with one query.
The structural fix to the audit-collapse is the same fix that lets the platform admit a second operator kind, a parallel research surface, a fourth distribution method. Audit-readiness is the infrastructure of novelty-readiness. The platform that can answer the auditor’s question is the same platform that can add an AI research agent without buying it a separate logging stack.
You don’t choose between auditability and velocity. You choose between substrate-up and feature-down. The teams that chose substrate-up move faster — not despite the column, because of it.
What to do tomorrow
Concretely, for a research platform under construction:
- Pick one citation surface (writing notes, ADRs, signed approvals) and put it behind a typed prefix that every decision-row can reference. ADR-0030 / ADR-0049 are the prior art for the prefix scheme.
- Refuse construction of a decision-row that doesn’t carry the column.
Pydantic / dataclass
__post_init__is enough. The strict-citation policy (ADR-0015) is the same refusal at a different layer. - Wire one path that consumes the column — a /writing/ back-link footer, a /book attribution rollup, a refusal-payload trail — so the column has a visible user from day one. Otherwise it decays.
- Resist the temptation to add per-decision log streams to fill in what the column doesn’t have. The column should be enough, and if it isn’t, the gap is on the column, not on the logging.
The shortest version of this post is: the auditor’s question is a join question. If you’ve planned for the join — one column, on every decision-row — you’ve planned for the question. If you haven’t, you’ve planned for the collapse.