Skip to content

fix(devx): the docs-frontmatter self-test refuses an unreadable extractor with exit 3 PREREQUISITE NOT MET - #15940

Merged
os-project-manager merged 4 commits into
mainfrom
claude/issue-15835-doc-frontmatter-prerequisite-refusal
Sep 5, 2026
Merged

fix(devx): the docs-frontmatter self-test refuses an unreadable extractor with exit 3 PREREQUISITE NOT MET#15940
os-project-manager merged 4 commits into
mainfrom
claude/issue-15835-doc-frontmatter-prerequisite-refusal

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15835

scripts/check-doc-frontmatter.mjs --self-test answered an uninstalled apps/docs with exit 1 and, in its own words, the battery is the bug — a finding's exit code and a remedy that sends the reader hunting for a deleted case that was never deleted. Battery (7) resolves the docs build's own extractor from apps/docs; with the dependency absent its one resolvability assertion failed, its other nine cases never registered, and the floor then fired on top of that. This brings the gate onto the convention its siblings already hold: exit 3 PREREQUISITE NOT MET, raised through the shared frame in scripts/import-prerequisite.mjs.

Before / after, same tree, same command

Root installed, apps/docs not — the card's own repro:

base    node scripts/check-doc-frontmatter.mjs --self-test  -> EXIT=1
        "3 of 76 assertion(s) failed" + "the battery is the bug, not the number"
head    node scripts/check-doc-frontmatter.mjs --self-test  -> EXIT=3
        "check-doc-frontmatter: PREREQUISITE NOT MET — `fumadocs-core` is not installed for `apps/docs`"
        "Fix:  pnpm install" + "Nothing was measured" + "It is NOT a finding"

The misleading half is gone from what it prints: the battery is the bug, below its pinned floor and cases that used to run no longer do are all absent, and that absence is asserted, not just observed.

This is a diagnosis change, not a verdict change — measured, not assumed

Every state, both legs, base vs head, on one worktree:

tree leg base head reading
fully installed enforcing 0 0 unchanged
fully installed --self-test 0 (85 assertions) 0 (99 assertions) unchanged; 14 new cases
root installed, apps/docs absent enforcing 0 0 unchanged
root installed, apps/docs absent --self-test 1 3 both FAIL; only the diagnosis moves
nothing installed --self-test 3 (yaml) 3 (yaml) unchanged

⇒ No case passes that previously failed, in either direction. And the row that moves is not reachable in CI at all: the lint job runs pnpm install --frozen-lockfile over the whole workspace before it invokes this gate, so the guard is never entered there and CI's exit codes are byte-identical.

Where the refusal sits, and why

At the top of selfTest(), before the first case registers — the shape scripts/pm/check-governed-merges.mjs landed. The shared frame's clause "this gate exited before running a single check" is inherited verbatim by every importer, so a refusal raised beside battery (7), after six batteries had already judged, would make that sentence false in all of them at once. The price is named in the code: on an uninstalled local tree the other batteries no longer report their own findings. The run still fails either way.

What is a prerequisite, and what deliberately is not

Only the two on-disk facts, read with the frame's OWN probes (findPackageDir, entryPointOnDisk) rather than re-spelled here:

  • fumadocs-core is nowhere on the resolution path from apps/docspnpm install
  • it is there, but the entry point its own package.json declares is not → pnpm install

Everything else stays a finding at exit 1, judged by battery (7) as before: the package is whole and the subpath does not resolve (the build's extractor moved — exactly the drift battery (7) exists to catch), or it resolves and the import throws (a real defect in an installed dependency, which this gate has no standing to prescribe an install for). A manifest declaring no default entry is deferred, never guessed at.

What pins it

New battery (11), floor 14 — and what is pinned is the printed refusal, never the exit code alone. A run that exited 3 while still printing the battery is the bug would satisfy a code-only assertion and leave the reader exactly where the card found them.

  • the probe returns null for this checkout, so batteries (1)–(10) are observed unaffected;
  • and null for a constructed tree that HAS the extractor — the control that an always-refusing probe would fail;
  • absent tree / node_modules present without the package / package present without its entry / manifest with no declared entry — four constructed roots, four distinct verdicts;
  • end to end: a copied checkout with no apps/docs, spawned as a real process, asserted on the exit code and on the text (names the class, the prerequisite, the remedy, states that nothing was measured) and on the absence of the old diagnosis. The child is marked --fixture-child so it can never spawn one of its own.

scripts/import-prerequisite.mjs is untouched — this card calls that frame, it does not edit it. Merged origin/main after #15911 landed (14:49:24Z), so the refusal is raised through the fixed frame; with yaml met, no closure section is added, which is the designed behaviour.

Verification

Run on the final head 425f2929, exit codes captured before any pipe.

  • dispatch-gates --changed --commands: 32 families derived, 32 run, all exit 0; --ran reconciliation 0 NOT-MEASURED, 0 UNRUN.
  • pnpm lint (repo-wide eslint . --no-inline-config, no narrowing): exit 0.
  • check-governed-merges.mjs --test scripts/check-doc-frontmatter.mjs: NOT governed — ordinary queue landing.
  • Ablation: base blob restored to disk (hash-verified), re-measured, restored (git diff HEAD empty). The uninstalled row returns to exit 1 with the battery is the bug — the guard is load-bearing.

Changeset: none, skip-changeset applied. The diff is one CI-internal gate script and publishes nothing from any package — lint.yml's own note calls "this PR edits a CI-internal script" the textbook case, and the two most recent sibling PRs on this family (#15862, #15911) landed the same way.


Generated by Claude Code

…EQUISITE NOT MET frame

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtdMao3dQS9EfQfpyWixd
…ctor with exit 3 PREREQUISITE NOT MET

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtdMao3dQS9EfQfpyWixd
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added the size/m label Sep 5, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review September 5, 2026 15:52
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 904e707 Sep 5, 2026
36 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-15835-doc-frontmatter-prerequisite-refusal branch September 5, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants