Reproducibility
Same key, same bytes, forever. That is the whole product.
OUTCOME-HORIZONS SECTION: PROPOSED (DECK-FIRST, PENDING RATIFICATION)The key
Every result carries its full identity, in the body and in the headers:
"reproducibility_key": {
"schema_version": what grammar parsed the document
"normalization_version": what normalization shaped the values
"feature_version": what feature definitions computed the columns
"dataset_revision": what exact data answered
"canonical_query_hash": what exact question was asked
}Those five values pin the answer completely: the same key always returns byte-identical result bytes. Not "equivalent", not "re-computed the same way" - the same bytes. The API streams the engine's canonical bytes verbatim and never re-encodes them, so the promise survives the transport.
How to cite a result
Quote canonical_query_hash + dataset_revision (the headers X-Canonical-Query-Hash + X-Dataset-Revision carry both). Anyone with the same document reproduces the same bytes while the revision stands; the hash alone identifies the QUESTION across revisions. Different callers, different days, same key: same result.
The ETag
The ETag is derived from the full tuple (including the result encoding version). Send it back as If-None-Match and identical data answers 304 Not Modified: free, instant, and proof nothing changed. A changed answer can never hide behind a 304, because any input change changes the tuple.
Freshness: dataset_revision
The feature store appends nightly. A new day of data produces a NEW dataset_revision - never a silent merge into results you already hold. Practically: your cached result stays exactly valid for its revision; re-running the document after the nightly append is a new scan over the wider window, with a new revision in its key. If the revision header changes mid-pagination, the result set changed: restart from page 1.
Honesty in the bytes
Denominators ride inside the result: eligible symbol-buckets, warmup exclusions, absent as absent (never zero). outcomes_summary is computed over ALL occurrences before paging, so rates never come from a page sample. And outcome fields can never be filtered (OUTCOME_IN_PREDICATE): what followed is reported, not selected.
Outcome horizons and the record edge
Every occurrence carries forward outcomes over four horizons: 30m return, 1h MFE/MAE, and (since record_result.v3) 4h and 24h return + MFE/MAE, with a closed threshold ladder of ±1/2/5/10/15/20 percent. One rule keeps the long horizons honest: an anchor closer to the end of recorded data than a horizon has INCOMPLETE forward data, and its outcomes for that horizon are ABSENT - never a truncated excursion, never an implied zero. The absence is counted in the summary's absenttally, so the denominator under every rate is only the matches with a COMPLETE horizon. After the nightly append, yesterday's edge anchors complete - as a new dataset_revision, never a silent merge.
The result shape itself is versioned in-band: result_encoding (currently record_result.v3) joins the ETag, so a shape change can never hide behind a 304. A result_encoding bump is NOT an API version bump - pin on it the way you pin on the ETag. Published reports keep the exact bytes they pinned, whatever encoding they ran under.