Reproducibility
Same key, same bytes, forever. That is the whole product.
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 six horizons - 30m, 1h, 4h, 24h and (since record_result.v7) 72h and 7d - each with return, MFE and MAE on one closed threshold ladder from 0.1 percent to +400 percent, floored at -100 percent. Read the horizon list from the summary's metric keys, not from memory. 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.v4) 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.