Skip to content

fix(gates): move post-cut-fragile live-repo assertions onto synthetic controls - #8670

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8654-post-cut-gate-selftests
Aug 14, 2026
Merged

fix(gates): move post-cut-fragile live-repo assertions onto synthetic controls#8670
os-zhuang merged 1 commit into
mainfrom
claude/issue-8654-post-cut-gate-selftests

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8654
Fixes#8658

The contract decision this PR implements

The self-test / input-assertion controls in these two gates exist to catch checker rot (a synthetic-only green, a rotted detector) — NOT to assert the repo is mid-cycle. The pre-change code conflated the two, exactly as both cards name: it asserted properties of the live repository (major-declaring stock present, .changeset/pre.json present, breaking-declaring stock present) that a release cut legitimately falsifies, so every PR in the post-cut window went red on a required check while the assertions verified nothing about either checker. Every live-repo assertion now runs on synthetic temp-repo/fixture controls that run on every invocation regardless of repo phase; the assertions of repo phase are removed deliberately. No loud-skip path was added (rejected in the ruling: a synthetic control that always runs is strictly better than a skip that must be noticed), and no diff-judging behavior of either gate changed.

Maintainer authorization, quoted verbatim from the claim comment on #8654 (maintainer directive in the live PM session, 2026-08-14, followed by explicit ratification of the repair direction, "按建议修"):

「当前处理的任务要跟踪到合并啊」

The recorded ruling: move the live-repo assertions onto synthetic temp-repo fixtures, dropping no guarantee — the #4690 "absence is never a pass" and #7005 anti-vacuity controls are preserved by synthetic positive controls that always run; assertions that the repo is mid-cycle are removed as not the checker's contract.

What changed

scripts/check-changeset-no-major.mjs (selfTest())

  • The real-stock check-changeset-no-major.mjs judges the whole .changeset stock, not the PR's diff — at changeset pre exit it reds every unrelated PR (171 files measured) #7005 block is gone. judge() takes only { introduced, pre } — it never reads the stock — so the one guarantee that needed a major-declaring stock was the report-scope negative ("the report names ONLY what this diff introduced, never a pending stock file"). That negative now lives in the temp-repo STOCK block, where the synthetic stock really declares majors, really sits on disk at the branch point, and is read by the real scan() on every run — with its own anti-vacuity control (the synthetic stock is asserted to contain majors).
  • The pre.json reader assertion is now no-third-state: IF .changeset/pre.json exists it must parse; absent is a legal post-GA terminal state (absent ⇒ null ⇒ no exemption was already pinned in the temp-dir block). Present-but-unparsable is the one shape that still fails — readPre collapses it to a silently dropped exemption nobody decided.
  • The "real directory is non-empty" reader assertion (the transient third failure measured on the card) was the same phase class one notch down; it is replaced by a populated-directory control on the temp dir the test populates itself. Reachability of the real directory (tracked README/config, phase-independent) is still asserted.
  • Every temp-dir and temp-repo control that already existed is unchanged.

scripts/check-adr-0087-registration.mjs (assertInputs())

  • Assertion (4) CONVENTION ROT no longer reads the live stock. It drives a fixed set of synthetic control texts through breakingDeclaration() on every invocation: five must-match spellings (one per documented signal — major bump, quoted major with trailing YAML comment per the A trailing YAML comment hides a bump entry from all three changeset frontmatter parsers #7004 dialects, **BREAKING** marker, BREAKING CHANGE: line, conventional-commit ! summary) and two must-NOT-match shapes. Rot in either direction refuses a verdict, in every repo phase.
  • Assertion (1) now distinguishes an EMPTY stock (zero pending changesets — the legitimate state of main right after version packages; the gate's subject is the diff, so it is judged, not refused) from a MISSING/unlistable .changeset/ directory (not even the tracked README/config found — still a check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 refusal, kept deliberately).
  • Self-test: the old I2 ("stock with nothing breaking must be RED") is flipped to green with the reason recorded; new cases pin the post-cut empty-stock state green end-to-end (I2b, through the real scan()), the missing-directory state red (I2c), and the mid-cycle state unaffected (I2d). 206 assertions, up from 202.

Verification (all readings at head a50c016dc, the only commit; union re-run after it)

Red before — pristine origin/main (189a73225), measured in this worktree before any edit:

  • node scripts/check-changeset-no-major.mjs --self-test → exit 1, the exact two failures on the card (control (#7005) + reader: pre.json).
  • node scripts/check-adr-0087-registration.mjs --base origin/main → exit 1, not one of 2 changeset(s) in stock matches the breaking-change detector.

Green after, at a50c016dc:

  • pnpm check:changeset-gate-self-tests → exit 0 (118 + 206 + 116 assertions).
  • node scripts/check-changeset-no-major.mjs --base origin/main → exit 0; node scripts/check-adr-0087-registration.mjs --base origin/main → exit 0 on the post-cut stock (2 changesets, none breaking, diff introduces none). The repair PR heals its own CI.
  • node scripts/check-nul-bytes.mjs → exit 0; npx eslint --no-inline-config on both files → exit 0.
  • Gate derivation re-run: node scripts/pm/dispatch-gates.mjs on the two changed paths names exactly the three families above (composite self-tests + the two real-scan call sites in pr-automation/cut-rc) — all run, all green; no family beyond the dispatch prompt's list surfaced.

Reverse verification of the rot controls (prediction stated first, each restore verified byte-identical via git hash-object against the committed blob):

  • A1breakingDeclaration() rotted to never-match → predicted RED; measured: real gate refuses, exit 1, all 5 must-match controls named.
  • A2 only the major signal rotted ('majorx') → predicted RED with exactly the 2 major-spelling controls; measured: exactly those 2.
  • A3 detector rotted to match everything → predicted RED with the 2 must-not-match controls; measured: exactly those 2.
  • B the no-major bump parser rotted ('majorx') → predicted self-test RED including the new synthetic-stock anti-vacuity control; measured: exit 1, 38 failures, that control among them.

All exit-status judged, never tail output (the prose-failure trap recorded on #8654).

Scope notes


Generated by Claude Code

… controls (#8654, #8658)
The self-test of check-changeset-no-major.mjs and the input assertions of
check-adr-0087-registration.mjs each asserted properties of the LIVE repo
(major-declaring stock present, pre.json present, breaking-declaring stock
present) that a release cut legitimately falsifies, conflating 'the checker
has not rotted' with 'the repo is mid-cycle'. Per the maintainer ruling on
the two cards, every such assertion now runs on synthetic temp-repo/fixture
controls that run on every invocation regardless of repo phase; assertions
of repo phase are removed deliberately. The #4690 posture (unreadable input
is a refusal, never a pass) and the #7005 anti-vacuity guarantee are both
preserved on the synthetic controls.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5tUwGM3LQoqErTfkvRW7W
@vercel

vercelBot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 14, 2026 11:17am

Request Review

@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 14, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 14, 2026 11:23
@os-zhuang
os-zhuang enabled auto-merge August 14, 2026 11:23
@os-zhuang
os-zhuang added this pull request to the merge queueAug 14, 2026
Merged via the queue into main with commit 247c55aAug 14, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8654-post-cut-gate-selftests branch August 14, 2026 11:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants

@os-zhuang@claude