Repository navigation
fix(pm): a key-INITIAL clause-② line that QUOTES the spelling is not a declaration - #17799
Merged
os-sales merged 4 commits intoSep 12, 2026
Merged
Conversation
…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
…ing-line Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
…ing-line Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
os-sales
marked this pull request as ready for review
September 12, 2026 06:04
os-sales
deleted the
claude/issue-17098-clause2-key-initial-describing-line
branch
September 12, 2026 06:33
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…eview to the skills seat (objectstack-ai#17806) Fixes objectstack-ai#17490 ## What changed One line of `.claude/skills/pm-dispatch/SKILL.md` (the governed ACCEPT fork, rule ①) is replaced in place. Nothing else moves: same line count, the four-axis frame block untouched, no reference twin, the tiering ruling's own text untouched. | | line 610 | bytes (no newline) | |:--|:--|--:| | before (`a77b4e92e`, identical on the dispatch tip `6682c5d8`) | `- ① 复核结论照常写在 issue 上;技能面 PR 的复核席须跑在契约复审档位。` | 96 | | graded candidate (comment 5624178855, re-measured on the base) | `- ① 复核结论照常写在 issue 上;技能面 PR 须由契约复审档的席复核,档外席交 skills 席。` | 114 | | **after (this PR)** | `- ① 复核结论照常写在 issue 上;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。` | **119** | Line cap is 120 bytes (`scripts/pm/check-skill-line-ratchet.mjs`, "Why 120 bytes"); 119 is under it. File stays 812 lines, ceiling 812, headroom 0 — no line added, no rule deleted, no re-wrap, no density payment. ### The three ruled elements, mapped to the words The ruled direction is option 3 on the card (5624178855): the seat that cannot run the tier hands the `skills/**` hunk's review to the skills seat, which records it on the card before the four-piece terminal fires; the lane's own review of the code half stands. | element | words | |:--|:--| | (a) the conclusion still goes on the issue | `复核结论照常写在 issue 上` — unchanged from the old line | | (b) the review of the `skills/**` hunk runs at the contract-review tier | `技能面 hunk 须由契约复审档的席复核` — the skills-face hunk must be reviewed by a seat AT the contract-review tier | | (c) a seat outside that tier hands that review to the skills seat | `档外席先交 skills 席` — an out-of-tier seat hands it to the skills seat first | Two wording changes against the graded 114 B candidate, both inside the ruled direction and inside the cap: - `PR` → `hunk` (+2 B). The ruling hands over the **hunk's** review and keeps the lane's review of the code half; with `PR` the sentence read as handing the whole PR review across seats, which contradicts that second clause. `hunk` is already vocabulary in this skill (`references/dispatch-runbook.md` lines 101 and 151). For a PR that is entirely `skills/**`, hunk = PR, so nothing narrows. - `交` → `先交` (+3 B). The ruling says the handed-off review is recorded on the card **before the four-piece fires**; `先` carries that ordering, closing the failure mode where a seat fires the four-piece and hands off afterwards, leaving the PR at the maintainer with no at-tier read. Kept from the candidate: `技能面` (the term the old line and line 600's `触 skills/** 的 PR` pairing already use), `契约复审档`, `skills 席`. ### core-rules twin check `git grep -n '契约复审档位' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). A wider probe `git grep -n '复核席\|技能面 PR' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). The only tier sentence in core-rules (line 112, `语义面卡恒契约复审档施工,契约卡达档复核归 spec 席`) is about the build tier and the spec seat's clause-② review, not the governed fork's rule ① — not a twin. `references/core-rules.md` is untouched. The phrase `契约复审档位` also appears in `references/contract-review.md` line 57 (`产出裁决的每轮都须读到契约复审档位`) — that is the contract-review reading's own tier requirement, a different rule; untouched. ### Premise check (all three PM readings held) 1. Line 610 on `6682c5d8` and on the actual base `a77b4e92e` (origin/main moved by one commit between dispatch and branch cut — PR objectstack-ai#17794, which touches only `references/dispatch-runbook.md`; `git diff --stat 6682c5d HEAD -- SKILL.md` is empty) is the 96 B sentence above, 97 B with its newline. Ratchet verdict on the base: `SKILL.md is 812 lines (ceiling 812; headroom 0)`. No open PR touches SKILL.md or core-rules.md: the 30 most recently updated open PRs (newest at 06:19Z, i.e. none after the PM's 06:29Z reading) were listed via REST; the two pm/ci PRs in that list (objectstack-ai#17799, objectstack-ai#17801) were file-listed and touch `scripts/pm/check-clause2-carriers.mjs` and workflow/script files only. 2. The interim was already executed at tier by the previous skills seat on card 17134 (PR objectstack-ai#17462); that card was read only, not touched. 3. `node scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md` prints `Model tier — MANDATORY: claude-fable-5-1`. This PR was authored at that tier (claude-fable-5-1). ### Governed verdict `node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md` ⇒ exit 3: `governed-surface predicate: 1 of 1 path(s) hit the register (5 surfaces, repo-agnostic).` / `⛔ GOVERNED — a human merge is the review record for this PR`. ⇒ draft at the human terminal; no ready flip, no queue, no auto-merge from any seat. `skip-changeset` applied additively (`.claude/**` publishes nothing). ## Verification (final commit `8b7dae6c0`) Gate set derived, not recalled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set derived three-dot from the merge base `a77b4e92e`, 1 path) ⇒ 16 commands. All 16 run on `8b7dae6c0` with exit captured before any pipe (`cmd > log 2>&1; ex=$?`), all exit 0; reconciliation `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE` ⇒ `Run reconciliation — 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN.` The same 16 ran once before the commit on the identical bytes; the command list was byte-identical pre/post commit. Verdict lines as printed by the gates on `8b7dae6c0`: - `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0).` - `✓ check-skill-id-lint: 27 file(s) clean (pattern /#[0-9]{3,}/g).` - `✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent (.claude/skills/pm-dispatch/SKILL.md; no second copy to compare — …)` — plus the direct proof the block did not move: `sed -n 734,755p SKILL.md | md5sum` ⇒ `3327d02c56f8a0eca88569dad2270f32` before and after the edit (the dispatch's expected hash). - `✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (…) and claim no others.` - `check-nul-bytes: OK (scanned 8456 text file(s) … no raw ASCII control bytes).` — plus the manual control-byte self-scan on SKILL.md (`grep -naP` over the C0/DEL range) ⇒ no hits. - `pnpm check:pm-governed-merges`, `check-governed-queue-guard --self-test`, `check-closing-keyword-parity` (+ `--self-test`), `check-comment-mask-corpus`, `check:agent-test-spelling`, `check:doc-authoring`, `check:driver-memory-census`, `check:refd-timer-probe`, `check:watch-hint-literal` — each printed its own ✓/OK line, exit 0. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions`: first run exit 3 `PREREQUISITE NOT MET` (`@objectstack/formula` / `@objectstack/lint` not built — NOT MEASURED, not a finding). Built both under the shared verify lock (`os-verify-lock.sh`, `VERDICT command-exit 0`, held 182 s), re-run ⇒ `✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1374 TS blocks judged clean`, exit 0; included in the post-commit union above. On-disk proof of the edit: `grep -cF` of the old line 1 → 0 and of the new line 0 → 1; `git show HEAD:…SKILL.md | sed -n 610p` prints the new line. No repo-wide `pnpm lint` run locally (CI-owned); no package build/test owed — the diff touches no package (no ① closure, no ②). Not measured locally: CI convergence (owned by the review side). ## Acceptance notes - noted, not filed: `references/contract-review.md` line 57 uses the spelling `契约复审档位` for the contract-review reading's own tier, while SKILL.md line 610 now spells the same tier `契约复审档` (as lines 505–527 do). Both spellings already coexisted on the tip; no gate reads either. Carrier: whoever next edits contract-review.md; none queued. - Out of scope, untouched by design: the tiering ruling's text (card 17285 / PR 17294 — those cards remain as they are), card 17134's labels, the four-axis block (lines 734–755). ## 维护者速读(草稿) **改了什么**:`.claude/skills/pm-dispatch/SKILL.md` 第 610 行(治理面 ACCEPT 分叉的规则①)原地换了一句话,96 字节变 119 字节,行数 812 不变。旧句要求「技能面 PR 的复核席须跑在契约复审档位」;新句改为「技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席」。 **为什么改**:旧规则①与分档裁决(契约复审档只留给 skills 席、spec 席的条款②复核与维护者召集的 director)构成一对谁都满足不了的矛盾:一个 `domain:cli` 席复核一张顺手修了 `skills/**` 里一行的 PR,被要求跑一个它被禁止跑的档位。这不是边角:「已发布的说法本轮变假就地修、不立卡」这条规则天然会让普通车道的 PR 顺带碰到 `skills/**`。一张 p1(PR objectstack-ai#17462)已经撞上。方向由 skills 席自分诊定为选项 3(5624178855,轮次报告否决窗已过):档外席把 `skills/**` 那一块的复核交给 skills 席,skills 席在四件套之前记到卡上;代码那一半仍由本车道自己复核。 **风险与代价(含回滚)**:每次顺带的 skills 修补多一次跨席交接 —— 几行 hunk、一条评论,不是一张卡。文字层面只动这一行,分档裁决原文、四轴框架块、objectstack-ai#17134 的标签都没碰;`references/core-rules.md` 无孪生句(已 grep 核实),未动。回滚 = revert 这一个 commit,不牵连任何生成物。 **席位意见**:(留空,席位定稿时填) **你要做的**:读第 610 行这一句,认可则人工直合;不认可则在此 PR 评论里写下你要的措辞,由 skills 席改。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17098
CLAUSE2_KEY_LINEdecided "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
15805ea3declared/yesmissing, reasondescribing5636056726)declared/yesmissing, reasondescribingdeclared/yesmalformeddeclared/yesmissing, reasondescribingmissingmissing, unchangeddeclareddeclaredmalformedLive 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:CLAUSE2_KEY_LINEgains 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.
readValueTokenrefuses 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 readsmalformedfor an angle-bracket placeholder, for an uppercase value and for an empty one); the two remedies are not interchangeable (malformedsays 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.mjsimports this samereadClause2Lineand 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.
clause2LineDescribesforced tofalse: the card's own specimen readsdeclared/yesagain — the defect, reproduced — and the self-test goes red on 19 of 546 cases. HEAD blobaecbb2d8, mutated0074f87f, restoredaecbb2d8,git diff HEADempty.declared/yesagain, 6 of 546 cases red. HEAD blobaecbb2d8, mutated87edf047, restoredaecbb2d8,git diff HEADempty.The two are independent — under ablation 1 the template bullet falls to
malformedrather than todeclared, 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/objectstackderived 35 families from the real change set (1 path, three-dot against the merge base). All 35 run, all exit 0, reconciled with--rancarrying 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 afterpnpm 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/mainmoved 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
15805ea3stops at that bullet and returnsdeclared/yes— a falseyeson the Check Changeset contract axis, for a PR that touches nothing published. The reader this PR ships returnsdeclared/nofrom the line under the acceptance notes.Acceptance notes
malformedrather 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 existingmalformedsentence ("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.mdis 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 againstcheck: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'spool = claimRowsfallback 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 predicateclaimRowsfilters 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 setsmalformedand returnsclaim-branch-unparsedabove it. A dead branch is a report line, not a rewrite.claude/issue-17098-contract-review-exit-2was pushed as the routing probe for the governed clause above and now holds zero commits — it is byte-identical toorigin/mainat8fa3fe63dand has no PR. This session cannot delete it:git push --deleteis cut off at the transport and the REST ref delete returns 403.--jsonoutput 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'sfiles[]ships it, and this diff moves no runtime, spec or exported surface.Generated by Claude Code