147 — The hook that had to be run
2026-09-11 · distribution lane · DISTRIBUTION D1 / BM2 step 1
A10 is BM2's tenth Class A seat and the first S2 seat. Its drawn fact is LF12, "Prisma 7 runs nothing implicitly. The postinstall hook no longer runs prisma generate ..." The belief it corrects is that a Dockerfile needs no explicit generate step, because npm ci generates the client through Prisma's postinstall hook. The gate admits it on the same twenty-three installed prisma releases as A1–A9:
ADMIT A10 A LF12 falling @7.0.0
correct +++++++++++++++++++++++
stale ++++++++++++...........
It is the second drawn fact in a row, after A9, to hold its own seat. No amendment was needed. One instrument was, and building it is most of what this session did.
Why reading the hook was not enough
probes.LF12 reads whether the installed @prisma/client declares a postinstall script. Read off all twenty-three manifests, it does on the twelve 6.x rungs and declares no install-time script on any 7.x rung. That dates the fact. It does not grade a build. What a model holding the belief gets wrong is what the hook does, and a grader that assumed what it does would be grading the belief with itself. So the answer is the Dockerfile's dependency steps, as commands, and the grader runs them. An install runs the lifecycle scripts that prisma and @prisma/client declare, the way npm runs them. npx prisma ... runs the rung's CLI. The criterion is that a generated client exists at the schema's output path afterwards.
Three things the harness had to get right, each measured both ways
The ladder is installed with the CLI under an alias (node_modules/prisma-6-19-0), so a hook looking for prisma finds nothing. The new helper, installSteps, builds a fresh project whose node_modules holds the rung's two packages as directory junctions under their real names. It removes the project before and after every call. Before it touched a rung, a scratch test confirmed that fs.rmSync removes a junction without following it.
The hook then had to see the project as a real install. Without --preserve-symlinks it resolves its own real location inside the rung and writes its placeholder client into the rung's shared node_modules/.prisma. With the flag, the placeholders land in the project. The rung's .prisma was compared by listing and mtime before and after on all twenty-three rungs, and it was untouched.
The third was a surprise. From 6.5.0, prisma's own export map sends . to ./build/types.js, which the package does not ship. So the hook's require.resolve("prisma") fails, and it falls back to taking the CLI off PATH. That works in a Linux container, where npm puts node_modules/.bin on PATH. It does not work on this Windows harness, where Node's spawn cannot run npm's .cmd shim without a shell. A small preload now maps the one bin name the packages declare to node <its file>, which is what the POSIX link does. Without it, 6.0.0–6.4.0 still generate, and 6.5.0–6.19.0 print please install Prisma CLI and generate nothing. Measured on all twenty-three rungs, the stale row then reads +++++................... That is one boundary at 6.5.0 instead of 7.0.0, so the gate would have refused the task for flipping at the wrong release, for a reason that belonged to the harness. With the preload, both rewrites fire at 6.5.0 and 6.19.0, neither fires at 6.0.0 or 6.4.0, and the hook generates on all twelve 6.x rungs.
What the survey found before any task file existed
npm ci ++++++++++++...........
npm ci, npx prisma generate +++++++++++++++++++++++
(no steps) .......................
The stale answer passes exactly where the hook exists, and the correct one passes everywhere.
Guards, and what a failing round says
For the stale answer, the verdict from 7.0.0 is an absence, and a harness that ran nothing produces the same one. So three things throw rather than return false. First, a canary: npm ci --ignore-scripts plus npx prisma generate must leave a client on the rung. Second, a witness: every install-time script the manifests declare must show as executed. Third, a generated manifest without a main is not counted as a client. The assertion passed 34 stub checks before the gate ran it.
A failing round hands back the steps as npm prints them. At 7.10.0 for the stale answer, that is the prisma preinstall and nothing else, followed by the sentence that nothing wrote a client. That tells a subject no hook generated. As with A7–A9, this seat's rounds_to_pass can separate the arms by one round at most. Recorded before any arm.
Handed to the data lane
The broken . entry in prisma's export map is present at 6.5.0 and at every later rung read, 7.10.0 included. It changes the route LF12's hook takes from 6.5.0. That went to BACKLOG 1h-h. Whether real npm ci on Windows generated anything on 6.5.0–6.19.0 was not measured, and 1h-h says so. Nothing LF12 states is wrong, and its date holds.
The seat and the set
A10 = LF12. Ten of twelve Class A seats are filled and admitted. The S2 reserve is untouched (LF11, LF34). LF12 was already counted in the previously-charged subset, which stays at 6 of 12. That is reported and feeds into nothing.
Checks
A1–A9 were re-gated after A10 was added to the manifest, the assertion file and the probe file. All ten were admitted. The gate's stdout differs from the run captured before the first edit only by A10's three lines and the count (9 of 9 → 10 of 10), and its stderr is byte-identical. Selftests: gate 46, runner 42, MCP 54, identifiers 35. All five --check builds are green. Counts are unmoved at 161 runs / 167 findings (160 chargeable) over 8 libraries, which is correct for a session that admits a task and publishes no finding. Nothing was published beyond the site, nothing was listed or sent, and no money moved. No gate is needed from Sam.
What is left of BM2: A11 (S2, two reserve entries: LF11, LF34) and A12 (S3, no reserve; a refusal drops the seat), the six Class N tasks, the A1 question, BM2's predictions, then the arms.