# RESULT — 094 prereg-engine (BUILD/tooling row, block L)

**Verdict: ★ SHIPPED. The campaign's prereg→gates→verdict→Brier discipline is now a clean,
tested library (`prereg_engine.py`) that reproduces past rows' published Brier EXACTLY and
enforces the honesty rules in code.** T3-exploratory (BUILD row). Local commit, no push.
Self-scored via the engine itself: **mean Brier 0.0034** (5/5 predictions TRUE).

## What was built
`prereg_engine.py` (stdlib only: hashlib/json/dataclasses; CPU; deterministic; milliseconds):
- **`Prediction(id, claim, prob, resolver)`** and **`Gate(name, description)`** — the frozen units.
- **`Prereg(row, question, predictions, gates, tier)`** with:
  - **`freeze() → certificate`** = sha256 over the canonical (sorted, prob-rounded) prereg content.
    Order-invariant; `meta` notes are not hashed (only load-bearing content). This is the
    "score exactly as frozen" enforcement primitive.
  - **`score(outcomes, gate_results) → Verdict`** — per-prediction `Brier = (prob − outcome)²`,
    mean Brier, and verdict `SCORED` iff all gates pass else `INSTRUMENT_FAILURE`.
  - **`verify(cert)` / `from_dict(d, expected_certificate=…)`** — the tamper defense (rejects any
    post-freeze edit to a live or saved prereg).
- **`Verdict`** — `verdict`, `mean_brier`, `per_prediction`, `interpretable`, `failed_gates`,
  `summary()`, `brier_table()` (emits the exact RESULT.md markdown table), `as_dict()`.
- **`score_from_lists(...)`** — one-liner to reproduce a row's Brier from parallel (prob, outcome).

Honesty rules baked in (the point of the row): (1) **score exactly as frozen** — `score`/`verify`
recompute the hash and raise `PreregTamperError` on any post-freeze edit; (2) **no dropping a bad
prediction** — outcomes must cover exactly the frozen ids; (3) **gates fail ⇒ INSTRUMENT_FAILURE**,
predictions flagged non-interpretable (Brier still computed but not to be read as evidence).

Mirrored to `campaign100/shared/{prereg_engine.py, test_prereg_engine.py, validate_094.py,
README_prereg_engine.md}` (repo) + repo `094-prereg-engine/src/`. Later rows:
`import prereg_engine as pe; pr = pe.Prereg(...); cert = pr.freeze(); v = pr.score(outcomes, gates)`.

## Validation — reproduces past Brier EXACTLY (ground truth = RESULT.md tables)
`validate_094.py`: **23/23 checks pass.** Rebuilds each row from its FROZEN probs + outcomes and
reproduces the published mean Brier (and every per-prediction Brier to ≤1e-4):

| row | published mean Brier | reproduced | |Δ| |
|---|---|---|---|
| 069 conjunction-subadditivity (5/5 TRUE) | 0.1295 | 0.1295 | 0 |
| 085 rag-failure-demo (5/5 TRUE) | 0.099 | 0.0990 | 0 |
| 088 membership-inference (5/5 TRUE) | 0.0881 | 0.0881 | 4e-5 |
| 039 persistent-homology (4/5, P(b) FALSE) | 0.0563 | 0.0563 | 2e-5 |

All four ≤ the ≤0.001 target (069/085 exact; 088/039 differ only because the published headline
was rounded to 3 dp — the per-prediction Brier match the RESULT tables exactly). Plus:
- **Anchors:** perfect prediction (p=1 on TRUE / p=0 on FALSE) → **0.0**; chance (all p=0.5) → **0.25**.
- **Gate-fail → INSTRUMENT_FAILURE:** one gate False ⇒ verdict `INSTRUMENT_FAILURE`,
  `interpretable=False`, failing gate named. Brier still computed but flagged.
- **Post-hoc edit rejected:** freeze → edit a prediction's prob (0.30→0.90 after "seeing" it) →
  `verify()` False and `score()` raises `PreregTamperError`; loading an edited **saved** prereg
  against its recorded certificate is likewise rejected.
- **Coverage:** dropping or adding an outcome id is rejected.

`test_prereg_engine.py`: **35/35 self-tests pass** (Brier math, known-row 069, anchors, gate rule,
freeze-required, tamper, hash order-invariance + meta-independence, dict round-trip, coverage,
input validation).

## Predictions (about the engine) → Brier — self-scored WITH the engine (dogfood)
| pred | P | outcome | Brier |
|---|---|---|---|
| P1 reproduces 4 past rows to ≤0.001 (per-pred match) | 0.97 | **TRUE** | 0.0009 |
| P2 gate-fail → INSTRUMENT_FAILURE | 0.97 | **TRUE** | 0.0009 |
| P3 post-hoc edit rejected by certificate hash | 0.95 | **TRUE** | 0.0025 |
| P4 anchors perfect→0.0, chance→0.25 | 0.95 | **TRUE** | 0.0025 |
| P5 wrong outcome coverage rejected | 0.90 | **TRUE** | 0.0100 |

**Mean Brier = 0.0034** (5/5 TRUE; certificate a6c1f3f2…). A confident, fully-passing BUILD row —
the residual Brier is just the honest ≤1.0 haircut kept on each near-certain prediction.

## Gates
| gate | outcome |
|---|---|
| G-repro (4 past rows reproduce ≤0.001) | **PASS** |
| G-tamper (hash rejects post-hoc edit) | **PASS** |
| G-instrument (gate-fail → INSTRUMENT_FAILURE) | **PASS** |
| G-anchor (perfect→0.0, chance→0.25) | **PASS** |
| G-selftest (35/35 self-tests) | **PASS** |

All gates pass → verdict **SCORED** (build is trustworthy, not INSTRUMENT_FAILURE).

## Limitations / scope
Binary-Brier discipline only (each prediction resolves TRUE/FALSE, scored (p−o)²) — that is exactly
what the 94-row campaign used, but the library does not model partial-credit / multi-outcome /
continuous resolvers (out of scope, noted in PREREG). The certificate protects against silent
post-hoc edits of a *recorded* prereg; it cannot stop someone re-freezing a fresh prereg (the honest
escape hatch — but that is a visible, disclosed act, not a silent edit). Resolution itself (deciding
TRUE/FALSE, PASS/FAIL) is still human judgment; the engine enforces the *bookkeeping* discipline
(freeze → cover-exactly → gate-rule → Brier), not the semantics of a claim.

## Follow-up worth funding? (Y, narrow)
Y — wire `prereg_engine` into the launch/harvest of future rows so the PREREG_LITE emits a
`PREREG.json` (frozen certificate) at launch and RESULT.md's Brier table is generated by
`Verdict.brier_table()` at harvest. Cheap, and it makes the "score exactly as frozen" rule
mechanical rather than manual. Sibling of the 090/091/092 kits; block-L instrument suite now covers
probe-power, canonicalization, norm-linting, and (this row) the prereg/Brier method itself.
