Uh oh!
There was an error while loading. Please reload this page.
fix(devx): guard the last two run-on-import gate scripts — KNOWN_IMPORT_UNSAFE 2 to 0 - #14593
Conversation
`check-changeset-no-major.mjs` and `check-empty-changeset.mjs` were the final two entries in the SHRINK-ONLY `KNOWN_IMPORT_UNSAFE` ledger, held back until the changesets-v3 epic released their file territory. Both take the inverted 4-line insert, so every existing dispatch branch keeps its indentation. Measured on this tree before the guards: - importing `check-changeset-no-major.mjs` for its exports ran the whole gate inside the importer and then `main()`'s trailing `process.exit(exitCode)` ended that process mid-import carrying status 0. The probe never reached the statement after its own `import()`, so a caller reading the status alone cannot tell it from a clean import. - importing `check-empty-changeset.mjs` ran the gate and wrote its verdict to the importer's stdout before returning the exports. After: both probes return their exports silently, exit 0, no foreign output. All three CLI modes (default, `--self-test`, `--list`) are byte-identical before and after on the same tree. Nothing in the repo imports either file -- every reference in `.github/**`, `package.json` and `scripts/**` spawns them as `node scripts/...` -- so neither guard silences a census. That is the sitting-3 `ts-parse.mjs` inversion check, applied and cleared. The ledger entries were deleted only after `check:entry-guard` named both STALE. The set is now empty and its SHRINK-ONLY doc comment is untouched: that comment is what keeps an empty set empty. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…try-guard-ledger-sitting-4
The ablation strips check:entry-guard's declared subtree hint and asserts the brand-new-file verdict falls back. Its claim is that the subtree declaration is the load-bearing half -- i.e. that without it the file is NOT MATCHED -- but it spelled that as exactly `'silent'`. `'silent'` was only ever the residual because check-entry-guard.mjs's KNOWN_IMPORT_UNSAFE roster still contributed two path literals as hints. That ledger is SHRINK-ONLY and reaching zero is its GOAL, so the sitting that emptied it left the stripped hint set bare, the residual became `'undetermined'`, and this case went red over a gate that had not changed at all. The pin was calibrated against a debt ledger designed to disappear. Both verdicts are not-matched and either one proves the subtree hint is load-bearing, so both are accepted -- spelled as an explicit pair rather than `!== 'matched'`, so a NEW verdict value added later cannot slip through as a pass. The vacuity guard (`undeclared.hints.length < entry.hints.length`) is untouched, the case label now says NOT MATCHED rather than 'silent', and the residual verdict is carried into the failure detail so a future red names what it actually got. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33655524730 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10667
Sitting 4, the final sitting of the
KNOWN_IMPORT_UNSAFEburn-down. The ledger held exactly the two files the changesets-v3 epic #9465 had fenced; that epic has since completed, releasing them.Ledger: 2 to 0. Sittings 1-4 across the whole card: 35 to 10 to 5 to 2 to 0.
File surface — four files, the fourth authorized
Three files are the card's own surface. The fourth,
scripts/pm/dispatch-gates.mjs, carries a one-pin fix that emptying the ledger makes unavoidable. It was escalated rather than taken, and authorized by the PM decision on the card — comment 5511119817, which widens the surface "for exactly that one pin". See "The pin the empty ledger broke" below.git diff --stat origin/main...HEAD:Both gate scripts had one offending top-level statement each — a trailing
--self-test/--list/maindispatch chain — so both took the inverted 4-line insert. Zero re-indentation: the only line touched in each chain is its head, which becomes} else if (...) {.scripts/check-entry-guard.mjsis touched only to delete the two ledger lines; the set becomesnew Set([]). Its SHRINK-ONLY doc comment is untouched by design — the sentence "a file this rule newly reaches is a failure with one remedy, never a line in here" is exactly what keeps an empty set empty, and the:800self-test assertions over the roster hold on an empty set (verified: 54 cases green).The inversion check (sitting 3's
ts-parse.mjslesson), appliedBefore guarding anything, the question sitting 3 raised: does any top-level statement here have to run for an importer?
No — nothing in the repo imports either file. Every reference is a spawn or prose:
.github/workflows/pr-automation.yml(:766-767,:942),cut-rc.yml(:482-483,:492-493), andpackage.json(check:empty-changeset,check:changeset-gate-self-tests) — allnode scripts/..., a child process, unaffected by an import guard.check-adr-0087-registration.mjs,objectui-changeset-digest.mjs,check-required-contexts.mjs,objectui-range.mjs,pm/dispatch-gates.mjs,check-entry-guard.mjs.import/import()of either module returns nothing.So the only statement moved behind
isEntrypointis CLI dispatch, and neither guard silences a census. This is the check that would have caughtts-parse.mjs; here it clears.What the guards actually fixed — measured, both directions
Import probe:
node -e "import('./scripts/NAME.mjs').then(m => console.log('SENTINEL exports:', Object.keys(m).join(',')))".check-changeset-no-major.mjsmain()'s trailingprocess.exit(exitCode)ended the importer mid-import carrying status 0 — SENTINEL never printed, probe exit 0SENTINEL exports: judge,majorPackagesIn,mergeBase,readChangesets,readPre,render,resolveCommit,scan; exit 0check-empty-changeset.mjsSENTINEL exports: declaredBumpsIn,mergeBase,scan; exit 0The first is the worst shape in this class, and the one sitting 2 flagged as most dangerous: a silent success is indistinguishable from a clean import to any caller reading the exit status alone.
Zero behaviour change when invoked directly
Re-proven on the final commit
d11a00a29as a same-tree ablation: capture guarded output, revert both scripts toorigin/main(pre-guard —isEntrypointcount 0/0 confirmed on disk), capture again, restore withgit checkout HEAD --, prove the restore (both blob hashes match HEAD,git diff HEADempty, guard count back to 3/3), thencmp.check-changeset-no-majorcheck-empty-changeset--self-test--listcheck:entry-guardafterBefore this PR that read
148 of them inert on import (2 known-unsafe). The two ledger lines were deleted only after the gate named them STALE — never in the same breath as the guard:The pin the empty ledger broke
Emptying the ledger turned one case in
scripts/pm/dispatch-gates.mjsred — the #10784 ablation, which stripscheck:entry-guard's declared subtree hint and asserts the brand-new-file verdict falls back:Measured through the tool's own exported
discoverFamilies/classifyEntry:The ablation was passing only because the ledger still contributed two path literals as hints. That ledger is SHRINK-ONLY and reaching zero is its goal, so the sitting that empties it leaves the stripped hint set bare, the residual becomes
undetermined, and the case reds over a gate that had not changed at all. The pin was calibrated against a debt ledger designed to disappear. Unreachable in sittings 1-3 — only the sitting that empties the set can hit it.The fix pins the claim, not the spelling. The ablation exists to prove the subtree declaration is the load-bearing half — i.e. that without it the file is not matched. Both
silentandundeterminedare not-matched, so both are accepted, spelled as the explicit pair['silent', 'undetermined'].includes(residual)rather than!== 'matched'so a new verdict value cannot slip through as a pass. The vacuity guardundeclared.hints.length < entry.hints.lengthis untouched. The case label now saysNOT MATCHEDinstead ofsilent, and the residual verdict is carried into the failure detail so a future red names what it actually got.The pin still discriminates — shown, not asserted
Two one-off mutations on the final commit, each proven on disk before running, each restored with
git checkout HEAD --and the restore proven by blob hash plus an emptygit diff HEAD:filter(() => true), subtree hint not stripped)check:entry-guardandcheck:parse-guard'matched'So the widened pin still fails when the subtree declaration is not what is doing the work, and still fails on a
matchedresidual. It was not loosened into a tautology.Gates
Union re-derived at the final commit
d11a00a29(clean working tree) withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands— provenance namesobjectstack-ai/objectstackatd11a00a29. The fourth file grew the union from 18 to 20 families:node scripts/check-self-test-wired.mjsandpnpm check:declared-population-liveare new, and neither was in the first derivation — re-deriving after the surface widened is what caught them. Exit codes captured by redirect before any pipe.19 RAN-PASS (exit 0):
check:agent-test-spelling·check:bash32-floor·check:changeset-gate-self-tests·check:cli-command-ids·check:cross-package-test-inputs·check:declared-population-live("158 of 202 famil(ies) declare a path population, and every one of them reaches this tree's 7985 tracked file(s)") ·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check:ratchet-remedy-authority·check:watch-hint-literal·check:pm-dispatch-gates("✓ dispatch-gates self-test: 1240 cases pass", gate exit 0 — run detached throughscripts/pm/os-verify-lock.sh, held 439s, waited 320s) ·check-changeset-no-major.mjs·check-ci-filter-parity.mjs·check-cross-package-test-inputs.mjs·check-empty-changeset.mjs·check-self-test-wired.mjs·check-shard-attestation.mjs·pm/bare-root-worklist.mjs --self-test("none stale, none missing, none contradicted").1 NOT MEASURED:
check-test-completeness.mjs, exit 3 — its own PREREQUISITE NOT MET branch (it grades a saved turbo test log and the family names it with no argument). Its text is explicit that this is not a red, and CI, which tees the log and passes the path, never reaches that branch.Changeset: none —
scripts/**only, no package publishes, so the PR carriesskip-changeset.Generated by Claude Code
Generated by Claude Code