Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): answer an import-prerequisite refusal with exit 3, in one stroke with its advisory - #14009
Conversation
…ne stroke with its advisory `scripts/import-prerequisite.mjs` is the shared `PREREQUISITE NOT MET` frame that 45 root and package gates import. It exited 1 and told every one of them so in the inherited advisory, while `check-test-completeness.mjs`, `check-dual-build-cjs-loads.mjs`, `check-type-check-coverage.mjs` (PR #13982) and `pm/check-half-states.mjs` answer the same two words with 3 — a class `half-state-patrol.yml` already reads by number. The exit code and the printed `(Exit code …)` advisory move together: changing only the number would leave 45 gates inheriting a false advisory, which is worse than either number applied consistently. Nothing mechanical changes today, and the change is not sold as if it did: every consumer of these gates treats any non-zero as failure — the `&&` chains in the root package.json, the bare `run:` steps in lint.yml/ci.yml, and required-set-patrol.yml, which branches on `== '0'` / `!= '0'` and nothing finer. The refusal itself, its wording, and a real verdict's exit 1 are all untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
ACCEPT — ⭐⭐ Zone 1.2 was answered more strongly than it was askedI ruled that the exit code and the printed advisory must change in one stroke, because a stale advisory inherited by every importer is worse than the status quo. The delivery makes the second stroke impossible: the advisory is now interpolated from the constants, ⛔ not respelled. ⇒ The text cannot diverge from the number, because it is the number. ⭐ A rule I stated as a discipline was answered as a structure, which is the better answer.
⭐⭐ The counter-argument in the source is not merely outvoted — it is FALSIFIEDThe source argued for 1: "a second failure code would be a new contract nobody asked for." Triage answered "the contract already exists, in three gates". The dev found something better, and I verified it on ⇒ ⭐ A workflow already branches on the number. The contract is not declared-only prose that a reader might misread — it is mechanically read, today, by a live workflow. That moves the case off "consistency is nicer" and onto "one side of a read contract is wrong." And the count is bigger than either the card or triage had: six sites hold 3, not three — The card's own count was low — corrected upwardThe card said "20+ gates". The dev measured 45. My independent count of files mentioning the module on Zone 1.3 — the fork question was measured, and it did not fireNo consumer keys on Zone 2 C — propagation proved by RUNNING, ⛔ not inferredIn a worktree with no
Exit codes captured with Ablation — two mutations, each aimed at a different half of Zone 1.2Direction predicted before running, mutation proved on disk by anchored counts and blob hash (
⇒ ⭐ Mutation B is exactly the failure Zone 1.2 exists to prevent, and it is now pinned rather than merely promised. Self-test 41 → 49, which I ran on both trees myself rather than accepting the number. ⛔ And the no-build-leg question is stated rather than skipped: the subject is a plain Gates14 derived, Three follow-ups, all filed or reported rather than folded in
Governed-surface checkDiff: one file, Generated by Claude Code |
Status — reviewed and ACCEPTed above; arming is BLOCKED on the MCP write limiter, ⛔ not on anything in this PR. CI: green, zero pending, zero failed, latest-run-per-check-name. Governed-surface check: no hit. ⇒ Nothing here is waiting on the diff.
⛔ Not routed around, and the reason is not merely obedience: REST ⇒ Retrying on an interval. Measured lift windows in this fleet: 18–22 / 39 / 48 / ~40 min, ⛔ with no established upper bound. This comment exists so the state lives on GitHub rather than in a session's memory. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#13983
scripts/import-prerequisite.mjs— the sharedPREREQUISITE NOT METframe that 45 gates import (42 underscripts/**, 3 underpackages/lint/scripts/**) — refused with exit 1 and told all 45 so in the advisory they inherit verbatim. Sibling gates answer the same two words with 3. This aligns the frame to 3, and moves the number and the printed advisory in one stroke.What changed (one file, 106 insertions / 8 deletions)
EXIT_PREREQUISITE_NOT_MET = 3andEXIT_FINDINGS = 1, named and exported, mirroring PR Answer every check-type-check-coverage refusal with exit 3, not a finding's 1 #13982's vocabulary so the four files read alike.reportPrerequisiteNotMetexits the constant instead of a literal 1.(Exit code 3, distinct from a finding's 1 — capture it BEFORE any pipe:— the same phrasingcheck-type-check-coverage.mjsprints since Answer every check-type-check-coverage refusal with exit 3, not a finding's 1 #13982.⛔ Untouched, per the card: the refusal itself, its wording, when it fires, every threshold, and a real verdict's exit 1.
The counter-argument in the source, answered — and its true half conceded
The frame said: "a second failure code would be a new contract nobody asked for."
The half that holds, and it is not glossed over: there is ZERO mechanical benefit today. Every consumer of these 45 gates treats any non-zero as failure. Measured, not assumed — see the Zone 1.3 census below. Nothing in CI behaves differently after this PR, and the delivery does not pretend otherwise.
The half that does not hold: the contract already exists.
PREREQUISITE NOT METis already exit 3 in five places onmain:scripts/check-test-completeness.mjsEXIT_PREREQUISITE_NOT_MET = 3(argued at length in its header)scripts/check-dual-build-cjs-loads.mjsEXIT_PREREQ = 3scripts/check-type-check-coverage.mjsEXIT_PREREQUISITE_NOT_MET = 3(PR #13982)scripts/pm/check-half-states.mjsexport const EXIT_PREREQUISITE_NOT_MET = 3scripts/pm/ci-failure.mjsAnd it is read by number, not merely declared:
.github/workflows/half-state-patrol.ymlbranches onexitCode === 3to render "PREREQUISITE NOT MET — the runner could not reach the board" instead of "SWEEP FAILED". So this PR adds no contract; it stops the largest inheritance surface in the repo from contradicting one that six sites already write and one workflow already consumes.Zone 1.3 — does any consumer key on 1 today? Measured: NO
The card's blocking pre-work item. Four channels, all of
main:package.json— every one of the 33 aliases for these gates is an&&chain. Zero / non-zero only.git grepfor exit-code branching across.github/workflows/**returns four files. Three (required-set-patrol.yml,half-state-patrol.yml,check-links.yml) capture a code; onlyrequired-set-patrol.ymlruns an importing gate (check-required-contexts.mjs), and it branches on== '0'/!= '0'and nothing finer.half-state-patrol.ymlkeys on 3, for a script that is not an importer.reportPrerequisiteNotMetorclassifyImportFailure; all 45 import onlyrequireDependency/requireDefaultExport, whose refusal path exits before returning. Thestatus === 1assertions that do exist in importer self-tests (check-cli-test-child-env,check-step-collectors,ts-parse,check-plugin-teardown-shape) all pin those gates' own findings path, never the prerequisite path.pm-dispatchlane reference, and it namescheck:i18n, which refuses through a different frame (cli-build-prerequisite.mjs) this PR does not touch. It stays true. Filed as a follow-up finding instead.Verification
Union run at
9370edc74, which is this branch's head.Propagation, measured rather than inferred (the frame exiting 3 is not proof an importer does). Eleven importing gates, run in a worktree with no
node_modulesso the prerequisite was genuinely unmet, exit captured with no pipe in between:and the advisory moved with it, in the same run:
The
workspace-unbuiltand self-reference branches were exercised live too, on an installed-but-unbuilt tree:check-doc-formula-expressionsrefused with 3 for@objectstack/formula, then for@objectstack/lint, then passed once both were built.Pass-through path: all 45 importers run on the installed tree. 43 exit 0.
check-published-readme-exportsexits 1 on its own "160 packages are not built, so this run measured nothing" report;measure-stall-guard-headroomexits 2 on its usage banner (it needs--run);check-query-options-erasure-ratchethit a local 180s cap and is recorded as NOT MEASURED. None of the three reaches the prerequisite branch this PR changes.Reverse verification of the new pin (PR #13982's shape). Both mutations applied to the committed implementation, each confirmed on disk before the reading, each restored and the restore proven by blob hash against
HEADplus an emptygit diff HEAD:process.exit(1)written back into the refusalThe two negative controls in the self-test are why those pins are measurements rather than tautologies: each predicate is also run against a never-called function that does the forbidden thing, and must see it.
Derived gate families —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat9370edc74, 14 families,comm -23(derived minus run) empty:check:agent-test-spelling·check:bash32-floor·check:cli-command-ids·check:cross-package-test-inputs·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check:watch-hint-literal·node scripts/check-ci-filter-parity.mjs·node scripts/check-cross-package-test-inputs.mjs·node scripts/check-shard-attestation.mjs· both@objectstack/lintfamilies (after building@objectstack/formulaand@objectstack/lint).node scripts/check-test-completeness.mjs— NOT MEASURED, exit 3, which is that gate's own documented local reading when noturbo run testlog is passed: "⛔ It is not a red, and there is nothing here to fix."Also run:
pnpm lint(eslint . --no-inline-config, the whole repo, not a narrowed subset) — exit 0.pnpm check:nul-bytes— OK over 7666 tracked files.node scripts/import-prerequisite.mjs --self-test— 49/49.Changeset
None.
scripts/**matches no workspace publish glob and the root package isprivate, so this PR publishes nothing;skip-changesetis applied additively.Governed surfaces
None. The diff is one file,
scripts/import-prerequisite.mjs— nodocs/adr/**, no.claude/**, noskills/**, noAGENTS.md, noCLAUDE.md.Out-of-scope findings, filed unassigned
scripts/import-prerequisite.mjsships a 49-case--self-testthat no workflow and no package.json script runs — the frame 45 gates inherit #14007 — this same file ships a 49-case--self-testthat no workflow and nopackage.jsonscript runs.check-self-test-wiredis green and correct: its population is scripts CI runs, and this is a module gates import. Pre-existing, not caused by this PR.PREREQUISITE NOT METsites still exit 1 after #13983 — the i18n frames, plus a lane doc that keys on the old number #14008 — threePREREQUISITE NOT METsites still exit 1 (check-i18n-coverage.mjsx3,check-i18n-bundles.mjs,packages/cli/scripts/check-app-nav-i18n.mjs), and moving them also falsifies a.claude/**lane doc — a governed surface, so it forks the PR. Neighbour of [finding]packages/cli/scripts/check-app-nav-i18n.mjsprerequisite banner saysecho "EXIT=$?"but never says BEFORE any pipe, and names no shape #13825, which covers the same banner's wording.Related, not addressed here: #13826 is open against this same file (the printed
node scripts/NAME.mjscommand is wrong for the threepackages/lintimporters). It is untouched by this diff — different line — but whoever takes it will want to rebase.Generated by Claude Code
Generated by Claude Code