Skip to content

docs(checklist): rule 42 — a shared refusal code makes an arm's witness ambiguous - #2008

Merged
lilyshen0722 merged 2 commits into
mainfrom
kai/checklist-refusal-code-attribution
Sep 29, 2026
Merged

lilyshen0722 merged 2 commits into
mainfrom
kai/checklist-refusal-code-attribution

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What

One rule appended to docs/development/review-checklist.md (rule 42, new section Refusal codes as witnesses). Docs-only: no version bump, no source change.

A guard added above an existing one can invalidate the arms below it without changing a line of them — the arm still passes, for the new guard's reason. What makes such an arm a witness is that its FIXTURE reaches the guard the arm NAMES, and the instrument for that is a mutation of that guard, not the shape of the assertion.

Two guards sharing a refusal code make that code non-discriminating for a given input, so counting a code's emitters says only where attribution is at risk (rule 25); the per-arm question is which guards can fire on its input. Re-aiming the fixture is the fix, and it is local and cheap. Asserting the exception message is an option that additionally pins which refusal — never a requirement. (An earlier revision of this rule claimed the opposite; see Earned.)

Earned

Today, on #2007/TASK-172. My RFC 8414 §3.3 issuer check landed above an established endpoint guard in backend/services/hostedMcpIntakeService.ts. The neighbour arm — "refuses an incomplete document rather than guessing an endpoint" — was written when only the endpoint guard existed and sent a document naming no issuer, so it was answered by the newer guard, under the same issuer_metadata_incomplete. Deleting the endpoint guard changed no test in the neighbourhood: the arm named a guard that could no longer answer its input.

Found by the gate, not the author: vera measured that deleting if (!body.authorization_endpoint || !body.token_endpoint) left the neighbourhood green, and her repair was one line (give the arm an issuer).

Measured as one instrument, same suite (30 tests), both directions — the survivor is not taken from the report:

arm fixture endpoint guard deleted result
no issuer (pre-repair, f0354405) yes 30/30 green — the guard is unwitnessed
issuer present (post-repair) yes that arm alone reds — on the code assertion: with the guard gone nothing is thrown, so thrown.code is a TypeError

That second row is also why the first revision of this rule was wrong, and the docs gate refused it at 568df1c2 with exactly this counter-example. The rule was rewritten against the measurement: a checklist line that over-prescribes the assertion shape sends every future reader to add a second assertion instead of asking which guard answers their input.

Rider in the rule

Where a route answers { error: code } with no message — vendorFailureStatus in routes/hostedMcpConnect.ts does exactly this — two defects sharing a code are indistinguishable to the operator: a 502 reading issuer_metadata_incomplete does not say whether the document named no issuer or named no endpoint. So "same code" is a client-visible contract decision, not a test detail. Splitting the vocabulary is deliberately not in #2007; it is recorded on the TASK-172 row for whoever takes it.

Guard

node scripts/verify-numbered-rules.js docs/development/review-checklist.md
✓ 42 rules, numbers 1..42 ascending with no gap, 20 citation(s) all resolve.

Gate: rhea — cleared at 3ec8034b. Numbering passes against current main; #2000 (rules 42 + 43) remains open, so whichever of the two merges second renumbers — mine, if it is mine.

…ss ambiguous

Two guards that refuse with the same code make one arm ambiguous, and a guard
added ABOVE an existing one silently transfers the old guard's only witness to
the new one. The arm does not change, the test file shows nothing, the suite is
green — which is why the review check is not "is this arm good" but "how many
guards emit this code".

Earned today on #2007/TASK-172. The new RFC 8414 §3.3 issuer check landed above
an established endpoint guard; the neighbouring arm sent a document naming no
issuer, kept refusing with the same `issuer_metadata_incomplete`, and so was
pinning the new guard rather than the one it was written for. Deleting the
endpoint guard changed no test in the neighbourhood. Measured on both sides of
the repair, in one instrument: pre-repair arm + guard deleted = 30/30 green (the
survivor), post-repair arm + guard deleted = that arm alone goes red, by name.

Rider: where a route answers `{ error: code }` with no message, the shared code is
client-visible too, so "same code" is a contract decision rather than a test
detail.

Guard: scripts/verify-numbered-rules.js → 42 rules, 1..42, 20 citations resolve.
…ssertion shape

Corrected per the docs gate's HOLD at 568df1c. The first draft claimed a shared
refusal code obliges every arm to assert the message; the rule's own incident
disproves it, and I measured the counter-example before rewriting:

  arm fixture          endpoint guard deleted   result
  no `issuer` (pre)    yes                     30/30 green  (guard unwitnessed)
  `issuer` present     yes                     that arm ALONE reds — on the
                                               CODE assertion: with the guard
                                               gone nothing is thrown, so
                                               `thrown.code` is a TypeError

So re-aiming the fixture is the fix and it is local; pinning the message is an
option that additionally pins WHICH refusal, not a requirement. The review move:
counting a code's emitters says where attribution is at risk (rule 25); the
per-arm question is which guards can fire on its input, and the witness is a
mutation of the guard that arm NAMES.

The rider (a shared code is client-visible, so "same code" is a contract
decision) is unchanged.

Guard: 42 rules, 1..42, 20 citations resolve.
@lilyshen0722
lilyshen0722 added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 8caf4e6 Sep 29, 2026
22 checks passed
samxu01 pushed a commit that referenced this pull request Sep 29, 2026
#2008 landed rule 42 (refusal codes as witnesses) at EOF first. This
merge appends #2000's two sections after it and renumbers them 43 and
44, including the three in-text references to rule 43 (now 44). No
other wording changed; verify-numbered-rules.js reports 1..44 with all
25 citations resolving.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NDyNbwmCco62PAAviSL1k
samxu01 pushed a commit that referenced this pull request Sep 29, 2026
…er clause's real cause

ux-lead's docs gate on the previous head measured three false sentences.

The pin does not land *between* two raw offsets in rules 3, 23, 41 and 42 —
it lands on one: mutating a character either side of the boundary shows the
pin ending at raw 60 in 37 of the 40 long leads, at 61 in rule 13, at 62 in
rule 41 and at 64 in rule 35. The 59 -> 61 step is real but belongs to a
prefix-by-prefix re-derivation, where `.trim()` discounts a trailing space;
in the full lead that space is interior and counted. The sentence now gives
the four end offsets, which add to 40, and moves the step to the sentence
about re-deriving.

"every lead that carries markup" is false for rules 14, 26 and 33, whose
markup sits after the boundary and which end at raw 60 — now "every lead
whose markup sits before it".

Rule 43 attributed the 42/43 -> 43/44 discrepancy to #2008 merging between
the gate and the press. It did not: #2008 merged at 15:35:29Z, both clearing
gates are at `b32428b9` (15:39 and 15:41), and that head's file already reads
43 and 44. What stayed stale is the title and the first commit's subject,
which the squash composed.

Also: the two-parent commit count now names the base it was measured from
(`50a454b0`), and the collision error is cited to TASK-198 rather than to
"above", where it was never quoted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant