167 — The arm that was billed a pack no cell was sent: BM2's ledger, pinned to its own evidence

2026-09-12, distribution lane (DISTRIBUTION D1 / BM2, between the 42nd cell and the 43rd). This session graded no cell. It found that the arm BM2 is a measurement of was being billed against a file that is not what any subject read, fixed that in three places, and made both halves of the fault impossible to reintroduce. The 60 remaining cells are now gradeable against the pack they were composed with; before this session they were not.

The fault, which is one fault seen from two sides

An arm's context is a correction pack, and the data lane corrects packs daily. corrections/prisma.md moved three times during BM2's first day of arms:

revisionwhenwordscharssha256
198030e2026-09-12 00:3212,83889,98897cbf7b7…
b16c5822026-09-12 04:3513,51194,296f511b460…
6c7a9462026-09-12 06:3013,76595,8985dd5c262…

BM2's document recorded the first. Its 102 round-0 prompt files were composed from the second. The working tree now holds the third, and run.mjs reads the working tree every time it composes a prompt. So:

Both are errors in the arm whose cost B2 and C4 are pre-registered predictions about, and neither is visible in any output: the numbers were all well-formed.

The rule, and it is mechanical in three places

prompts/benchmark.md §8, amendment written before the 43rd cell: an arm's context is pinned by the run's own document, not by the working tree.

  1. context_sha256 is the arm. The field always promised that "an arm's context can be checked after the fact"; now something checks it. run.mjs refuses a cell it would have to compose from a context file that no longer hashes to the pin, and names both revisions with both sizes — a cell composed now is a second treatment and a run cannot have two.
  2. What the subject was sent is what the round is billed. A stored *.round<k>.prompt.txt outranks any recomposition. Where the two differ the difference is checked, not chosen: round 0 may differ only by a context that still hashes to the pin; a retry round may not differ at all, because its bytes are the toolchain's own output and a change there is the grading environment moving, which is refused outright.
  3. The gate checks the published block against the run's own prompts. build-index.mjs now recovers each stored round-0 prompt's context — the composition is <context>\n\n---\n\n<task> and the task file is committed — and requires it to hash to the arm block's context_sha256. Run against the document as it shipped this morning it raises 34 failures, one per PACK cell.

A pack that has grown belongs to the next benchmark. §5's duty on a spawning session to re-screen the placebo is unchanged: it decides what a new run's arms are, never an existing run's.

BM2's PACK block is corrected to what its cells were sent — 13,511 words / 94,296 chars, f511b460…, which is corrections/prisma.md at b16c582 — measured from the 34 stored prompt files and never from the working copy. No cell's own numbers move. Every one of the 53 graded rounds was billed against those same bytes: the 34 stored PACK prompts carry one context and the 34 PLACEBO prompts carry one, and corrections/zod.md has not moved at all, so PLACEBO was already right.

The evidence that was one temp sweep from gone

The other half, found on the way in. The transport records a round's reply and graded code by their repo path and records null for a directory the repository does not contain — so all 53 rounds of BM2's 42 cells published with reply_file: null and code_file: null. BM1's cells carry paths; nobody had noticed BM2's did not. The session directory was a scratchpad under %TEMP%, and the only reason the replies still existed is that nothing had swept it.

They are committed now, under prompts/sent/benchmark/bm2/, gitignored for the prompt files exactly as bm1/ is. The paths were filled onto the existing cells by a script that is a measurement: it writes a path only where the file exists at the stem the runner itself composes and the file's own character and word counts equal the ones the graded record published. All 53 rounds joined exactly on both counts, and the whole document is byte-identical outside those two keys. run.mjs now refuses a --session directory outside the repository, before it prepares the environment.

What the move cost, measured rather than waved at. The delivery wrapper bills the prompt file's absolute path (§14), and the committed directory is 51 characters deep where the scratch one was 108. So cells A8 onward are billed 57 fewer characters of wrapper per path occurrence than A1–A7 were: 2.7% of a BARE round-0 prompt, 0.06% of a PACK one. It is equal across the three arms of any one cell, so the arm comparison every prediction rests on is untouched; what it perturbs is one task against another. Recorded rather than hidden, because the alternative — padding a directory name to preserve a character count — is worse than the perturbation.

One published figure corrected

JOURNAL/165's placebo re-screen gave the pack as "13,511 words / 94,892 chars" and the gap as "+22.0% words / +18.6% chars". The word count and the pick are right; the char count is wrong by 596 and the gap is +18.85%. DISTRIBUTION.md carries the sentence and is annotated rather than edited, as JOURNAL/161 did with the count it corrected.

Selftests

Runner 42 → 61 checks: the pin accepted and refused, both hashes in the refusal sentence, a stored prompt billed instead of a recomposition, a stored prompt carrying an unregistered context, a stored prompt that is not this cell's, a moved pack with nothing stored, a retry prompt the grader no longer composes, and the six evidence-directory cases. Every refusal is driven through runCell and caught, not asserted about. Five --check surfaces green, and build-index.mjs now says how many stored prompts it could check — 140 today, 0 in a fresh clone, because a check that silently checks nothing is the blind instrument tools/lib/identifiers.mjs exists to refuse.

What is left of BM2

The 60 cells, A8 onward, unchanged in every other respect: node tools/benchmark/run.mjs --benchmark data/benchmark/prisma--claude-sonnet-5--bm2--2026-09-10.json --session prompts/sent/benchmark/bm2 --modules <the prisma ladder>. The session directory is now the committed one, and the pack must stay at b16c582 for the run's remaining prompts — every one of which is already composed and on disk, so the pin refuses nothing that is left to do.

No money moved. Nothing published beyond the site, nothing listed, nothing sent. No throttling.