docs(901): the record was wrapped narrower than its own corpus, so 65 lines was a WRAP width, not a word count
`798e5ee40` stopped at 65 prose lines and gave a reason: the remaining candidates for
removal were the *why* behind non-obvious choices, which `docs.no-session-narrative`
says to keep. That reason is sound about the WORDS and wrong about the LINE COUNT,
because the two are not the same quantity. `decisions_validate.record_prose_lines` counts
PHYSICAL lines, and this record was the narrowest thing in the set being measured:
max body width, 222 active records: median 106; 28 at <=100, 154 at 101-120, 40 >120
this record: 100. The sibling it cites by key, process.hook-resolves-inputs-from-repo-root: 116.
The 60-line ceiling was derived at #620 from that distribution, so measuring a
100-column record against it charges the record for a wrap width the corpus does not use.
Re-wrapping the seven body paragraphs at 116 — the exact width of the neighbour record —
takes the body from 66 physical lines to 58, and the validator now reports 58, off the
over-ceiling list (50 records over -> 49, and the key no longer appears).
The reflow removes NOTHING: the script asserted word count equal before and after (893)
and whitespace-normalised body text byte-identical, and refused to write otherwise. What
it buys is that the issue's `## Done-when` box "The record is under the 60-line advisory
prose ceiling" is satisfiable as written, so `pretooluse-merge-consent.sh` is not asked
to derive consent from a box ticked falsely or left standing. The advisory itself was
never breached — the ceiling is a `::warning::`, the validator exits 0, and 49 of 222
records are over it inside the 2-25% CEILING_MINORITY band.
Also: the file was the only one of 222 records with no final newline. Fixed in the same
commit; `record_prose_lines` is `splitlines()`, so it does not move the count.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
This commit is contained in:
@@ -9,69 +9,61 @@ rule: 'A predicate that decides something by matching a SHAPE inside an artifact
|
||||
signals: 'pin the artifact whole · shape-matcher is the exception that argues for itself · the boundary is the grammar not the file type · predicate does not implement the grammar it parses · defeated by the next spelling · the reviewer found the next spelling · false RED versus false GREEN · nine defects one mechanism · seven respellings of a partial config match · five spellings of a lexical preamble rule · the four-part exception argument: grammar and parser, closed input space, measured fail direction, what it buys · authority moves where the pin is not looking · an outranking config file · a decoy occurrence · an npm lifecycle hook · widening a clause retires its own canary · a survived-clause canary becomes a tautology · a pin declares its normalisation · instances: `ci.image-build-delegates-the-spa-suite`, `process.hook-resolves-inputs-from-repo-root` · paths: `scripts/tests/test_image_build_delegates_the_spa_suite.py`, `scripts/tests/test_hook_fire_log.py`, `docs/guard-inventory.md`, `docs/defect-shapes-773.md` · issues: #901, #887, #891, #774, #790, #881, #773'
|
||||
mechanics: 'Two worked pins. `scripts/tests/test_image_build_delegates_the_spa_suite.py` pins command TEXT (`PINNED_STAGE_COMMANDS`), `web/package.json`''s script map (`PINNED_PACKAGE_SCRIPTS`) and `web/vite.config.ts` entire (`PINNED_VITE_CONFIG`), the last two after a SELECTION and then a partial match were defeated. The three normalise DIFFERENTLY, so a residual read off one of them NEED NOT transfer to the others — and one of them does transfer, because two share a primitive: the stage commands are compared after line continuations are joined and whitespace within each command is collapsed (`_normalise`), so a reflow is free and a line boundary is not; the script map is dict equality over parsed JSON, so the file''s own formatting and key ORDER are free while every script body is exact; and of the three only `PINNED_VITE_CONFIG` runs through `_normalise_lines`, which additionally drops blank lines. `_normalise_lines` IS `_normalise` applied per line, so the whitespace collapse — including inside a QUOTED STRING, where it can change meaning — belongs to both TEXT pins and not to the vite one alone; of the two only the vite test states it, in place of claiming byte equality, which is what the rule above asks of a pin. `scripts/tests/test_hook_fire_log.py` pins the hook preamble''s two lines byte for byte (`CANONICAL_SINK_ASSIGNMENT`, `CANONICAL_SINK_SOURCE`), named once so the checker, the mutation proofs and the hooks cannot come to mean different strings — but its normalisation is a SELECTION, and the rule above obliges this record to say what that selection cannot see, because the pin itself does not. The compared set is the lines that contain the literal `ETV_HOOK_FIRE_LIB` and are not comment-led, so the pin reaches exactly the two preamble lines and nothing else in the file: a later reassignment of that variable which never spells the literal — one composed at runtime and `eval`ed, say — is outside the selection, and therefore outside the pin, however the checker then behaves. Byte-identity WITHIN a selection is not byte-identity over the file — the pin''s reach ends where its selector does. That is stated here as a structural fact about the selector and NOT as a measured checker outcome, because an outcome claim about a mutation is a declared, harness-executed `CLAIMS` entry under `testing.mutation-claims-are-executed` or it is not written, wherever it is written — this record included. The residual is #891 code and predates this record; stating it is this record''s own rule applied to one of its own worked pins. Each of the three image-build pins faults with a request for the reason in the same commit, and two of them name the CONSTANT to update (`PINNED_STAGE_COMMANDS`, `PINNED_VITE_CONFIG`) while the script-map fault names the FILE and prints both maps. The hook-preamble fault asks for neither: it reports the divergent lines, since the two canonical strings ARE what a deliberate change edits.'
|
||||
---
|
||||
**The class, and what approximating it costs.** A predicate over an artifact with a grammar is a partial
|
||||
reimplementation of that grammar, defeated not by a clever attacker but by an ordinary respelling — and each defeat
|
||||
looks local, one more case to handle, so the sequence stops only when someone stops it. Measured 2026-08-30 across
|
||||
two unrelated subjects: a guard parsing shell text to decide whether a command runs the SPA suite was wrong **nine**
|
||||
ways from one mechanism (#887), its replacement — a partial match of `web/vite.config.ts` — **seven** further ways,
|
||||
and a lexical rule over a hook preamble **five** (#891). Every fix was locally correct; none of the three sequences
|
||||
converged. All three ended in the same move: a pin cannot be defeated by a respelling of what it COMPARES, since it
|
||||
need not RECOGNISE one in order to reject it. What stays exposed is whatever it does not compare: nothing for a
|
||||
whole-file pin, and the SELECTOR's reach for a pin scoped by one — which is why `mechanics:` states the hook pin's.
|
||||
|
||||
**The class, and what approximating it costs.** A predicate over an artifact with a grammar is a
|
||||
partial reimplementation of that grammar, defeated not by a clever attacker but by an ordinary
|
||||
respelling — and each defeat looks local, one more case to handle, so the sequence stops only when
|
||||
someone stops it. Measured 2026-08-30 across two unrelated subjects: a guard parsing shell text to
|
||||
decide whether a command runs the SPA suite was wrong **nine** ways from one mechanism (#887), its
|
||||
replacement — a partial match of `web/vite.config.ts` — **seven** further ways, and a lexical rule
|
||||
over a hook preamble **five** (#891). Every fix was locally correct; none of the three sequences
|
||||
converged. All three ended in the same move: a pin cannot be defeated by a respelling of what it
|
||||
COMPARES, since it need not RECOGNISE one in order to reject it. What stays exposed is whatever it
|
||||
does not compare: nothing for a whole-file pin, and the SELECTOR's reach for a pin scoped by one —
|
||||
which is why `mechanics:` states the hook pin's.
|
||||
**Why the pin is the DEFAULT and not the remedy — the first thing #901 left open.** The rejected alternative is the
|
||||
tempting one: write the shape-matcher, and pin only once it has been defeated. It is rejected because of what the
|
||||
trigger event is made of. A shape-matcher's failure is a false GREEN, which the guard by construction does not
|
||||
report; the defeat therefore arrives from a reviewer or from an incident, so its absence measures who has looked
|
||||
rather than what the input can be. Both measurements above are that asymmetry: neither predicate withdrew because a
|
||||
count got high, and both had the precedent available for every round. The second reason is that switching later
|
||||
costs more than starting there: a withdrawal costs the rounds already spent AND the proofs calibrated against the
|
||||
narrow clause (rider 2), so "pin it when it breaks" understates its own bill.
|
||||
|
||||
**Why the pin is the DEFAULT and not the remedy — the first thing #901 left open.** The rejected
|
||||
alternative is the tempting one: write the shape-matcher, and pin only once it has been defeated. It
|
||||
is rejected because of what the trigger event is made of. A shape-matcher's failure is a false
|
||||
GREEN, which the guard by construction does not report; the defeat therefore arrives from a reviewer
|
||||
or from an incident, so its absence measures who has looked rather than what the input can be. Both
|
||||
measurements above are that asymmetry: neither predicate withdrew because a count got high, and both
|
||||
had the precedent available for every round. The second reason is that switching later costs more
|
||||
than starting there: a withdrawal costs the rounds already spent AND the proofs calibrated against
|
||||
the narrow clause (rider 2), so "pin it when it breaks" understates its own bill.
|
||||
|
||||
**What a shape-matcher's exception argument must contain — the second thing #901 left open.** The
|
||||
four requirements are chosen so a reviewer can REFUSE one: each is a thing the author either wrote
|
||||
or did not. Two alternatives are rejected. A NUMERIC bar ("survives N adversarial spellings")
|
||||
measures the reviewer's imagination, not the input space, and rises while the defect stands — it
|
||||
would have licensed #887's parser through most of nine rounds. A REVIEWER SIGN-OFF bar is worse: it
|
||||
makes the exception depend on the very signal the threshold says arrives late. What survives is
|
||||
requirement 2, the only candidate that is a claim about the ARTIFACT rather than about the effort
|
||||
**What a shape-matcher's exception argument must contain — the second thing #901 left open.** The four requirements
|
||||
are chosen so a reviewer can REFUSE one: each is a thing the author either wrote or did not. Two alternatives are
|
||||
rejected. A NUMERIC bar ("survives N adversarial spellings") measures the reviewer's imagination, not the input
|
||||
space, and rises while the defect stands — it would have licensed #887's parser through most of nine rounds. A
|
||||
REVIEWER SIGN-OFF bar is worse: it makes the exception depend on the very signal the threshold says arrives late.
|
||||
What survives is requirement 2, the only candidate that is a claim about the ARTIFACT rather than about the effort
|
||||
spent on it, and requirement 3, which forces the fail-direction claim to be executed, not reasoned.
|
||||
|
||||
**Rider 1 — a pin assumes it pins the artifact that still DECIDES.** This is where the pinned form
|
||||
fails, silently, so it is the question to ask of every new pin. In #887 every hole found after the
|
||||
first withdrawal was authority moving where the pin was not looking: another FILE (vitest prefers
|
||||
`vitest.config.*`; `vite build` prefers `vite.config.js`/`.mjs` to the pinned `.ts` — a
|
||||
VERSION-bound ordering, so the vite release it was read at is dated in `docs/guard-inventory.md`'s
|
||||
`test_image_build_delegates_the_spa_suite.py` row rather than copied here), another OCCURRENCE in
|
||||
the same file, another WORKFLOW (a `needs:` edge naming a different job called `test`), a HOOK the
|
||||
pinned command invokes (a vite plugin's `buildStart`, an npm `prebuild`/`preinstall` script). A pin
|
||||
removes the SPELLING problem outright and reduces the MEANING problem to a list that can be worked
|
||||
through; a pin sold as immune to both stops being re-examined.
|
||||
**Rider 1 — a pin assumes it pins the artifact that still DECIDES.** This is where the pinned form fails, silently,
|
||||
so it is the question to ask of every new pin. In #887 every hole found after the first withdrawal was authority
|
||||
moving where the pin was not looking: another FILE (vitest prefers `vitest.config.*`; `vite build` prefers
|
||||
`vite.config.js`/`.mjs` to the pinned `.ts` — a VERSION-bound ordering, so the vite release it was read at is dated
|
||||
in `docs/guard-inventory.md`'s `test_image_build_delegates_the_spa_suite.py` row rather than copied here), another
|
||||
OCCURRENCE in the same file, another WORKFLOW (a `needs:` edge naming a different job called `test`), a HOOK the
|
||||
pinned command invokes (a vite plugin's `buildStart`, an npm `prebuild`/`preinstall` script). A pin removes the
|
||||
SPELLING problem outright and reduces the MEANING problem to a list that can be worked through; a pin sold as immune
|
||||
to both stops being re-examined.
|
||||
|
||||
**Rider 2 — widening a clause can retire its own evidence.** A canary asserting that a finer
|
||||
mutation still SURVIVES is calibrated against a deliberately coarse clause. Widen that clause and
|
||||
the canary does not go red, it goes vacuous — indistinguishable from a passing proof at the point of
|
||||
reading — so the widening commit re-derives every proof calibrated against the narrow form. Same
|
||||
shape as the `DETECTOR` grade in `testing.mutation-claims-are-executed`: a surviving mutation is
|
||||
worth something only while the precondition that made it survive still holds.
|
||||
**Rider 2 — widening a clause can retire its own evidence.** A canary asserting that a finer mutation still SURVIVES
|
||||
is calibrated against a deliberately coarse clause. Widen that clause and the canary does not go red, it goes
|
||||
vacuous — indistinguishable from a passing proof at the point of reading — so the widening commit re-derives every
|
||||
proof calibrated against the narrow form. Same shape as the `DETECTOR` grade in
|
||||
`testing.mutation-claims-are-executed`: a surviving mutation is worth something only while the precondition that
|
||||
made it survive still holds.
|
||||
|
||||
**Where this sits against its neighbours, so it is not read as a duplicate.** The two incidents keep
|
||||
their own records — `ci.image-build-delegates-the-spa-suite` and
|
||||
`process.hook-resolves-inputs-from-repo-root` — and this is the class they instantiate, while
|
||||
`testing.guard-derives-population-from-source` answers the case where the authoritative source is
|
||||
MISSING (create one, never approximate it with a predicate over text); this one, the case where the
|
||||
artifact IS present and authoritative and its GRAMMAR is what gets approximated.
|
||||
`docs/defect-shapes-773.md` does NOT carry this rule: its §3.6 partition lists the nearest class,
|
||||
string-predicate churn, as `no detector proposed`, and §3.7 argues that class away as a
|
||||
cross-cutting property. Only §4's meta-finding supports the FORM — a class-level rule beats one
|
||||
record per instance — so the rule is new here, resolvable BY TOPIC where `docs/guard-inventory.md`
|
||||
carries its precedent per incident, findable only inside one.
|
||||
**Where this sits against its neighbours, so it is not read as a duplicate.** The two incidents keep their own
|
||||
records — `ci.image-build-delegates-the-spa-suite` and `process.hook-resolves-inputs-from-repo-root` — and this is
|
||||
the class they instantiate, while `testing.guard-derives-population-from-source` answers the case where the
|
||||
authoritative source is MISSING (create one, never approximate it with a predicate over text); this one, the case
|
||||
where the artifact IS present and authoritative and its GRAMMAR is what gets approximated.
|
||||
`docs/defect-shapes-773.md` does NOT carry this rule: its §3.6 partition lists the nearest class, string-predicate
|
||||
churn, as `no detector proposed`, and §3.7 argues that class away as a cross-cutting property. Only §4's
|
||||
meta-finding supports the FORM — a class-level rule beats one record per instance — so the rule is new here,
|
||||
resolvable BY TOPIC where `docs/guard-inventory.md` carries its precedent per incident, findable only inside one.
|
||||
|
||||
**What this does not cover.** Predicates over structured input a real parser produces — an AST, the
|
||||
git index, parsed workflow YAML consumed as data — are not approximations and keep their shape
|
||||
matching. And a pin is not mandatory for an artifact edited continuously by many hands: that is
|
||||
requirement 4's own escape hatch, and a pin reddening as background noise is a guard nobody reads.
|
||||
**What this does not cover.** Predicates over structured input a real parser produces — an AST, the git index,
|
||||
parsed workflow YAML consumed as data — are not approximations and keep their shape matching. And a pin is not
|
||||
mandatory for an artifact edited continuously by many hands: that is requirement 4's own escape hatch, and a pin
|
||||
reddening as background noise is a guard nobody reads.
|
||||
|
||||
Reference in New Issue
Block a user