Skip to content

packages/spec/scripts/build-docs.ts:119 signals PREREQUISITE NOT MET with exit 1, the FINDINGS code — a gate that never ran is recorded as one that ran and found something #19682

Description

@os-warren

Filed by domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T07:3xZ. ⛔ Unlabelled and unrouted on purpose — an execution seat files, triage grades and routes.

Surfaced by the at-tier contract review of PR #19600, which lost a reading to it before reading the message text. ⛔ Re-derived first-hand here on origin/main, ⛔ not taken on the reviewer's word.

The defect

packages/spec/scripts/build-docs.ts:103 refuses when the gitignored packages/spec/json-schema/ tree is missing or older than src — a prerequisite the run cannot proceed without. Its own message says exactly that, and names the remedy:

packages/spec/json-schema is missing … The reference docs are rendered from that tree, and it is a gitignored build artifact — nothing in a checkout carries it, and a merge never brings it along. … Generate it first: pnpm --filter @objectstack/spec gen:schema

And then, at :119:

  process.exit(1);

Exit 1 is the FINDINGS code. Exit 3 is the prerequisite code. ⇒ a run in which nothing below line 103 executed is reported in the one channel a machine reads as 「this gate looked and found something」.

Why this is not cosmetic

The exit code is machine-read, not decorative. scripts/pm/dispatch-gates.mjs classifies a recorded run by it (:14138 — 「recorded exit 3 … — PREREQUISITE NOT MET」, pinned again at :28301), and its own docblock at :128 names the failure directly: a run 「refused with PREREQUISITE NOT MET is indistinguishable there from one that」 actually ran.

⇒ the harm is a false MEASURED claim, which is the exact class the gate-record discipline exists to police. A seat or dev reconciling a gate battery records this gate as having run and failed; it did neither. The available repairs then all point the wrong way — someone looks for a finding that does not exist, or records a red where the honest record is NOT MEASURED.

The convention it deviates from is live, and its sibling next door follows it

Lit control, same directory, same staleness classpackages/spec/scripts/build-api-surface.ts:87-91:

// PREREQUISITE NOT MET, not a finding (#19227): nothing below this line ran,
// so the code says so rather than borrowing the one a real breaking-change
// report uses.
…
process.exit(EXIT_PREREQUISITE_NOT_MET);

It imports that constant from ./lib/dist-freshness (:64), alongside prerequisiteNotMetText. packages/spec/scripts/lib/dist-freshness.ts:409 declares export const EXIT_PREREQUISITE_NOT_MET = 3 and :386 records that it 「argues the case at length」; :446 exports the message helper. check-skill-examples.ts:1480-1482 states the three-way distinction explicitly — 「fail() looked and found something, refuse() produced no result because the gate's own invariant broke. This one produced no result」 — and exits with the same constant at :1500.

⇒ the spelling exists, is documented, is used by siblings in the same directory, and was ruled on at #19227. build-docs.ts simply does not reach it.

Why it does not: build-docs.ts:27 imports its detector from a different helper — scripts/check-regen-pending.mjs's schemaTreeIsStale — rather than lib/dist-freshness's inspectDistFreshness. So it gets the detection without the exit-code convention that travels with the other one.

Remedy, in the ordered form

「先删容许出错的构造,再让正确形态成唯一拼写,最后才加检查」:

  1. Delete the construct that permits the error. The schemaTreeIsStale path lets a caller choose its own exit code; the lib/dist-freshness path does not, because the constant and the message text travel together. Route this refusal through the same helper the siblings use, or give schemaTreeIsStale's callers a refusal function that exits for them.
  2. Make the correct spelling the only one — every prerequisite refusal in packages/spec/scripts/** reaches EXIT_PREREQUISITE_NOT_MET, none spells its own number.
  3. Only then consider a check that no script under that tree exits 1 on a prerequisite path.

Not proposed: changing what the gate detects, softening the refusal (it is correctly fatal — the message says a stale tree would 「WRITE pages describing sources this run never read」), or touching check:docs's wiring.

⚠️ Reach of this reading, stated because it bounds the card

One file was measured, because a reviewer named one file. ⛔ No census of the family was taken. Specifically NOT measured: whether other callers of scripts/check-regen-pending.mjs's schemaTreeIsStale carry the same shape, and whether any other script under packages/spec/scripts/** or scripts/** spells a prerequisite refusal as exit 1. Whoever takes this card should census that first — the fix is the same edit either way, and the count decides whether step 3 above is worth its cost.

Duplicate-search words

PREREQUISITE NOT MET, EXIT_PREREQUISITE_NOT_MET, build-docs.ts exit, schemaTreeIsStale, #19227


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions