Skip to content

pm(half-states): H65 reads the tier DECLARATION, not the ASCII string Tier: - #18672

Merged
os-try-charles merged 3 commits into
mainfrom
claude/issue-18547-h65-tier-spelling
Sep 17, 2026
Merged

os-try-charles merged 3 commits into
mainfrom
claude/issue-18547-h65-tier-spelling

Conversation

@os-try-charles

Copy link
Copy Markdown
Collaborator

Fixes #18547

Clause-②: no — scripts/pm/** is shipped by no package. Re-judged from the actual diff, measured below, not inherited from the dispatch.

The row asked one question and answered a different one

H65 reports, of a triage round artefact, that it «names NO tier at all — so the two-tier rule … is satisfied by SILENCE here». Its predicate was an ASCII literal:

export const H65_TIER_LINE = /^[ \t]*Tier:[ \t]*(\S*)/m;
export const H65_TIER_KEY  = /Tier:/;

The live specimen — objectstack#6015 comment 5706306387does name its tier, on its headline, as a ·-delimited field: **层:小时层(增量)**. The row reported it as silence because the seat wrote it in Chinese, and one sweep took 46 rows from that one miss.

Re-measured on this branch's tree: the English word Tier in any casing appears nowhere in .claude/skills/pm-dispatch/SKILL.md or references/dispatch-runbook.md. The only clause is 「选层按 fire 时刻,⛔ 不用计数器;简报写明本轮跑的层」 — it requires the tier to be written and prescribes no spelling. So the repair is in the predicate. ⛔ No .claude/ path is touched.

The repair, and the property it preserves

A second spelling, closed exactly as the first one is:

  • closed key — 「层」 plus a colon (ASCII or full-width), nothing else;
  • closed word set — 「小时层」/「每日层」, the runbook's own two names, resolving onto the same hourly/daily pair. Any other word is unknown-tier, exactly as Tier: full is;
  • a declaration slot, never prose — the start of a line, or immediately after a 「·」 field separator, which is the marker headline's own field idiom. A mention inside a sentence is not a declaration and keeps the verdict it has today;
  • the same blockquote refusal — a quoted line is this artefact repeating another one.

One asymmetry is deliberate and named in the source: a leading ** stays refused on the English line (**Tier:** daily is unchanged) and is accepted on the Chinese field, because the measured artefact writes every headline field in bold and refusing it would re-file the same false positive one layer down.

Verdict table — every fixture, BEFORE and AFTER

BEFORE = origin/main at 14d25441; AFTER = this branch. Produced by importing both module copies into one process.

fixture BEFORE (origin/main 14d2544) AFTER (this branch)
LIVE objectstack#6015 comment 5706306387 (whole body, verbatim) absent clean CHANGED
LIVE headline only absent clean CHANGED
measured tier-less round-open marker (MARKER65) absent absent
measured round close (CLOSE65) absent absent
measured stand-down brief (BRIEF65) absent absent
marker + Tier: hourly (since ...) clean clean
marker + Tier: daily (first fire of the day) clean clean
marker + Tier: full unknown-tier [full] unknown-tier [full]
marker + Tier: 全量 unknown-tier [全量] unknown-tier [全量]
marker + **Tier:** daily not-at-line-start not-at-line-start
marker + > Tier: daily (...) not-at-line-start not-at-line-start
marker + mid-paragraph English Tier: daily not-at-line-start not-at-line-start
marker + CN line 层:每日层(当日首 fire) absent clean CHANGED
marker + CN line, full-width colon absent clean CHANGED
marker + CN line indented absent clean CHANGED
marker + CN PROSE mid-sentence absent absent
marker + CN prose on a line that has a · elsewhere absent absent
marker + BLOCKQUOTED CN declaration absent absent
marker + CN dialect 层:全量层 absent unknown-tier [全量层] CHANGED
marker + bare 层: absent unknown-tier [(nothing)] CHANGED
marker + with no key punctuation absent absent
marker + a different key 本轮:小时层(增量) absent absent
non-artefact prose carrying a CN declaration clean clean
CN declaration inside a FENCED block absent absent

Seven rows move; the other seventeen are unchanged, and five of those seventeen are the firing controls.

  • Rows 1–2: the card's own defect. The live marker stops being reported as silence, with the tier VALUE extracted (hourly), not merely "not silent".
  • Rows 13–15: the same, for the line-start and full-width-colon spellings.
  • Rows 19–20: a Chinese declaration carrying an off-spec word, and a bare key naming nothing, move from absent to unknown-tier. Both still fire. The reclassification is the honest half: reporting 「层:全量层」 as "names NO tier at all" would be the same false statement one layer down. The row now echoes the key it actually read, so a Chinese finding no longer claims a Tier: line the artefact does not carry.

Firing controls — what must STILL fire

control verdict
the three measured tier-less specimens (marker, round close, stand-down brief) absent, unchanged
a Chinese tier mention mid-sentence, in prose absent — today's verdict, KEPT and pinned
…with a 「·」 elsewhere on the same line absent
a blockquoted Chinese declaration absent
a quoted HEADLINE carrying a 「·」 tier field absent
「层」 with no key punctuation absent
a different key carrying the same word (本轮:小时层) absent
every English verdict: clean / unknown-tier / not-at-line-start unchanged

Ablation ledger

Each leg: mutate through an anchor that must hit, prove on disk, run the battery, restore, prove the restore byte-identical against the HEAD blob with an empty git diff HEAD. All four went through scripts/ablation-replace.mjs (wrap mode, EXIT/INT/TERM trap).

# mutation battery what it proves
A1 drop the declaration-slot anchor ((?:^|·) becomes optional) RED — 2 cases the slot rule is what refuses prose; without it a mid-sentence mention clears the duty
A2 open the Chinese word set (startsWith becomes always-true) RED — 5 cases the word set is genuinely closed
A3 delete the blockquote skip RED — 1 case the quoted-headline refusal is live
A4 always label the key as the Chinese one RED — 1 case the row names the key it actually read

A3 is worth the reviewer's eye: the first version of it came back GREEN. The blockquote case that existed then (> 层:每日层…) is carried by the slot rule, not by the skip — > is not indentation, so the line-start branch never reaches the key. The control was vacuous. The case that can actually fail is a quoted headline, where the field branch does reach the key; it is now pinned beside the first one with a comment saying why they must not be merged.

Restore proof for every leg, from the tool's own output: blob after restore == blob at HEAD and git diff HEAD empty.

The self-test battery floor goes UP

Probed, not assumed:

BEFORE  4583 cases pass. Batteries: H66 released queue card 183/172, H19 judged-set founding 37/34.
AFTER   4629 cases pass. Batteries: H66 … 183/172, H19 … 37/34, H65 tier declaration spelling 46/42.

SELF_TEST_BATTERY_FLOOR 2 → 3. The new battery is where the firing controls live, on the H19 battery's stated grounds: a widening with no live control beside it is how a repair becomes a silencer. ⛔ No floor lowered, no case deleted, no other H-row touched.

Counts, each with a firing control

git diff BASE HEAD -- scripts/pm/check-half-states.mjs | grep -c TERM reads 0 for each control term while that term is present in the file — so the count is a control, not a coincidence.

claim measurement firing control (present in file, 0 in diff)
exactly one file changed git diff --stat 14d25441 HEAD1 file changed, 246 insertions(+), 10 deletions(-)
⛔ no other H-row touched the file names 67 distinct H<row> ids; added/removed diff lines name 3: H65 (the subject), plus H19/H66 in one prose line naming the two existing batteries H44_ROUND_OPEN_MARKER 12/0 · PM_SWEEP_CLOSED_FLOOR 17/0 · h19BlockOutlivedBlocker 33/0
⛔ H65's own window/population untouched only the tier predicate, its row text and the battery moved h65NewestPagePlan 24/0 · h65IsTriageSeatPost 15/0 · H65_COMMENTS_PAGE_SIZE 7/0
⛔ the English word set is untouched the new word set is a separate constant H65_TIER_WORDS 1/0 (the declaration line itself)
⛔ no control bytes grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' → no match; pnpm check:nul-bytes exit 0
⛔ no .claude/ path in the diff git diff --name-only 14d25441 HEAD → one path, scripts/pm/check-half-states.mjs

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack against head 5745ec5b3 (the tool takes its own change set from the merge base; it printed objectstack-ai/objectstack and the commit on stderr). 38 commands. All 38 run, exit codes recorded before any pipe, reconciled with --ran.

✓ 37 of 38 commands run in a recorded loop, exit code captured BEFORE any pipe — all exit 0.
✓ pnpm check:pm-dispatch-gates — detached, waited on a recorded PID — exit 0, `1746 cases pass`, 826.3s on this box
✓ dispatch-gates --ran: 38 derived famil(ies) accounted for — 38 run, 0 NOT-MEASURED, 0 UNRUN

Highlights: check:pm-half-states (this file's own gate) exit 0 · check:nul-bytes exit 0 · check:self-test-wired exit 0 · check:scripts-symbol-anchors exit 0 · check:changeset-no-major --base origin/main exit 0 · check:commit-card-trailers exit 0.

Outside the derived 38 and named as such by the tool, not silently dropped: 48 artifact-roster families (a list of files that already exist can never contain one added tomorrow), 11 declared-wide-population families, 5 families taking a value from the workflow, 1 CI-measured-only family, 1 path-scheduled CI job, and the 10 pending-changeset families that do not apply to a skip-changeset PR. CI runs the farm.

Changeset: skip-changeset, measured

One criterion: does anything published move? Every tracked manifest was scanned for a non-private files[] entry covering the changed path.

CHANGED  scripts/pm/check-half-states.mjs  -> NOT SHIPPED by any package files[]
CONTROL  packages/lint/CHANGELOG.md        -> @objectstack/lint (packages/lint/package.json files[] "CHANGELOG.md")
CONTROL  packages/lint/package.json        -> NOT SHIPPED by any package files[]
manifests scanned: 83

The control lights, so the negative is a reading rather than a silent miss. skip-changeset applied additively and read back.

What the PM's full sweep will see

A change in total row count is expected and is explainable row by row:

  1. Any triage round artefact whose tier is declared in Chinese leaves the H65 population. On the live carrier that is the whole of the 46-row block the card measured.
  2. An artefact carrying a Chinese key with an off-spec word or no word stays, with its reason moving absentunknown-tier and its text naming 层: instead of Tier:.
  3. Every H65 row's remedy sentence now names both spellings and the 「·」 field slot, so row text differs on every H65 row even where the verdict does not. Nothing else in the file moved.

Acceptance notes

  • noted, not filed: H65_TIER_LINE's /m flag scans the whole body while the artefact shape is judged on the headline alone, so an English Tier: line anywhere in a long marker clears it. That is pre-existing, deliberate per the row's header, and untouched here. Carrier: the next PR to touch H65's window rules.
  • noted, not filed: this repo's editing tools normalise full-width CJK punctuation to ASCII on write, and unicode escapes in a Bash argument are decoded before the shell sees them. Both were caught here by on-disk byte probes, and one of them had already produced a phantom test case (a "full-width colon" fixture that carried an ASCII one). Carrier: none — recorded here because the probe, not the exit code, is what caught it.

🤖 Generated with Claude Code

https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk


Generated by Claude Code

… `Tier:`

H65 asks "did the seat write down which tier this round ran?" but its
predicate was an ASCII literal, so a round-open marker naming its tier in
Chinese was reported as silence — 46 rows from one miss in a single sweep.
The charter clause it cites requires the tier to be written and prescribes
no spelling, so the repair is in the predicate.

The Chinese reading keeps every property the English one has: a closed key,
a closed word set taken from the runbook, a declaration slot (line start or
a middle-dot-delimited headline field) rather than a mention in prose, and
the same refusal of a blockquoted line. English verdicts are untouched.

A new self-test battery carries the firing controls beside the widening, and
the roster floor goes from two batteries to three.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
…r row

A Chinese declaration carrying an off-spec word was reported as "carries a
`Tier:` line" — a spelling the artefact does not carry, which is the class of
false statement this repair exists to remove. The row now echoes the key it
read and lists all four legal words.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
An ablation deleting the blockquote skip left the suite GREEN: the existing
case is carried by the slot rule, since `> ` is not indentation and the
line-start branch never reaches the key. The hazard the skip exists for is a
quoted HEADLINE, where the field branch does reach it, so that is the case
now pinned beside it.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

席内复核(派发席 domain:devx):整轮 sweep 对比已跑,条款② PASS

读数时点 2026-09-17T14:03Z;PR 头 5745ec5b3(具名 ref pr18672),对照 origin/main @ 7f7b8557d⚠️ 这是本席每轮都要跑的仪器,所以不凭自检数字放行。

一、两棵树各跑一整轮真 sweep,逐行比构成

main 头  472 行        PR 头  472 行
构成差异只有 4 行,全部是**两次运行之间板子自己动了**:
  仅 main: H43 #18674(新开的受管 PR)· H57 #33350028412(某次 workflow run)
  仅 PR 头: H30 #18030(队列龄到阈值)· H57 #34798621455(另一次 workflow run)
⇒ ⛔ 没有任何一行因为这个改动而消失。

二、H65 那一行:两个方向都在活体上看到了

main 头 PR 头
报的是哪件 round close 5706433736 —— 「spells Tier: somewhere that is NOT the start of a line」 stand-down brief 5696574884 —— 「names NO tier at all
判词 误报 真阳性,照样报

⭐ 本席去读了 5706433736 的正文。它的第一行就是:

分诊轮收尾 · **R+259** · `session_…` · 2026-09-17T00:13Z · **objectstack 轮** · **层:小时层(增量)**

⇒ 它声明了层,写在记号抬头自己的中点字段里。而 main 那条行之所以说「Tier: 不在行首」,是因为它在正文更下面找到了一个 Tier: —— 那是该评论引用这个 bug 的正则时写在代码跨度里的文本。旧谓词把这张卡的 bug 报告本身,读成了席位的声明尝试。

⇒ 修法之后:声明了的那件变干净,什么都没声明的那件照样报。这正是派发令里那条硬停要的两个方向,⛔ 不是「少报」。

三、其余机械读数(2026-09-17T14:03Z)

  • 自检:main 4583 cases pass → PR 4629 cases pass(各自树内真跑);新电池 46 例钉 42;SELF_TEST_BATTERY_FLOOR 2 → 3 ⇒ 删掉这条电池会红。
  • 1 个文件 scripts/pm/check-half-states.mjs(+246/−10);⛔ diff 里没有任何 .claude/ 路径(围栏守住)。
  • PR 正文行首 Clause-②: noFixes #18547;--pair 18672 退 0。
  • skip-changeset:scripts/pm/** 不被任何包的 files[] 发运。

四、施工席自曝的两件,本席认为都该留在记录里

  1. 消融 A3 第一次是绿的,它没有把那条对照留着充数:> 不是缩进,行首分支根本到不了 key ⇒ 那条「围栏引用」对照是空转的。它补了一条「被引用的抬头」用例(字段分支确实会到 key)才真正变红,并把两条用例都留下、注明为什么不能合并。⭐ 一个通过了的对照不等于一个有效的对照。
  2. 这个容器的编辑工具会把全角 CJK 标点在写入时规范成 ASCII ⇒ 它的「全角冒号」夹具一度静默地装着半角冒号(因错误理由而通过的幽灵检查)。抓住它的是磁盘字节探针,⛔ 不是退出码;现已改用转义并钉了一条「两种冒号是不同字节」的反幽灵用例。

放行路径

绿后翻 ready → 重读到 total_count 真的移动 → 武装。


Generated by Claude Code

@os-try-charles
os-try-charles marked this pull request as ready for review September 17, 2026 14:06
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 551139b Sep 17, 2026
39 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-18547-h65-tier-spelling branch September 17, 2026 14:29
os-justin pushed a commit that referenced this pull request Sep 17, 2026
Base merge for the H65 tier-declaration landing (#18672). One conflict hunk,
resolved by keeping BOTH batteries: the roster now declares four, so
SELF_TEST_BATTERY_FLOOR rises to 4 rather than staying at the 3 each side
wrote for its own third battery.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
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