Conversation
…med card Reader + H19 leg + H26 stand-down in check-half-states.mjs; the three prose carriers rewritten in place. Verbose draft; compressed next. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
…amed card `Unlock-action: re-check #N when label <label> <absent|present>` joins the closed set beside `re-check PR #M`. check-half-states.mjs is its first reader: `unlockLabelExits` (both channels, the shared decorated-directive reader), `h19DeclaredExitFired` (the unlock sweep's second exit, under H19, on an OPEN resolved target whose label state matches), and H26 stands down for a target the card gives such an exit, prescribes the spelling or the close, and names the card's `Unlock-action:` lines that are not this exit — the silent fallback made loud on the card. Battery 'H19/H26 label-transition unlock exit' (11 cases, pin 10), roster floor 6 -> 7. The three prose carriers rewritten in place, each under the 120-byte cap, net 0. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi
…lock-action-label-transition
…lock-action-label-transition
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19255
Clause-②: no
A second recognised
Unlock-action:value whose predicate is a label transition on a named card, and its first machine reader. No new label, no new state; every other spelling still falls back silently, by ruling — the closed set is the contract, and now H26 says so on the card.(Angle-bracket placeholders are spelled out in this body —
LABELfor the label slot,absent|presentfor the state slot — because the platform sanitizer eats short angle-bracket fragments even inside backticks; the files carry the real spelling.)Reader measurement (mechanism assumption 1) — taken on
e3b3cddgit grep -n "Unlock-action" HEAD -- scripts .github .claude→ exactly three prose lines and no script:.claude/skills/pm-dispatch/SKILL.md:128,references/core-rules.md:33,references/state-machine.md:29. Case-insensitive /unlockAction/UNLOCK_ACTIONvariants: zero hits. Control:Blocked-byhitsscripts/pm/check-half-states.mjs299 times. So before this PR the PR-shaped value was honoured by seats reading prose and by nothing else; 「别的拼写静默回落」 was a statement about people.The target-closed predicate does exist as code:
h19BlockOutlivedBlocker(check-half-states.mjs:5113) judges 「target CLOSED」 on the resolutionsresolveBlockerTarget(:24098) already holds, andh26BlockOnIndefiniteTargetjudges the same rows' labels. So per the dispatch's own branch for this reading, the sweep half is that reader learning the new exit — a sibling leg under the sameH19id, judged on the same resolutions, report-only, zero new requests — ⛔ not an invented sweep.The spelling
Unlock-action: re-check #N when label LABEL absent|present(cross-repo:re-check owner/repo#N when label LABEL absent|present). It names the card, the label and the state this sweep tests on it — a state, not an event, because a sweep observes labels and never transitions.absentis the live case (the ruling lands andneeds-user-decisionleaves the target). Trailing prose after the state word is tolerated; a backticked label is read bare; the decorated-directive reader and both channels (body, comments) are the same onesBlocked-by:uses.What changed —
scripts/pm/check-half-states.mjs(+87 / −8 = +79 net, budget ≤ +80)directiveValuesJSDoc key union admits'Unlock-action'(one annotation line; the reader itself is unchanged).UNLOCK_LABEL_EXIT_RE,unlockLabelExits(issue, commentBodies, ownerRepo)(body then comments, keyed byblockerTargetKeylike the card's targets) andh19DeclaredExitFired(issue, resolutions, commentBodies, ownerRepo): null unless a declared exit has come true on a resolved OPEN target; a closed target stays H19's own leg and an unresolved one its UNJUDGED leg, so no target reports twice under one id.h26BlockOnIndefiniteTarget(issue, resolutions, commentBodies?, ownerRepo?)— stands down for a target the card gives such an exit (the exit is now fireable); the remedy sentence prescribes the live spelling or thenot plannedclose (「无机制可唤醒的卡 ⛔ 不 hold」 one state over) and names the card'sUnlock-action:lines that are not this exit — the silent fallback made loud on the card. Two-argument callers are unchanged (every pre-existing H26 case still passes as written).h19DeclaredExitFiredrides the same resolutions and the same comment fallback as H19/H26; H26 is handed the comment bodies.SELF_TEST_BATTERIESgains'H19/H26 label-transition unlock exit': 10(11 registered);SELF_TEST_BATTERY_FLOOR6 → 7, and the three existing floor pins (:29049 / :35446 / :35789) move with it.selfTest()'s tail.Prose — three carriers, each rewritten in place, net 0, all under the 120-byte cap
SKILL.md:128- \Unlock-action: re-check PR #M` 行改写完工卡的解锁动作,只认此一值,别的拼写静默回落。` (111)- \Unlock-action:` 只认 `re-check PR #M` 与 `re-check #N when label LABEL absentstate-machine.md:29- 正文加机器可读行 \Unlock-action: re-check PR #M`,把解锁出口改为重查该 PR 落地。` (102)- 正文行 \Unlock-action: re-check PR #M`(重查落地)或 `re-check #N when label LABEL absentstate-machine.md:30- 只认此一值,别的拼写静默回落重派,散文另起行;停放的 PR 正文须点名那张门禁卡。(111)- 只认此二值,别的拼写静默回落重派,散文另起行;停放的 PR 正文须点名那张门禁卡。(111)core-rules.md:33- \pm:blocked` 配正文行 `Blocked-by:`,工已完而卡在门禁的同用此态并写 `Unlock-action:` 行。` (111)- \pm:blocked` 配正文行 `Blocked-by:`,完工卡遇门禁或等换标的同用此态并写 `Unlock-action:` 行。` (117)Line counts unchanged: 813 / 42 / 151 (
check:pm-skill-ratchet: 「SKILL.md is 813 lines (ceiling 813; headroom 0)」, 「state-machine.md is 42 lines (ceiling 42; headroom 0)」, 「core-rules.md is 151 lines (ceiling 151; headroom 0)」).Verification (final head
55d5e47)pnpm check:pm-half-states→ exit 0: 「✓ check-half-states self-test: 5092 cases pass. Batteries: … H19/H26 label-transition unlock exit 11/10.」node scripts/ablation-replace.mjs, wrap mode, on the committede38a8f8whose script blob is byte-identical to55d5e47's —2b93576c1e88…at both): anchorfor (const text of [issue?.body, ...(commentBodies ?? [])]) {×1 →for (const text of [/* ABLATION-19255: reader blinded */]) {; on-disk proof 「anchor 1 -> 0, blob 2b93576c1e88 -> 5429ddd97e1f」; self-test under the mutation exit 1, 7 of 5092 cases failed, all in this battery (the three reader pins, both H19 exit pins, both H26 stand-down pins) — direction: turns red, as expected; restore proven 「blob after restore 2b93576c…, blob at HEAD 2b93576c…, git diff HEAD empty」;git status --porcelainempty afterwards.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; derived from the merge-base changed set) → 48 families, list identical one38a8f8,cad8654and55d5e47. All 48 run on55d5e47, each exit captured before any pipe, 48 × exit 0; then--ranover the exit-coded record: 「✓ dispatch-gates --ran: 48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED (a DERIVED zero — all 48 recorded an exit code and none of them is 3).」check:pm-dispatch-gates(48th) ran detached per its own header (its battery exceeds the foreground cap on an agent box): 「✓ dispatch-gates self-test: 1867 cases pass.」,GATE-EXIT=0, 1057.3s on the shared box (not an idle-box figure).check:doc-formula-expressions(@objectstack/formula,@objectstack/lintclosures) underbash scripts/pm/os-verify-lock.sh: 「VERDICT command-exit 0 · held the lock 215s · waited 0s」.node scripts/pm/check-governed-merges.mjs --testwith the four paths (and--branchon the pushed ref) → exit 3, 「GOVERNED — Tier S: 3 of 4 path(s) hit the register」,.claude/**×3 — so this PR stays draft; the owning seat's in-seat contract-tier review lands it.npx eslint --no-inline-config --format json scripts/pm/check-half-states.mjs→ exit 0, 1 file, 0 errors, 0 warnings): ① population read fromeslint.config.mjsitself — root-only flat config,.mjsunderscripts/in scope; ② file count 1 from the--format jsonoutput; ③ invariance:eslint.config.mjs:328states noparserOptions.projectand no typed rules, so this diff cannot move any untouched file's verdict. The repo-widepnpm lintis CI's run.grep -naPwith the exit captured before any pipe) → exit 1, zero matches;check:nul-bytesexit 0.origin/maintwice (596090e, then13d5294); neither moved the four files (three-dot);os-regen-pendingabsent; delta vsorigin/mainis exactly the four files.Mechanism-assumption readouts
state-machine.mdneeded two in-place lines (:29 and :30 — 「只认此一值」 → 「只认此二值」, or the pair would contradict itself); still net 0 and both ≤ 120 B.9180b909, read viagit diff origin/main...) retires the 「NO MECHANISM」 wording at :586–:614 / :6756–:6907 / :23968 / :30493–:30599; this PR's assertions pin none of those phrases (they pin 「falls back silently」, 「⭐ The exit」, the live spelling, the close, 「on 1 target(s)」, and the named fallback line). The remedy is a separateparts.push(...)after the indefinite sentence, so finding(pm-gate): H26's "can never CLOSE" premise is asserted, not counted — counting refutes it, and objectui's ported copy cannot be fixed without this one moving first #18017's rewrite of that sentence merges around it — expect one trivial textual conflict there (its hunk ends within three lines of this insertion) and none elsewhere.re-check #N when label LABEL absent|present— one value, both directions, card + label + state all named.Acceptance notes
--ranaccepts a bare command list but grades exit codes only when each line is spelledcommand :: exit N(its own text says so); the run record here uses that form. 承接者: whoever next editsdispatch-gates.mjs(finding(scripts/pm):check-half-statesreads the LOCAL checkout with nocwd, so a cross-repo sweep validates H17's trigger-file index against the wrong repo'sgit ls-filesand H57 never runs at all #19191's seat).--commandsderivation one38a8f8printed 「STALE TREE — 3 commit(s) behind origin/main, 2 file(s) it derives from CHANGED」; re-derived after each merge, list identical. 承接者:无.parts.pushlands directly under the sentence that branch rewords; keep both on merge. 承接者: finding(pm-gate): H26's "can never CLOSE" premise is asserted, not counted — counting refutes it, and objectui's ported copy cannot be fixed without this one moving first #18017's dev.e38a8f8,PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-half-states.mjs --self-test→ exit 1, 3 of 5092 cases fail (「H38 seat: a numbered seat on a SIBLING board is still foreign」, 「H38 verdict: ⛔ on the OLD window the post reads STALE…」, 「seat marker window: …the held own-board seat it DOES read is a subset of H44's leg」); 0 of the 3 are in this card's battery or regions, and this card's cases pass under that env because they passREPO_OSexplicitly. Not re-measured onorigin/main. Dedupe words:self-test env-dependent PM_SWEEP_REPO·H38 seat sibling board self-test·check-half-states self-test foreign repo.维护者速读(草稿)
改了什么:状态模型的
Unlock-action:行从只认一种拼写(re-check PR #M,重查 PR 落地)变成认两种:新增re-check #N when label LABEL absent|present,意思是「等某张卡上的某个标签变成有/无」。半状态巡查脚本第一次真正读这行:目标卡的标签状态一旦符合,H19 报「出口已成立」;给了这种出口的 H26「永远放不开」行自动闭嘴;没给的 H26 行现在会直接告诉你该写哪一句,或者关 not planned,并把卡上那些机器读不到的Unlock-action:拼写点名出来。为什么改:
pm:blocked卡等一张needs-user-decision卡时,解锁判据是「目标关闭」,而决策卡裁完通常仍 open,所以这种等待以前没有任何机制能放开;唯一允许的改写又是 PR 形状、只认一种拼写,别的拼写静默回落——最坏的失败形态,因为那行读起来像做了事。实测e3b3cdd上Unlock-action:只出现在三行文档里,没有任何脚本读它;这次把「只认此一值」扩成「只认此二值」,并给它第一个读者。风险与代价(含回滚):不新增标签、不新增状态、不写任何标签(仍是 report-only,放行照旧走解锁扫描的双查);其它拼写行为不变(仍静默回落,只是 H26 会点名)。脚本净增 79 行(预算 80),三份文档各净增 0 行、每行 ≤ 120 字节;自测新增一个 11 例的电池并把电池底线 6→7。与 #18017 在 H26 同一行文字相邻,预计一次琐碎冲突。回滚 = revert 本 PR 的四个文件,无生成物、无 changeset。
席位意见:(留空,由席位定稿)
你要做的:本 PR 触及
.claude/**(Tier S),保持 draft;由domain:skills席位做席内达档复核后落地,无需维护者点击。若不同意第二种拼写的措辞(when label LABEL absent|present),在复核里改一处即可:SKILL.md:128、state-machine.md:29、check-half-states.mjs的UNLOCK_LABEL_EXIT_RE与两条 remedy 文案。Generated by Claude Code