ADR-0002 — Point-in-time correctness enforced by API shape¶
Status: Accepted · Date: 2026-08-03
Context¶
Look-ahead bias — using information that was not available at the decision time — is the most damaging class of bug in a system like this, because it does not cause failures. It causes good results. A backtest that quietly uses restated financials or today's index membership produces an encouraging equity curve, and the error is discovered only when real money behaves differently.
The usual mitigation is developer discipline: remember to filter by availability date. Discipline fails, and it fails silently.
Decision¶
Make the correct call the only call.
- Every stored fact carries
event_time,available_at, andingested_at. - The feature store's read interface takes a mandatory
as_ofparameter with no default. There is no function that returns "the current value" without one; the live path passesnow()from the clock module. - Serving views filter
WHERE available_at <= :as_of; direct table access outside the feature store is restricted by grant. - No component may call
datetime.now(). Time comes from an injected clock, which is virtual during backtests. A lint rule enforces this outside the clock module. - A CI audit harness replays historical decisions against a database snapshot truncated at the decision time and asserts identical output.
- Delisted instruments are never deleted;
listed_tomarks them inactive. - Retrieval for RAG carries the same time filter — retrieving a future document produces a confident and convincing wrong explanation.
Rationale¶
Every one of these makes the wrong thing require deliberate effort rather than mere forgetfulness. A developer cannot omit the as_of argument, because the call will not compile. They cannot read the wall clock, because the lint rule fails. They cannot delete a bankrupt company, because the schema has no delete path for instruments.
The alternative — code review and convention — has an unbounded failure rate over the life of a codebase, and the failures are invisible in exactly the way that matters.
Consequences¶
Positive. Backtest results are believable. The same code runs live and historically. Reproducing a past decision is a query, not an archaeology project. Compliance verdicts can be recomputed as they stood.
Negative. More verbose call sites. More storage — every fundamental vintage is retained rather than overwritten. Ingestion adapters must determine available_at correctly per source, which is real per-source work and a documented adapter responsibility.
Rejected alternative. Storing only current values and reconstructing history from filing dates. This fails for silently revised macro series and for vendor data delivered on a lag, where the filing date is not the availability date.