Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 94 additions & 35 deletions scripts/pm/check-half-states.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,17 @@
* state-model row (its protocol face) and applying the label to the
* specimen card, which is a seat's write.
*
* ## H26 — the block that nothing can ever release
* ## H26 — the block nothing is SCHEDULED or DECLARED to release
*
* H26 an open `pm:blocked` card whose resolvable `Blocked-by:` target is OPEN
* and parked in a state that can never close — `pm:on-hold` or
* `needs-user-decision`, both by definition states a card sits in WHILE
* OPEN. The unlock predicate is "the target closed", so such a block is
* structurally indefinite and nothing reported it: the waiting card is
* and parked in `pm:on-hold` or `needs-user-decision` with no exit this
* sweep can fire. ⛔ NOT a claim that such a target CANNOT close: that
* categorical half was asserted, never counted, and counting refutes it
* (#18017 — the census and its control live in the row's own rationale
* block). What survives is that no timer, sweep or queue in this loop
* closes either state and, since #19255, that the card declared no
* label-transition exit for this target — so the block has NO SCHEDULED
* OR DECLARED RELEASE and nothing reported it: the waiting card is
* perfectly well-formed (H4 clean, target resolves, target open, so H19
* clean, label correct), and H9 — the nearest neighbour — audits the HELD
* card rather than the waiting one. Six measured instances, all found by
Expand All @@ -607,7 +611,7 @@
* cycle. FREE: H19 already resolves every distinct target, and a resolved
* target's labels rode in on a payload this sweep had already paid for.
* ⛔ Not a judgement that the block is wrong — waiting on a deferred card
* is sometimes right; the row says the wait has no releasing mechanism,
* is sometimes right; the row says nobody is SCHEDULED to end the wait,
* which is a fact a human should be handed rather than discover.
* NOT reported BY THIS ROW: a target labelled `pm:queue` while titled
* `[Decision]` (one of the six). The reason is unchanged and this row is
Expand Down Expand Up @@ -6880,12 +6884,47 @@ export function h19DeclaredExitFired(issue, resolutions, commentBodies, ownerRep
}

// ---------------------------------------------------------------------------
// H26 — a block whose target can never CLOSE, and the stale chain (#11219).
//
// The unlock predicate is "the `Blocked-by:` target CLOSED". `pm:on-hold` and
// `needs-user-decision` are, by definition, states a card sits in WHILE OPEN.
// A block naming such a target is therefore structurally indefinite: nothing in
// the machinery can ever fire it, and until this row nothing said so.
// H26 — a block with no scheduled or declared release, and the stale chain (#11219).
//
// The unlock predicate is "the `Blocked-by:` target CLOSED", and since #19255 a
// card can declare a second one: a label-transition exit this sweep fires. This
// row is what is left when NEITHER is in reach — the target is parked in
// `pm:on-hold` or `needs-user-decision`, no timer, sweep or queue in this loop
// closes either state, and the card declared no exit for that target. So the
// block has NO SCHEDULED OR DECLARED RELEASE, and until this row nothing said so.
//
// ## Why this row no longer says "can never close" (#18017)
//
// It used to, and that half was an ASSERTION — never counted. Counting refutes
// it. Taken on this board 2026-09-20T19:06Z, REST
// `/issues?labels=LABEL&state=STATE&per_page=100` walked to exhaustion, the
// population every ISSUE carrying the label — pull requests excluded, which is
// not tidying: 96 of the 111 rows the closed `needs-user-decision` leg returns
// are PRs, so dropping that filter measures another population.
//
// label open closed closed `state_reason`
// `pm:on-hold` 101 14 7 completed, 7 not_planned
// `needs-user-decision` 8 15 9 completed, 6 not_planned
// `pm:blocked` (control) 69 21 17 completed, 4 not_planned
//
// ⭐ The control has a known direction and it HIT: the same enumerator returned
// non-zero on all three CLOSED rows, so a `state=closed` leg answering nothing
// silently would have read as three zeros, and it did not. ⇒ cards DO close out
// of both indefinite states, 16 of those 29 closures `completed` — the hold
// lifted and the work finished, the very release the retired wording denied.
//
// This file presupposed it already, twice, in its own text: `PM_RESIDUE_LABELS`
// (H22) carries `pm:on-hold`, and a label is residue only on a CLOSED card; and
// H22's own note calls `needs-user-decision` one of the "perfectly good states
// for a closed card to have ended in" — the direct converse, one file away.
//
// ⚠️ What is KEPT is the half that survives the count, and the POPULATION is
// untouched: every card this row fired on before, it fires on now. The
// overstatement was "no one is scheduled to act" rendered as "no mechanism
// exists", and only the rendering changed. This sentence does not stay in a
// report body — it is quoted outward as a structural verdict, and seats have
// cited it as grounds to abandon locks they would otherwise have opened, so
// what it claims has to be what the numbers support.
//
// ## Why every existing check passes on these cards
//
Expand Down Expand Up @@ -6929,16 +6968,18 @@ export function h19DeclaredExitFired(issue, resolutions, commentBodies, ownerRep
// rows simply stopped throwing the labels away.
//
// Report-only, and pointedly not a judgement that the block is WRONG: waiting
// on a deferred card is sometimes exactly right. The row says this block has no
// mechanism that will ever release it, which is the thing a human should see
// rather than discover in a hand sweep.
// on a deferred card is sometimes exactly right. The row says nobody is
// SCHEDULED to release this block and the card declared no exit for it, which
// is the thing a human should see rather than discover in a hand sweep.
// ---------------------------------------------------------------------------

/**
* Target states that can never satisfy the unlock predicate, because they are
* states an OPEN card sits in. `pm:blocked` is deliberately not here — that is
* the chain leg below, and it says something different: the target CAN close,
* once its own blocker does.
* Target states that nothing is SCHEDULED to move a card out of, so nothing is
* scheduled to satisfy the unlock predicate either. ⛔ NOT states a card cannot
* leave — cards close out of both, counted in the block above; they lift when
* a person acts and on no timer this loop owns. `pm:blocked` is deliberately
* not here — that is the chain leg below, saying something different: the
* target has a named next step, its own blocker closing.
*/
export const INDEFINITE_TARGET_LABELS = ['pm:on-hold', 'needs-user-decision'];

Expand Down Expand Up @@ -6991,15 +7032,19 @@ export function h26BlockOnIndefiniteTarget(issue, resolutions, commentBodies, ow
? ` +${indefinite.length - H19_TARGET_LIST_CAP} more`
: '';
parts.push(
`\`pm:blocked\` on ${indefinite.length} target(s) that can never CLOSE: ${named}${more}. ` +
'The unlock predicate is "the `Blocked-by:` target closed", and `pm:on-hold` / ' +
'`needs-user-decision` are by definition states a card sits in WHILE OPEN — so this ' +
'block has NO MECHANISM THAT WILL EVER RELEASE IT. Every existing check passes on this ' +
`\`pm:blocked\` on ${indefinite.length} target(s) with no scheduled or declared release: ${named}${more}. ` +
'The unlock predicate is "the `Blocked-by:` target closed"; no timer, sweep or queue in ' +
'this loop closes `pm:on-hold` or `needs-user-decision`, and this card declares no ' +
'label-transition exit for them — so this block has NO SCHEDULED OR DECLARED RELEASE. ' +
'⛔ Deliberately weaker than the categorical claim this row used to make, which counting ' +
'refutes: on this board 2026-09-20, `pm:on-hold` closed 14 and `needs-user-decision` ' +
'closed 15, 16 of the 29 `completed` (control: `pm:blocked` closed 21, so the closed ' +
'enumerator fires) — these targets DO close. Every existing check passes on this ' +
'card (the line is present, the target resolves, the target is open, the label is ' +
'correct), which is why the measured instances were found by a human reading and by no ' +
'gauge; H9 asks the mirror question about the HELD card and nothing asked about the ' +
'WAITING one. ⚠️ Not a claim that the block is wrong — waiting on a deferred card is ' +
'sometimes exactly right. It says the wait is indefinite BY CONSTRUCTION, so the release ' +
'sometimes exactly right. It says nobody is SCHEDULED to end the wait, so the release ' +
'has to come from the target\'s own state changing (a ruling answered, a hold restarted) ' +
'and someone has to want that.',
);
Expand Down Expand Up @@ -24224,11 +24269,12 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, seen
// waiting on one epic is the normal shape, and it costs one read.
//
// The map holds the ISSUE, not just its number, because H26 (#11219) asks a
// second question of the same target — is it parked in a state that can never
// close? — and the answer is a field the payload already carried. Free by
// construction: a locally-open target is answered from a listing in hand, and
// a fetched one arrives with its labels on the same response. Nothing here
// adds a request; the resolution rows simply stop discarding the labels.
// second question of the same target — is it parked in a state nothing is
// scheduled to end? — and the answer is a field the payload already carried.
// Free by construction: a locally-open target is answered from a listing in
// hand, and a fetched one arrives with its labels on the same response.
// Nothing here adds a request; the resolution rows simply stop discarding the
// labels.
const openLocalIssues = new Map();
for (const [number, issue] of seenUnscoped) openLocalIssues.set(number, issue);
for (const [number, issue] of seen) openLocalIssues.set(number, issue);
Expand Down Expand Up @@ -30798,7 +30844,7 @@ async function selfTest() {
t('#16393 summary: a bare line reports the divisor UNCHECKED, not agreed with', saidBy('h22Window', summaryLine({}, 0)).includes('RATE PREMISE UNOBSERVED'), true);
t('#16393 summary: …and renders numbers, never `undefined`', saidBy('h22Window', summaryLine({}, 0)).includes('undefined'), false);

// -- H26: a block whose target can never close, + the stale chain (#11219) --
// -- H26: a block with no scheduled or declared release, + the chain (#11219)
// The measured cards, by name, and both directions of every leg.
const waiting = (number = 1119) => ({
number,
Expand All @@ -30822,11 +30868,23 @@ async function selfTest() {
...extra,
});
t('H26: target parked in pm:on-hold -> finding', typeof h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold'])]), 'string');
t('H26: …and the row says the block has no releasing mechanism', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('NO MECHANISM THAT WILL EVER RELEASE IT'), true);
// #18017 — the categorical half is RETIRED and its replacement pinned
// POSITIVELY: an absence-only check stays green when a later edit deletes the
// assertion with the sentence it named. ⛔ And each retired phrase is pinned
// in the CASE the row wrote it — `can never CLOSE`, not a paraphrase that was
// already absent before this change and would therefore pin nothing.
t('H26: …and the row says the block has no scheduled or declared release', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('NO SCHEDULED OR DECLARED RELEASE'), true);
t('H26: …and the retired categorical claim is gone', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('NO MECHANISM THAT WILL EVER RELEASE IT'), false);
t('H26: …and so is the retired opening, in the case the row wrote it', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('can never CLOSE'), false);
t('H26: …and it carries the counted population that refuted them', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('`pm:on-hold` closed 14 and `needs-user-decision` closed 15'), true);
t('H26: …with the control, so the count is a reading and not a bare number', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('control: `pm:blocked` closed 21'), true);
t('H26: ⭐ …and the USEFUL half survives verbatim', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('the release has to come from the target\'s own state changing (a ruling answered, a hold restarted) and someone has to want that'), true);
t('H26: …and names the target and its state', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('`#987` (`pm:on-hold`)'), true);
t('H26: target parked in needs-user-decision -> finding', typeof h26BlockOnIndefiniteTarget(waiting(75), [tgt(68, ['needs-user-decision'])]), 'string');
t('H26: a target carrying BOTH indefinite states names both', h26row(waiting(), [tgt(987, ['pm:on-hold', 'needs-user-decision'])]).includes('`pm:on-hold` + `needs-user-decision`'), true);
// The clean directions — an ordinary open target is not this row's business.
// The clean directions — an ordinary open target is not this row's business,
// nor is a parked one the card hands a fireable exit for (#19255 stands down).
t('H26: a parked target with a DECLARED label-transition exit stands down (#19255)', h26BlockOnIndefiniteTarget({ ...waiting(), body: 'Blocked-by: #987\nUnlock-action: re-check #987 when label pm:on-hold absent' }, [tgt(987, ['pm:on-hold'])], [], REPO_OS), null);
t('H26: an ordinary open target -> clean', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:queue', 'domain:devx'])]), null);
t('H26: an unlabelled open target -> clean', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, [])]), null);
t('H26: no targets at all -> no row (H4 owns the missing line)', h26BlockOnIndefiniteTarget(waiting(), []), null);
Expand All @@ -30843,14 +30901,15 @@ async function selfTest() {
// The chain leg.
t('H26: a target that is itself pm:blocked -> the transitive row', typeof h26BlockOnIndefiniteTarget(waiting(1395), [tgt(10101, ['pm:blocked'])]), 'string');
t('H26: …and it says to look one level further', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('TRANSITIVE'), true);
t('H26: …and does not claim the block can never release', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('NO MECHANISM'), false);
t('H26: …and does not claim the block has no scheduled or declared release', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('NO SCHEDULED OR DECLARED RELEASE'), false);
t('H26: …nor the retired categorical claim it replaced', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('NO MECHANISM'), false);
// Both legs at once, on two different targets, in one row.
const bothLegs = String(h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold']), tgt(10101, ['pm:blocked'])]) ?? '');
t('H26: both legs report together', bothLegs.includes('NO MECHANISM THAT WILL EVER RELEASE IT') && bothLegs.includes('TRANSITIVE'), true);
t('H26: both legs report together', bothLegs.includes('NO SCHEDULED OR DECLARED RELEASE') && bothLegs.includes('TRANSITIVE'), true);
// A target that is BOTH parked and blocked is named ONCE, under the reading
// that ends the wait forever rather than the one that merely lengthens it.
const bothOnOne = String(h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold', 'pm:blocked'])]) ?? '');
t('H26: a parked AND blocked target is named once, as indefinite', bothOnOne.includes('NO MECHANISM THAT WILL EVER RELEASE IT'), true);
t('H26: a parked AND blocked target is named once, as indefinite', bothOnOne.includes('NO SCHEDULED OR DECLARED RELEASE'), true);
t('H26: …and not a second time as a chain', bothOnOne.includes('TRANSITIVE'), false);
// A partially indefinite block still reports: one live blocker does not make
// the indefinite one fireable.
Expand Down
Loading