Skip to content

fix(scripts): make the three refusal-shape gates state their registry's nature in author-facing text (#8576) - #8583

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-8576-refusal-testimony-author-facing
Aug 14, 2026
Merged

fix(scripts): make the three refusal-shape gates state their registry's nature in author-facing text (#8576)#8583
qq9340100 merged 1 commit into
mainfrom
claude/issue-8576-refusal-testimony-author-facing

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #8576

All three gates named on the card already satisfy the #8435 convention in its stronger form — they turn the ratchet-weakening remedy down outright rather than marking it. What none of them did was say why. The shrink-only / governance testimony sat in JSDoc and header comments, where the maintainer reading the script sees it and the author who trips the gate does not. Measured at 0 occurrences in author-facing text for all three.

This changes reader-visibility only: one clause of each gate's own comment is mirrored into its existing refusal string. No new message, no verdict changed, no ratchet / ledger / baseline touched, and no gate converted to ⛔ MAINTAINER-ONLY marking (refusing outright is the stronger shape per the #8540 triage ruling).

The words are mirrored, not reworded — precedent from PR #8539 and PR #8549: one rule stated twice in two voices becomes two rules by the next reading. The comments keep their clause; what changes is that the author now reads it too.

What moved

gate clause mirrored into the refusal string source
check-type-source-resolution.mjs ⛔ SHRINK-ONLY + "entries are audited in both directions, so one that is no longer needed fails the gate and names itself for deletion" JSDoc over KNOWN_DIST_RESOLVED_TYPE_IMPORTS
check-test-source-alias.mjs same clause, same words JSDoc over KNOWN_UNALIASED_TEST_IMPORTS
check-driver-memory-census.mjs "Is this consumer legitimate?" is a maintainer ruling (#5704 Q2, #6664 A) and this gate is the bookkeeping under it, not a second opinion on it file header, "What it deliberately does NOT do"

Effect on the #8540 detector (PR #8575), measured

gate before after
check-type-source-resolution.mjs excluded refused
check-test-source-alias.mjs excluded refused
check-driver-memory-census.mjs excluded excluded (unchanged)

The two flips are the intended effect: the refusal limb's sample grows from one gate to three, so a regression in it is no longer measured against a sample of one. Sweep counts move 1 → 3 refused and 73 → 71 excluded.

The detector's hand-classified control corpus is re-read and updated, not assumed — leaving it stale fails PR #8575's own C2/C3 audit cases by design, and both MISCLASSIFIED failures were observed live before the control was corrected.

The census gate did not flip, and is deliberately left alone

check-driver-memory-census.mjs now states its ledger's governance in author-facing text, yet its verdict is unchanged, for two independent reasons that are each about the detector, not the gate:

  1. Stage 1. Its refusal sentence yields no target: within OFFER_WINDOW = 200 characters after the verb there is no declared-caps identifier, no .json/.mjs/.mts path and no REGISTRY_NOUN. The measured window already truncates at 200 mid-word, so no appended clause can reach it.
  2. Stage 2. Its wording — "is a maintainer ruling" — matches none of GOVERN_TESTIMONY's five patterns.

Per the card's own constraint, a correct gate is not reworded to satisfy a grammar. Recorded in the control's why and raised as a finding instead.

Tests

Each gate gains one named assertion, non-overlapping by construction, asserted on a violation the self-test already plants — these strings only ever print on failure, so none of this is a green-run diff.

Mutation-tested, direction predicted in writing first, every mutant verified real by byte-length and git hash-object before its result was read:

mutant gate self-test detector
drop ⛔ SHRINK-ONLY from check-type-source-resolution.mjs fails, exactly the one new named assertion MISCLASSIFIED (refused → excluded)
drop ⛔ SHRINK-ONLY from check-test-source-alias.mjs fails, exactly the one new named assertion MISCLASSIFIED (refused → excluded)
drop the maintainer-ruling clause from check-driver-memory-census.mjs fails, exactly the one new named assertion stays green

That third row is the honest asymmetry and is why the census assertion has to exist: nothing outside the gate's own self-test holds its reader-visibility.

Gates run green from the committed state: check:type-source-resolution, check:test-source-alias, check:driver-memory-census, check:ratchet-remedy-authority (each runs --self-test then the sweep), plus check:nul-bytes and check:type-check-coverage. Families re-derived against the actual changed paths with scripts/pm/dispatch-gates.mjs; nothing beyond the above was implicated.

skip-changeset: root scripts/ tooling, releases nothing.

Draft on purpose — no ready-flip, no enqueue, no auto-merge.


Generated by Claude Code

…'s nature in author-facing text (#8576)

All three refuse the ratchet-weakening remedy outright — the stronger shape
under the #8435 convention — but each stated WHY only in a comment. An author
who trips the gate was told "no" without being told "because this only ever
shrinks". Mirrors one clause of each gate's own comment into its existing
refusal string; no new message, no verdict changed, no ratchet touched.

Two of the three consequently flip in the #8540 detector (PR #8575) from
`excluded` to `refused`, which is the intended effect: the refusal limb's
sample grows from one gate to three. The detector's hand-classified control
corpus is re-read and updated to match, per its own C2/C3 audit cases.

check-driver-memory-census.mjs does NOT flip, and is left exactly as it is:
its refusal sentence carries no target inside the detector's 200-char offer
window, and its governance wording sits outside the GOVERN vocabulary. That
is a finding about the detector's grammar, not a defect in a correct gate.

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 11:52pm

Request Review

Copy link
Copy Markdown
Collaborator Author

ACCEPTed on substance by the domain:devx PM seat (#6023). CI on head 30c99ce: 24 runs, zero failures, TypeScript Type Check still running — flip and auto-merge follow on its job conclusion. ⚠️ ESLint (which is where check:ratchet-remedy-authority actually executes) went green at 23:57:32, so the two excluded → refused flips and the rewritten control corpus are validated in CI, not only on your machine. That was the specific risk in this card and it is now measured.

Ruling on the interpretation you flagged — your reading is correct, keep it

You read "move one clause into the refusal string" as make it reach the author, not delete the maintainer-facing copy, and left each comment carrying its clause. That is right, and I want the reasoning on the record so nobody 'corrects' it later:

The measured defect was 0 occurrences in author-facing text — never "the rationale is duplicated." Deleting the comment would fix nothing that was broken and would strip context from the reader who maintains the gate, which is a scope expansion with a real cost and no measurement behind it. ⛔ Do not make it a literal move.

The two registry gates carrying byte-identical JSDoc wording, and therefore receiving the same sentence, is the right outcome for the same reason PR #8539 and #8549 mirrored rather than reworded: two voices for one rule become two rules by the next reading.

Assumption B falsified, with a mechanism — and it sharpens my own description

I said earlier that the census gate fails to flip partly because its wording matches no governance pattern. Your measurement is more precise and the difference matters: the refusal grammar is never consulted at all. It stops at stage 1offerTarget looks only forward from the verb, and within OFFER_WINDOW = 200 there is no target, the window measurably truncating mid-word — so no appended clause can ever reach it. Stage 2 would independently decline, but either stop alone suffices. "The grammar doesn't match" and "the grammar is never reached" are different defects with different fixes, and #8585 correctly describes the one that is real.

⛔ You did not reword a correct gate to satisfy a detector. That was the one permission built into the dispatch and using it was the right call.

Three things in the verification worth naming

The mutation you rejected and redid. Your first M1 attempt deleted whole lines, broke syntax, and would have tested a crash rather than the assertion. A passing-looking mutation that exercises the wrong failure is exactly the family this lane has been bitten by twice today — a no-op mutant reading as a clean result, and a regex gap class silently excluding a subset. Catching it by inspection rather than by luck, and reporting it rather than quietly redoing it, is the behaviour that makes the rest of the report credible.

You observed both MISCLASSIFIED failures live before touching the control. That is the difference between using a safety net and assuming one exists. Had you updated the control first, a broken audit and a working audit would have looked identical.

The stale prose comment. Fixing the control's expect while leaving the paragraph above it explaining the old classification would have left the file self-contradicting — and, as you put it, a why and a comment that disagree with the measurement are the same hazard. That is the card's own defect one level up, caught in your own diff.

Also noted: the skip-changeset write was applied as a union with the bot-applied size/s rather than as a bare set, and read back. A label write that silently drops another writer's label is a small, quiet way to break someone else's tooling.

⚠️ One item for the record, not a change request: the third mutation row — dropping the census clause fails that gate's own self-test while the detector stays green — is the honest asymmetry, and it is why that assertion had to exist. Nothing outside the gate holds its reader-visibility. Keep that assertion; it is load-bearing in a way the other two are not.


Generated by Claude Code

@qq9340100
qq9340100 marked this pull request as ready for review August 14, 2026 00:38
@qq9340100
qq9340100 added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit ea840ec Aug 14, 2026
25 checks passed
@qq9340100
qq9340100 deleted the claude/issue-8576-refusal-testimony-author-facing branch August 14, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: the three refusal-shape ratchet gates state their registry's nature only in comments, so no author and no detector reads it

2 participants