098 — The severity that was set before the fact moved
2026-09-08 · data lane · BACKLOG 11k-t-ii-i-g-a and 11k-t-ii-i-g-d, closed together
Two backlog items had been sitting on the same missing mechanism, and the second one said so: "both of these items are now waiting on the same mechanism — a way to re-score a published finding when the fact under it moves. Consider deciding that instead of these two cases one at a time." This session decided that. The rule is in HARNESS.md; the three cases follow it.
The deadlock, and why it was not real
Both items refused themselves with the same sentence: severity is set in the battery prompt before a draw is read, so moving it afterwards is what the severity-ceiling rule exists to prevent.
The first thing to check was whether that premise is true, and for the case in hand it is not. prompts/prisma.md contains the string "severity" zero times. So does prompts/zod.md, and so does prompts/nextjs.md. The prisma findings the item was about were not scored under any pre-registration at all, and the rule blocking their correction was one they were never subject to.
*The second thing was to read what the prompts that do pre-register severity actually say*, and none of them fixes a number:
- valibot — "Severity is set by the code the draw then ships"
- tailwindcss — "Severity by direction", then three failure modes mapped to S3, S2, S2
- langchain — "set severity ceilings by failure mode, not by change kind", the standing rule from JOURNAL/021
Each fixes a function: if the draw's artifact does X, score it S(n). What the rule forbids is choosing the branch after seeing which score you would prefer. And which failure mode a draw's artifact produces is not a reading of the draw — it is a property of the library, measured on installed releases. When a bisect moves it, the input to the function moved and the function did not. Refusing to re-score then does not preserve the pre-registration; it publishes the number the pre-registration would no longer produce. Applying the original mapping to the corrected input is obedience to it, not an override.
That is the whole decision. The rest is making it structural rather than promised.
The corpus had already been doing half of it, unwritten
Before building anything, the question worth asking was whether the Index had ever moved a published severity. It has, three times, and each time recorded it in prose and nowhere else:
| was | is | forced by | |
|---|---|---|---|
| langchain LF26 | S4 | S2 | three of three subjects producing the wrong form |
| tailwindcss LF5 | S1 | S3 | JOURNAL/041 — the compiler never dropped the aliases |
| valibot LF6 | S1 | S3 | JOURNAL/072 — the bisector, against the shipped declarations |
So for facts, re-scoring on measurement was established practice with no rule behind it. What had never happened is re-scoring a finding — a published charge against a named subject — and that is the case that needs a mechanism, because the score a finding was published at is itself something a reader is owed. The two are not the same operation and the rule now says why: a battery prompt fixes how a draw's failure is scored, not how a library's own row is. A fact has no pre-registration binding it at all.
The obvious mechanism, measured and refused
The tempting design is to derive a finding's severity from its fact: one number, no discretion, nothing to argue about. It is wrong, and the corpus refutes it. Joining every finding to a unique fact by api gives 114 pairs; 108 agree and six disagree, all six legitimately:
next.js F1, F3 LF2 fact S2 silently-wrong -> finding S1 breaks-build
prisma F9, F10 LF14 fact S1 breaks-build -> finding S2 silently-wrong
tailwindcss F1, F2 LF31/33 fact S3 deprecated -> finding S2 silently-wrong
A fact's severity is what the change costs in general. A finding's is what this subject's artifact on this draw costs — tailwindcss's own prompt spends four lines on exactly that split. Deriving one from the other would have overwritten six correct rows to fix two wrong ones.
So the mechanism records the move instead of computing it.
What was built
rescored, in schema/run.schema.json, beside withdrawn and shaped like it. severity/severity_label carry the current score — so every count, the correction packs, the rules files, the MCP tools and the question pages read the right number without knowing the field exists — and from/from_label carry the original. fact, reason and journal say what forced it. The run page prints the move, both scores, the fact and the entry.
Six refusals in build-index.mjs, each exercised on a real mutation of a real file:
severityS1 relabelleddeprecated→ refused- rescored from S1 to S1 → refused (a re-score that moves nothing is a note)
rescored.from_labelnot the label forrescored.from→ refusedrescored.factnamingLF999, which prisma does not have → refusedrescored.factnaming LF18, whose api isenum member @map, not this finding's → refused- rescored dated before the run's own test date → refused
Refusal 5 is the one that matters. It is the record.api === fact.api join JOURNAL/096 established as the join that actually reaches records, and it makes the trigger structurally a measurement: a re-score has to name a fact this finding is about, in a file whose corrections carry their own ladders and citations. A re-score that names no measurable fact is a re-reading of the draw, which the severity rule forbids and this now refuses rather than trusts.
A gate nobody had written, found on the way. severity and severity_label are two fields that must agree, and nothing checked them. Measured before touching anything: 359 rows across every facts file and every run, zero mismatches — the whole corpus held the invariant by hand. That is worth knowing and it is not worth relying on, because a re-score is the first mechanism that edits a published severity and one that moved severity and forgot severity_label would publish "S3 breaks-build" on a page whose entire subject is what a stale belief costs. Paired now on every fact, every finding, and every rescored.from.
The three cases
prisma fable-5 F2 and sonnet-5 F2 — S1 breaks-build → S3 deprecated. These were worse than the backlog recorded. JOURNAL/096 corrected their impact to "Stale setup advice, not a build break" and left severity_label: "breaks-build" above it. The page has been contradicting itself for a day — one field saying the build breaks, the next saying it does not. LF4, measured across twenty-three installed releases, says the prisma-client-js block these draws actually wrote generates into node_modules/@prisma/client at every 7.x release the Index covers. The artifact compiles and runs, which is not S1 under the failure-mode scale; what it costs a reader is setup advice that works today and that Prisma 7 documents moving off, which is S3. prisma/v1 pre-registered no ceiling, so the scale is the binding rule. The charge does not move — 6.6.0 is ten months inside both subjects' stated cutoffs.
better-auth LF4 — S3 deprecated → S1 breaks-build. Scored when the fact read "the SSO plugin speaks SAML", where the stale belief cost you the wrong feature. After two corrections its one dated event is a removal, and its own stale_code — import { sso } from 'better-auth/plugins/sso' — was measured on the full 46-rung ladder not to resolve at any release from 1.4.0. Code that does not resolve does not build. Fourth instance of an established practice, first one under a written rule. No record in the corpus cites this api, checked rather than assumed, so nothing charged moves.
The counts moved, and that is the point
Baseline captured at the top of the session, before it was known which rows would change — the method note from JOURNAL/096, which cost nothing there and cost nothing here.
by_severity_all S1 34 -> 32 S3 23 -> 25 (S2, S4 unchanged)
by_severity_chargeable S1 34 -> 32 S3 23 -> 25 (S2, S4 unchanged)
findings 164, chargeable 157, withdrawn 3, runs 154 — all unchanged
Twelve leaf fields in index.json moved and every one is accounted for: four counts, four fields on the two findings, and four derived sentences that quote the severity mix (two question-page answers, two comparison rows). The finding counts on those pages — 12 for Fable 5 on prisma, 14 for Sonnet 5 — are unchanged, because a re-score is not a retraction.
This is the first session whose headline is that a published number went down. JOURNAL/096's was that thirty-four records moved and no count did; the honest version of that discipline is that when a count should move it moves, and the session says so in the same breath. Two of the Index's thirty-four S1s were not S1s.
What is not decided
The severity-ceiling rule's actual subject — a draw read, then a score chosen to suit it — is untouched and still forbidden. Nothing here licenses re-reading a draw; refusal 5 and bound 2 exist precisely to keep the two apart.
prompts/prisma.md still pre-registers no severity at all, and that is now visible rather than implicit. Whether the next prisma battery should fix a failure-mode mapping in advance is a question for whoever writes it, queued in BACKLOG.