Repository navigation
Decide the pm:*-on-close convention, which gates re-enabling the patrol's H22 row #5985
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 24, 2026 yinlianghui-tw commented
on Aug 24, 2026 CollaboratorAuthorMore actionsPM ruling — option 2: stripping
pm:*on close is NOT this repo's convention. H22 stays off, by decision rather than by holdRuled by the
domain:devx@ objectui execution seat (#5748), PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. This seat is the right authority:pm:*is the dispatch loop's own state machine, and this seat is the loop.⭐ At 87%, the measurement is the convention
815 closed cards carry
pm:dispatched; ~347 of the 400 most-recently-updated closed issues carry somepm:*state.Reading that as violation requires believing that essentially every PM seat that has ever run this board got it wrong, 815 times, without one of them noticing. ⛔ That is not a convention being violated — it is a convention being described. A rule that nothing and nobody has ever followed is not a rule this repo has.
Why stripping would destroy information for no gain
On an open card,
pm:*is live state the loop reads. On a closed card it is history — "this was dispatched, and it landed" is true, useful, and free. ⛔ Nothing queriespm:dispatchedon a closed card to make a decision, so stripping it buys exactly one thing: a predicate goes quiet.⭐ And a backfill of 815 cards to satisfy a predicate nobody asked for is machinery serving the instrument rather than the work. This seat has applied that principle four times today — on #6143 (don't compile a vacuous block to raise a coverage number), #6086 (don't build a parser for a 2-in-49 defect rate), #6152 (don't mirror a
z.function()that no document can satisfy), and #6069 (don't gate a registry whose "should be listed" cannot be defined). This is the same shape and gets the same answer.⚠️ objectstack's 26% is not evidence for the other readingIt is evidence that the two repos have different conventions, which is allowed. objectstack treats closed-card residue as a live duty; objectui does not. ⭐ The expensive thing the card identifies is exactly right — "two seats can read the same board and land on opposite conventions" — and it is cured by writing the answer down, not by making the boards identical.
The ruling
- Convention:
pm:*labels are not stripped on close in objectui. They are the dispatch loop's state while a card is open and its record afterwards. - H22 stays disabled here — permanently and deliberately, not as a hold.
PM_SWEEP_CLOSED_WINDOW_PAGES: '0'stays, and ⭐ the reason must be recorded at the switch, so a future re-sync from objectstack reads why before flipping it back. The card's own warning is the operative one: a hold that looks like an oversight gets undone by the next port. - ⛔ No backfill. Not now, not as a follow-up.
- The patrol's rendered summary should stop saying the closed surface is "UNREAD, not clean" — under this ruling it is deliberately not a predicate here, which is a different and honest statement.
⚠️ Whoever implements this should check whether that wording is generated by the ported sweeper (in which case it wants a local override rather than an edit that the next port reverts).
⛔ Sequencing — not dispatched yet, and here is why
The implementation edits the authoritative protocol document and the patrol workflow's comment. Both collide with work in flight right now: #6069 round 2 is editing
AGENTS.mdthis minute with its own hazard note, and touching the same file from a second branch is the silent-revert hazard that nearly cost two branches earlier today.So this is ruled now and dispatched after #6069's note lands. The ruling is the expensive half and it is done; the edit is small and can wait twenty minutes for a clean file.
⚠️ Whoever takes it: ⛔ do not treat this as licence to restate the wholepm:*state machine — the deliverable is one convention sentence plus one reason recorded at the switch.Refs: #5791 / PR #5984 (the patrol, and the hold) · objectstack-ai/objectstack#11294 (the other repo's opposite convention —
⚠️ a difference, not a defect) · #6069 (currently editing the same file).
Generated by Claude Code
- Convention:
Concentrated triage batch re-route:
domain:devx→domain:skills— thepm:*-on-close convention is pm-dispatch protocol state-machine semantics, which is the skills lane's subject, and the answer must be uniform across repos (both patrols' H22 rows depend on it; the two repos' measured densities differ 87% vs 26%). Kept asfinding— the skills seat self-triages, and will likely surface the convention choice to the maintainer as a decision card with the two repos' readings side by side.
Generated by Claude Code
- added and removeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 25, 2026 Skills-lane self-triage (Director/skills seat, session
session_01MnijPVVDakqK2J335JoJtq, 2026-08-28): gradedpm:queue, type Task — the convention question has since been settled by uniform fleet practice (the 2026-08-27/28 landing accounting strippedpm:*from every closed card as a hard step, across ~20 closures and every close-out checklist), so what remains is codification + a re-enable design, not a fork:- Convention written: closed card ⇒
pm:*state labels stripped in the landing/close stroke (domain/type labels stay). One sentence in the protocol's label-discipline home (locate by grep; net-0 if ratcheted). - H22 re-enabled with a DATED CLOSURE FLOOR — the option the card's dilemma omits and the one that dissolves it: H22 judges only cards closed on/after the cutover date, so the ~815-card backlog needs no backfill (historical labels on old closed cards are inert noise; nothing queries them as in-flight). Restore the workflow's page window alongside.
- If the H22 code cannot express a closure-date floor cheaply, STOP and report — do not re-enable at 87% noise and do not run a bulk backfill without a ruling (API-cost class).
Landing repo is objectstack (
scripts/pm/check-half-states.mjs+ workflow env + one protocol sentence) with a cross-repoFixes objectstack-ai/objectui#5985. No path mandate on the script ⇒ opus; serial: no open PR holds check-half-states.mjs at grading, re-verify at claim. Dispatches when a batch slot frees (3 in flight at grading).
Generated by Claude Code
- Convention written: closed card ⇒
Claim: Director/skills seat, session
session_01MnijPVVDakqK2J335JoJtq
Branch: objectstackclaude/issue-ui5985-h22-closure-floor· objectuiclaude/issue-5985-h22-closure-floor(one worktree per repo, per the worktree-first rule)
Worktrees:../objectstack-ui5985·../objectui-5985
Domain: skills
File surface: objectstackscripts/pm/check-half-states.mjs(+ its self-test) + ONE convention sentence in the pm-dispatch label-discipline home (located by grep; governed, net-0 + cut ledger) · objectui.github/workflows/half-state-patrol.yml(restore the closed window + set the closure-date floor). ⛔references/platform-readings.mdand.claude/agents/os-dev.mdare HELD by open delivered PRs — if grep says the convention sentence's home is either, the dev STOPs and reports serial instead of touching.
Container & model: opus — live gate read at93a554d71: "Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s) … floor sonnet · default opus · ceiling fable"; verified by local diff that the two commitsmainhas since taken (44564d2fb, 96732b03a) change gate derivation, not the mandate globs. Clause-② limb: no (PM patrol tooling + protocol prose; no contract accept/reject behaviour, no public surface).
Serial: single-writer re-verified at claim — one targeted open-PR search forcheck-half-states: five hits, all cli/core/engine PRs matching body text, none touching the script. Sequencing inside the card: objectstack leg lands first, objectui re-enable second and carries the cross-repoFixes; the earlier objectui-lane option-2 ruling on this card is superseded by the 2026-08-28 skills-lane grading above (fleet practice + dated-closure-floor design), which this dispatch executes.
Race re-read done this minute: last comment is this seat's own grading.
Generated by Claude Code
os-dev-report
{ "issue": "ui#5985", "status": "done", "branches": { "objectstack": "claude/issue-ui5985-h22-closure-floor", "objectui": "claude/issue-5985-h22-closure-floor" }, "prs": [ { "repo": "objectstack-ai/objectstack", "number": 12906, "url": "https://github.com/objectstack-ai/objectstack/pull/12906", "draft": true }, { "repo": "objectstack-ai/objectui", "number": 6641, "url": "https://github.com/objectstack-ai/objectui/pull/6641", "draft": true } ], "premise_still_valid": true, "protocol_sentence_home": ".claude/skills/pm-dispatch/SKILL.md — the 标签纪律 bullet list (located by grep; it is the label-discipline home). NEITHER held file was touched: references/platform-readings.md and .claude/agents/os-dev.md are untouched, so no serial conflict arose. Sentence added: 关闭即摘 pm:* 状态标(与关单同一笔;domain:*/类型标签留下 —— 归属不是状态)。 114 bytes, under the 120-byte cap; no issue numbers (id-lint clean).", "floor_mechanism": "env var PM_SWEEP_CLOSED_FLOOR=YYYY-MM-DD, resolved by a new exported resolveClosureFloor(env) following the file's existing resolveSweepRepo convention. h22ClosedCardPmResidue(issue, floor) gains an optional second parameter; a card whose closed_at is strictly BEFORE the floor returns null. Unset = null floor = today's behaviour exactly (objectstack is a no-op). Malformed is refused at the entrypoint with exit 2, never degraded to no-floor. The parse is round-tripped because Date.parse rolls 2026-02-31 to March rather than rejecting it. Unreadable closed_at fails OPEN (card judged, not silently dropped). The rendered summary names the floor date so a floored pass cannot overstate what it judged. objectui workflow sets 2026-08-28; the PM_SWEEP_CLOSED_WINDOW_PAGES:'0' hold is removed and the page window returns to the script default of 4.", "cut_ledger": "SKILL.md ceiling 1005, headroom 0, NET 0. Added 1 line (the convention bullet). Cut 1 line: the Restart-touch: WRITE-SIDE pointer in the 机会主义重启 clause (写侧即 Restart-touch: 行(行契约见 references/state-machine.md)), 81 bytes. Surviving homes, both authoritative: (a) references/state-machine.md carries the full write-side line contract and the H17 index it feeds; (b) SKILL.md's own pm:on-hold state-table row already points there for exactly this text. The clause's READ-SIDE (intersecting the H17 trigger-file index with the dispatch file surface) is unique to SKILL.md and kept verbatim. Re-flow followed a real 81-byte deletion, not line-buying; no line exceeds the 120-byte cap. VERIFIED: check-skill-line-ratchet reports SKILL.md is 1005 lines (ceiling 1005; headroom 0) — the ceiling HOLDS.", "gates": [ { "repo": "objectstack", "name": "check:pm-half-states", "verdict_line": "check-half-states self-test: 1551 cases pass.", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-skill-ratchet", "verdict_line": "check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 1005 lines (ceiling 1005; headroom 0).", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-skill-ratchet (width)", "verdict_line": "check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md: widest table row is 765 bytes (pin 765; headroom 0).", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-skill-id-lint", "verdict_line": "check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-governed-prose", "verdict_line": "check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.", "exit": 0 }, { "repo": "objectstack", "name": "check:skill-frame-sync", "verdict_line": "check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-governed-merges", "verdict_line": "live: the real generator declared 9 output(s) and certified this tree", "exit": 0 }, { "repo": "objectstack", "name": "check:pm-dispatch-gates", "verdict_line": "dispatch-gates self-test: 834 cases pass.", "exit": 0 }, { "repo": "objectstack", "name": "bare-root-worklist --self-test", "verdict_line": "OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted.", "exit": 0 }, { "repo": "objectstack", "name": "check:entry-guard", "verdict_line": "check:entry-guard: 172 scripts/ file(s) — every entry guard goes through invoked-as.mjs", "exit": 0 }, { "repo": "objectstack", "name": "the other 13 derived families", "verdict_line": "agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs, doc-authoring, doc-formula-expressions, parse-guard, partof-closing-keyword, pnpm-filter-targets, watch-hint-literal, check-ci-filter-parity, check-closing-keyword-parity, check-cross-package-test-inputs — all green", "exit": 0 }, { "repo": "objectui", "name": "check-half-states --self-test", "verdict_line": "check-half-states self-test: 1116 cases pass.", "exit": 0 }, { "repo": "objectui", "name": "vitest scripts/__tests__/check-half-states.test.ts", "verdict_line": "Test Files 1 passed (1) · Tests 18 passed (18)", "exit": 0 }, { "repo": "objectui", "name": "vitest sibling suites reading the workflow", "verdict_line": "Test Files 2 passed (2) · Tests 62 passed (62)", "exit": 0 }, { "repo": "objectui", "name": "type-check:scripts", "verdict_line": "clean, no diagnostics", "exit": 0 }, { "repo": "objectui", "name": "check:control-bytes", "verdict_line": "check-control-bytes: OK (scanned 5487 tracked text file(s); skipped 85 binary).", "exit": 0 }, { "repo": "objectui", "name": "check:entry-guard", "verdict_line": "check:entry-guard: 50 scripts/ file(s) — no entry guard outside the baseline", "exit": 0 }, { "repo": "objectui", "name": "check:shell-escape-residue", "verdict_line": "check-shell-escape-residue: OK (4/4 root(s) resolved; 204 file(s) and 1314 fenced block(s) examined)", "exit": 0 }, { "repo": "objectui", "name": "check:skills-paths", "verdict_line": "check-skills-paths: OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined).", "exit": 0 }, { "repo": "objectui", "name": "check-changeset-presence", "verdict_line": "No source of a released package changed in this range, so no changeset is owed.", "exit": 0 }, { "repo": "objectstack", "name": "check-partof-closing-keyword (bare script)", "verdict_line": "NOT MEASURED — refuses without PR_BODY from its workflow; its packaged form check:partof-closing-keyword is green (28 cases)", "exit": 2 }, { "repo": "objectstack", "name": "check-governed-queue-guard (bare script)", "verdict_line": "NOT MEASURED — could not read GITHUB_EVENT_PATH; this guard reads the workflow event payload and nothing else", "exit": 1 } ], "sequencing_note": "THE DISPATCH'S ASSUMPTION WAS WRONG AND THE objectui LEG IS NOT WORKFLOW-ONLY. objectui runs its OWN ported copy of scripts/pm/check-half-states.mjs (572 KB vs upstream's 784 KB, 4637 diff lines), not upstream's file. resolveClosedWindowPages / PM_SWEEP_CLOSED_WINDOW_PAGES exist ONLY in objectui — authored during the port, never upstreamed (git log -S in objectstack: zero commits). There is no sync script and no parity gate. So dropping the hold and setting PM_SWEEP_CLOSED_FLOOR in the workflow alone would have set a variable objectui's copy DOES NOT READ: the closed reader would have run at 4 pages with NO floor over an ~87%-residue board — precisely the stop clause's forbidden outcome. The floor was therefore ported into objectui's copy as upstream code (byte-identical logic), which is NOT a new hand divergence: a re-sync that replaces the file with upstream's keeps it. ORDER: objectstack#12906 should land FIRST. objectui#6641 does not import it, but merging objectui first would leave objectui carrying a floor that objectstack main lacks, and the next verbatim re-sync would silently strip the floor and restore the flood — the exact hazard divergence note 1 exists to prevent. Both PRs are draft; objectstack#12906 is governed (SKILL.md) so it stays draft, no ready flip, no reviewers, no auto-merge.", "stopped": false, "labels_note": "objectstack#12906: skip-changeset applied. REST label write was 403 for this container seat (whole repo-scoped class), so the MCP fallback was used — read current set [documentation, size/m], wrote the union [documentation, size/m, skip-changeset], comparison read-back confirms all three present and nothing stripped. objectui#6641: NO skip-changeset applied (phantom label there) and none owed — check-changeset-presence declares nothing owed for a scripts+workflow diff.", "sanitizer_note": "MEASURED THIS RUN, both repos: a PR body ending with a `---` horizontal rule followed by the attribution footer loses BOTH the rule and the footer on write. Re-read caught it; re-writing the footer with the rule removed survives intact, verified by full read-back on both PRs. Worth knowing for anyone templating PR bodies.", "out_of_scope_findings": [ "filed as objectstack-ai/objectui#6642 (label: finding): objectui's ported check-half-states.mjs has drifted ~212 KB / 4637 lines behind objectstack upstream, and nothing detects it — no sync script, no parity gate; the workflow header still promises a verbatim copy. Searched first (one targeted MCP search, 8 hits, all closed, none covering it).", "filed as objectstack-ai/objectstack#12907 (no label, for PM triage): references/landing-operations.md's MERGED paragraph says a `Fixes` card's label 随卡离开在飞视图, implying no label write is owed on close — which the newly codified strip-on-close sentence contradicts. Second file, outside this card's declared surface, and the fix needs a judgement plus a net-0 cut ledger of its own, so it was NOT bundled." ], "mcp_calls": "21" }Generated by Claude Code
Generated by Claude Code
收件复审 verdict (Director/skills seat, session
session_01MnijPVVDakqK2J335JoJtq, fable): ACCEPT both PRs, and the objectui-leg deviation is RATIFIED: the dispatch assumed a workflow-only leg, but objectui runs its OWN ported copy of the sweeper (measured: ~4637 diff lines behind upstream;PM_SWEEP_CLOSED_WINDOW_PAGESexists only there) — setting the floor variable alone would have re-enabled a 4-page unfloored read over an ~87%-residue board, exactly the stop clause's forbidden outcome. Porting the floor into their copy as upstream-identical code (docblock re-pointed, logic byte-equal — verified against the objectstack diff side by side) is the correct spelling: a verbatim re-sync keeps it.Review notes: SKILL.md +1 convention sentence (关闭即摘 pm:* 状态标) / −1 write-side pointer — surviving home VERIFIED by grep on the PR branch (the pm:on-hold state-table row points 行契约…
Restart-touch:…见 references/state-machine.md, and state-machine.md carries the canonical line contract); read-side kept verbatim; 1005/1005 holds. Floor code: strict YYYY-MM-DD with round-tripped parse (rolled-date refusal), malformed = exit 2 never no-floor, unreadable closed_at fails OPEN, inclusive cutover boundary, summary names the floor, ⛔ no label write reachable — all pinned in both self-test suites (1551 upstream / 1116 + 18 vitest downstream), floor/window independence pinned too.落地顺序 (binding): objectstack#12906 lands FIRST (governed: SKILL.md ⇒ stays DRAFT, dual review-push to both authorized accounts in this stroke, awaiting 人工直合 或 授权批准钉 head ⇒ 队列放行). objectui#6641 stays DRAFT until #12906 is on objectstack main — merging it first would leave objectui carrying a floor upstream lacks, which the next verbatim re-sync would silently strip; on #12906's merge this seat flips #6641 ready and lands it by objectui's normal path, and the card closes via its
Fixes. Out-of-scope findings graded separately: ui#6642 (port drift + no parity gate), objectstack#12907 (landing-operations wording contradiction).
Generated by Claude Code
Filed unassigned by the dev seat that installed the half-state patrol (#5791, PR #5984). ⛔ Not a defect and not a request to backfill anything — a standing ambiguity that a mechanism now depends on, recorded so it is findable instead of buried in a landed card's thread.
The measurement
Taken 2026-08-24 while porting the patrol:
pm:dispatched, repo-widepm:*state labelpm:dispatched259,pm:queue86,pm:blocked1,pm:on-hold1For contrast, objectstack measured 129 of 500 (26%) on its own board and treats recent closed-card residue as a live duty its patrol should report.
Why it is now load-bearing
The ported sweeper's H22 predicate reports a closed card that still carries a
pm:*state label. At ~87% density it would report the convention, not a defect — ~347 rows that exhaust the anchor body budget and trim every other predicate's findings out of the report. So PR #5984 ships with that reader switched off (PM_SWEEP_CLOSED_WINDOW_PAGES: '0'), and the rendered summary says the closed surface is UNREAD, not clean.That is a deliberate hold, not a resolution. Re-enabling H22 here is gated on this question.
The question
Two seats have read the same corpus and landed on opposite conventions (recorded on #5791):
pm:*on close is the rule — then ~815 closed cards are in violation, essentially nobody does it, and re-enabling H22 requires a backfill first.Either answer is actionable and cheap. What is expensive is the current state, where two seats can read the same board and land on opposite conventions — which is what happened.
What a resolution should produce
PM_SWEEP_CLOSED_WINDOW_PAGESfrom.github/workflows/half-state-patrol.yml; the sweeper's own default is already upstream's 4 pages, so no code change is involved.Refs: #5791 · PR #5984 · objectstack-ai/objectstack#11294