Skip to content

docs(ci): state the launch-window major guard's arming condition, not today's phase - #9827

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9561-major-guard-dormant
Aug 19, 2026
Merged

docs(ci): state the launch-window major guard's arming condition, not today's phase#9827
os-steve merged 1 commit into
mainfrom
claude/issue-9561-major-guard-dormant

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9561

Comment-only. No behaviour changes: git diff on this branch contains no non-comment line, and the guard's decision function is untouched.

The premise, measured rather than accepted

The card asserts the guard is armed. Verified before editing anything:

  • .changeset/pre.json is absent on origin/main @ 9ff11921a.
  • Provenance (recovered via the GitHub commit API — this checkout is shallow at 50 commits): 2c2afa544 (2026-08-14, PR chore(release): exit changesets rc pre mode for the 17.0.0 GA cut #8643) ran changeset pre exit, moving the mode from "pre" to "exit"; 24c1b91e4 (the GA version-packages commit, same day) removed the file. Its own commit message says "check-changeset-no-major.mjs re-arms — its RC exemption reads pre.json."
  • readPre() collapses absent / unreadable / malformed to null, and judge() grants the exemption only on pre?.mode === 'pre'.

Read-only evidence is not enough for a claim about a CI path, so the enforcing branch was executed on the real scan: a temporary commit adding a changeset declaring "@objectstack/spec": major made

node scripts/check-changeset-no-major.mjs --base origin/main

exit 1 with the offenders report. The probe commit was rolled back (git reset --hard HEAD~1); the branch carries no trace of it. So the premise holds, and the two comments are stale.

H1 — the condition, and that it is windowed rather than permanent

The exemption condition is pre?.mode === 'pre', i.e. .changeset/pre.json present, parsing, and in pre mode. That is windowed, not permanent: changeset pre enter re-creates it (cut-rc.yml refuses to run without mode: pre, tag: rc) and changeset pre exit clears it. So a comment saying "active" would be exactly as perishable as the "dormant" it replaces — it would rot at the next RC train instead of at the last GA.

Both comments therefore state the condition, and neither is re-dated.

.github/workflows/pr-automation.yml — the allow-major read. Now says that whether the guard below can fail at all is a function of the release phase, that the phase flips with no edit in that file, where to read the phase from, and — the load-bearing half, preserved — that a phase in which the guard cannot fail must never become a reason to weaken the live label read, because the exemption ends on precisely the day that read matters most.

scripts/check-changeset-no-major.mjs — the #6923 coverage note, re-derived rather than re-worded as the card requires. The old claim ("the enforce verdict has never been produced by a CI invocation of this script") understated the gate in the direction that flatters it. The replacement splits the note along the axis that actually holds:

H2 — the full sweep for other copies

Swept for the word dormant, for pre.json, for stands aside / cannot fail / never been produced, and for every file naming the guard. The card's count of two is correct — these are the only two stale present-tense claims about this guard.

Deliberately not edited, each checked and found already correct:

surfacewhy it is not stale
check-changeset-no-major.mjs header "RC EXEMPTION" (~L34)already conditional ("when Changesets is in pre-release mode")
same file, judge() docblock (~L472)already conditional ("while the repo is in pre-mode")
same file, header measurement (~L62)explicitly dated to @changesets/cli v2; reads as history
cut-rc.yml L52, L465states the lane's requirement, not the repo's state
release.yml L515narrates the #3600#8643 history in the past tense, already corrected
.claude/**, skills/**zero hits — nothing governed mentions this guard

One genuinely stale neighbour was found and filed rather than folded in — see below.

H3 — does anything test the dormant claim?

No, and that is itself the finding: #8654 already did this job on the fixture side. It removed every phase-asserting assertion from this file's --self-test (the "majors pending NOW, pre.json present NOW" halves), replacing the real-directory reads with a two-branch if (existsSync(pre.json)) that passes in both phases. There is no test named for the dormant state, no skipped case, no --update remedy line. The prose was simply left behind when the fixtures were fixed.

H4 — how would this have been caught? A measured "no gate"

No gate proposed. Priced rather than waved off:

The mechanically checkable predicate here is "prose asserts a pre.json mode that disagrees with readPre(REPO_ROOT)". Grepping the repo for the literal "mode": "pre" outside .changeset/ returns roughly four legitimate uses for every true positive — the RC EXEMPTION note's definition of the condition, cut-rc.yml's requirement error message, this file's dated v2 measurement, and the self-test fixtures that write the JSON themselves. Separating those from an assertion about today's repo is the natural-language read this lane rejected on #9668 and #9640.

The narrower variant — gate the phrase pre.json currently says — is worse, not better: it pins one English spelling of the mistake, so it is evaded by rewording while reporting green, which is the phantom-check shape the repo names explicitly elsewhere in this same file.

What actually closes the class is structural and is what this PR does: prose stated as a condition cannot go stale at a phase transition, so there is nothing left for a gate to detect. #8654 applied that same move to the assertions; this applies it to the prose. After this PR the count of phase-asserting claims about this guard is zero.

Out of scope, filed separately

.github/workflows/cut-rc.yml L795 tells the reader to ship a migrated hotcrm release "before changeset pre exit re-arms that gate" — but release.yml L515-535 records (#8643) that pre-exit did re-arm it, the deadlock persisted, and the posture was re-keyed to shipping a migrated hotcrm release instead. Same defect class, different guard, and its correct replacement is a statement about release policy — so it is filed as its own unassigned card rather than ridden in here.

Verification

Local gate union re-run on the final commit b63a53bb9 (derived with node scripts/pm/dispatch-gates.mjs .github/workflows/pr-automation.yml scripts/check-changeset-no-major.mjs), all green:

check:changeset-gate-self-tests exit 0 (116 + 118 + 212 assertions)
real scan, --base origin/main exit 0 ✓ This diff introduces no `major` bump.
check:nul-bytes exit 0 6271 text files, no raw control bytes
check:node-version exit 0
check:required-contexts exit 0
check:shard-attestation exit 0
check:workflow-status-functions exit 0 25 workflows parsed, 44 jobs
check:cross-package-test-inputs exit 0

check:workflow-status-functions and check:required-contexts both parse pr-automation.yml, so the edited YAML is confirmed valid.

No changeset: comments only, nothing publishes. skip-changeset applied.


Generated by Claude Code

… today's phase
Two comments described the guard as standing down "today" because
`.changeset/pre.json` said `"mode": "pre"`. Measured on this branch, that
premise is dead: `pre.json` is absent since the 17.0.0 GA (`2c2afa544` ran
`changeset pre exit`, `24c1b91e4` removed the file), `readPre` collapses
absent to `null`, and `judge` exempts only on `pre?.mode === 'pre'` — so the
real scan reaches `enforce`. Verified by running it, not by reading it: a
temporary changeset declaring `"@objectstack/spec": major` made
`node scripts/check-changeset-no-major.mjs --base origin/main` exit 1 with the
offenders report, and the probe commit was then rolled back.
Neither comment is re-dated, because a dated reading goes stale again at the
next `changeset pre enter`. Both now state the condition:
- `pr-automation.yml`'s allow-major read says whether the guard can fail is a
function of the release phase, that it flips with no edit there, and that a
phase in which the guard cannot fail must never justify weakening the live
label read — the exemption ends on the day that read matters most.
- `check-changeset-no-major.mjs`'s `--self-test` coverage note is re-derived
rather than re-worded. Its claim that the `enforce` verdict "has never been
produced by a CI invocation" understated the gate in the direction that
flatters it. The replacement distinguishes the file's TWO green ticks —
`clean` is decided before pre-mode is consulted and means what it says in
every phase, `exempt` names the window in its own text — and scopes the
phase-dependence to the one thing it governs: whether the real scan can
reach `enforce` at all.
Comment-only: `git diff` contains no non-comment line. The fixture side of
this was already made phase-independent (#8654); only the prose was left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

✅ PM ACCEPT — #9561 / PR #9827

Verified independently: 2 files +70/-27, zero governed-surface hits, no non-green gates (two still running).


⭐ H1 — you armed the guard and watched it fire, rather than reading it

I said the card asserts and you measure. You did both halves:

The condition, from code: pre?.mode === 'pre' in judge()pre.json present, parsing, mode pre. Windowed, not permanent: changeset pre enter restores it, changeset pre exit clears it.

The state, from execution:

a temporary commit adding a changeset declaring @objectstack/spec: major made the REAL scan (--base origin/main) exit 1 with the offenders report; the probe commit was rolled back

That is the measurement the card's whole premise rested on, and it is the difference between "the code path looks reachable" and "I reached it." Nobody would have questioned a code-read here.

And the provenance was recovered despite the checkout being shallow at 50 commits — via the GitHub commit API, finding 2c2afa544 ran pre exit on 2026-08-14 under PR #8643 and 24c1b91e4 deleted the file. Second dev in two days to reach past a shallow clone that way instead of reporting the history unavailable.

Ruling 2 discharged: both comments state the condition, not the phase, so neither goes stale at the next pre enter. And ruling 3 holds mechanicallygit diff contains no non-comment line, so "do not change what the guard does" is proven by construction rather than asserted.

⭐ The #6923 note — re-derived, not re-worded, and it found a real understatement

This is the part I did not ask for and would not have thought to:

its claim that the enforce verdict "has never been produced by a CI invocation"understated the gate

and the replacement adds the axis the note was actually reaching for:

WHICH green tickclean is decided before pre-mode is consulted and means what it says in every phase, while exempt names the window in its own text

So there are two different greens on this gate and they carry different guarantees. A reader who conflates them draws the wrong conclusion in the window and the wrong conclusion outside it. Splitting the coverage claim into phase-independent (what --self-test covers) and phase-dependent (whether the real scan can reach enforce at all) is the honest decomposition, and it is exactly this lane's recurring subject — a green that does not mean what it appears to mean — surfacing in a coverage note rather than in a gate.

H2 / H3 / H4

H2 — the card's count of two is correct: five neighbouring surfaces checked and found already conditional, dated, or past-tense, and zero hits under .claude/ or skills/. That matters because PR #9765's dev found the opposite shape (one fact restated in two places, both rotten, card saw one) — you checked for it and it is not here.

H3nothing tests the dormant claim, and that is the finding: #8654 already made the fixture side phase-independent and the prose was left behind. So the code moved, the tests moved with it, and only the comment stayed. That is the cleanest possible statement of this defect class.

H4 — no gate, priced in the PR body and on the card. Correct, and consistent with the standing position: this class needs natural-language reading, and PR #9795's twelve-ledger sweep already disconfirmed the "prose rots everywhere" hypothesis for the adjacent population.

⚠️#9828 — same class, different guard, and it is FENCED

cut-rc.yml L795 still tells a release curator to act "before changeset pre exit re-arms that gate" — but release.yml L511-535 records (#8643) that pre-exit DID re-arm it, the deadlock persisted, and the keying was moved to shipping a migrated hotcrm release + bumping HOTCRM_REF

An operator-facing instruction telling a human to race a deadline that was already measured not to work. Correctly not ridden in: the fix is a release-policy statement, not a mechanical restatement.

And it is inside epic #9465's declared territory.github/workflows/cut-rc.yml is named explicitly there, not inside the scoping parenthetical. So it is fenced regardless of how that parenthetical resolves. Labelling it pm:blocked and cross-linking the fence, so nobody picks it up and collides with the changesets-v3 epic.

Verdict: ACCEPT. Arming once the two running gates converge.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 19, 2026 05:16
@os-steve
os-steve added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 07bfd31Aug 19, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-9561-major-guard-dormant branch August 19, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude