Skip to content

fix(pm): a key-INITIAL clause-② line that QUOTES the spelling is not a declaration - #17799

Merged
os-sales merged 4 commits into
mainfrom
claude/issue-17098-clause2-key-initial-describing-line
Sep 12, 2026
Merged

fix(pm): a key-INITIAL clause-② line that QUOTES the spelling is not a declaration#17799
os-sales merged 4 commits into
mainfrom
claude/issue-17098-clause2-key-initial-describing-line

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #17098

CLAUSE2_KEY_LINE decided "is this line a declaration?" by POSITION, and a standing-rules bullet teaching the spelling puts the key in exactly the position a declaration does. On a claim comment whose only key-initial line was such a bullet, the EXPLANATION became the card's declaration — and the gate's own self-test asserted the opposite property in general terms while the single fixture under it covered only the half where prose comes first.

Measured before the change, on 15805ea3

line before after
the card's own specimen: a bullet naming the key twice, bolded and backticked declared / yes missing, reason describing
the dispatch-template bullet a second seat measured (5636056726) declared / yes missing, reason describing
a bare unfilled template, the two values separated by a pipe declared / yes malformed
the key quoted inside a span the line goes on talking outside of declared / yes missing, reason describing
prose before the key (the fixture's existing half) missing missing, unchanged
the two fixed spellings, and every decoration the key line tolerates declared unchanged
reasoning after the value (#12297 / #13914's control shape) declared unchanged
an angle-bracket placeholder in the value slot malformed unchanged

Live board, the same sweep before and after: 17 open PRs, 16 pairs, absent 0 / missing 0 / sibling 0, 0 finding rows, 4 unjudged — identical in both runs. A separate scan of the 15 claim comments behind those pairs found 0 with more than one key-initial declaration line and 0 producing a describing residue, so this change moves no verdict on the board today.

The change

1. Two STRUCTURAL tells demote a matching line to a describing near miss (clause2LineDescribes), decided without reading a word of what the seat wrote:

  • TWICE-NAMED — the fixed key appears more than once on the line.
  • QUOTED-AND-CONTINUED — the key's inline-code span was opened before the key, was not closed before the colon, closes later, and the line then continues outside it. The continuation is load-bearing in both directions: a line that is only the quoted declaration stays a DECLARATION, because that spelling is what this file's own remedy sentence teaches.

CLAUSE2_KEY_LINE gains two capture groups so the match can say which backticks it consumed. The decoration it tolerates is byte-identical.

2. A describing line is SKIPPED, not returned. The scan continues past it, so a real declaration written below a standing-rules bullet is now read. That is what a seat had to arrange by hand on three live cards, where position was the whole remedy.

3. readValueToken refuses a value token followed immediately by an alternation. The token class ends at word characters, so a menu opened with a valid token and returned the first alternative. It is refused there rather than as a describing tell, and the card's open question about which state is safer is answered on the four axes in the function header: one state per fact (the same slot already reads malformed for an angle-bracket placeholder, for an uppercase value and for an empty one); the two remedies are not interchangeable (malformed says CHOOSE, the describing remedy says ADD a line above, and for an unfilled template the act that exists is choosing); strictness is identical either way, both a C2 row at exit 4; and the only line where this tell is the sole one is an undecorated unfilled template, so it costs nothing on any measured occurrence. The refusal is ADJACENCY, never a scan — a pipe later in the reasoning is the seat's argument and is untouched.

4. The describing residue gets its own remedy sentence. It says the line quotes the spelling rather than declaring a value, that there is nothing to fix on the quoted line, and that what is owed is a declaration of its own above it. Three residues, three distinct sentences.

5. The one-sided fixture is finished where it is written. Both halves are now asserted at the sentence that claims them, and a new battery (48 cases, registered and floored) carries the mechanism from the measured specimens, each tell pinned alone so neither can be carrying the other, plus controls that #12297's reasoning-after-value allowance and #13914's four-valued reading are intact. The pre-registered FLIP TRIGGER in the #17366 battery is flipped rather than deleted, as its own comment instructed.

Second consumer, worth naming: check-changeset-no-major.mjs imports this same readClause2Line and applies it to the PR BODY, so the fix reaches the Check Changeset axis too.

Reverse verification

Both discriminators ablated from the committed state, each proven on disk by blob hash and restored byte-identically.

  • Discriminator 1, clause2LineDescribes forced to false: the card's own specimen reads declared / yes again — the defect, reproduced — and the self-test goes red on 19 of 546 cases. HEAD blob aecbb2d8, mutated 0074f87f, restored aecbb2d8, git diff HEAD empty.
  • Discriminator 2, the alternation lookahead alone removed: the unfilled template reads declared / yes again, 6 of 546 cases red. HEAD blob aecbb2d8, mutated 87edf047, restored aecbb2d8, git diff HEAD empty.

The two are independent — under ablation 1 the template bullet falls to malformed rather than to declared, because the alternation refusal still stands — so each carries its own half and neither is dead weight.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 35 families from the real change set (1 path, three-dot against the merge base). All 35 run, all exit 0, reconciled with --ran carrying exit codes: 35 derived, 35 run, 0 NOT-MEASURED (a DERIVED zero), 0 UNRUN. Seven first returned exit 3, PREREQUISITE NOT MET, and were re-run green after pnpm install; that exit is not a finding and is not recorded as one.

  • pnpm check:pm-clause2-carriers — 546 cases pass (493 before).
  • pnpm check:nul-bytes — OK over 8451 text files; a separate control-character scan of the changed file matches nothing.
  • pnpm check:pm-dispatch-gates — 1678 cases pass.

origin/main moved twice during the round and was merged in both times; the derivation above is from the merged tree and carries no STALE TREE warning.

This PR body is itself a specimen

The line below names the key twice and is therefore a quotation, not a judgement. Under the reader this PR replaces it would have been read as this PR's declaration; under the reader this PR ships it is skipped, and the real declaration further down is what the Check Changeset axis reads.

  • Clause-②: yes / Clause-②: no — the two spellings, quoted here on purpose.

Measured on this body: the reader as it stood at 15805ea3 stops at that bullet and returns declared / yes — a false yes on the Check Changeset contract axis, for a PR that touches nothing published. The reader this PR ships returns declared / no from the line under the acceptance notes.

Acceptance notes

  • Direction 3 of the card (a second matching line with a conflicting value reads malformed rather than first-match-wins) is NOT in this PR. Noted, not filed — it is already Direction 3 on the card this PR closes, and it is not a reproducible defect, a contract violation or a metadata-authoring trap. Measured pull today is zero: 15 claim comments behind the 16 open pairs, none with more than one key-initial declaration line. Doing it honestly also needs its own remedy sentence, because the existing malformed sentence ("carries the key but not one of the two values, so there is no reading") would be false of a conflict where two lines each carry a valid value — a second field threaded through three layers, which is a separable change and a second review round. The describing fix already removes the mechanism by which a QUOTATION produced a second value, which is what every measured occurrence was.
  • references/contract-review.md is not touched, and the reason is a measurement. The dispatch pointer asked for one clause naming exit 2 (UNJUDGED) on the declaration limb, if it fits by density. It does not: L43 is 115 bytes against check:pm-skill-ratchet's 120-byte per-line budget, and the file is 60 lines against a ceiling of 60 with zero headroom. The shortest clause that names exit 2 and keeps the line's meaning costs 11 bytes (126 B, over); the tightest rewrite merging exits 3 and 2 into one clause is 124 B, still over. Wrapping is the ratchet's own prescribed remedy and costs a line the ceiling cannot pay. The only legal currency is deleted content, and deleting content elsewhere in a governed file for an optional clause is scope this card does not carry.
  • cardDeclaration's pool = claimRows fallback is measured DEAD, and is left exactly as it stands. Both arms are pinned in the new battery so the measurement is re-runnable: a governing claim always leaves a non-empty pool because it came from a row matching the same predicate claimRows filters on; and a null governing claim can only reach that line when there were no claim comments at all, since every other way of producing one also sets malformed and returns claim-branch-unparsed above it. A dead branch is a report line, not a rewrite.
  • A leftover empty branch needs a hand. claude/issue-17098-contract-review-exit-2 was pushed as the routing probe for the governed clause above and now holds zero commits — it is byte-identical to origin/main at 8fa3fe63d and has no PR. This session cannot delete it: git push --delete is cut off at the transport and the REST ref delete returns 403.
  • The gate --json output still does not expose the matched declaration line for PASSING pairs, so a green pair cannot be audited for WHY it passed. That is the second seat's own note on the card (5636056726), it is unchanged by this PR, and it is recorded here rather than filed.

Clause-②: no

skip-changeset: scripts/pm/** publishes nothing — no package's files[] ships it, and this diff moves no runtime, spec or exported surface.


Generated by Claude Code

…tion

`CLAUSE2_KEY_LINE` decides "is this a declaration?" by position, and a
standing-rules bullet teaching the spelling puts the key in exactly the
position a declaration does. A claim comment whose only key-initial line
was such a bullet had its EXPLANATION read as the card's declaration.

Two structural tells now demote a matching line to a describing near
miss (card state `missing`, a new `describing` reason with its own
remedy sentence): the fixed key named more than once on the line, and
the key held inside an inline-code span the line goes on talking outside
of. A describing line is skipped rather than returned, so a real
declaration written below one is now read.

Separately, `readValueToken` refuses a value token followed immediately
by an alternation: `yes|no` is a menu, not a choice, and it now reads
`malformed` alongside the `<yes|no>` placeholder it has always matched.
The pre-registered flip case in the #17366 battery is flipped, not
deleted.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
The assertion states a general property and had one case under it: prose
before the key, which the line-anchored reader never matched at all, so
it passed for a reason narrower than the sentence it was written under.
Both halves are now asserted at that sentence, and a new battery carries
the mechanism from the measured specimens — the filing card's
standing-rules bullet and the dispatch-template bullet a second seat
measured, where the defect fired fail-OPEN into a landing pre-check.

The battery pins each tell alone so neither can be carrying the other,
the skip-not-return property (a declaration below a quotation is now
read, which is what a seat had to arrange by hand), the row's three
distinct remedies, and controls that the reasoning-after-value allowance
and the four-valued reading are both intact. The `pool = claimRows`
fallback is pinned as measured-unreachable on both arms rather than
rewritten.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
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