Skip to content

fix(devx): stop check:published-files spelling a bare repo root it never opens - #10890

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10875-published-files-bare-root
Aug 21, 2026
Merged

fix(devx): stop check:published-files spelling a bare repo root it never opens#10890
os-zhuang merged 1 commit into
mainfrom
claude/issue-10875-published-files-bare-root

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#10875

check:published-files reached the literal scripts through a package-relative predicate over would-be tarball contents, not through the repo root it appeared to name. Verified in source:

test: (rel)=>rel.startsWith('scripts/'),// rel is PACKAGE-relative

The dispatch derivation read that literal as a declaration of the repo's own scripts/ tree, and hintCovers refuses a bare top-level word as too generic — so the gate was unnameable for every card while appearing to name a root it never opens. It was the last row of ESCAPABLE_LITERAL_LEDGER.

The remedy, and the one that was refused

Respell the predicate as an anchored regex — the idiom the file's three other FORBIDDEN entries already use. Behaviour is identical; the row discharges by construction rather than by declaration, and the ledger empties.

⛔ The ROOT_DIR_WATCH_HINTS escape (['scripts/**']) is the wrong remedy here. It is the idiom, it would have made the ledger green, and it would have been a false declaration. Measured on this tree:

pairs
the bare scripts literal ever contributed0
scripts/** would have added, to this one gate+226

The bare literal was already inert for matching, so removing it changes zero match results tree-wide (50259 → 50259 pairs over 131 families × 6300 files). It only ever bought the ledger row. The forbidden remedy would have bought 226 fabricated leads — the currency hintCovers' refusal is priced in.

hintCovers:920 was not loosened.

The verdict is correct, not merely quiet

Both directions are pinned against the live tree (check:pm-dispatch-gates), because once a ledger row is out the ledger says nothing at all:

  • a brand-new repo-root scripts/ file does not name this gate;
  • …and it is not silent everywhere — scripts/check-published-files.mjs, a population it really does have, still resolves matched.

The second is what separates a correct verdict from a dead one, and it is what the first case's non-vacuity rests on.

The ledger's #4690 guard had to be re-anchored

the live sweep still reaches this species at all was ledgerRows.length > 0 — a live-tree count that read the right rule off the wrong quantity. It conflated two separable claims: "the recognizer works" and "the tree still owes a row". While a debt existed they moved together, so the conflation was invisible. Paying the last row is what pulled them apart, leaving a SHRINK-ONLY ledger that could not be allowed to reach zero — the one end state it exists to reach.

A probe family spelling a bare root is now spliced into the live corpus and must be recognized. That holds at zero rows and at any other count, and unlike the fixture cases it exercises the live prefix set.

Two further cases were kept from going vacuous as the ledger emptied — the shape #10784's ablation caught one case over, where silent === silent read as a pass.

Ablations — prediction stated before each run

ablationpredictedobserved
re-add a ledger row (stale direction)11 — and no ledger row is stale, naming the row
revert the gate spelling (fresh direction)11 — no gate has NEWLY joined the escapable-literal species
remove the probe splice (is the new guard real?)22 — the probe guard + every ledger row names a root the tree HAS
apply the forbidden scripts/**11 — …a brand-new repo-root scripts/ file does NOT name it

One prediction missed and is reported as a defect in the test, not noise: killing trackedPrefixes globally was predicted at 2 and observed 7. trackedPrefixes is a shared helper and 5 reds belonged to the residue/reachability sweep, which I had failed to include in the blast radius. Both target cases did red; the ablation was simply too broad to isolate the new guard, so it was re-run in isolated form (row 3 above).

Note the fourth row: FRESH stayed green under the forbidden remedy, because a subtree declaration is not a bare root. That is exactly the failure the ledger alone could never have caught, and why it is pinned separately.

⚠️ An empty ledger is not a closed species

This list only ever saw the half the derivation can see — a literal that reaches the hint set, which needs a separator in the source spelling. #10840 measures the other half at ~33 gates whose population literal carries no separator at all. Those are invisible to the derivation and to this ledger, and emptying this list moves none of them. The ledger's own comment now says so, so nobody reads an empty list as "done".

Verification

Head 825d27a872. Gates derived with node scripts/pm/dispatch-gates.mjs (no paths — it reads the merge-base changeset itself), all 7 run plus check:nul-bytes, every one exit 0:

✓ dispatch-gates self-test: 506 cases pass.
✓ check:published-files — 69 publishable package(s) of 77 workspace member(s) …
✓ check:entry-guard: 129 scripts/ file(s) …
✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
check-nul-bytes: OK (scanned 6296 text file(s) … no raw ASCII control bytes).

check:published-files' live verdict is unchanged from base (69 publishable packages, same registered-reason count), which is the behaviour-preservation half.

check:entry-guard / check:parse-guard were run by hand as instructed — though both are now derived automatically via #10873's scripts/** declaration, so that dispatch note is stale.

Rebased onto origin/main after four sibling PRs landed mid-task; the diff is exactly two files.

skip-changeset: scripts/-only internal CI tooling, releases nothing.


Generated by Claude Code

…ver opens
`check:published-files` reached the literal `scripts` through a
package-relative predicate over would-be tarball contents
(`rel.startsWith('scripts/')`), not through the repo root of the same
name. The dispatch derivation read that literal as a declaration of the
repo's own `scripts/` tree, and `hintCovers` refuses a bare top-level
word as too generic — so the gate was unnameable for every card while
appearing to name a root it never opens. It was the last row of
`ESCAPABLE_LITERAL_LEDGER`.
Respell the predicate as an anchored regex, the idiom the file's three
other FORBIDDEN entries already use. The row then discharges by
CONSTRUCTION rather than by declaration, and the ledger empties.
The ROOT_DIR_WATCH_HINTS escape — declaring `scripts/**` — is the wrong
remedy for this gate: it would be a false declaration, and would name
the gate for all 226 repo-root scripts/ files it does not read. Pinned
against, since the ledger cannot say so once its row is out.
Re-anchor the ledger's #4690 anti-vacuity guard. It was
`ledgerRows.length > 0`, a live-tree count that conflated "the
recognizer works" with "the tree still owes a row"; paying the last row
pulled those apart and left a shrink-only ledger that could not reach
zero. A probe family spelling a bare root is now spliced into the live
corpus and must be recognized, which holds at zero rows and at any
other count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 16:11
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 2695a8cAug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10875-published-files-bare-root branch August 21, 2026 16:23
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32501648886 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.93s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 32 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

os-zhuang pushed a commit that referenced this pull request Aug 21, 2026
…pecies
A gate whose population literal carries no separator builds no watch hint at
all -- `looksPathy` drops it before `hintCovers` is consulted -- so it is
unnameable by any dispatch brief and leaves no residue saying so. That half is
invisible to the derivation AND to ESCAPABLE_LITERAL_LEDGER, which #10890's
comment says in the source and names #10840 for.
Adds scripts/pm/bare-root-worklist.mjs: the sweep, derived at runtime from the
derivation's own exported predicates, plus one recorded verdict per row. Its
self-test pins the triage to the tree in both directions -- a verdict cannot
outlive its row, and a new row cannot land unjudged -- and proves the
recogniser both speaks and discriminates.
Lands three per-gate declarations, the only rows clearing precision >= 85% and
blast radius <= 300 files:
check:driver-conformance packages/drivers/** 259/291 (89%)
check:skill-frame-sync skills/** 48/50 (96%)
check:skill-compatibility skills/** 49/50 (98%)
Each pins the coupling in its own self-test, derived from the population
constant rather than re-spelled, and pins its REFUSALS by name too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
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