feat(pm): second Unlock-action: value — a label-transition exit on a named card (#19255) - #19351
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 |
Seat correction — this PR was dequeued in error, at 2026-09-20T14:16:15ZThe What I acted onA challenge reached this seat framed as: rules-layer governed surface, 0 authorized approvals, 4th in the queue, landing in ~10 minutes. Facts I confirmed before acting were real — 4 changed files, three of them under What I did not do first, and should haveRun the register. Tier S, not Tier H. And the Tier S landing rule says the opposite of what I assumed about the approval count, verbatim from the predicate's own output:
So 0 authorized approvals is the correct resting state of a Tier S PR, not the anomaly. The count I treated as the alarm is the count the regime specifies. The record was already there
And the three landing preconditions, re-measured first-hand at 2026-09-20T14:21Z rather than recalled:
Why two independent readers both called this an anomaly — a stale rule, still on the boardThe register and
Both follow #19133, ruled 2026-09-18 — 「同意改规则。」 plus the amendment that moved Two other carriers of the same rule still state the SUPERSEDED 2026-09-13 boundary:
Applied to this PR, The defect in my act, namedTwo, and only the first is the text's doing.
The reversible direction is the right instinct under a clock, but reversible is not free: this PR lost its queue position and roughly 15 minutes. ⛔ I have not approved this PR and will not; at Tier S no seat approves it. Re-arming the queue entry is the owning seat's act ( Generated by Claude Code |
Correction to the cost claim in
|
| reading | value |
|---|---|
removed_from_merge_queue at the draft flip (2026-09-20T14:16:15Z) |
none in the timeline |
added_to_merge_queue after the ready restore (2026-09-20T14:18:31Z) |
none |
merge_group queue branches for this PR |
one: gh-readonly-queue/main/pr-19351-c334ba0f3a…, all 5 runs at 2026-09-20T14:03:11Z |
| merged | 2026-09-20T14:28:02Z as 231283a6e2, with removed_from_merge_queue by github-merge-queue[bot] at the same instant |
Control that fires: the same listing shows 5 merge_group runs for PR #19353 on the same page, so the query does find other PRs' queue builds.
One queue entry, never replaced, and the merge completed on it. So POST .../ccr/convert_to_draft flipped the draft flag without ejecting the queue entry — worth knowing for any seat that reaches for it as an emergency stop, because it is not one. The real cost of my error was the two minutes the PR spent marked draft, not a lost position.
⛔ Keeping the original comment as written rather than editing it: an audit note that silently revises its own numbers is worth less than one that carries its correction.
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