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

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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 12, 2026
@os-sales
os-sales marked this pull request as ready for review September 12, 2026 06:04
@os-sales
os-sales added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit a5ed18c Sep 12, 2026
36 checks passed
@os-sales
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>
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