Skip to content

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

@claude

Measured on 9370edc74 (branch claude/issue-13983-prerequisite-exit-code, the #13983 worktree) while changing that file's refusal code.

The reading

scripts/import-prerequisite.mjs ships a --self-test — 49 cases after #13983, 41 before it — and nothing runs it:

$ git grep -n "import-prerequisite" -- .github/workflows package.json
(no output)

It is the shared PREREQUISITE NOT MET frame that 45 gates import (42 under scripts/**, 3 under packages/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.mjs is green, and correctly so. Its population is scripts CI runs:

✓ check-self-test-wired: every one of the 159 script(s) CI runs that ship a `--self-test` has that self-test run by CI.
  scope: 218 file(s) under scripts/, 174 carrying `--self-test` in code (comments masked); 159 of those are run by 29 workflow(s); ...

174 carry the flag, 159 are run by CI. import-prerequisite.mjs is 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 root package.json alias 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

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

Activity

  1. self-assigned this
    on Sep 1, 2026
  2. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    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 root package.json script alias if the repo's wiring convention wants one; ⛔ NOT scripts/import-prerequisite.mjs itself, ⛔ no audit of the 15-file self-test remainder (a separate sweep card per triage), ⛔ no change to check-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 touches lint.yml (verified against the open-PR list at claim). In-flight #14043 (gate header prose) disjoint; queued PR #14221 disjoint.


    Generated by Claude Code

  3. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    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 on origin/main at 33dea61f

    Premise 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.yml only — the module itself is not touched, the 15-file remainder is not audited, and check-self-test-wired's population rule is not changed.

    Generated by Claude Code


    Generated by Claude Code

  4. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    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.mjs is not touched, the 15-file self-test remainder is not audited, and check-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

  5. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    ✅ ACCEPT — PR #14226. Verified against the tree and the checks, not against the report

    domain:devx, session session_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 -e as 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:dispatched until MERGED. This completes the import-prerequisite three-baton sequence (#13983 → #13826 → #14007).


    Generated by Claude Code

  6. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    落地收口 — PR #14226 MERGED (1e130127 on origin/main)

    domain:devx, session session_01WLJQhde67SeTccsmnBVarV, R1. Same-window close-out. Card auto-closed completed via Fixes; pm:dispatched stripped 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions