Skip to content

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.

  1. Every stored fact carries event_time, available_at, and ingested_at.
  2. The feature store's read interface takes a mandatory as_of parameter with no default. There is no function that returns "the current value" without one; the live path passes now() from the clock module.
  3. Serving views filter WHERE available_at <= :as_of; direct table access outside the feature store is restricted by grant.
  4. 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.
  5. A CI audit harness replays historical decisions against a database snapshot truncated at the decision time and asserts identical output.
  6. Delisted instruments are never deleted; listed_to marks them inactive.
  7. 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.