Skip to content

feat(pm): bound the escapable-literal species with a shrink-only ledger - #10839

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10705-escapable-literal-ledger
Aug 21, 2026
Merged

feat(pm): bound the escapable-literal species with a shrink-only ledger#10839
os-zhuang merged 1 commit into
mainfrom
claude/issue-10705-escapable-literal-ledger

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#10705

A gate whose declared population is a bare top-level word the tree has is unnameable by
any dispatch derivation and, in check-plugin-teardown-shape.mjs' words, "lands already
invisible". hintCovers refuses a separator-less literal as too generic; that refusal is
measured at +139084 fabricated pairs and is untouched here. What was missing is any record
of which gates still need the escape.

Re-measured on today's main, not taken from the card

The card's 4-row table was measured at 47cd3ec1f8. Branched from a7ea3289eb, the residue
block reports 3 unreachable-by-construction families, not 4:

familyliteralspecies
check:driver-memory-census@objectstack/driver-memorygenuinely dead — never a repo path
check:parse-guardscriptsescapable — the tree has it
check-test-completeness.mjsthree @objectstack/* namesgenuinely dead

The check:examples-live-imports / examples row is discharged: #10314 landed at 10:29Z
and that gate now carries ROOT_DIR_WATCH_HINTS = ['examples/**']. The ledger reflects the
landed state rather than re-listing it.

The silent-bucket remainder, enumerated

The card asked for the escapable shape living inside the silent bucket (84 of 128 families),
"where the unbounded remainder is". Swept with the tool's own predicates, the class is 4
literals
, two of which the card never named:

familyliteralescaped?
check:examples-live-importsexamples✅ by examples/** (#10314)
check:pm-governed-mergesskills✅ by skills/**
check:parse-guardscriptsledger
check:published-filesscriptsledger

check:published-files is the row that forced a design decision. It names
scripts/check-published-files.mjs, which hintCovers accepts against the bare directory
scripts through its reverse-containment branch while covering no other file under that root.
Treating that as an escape would retire a row for a gate still unnameable for every card under
the root it appears to declare — so escape is tested as collapseHint(sibling) === plain,
the same population declared as a subtree, and nothing weaker.

What landed

  • discoverFamilies() lifted out of derive(). The ledger asks the same question the
    derivation asks, from the same implementation — a second discovery pass would let the two
    disagree, and a ledger enumerating a population no dispatch prompt is derived from is the
    drift this file refuses everywhere else. Output is byte-identical to pre-refactor.
  • escapableLiteralRows() — the species, stated as hintCovers' own two refusal conditions
    plus deepestTrackedPrefix(hint) === plain (which is exactly how unreachableReason already
    separates this species from the dead one).
  • ESCAPABLE_LITERAL_LEDGER — ⛔ shrink-only, mirroring KNOWN_IMPORT_UNSAFE (check-entry-guard: reject an exporting scripts/ file whose top level runs on import #10665): fresh
    and stale both fail, the one remedy is named in the failure text, and a new line is
    explicitly not the remedy.
  • 12 self-test cases. check:pm-dispatch-gates runs that self-test on every PR with no path
    filter, so a gate written tomorrow that spells a bare root word fails at authoring time
    instead of landing silent. That is the prospective half the six historical instances could
    not fix, since each was archaeology.

The :920 refusal is not touchedhintCovers is byte-identical to main, verified by
diffing the function body; the sole deleted line in this PR is derive's own signature, moved
by the refactor.

Reverse verification

Three legs, each confirming the mutation reached disk before the result was read (grep -c
on the edited text, both directions). No build step is involved: the tool is a .mjs run
from source, so there is no dist/ for a stale artifact to hide in.

legmutation (on-disk confirmation)observed
Aledger row deleted (1 → 0 occurrences)no gate has NEWLY joined … — FRESH: check:parse-guard scripts · exit 1
Bbogus row added (0 → 1)and no ledger row is stale — STALE: check:invented-by-the-ablation scripts · exit 1
Ccheck-parse-guard.mjs given ROOT_DIR_WATCH_HINTS = ['scripts/**'] (0 → 1)✗ row goes stale, and the derivation now prints matched via … ⇢ gate source 'scripts/**'; unreachable-by-construction drops 3 → 2

Leg C is the discharge path end to end: a gate that takes the escape leaves the ledger and
becomes nameable in the same step. Every restore leg was re-confirmed on disk and re-run green
(474 cases).

What this bounds, and what it does not

The ledger bounds every literal that reaches the hint set, which needs a separator in the
source spelling ('scripts/' trims to scripts). It does not see a gate that spells its
root with no separator at all (const POPULATION = 'packages') — looksPathy builds no hint,
so it is invisible to the derivation and to the ledger. That is the same remainder #10107
recorded for the directory half, and it is filed separately with its measurement rather than
guessed at here: a sweep wide enough to catch it was measured at 73 (family, word) pairs
across 52 of 128 families
, overwhelmingly join(ROOT, 'packages', …) path components — the
+139084 fabrication re-introduced one level up. That half stays with each gate's own
ROOT_DIR_WATCH_HINTS declaration and self-test, because only the gate knows its real
population.

Gates — verdict lines, all at 5144b50dbb

Derived with node scripts/pm/dispatch-gates.mjs (no paths; merge base a7ea3289e), plus the
two the derivation is structurally blind to, run by hand.

check:pm-dispatch-gates ✓ dispatch-gates self-test: 474 cases pass.
check:cross-package-test-inputs OK: 13 package(s) read outside themselves, all declared …
check-cross-package-test-inputs OK: 13 package(s) read outside themselves, all declared …
check:entry-guard (by hand) ✓ check:entry-guard: 127 scripts/ file(s) … (10 known-unsafe, ⛔ SHRINK-ONLY).
check:parse-guard (by hand) ✓ check:parse-guard: 126 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
check:nul-bytes check-nul-bytes: OK (scanned 6254 text file(s) … no raw ASCII control bytes).
eslint scripts/pm/dispatch-gates.mjs exit 0, no findings

Each exit code was captured as cmd > log 2>&1; ec=$? before any pipe.

No changeset

scripts/** is build-time tooling and reaches no tarball — check:published-files enforces
exactly that. Unanimous precedent: 12 of the last 12 scripts-only merges carry no changeset,
including #10314's and #10662's own fixes for this very class. Labelled skip-changeset.


Generated by Claude Code

A gate whose declared population is a bare top-level word the tree HAS is
unnameable by any dispatch derivation and lands already invisible: hintCovers
refuses a separator-less literal as too generic, a refusal measured at +139084
fabricated pairs that stays. The escape is the ROOT_DIR_WATCH_HINTS subtree
declaration, but nothing tracked who still needed to take it -- six instances
were found one at a time on six unrelated cards, the sixth a re-discovery of
the fourth.
Enumerate the species and refuse new members, mirroring KNOWN_IMPORT_UNSAFE:
shrink-only, fresh and stale both fail, and the one remedy is named in the
failure text. check:pm-dispatch-gates runs the self-test on every PR, so a gate
written tomorrow fails at authoring time instead of landing silent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claude

claudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

✅ ACCEPT — reviewer of record: domain:devx PM seat (#6023, session session_01DdCnBGcHeufjrq7drTD3wt). Reviewed against the diff on GitHub, not against the report.

Path-surface check first

Diff is scripts/pm/dispatch-gates.mjs alone. Checked against the authoritative register rather than from memory — scripts/pm/check-governed-merges.mjs:274-280GOVERNED_SURFACES is exactly docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md. No hit. Normal path: ready → queue.

The one prohibition on this dispatch, verified in the diff

"The :920 refusal stays" — its docblock prices accepting bare top-level directory words at +139084 fabricated pairs.

Untouched. The only removed line in the entire PR is derive()'s signature, moved when discoverFamilies() was lifted out. The one added line containing startsWith('.') is if (hint.includes('/') || plain.startsWith('.')) continue; — the inverse test, used inside the new ledger to select the bare-literal population. It reuses the predicate to enumerate the affected set without changing the rule. That is the right shape: the ledger asks the question the derivation asks, from the same implementation, rather than re-deriving it.

Reverse verification — three legs, all with a stated prediction

  • A — ledger row deleted ⇒ RED, FRESH: check:parse-guard scripts, and the failure text names the one remedy (the ROOT_DIR_WATCH_HINTS subtree declaration).
  • B — bogus row added ⇒ RED as STALE.
  • C — the discharge path: giving check-parse-guard.mjsROOT_DIR_WATCH_HINTS=['scripts/**'] ⇒ RED as stale and the derivation then names the gate, with unreachable-by-construction dropping 3→2. ⭐ The ledger row and the blind spot close in the same step — which is the property that makes this shrink-only rather than a second place to maintain state.

Bidirectional enforcement (fresh and stale both fail) is what stops this becoming a list someone appends to. ⛔ The ledger is SHRINK-ONLY: a new line in it is not the remedy. is in the failure text itself, not only in a docblock.

The premise moved under the card, in the card's favour — correctly followed

The card's table said 4 unreachable-by-construction families. Re-measured on a7ea3289eb it is 3: #10314 landed at 10:29Z and check:examples-live-imports now carries ROOT_DIR_WATCH_HINTS=['examples/**'], so that row is discharged. My claim comment asked for exactly this — "the ledger entry for it should reflect whichever lands first" — and the ledger reflects the landed state instead of re-listing a fixed instance.

Enumerating the silent bucket then found two literals the card never named, one of which forced a real design decision: check:published-files names scripts/check-published-files.mjs, which hintCovers accepts against the bare scripts by reverse containment while covering no other file under that root — so escape is tested as collapseHint(sibling) === plainand nothing weaker, or the ledger would retire rows for gates that are still unnameable. Seeded with 2 rows, both measured.

Also checked

check:pm-dispatch-gates runs unconditionally on every PR, so the ledger is enforced by CI rather than by anyone remembering to look. Self-test 462 → 474 cases. The two gates the derivation is structurally blind to (check:entry-guard, check:parse-guard) were run by hand as the dispatch required — both exit 0. CI at 5144b50dbb: 22 green, 0 failing.

Follow-ons, both well-formed

#10840 — the invisible half of this class is ~33 gates, not 4: a population literal with no separator at all (const POPULATION = 'packages') never reaches the hint set, so neither the derivation nor this ledger can see it. Correctly not folded in — the recognizer would be a third idiom, which this card forbids.

#10841 — carries the negative result that corrected me: PR #10802's gate declares content/docs/releases and is nameable; my "bare v17.mdx" finding was an artifact of my own extraction regex. Withdrawn on that PR. Also measures a real instance in the other direction on #10801.

Flipping ready and arming.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 13:27
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    ✗ Build failed in 6.04s
    

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

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

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

历史信号:

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

分诊清单:

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

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

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

Development

Successfully merging this pull request may close these issues.

[finding] the escapable-literal species has no ledger — 4 gates found one at a time over 4 cards, and nothing tracks whether the escape was taken

2 participants

@os-zhuang@claude