fix(scripts): check-release-section-coverage no longer renders a could-not-read as a floor-mismatch verdict - #14160
Merged
Conversation
…ot-read as a floor verdict The self-test reads the sibling gate to pin the two scope floors equal. The path was repo-root-relative and the read sat inside a swallowing `catch`, so from any cwd but the repo root the read failed, the floor stayed null, and the case reported a content verdict about the OTHER gate — in the exact prose a real cross-gate floor drift produces. Exit 1 on a clean tree, at a commit that exits 0 from the repo root. Both halves, because either alone leaves the defect reachable: 1. The sibling is resolved from `import.meta.url`, so the read no longer depends on where the process was started. 2. The `catch` no longer feeds null into the comparison. An unreadable sibling, or one that no longer declares a floor, REFUSES: exit 3 in the shared PREREQUISITE NOT MET frame (check-test-completeness.mjs / import-prerequisite.mjs), before the first case runs. Self-test grows from 41 to 50 cases: the cwd-independence is observed from an unrelated working directory rather than argued, both refusal branches and the exit-code class are pinned, and two source-level pins with a shared negative control forbid the placeholder shape and a hardcoded exit code from coming back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
baozhoutao
marked this pull request as ready for review
September 1, 2026 09:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13954
scripts/check-release-section-coverage.mjs --self-testreads its sibling gate to pin the twoscope floors equal. The path was repo-root-relative and the read sat inside a swallowing
catch, so from any working directory but the repo root the read failed,siblingFloorstayednull, and the case reported a content verdict about the other gate — in the exact prose areal cross-gate floor drift produces. Exit 1 on a clean tree, at the same commit that exits 0
from the repo root.
Per the triage ruling on the card, both remedies land, and the diff is fenced to that one
file. The "do sibling gates share this shape" sweep is another card and is not folded in
here;
check-release-page-status.mjsis untouched.What changed
The sibling is resolved from
import.meta.url.SIBLING_GATEstays the repo-relativename every message prints (a reader is told where to look in the tree); a new
SIBLING_GATE_PATH, resolved from this file, is what the read actually opens. An absolutepath cannot depend on
process.cwd().import.meta.urlwas already load-bearing here (theentry guard at the bottom of the file), so this is the same seed used one step earlier.
The swallowing
catchis gone.readSiblingFloor()answers with either a floor or anunmet prerequisite — there is deliberately no third "no floor, carry on" value, because that
value is precisely what used to reach the comparison and come back out of it as a verdict.
Two branches refuse: an unreadable sibling, and a readable one that no longer declares a
floor to inherit. Both exit 3 in the shared PREREQUISITE NOT MET frame
(
check-test-completeness.mjs/import-prerequisite.mjs— same number, same clauses),printed before the first case runs. One clause is this gate's own and is the point of the
card: the refusal states in words that it is not the claim that the two gates floor at
different majors.
Exit codes are now named constants (
EXIT_OK/EXIT_FINDINGS/EXIT_PREREQUISITE_NOT_MET)rather than spelled inline, so the self-test pins the value each path actually returns. Only the
prerequisite branch gets a code of its own — a broken instrument is still this gate's own exit
1, because that is a measurement that was attempted over the corpus and came back impossible.
Self-test: 41 to 50 cases
The new cases pin behaviour and names, not a count:
scope/read— the sibling path is absolute and equalsresolve(REPO_ROOT, SIBLING_GATE).scope/cwd— the defect, observed rather than argued: the floor reads the same afterprocess.chdir()to an unrelated directory (cwd restored in afinally).scope/refusal(x5) — a throwing read is an unmet prerequisite carrying the reason; areadable sibling with no floor declaration is the same class; the refusal exits 3, distinct
from 1 and from 0; a floor that WAS read produces no refusal (a refusal that fires always is
not a refusal); the text says nothing was measured, says it is not a finding, denies the
floor-disagreement reading by name, and names both codes.
scope/verdict(x2) — two source-level pins with a shared negative control fixture: nocase in the battery can render the old could-not-read placeholder into a verdict again, and
the refusal interpolates its exit codes rather than spelling them. The control carries both
forbidden shapes so each pin is proven able to fail.
Evidence — both directions, exit codes captured with no pipe in between
Before, at the merge base, on a clean tree:
After, same tree, three working directories:
The loud prerequisite path, observed end to end in a scratch copy (the repo tree was never
mutated) — with a control that separates the fixture from the cause:
The mutation was confirmed on disk before each reading (0 occurrences of the old declaration, 1
of the renamed one), not inferred from the editor's exit code.
Ablation of remedy 1, in a throwaway copy: undoing only the
import.meta.urlresolution(back to the bare relative spelling) turns the copy from EXIT=0 to EXIT=1 with exactly the two
new cwd pins red —
scope/readandscope/cwd. So those pins can fail, and they fail on theregression they exist for.
Gates, all run at
d6fdbb5e(the head this PR opens with)Family derived from the actual diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(the script reads its own change set from the merge base), then runwith each exit code captured before any pipe:
check-test-completeness.mjs's 3 is NOT MEASURED, not a red: the derived family names itwith no argument and there is no local
turbo run testlog to grade. Its own header documentsthat branch as the one the family hands out. Reading it as a finding is the very confusion this
PR is about, one gate over.
The two convention-triggered obligations for editing a gate script both pass:
bare-root-worklist.mjs --self-testandcheck:pm-dispatch-gates. The edited script's own testsuite is its
--self-test(green, 50 cases); no*.test.tsin the tree names this script.Is the fix CI-observable? No, and deliberately no rider
Every invocation of this gate runs from the repo root:
lint.yml(Release section-coverageguard),
release-coverage-patrol.yml(Sweep the corpus) andrelease.yml(run_gate). None ofthe three workflows carries a
working-directory:key anywhere. So no CI job could have hit thefalse verdict, and none exercises the new path either. Adding a non-root invocation to a
workflow to make it observable would be a rider on this card, so it is reported here instead.
--self-testis only ever invoked inlint.yml, where an exit 3 would fail the step — correct:a pin that genuinely cannot be evaluated is not a pass.
The gate's non-self-test behaviour is unchanged: it does not read the sibling at all, and
exitCodeForstill answers only 0 or 1. The patrol reads that exit code as "did the instrumentread the corpus" and is unaffected.
Changeset
None, and
skip-changesetis applied instead. The diff is one repo-internal gate script: theroot package is
private, and every published package ships["dist", "README.md", "CHANGELOG.md"]only, so nothing here reaches an npm consumer and there is no package achangeset could honestly bump. Declared deviation: the dispatch brief's closed list for that
label names
scripts/pm/**rather thanscripts/*.mjs; this file is the same species(repo-internal tooling that publishes nothing) and the substantive criterion holds, so the label
is applied and the deviation is stated here rather than made silently.
What was deliberately not touched
SPEC_CHANGELOG,RELEASES_DIRandINDEX_PATHremain cwd-relative. They belong tomain(),which operates on the repo tree and, from a non-root cwd, dies loudly on an unhandled
ENOENTrather than rendering a verdict. That is a different class from the one this card names, and
changing it would alter the gate's behaviour beyond the fence.
Generated by Claude Code
Generated by Claude Code