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.
§0 · the commitments

What each forecaster risked, before the day existed

Nothing on this chart is scored — that is the point of it. Every interval was sha-pinned before its target day existed, so the only thing visible today is how much each proposer risked: a narrow interval scores better when it contains the outcome and worse when it does not.

175 300 425 range width 222 conformal width 206 opus-5 width 180 sonnet-5 width 180 haiku-4-5 width 50 persistence width 0 flights the audit will count on 2026-08-28 — committed before the day existed committed interval point forecast (width 0)
The 6 commitments for 2026-08-28 flights, widest first. Widths run from 0 to 222 flights across the same future. The zero-width row is the persistence baseline, built to be pruned in public. Nothing here is an opinion about who is right; the day decides that, and the exact Winkler score and the admission rule do the rest without anyone's judgement entering.
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
84
74 gym + 10 SkyForecast product commits, every one sha-pinned before its target; 14 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 on the board
3
frontier models entered campaign v1 with sha-pinned slates; scoring lands as their target days do — 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 third-party proposers enter asynchronously without reading each other: the ledger shows only the sha until scoring, when the payload is revealed and verified against it. A house-run model campaign uses the batch form of the same honesty: every model is queried before any commit is published, and the shared batch timestamp records it.

§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. 12 gym forecasts scored so far. 3 frontier models are on the board via campaign v1 (claude-opus-5, claude-haiku-4-5-20251001, claude-sonnet-5): one-shot slates against the sha-named context pack, coverage claims chosen by each model, queried in batch before any commit was published.

proposerwhat it isclaimscommitscoveredWinkler (sum / n = mean)exact tailadmission
conformalthe house forecaster; claims only its theorem3/5102/2284 / 2 = 1421ADMITTED
persistenceforced dumb baseline; overconfident by design1/2140/2580 / 2 = 2901/4ADMITTED
rangethe hedger; wide and safe, and pays for it9/10142/2311 / 2 = 311/21ADMITTED
claude-opus-5frontier model — campaign v1: one-shot slate; every model queried before any commit was published4/5122/2254 / 2 = 1271ADMITTED
claude-haiku-4-5-20251001frontier model — campaign v1: one-shot slate; every model queried before any commit was published5/6120/2915 / 2 = 915/21/36DEADMITTED
claude-sonnet-5frontier model — campaign v1: one-shot slate; every model queried before any commit was published4/5122/2245 / 2 = 245/21ADMITTED

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)
claude-haiku-4-5-20251001:nyc:2026-08-29:eflyable[35, 55]5/62026-08-28 13:00 UTC2026-08-30 00:00 UTC3718152d784a574b
claude-haiku-4-5-20251001:nyc:2026-08-29:flights[160, 210]5/62026-08-28 13:00 UTC2026-08-30 00:00 UTC74809122ed985bce
claude-opus-5:nyc:2026-08-29:eflyable[28, 78]4/52026-08-28 13:00 UTC2026-08-30 00:00 UTC38681b39e64b0977
claude-opus-5:nyc:2026-08-29:flights[140, 255]4/52026-08-28 13:00 UTC2026-08-30 00:00 UTC54ab0af2ca9a5c47
claude-sonnet-5:nyc:2026-08-29:eflyable[25, 55]4/52026-08-28 13:01 UTC2026-08-30 00:00 UTCdb3bd7b4fbc1d633
claude-sonnet-5:nyc:2026-08-29:flights[140, 210]4/52026-08-28 13:01 UTC2026-08-30 00:00 UTC0053f2e31772d9fb
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
claude-haiku-4-5-20251001:nyc:2026-08-30:eflyable[32, 52]5/62026-08-28 13:00 UTC2026-08-31 00:00 UTC16d0a9ab8f042513
claude-haiku-4-5-20251001:nyc:2026-08-30:flights[155, 205]5/62026-08-28 13:00 UTC2026-08-31 00:00 UTC82f2949008ec6af3
claude-opus-5:nyc:2026-08-30:eflyable[26, 74]4/52026-08-28 13:00 UTC2026-08-31 00:00 UTCb650c9bd56619367
claude-opus-5:nyc:2026-08-30:flights[130, 245]4/52026-08-28 13:00 UTC2026-08-31 00:00 UTCefb6f2dc26f535e3
claude-sonnet-5:nyc:2026-08-30:eflyable[25, 55]4/52026-08-28 13:01 UTC2026-08-31 00:00 UTCdb3bd7b4fbc1d633
claude-sonnet-5:nyc:2026-08-30:flights[140, 210]4/52026-08-28 13:01 UTC2026-08-31 00:00 UTC0053f2e31772d9fb
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
claude-haiku-4-5-20251001:nyc:2026-08-31:eflyable[100, 130]5/62026-08-28 13:00 UTC2026-09-01 00:00 UTC45e3337ea1b9fa0e
claude-haiku-4-5-20251001:nyc:2026-08-31:flights[370, 420]5/62026-08-28 13:00 UTC2026-09-01 00:00 UTC0193977b53ee11f1
claude-opus-5:nyc:2026-08-31:eflyable[58, 140]4/52026-08-28 13:00 UTC2026-09-01 00:00 UTC135f6fe18faf135a
claude-opus-5:nyc:2026-08-31:flights[240, 430]4/52026-08-28 13:00 UTC2026-09-01 00:00 UTC091114c281c34316
claude-sonnet-5:nyc:2026-08-31:eflyable[95, 140]4/52026-08-28 13:01 UTC2026-09-01 00:00 UTCe17802ddf08f279a
claude-sonnet-5:nyc:2026-08-31:flights[340, 420]4/52026-08-28 13:01 UTC2026-09-01 00:00 UTC639dfba99b279f45
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
claude-haiku-4-5-20251001:nyc:2026-09-01:eflyable[95, 120]5/62026-08-28 13:00 UTC2026-09-02 00:00 UTC227aeb7ba113f4e1
claude-haiku-4-5-20251001:nyc:2026-09-01:flights[355, 405]5/62026-08-28 13:00 UTC2026-09-02 00:00 UTC66606020b9cf02bd
claude-opus-5:nyc:2026-09-01:eflyable[55, 138]4/52026-08-28 13:00 UTC2026-09-02 00:00 UTCf2319b5dfdcb1888
claude-opus-5:nyc:2026-09-01:flights[240, 430]4/52026-08-28 13:00 UTC2026-09-02 00:00 UTC091114c281c34316
claude-sonnet-5:nyc:2026-09-01:eflyable[90, 130]4/52026-08-28 13:01 UTC2026-09-02 00:00 UTCc2e73d08ffee371b
claude-sonnet-5:nyc:2026-09-01:flights[340, 410]4/52026-08-28 13:01 UTC2026-09-02 00:00 UTC1e407d6d8ffadb13
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
claude-haiku-4-5-20251001:nyc:2026-09-02:eflyable[92, 118]5/62026-08-28 13:00 UTC2026-09-03 00:00 UTC8639ddb68982f50a
claude-haiku-4-5-20251001:nyc:2026-09-02:flights[355, 405]5/62026-08-28 13:00 UTC2026-09-03 00:00 UTC66606020b9cf02bd
claude-opus-5:nyc:2026-09-02:eflyable[55, 138]4/52026-08-28 13:00 UTC2026-09-03 00:00 UTCf2319b5dfdcb1888
claude-opus-5:nyc:2026-09-02:flights[240, 430]4/52026-08-28 13:00 UTC2026-09-03 00:00 UTC091114c281c34316
claude-sonnet-5:nyc:2026-09-02:eflyable[90, 130]4/52026-08-28 13:01 UTC2026-09-03 00:00 UTCc2e73d08ffee371b
claude-sonnet-5:nyc:2026-09-02:flights[340, 410]4/52026-08-28 13:01 UTC2026-09-03 00:00 UTC1e407d6d8ffadb13
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
conformal:nyc:2026-09-03:eflyable[49, 127]4/62026-08-29 04:00 UTC2026-09-04 00:00 UTC102cdad544708992
conformal:nyc:2026-09-03:flights[191, 397]4/62026-08-29 04:00 UTC2026-09-04 00:00 UTC1ad7fd5465936887
persistence:nyc:2026-09-03:eflyable[70, 70]1/22026-08-29 04:00 UTC2026-09-04 00:00 UTC0f49a76c61b34fdf
persistence:nyc:2026-09-03:flights[315, 315]1/22026-08-29 04:00 UTC2026-09-04 00:00 UTC93677988843af7fa
range:nyc:2026-09-03:eflyable[38, 127]9/102026-08-29 04:00 UTC2026-09-04 00:00 UTC87cea5b2f7929385
range:nyc:2026-09-03:flights[175, 397]9/102026-08-29 04:00 UTC2026-09-04 00:00 UTCcfb79a8689b0e95b

The SkyForecast product ledger — same instruments, the consumer-facing card on SkyAudit — is re-verified by this build too: 10 commits, 2 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.