cert-machine · the eval shelf · forecasting

The Forecast Gym: the test set that cannot leak

A forecasting eval in which contamination is impossible by construction, because the outcomes do not exist yet. Every forecast is committed to an append-only ledger — sha256, timestamp strictly before the target — and scored afterward with a proper score in exact rationals, so honesty is the optimal policy and the reward cannot be gamed. Admission is prune-only: a forecaster exactly-improbably below its own claimed coverage is deadmitted, with the exact binomial tail as the certificate. Forecasts are proposals here, never verdicts — this page never confuses the two.

tl;dr
  • The finding. Hindcast benchmarks cannot prove a model has not seen the outcome; the only outcome provably unseen is one that did not exist when the forecast was committed. The Gym makes temporal hygiene a checkable property: git log certs/forecast-gym-ledger.jsonl dates every sha before its target.
  • The mechanism. commit-before / score-after on an append-only ledger (backdating, premature scoring, rescoring and tampering are refused — each a red control); Winkler interval score in exact rationals; prune-only admission by exact binomial tail at a stated bar of 1/20.
  • Check it. node instruments/forecast/battery.js — 7 checks, 5 reds that must fire, in seconds.
the test set
the future
outcomes that do not exist at commit time — the one split no training corpus can contain
commits on the ledger
40
32 gym + 8 SkyForecast product commits, every one sha-pinned before its target; 0 scored so far
the score
proper
Winkler interval score, exact rationals: uniquely optimized in expectation by the true quantiles — hedging pays in width, overconfidence pays in distance
admission
prune-only
a proposer below its claimed coverage is DEADMITTED with the exact binomial tail as certificate; admission is lost by record, never by opinion
reds at this build
5
backdated commit · premature score · rescore · tampered reveal · the admission rule itself — each must fire or this page does not exist
models graded
0
no model has run yet — the board below holds the house baselines; a campaign is operator-gated spend, like every eval here
§1 · the leak

Why every hindcast benchmark is an answer key

Evaluate a model's forecasting on past data and you are asking a question the training corpus may already answer. Cutoff dates are self-reported, contamination audits are heuristic, and a re-scraped benchmark leaks the moment it is published. No split protocol can prove a negative about what a model has seen.

The Gym inverts the burden. A forecast is committed — content-addressed by sha256, timestamped — while the outcome is still in the future, so "the model saw the answer" is not improbable but impossible: the answer did not exist. Hygiene is then a property a stranger can check mechanically: the ledger is append-only in a public git history, so every commit carries two independent timestamps (the row's, and git's) that must both precede the target day. The same discipline that removes the answer key from the matmul eval — there the reference is a proof, here the reference is the future.

§2 · the mechanism

Commit before, score after, never rewrite

A commit row pins {id, target, madeAt, targetTime, sha256(forecast)} with madeAt < targetTime enforced — a backdated commit is refused, a premature score is refused, a second score of the same id is refused, and a sealed commit whose revealed payload does not hash to its committed sha is refused as tampered. Each refusal is a red control in the battery: the mechanism is proved able to say no before it is trusted to say yes.

Sealed commits are how competing proposers enter without reading each other: the ledger shows only the sha until scoring, when the payload is revealed and verified against it. A model campaign commits sealed; the reveal is part of the scoring transcript.

§3 · the score

A reward that makes honesty the optimal policy

Every scored forecast pays the Winkler interval score at its own committed miss-rate α: width + (2/α) · distance-outside, computed in exact rationals and stored as a string. The score is strictly proper for central intervals — its expectation is uniquely minimized by reporting the true quantiles — so there is no strategy against it: hedge wide and the width term pays, claim narrow and the miss term pays. From the battery's hand-computed row: forecast [10, 20] at α = 1/4 scores 10 when the outcome is 15, and 10 + 8·6 = 58 when the outcome is 26.

This is the probabilistic sibling of certify(): there, a claim is decided and the reward cannot pay a false claim; here, a forecast is scored and the reward cannot pay a dishonest one better than an honest one — in expectation, which is the strongest thing a probabilistic channel can promise. Both channels share the discipline: append-only ledgers, red controls first, exact arithmetic everywhere a number is decided.

§4 · admission

Prune-only: below your claimed coverage, you stop being admitted

Doctrine: prediction enters as a proposer, never an authority, and a proposer that misses its certified coverage stops being admitted until recalibrated. The Gym computes that rule instead of judging it. A proposer claiming coverage p with k of m scored forecasts covered is held to the exact binomial tail P[X ≤ k], X ~ Binomial(m, p) — BigInt rationals, no float — and is DEADMITTED when the tail falls to 1/20 or below. The verdict reads: "if the claimed coverage were true, a record this bad or worse has this exact probability." The tail is the certificate; the bar is a stated convention; only under-coverage prunes, because over-coverage is the conservative direction and the score already prices it.

A deadmitted proposer's new commits are refused by the gym runner, and its deadmission stands in the ledger permanently — wrong forecasts are exhibits here, not embarrassments. The forced dumb baseline below exists to be pruned in public: the mechanism firing on schedule is the demonstration.

§5 · the board

The standing record, recounted from the ledger at this build

Pack v1 is SkyAudit's pinned day series: two quantities per day (total rotorcraft flights, E-FLYABLE count under the pinned spec/rule pair), forecast for named future days. Three house proposers run — deterministic functions of the pinned series, so every commit is rerunnable. No gym forecast has been scored yet — the first targets are still in the future; the outstanding commits below are the record aging in public. No model has run: model campaigns enter sealed, on the operator's word, and are graded by exactly this machinery.

proposerwhat it isclaimscommitscoveredWinkler (sum / n = mean)exact tailadmission
conformalthe house forecaster; claims only its theorem3/58ADMITTED
persistenceforced dumb baseline; overconfident by design1/212ADMITTED
rangethe hedger; wide and safe, and pays for it9/1012ADMITTED

Outstanding commits — sha-pinned, waiting for their day to exist. Lead time is on the record: a forecast committed minutes before midnight is worth less than one committed days out, and the ledger shows which is which.

idintervalclaimscommitted attarget closessha256 (prefix)
conformal:nyc:2026-08-28:eflyable[49, 127]3/52026-08-28 03:54 UTC2026-08-29 00:00 UTCd6a96524f4d9f830
conformal:nyc:2026-08-28:flights[191, 397]3/52026-08-28 03:54 UTC2026-08-29 00:00 UTCb069bd8df3bc4898
persistence:nyc:2026-08-28:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-08-29 00:00 UTC264d40311893de18
persistence:nyc:2026-08-28:flights[191, 191]1/22026-08-28 03:54 UTC2026-08-29 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-08-28:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-08-29 00:00 UTC01bfe750f62b4535
range:nyc:2026-08-28:flights[175, 397]9/102026-08-28 03:54 UTC2026-08-29 00:00 UTCfcffc4a4dea7f5e7
persistence:nyc:2026-08-29:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-08-30 00:00 UTC264d40311893de18
persistence:nyc:2026-08-29:flights[191, 191]1/22026-08-28 03:54 UTC2026-08-30 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-08-29:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-08-30 00:00 UTC01bfe750f62b4535
range:nyc:2026-08-29:flights[175, 397]9/102026-08-28 03:54 UTC2026-08-30 00:00 UTCfcffc4a4dea7f5e7
persistence:nyc:2026-08-30:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-08-31 00:00 UTC264d40311893de18
persistence:nyc:2026-08-30:flights[191, 191]1/22026-08-28 03:54 UTC2026-08-31 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-08-30:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-08-31 00:00 UTC01bfe750f62b4535
range:nyc:2026-08-30:flights[175, 397]9/102026-08-28 03:54 UTC2026-08-31 00:00 UTCfcffc4a4dea7f5e7
conformal:nyc:2026-08-31:eflyable[49, 127]3/52026-08-28 03:54 UTC2026-09-01 00:00 UTCd6a96524f4d9f830
conformal:nyc:2026-08-31:flights[191, 397]3/52026-08-28 03:54 UTC2026-09-01 00:00 UTCb069bd8df3bc4898
persistence:nyc:2026-08-31:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-09-01 00:00 UTC264d40311893de18
persistence:nyc:2026-08-31:flights[191, 191]1/22026-08-28 03:54 UTC2026-09-01 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-08-31:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-09-01 00:00 UTC01bfe750f62b4535
range:nyc:2026-08-31:flights[175, 397]9/102026-08-28 03:54 UTC2026-09-01 00:00 UTCfcffc4a4dea7f5e7
conformal:nyc:2026-09-01:eflyable[49, 127]3/52026-08-28 03:54 UTC2026-09-02 00:00 UTCd6a96524f4d9f830
conformal:nyc:2026-09-01:flights[191, 397]3/52026-08-28 03:54 UTC2026-09-02 00:00 UTCb069bd8df3bc4898
persistence:nyc:2026-09-01:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-09-02 00:00 UTC264d40311893de18
persistence:nyc:2026-09-01:flights[191, 191]1/22026-08-28 03:54 UTC2026-09-02 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-09-01:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-09-02 00:00 UTC01bfe750f62b4535
range:nyc:2026-09-01:flights[175, 397]9/102026-08-28 03:54 UTC2026-09-02 00:00 UTCfcffc4a4dea7f5e7
conformal:nyc:2026-09-02:eflyable[49, 127]3/52026-08-28 03:54 UTC2026-09-03 00:00 UTCd6a96524f4d9f830
conformal:nyc:2026-09-02:flights[191, 397]3/52026-08-28 03:54 UTC2026-09-03 00:00 UTCb069bd8df3bc4898
persistence:nyc:2026-09-02:eflyable[49, 49]1/22026-08-28 03:54 UTC2026-09-03 00:00 UTC264d40311893de18
persistence:nyc:2026-09-02:flights[191, 191]1/22026-08-28 03:54 UTC2026-09-03 00:00 UTC78f9d7e07ae8f651
range:nyc:2026-09-02:eflyable[38, 127]9/102026-08-28 03:54 UTC2026-09-03 00:00 UTC01bfe750f62b4535
range:nyc:2026-09-02:flights[175, 397]9/102026-08-28 03:54 UTC2026-09-03 00:00 UTCfcffc4a4dea7f5e7

The SkyForecast product ledger — same instruments, the consumer-facing card on SkyAudit — is re-verified by this build too: 8 commits, 0 scored, every Winkler recounted. One mechanism, two surfaces.

§6 · enter

How a model (or you) gets on the board

A proposer is anything that emits interval forecasts with a claimed coverage for the named future targets. Submissions are sealed commits: the sha enters the public ledger before the target day, the payload is revealed and hash-verified at scoring, attribution required. Scoring and admission are deterministic, so a submission cannot be argued with — only outperformed.

node tools/forecast-gym.js record prints the board; node instruments/forecast/battery.js proves the machinery on your machine first, reds included. Model campaigns through the same path are spend and run on the operator's word, like every campaign on the eval shelf.