Uh oh!
There was an error while loading. Please reload this page.
fix(devx): let a gate declare a scan surface, not just its baseline artifact - #10873
Conversation
…rtifact `dispatch-gates` never named `check:entry-guard` for a NEW `scripts/` file — the gate whose population is exactly that directory. Its only module-body literals were the ten `KNOWN_IMPORT_UNSAFE` entries, an enumeration of the files that already violate the import-safety half, so the declared population was a roster of current members and a newly added script could never be in it, by construction. The sibling `check:parse-guard` was invisible the opposite way: it declared the bare root `scripts`, which the covering rule refuses as too generic. Both gates now declare the scan surface with the existing `ROOT_DIR_WATCH_HINTS` idiom, keeping their artifacts as data. Declaring it discharged the `check:parse-guard scripts` row in `ESCAPABLE_LITERAL_LEDGER`, which failed as STALE by name; the row is deleted, the sanctioned shrink-only path. Also splits the `silent` verdict in the residue output: a family whose declared literals are all tracked FILES has named artifacts, not a population, and where that roster sits in a directory the card edits the verdict is not evidence in either direction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Measured on the ablation run: with both declarations removed there is no subtree hint to strip, so `silent === silent` read as a pass while the two cases above it went red. The length check makes the strip real. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
✅ ACCEPT — reviewer of record: |
| before | after | |
|---|---|---|
check:entry-guard for a new scripts/ file | grep count 0, filed under Silent | matched via … ⇢ gate source scripts/** |
check:parse-guard | Silent andUnreachable — dead: scripts | matched |
| matched / unreachable / silent | 3 / 3 / 86 | 5 / 2 / 84 |
| self-tests | entry-guard 47, parse-guard 41, dispatch-gates 474 | 52 / 46 / 501 |
Hand-run both blind gates before and after, exit 0 both times, verdict lines identical — the runtime behaviour did not move, only its visibility. That is the right shape for this card.
The two leads you did not file — both restraints correct
check:published-files scriptsis now the ledger's only row. You declined to file because one semantic search returned zero hits and the second was rate-limited ⇒ you would have been filing blind. Right call, and I am picking it up from here rather than leaving it to the next reader.- The 21 artifact-roster families — a derived list, and this repo's doctrine is not to copy derived lists into cards where they rot. Also right.
Recorded
The sanitizer ate <!-- os-dev-report --> again, even mid-body; the literal-text first line survived. That is now four occurrences today across four different agents — the literal form is the only spelling that works on this repo's issue comments.
Flipping ready and arming.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32497066540 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#10784
node scripts/pm/dispatch-gates.mjsnever namedcheck:entry-guardfor a newscripts/file — the gate whose population is exactly that directory. Its only module-body literals were the tenKNOWN_IMPORT_UNSAFEentries, the allowlist of files that already violate the import-safety half, so the declared population was an enumeration of the members that already exist and a newly addedscripts/*.mjscould never appear in it, by construction. At runtime the gate walks all 129 files there and judges new ones, so the derivation answered "silent" for exactly the input most likely to fire. The cost was already paid on #10703's CI round-trip, after the dev had reported.The sibling
check:parse-guardwas invisible the opposite way: its only literal was the bare rootscripts, whichhintCoversrefuses as too generic. Twoscripts/**gates, two different routes, and anyone adding a script got neither named.What changed
Triage option 1: a gate now declares a scan surface distinct from its baseline artifact, using the existing, copyable
ROOT_DIR_WATCH_HINTSidiom (check-role-word.mjs,check-examples-live-imports.mjs). No extractor was widened —hintCovers' refusal of a bare root word stays exactly as measured (+139084 fabricated pairs priced in its docblock).scripts/check-entry-guard.mjs— declaresscripts/**;KNOWN_IMPORT_UNSAFEstays data. Self-test derives both directions from the walked root and pins that the surface covers a file the roster can never contain.scripts/check-parse-guard.mjs— declaresscripts/**. ItswalkOutside(REPO_ROOT)census side is deliberately not declared (it cannot fail the gate; naming the repo root would put this gate in every card's brief), and that refusal is pinned rather than left in prose.scripts/pm/dispatch-gates.mjs— the discharged ledger row deleted, plus triage option 3.⭐ The ledger interlock, walked end to end
PR #10839's
ESCAPABLE_LITERAL_LEDGERis shrink-only and bidirectionally enforced, and one of its two seeded rows wascheck:parse-guard scripts— on this surface. Declaring the subtree made it stale, exactly as that PR's leg-C ablation recorded. Observed here, before touching the ledger:The row is deleted — the sanctioned path the failure text names. No row was added, and the ledger was not weakened: the ablation below shows its FRESH-row half still fires if the declaration is removed. One row remains (
check:published-files scripts), whose remedy is the other one the idiom allows and belongs on its own card.Option 3 — telling a weak silence from an inverted one
silentis the derivation's weakest claim, and the residue block already named two ways to earn it that have nothing to do with the caller's paths. This adds the third, as output rather than prose: a family whose declared literals are all tracked FILES has named artifacts — a baseline, or an allowlist of current members — not a population. Counted in the summary on every run, named with the discriminator under--residue.It deliberately stops short of claiming the gate reads your file. Both shapes are live and indistinguishable from the tree:
check:where-matchernames one baseline JSON underscripts/and walkspackages/**test files, whilecheck-entry-guardnamed ten files underscripts/and walked all of it. What it says instead is exactly true of both — a list of the files that already exist can never contain one added tomorrow, so the verdict is not evidence in either direction — and it hands over the discriminator. A pinned self-test case holds it to that.Proof — the before/after of whether the derivation NAMES these gates
Both measured against
origin/mainin a dedicated worktree, never the shared checkout. Card path: one newscripts/file.check:entry-guardUnreachable, from the tier lines; grep count 0 in the default output.--residuefiles it under Silentscripts/check-new-thing.mjs ⇢ gate source 'scripts/**'check:parse-guardnames: scripts) andUnreachable — dead: 'scripts' — the tree HAS it; the covering rule refuses the literal as too genericBefore, the whole of what the derivation said about
check:entry-guard:For this PR's own diff the tool now names itself correctly, including both gates for the two sibling files.
The gates run BY HAND, before and after
The derivation is structurally blind to these two, which is the card's whole subject, so neither run relies on it.
check:entry-guard✓ check:entry-guard: 129 scripts/ file(s) …exit 0check:parse-guard✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.exit 0check-entry-guard --self-testcheck:parse-guard --self-testdispatch-gates --self-testThe self-test cases that fail without this change
Ablation, run from the committed state: both
ROOT_DIR_WATCH_HINTSdeclarations replaced with[], confirmed on disk by marker count (injected 2, removed literal 0 — never by an editor's exit code), then restored withgit checkoutand confirmed byte-clean by an emptygit status.The derivation under ablation reproduced the base state exactly: 3 matched,
entry-guardgrep count 0,parse-guardonly inUnreachable.The FRESH-row line is the ledger's other direction still armed after the deletion — remove the fix and the ledger demands it back rather than accepting a re-added row.
One case first passed vacuously under ablation (with no subtree hint to strip,
silent === silentreads as a pass). It carries a length check now, and the second ablation run confirms it goes red — 3 failures became 5.Gates
Derived with
node scripts/pm/dispatch-gates.mjs(no paths — it reads its own merge-base change set), all run at2f61b2e878, each quoting the gate's own verdict line:pnpm check:entry-guard✓ check:entry-guard: 129 scripts/ file(s) …exit 0pnpm check:parse-guard✓ check:parse-guard: 128 scripts/ file(s) …exit 0pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 501 cases pass.exit 0pnpm check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared …exit 0node scripts/check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) (71 unique) …exit 0node scripts/check-cross-package-test-inputs.mjsOK: 13 package(s) read outside themselves …exit 0pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6284 text file(s) … no raw ASCII control bytes)exit 0Run under
scripts/pm/os-verify-lock.sh(VERDICT command-exit 0 · held the lock 20s · waited 76s), each exit code captured before any pipe.No changeset:
scripts/repo tooling, no published package changes — declared with theskip-changesetlabel.Serial
⛔ Not widened into #10840 (the ~33 gates whose population literal carries no separator at all). This PR adds no third idiom: it uses
ROOT_DIR_WATCH_HINTS, already carried by two gates and pinned in each one's self-test, which is what #10705 asks for. #10840 is left slightly cheaper — two more worked instances of the idiom, and the residue now counts and names the artifact-roster species out loud, which is a different population from #10840's but overlaps the reader's triage path.Generated by Claude Code