Skip to content

Commit 9391aff

Browse files
committed
fix(pm): node is a fireable Restart-when: command head
`classifyRestartWhen` decides fireability from the first word alone, and `node` was not on `RESTART_WHEN_COMMAND_HEADS`, so a runnable `node -e` predicate classified `prose` — the class H9 calls "a `manual` in disguise, waiting on an actor nothing schedules" — while the same predicate re-headed `pnpm exec node -e …` classified `command`. The head is the head every tool in this repo is invoked with, so the row reported a schedulable exit as unschedulable and its remedy sentence pointed at the wrong defect. Adds `node` to the measured head list with its corpus provenance, states in the docblock what the head-word test can and cannot tell, and records that the `pnpm exec` prefix is a workaround for this hole rather than a sanctioned spelling. Pins land in a new floored self-test battery carrying the card's four controls plus the closed-list and whole-word controls. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7e1b048 commit 9391aff

1 file changed

Lines changed: 54 additions & 8 deletions

File tree

‎scripts/pm/check-half-states.mjs‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3084,14 +3084,28 @@ export function h8MergedPrStillDispatched(issue, mergedPrs, openPrs) {
30843084
* `npx` the same census (#9613)
30853085
* `npm` this file's own pinned specimen, transcribed from a live card
30863086
* (`npm view create-objectstack dist-tags reports >= 17.0.0`)
3087+
* `node` the live objectui#9868 hold, read 2026-09-18 (#19129): its
3088+
* `Restart-when:` is a `node -e` probe of the INSTALLED spec, and
3089+
* `node` is the head every tool in this repo is invoked with
3090+
* (`node scripts/pm/…`)
30873091
*
30883092
* A value opening with a BACKTICKED span needs no head at all — the span is
30893093
* the canonical spelling of an executable predicate — so this list only has to
30903094
* cover the un-backticked residue, which the census puts at one value. It
30913095
* grows by MEASUREMENT: a head is added when a census finds one, ⛔ never
30923096
* because it seemed likely.
3097+
*
3098+
* ⚠️ What the head word CAN tell is whether some mechanism could run the line
3099+
* at all — the only question H9's row asks. What it CANNOT tell is whether
3100+
* running it means anything: `node -e "process.exit(0)"` fires on the spot and
3101+
* answers nothing, and this list does not see the difference. ⛔ And because
3102+
* the head is the WHOLE test, re-heading a real predicate (`node -e …` →
3103+
* `pnpm exec node -e …`) moved the verdict without moving one byte of what
3104+
* runs — a WORKAROUND for a hole in this list, ⛔ never a sanctioned spelling:
3105+
* a gauge a seat satisfies by prefixing two words measures spelling, not
3106+
* fireability (#19129).
30933107
*/
3094-
export const RESTART_WHEN_COMMAND_HEADS = ['git', 'npm', 'npx', 'pnpm'];
3108+
export const RESTART_WHEN_COMMAND_HEADS = ['git', 'node', 'npm', 'npx', 'pnpm'];
30953109

30963110
/**
30973111
* Which classes of `Restart-when:` value some mechanism can actually fire.
@@ -25097,10 +25111,17 @@ export const SELF_TEST_BATTERIES = Object.freeze({
2509725111
// Registered with the class-(b) seam-line row (#18901), pin just under the
2509825112
// count: a READER pinned on MEASURED live shapes, its firing controls inside.
2509925113
'H68 class-(b) seam line': 24,
25114+
// Registered with the `node` command head (#19129), pin just under the
25115+
// count. What this battery floors is a WIDENING — a class that reported a
25116+
// runnable predicate as unschedulable stopped doing so — so the SILENCING
25117+
// controls live inside it: the `prose` specimens the row exists for, and the
25118+
// closed-list controls (`bash`, `gradle`, `nodemon`) proving the head set did
25119+
// not become a guess at command-shaped words.
25120+
'H9 node command head': 9,
2510025121
});
2510125122

2510225123
/** The floor on the ROSTER itself — how many batteries must be declared at all. */
25103-
export const SELF_TEST_BATTERY_FLOOR = 8;
25124+
export const SELF_TEST_BATTERY_FLOOR = 9;
2510425125

2510525126
async function selfTest() {
2510625127
const cases = [];
@@ -26485,7 +26506,7 @@ async function selfTest() {
2648526506
t('H9 paths: …and one closing a parenthesis', restartWhenTrackedPaths('(see packages/runtime/src/http-dispatcher.ts).', h9Tracked).join('|'), 'packages/runtime/src/http-dispatcher.ts');
2648626507
t('H9 paths: no oracle means no path, never a guess', restartWhenTrackedPaths(v7898).length, 0);
2648726508
t('H9 class: the fireable set is exactly the three', FIREABLE_RESTART_WHEN_CLASSES.join('|'), 'closed-ref|issue-ref|command');
26488-
t('H9 class: the bare command heads are the measured four', RESTART_WHEN_COMMAND_HEADS.join('|'), 'git|npm|npx|pnpm');
26509+
t('H9 class: the bare command heads are the measured five', RESTART_WHEN_COMMAND_HEADS.join('|'), 'git|node|npm|npx|pnpm');
2648926510

2649026511
// The rows the two new classes produce — report-only, each naming the remedy
2649126512
// a seat can execute without re-reading the card.
@@ -26522,6 +26543,31 @@ async function selfTest() {
2652226543
t('gate: the ruling spelling still buys nothing', needsRestartWhenComments(hold('Restart-when: #13651 rules on it'), h9Tracked), false);
2652326544
t('gate: the reserved `closed …#N` class still buys nothing', needsRestartWhenComments(hold(`Restart-when: ${v5499}`), h9Tracked), false);
2652426545

26546+
// -- H9: `node` is a command head, the fleet's own invocation word (#19129) -
26547+
// The head word was the WHOLE verdict and `node` was not on the list, so a
26548+
// runnable `node -e` predicate on a live hold classified `prose` — 「a
26549+
// `manual` in disguise, waiting on an actor nothing schedules」 — while the
26550+
// SAME predicate re-headed `pnpm exec node -e …` classified `command`. The
26551+
// card's four controls ARE this battery: the subject, the re-headed twin it
26552+
// must now agree with, and the controls that keep the instrument lit in both
26553+
// directions — the `prose` row must still fire, and the head set must still
26554+
// be closed.
26555+
const BATTERY19129 = 'H9 node command head';
26556+
// VERBATIM from objectui#9868's body (read 2026-09-18), the live specimen the
26557+
// card was filed on: an INSTALLED-surface probe, re-headed by a seat at
26558+
// 2026-09-18T22:00Z to clear a row this list should never have fired.
26559+
const v9868 = `node -e "const s=require('@objectstack/spec/ui'); process.exit(s.NavigationItemSchema.safeParse({type:'view',id:'x',order:1}).success?0:1)" exits 0`;
26560+
b(BATTERY19129, 'heads: `node` joined the list, in one closed measured set', RESTART_WHEN_COMMAND_HEADS.join('|'), 'git|node|npm|npx|pnpm');
26561+
b(BATTERY19129, '⭐ subject: the live objectui#9868 predicate classifies `command`, and that class is FIREABLE', [classifyRestartWhen(v9868), FIREABLE_RESTART_WHEN_CLASSES.includes(classifyRestartWhen(v9868))].join(), 'command,true');
26562+
b(BATTERY19129, '⭐ control: the `pnpm exec node …` re-heading is the same predicate byte for byte after the head, and now reads the same class — the rewrite buys nothing', classifyRestartWhen(`pnpm exec ${v9868}`), 'command');
26563+
b(BATTERY19129, 'control: the two fireable controls are unmoved, so the instrument is lit in that direction', [classifyRestartWhen('git grep -q foo -- bar'), classifyRestartWhen('closed objectstack-ai/objectstack#19049')].join(), 'command,closed-ref');
26564+
b(BATTERY19129, '⭐ control: the `prose` class still fires on prose — the widening did not silence the row it exists for', [classifyRestartWhen('someone gets around to it'), classifyRestartWhen(v3739), classifyRestartWhen('')].join(), 'prose,prose,prose');
26565+
b(BATTERY19129, '⭐ control: the head set is still CLOSED, not a guess at command-shaped words — an unmeasured head stays `prose` however runnable it looks', [classifyRestartWhen('gradle assemble reports a clean build'), classifyRestartWhen('bash scripts/pm/os-verify-lock.sh -c true exits 0'), classifyRestartWhen('sh -c "true"')].join(), 'prose,prose,prose');
26566+
b(BATTERY19129, 'control: …and `node` counts as a WHOLE head word only; the `manual` opt-out still outranks the head', [classifyRestartWhen('nodemon restarts cleanly'), classifyRestartWhen('node_modules/.bin/foo exits 0'), classifyRestartWhen(`manual — ${v9868}`)].join(), 'prose,prose,manual');
26567+
b(BATTERY19129, '⭐ row: a hold carrying the subject line is a LEGAL hold now, in either channel', [h9OnHoldNoRestartWhen(hold(`Restart-when: ${v9868}`)), h9OnHoldNoRestartWhen(hold('parked'), [`Restart-when: ${v9868}`])].join(), ',');
26568+
b(BATTERY19129, 'row: …and the gathering policy moved with the verdict — a body that now answers buys no comment fetch', needsRestartWhenComments(hold(`Restart-when: ${v9868}`)), false);
26569+
b(BATTERY19129, 'floor: this battery is DECLARED on the frozen roster with a positive pin, and the roster floor rose to NINE with it', [Object.hasOwn(SELF_TEST_BATTERIES, BATTERY19129), SELF_TEST_BATTERIES[BATTERY19129] > 0, Object.isFrozen(SELF_TEST_BATTERIES), SELF_TEST_BATTERY_FLOOR === 9, Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR].join(), 'true,true,true,true,true');
26570+
2652526571
// -- H9: `issue-ref` is the DECLARATION position (#17605) -------------------
2652626572
// The wide form (`/#\d+\b/`) read a MENTION as an exit, so four live holds
2652726573
// whose value names a tracked repo path cleared H9 on an unrelated issue
@@ -28300,7 +28346,7 @@ async function selfTest() {
2830028346
b(BATTERY18901, 'H68 band: …and the sweep really pushes it, so the registry sees it', familyRegistryCoverage().emitted.includes('H68'), true);
2830128347
b(BATTERY18901, 'H68 band: no code is left unregistered and none registered that the sweep never pushes', familyRegistryCoverage().missing.length + familyRegistryCoverage().extra.length, 0);
2830228348
b(BATTERY18901, 'floor: this battery is DECLARED on the roster, with a positive pin', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, BATTERY18901) && SELF_TEST_BATTERIES[BATTERY18901] > 0, true);
28303-
b(BATTERY18901, 'floor: the roster now declares EIGHT batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR === 8 && Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR, true);
28349+
b(BATTERY18901, 'floor: the roster now declares NINE batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR === 9 && Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR, true);
2830428350

2830528351
// -- H64 — a seat- or dev-signed artefact that names no session (#18069,
2830628352
// re-keyed by #18237) ---------------------------------------------------
@@ -29439,7 +29485,7 @@ async function selfTest() {
2943929485
b(BATTERY18664, '#18664 floor: this battery is DECLARED on the roster', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, BATTERY18664), true);
2944029486
b(BATTERY18664, '#18664 floor: …with a positive pin, so an empty battery cannot satisfy it', SELF_TEST_BATTERIES[BATTERY18664] > 0, true);
2944129487
b(BATTERY18664, '#18664 floor: the roster is frozen', Object.isFrozen(SELF_TEST_BATTERIES), true);
29442-
b(BATTERY18664, '#18664 floor: the roster now declares EIGHT batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 8);
29488+
b(BATTERY18664, '#18664 floor: the roster now declares NINE batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 9);
2944329489
b(BATTERY18664, '#18664 floor: …including the five this battery landed BESIDE, so neither side of the base merge silently dropped one', ['H66 released queue card', 'H19 judged-set founding', 'H65 tier declaration spelling', 'H67 queued merged-delivery reading', 'H2/H47/H66 decorated ownership marker'].every((name) => Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, name)), true);
2944429490
b(BATTERY18664, '#18664 floor: …and the roster really carries at least that many', Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR, true);
2944529491

@@ -31086,7 +31132,7 @@ async function selfTest() {
3108631132
b(BATTERY19255, '⭐ H26 stand-down: an exit naming the parked target silences the row in either channel — and once the label leaves, H19 fires the exit while H26 stays quiet', [h26row(exitCard(`Blocked-by: #68\n${LIVE_EXIT}`), [parked], undefined, REPO_OS), h26row(exitCard('Blocked-by: #68'), [parked], [LIVE_EXIT], REPO_OS), h26row(exitCard(`Blocked-by: #68\n${LIVE_EXIT}`), [ruled], undefined, REPO_OS)].every((v) => v === '') && typeof h19exit(exitCard(`Blocked-by: #68\n${LIVE_EXIT}`), [ruled]) === 'string', true);
3108731133
b(BATTERY19255, 'H26 stand-down: ⛔ a comment it was not handed is not read; an exit naming ANOTHER card, or the PR-shaped value, leaves the row firing and the latter is named as a line nothing fires; two parked targets with one exit report only the other', [h26row(exitCard('Blocked-by: #68'), [parked], undefined, REPO_OS) !== '', h26row(exitCard('Blocked-by: #68\nUnlock-action: re-check #987 when label pm:on-hold absent'), [parked], undefined, REPO_OS) !== '', h26row(exitCard('Blocked-by: #68\nUnlock-action: re-check PR #123'), [parked], undefined, REPO_OS).includes('already do (`re-check PR #123`)'), h26row(exitCard(`Blocked-by: #68, #987\n${LIVE_EXIT}`), [parked, tgt(987, ['pm:on-hold'])], undefined, REPO_OS).includes('on 1 target(s)'), h26row(exitCard(`Blocked-by: #68, #987\n${LIVE_EXIT}`), [parked, tgt(987, ['pm:on-hold'])], undefined, REPO_OS).includes('`#987`')].join(), 'true,true,true,true,true');
3108831134
b(BATTERY19255, 'H26 remedy: the row without an exit prescribes the live spelling and the close, says other spellings fall back silently, and the chain leg carries none of it', [h26row(waiting(75), [parked]).includes('`Unlock-action: re-check #N when label needs-user-decision absent`'), h26row(waiting(75), [parked]).includes('close the waiting card `not planned`'), h26row(waiting(75), [parked]).includes('falls back silently'), h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('⭐ The exit')].join(), 'true,true,true,false');
31089-
b(BATTERY19255, 'floor: the roster now declares EIGHT batteries, and this one is on it', SELF_TEST_BATTERY_FLOOR === 8 && Object.hasOwn(SELF_TEST_BATTERIES, BATTERY19255), true);
31135+
b(BATTERY19255, 'floor: the roster now declares NINE batteries, and this one is on it', SELF_TEST_BATTERY_FLOOR === 9 && Object.hasOwn(SELF_TEST_BATTERIES, BATTERY19255), true);
3109031136

3109131137
// -- The UNGATED liveness read + H28: the stale body line (#11747) ----------
3109231138
//
@@ -35904,7 +35950,7 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
3590435950
// THE ROSTER — a floor that cannot be satisfied by a zero.
3590535951
b(BATTERY67, 'H67 floor: this battery is DECLARED on the roster', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, BATTERY67), true);
3590635952
b(BATTERY67, 'H67 floor: …with a positive pin, so an empty battery cannot satisfy it', SELF_TEST_BATTERIES[BATTERY67] > 0, true);
35907-
b(BATTERY67, 'H67 floor: the roster grew again with #19255\'s and #18901\'s batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 8);
35953+
b(BATTERY67, 'H67 floor: the roster grew again with #19255\'s, #18901\'s and #19129\'s batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 9);
3590835954
b(BATTERY67, 'H67 floor: …including the two batteries this row landed BESIDE, so neither side of the base merge silently dropped one', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, 'H65 tier declaration spelling') && Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, 'H19 judged-set founding'), true);
3590935955
b(BATTERY67, 'H67 floor: …and the roster really carries at least that many', Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR, true);
3591035956

@@ -36247,7 +36293,7 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
3624736293
// FLOOR — this battery is declared, pinned, and the roster grew with it.
3624836294
b(BATTERY68, 'floor: this battery is DECLARED on the roster', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, BATTERY68), true);
3624936295
b(BATTERY68, 'floor: …with a positive pin, so an empty battery cannot satisfy it', SELF_TEST_BATTERIES[BATTERY68] > 0, true);
36250-
b(BATTERY68, 'floor: the roster now declares EIGHT batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 8);
36296+
b(BATTERY68, 'floor: the roster now declares NINE batteries, and the floor rose with it', SELF_TEST_BATTERY_FLOOR, 9);
3625136297
b(BATTERY68, 'floor: …including the four this battery landed BESIDE and the one that landed after it, so neither side of the base merge silently dropped one', ['H66 released queue card', 'H19 judged-set founding', 'H65 tier declaration spelling', 'H67 queued merged-delivery reading', 'ISSUE_BODY_LIMIT measured cap'].every((name) => Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, name)), true);
3625236298
b(BATTERY68, 'floor: …and the roster really carries at least that many', Object.keys(SELF_TEST_BATTERIES).length >= SELF_TEST_BATTERY_FLOOR, true);
3625336299

0 commit comments

Comments
 (0)