ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

ci(lint): run the shared prerequisite frame's self-test - #14226

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci
Sep 1, 2026
Merged

ci(lint): run the shared prerequisite frame's self-test#14226
baozhoutao merged 1 commit into
mainfrom
claude/issue-14007-import-prerequisite-selftest-ci

Conversation

@baozhoutao

@baozhoutaobaozhoutao commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#14007

scripts/import-prerequisite.mjs is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/lint/scripts/**). Its --self-test is the one place the inherited advisory is pinned — the module says so itself: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." Since PR #14009 it also pins the exit-code CLASS those same 45 gates return for an unmet prerequisite. Nothing ran it.

This wires it. One line of CI, plus the prose that keeps the line explicable.

The reading, re-verified on this branch before any edit

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output) # exit 1
$ git grep -n "check-self-test-wired" -- .github/workflows package.json # control
.github/workflows/lint.yml:1092: node scripts/check-self-test-wired.mjs --self-test
.github/workflows/lint.yml:1093: node scripts/check-self-test-wired.mjs
$ node scripts/import-prerequisite.mjs --self-test
✓ import-prerequisite self-test: 60 cases pass — not-installed, workspace-unbuilt, broken-install
and dependency-missing stay distinct, and a resolved-then-threw package is rethrown.

The card was filed against a 49-case self-test; the count is 60 now (PR #14009 and PR #14217, both merged). Sixty cases that executed only when a human or an agent typed the command by hand.

Why the wiring gate did not catch it, and why that is not a bug in the wiring gate

Quoting the card's load-bearing sentence verbatim, because it is the whole value of the finding:

The blast radius and the population rule point in opposite directions here: the file with the largest inheritance surface in the farm is the one shape the wiring gate cannot see.

check-self-test-wired's population is the scripts CI runs, and this file is a MODULE gates import, never a script a workflow invokes — so it sat in that gate's remainder by construction, with the gate correctly green. ⛔ Nothing here changes that gate's population rule, and ⛔ nothing here audits the rest of that remainder (a separate sweep card).

Naming the module in a workflow step is the other, smaller remedy — and it is what moves the file INTO the population. Measured on this branch, same gate, same tree, before and after the step:

before 161 script(s) CI runs ship a --self-test; 157 have it run through the flag
after 162 script(s) CI runs ship a --self-test; 158 have it run through the flag

The 176-file --self-test corpus and the 220-file scripts/ sweep are unchanged; only the "run by CI" count moves, which is exactly the claim being fixed. Full verdict lines under Gates below.

Placement, and why

Chosen: a fourth leg of lint.yml's existing scripts/ shared-module self-tests tolerate-and-collect step, rather than a standalone step or the packages/lint job — that step is already the home for scripts/** shared modules whose behaviour is pinned only by their own self-test, it sits early in the lint job, and its collector shape gives the new leg a verdict of its own whichever way the other three go. A standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails) — the same defect one level up, and the split check-step-collectors explicitly refuses as a remedy.

No root package.json alias: lint.yml's GATE INVOCATION IDIOM note makes the direct node scripts/X.mjs --self-test form the local convention, and dispatch-gates.mjs derives families from either spelling, so nothing is lost.

Ablation — the wiring is load-bearing

The real run: block was extracted out of lint.yml and driven under a real bash -e, exactly as Actions runs it. Mutation: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 in the module — the very exit-code class PR #14009 established. Mutation confirmed on disk (anchor count 1 -> 0, injected marker present) before any reading was taken; restore pinned to HEAD and verified byte-identical (git diff HEAD empty, blob a7ccff1f on both sides).

CONTROL (tree at HEAD) exit 0 PASS x4, "all four ran and passed"
MUTATED (frame regression) exit 1 ✗ import-prerequisite self-test: 3 of 60 case(s) failed.
FAIL node scripts/import-prerequisite.mjs --self-test
the other three still report PASS — no masking

Before this PR that same mutation produced no CI signal at all.

A first mutation attempt is recorded rather than dropped: prefixing a string onto a local variable inside the self-test's own harness landed on disk and the self-test still passed. A mutation that is not load-bearing is not an ablation, so it was replaced with the one above rather than reported as a result.

Gates

Family derived from the real diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (23 families; no path list hand-fed to it — the script takes the change set from the merge base itself). All of them run, exit codes captured before any pipe, on 496a99c2 with a clean git status --porcelain:

✓ check-self-test-wired: every one of the 162 script(s) CI runs that ship a `--self-test` has that
self-test run by CI.
scope: 220 file(s) under scripts/, 176 carrying `--self-test` in code (comments masked); 162 of
those are run by 29 workflow(s); 158 have their self-test run through the flag, 4 through a
recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would
parse as a workflow command.
scope: 162 script(s) CI runs ship a `--self-test`; 17 mention a workflow-command token in code
(comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 385 `run:` steps across 29 workflow(s); 3 step(s) run 2+ independent
self-tests, all of them through a collector.
✓ check-step-collectors --self-test: 125 assertions, 3 live block(s) driven under a real `bash -e`.
OK check-ratchet-remedy-authority: 183 scripts swept; 12 mark the expanding remedy
⛔ MAINTAINER-ONLY, 6 turn it down outright, 165 hand out no ratchet-expanding remedy.

The wiring gates' arithmetic did not break. The module moved from the remainder into the population and is wired inside it; check-self-test-workflow-commands, which imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (162) and scanned real output; and check-step-collectors --self-test now drives the four-command block under a real bash -e, asserting in every red position that each leg still runs and is named.

Also green (exit 0): check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-shard-attestation, check-whole-set-label-write, docs-audit/check-drift-comment, pm/ci-failure --self-test, check:agent-test-spelling, check:declared-population-live, check:node-version, check:pm-dispatch-gates, check:pnpm-acquisition, check:pnpm-filter-targets, check:required-contexts, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:type-check-coverage, check:workflow-status-functions, check:nul-bytes, plus both wiring gates' own --self-test legs.

Declared narrowing, one gate: check:type-check-debt is NOT MEASURED here — it exited 3 (PREREQUISITE NOT MET, this very frame), refusing to --re-measure because 56 workspace dependencies have no built type entry point in this worktree. That is not a pass and not a finding. Its lint.yml-facing assertion — the --max-old-space-size=4096 CI heap ceiling this file pins — is covered by check:type-check-coverage, which ran here and printed OK; and this diff edits no TypeScript, so it cannot move a DEBT or TEST_DEBT number in either direction. CI builds the closure before that step and runs it for real.

skip-changeset: the diff is one workflow file, publishing nothing from any package.

Card #14007 is fixed by this PR; no other card is addressed here.

Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV

Generated by Claude Code

`scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame
45 gates import, and its 60-case `--self-test` is the one place the inherited
advisory — and, since #13983, the exit-code class those gates return — is
pinned. Nothing ran it: `git grep -n "import-prerequisite" -- .github/workflows
package.json` was empty, so the pin executed only when someone typed it.
Wire it as a fourth leg of lint.yml's existing `scripts/ shared-module
self-tests` tolerate-and-collect step, which already runs the same category of
module self-test early in the job and reports a verdict per leg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@baozhoutaobaozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 1, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 1, 2026 15:14
@baozhoutao
baozhoutao added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 1e13012Sep 1, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14007-import-prerequisite-selftest-ci branch September 1, 2026 15:39
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit

2 participants

@baozhoutao@claude