fix(pm): H14's strip direction declines on a seam card instead of accusing it - #13990
Conversation
…using it `pm:blocking` is a derived cache of the `Blocked-by:` reverse index, and the patrol builds that index from THIS repo's open listing only. Seam cards exist to be read from another repo — the protocol keeps them here carrying `repo:cloud` / `repo:objectui` / `repo:hotcrm` precisely so a sibling's work can depend on them — so for exactly those cards "no open card here points at me" is not evidence that nobody is waiting. It is the reading the index is structurally unable to make. The stale direction now DECLINES on a card carrying any `repo:*` label: the row is still emitted (a suppressed row makes the seam card invisible to the patrol, which is the second defect, not the fix), carries `UNJUDGED_MARKER` so the markdown trim can never be what removes it, and says it is declining and why. The ADD direction is untouched — a cross-repo edge the index cannot see can only make that one under-label, which is the status quo. The surviving stale rows now name their scope in the head clause and disclose the predicate's measured under-coverage: on the 2026-08-31 board, 58 of the 67 open cards naming a sibling-repo issue carry no `repo:*` label, so the absence of the label is not evidence of an in-repo-only audience. Report-only throughout: no label write, no exit-code change, no row made build-failing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
|
ACCEPT — ⛔⛔ My Zone 1 ruling 4 quoted a row text that had been fixed eleven days earlierI wrote, as a ruling: "The row must name its own scope. Today it reads 'no open card's That "today" was wrong. I took the quote from the card and did not check it. On
Landed in ⇒ ⭐ Half of the card's complaint was already paid. What was still true, and is what this PR removes, is the other half: the row accused seam cards rather than declining. ⛔ A ruling that quotes a stale line is the same defect this lane keeps catching in other people's cards — recorded here, in the place I made it. ⭐⭐ The control that proves this is a DECLINE and not a suppressionBoth builds driven over one captured snapshot of the live board (402 open cards + 43 gated comment threads — identical input, so drift cannot explain a difference):
⭐ The finding count is unchanged — 7 = 7 — which a suppression would not have preserved. And direction A's row text is byte-identical by md5, ⛔ not by reasoning: Self-test 1922 → 1951 (+29), which I ran on both trees rather than accepting: Zone 2 A — SOUND but ⛔ NOT COMPLETE, and the gap is the honest half of this deliveryMy hypothesised predicate — "carries any
⛔ This is not a defect in the delivery. Zone 2 A asked precisely for this — "if the predicate under-covers, report the count; ⛔ do not silently widen" — and the dev did both halves. The surviving rows now carry the shortfall in their own text ("that absence is not evidence of an in-repo-only audience: 58 of the 67 …"), so the two uncovered cards are no longer accused without a warning attached. Combined with the patrol being report-only, nothing acts on them automatically. Rulings on the three open questionsQ1 — widen the predicate? ⇒ A, as shipped, and ⛔ B is refused. A textual test would decline on 58 more cards, silencing the row across most of the board and trading a declared signal for a proxy — the dev's four-axis reading against it is correct and I adopt it. C — the cross-repo Q2 — narrow the predicate away from Q3 — a summary counter for declines? ⇒ A. The declines are emitted rows carrying
|
Fixes #13975
H14's strip direction (
pm:blockingcarried while nothing targets it) now declines on a card carrying anyrepo:*label, instead of prescribing a strip it has no evidence for. The add direction is untouched. Report-only throughout: no label write, no exit-code change, no row made build-failing.Why
pm:blockingis a derived cache of theBlocked-by:reverse index, andbuildBlockingIndexscans this repo's open listing only. Seam cards exist to be read from another repo — the protocol keeps them here carryingrepo:cloud/repo:objectui/repo:hotcrmprecisely so a sibling's work can depend on them. For exactly those cards, "no open card here points at me" is not weak evidence of absence; it is the reading the index is structurally unable to make. There is no cross-repoBlocked-by:reader in the fleet, so no re-run resolves it.The stakes are asymmetric, which is why only one direction moves: a wrong strip demotes a card that outranks everything but
priority:p0in lane selection and starves the sibling waiting on it; a missed cross-repo edge on the add side can only under-label, which is the status quo.What changed (one file,
scripts/pm/check-half-states.mjs)seamRepoLabels(issue)— a named, exported, pinned predicate (therepo:*family, read withcensusLanesOf's spelling so the two rows cannot disagree about what a repo lane is).UNJUDGED_MARKER. That is the mechanical half of "stays visible": unjudged rows sort ahead of judged ones so the markdown body's size trim can never be what removes them. An emitted decline the trim can drop is the suppression this row rejects, arriving one budget overrun later.repo:*label has no off-repo audience.Rulings and hypotheses, measured
Zone 1 · candidate 1, implemented as ruled. Candidate 2 (a cross-repo
Blocked-by:reader) was not built — it is not needed to make the decline honest, because declining is exactly the statement that the question is unanswerable here. Candidate 3 is not relied on: the row now states its own scope rather than asking the reader to know it.Hypothesis A — "carries any
repo:*label" — SOUND, NOT COMPLETE (falsified as a coverage test). Measured on the live board, 2026-08-31 (402 open cards):repo:*labels that existrepo:cloud(12 open cards),repo:objectstack(4),repo:objectui(3),repo:hotcrm(2)repo:*labelobjectui#N/cloud#N/hotcrm#N) in title, body or gathered commentsrepo:*labelrepo:hotcrm), #12931 (repo:cloud), #13439, #13515, #13568So a card can certainly have an off-repo audience without a
repo:*label — the card's own filing recorded cloud#1451 waiting on #13439, and an objectui presentation card commissioned by #13568's ruling; neither carries the label. The predicate was not widened (that needs its own ruling); instead the surviving rows now say the missing label is not evidence, and quote the 58-of-67 reading.Also found: the predicate over-covers on
repo:objectstack. That label is not in the label ledger's seam family (ensure-pm-labels.shcreates only the three sibling labels, each described as "Seam card: cross-repo ordering with X is the substance") but 4 open cards carry it, and a label naming the swept repo cannot make this sweep's index blind. Kept literal per the ruling; the row says the decline is CONSERVATIVE in that case rather than asserting an off-repo audience it cannot see, and the reading follows the swept repo rather than a hardcoded name. 0 of the 5 live strip rows carry it, so the over-coverage costs nothing today. A predicate narrowing is the PM's ruling to make.Hypothesis B — H32's decline shape — FALSIFIED in detail. H32 does not emit a decline row; it returns
nulland the disclosure lives in the summary's coverage pair:That shape is right for H32 and wrong here: a seat on an uncountable lane is out of the population, while a seam card is squarely in this row's population (it carries the very label the row judges) and it is the evidence that is unreadable. The taxonomy's convention for that second case is an emitted row, and H27 already holds it —
...so this dispatch is UNJUDGED, not confirmed healthy. Unread is not "no activity" and it is not "activity" either (#4690)— which is also whatUNJUDGED_MARKERand its trim reservation exist for. Zone 1's emitted decline is therefore backed by precedent, and the docblock records why H32's shape was not copied.Hypothesis C — separability — FALSIFIED as stated, then proven by run. The two directions are not separate functions: they are two branches of one predicate, driven by one derivation loop at the call site. Direction A was proven byte-identical anyway, by running both builds over one captured snapshot of the live board (402 open cards + the 43 gated comment threads, identical input for both runs) rather than by two live sweeps that would differ by board drift:
The finding count is unchanged, which is the property a suppression would not have had.
Hypothesis D — nothing consumes H14 rows mechanically — CONFIRMED.
grepover.github/workflows/**andscripts/**: no hit forH14and no hit for the row phrase outside the script itself. The only consumer ishalf-state-patrol.yml, which writes the whole rendered markdown into the anchor issue body and parses no row. So the row-text change is not a contract change.Hypothesis E — a declined row must stay visible — CONFIRMED, and the taxonomy supplies the mechanism. Emitting is not enough on its own: the 2026-08-25 measurement behind
UNJUDGED_MARKERis a run where the trim ate 199 rows while the header promised they were kept. The decline therefore takes the UNJUDGED rank.Premise re-check (one correction to the card). The card says the row today reads exhaustively. Half of that has been true since 2026-08-20 (commit
108fae984, from #10239): the strip row already carried "scoped to THIS REPO ONLY" and a remedy conditional on a cross-repo check. What was not true is that it declined — it still emitted an accusation on seam cards, which is the defect the card measured and this PR fixes. The head clause has now been reworded too, so the scope is stated where the row starts rather than only in its middle.Verification
Re-check from the card, run against this build (live sweep, exit 0):
Self-test:
pnpm check:pm-half-states— 1922 cases before, 1951 after (+29), green. Cases cover the predicate itself, the decline's wording and UNJUDGED rank, the paired non-seam card that still fires, an earned label that stays clean, the incomplete-index suspension that still outranks the decline, the add direction proven byte-identical with and without the label, and the conservative same-repo edge in both directions.Reverse verification (ablation). Committed first, then mutated:
if (seam.length > 0)toif (false && seam.length > 0). Mutation confirmed on disk before the run — injected textgrep -c= 1, original anchorgrep -c= 0,git hash-objectmoved from1ed4cf1to159a673. Predicted direction: red. Result: 11 of 1951 cases failed, exit 1 — every decline assertion, and the two negatives inverted (reads as UNJUDGED, never as a stale cacheandnever prescribes the stripboth flipped). Restore leg:git checkout HEAD -- ABSOLUTE_PATHunder a trap, then proven —git diff HEADempty,git hash-objectback to1ed4cf1= the HEAD blob, injected markergrep -c= 0, self-test green again at 1951.Gates. Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(17 path-matched + 2 convention-triggered for a gate-script edit);comm -23derived-minus-run is empty. All green except two that measured nothing and say so:node scripts/check-partof-closing-keyword.mjsexits 2 NOT WIRED without a PR body (run wired against this body before opening the PR — green), andnode scripts/check-test-completeness.mjsexits 3 PREREQUISITE NOT MET with no saved turbo log, which its own text says to record as NOT MEASURED.pnpm check:entry-guardgreen. Repo-widepnpm lint(eslint . --no-inline-config) run in full, green, no narrowing claimed. The three other scripts that import this one —check:pm-clause2-carriers(64 cases),check:pm-governed-merges(255 assertions),scripts/pm/ci-failure.mjs --self-test— all green.Gate union re-run on the final commit;
git rev-parse --short HEADat that run:a1cb828b8.Changeset: none. The diff is
scripts/**only, which matches no workspace publish glob and the root package isprivate;skip-changesetapplied additively.Governed surfaces: the diff touches
docs/adr/**·.claude/**·skills/**·AGENTS.md·CLAUDE.md— none of them. One file,scripts/pm/check-half-states.mjs.Not done here, deliberately: no label was applied or stripped on any card, including the four the card names; the patrol stays report-only; and the cross-repo
Blocked-by:reader stays unbuilt, out of scope: #12931 remains open and #13439 remains open, unchanged by this PR.Generated by Claude Code
Generated by Claude Code