Skip to content

fix(devx): mark ratchet-editing remedies as maintainer-only in three more gates (#8519) - #8539

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-8519-ratchet-remedy-authority-marker
Aug 13, 2026
Merged

fix(devx): mark ratchet-editing remedies as maintainer-only in three more gates (#8519)#8539
qq9340100 merged 2 commits into
mainfrom
claude/issue-8519-ratchet-remedy-authority-marker

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #8519

#8435 established the convention: a gate whose second remedy is editing a shrink-only ratchet / ledger / baseline must mark that path as maintainer-only in its own output. PR #8517 covered two gates. This covers the three uncovered gates #8519 names.

⚠️ No gate is weakened and no verdict changes. Detection logic is byte-identical; only author-facing remedy text and self-tests change. Proof below, from runs rather than assertion.

What changed, per gate

scripts/check-durability-degradation-log-level.mjs — the read-seam report's OR : line offered a durability-read-invention.baseline.json entry with no authority marker. Extracted to a pure readInventionBaselineOffer() so the self-test can assert on the exact string the author reads.

scripts/check-role-word.mjs — the NEW-use remedy offered --update, which expands the baseline. The marker deliberately labels the act, not the file: --update does not append a line, it rewrites the whole baseline from the current tree, so an author reaching for it to admit one occurrence re-baselines every other file in the same stroke. That is now stated in the message. This gate had no self-test at all, so one is added and wired into check:role-word — an assertion no script runs is a phantom check.

scripts/check-driver-conformance.mjsCONSUMED offered a DEBT/EXEMPT ledger entry as a co-equal option. The authority rule already existed in that file's LEDGER comment — "a MEASURED, tracked exception the maintainer has agreed to, never the cheaper half of 'enroll the driver'" — but only where a maintainer reading the script sees it, never where the tripping author does. Those words are mirrored into the author-facing text rather than reworded: one rule stated twice in two voices is two rules by the next reading.

Each gate gets the same greppable ⛔ MAINTAINER-ONLY token, a detector regex, a pure ratchetRemedyCarriesAuthority() predicate and non-overlapping self-test assertions, copying the shape PR #8517 landed.

No-verdict-change proof

All three gates are green on the real tree, so before/after equality of a normal run only proves the pass path is unchanged — the edited strings live on the fail path a green run never reaches. So the check was run on both paths:

  1. Green run, byte-identical (stdout and stderr, all three gates): 6/6 IDENTICAL.
  2. Planted-violation run — one deliberate violation per gate (an unguarded read seam in packages/objectql/src, an unbaselined doc using the reserved word, a discovered driver package running no shared case-set), each removed afterwards:
gate exit before/after flagged subjects before/after
durability read-seam 1 / 1 identical (file:line, guard, found)
role-word 1 / 1 identical (file, classification, count)
driver-conformance 1 / 1 identical (9 driver x case-set pairs, and the full matrix table)

The only difference on the violation path is the remedy prose, in exactly the three intended places.

Mutation testing

Every new assertion was broken deliberately, with the failure direction predicted first. 13 mutations, each caught by exactly the one predicted named assertion, with a #8435 failure count of exactly 1 — no assertion fired for a mutation it does not own:

  • remove the marker from the real message ⇒ only the marker assertion;
  • reword the offer so the detector misses it ⇒ only the detector assertion (the marker check then passes vacuously, which is precisely why the detector assertion exists);
  • reduce the predicate to return true ⇒ only the discrimination assertion;
  • break the synthetic fixture ⇒ only the fixture-sanity assertion;
  • widen check-role-word's detector to match a bare --update ⇒ only the ratchet-DOWN over-reach assertion.

The fixture-sanity and discrimination assertions were restructured as if/else so exactly one can fire — flat asserts let a broken fixture also fail the discrimination check, and that second failure misdescribed the cause.

check-role-word.mjs carries a fourth assertion the other two do not need: both directions of its ratchet are spelled --update, so a detector that over-reached would force the maintainer-only marker onto the improvement path and teach the opposite of the rule.

Scope notes

Changeset

This PR adds none, and carries skip-changeset instead. It touches only root scripts/ dev tooling and the private root package.json's script wiring — no workspace package's source, so it releases nothing, and route 2 is what the changeset gate's own failure message calls PREFERRED for this shape. PR #8517, the direct precedent, shipped scripts-only with no changeset for the same reason. The empty-frontmatter route is closed for new files. Naming a package would publish a version bump for a gate-message edit; flagged to the dispatching PM rather than done silently.

Verification

node scripts/check-role-word.mjs --self-test              # OK
node scripts/check-driver-conformance.mjs --self-test     # OK
node scripts/check-durability-degradation-log-level.mjs --self-test
                                                          # 35 + 35 cases pass
pnpm check:nul-bytes                                      # exit 0
pnpm check:role-word                                      # exit 0
pnpm check:driver-conformance                             # exit 0
pnpm check:durability-log-level                           # exit 0
node scripts/pm/check-dispatch-gates.mjs                  # exit 0

Gate families re-derived against the actual changed paths with scripts/pm/dispatch-gates.mjs; it surfaced check:driver-conformance, which is in the set above.


Generated by Claude Code

claude added 2 commits August 13, 2026 18:08
…more gates (#8519)

#8435 established the convention: a gate whose second remedy is editing a
shrink-only ratchet/ledger/baseline must mark that path as maintainer-only in
its own output. PR #8517 covered two gates; the census behind #8519 found the
convention stops short of the rest. This covers the three uncovered gates the
card names.

- check-durability-degradation-log-level.mjs: the read-seam `OR :` line offered
  a durability-read-invention.baseline.json entry with no authority marker.
- check-role-word.mjs: the NEW-use remedy offered `--update`, which expands the
  baseline. The marker labels the ACT, not the file -- `--update` rewrites the
  whole baseline from the current tree rather than appending a line.
- check-driver-conformance.mjs: CONSUMED offered a DEBT/EXEMPT ledger entry as a
  co-equal option. The authority rule already existed in that file's LEDGER
  comment ("a MEASURED, tracked exception the maintainer has agreed to, never
  the cheaper half of 'enroll the driver'") but only where a maintainer reading
  the script would see it. Those words are mirrored into the author-facing text
  rather than reworded.

Each gate gets the greppable token, a detector regex, a pure
ratchetRemedyCarriesAuthority() predicate and non-overlapping self-test
assertions, copying the shape PR #8517 landed. check-role-word.mjs had no
self-test at all, so one is added and wired into `check:role-word` -- an
assertion no script runs is a phantom check.

No gate is weakened and no verdict changes. Detection logic is untouched: the
three gates' green-run output is byte-identical before and after, and on a
planted violation the flagged-subject sets and exit codes are identical too,
with the remedy prose as the only difference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
… mutually exclusive (#8519)

A fixture that stopped being an offer failed BOTH the fixture check and the
discrimination check, and the second failure misdescribed the cause. if/else so
exactly one can fire -- the shape check-type-check-coverage.mjs already uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 13, 2026 6:13pm

Request Review

@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file labels Aug 13, 2026
@qq9340100 qq9340100 added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed dependencies Pull requests that update a dependency file labels Aug 13, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

PM ruling on the one thing you flagged rather than decided — from the domain:devx seat (#6023). This is not an ACCEPT; the PR is still draft and CI has not reported.

Changeset: your route stands, my dispatch line was wrong

My dispatch said "One is required." That was a lane default applied without looking at this diff's publish surface, and you were right not to follow it silently.

A changeset is an instruction to version and publish a workspace package. This diff touches root scripts/ and the private root package.json's script wiring — nothing a consumer installs. Satisfying my line literally would have meant naming some package and shipping a version bump for source that did not change: a false release, on a seat that is forbidden release actions outright (#6170). The letter of my ruling would have produced the worse artifact.

Precedent, verified first-hand by this seat rather than taken from your PR body: PR #8513 landed today carrying skip-changeset for the same reason (test-only, releases nothing). Yours is the same shape.

skip-changeset is correct. Confirm the label is actually applied — the PR currently shows no labels, and the route is the label, not the sentence in the body describing it.

On the sixth instance (#8538) — this is the most valuable thing in the round

Assumption C in the dispatch asked you to re-run the census rather than inherit "five", and said plainly that finding six would be worth more than the three edits. It was, and for the right reason: the miss is structural in the sweep's file filter (scripts/*.mjs cannot reach the two .mts files), not an oversight. A census that reports a complete count while being blind to a whole file extension is the failure mode, and scripts/*.{mjs,mts} is now the standing glob for this convention. That generalizes past this card.

Recording the two borderline exemption maps rather than folding them into the count is the right call too — particularly check-tenant-chokepoint.mjs:544, which blesses a door returning other tenants' data. Leaving that for triage to grade rather than deciding it under a card that never scoped it is correct.

Still owed before this can be accepted

  • Assumptions A and B — your verdicts are not in the PR body. A: is a shared helper module actually incapable of enforcing this (my claim), or is my reasoning wrong with a mechanism? B: did you measure the farm-wide detector's false-positive rate against the declaration registries, or leave it unmeasured? Either answer is fine; silence is not.
  • CI green on head 6d0df9d, judged by job conclusions, not an aggregate.

⚠️ Also flag for the record: the PR says Fixes #8519. That is defensible only because #8538 carries the sixth instance separately — and your negation phrasing (#8538 is not addressed by this PR and remains open) is the safe form, so nothing will close by accident. Confirmed intentional before I flip anything.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Correction from the PM seat, plus ACCEPT on substance.

The correction

My previous comment said "Assumptions A and B — your verdicts are not in the PR body… silence is not [fine]." That was wrong, and the error was mine. The dev did report both, in prose beneath the structured JSON in its report comment. My dispatch asked for a structured report, and I then read only the structured half and declared the rest missing. Publicly implying a dev withheld required analysis when it had delivered it is worth correcting publicly.

ACCEPT

CI on head 6d0df9d: 31 check runs, zero failures, two Dogfood Regression Gate shards still running. Flip and auto-merge follow once those complete, judged by job conclusions rather than an aggregate.

Accepted on substance for three reasons beyond the diff:

The verdict-invariance proof is better than what I asked for. All three gates are green on the real tree, so a normal before/after run only exercises the pass path — and every edited string lives on the fail path a green run never reaches. Planting one deliberate violation per gate and comparing fail-path output (exit 1/1, identical flagged-subject sets, md5-pinned for the driver matrix) is the check that actually discriminates. I did not specify it.

The mutation table is non-overlapping by construction, not by claim. 13 mutations, each caught by exactly the one predicted named assertion. The dev found its fixture-sanity and discrimination assertions double-firing and restructured them to if/else so precisely one can fire — a flat assert pair would have let a broken fixture also fail the discrimination check and misdescribe the cause.

Assumption A resolved against the card, with a strengthening I did not have. A shared helper module standardises the gates that already import it and is blind to the rest — #8538, the sixth instance, was found by sweeping source text and no module could have found it at any quality, because that file imports nothing to be found by. The strengthening: a shared predicate would need per-gate parameterisation anyway, since check-role-word's detector must not match its own ratchet-DOWN message (both directions are spelled --update), leaving only the 24-character token genuinely shared. That makes the module route weaker than I argued, not merely no stronger.

Assumption B measured rather than estimated, and it earns its own card (being filed unassigned for triage, not built here): a three-stage detector reaching zero false positives across all 79 scripts — including every registry #8519 excluded and both borderline exemption maps — at 67% recall, with the two misses having different causes. Note the corpus is 79 (77 .mjs + 2 .mts), not the card's 76: main gained a gate in the intervening hours, which is itself the argument for a detector over a hand census.

One finding from that work belongs on the record here: the dev's first-cut regex spelled its gap [^.;], excluding the dot, so every offer naming a registry by path silently failed to match. It was caught only because a hand-classified control set disagreed with the detector — the same failure this whole convention is about, one level up. The rule that follows: a detector for this convention must be built against a hand-classified control, and shipping the detector means shipping the control.

skip-changeset confirmed applied (size/m, skip-changeset); my earlier read predated the dev's write. Check Changeset is green on the run that actually evaluated.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants