fix(pm): the dispatch-gates exclusion vocabulary recognises DEFERRED, so a deferred table stops deriving as a scan surface - #19308
Conversation
…table stops reading as a scan surface `EXCLUSION_DECL_NAME` listed no `DEFERRED`, and `check-issue-citations.mjs` declares its exclusion table as `DEFERRED_SURFACES` / `DEFERRED_GLOBS` and applies it as an exclusion — `surfaceFor` opens by returning `null` for every deferred glob. So the derivation read that exclusion table as a watch surface and told a `.changeset/**` path it triggers a gate that looks at nothing there. One `DEFERRED` branch in the predicate. The measured census in the docblock is re-measured in the same stroke, by the method it now spells out, and the per-word tally, the moved-hint table and the cost split are rewritten from that reading rather than carried forward. One self-test case joins the named-spelling cases beside it. Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2 Co-authored-by: Claude <noreply@anthropic.com>
…lusion Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: In-seat review by the ① Derived judgments
② Semver levelNone — ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19260
Clause-②: no
EXCLUSION_DECL_NAMEinscripts/pm/dispatch-gates.mjslisted noDEFERRED, whilescripts/check-issue-citations.mjsdeclares its exclusion table under exactly that word —DEFERRED_SURFACES/DEFERRED_GLOBS— and applies it as an exclusion:surfaceForopens byreturning
nullfor every deferred glob. So the derivation read an exclusion table as ascan surface, and a
.changeset/**path was told it triggers a gate that looks at nothingthere. That derivation is the one PR #19259 (card #18224) trips on, and the assertion it reds —
「a changeset path alone reaches NO value-bearing family any more」 — is correct and is untouched
here.
One
DEFERREDbranch in the predicate; the measured census in the docblock above it re-measuredand rewritten in the same stroke; one self-test case beside the existing named-spelling cases.
⛔ Nothing is renamed in
check-issue-citations.mjs—DEFERRED_SURFACESandDEFERRED_GLOBSare exported and pinned by that gate's own self-test, and this docblock says the fix belongs on
the derivation side: 「an author's next spelling should be met by this predicate rather than by a
rediscovery of this card」.
Acceptance notes
Firing pair, both directions, on PR #19259's head
The script derives from the tree it LIVES in —
ROOTcomes fromimport.meta.url, andtrackedFiles()reads that root — so acwddoes not redirect it at another tree. The fixedfile was therefore copied into a detached worktree of PR #19259's head
(
6d1272b3850e5c0e756745dc6486d20aa330653d, blob5d78406d6before,8216826a5while mutated)and run from there; the probe was restored with
git checkout HEAD -- scripts/pm/dispatch-gates.mjsback to blob
5d78406d6,git diff HEADempty..changeset/test-abc.mddid not need to exist.6d1272b385--commandslinespnpm check:issue-citationsrows.changeset/test-abc.md, before.changeset/test-abc.md, afterpackages/types/src/index.ts, beforepackages/types/src/index.ts, afterThe subject's two command lists differ on exactly one line —
diffreports hunk13d12, deletingthe
pnpm check:issue-citationsline and nothing else —the control's two lists are byte-identical. Command:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack PATH, exit 0 on all four runs.The census: the method it never stated, and what it now reads
The block named its method only in prose and named no command, so the method is now spelled out in
the block itself: enumerate
topLevelDeclsover every tracked JS/TS file underscripts/, keep thenon-callable declarations, test each name against the predicate, and price the arm by diffing
extractWatchHintsagainst a build of this module whose predicate matches nothing. Attribution isthe regex engine's own — leftmost position first, then alternation order (the old text said
"first-match attribution" without saying which of the two, and they disagree on a multi-word name
such as
EXCLUDED_SKIP).Measured over the objectstack-ai/objectstack tree at
e6a03e6491, with the export-visibility ofsix internal bindings as the only difference from the shipped file (a throwaway copy, never
committed):
DEFERREDDEFERREDscripts/Per-word tally, same ordering as the block:⚠️ Every one of those numbers was already
SKIP 54, EXCLUDED 10, EXCLUSION 4, NOISE 2, SKIPPED 2, DEFERRED 2, EXCLUSIONS 1, and EXCLUDE / EXCLUDES / IGNORE / IGNORED 0— four zero-scoring arms now,not three, because
EXCLUDESwent 1 → 0 on its own.stale before this card: the middle column is the same tree with the OLD predicate, so the drift
from
230 / 3,513 / 64 / 58 / 14is the tree moving, not the arm. Nothing reds when it does, whichis why the block now carries the tree ref it was read on.
DEFERREDmatches exactly two declarations on this tree, both incheck-issue-citations.mjs, andboth are that gate's own exclusion table. It moves 8 hints off that gate, 5 of which cost nothing —
the test globs sit under the gate's own
packages/**inclusion, sohintCoversstill reaches atest path — and 3 of which were fabricated leads:
.changeset/**(the one the card measured),plus
scripts/**anddocs/adr/**, which nobody had named. The gate stays reachable throughpackages/**,packages/**/src/**/*.tsand its siblings, which is what keeps the control lit.Re-verified after the merge of
origin/maine6a03e6491: every figure above reproduces, and theedit adds no top-level VALUE declaration to this file (112 before, 112 after), so the 4,697 holds
for the delivered tree.
Gates — every exit code captured before any pipe
node scripts/pm/dispatch-gates.mjs --self-test(this branch)✓ dispatch-gates self-test: 1867 cases pass.node scripts/pm/dispatch-gates.mjs --self-test(pristinee6a03e6491)✓ dispatch-gates self-test: 1866 cases pass.pnpm check:pm-dispatch-gates(detached, under the shared lock)check:pm-dispatch-gates: the battery took 1012.6s on this box.pnpm lint(repo-wide,eslint . --no-inline-config)Case count 1866 → 1867: the one new case is the
DEFERREDentry in the named-spelling loop besidethe existing exclusion-vocabulary cases, ⛔ not at the tail of
selfTest()— the tail regions thatPR #19162 (
:22173–:22203, now landed ase6a03e6) and PR #19024 (:23763) touch are untouchedhere, and the merge of
origin/maincarrying #19162 was clean. Inside the battery the case「⭐ a changeset path alone reaches NO value-bearing family any more」 — the one red on PR #19259's
head — now reads green.
Families derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(28, recomputed post-merge and identical to the pre-merge list) and reconciled with
--ran:28 derived famil(ies) accounted for — 28 run, 0 NOT-MEASURED (a DERIVED zero — all 28 recorded an exit code and none of them is 3). The 27 besides the battery:check-ci-filter-parity,check-closing-keyword-parity(+ self-test),check-comment-mask-corpus,check-declaration-mirrors(+ self-test),check-scripts-symbol-anchors(+ self-test),check-self-test-wired(+ self-test),check-self-test-workflow-commands(+ self-test),check-whole-set-label-write(+ self-test),check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:cross-package-test-inputs,check:declared-population-live,check:driver-memory-census,check:entry-guard,check:nul-bytes,check:parse-guard,check:pnpm-filter-targets,check:ratchet-remedy-authority,check:refd-timer-probe,check:watch-hint-literal— all exit 0.Control-byte self-scan beyond the gate, exit captured before any pipe:
grep -naPover the changed file for the C0 range plus DEL exits 1 — none present.Line budget and shape
scripts/pm/dispatch-gates.mjs28,345 → 28,371 lines againstorigin/maine6a03e6491:net +26 (+64 / −38), inside the +40 the dispatch set. Measured against the 28,337 the dispatch
quoted at
7d0f911/7ec8534, the file reads +34, of which +8 are PR #19162's own.git diff --statshows one file.
No changeset:⚠️ So
scripts/ships in no published package'sfiles[](checked across every non-privatemanifest in the tree), and
scripts/pm/**is on the non-publishing fast track.Check Changesetneeds the
skip-changesetlabel, and this dispatch forbids label writes to the dev — that one writeis the seat's, not left undone by accident.
Noted, not filed
The census in this docblock was already stale before this card, and nothing reds when it goes
stale. The middle column of the census table above is the same tree read with the OLD predicate:
230 → 287files,3,513 → 4,697declarations,64 → 73matches,14 → 16moved hints, and thetally's
EXCLUDES 1 → 0. The sibling gate keeps its own census as code with ameasuredOnref(
CENSUS_17512incheck-issue-citations.mjs, marked 「⛔ Readings, not a budget」); this one isprose in a comment, so it rots silently. Not filed — it is an observation about a missing guard,
not a reproducible defect, a broken declared contract or an authoring trap. 承接者: the standing
queue on this same file (#19070 → #19104 → #19105 → #19106 → #19172), any of which reads this block.
The mitigation this PR does ship is the tree ref and the spelled-out method, so the next reader can
tell a stale number from a current one.
维护者速读(草稿)
改了什么 — 派单工具
dispatch-gates里那张「哪些常量名代表『这个门禁不看这里』」的词表,补上了DEFERRED这个拼写。顺带把该处注释里那份实测普查重新测了一遍并改写,因为它早已过期。为什么改 —
check-issue-citations这个门禁用DEFERRED_SURFACES声明它故意不看的路径。词表不认这个词,于是工具把「不看的清单」读成了「要看的清单」,反过来告诉开发者:改一个 changeset
文件会触发这个门禁 —— 而该门禁对 changeset 路径其实什么都不做。后果不止是一条假线索:它还让 PR #19259
在
lint.yml第 32 步整条中止,#18224 自己新加的两步从未执行,卡住了一张 p2。实测这条修法还顺手消掉另外两条没人发现的假线索(
scripts/**与docs/adr/**)。风险与代价(含回滚) — 风险很低:改动是一个正则分支加注释,只影响派单提示里「你该跑哪些门禁」这份
清单,不影响任何门禁自身的判定,也不改任何对外发布的包。方向上只会少给一条线索、不会多给,而这一侧
的失效代价是「一张卡多跑一轮 CI」,比反方向「每张卡都被塞一条假线索」便宜得多 —— 这个不对称是该文件自己
写下的判据。回滚 = revert 这一个提交,单文件、无生成物、无迁移。
席位意见
你要做的 — ① 这是 draft PR,按席位流程补
## Contract review记录后再走 ready + auto-merge;②
skip-changeset标签需要席位来打(本单禁止开发侧写标签),否则Check Changeset会红;③ 落地后 PR #19259 / 卡 #18224 即可重跑,它那条断言本身是对的、本 PR 未动。
Generated by Claude Code