Skip to content

Commit 0f12e32

Browse files
claude[bot]claude
andauthored
fix(pm): H14's strip direction declines on a seam card instead of accusing it (#13990)
`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. Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC Co-authored-by: Claude <noreply@anthropic.com>
1 parent a6a2af5 commit 0f12e32

1 file changed

Lines changed: 211 additions & 5 deletions

File tree

scripts/pm/check-half-states.mjs

Lines changed: 211 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2300,6 +2300,31 @@ export function buildBlockingIndex(issues, options = {}) {
23002300
*/
23012301
export const BLOCKING_DEPENDENT_LIST_CAP = 5;
23022302

2303+
/**
2304+
* The `repo:*` labels a card carries — the SEAM predicate H14's stale
2305+
* direction declines on (#13975).
2306+
*
2307+
* Exported and pinned for the reason every scoping policy in this file is: a
2308+
* policy that decides what gets JUDGED AT ALL is where a silent hole would
2309+
* live. The spelling is `censusLanesOf`'s, deliberately — one reading of the
2310+
* `repo:*` family, so the two rows cannot come to disagree about what a repo
2311+
* lane is.
2312+
*
2313+
* ⚠️ SOUND, not COMPLETE, and the surviving rows say so. The ruled predicate
2314+
* (#13975) is "carries any `repo:*` label", which catches the cards whose
2315+
* off-repo audience is DECLARED. It is not a test for "has an off-repo
2316+
* audience": measured on the 2026-08-31 board, 67 open cards name a
2317+
* sibling-repo issue (`objectui#N` / `cloud#N` / `hotcrm#N`) and only 9 of
2318+
* them carry a `repo:*` label — and the two cards this row's own filing
2319+
* recorded off-repo waiters for (#13439, #13568) carry none. Widening it (to
2320+
* "the body mentions another repo", say) is a DIFFERENT predicate and needs
2321+
* its own ruling; narrowing it is one too. Both are why this is a named
2322+
* function rather than an inline `.some()`.
2323+
*/
2324+
export function seamRepoLabels(issue) {
2325+
return labelNames(issue ?? {}).filter((l) => /^repo:/u.test(l));
2326+
}
2327+
23032328
/**
23042329
* H14 — null when the cache agrees with the index, else the finding sentence.
23052330
*
@@ -2340,22 +2365,118 @@ export const BLOCKING_DEPENDENT_LIST_CAP = 5;
23402365
* and #7917 / objectui#4356 is a live one this row would otherwise have
23412366
* instructed a reader to sever.
23422367
*
2368+
* ## …and on a SEAM card it declines outright (#13975)
2369+
*
2370+
* Naming the scope was not enough. The row's instruction is still imperative,
2371+
* and the class it is most often wrong about is the one the protocol creates
2372+
* ON PURPOSE: a seam card is kept in THIS repo, carrying `repo:cloud` /
2373+
* `repo:objectui` / `repo:hotcrm`, precisely so a sibling repo's work can
2374+
* depend on it. For exactly those cards "no open card here points at me" is
2375+
* not weak evidence of absence — it is the reading the index is structurally
2376+
* unable to make, and the filing round measured the cost: 4 of 5 stale rows
2377+
* stood on cards other repos were, on the record, waiting for.
2378+
*
2379+
* So `seamRepoLabels(issue)` non-empty DECLINES: the row is still emitted, and
2380+
* it says it is declining and why. Emitted rather than suppressed, on two
2381+
* measurements rather than taste:
2382+
*
2383+
* - Dropping it silently would make the seam cards INVISIBLE to the patrol —
2384+
* a card that no row mentions reads as a coherent one, which is the second
2385+
* defect, not the fix. H27 already holds this shape for the same reason
2386+
* (an UNJUDGED dispatch is reported out loud, "not confirmed healthy").
2387+
* - The finding COUNT is unchanged by the swap, so a decline cannot quiet
2388+
* the summary sentence the way a suppression would.
2389+
*
2390+
* The row carries `UNJUDGED_MARKER`, which is the mechanical half of that
2391+
* visibility rather than a label on it: unjudged rows sort ahead of judged ones
2392+
* so the markdown body's size trim can never be what removes them (#11218,
2393+
* where the trim ate 199 rows while the header promised they were kept). An
2394+
* emitted decline the trim can drop is the suppression this row rejects,
2395+
* arriving one budget overrun later.
2396+
*
2397+
* ⚠️ H32's decline is the OTHER shape and this row deliberately does not copy
2398+
* it: there, a seat on a lane this board cannot count returns `null` and the
2399+
* summary's coverage pair carries the disclosure — correct, because the seat
2400+
* is out of the POPULATION. A seam card is in this row's population (it holds
2401+
* the label this row exists to judge); it is the EVIDENCE that is unreadable.
2402+
*
2403+
* ⛔ The decline is scoped to the stale direction alone. MISSING (the add
2404+
* direction) stays untouched on a seam card, and must: a cross-repo edge the
2405+
* index cannot see can only make that direction under-label, which is the
2406+
* status quo and harms nobody.
2407+
*
23432408
* @param {object} issue — an OPEN issue.
23442409
* @param {Map<number, number[]>} index — from `buildBlockingIndex`.
2345-
* @param {{ indexComplete?: boolean }} [options] — `false` when any gated
2346-
* comment fetch failed, i.e. the index is known to be missing edges.
2410+
* @param {{ indexComplete?: boolean, ownerRepo?: string }} [options] —
2411+
* `indexComplete` is `false` when any gated comment fetch failed, i.e. the
2412+
* index is known to be missing edges. `ownerRepo` is the swept `owner/repo`,
2413+
* defaulted like `buildBlockingIndex`'s and read for one purpose only: to
2414+
* tell a seam label naming ANOTHER repo from one naming this one.
23472415
*/
23482416
export function h14BlockingCacheIncoherent(issue, index, options = {}) {
23492417
const carries = labelNames(issue).includes('pm:blocking');
23502418
const dependents = index?.get?.(issue.number) ?? [];
23512419
const indexComplete = options.indexComplete ?? true;
23522420
if (carries && dependents.length === 0) {
23532421
if (!indexComplete) return null;
2422+
const seam = seamRepoLabels(issue);
2423+
if (seam.length > 0) {
2424+
const bareRepo = String(options.ownerRepo ?? OWNER_REPO)
2425+
.split('/')
2426+
.pop();
2427+
const named = seam.map((l) => `\`${l}\``).join(', ');
2428+
// The over-covered edge, named rather than narrowed away. `repo:objectstack`
2429+
// is not in the label ledger's seam family (`ensure-pm-labels.sh` creates
2430+
// `repo:objectui` / `repo:cloud` / `repo:hotcrm`, each described as 「Seam
2431+
// card: cross-repo ordering with X is the substance」) but it exists on the
2432+
// board and 4 open cards carried it on 2026-08-31. A label naming the SWEPT
2433+
// repo cannot make this sweep's index blind, so declining on it is
2434+
// conservative — and the row says that rather than asserting an off-repo
2435+
// audience it has no evidence for. Kept literal because the ruled predicate
2436+
// is "any `repo:*` label"; narrowing it is a predicate change and belongs to
2437+
// whoever rules on it, not to this function.
2438+
const conservative =
2439+
seam.every((l) => l.slice('repo:'.length) === bareRepo)
2440+
? ` ⚠️ ${named} names the SWEPT repo, so this decline is CONSERVATIVE rather than ` +
2441+
'structural — the ruled predicate is "any `repo:*` label" (#13975) and this is its ' +
2442+
'over-covered edge, left literal rather than narrowed here.'
2443+
: '';
2444+
return (
2445+
// The UNJUDGED rank, not decoration: it is what keeps this row ahead of
2446+
// the markdown body's size trim (#11218). A decline the trim can eat is
2447+
// a seam card the patrol silently stops mentioning — the same defect as
2448+
// suppressing the row outright, arriving later and less visibly.
2449+
`${UNJUDGED_MARKER} ` +
2450+
'`pm:blocking` carried while no open card in THIS REPO targets it with a `Blocked-by:` line ' +
2451+
`— body OR comment — and this row DECLINES to judge the label: the card carries ${named}, a ` +
2452+
'seam label whose whole meaning is that the ordering this card belongs to lives in another ' +
2453+
'repo (label ledger: 「Seam card: cross-repo ordering with X is the substance」), and the ' +
2454+
'protocol keeps such a card HERE precisely so a sibling repo can depend on it.' +
2455+
conservative +
2456+
' The strip this row would otherwise prescribe rests on ABSENCE of evidence, and for this ' +
2457+
'card the evidence is not absent, it is UNREADABLE (#4690): `buildBlockingIndex` scans this ' +
2458+
'repo\'s open listing only, `Blocked-by:` edges are protocol-legal across repos, and a ' +
2459+
'sibling repo\'s card waiting on this one is invisible to the index BY CONSTRUCTION, not by ' +
2460+
'any read failure — ⛔ no re-run resolves it, and there is no cross-repo `Blocked-by:` ' +
2461+
'reader in the fleet to resolve it with. ⇒ UNJUDGED: neither a coherent cache nor a stale ' +
2462+
'one. The stakes are why this declines rather than guessing — `pm:blocking` ranks second ' +
2463+
'only to `priority:p0` in lane selection, so a wrong strip starves the sibling that is ' +
2464+
'waiting, while a wrong KEEP only over-ranks one card. Report-only: the remedy is a ' +
2465+
'cross-repo `Blocked-by:` read by hand before the triage sweep\'s derivation pass touches ' +
2466+
'this card, never a label written from this script — and ⛔ never a hand-applied or ' +
2467+
'hand-stripped `pm:blocking` either, in any direction: the label is the sweep\'s derived ' +
2468+
'cache (「⛔ 不手工挂」).'
2469+
);
2470+
}
23542471
return (
2355-
'`pm:blocking` carried while no open card\'s `Blocked-by:` line — body OR comment — targets ' +
2356-
'it, judged against the two-channel index — a stale derived cache, scoped to THIS REPO ONLY: ' +
2472+
'`pm:blocking` carried while no open card in THIS REPO — the only board this sweep indexes ' +
2473+
'— targets it with a `Blocked-by:` line, body OR comment, judged against the two-channel ' +
2474+
'index — a stale derived cache, scoped to THIS REPO ONLY: ' +
23572475
'no dependent found in this repo; cross-repo dependents are not swept, so this is not a claim ' +
2358-
'of exhaustiveness over the population — `Blocked-by:` edges are legally cross-repo. The label ' +
2476+
'of exhaustiveness over the population — `Blocked-by:` edges are legally cross-repo. ' +
2477+
'⚠️ This card carries NO `repo:*` label, which is why the seam decline (#13975) did not ' +
2478+
'apply — and that absence is not evidence of an in-repo-only audience: measured 2026-08-31, ' +
2479+
'58 of the 67 open cards naming a sibling-repo issue carry no `repo:*` label. The label ' +
23592480
'is not a state a seat sets: the triage sweep derives it from the `Blocked-by:` reverse index, ' +
23602481
'and the lane selection order ranks it second only to `priority:p0`. So a stale one is worse ' +
23612482
'than an absent one — it boosts a card nothing depends on, with authority. Report-only: verify ' +
@@ -12590,6 +12711,91 @@ function selfTest() {
1259012711
t('H14-B: missing SURVIVES an incomplete index', h14row(carded(9465, ['domain:devx']), unionIdx, { indexComplete: false }).includes('#9709'), true);
1259112712
t('H14-B: …and an earned label stays clean either way', h14BlockingCacheIncoherent(epic9465, unionIdx, { indexComplete: false }), null);
1259212713

12714+
// -- H14: the SEAM DECLINE on the stale direction (#13975) -----------------
12715+
//
12716+
// ⚠️ NAMING, because the two vocabularies are inverted and a case written
12717+
// under the wrong one pins the wrong direction: the cases above call STALE
12718+
// "H14-A" and MISSING "H14-B", while the filing card #13975 calls the strip
12719+
// "direction B". The ruling is about the STRIP under either name, so these
12720+
// cases say `seam` and name the direction in words.
12721+
//
12722+
// The fixtures are the real cards the defect was measured on (2026-08-31, 5
12723+
// stale rows): #12931 carries `repo:cloud` and declines; #13439 sits in the
12724+
// SAME position carrying no `repo:*` label and still fires — which is the
12725+
// predicate's measured under-coverage, since that card's own filing records
12726+
// cloud#1451 waiting on it.
12727+
const seamIdx = idx([]);
12728+
const seamCard = carded(12931, ['pm:queue', 'repo:cloud', 'pm:blocking']);
12729+
const nonSeamCard = carded(13439, ['bug', 'priority:p1', 'pm:queue', 'domain:spec', 'pm:blocking']);
12730+
const swept = { ownerRepo: 'objectstack-ai/objectstack' };
12731+
12732+
// The predicate, alone: one reading of the `repo:*` family, `censusLanesOf`'s.
12733+
t('H14 seam: the predicate reads the repo:* family', seamRepoLabels(seamCard).join(','), 'repo:cloud');
12734+
t('H14 seam: …and is empty on a card carrying none', seamRepoLabels(nonSeamCard).length, 0);
12735+
t('H14 seam: …and a domain lane is not a repo lane', seamRepoLabels(carded(1, ['domain:spec', 'pm:blocking'])).length, 0);
12736+
t('H14 seam: …and more than one repo label is all of them', seamRepoLabels(carded(1, ['repo:cloud', 'domain:spec', 'repo:hotcrm'])).join(','), 'repo:cloud,repo:hotcrm');
12737+
t('H14 seam: …and a missing issue does not crash', seamRepoLabels(undefined).length, 0);
12738+
12739+
// The decline: EMITTED, never suppressed (the row is the seam card's only
12740+
// trace in the report — H32's silent `foreign` return is the other shape,
12741+
// and it belongs to a row whose POPULATION is out of scope, not one whose
12742+
// evidence is unreadable).
12743+
t('H14 seam: a seam card in the stale position still EMITS a row', typeof h14BlockingCacheIncoherent(seamCard, seamIdx, swept), 'string');
12744+
t('H14 seam: …and the row declines rather than accusing', h14row(seamCard, seamIdx, swept).includes('DECLINES to judge'), true);
12745+
t('H14 seam: …and names the label it declined on', h14row(seamCard, seamIdx, swept).includes('`repo:cloud`'), true);
12746+
t('H14 seam: …and says the evidence is UNREADABLE, not absent', h14row(seamCard, seamIdx, swept).includes('UNREADABLE'), true);
12747+
t('H14 seam: …and that no re-run resolves it', h14row(seamCard, seamIdx, swept).includes('no re-run resolves it'), true);
12748+
t('H14 seam: …and reads as UNJUDGED, never as a stale cache', h14row(seamCard, seamIdx, swept).includes('stale derived cache'), false);
12749+
t('H14 seam: …so it never prescribes the strip', h14row(seamCard, seamIdx, swept).includes('drops the label'), false);
12750+
t('H14 seam: …and forbids the hand-strip as well as the hand-apply', h14row(seamCard, seamIdx, swept).includes('hand-stripped'), true);
12751+
// The trim-order rank, which is what makes "emitted" mean "still there in the
12752+
// rendered body" (#11218). Without it a decline is a suppression on a delay.
12753+
t('H14 seam: …and the row carries the UNJUDGED rank', isUnjudgedFinding(h14row(seamCard, seamIdx, swept)), true);
12754+
t('H14 seam: …while the ordinary stale row does NOT (it made a determination)', isUnjudgedFinding(h14row(nonSeamCard, seamIdx, swept)), false);
12755+
// Report-only and never loud, the H14–H16 property the decline must not break.
12756+
t('H14 seam: …and the decline is not a loud finding', isLoudFinding(h14row(seamCard, seamIdx, swept)), false);
12757+
// The card's own re-check (`grep -E "H14 .*carried while no open"`) has to
12758+
// keep listing BOTH shapes, or the seam cards vanish from the one-liner the
12759+
// filing seat used to find them.
12760+
t('H14 seam: …and the row head still answers the card\'s re-check grep', /carried while no open/u.test(h14row(seamCard, seamIdx, swept)), true);
12761+
12762+
// The paired negative: the same position, no `repo:*` label -> still fires.
12763+
t('H14 seam: a NON-seam card in the same position still fires', h14row(nonSeamCard, seamIdx, swept).includes('stale derived cache'), true);
12764+
t('H14 seam: …and its row names its own scope', h14row(nonSeamCard, seamIdx, swept).includes('no dependent found in this repo'), true);
12765+
t('H14 seam: …and says the missing label is NOT evidence of an in-repo audience', h14row(nonSeamCard, seamIdx, swept).includes('58 of the 67'), true);
12766+
12767+
// An earned label is clean whether or not the card is a seam: the decline
12768+
// fires on the stale position only, never on the population.
12769+
t('H14 seam: a seam card with a real in-repo dependent -> clean', h14BlockingCacheIncoherent(seamCard, idx([carded(10, [], 'Blocked-by: #12931')]), swept), null);
12770+
// Suspension outranks the decline: an index known incomplete silences the
12771+
// whole direction, unchanged by this row.
12772+
t('H14 seam: an incomplete index still silences the direction outright', h14BlockingCacheIncoherent(seamCard, seamIdx, { ...swept, indexComplete: false }), null);
12773+
12774+
// ⛔ The ADD direction is untouched on a seam card, and byte-identically so:
12775+
// a cross-repo edge the index cannot see can only make THAT direction
12776+
// under-label, which is the status quo and starves nobody.
12777+
const addIdx = idx([carded(9249, ['pm:queue'], 'Blocked-by: #9919')]);
12778+
t('H14 seam: the ADD direction still fires on a seam card', h14row(carded(9919, ['pm:queue', 'repo:cloud']), addIdx, swept).includes('#9249'), true);
12779+
t(
12780+
'H14 seam: …and its sentence is byte-identical to the same card without the label',
12781+
h14row(carded(9919, ['pm:queue', 'repo:cloud']), addIdx, swept),
12782+
h14row(carded(9919, ['pm:queue']), addIdx, swept),
12783+
);
12784+
12785+
// The over-covered edge, pinned rather than narrowed. `repo:objectstack` is
12786+
// not in the label ledger's seam family but 4 open cards carried it on
12787+
// 2026-08-31, and the ruled predicate is "any `repo:*` label" — so it
12788+
// declines, and SAYS the decline is conservative rather than asserting an
12789+
// off-repo audience it cannot see.
12790+
const ownRepoCard = carded(1, ['pm:queue', 'repo:objectstack', 'pm:blocking']);
12791+
t('H14 seam: a label naming the swept repo declines too (the predicate is literal)', h14row(ownRepoCard, seamIdx, swept).includes('DECLINES to judge'), true);
12792+
t('H14 seam: …and says that decline is CONSERVATIVE', h14row(ownRepoCard, seamIdx, swept).includes('CONSERVATIVE'), true);
12793+
t('H14 seam: …while a sibling label claims nothing conservative', h14row(seamCard, seamIdx, swept).includes('CONSERVATIVE'), false);
12794+
// …and the reading follows the SWEPT repo rather than a hardcoded name, so a
12795+
// copy of this patrol installed in a sibling repo reads its own board.
12796+
t('H14 seam: …the conservative reading follows the swept repo', h14row(seamCard, seamIdx, { ownerRepo: 'objectstack-ai/cloud' }).includes('CONSERVATIVE'), true);
12797+
t('H14 seam: …and a seam label there is still structural', h14row(ownRepoCard, seamIdx, { ownerRepo: 'objectstack-ai/cloud' }).includes('CONSERVATIVE'), false);
12798+
1259312799
// The summary line carries the third `read X of Y` pair, and says out loud
1259412800
// when the shortfall cost H14 its stale direction.
1259512801
const fbCounts = (fallbackProbed, fallbackCandidates) => ({

0 commit comments

Comments
 (0)