Skip to content

feat(pm): H58 — a pm:queue card whose own headings declare it a ruling - #17558

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-17417-queued-card-ruling-marker
Sep 10, 2026
Merged

feat(pm): H58 — a pm:queue card whose own headings declare it a ruling#17558
os-litant merged 1 commit into
mainfrom
claude/issue-17417-queued-card-ruling-marker

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes #17417

One new report-only row on the half-state patrol — H58 — and nothing else. scripts/pm/check-half-states.mjs is the whole diff: one file, +510 lines, 0 deletions.

⚠️ Every quoted heading spelling below sits inside a fenced block. Two of the measured spellings contain a word GitHub's reference parser treats as a closing keyword, and a fence is the measured way to quote one without binding it to whatever card number stands nearby.

What the row reads

pm:queue carries two incompatible meanings — 「triaged and registered」 and 「ready for a dev」 — and the card-selection total order reads the second. H58 names an open pm:queue card whose own section heading declares the deliverable is a ruling, a decision or a record, so the triage seat's per-fire read can dispose it into a state that already exists.

  • Report-only. Exit codes untouched, nothing blocked, no label written. It reports a disagreement between two carriers and hands it to the seat that owns the second one — it never says which carrier is wrong.
  • No new state, no new label. The card's own datum is that all five measured instances went into a state that already exists, so the remedy sentence names exactly those four — tracking, needs-user-decision, shut as completed, or a corrected pm:queue — and proposes no fifth.
  • Buys nothing. The population is the pm:queue label page the sweep already lists (SEEN_LABEL_PAGES); the body rides in on that payload; the thread is read only out of commentCache, never fetched. The body-only vs body+thread split is disclosed on the summary line, because a per-card UNJUDGED row on every queued card would flood the board rather than inform it.
  • Adjacent to H54, not a restatement of it. H54 reads a [Decision] title against the same label; H58 reads the body and the thread. A card wearing both fires both, and a self-test pins that.

The anchor set — measured as HEADINGS, frozen, capped

H58_RULING_MARKER_ANCHORS, three entries under a declared cap of five, each frozen beside the live card it was measured on.

Anchor 1 — measured on objectui#8449

matched head:  why it is a ruling
as measured:   ## Why it is a ruling, not a mechanical fix

Anchor 2 — measured on objectui#8365

matched head:  why this is a decision
as measured:   ## Why this is a decision, not a mechanical fix

Anchor 3 — measured on objectui#7089 and objectui#7233

matched head:  rulings of mine you may want to overturn
as measured:   ## 5. Two rulings of mine you may want to overturn      (the first card)
               ## 8. Two rulings of mine you may want to overturn      (the second)

Matching is line-anchored on an ATX heading outside fenced code (^ up to three spaces, one to six #, whitespace, text; markdown decoration removed). A marker quoted in prose, in a table, in a code span, in a blockquote or inside a fence does not fire — this card's own body quotes all three spellings and correctly produces no row.

Zone-2 readings, measured before acting

(a) The pre-edit state, on 9540590b. grep -c for the three marker phrases in the sweeper: 0. Newest registered row: H57: 'stall'. --self-test before the edit: 3367 cases pass; after: 3462 cases pass (+95).

(b) The live heading spellings — and two of the dispatch's premises moved. One REST GET per card, no retry, five cards:

objectui#8449   ## Why it is a ruling, not a mechanical fix          depth 2, body line 14   HEADING  -> pinned
objectui#8365   ## Why this is a decision, not a mechanical fix      depth 2, body line 28   HEADING  -> pinned
objectui#8429   "Both are dispositions, and picking one is a ruling
                 this card deliberately leaves open."                body line 23, PROSE     inside a paragraph
                                                                                             under "## What this card
                                                                                             is NOT asking for"
                                                                                             -> REFUSED
objectui#7089   ## 5. Two rulings of mine you may want to overturn   depth 2, body line 48   HEADING  -> pinned
objectui#7233   ## 8. Two rulings of mine you may want to overturn   depth 2, body line 102  HEADING  -> pinned

Two corrections, and the live spelling wins both times:

  1. 「Both are dispositions」 is not a heading on the card it was measured on. A heading-anchored reader can never match it, so admitting it to the set would be an anchor nobody writes — PROSE_BLOCKER_ANCHORS's own rule, for its own reason. It is recorded in the banner and deliberately absent from the set, and the self-test exercises it as a negative precisely because it must not fire.
  2. The handover pair carries no marker in any of the card's three spellings. What both bodies do carry is a section 「Two rulings of mine you may want to overturn」 — a card declaring itself a record of rulings exactly as squarely as the other two declare themselves a ruling. That is the spelling measured on the live cards, so that is the spelling pinned in its place.

Also measured, and worth the seat's attention: all five instances have already been disposed — objectui#8449 is no longer open; objectui#8365 now carries needs-user-decision; objectui#8429 now carries pm:blocked; objectui#7089 and objectui#7233 now carry tracking. None of them carries pm:queue today, so none would fire now — the row is forward-looking, and a quiet board on these five is the card's own datum holding rather than a broken row.

(c) Negative controls, all pinned as cases. The filing card's own body shape — all three spellings, in italic prose and a table cell, under headings of its own — produces no row, with a positive control asserting the fixture really carries the words. A marker inside a fence (backtick and tilde spellings) produces no row. An ordinary queued card produces no row. Fourteen negatives in a length-pinned table, driven whole.

(d) The band, argued on the refused bands' own criteria. Registered H58: 'state'. ⛔ Not gate — that band is for an absence reading as a green light on a possibly-stripped gate; here both carriers are present and legible and they disagree (H54's refusal, for H54's reason). ⛔ Not stall — both halves of that criterion fail: whether such a card is stopped is unmeasured by this row (it sits in the dispatch pool and any seat may pick it up), and something else does move it — the triage seat's per-fire read, which is exactly what disposed all five measured instances. ⛔ Not inventory — the row alarms about one card; the population reading is a summary clause and takes no band at all (H39's shape). What is left is state's criterion exactly, and it is H54's band.

The live sweep

One report-mode sweep of objectstack-ai/objectstack on 24cdcf9c, through the proxy re-exec, exit 0. The new clause renders unconditionally:

Queued ruling markers (H58): 266 open pm:queue card(s) had their BODY judged against 3 frozen heading anchor(s) — 2 with a comment thread ALREADY in hand, 264 on the body ALONE. It fetches NOTHING of its own, so a card whose thread no other row bought has an UNREAD second channel rather than a clean one, and the rows are a LOWER BOUND. It matches ATX HEADINGS outside fenced code only: a marker quoted in prose, in a table, in a code span or in a blockquote is invisible here by design.

The family ledger shows the row's one finding, and that the body trim ate it:

| `H58` | state | 1 | 0 |

That one row is objectstack#16245, a genuine live specimen on this board — an open pm:queue card whose body carries:

## Why it is a ruling and not a cleanup

A spelling neither the filing card nor the dispatch measured, matched because the anchor is the distinctive head of the measured heading rather than the whole line. Identified by re-running the exported rulingMarkerHeadings over the same pm:queue listing (268 cards, 1 hit) rather than by a second sweep. ⛔ No comment was posted on that card: it is triage input, and the disposition is the triage seat's.

Ablation — both mechanisms proved failable, on disk, and restored

Each leg: mutate, prove the mutation reached disk by occurrence count and by git hash-object against the HEAD blob, run, restore with git checkout HEAD --, prove the restore by blob equality and an empty git diff HEAD. Absolute-path trap … EXIT INT TERM throughout. There is no build step for this script — it is run directly from source, so there is no dist/ leg to preflight, and the on-disk proof is the whole of it.

Predicted direction for both: red. Observed: red, both, with a clean separation.

leg 1  fence awareness
       stripMarkdownCode(text, { inline: false })  ->  the raw text
       blob 02666a24 -> 271ac0ac      self-test exit 1
       red cases: exactly 2 -- both fenced-block negatives, and nothing else

leg 2  heading anchor
       H58_HEADING_LINE widened to match any non-empty line
       blob 02666a24 -> 083cda96      self-test exit 1
       red cases: 11 -- prose, code span, blockquote, table, the comment-quote
       negative, THE FILING CARD'S OWN BODY CONTROL, and four scanner cases

A first attempt at leg 1 aborted before running, because its guard demanded the injected text occur exactly once and the file already contained an identical line in proseBlockerRefs. The guard was wrong, not the ablation; it was corrected to pre-existing + 1, and the trap had already restored the tree. Recorded because a guard that fails toward "refuse" is the one direction that never produces a false reading.

Four-axis note on the one judgement — the anchor set's breadth and the band

The single judgement here is how wide to make the frozen set and where the row sits in the trim, and the four axes point the same way. 实际业务需求: every anchor is a spelling measured on a live card, and the one candidate that was measured in prose is refused rather than admitted for symmetry — the live sweep then found a sixth real carrier on this board with a spelling nobody predicted, which is the measurement, not a hope. 项目长远合理性: the row is triage input under an existing state vocabulary, so the contract-first direction is served by a reader that makes an existing state reachable rather than by a protocol change that adds one. 防 AI 写代码犯错: the tight direction is the strict one — heading-anchored matching refuses prose, tables, code spans, blockquotes and fences structurally, and a lenient substring scan would have made the patrol loudest on precisely the careful cards that explain the shape (this card among them); a state band keeps the row below the two gate families, so nothing that can tell a stripped gate from an ungated card is displaced by it. 创业阶段不扩散需求: three anchors, cap five, ⛔ no new label, ⛔ no new state, ⛔ no new listing and ⛔ no new request — the row buys nothing the sweep was not already paying for, and a sixth anchor is deliberately an argument rather than a one-line edit.

Verification

Gate families derived from the final diff by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack — no paths, so the script derives its own change set from the merge base, and it printed change set derived from git — 1 path(s) vs merge base 9540590. All 39 were run; every exit code was captured before any pipe; reconciliation with --ran:

Run reconciliation — 39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN.

39 of 39 exit 0. Their own verdict lines, for the families this diff actually moves:

exit 0   pnpm check:pm-half-states
         > check-half-states self-test: 3462 cases pass.

exit 0   pnpm check:pm-dispatch-gates
         > dispatch-gates self-test: 1678 cases pass.

exit 0   pnpm check:watch-hint-literal
         > check-watch-hint-literal: 69 declaration(s) across 4 rostered name(s) --
         > ROOT_DIR_WATCH_HINTS 45, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3,
         > DECLARED_WATCH_HINTS 8 -- every one an array of quoted literals inside its
         > own statement, all 156 of those literals admitted by the hint extractor,
         > every rostered name non-empty, and no unrostered spelling of the idiom in
         > the tree.

exit 0   pnpm check:nul-bytes
         > check-nul-bytes: OK (scanned 8331 text file(s) -- 8331 tracked, 0
         > untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).

exit 0   node scripts/check-self-test-wired.mjs
         > check-self-test-wired: every one of the 203 script(s) CI runs that ship a
         > `--self-test` has that self-test run by CI.

exit 0   node scripts/check-scripts-symbol-anchors.mjs
         > check-scripts-symbol-anchors: 3174 anchors across 246 scripts resolve --
         > 38 symbol (38 declaration, 0 literal), 3136 file-level, 0 cross-repo,
         > 1 exempt, 2 continuation.

exit 0   node scripts/check-comment-mask-corpus.mjs
         > comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 6590 files,
         > 0 disagree, 0 unparseable, 60.6s (comparator self-test: 26 cases pass).

Beyond the derivation:

  • pnpm linteslint . --no-inline-config, the repo-wide union, ⛔ not a narrowing — on 24cdcf9c: exit 0, no output.
  • npx eslint --no-inline-config scripts/pm/check-half-states.mjs: exit 0.
  • grep -naP for raw ASCII control bytes on the edited file: no match, exit 1.
  • node scripts/pm/check-governed-merges.mjs --branch claude/issue-17417-queued-card-ruling-marker: exit 0✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.
  • node scripts/pm/check-half-states.mjs --probe: exit 0, transport prerequisite met.

Changeset: none. Nothing published moves — scripts/pm/** is in no package's files[], and the diff touches one file under it. The skip-changeset label is applied on this PR and the label set is read back after the write.

Acceptance notes

  • noted, not filed: objectstack#16245 is a live carrier of exactly this shape on this board — open, pm:queue, and its body carries the heading quoted above. It is the row's output, not a defect of its own: the triage seat's per-fire read is the named carrier, so the row itself is the hand-off and ⛔ no card was opened for it. Successor: the triage seat, on its next fire.
  • noted, not filed: the sweeper's markdown body trim ate the one H58 row it computed (computed 1, rendered 0 on the live run) while the family ledger still named it. That is the ledger mechanism working as designed, not a defect — but it does mean a first reader of the anchor issue sees the family and not the card. Successor: whoever next revisits the trim's reserved-section budget.
  • noted, not filed: PR feat(gates): give the closing-keyword parity gate a --body pre-flight mode #17552's --body lint mode has not landed on mainscripts/check-closing-keyword-parity.mjs carries no such flag at 9540590b — so this body was written to the discipline by hand instead: outside fenced blocks it contains zero closing verbs anywhere after the first line, which is stricter than "none beside a card number" and is checkable by grep. Successor: that PR's author.

Clause-②: no — a report-only patrol row; no accept set and no public surface moves.


Generated by Claude Code

`pm:queue` carries two incompatible meanings — "triaged and registered"
and "ready for a dev" — and the card-selection total order reads the
second. Three seats measured cards graded into the pool that were
rulings or records; the price of one instance is a dev round.

Adds one report-only row to the half-state patrol. It matches a frozen,
heading-anchored marker set, each anchor beside the live card it was
measured on, over the `pm:queue` label page the sweep already lists,
reading the comment thread only where another row already bought it.
Exit codes unchanged; nothing blocked; no label written.

The remedy names the four states that already exist — `tracking`,
`needs-user-decision`, closed, or a corrected `pm:queue` — because every
measured instance resolved into one of them. No fifth state is proposed.

Two Zone-2 readings moved the anchor set: "Both are dispositions" is
PROSE on the card it was measured on, not a heading, so it is recorded
and refused; and both handover cards carry the heading "Two rulings of
mine you may want to overturn", which is the spelling pinned in its
place.

Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK
Co-authored-by: Claude <noreply@anthropic.com>
@os-litant os-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review September 10, 2026 23:18
@os-litant
os-litant enabled auto-merge September 10, 2026 23:19
@os-litant
os-litant added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit d8a833d Sep 10, 2026
38 checks passed
@os-litant
os-litant deleted the claude/issue-17417-queued-card-ruling-marker branch September 10, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants