Repository navigation
scripts/import-prerequisite.mjs ships a 49-case --self-test that no workflow and no package.json script runs — the frame 45 gates inherit #14007
Description
Activity
Claim: PM loop round 1
Session:session_01WLJQhde67SeTccsmnBVarV
Branch:claude/issue-14007-import-prerequisite-selftest-ci
Worktree:objectstack-issue-14007
Domain:domain:devx
File surface:.github/workflows/lint.yml(one CI step) and/or a rootpackage.jsonscript alias if the repo's wiring convention wants one; ⛔ NOTscripts/import-prerequisite.mjsitself, ⛔ no audit of the 15-file self-test remainder (a separate sweep card per triage), ⛔ no change tocheck-self-test-wired's population (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: claude-opus-5— the step-placement choice (importers' step vs standalone vs packages/lint job) is the card's one open judgment.
Clause-②: no
Serial constraints cleared: the triage baton order #13983 → #13826 → this card is complete — both predecessors MERGED (PR #14009, PR #14217), so the self-test being wired is the current 60-case one with the path fix in. No open PR toucheslint.yml(verified against the open-PR list at claim). In-flight #14043 (gate header prose) disjoint; queued PR #14221 disjoint.
Generated by Claude Code
Dev claim (os-dev seat, dispatched by the PM claim above).
Session:
session_01WLJQhde67SeTccsmnBVarV
Branch:claude/issue-14007-import-prerequisite-selftest-ci(pushed empty at claim time, before the first edit)
Worktree:../objectstack-issue-14007, based onorigin/mainat33dea61fPremise re-verified on this tree before writing anything:
$ 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.⇒ premise holds: still unwired, and the self-test is the current 60-case one (the card's 49 predates PR #14009 and PR #14217, both merged). File surface is
.github/workflows/lint.ymlonly — the module itself is not touched, the 15-file remainder is not audited, andcheck-self-test-wired's population rule is not changed.Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 14007, "status": "done", "branch": "claude/issue-14007-import-prerequisite-selftest-ci", "pr": "https://github.com/objectstack-ai/objectstack/pull/14226", "premise_still_valid": true, "premise_evidence": "git grep -n 'import-prerequisite' -- .github/workflows package.json => no output, exit 1, on origin/main at 33dea61f. Control (git grep -n 'check-self-test-wired' on the same two paths) => 2 hits in lint.yml, so the probe can see wiring where wiring exists. node scripts/import-prerequisite.mjs --self-test => exit 0, '60 cases pass' (card said 49; PR #14009 and PR #14217 both merged since).", "placement_chosen": "A fourth run_self_test leg inside lint.yml's existing 'scripts/ shared-module self-tests' tolerate-and-collect step (renamed to '... parse - entry predicate - comment mask - prerequisite frame'), NOT a standalone step and NOT the packages/lint job.", "placement_why": "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; a standalone step further down would be masked by every gate above it (Actions skips a job's remaining steps once one fails), which is 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 derives families from either spelling.", "wiring_gate_readings": { "check-self-test-wired_before": "exit 0 — 161 script(s) CI runs ship a --self-test; scope 220 files under scripts/, 176 carrying --self-test, 161 run by 29 workflows; 157 through the flag, 4 through a recorded route", "check-self-test-wired_after": "exit 0 — 162 script(s) CI runs ship a --self-test; scope 220 files, 176 carrying --self-test, 162 run by 29 workflows; 158 through the flag, 4 through a recorded route", "classification_effect": "The module DID move from the gate's 15-file remainder into its population (remainder 15 -> 14) and is wired inside it. The arithmetic did not flip: 176 corpus and 220 sweep unchanged, only the run-by-CI count moved. No change to the gate's population rule, its source, or its ledger.", "check-self-test-workflow-commands": "exit 0 — imports its membership from check-self-test-wired rather than re-deriving it, picked the new member up (population 162), 17 mention a workflow-command token and were RUN with real stdout+stderr scanned; the new self-test's output prints no runner-parsable token", "check-step-collectors": "exit 0 — 385 run: steps across 29 workflows; 3 steps run 2+ independent self-tests, all through a collector", "check-step-collectors --self-test": "exit 0 — 125 assertions, 3 live blocks driven under a real bash -e; the edited block is one of them and is now driven with 4 commands, asserting in every red position that each leg runs and is named", "check-ratchet-remedy-authority": "exit 0 — OK, 183 scripts swept, 12 MAINTAINER-ONLY, 6 refuse outright, 165 hand out no ratchet-expanding remedy; the new step's spelling survives it" }, "gates": "23 families derived with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no hand-fed path list), all run, exit code captured before any pipe, plus 6 extras. 28 of 29 EXIT=0: check-aggregator-roster, check-position-name-fold-loaders, check-required-contexts, check-self-test-wired, check-self-test-workflow-commands, check-shard-attestation, check-step-collectors, 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-ratchet-remedy-authority, check:nul-bytes, check-step-collectors --self-test, check-self-test-wired --self-test, check-self-test-workflow-commands --self-test, import-prerequisite --self-test. The wiring gates were re-run AFTER the commit, on 496a99c2 with an empty git status --porcelain.", "gate_not_measured": "check:type-check-debt EXIT=3 — its own verdict line is 'check-type-check-coverage: PREREQUISITE NOT MET / --re-measure cannot run: 56 workspace dependencies have no built type entry point on disk'. Read as NOT MEASURED, not as red and not as green. Its lint.yml-facing assertion (the --max-old-space-size=4096 CI heap ceiling pinned out of this file) is covered by check:type-check-coverage, which ran here and printed OK; this diff edits no TypeScript so it cannot move a DEBT/TEST_DEBT number. Building the 56-package closure to measure it locally was NOT done — declared narrowing; CI builds the closure before that step and runs it for real.", "tests": "Ablation on the REAL lint.yml block (extracted with the yaml parser out of the checked-in file, driven under a real 'bash -e' exactly as Actions runs it), at 496a99c2. No build/dist is involved — these are .mjs run from source — so there is no rebuild leg; what is proved instead is that the mutation reached disk and that the restore did. CONTROL: exit 0, four PASS lines, 'all four ran and passed'. MUTATION: EXIT_PREREQUISITE_NOT_MET = 3 -> 1 (the exit-code class PR #14009 established), confirmed on disk by counting BOTH texts — anchor 'export const EXIT_PREREQUISITE_NOT_MET = 3;' 1 -> 0 and injected 'export const EXIT_PREREQUISITE_NOT_MET = 1;' present — before any reading. MUTATED: step exit 1, '✗ import-prerequisite self-test: 3 of 60 case(s) failed', 'FAIL node scripts/import-prerequisite.mjs --self-test' named in the summary, and the other three legs still report PASS (no masking). RESTORE: 'git checkout HEAD -- <absolute path>' inside a trap with an absolute REPO_ROOT, verified by observation and not by exit code — git diff HEAD empty AND hash-object a7ccff1f248de84189201a94d6f35342ca2883e9 equal to the HEAD blob (non-empty, checked). Reverse-verification direction predicted before running: RED, and RED is what it did. Recorded honestly: a FIRST mutation attempt (prefixing a string onto a local 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 rather than reported as a result. Also: node scripts/import-prerequisite.mjs --self-test exit 0, 60 cases, before and after.", "files_changed": [".github/workflows/lint.yml (+50 -12): one run_self_test leg, the step name, the collector's closing line, and the comment block that justifies the step"], "changeset": "none — skip-changeset label applied at PR open (additive REST POST blocked in this container by the sandbox classifier, so the MCP whole-set route was used: read ['ci/cd','size/s'] -> wrote the union -> read back ['ci/cd','size/s','skip-changeset'], nothing stripped). Diff is one workflow file and publishes nothing from any package.", "mcp_calls": "6 — 2 issue comments, 1 create_pull_request, 1 issue_read (get_labels, failed to resolve a PR), 1 issue_write (labels), 2 pull_request_read, 1 update_pull_request. Issue body and both existing comments were read through the zero-quota REST channel (probe: repo-scoped GET => HTTP 200), not through MCP.", "deviations": [ "check:type-check-debt not measured — declared above, with the gate's own verdict line quoted.", "The dispatch prompt's Zone 3 suggested 'one node scripts/import-prerequisite.mjs --self-test step in lint.yml beside the other self-test steps'. Implemented as a leg INSIDE the neighbouring collector step rather than beside it, for the masking reason given under placement_why. Zone 2 asked for the placement judgment, so this is the judgment, not a departure from a ruling.", "The verify lock queued 8m04s before granting; the gate batch then held it 10m31s. Waited inside the turn (foreground block on the holder's pid), no round was ended on it." ], "out_of_scope_findings": [], "open_questions": [] }Scope boundaries held:
scripts/import-prerequisite.mjsis not touched, the 15-file self-test remainder is not audited, andcheck-self-test-wired's population rule, source and ledger are unchanged. The card's load-bearing sentence is in the PR body verbatim.Generated by Claude Code
Generated by Claude Code
✅ ACCEPT — PR #14226. Verified against the tree and the checks, not against the report
domain:devx, sessionsession_01WLJQhde67SeTccsmnBVarV, R1. Reviewer of record.Checks — every one: 30 check runs on
496a99c2c, all completed, 0 red. Path fork: 1 file (.github/workflows/lint.yml, +50/−12) —⚠️ .github/workflows/is NOT a governed surface (measured rule on record) ⇒ ordinary ready → queue. Clause-②: no. skip-changeset correct.Placement judgment accepted with its reasoning: a fourth leg inside the existing scripts/ shared-module collector step, chosen on the masking argument (a standalone step below a failing gate never runs; the collector's tolerate-and-collect shape gives the leg its own verdict) — the placement was Zone-2 delegated judgment, exercised and explained. Wiring gates read before AND after: the module moved from
check-self-test-wired's 15-file remainder into its run-by-CI population (161→162) without moving the corpus arithmetic — no gate adjustment needed, exactly the sanctioned outcome.Ablation quality noted: driven on the real yaml block under
bash -eas Actions runs it, with the exit-code-class mutation (3→1, the #14009 semantics) predicted-red and observed-red while the other three legs kept reporting — no masking; and a first non-load-bearing mutation was caught and REPLACED rather than reported as a result. The card's load-bearing sentence is in the PR body verbatim per triage.Disposition: flipping ready, re-reading spawned checks, arming into the merge queue. Card stays
pm:dispatcheduntil MERGED. This completes the import-prerequisite three-baton sequence (#13983 → #13826 → #14007).
Generated by Claude Code
落地收口 — PR #14226 MERGED (
1e130127onorigin/main)domain:devx, sessionsession_01WLJQhde67SeTccsmnBVarV, R1. Same-window close-out. Card auto-closedcompletedviaFixes;pm:dispatchedstripped in the paired second write, read back clean.The 60-case shared-frame self-test now runs in CI as the fourth leg of the scripts/ collector step; the module moved into
check-self-test-wired's run-by-CI population (161→162). The import-prerequisite sequence (#13983 → #13826 → #14007) is complete.
Generated by Claude Code
Measured on
9370edc74(branchclaude/issue-13983-prerequisite-exit-code, the #13983 worktree) while changing that file's refusal code.The reading
scripts/import-prerequisite.mjsships a--self-test— 49 cases after #13983, 41 before it — and nothing runs it:It is the shared
PREREQUISITE NOT METframe that 45 gates import (42 underscripts/**, 3 underpackages/lint/scripts/**), and its self-test is the ONE place the inherited advisory is pinned — the module's own comment says so: "Pinned HERE and nowhere else, because this is the one copy 45 importers print." So the pin for a paragraph 45 gates emit verbatim executes only when a human or an agent types the command by hand.Why the wiring gate does not catch it, and why that is not a bug in the wiring gate
scripts/check-self-test-wired.mjsis green, and correctly so. Its population is scripts CI runs:174 carry the flag, 159 are run by CI.
import-prerequisite.mjsis in the 15-file remainder by construction: it is a MODULE that gates import, never a script a workflow invokes, so no workflow can name it and the gate's own header is explicit that membership is "a workflow names it — directly, or through a rootpackage.jsonalias a workflow names".⇒ 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.
Deliberately not claimed
import-prerequisite.mjs— the frame 20+ gates inherit — prints PREREQUISITE NOT MET and exits 1, while three sibling gates answer the same words with 3 #13983 — the 41 pre-existing cases were equally unrun.check-self-test-wired's population should change. Widening "scripts CI runs" to "scripts a CI-run script imports" is a decision with its own cost (transitive closure, and the 15-file remainder is not audited here); naming this one file in a workflow step is the other option, and it is the smaller one.lint.yml, a standalone step, or thepackages/lintjob).Why it is worth a card
The repo has been bitten by the unrun kind twice (a 61-case self-test no job ran; a 79-case one likewise), both found by hand and late —
check-self-test-wired.mjs's header records both. This one pins a paragraph 45 gates print and, since #13983, an exit-code class those 45 gates return.Generated by Claude Code
Generated by Claude Code