Skip to content

feat(pm): report a [Decision]-titled card sitting in the dispatch pool (H54) - #17197

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-16688-decision-title-in-queue
Sep 9, 2026
Merged

feat(pm): report a [Decision]-titled card sitting in the dispatch pool (H54)#17197
os-justin merged 1 commit into
mainfrom
claude/issue-16688-decision-title-in-queue

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #16688

One report-only row in scripts/pm/check-half-states.mjs. No new label, no vocabulary change, no workflow change, no exit-code change, and ⛔ no live card relabelled.

What the row is

H54 — an OPEN issue (⛔ never a PR, ⛔ never a closed card) carrying pm:queue whose title, after optional leading whitespace, begins with [Decision].

pm:queue is the dispatch pool: a PM seat reads it as capacity and picks from it. A card whose title opens [Decision] declares its deliverable is a ruling, and 「⛔ PM 席不得裁决契约或产品决定」 — so the one act the pool invites is the one act forbidden on that card. The filing card measured the cost on one lane, twice: 8 read as dispatchable and 1 was (2026-09-07), then 21 read and at most 3 were (2026-09-09).

This is exactly the shape triage's acceptance criterion names (§验收口径 2 on #16688): 「一张标题以 [Decision] 开头的卡带着 pm:queue」 — 「最便宜的一道网」.

⛔ The prefix is not the state

The same ruling fences the row in the same breath: 「⛔ 不要把闸门做成"标题前缀即状态":前缀是作者写的,状态是分诊判的,让前者决定后者就是把分诊席的职责交给了立卡人」.

So the row reports the disagreement between two carriers and hands it to the seat that owns the second one. It does not say which carrier is wrong. Its remedy names the reader — the triage seat, who re-judges the card:

  • already RULED ⇒ the title is the last thing still advertising an open question: drop the prefix (or point the body at the ruling comment) and pm:queue stands as it is;
  • genuinely UNRULEDneeds-user-decision, carrying its four-facet analysis at the moment the label lands.

⛔ No new label is needed or wanted (§验收口径 1): needs-user-decision is live on cards and PRs, pm:awaiting-maintainer is live on cards, and the ruled deliverable is 逐卡改判.

The row also declares what it cannot see, so a reader does not over-trust it: a hot-file hold releases on MERGE, a contract-review-tier wait shows in no carrier at all, and a ruled card keeps its prefix until someone edits the title. ⛔ None of those is readable from a title or a label, and none is claimed.

Reconciling the file's own prose (⛔ H26 is NOT widened)

The deferred-target row said this shape was 「deliberately NOT reported … a title heuristic would make this sweeper guess at intent」, at two sites — the header block and the predicate's own comment. That stance was about H26 inferring a BLOCK's fate from its TARGET's title, and it stands: H26 still reads labels only, and is not widened to chase it. Both sites now point at H54 so the file does not contradict itself.

Two self-test cases pin the reconciliation off the source: both sites carry the pointer, and both still carry H26's own criterion. The needles are assembled at runtime so the cases cannot satisfy themselves out of their own source text.

Placement

Band state, by the file's own band criteria — two carriers on one live card contradicting each other, the whole repair on the board in one seat's judgement. ⛔ Not gate (that band is for an ABSENCE reading as a green light; here both carriers are present). ⛔ Not stall (whether any of these cards is actually stopped is UNMEASURED by this row — the filing thread's own correction found the lane's binding constraint to be contract-review-tier headroom, not rulings). ⛔ Not inventory (it alarms about one card). No new band, no new render section, no reservation change.

Quota: free. The carrier holds pm:queue, which is a label page the sweep's loop already consumes, and the title rides in on the same payload — so the population is covered by construction and no card outside the listing can reach the row.

Mechanism assumptions, measured

PM's assumption measured
A — a title matcher already exists to reuse partly falsified. The file reads titles in three places, all the same inline ^\[PM seat\] … regex for seat stickers — a [PM seat]-specific parser, not a general bracket-prefix helper, and there is no [finding] reader at all. So there was nothing to reuse; H54 declares its own anchored DECISION_TITLE_PREFIX beside the row it serves. ⛔ The three [PM seat] sites are left alone — refactoring them is not this card.
B — the matcher is anchored at the start, ⛔ no substring held. /^\s*\[decision\]/iu, with [Decision] later in a title pinned silent.
C — HALF_STATE_FAMILY_BAND decides render order and trim survival, and the file states a placement rule held. The bands carry a what field written to be read before placing a new family; state is the one that fits, so the fallback ("take the lowest band and say so") was not needed.
D — 7337179d is the newest touch and the base superseded, disjoint. origin/main had advanced to c3756ff0 by the time the worktree was cut (four docs commits plus scripts/pm/check-skill-line-ratchet.mjs); none touches this file. Branched from c3756ff0.

Reading the case sensitivity, stated rather than assumed

The matcher is case-insensitive on the word and exact on the brackets. [decision] and [Decision] are one author's one intent, and this file's standing doctrine is that an unknown reading must never present as a clean one (#4690) — a carrier missed on its capitalisation reads as a clean queue. It widens nothing that matters: [finding], the 80-strong prefix that must never be caught, differs in the word, not in its case; and the closing bracket is required immediately after the word, so [Decisions log] and [Decision-log] are not this prefix. All four are pinned.

Tests

node scripts/pm/check-half-states.mjs --self-test3019 cases pass (2976 before; +43). The 43 are a positive control, six negatives each differing from it in exactly one feature (no prefix · a [finding] prefix · [Decision] without pm:queue · closed · a PR · [Decision] later in the title), driven over a length-pinned table so a fixture that stops being exercised fails; plus anchor, population, adjacency (H24/H3/H45 all silent on the specimen), row-text, band/registry and reconciliation cases.

Ablation, once, on the committed implementation and reverted in the same run — evidence in the report.

Every gate node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives for this diff was run; verdict lines are quoted in the os-dev-report comment on #16688.

Acceptance notes

⛔ Observed, not this card's, and ⛔ not filed:

  • The sharper half from 5600821612 / 5600838579 / 5601308373: the over-read is real, but on the measured lane its largest bucket is not "cards nobody has judged" — it is cards that were judged correctly and cannot move because the contract-review tier that must review them is unavailable (8 cards against 2 genuinely awaiting a ruling). A carrier that distinguished "queued" from "awaiting judgement" would still show all 8 as ready. H54 does not claim otherwise and says so in its own sentence.
  • The hot-file family on the same lane (9 cards behind two PRs): a hot-file hold releases on MERGE, not on arming, so its answer changes under a reader on a timescale of hours; its carrier is the seat post's hot-file serial queue, and the only mechanical instrument for it fires after a PR exists. ⛔ Nothing here reads it.
  • pm:queue under-reads as well as over-reads (5600838579): two cards were dispatchable only in part, or only after a ruling, and neither fact is recoverable from the label.
  • The three [PM seat] title parsers are the same regex written out three times. A cleanup, not a defect: nothing misreads today. ⛔ Not filed; ⛔ not touched. Carrier: whoever next changes the seat-sticker title shape.

Landing

Draft. scripts/pm/** with no .md in the diff ⇒ not a governed surface, but the PM flips ready after the in-seat review. No changeset: scripts/pm is not a package and nothing published moves — the changeset gate's verdict is quoted in the report.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ


Generated by Claude Code

…ool (H54)

`pm:queue` is the dispatch pool: a PM seat reads it as capacity and picks
from it. A card whose title opens `[Decision]` declares its deliverable is a
ruling, and a PM seat may not rule a contract or product decision — so the
one act the pool invites is the one act forbidden on that card. The filing
card measured the cost on one lane twice: 8 read as dispatchable and 1 was,
then 21 read and at most 3 were.

H54 is a report-only row over the `pm:queue` label page this sweep's loop
already consumes: one label and one title, both fields of a payload already
paid for, so the row costs no request and its population is covered by
construction. It fires on an OPEN issue (never a PR, never a closed card)
carrying `pm:queue` whose title, after optional leading whitespace, begins
with `[Decision]` — anchored at the start, never a substring.

The row reports the DISAGREEMENT between two carriers and hands it to the
triage seat, which owns the second one. It never relabels, never treats the
prefix as the state (the prefix is the filer's, the state is triage's), and
needs no new label: the vocabulary already carries the state.

H26's deferred-target note said this shape was deliberately not reported
because a title heuristic would make the sweeper guess at intent. That stance
was about H26 inferring a block's fate from its target's title and it stands
— H26 is not widened. Both prose sites now point at H54 so the file does not
contradict itself; two self-test cases pin the pointers and pin that H26's
own criterion survives at both.

Self-tests: 43 cases — a positive control plus six negatives that each differ
from it in exactly one feature, driven over a length-pinned table so a
fixture that stops being exercised fails.

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

Copy link
Copy Markdown
Collaborator Author

CI disposition (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-09T17:0xZ): Type Check · source gates (job 102546118019, run 34375196183, attempt 1) went red on head db16743d with the single annotation 「The hosted runner lost communication with the server」 after 45 minutes and no step result recorded — runner loss before the gate ran, not a verdict on this diff. The same check is green on origin/main d61139f1 and on PR #17199's head 41788860. Reproduced locally on this head: node scripts/check-type-check-coverage.mjs --self-test (all case families hold) and node scripts/check-type-check-coverage.mjscheck-type-check-coverage: OK — 76/80 workspace packages type-checked …, exit 0. Re-running the failed jobs once, as the runbook allows for a job that died before its body ran; a second failure is real and comes back to this seat. Check Changeset re-ran skipped after skip-changeset was applied (scripts/pm is not a package).


Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

[finding] domain:cli's pm:queue reads as 8 dispatchable cards and is 1 — five of them need a RULING, and the vocabulary already has a state for that

2 participants