Skip to content

fix(pm): the dispatch-gates exclusion vocabulary recognises DEFERRED, so a deferred table stops deriving as a scan surface - #19308

Merged
os-tesla merged 3 commits into
mainfrom
claude/issue-19260-dispatch-gates-deferred-exclusion
Sep 20, 2026
Merged

os-tesla merged 3 commits into
mainfrom
claude/issue-19260-dispatch-gates-deferred-exclusion

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #19260

Clause-②: no

EXCLUSION_DECL_NAME in scripts/pm/dispatch-gates.mjs listed no DEFERRED, while
scripts/check-issue-citations.mjs declares its exclusion table under exactly that word —
DEFERRED_SURFACES / DEFERRED_GLOBS — and applies it as an exclusion: surfaceFor opens by
returning null for every deferred glob. So the derivation read an exclusion table as a
scan surface, and a .changeset/** path was told it triggers a gate that looks at nothing
there. 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 DEFERRED branch in the predicate; the measured census in the docblock above it re-measured
and rewritten in the same stroke; one self-test case beside the existing named-spelling cases.

⛔ Nothing is renamed in check-issue-citations.mjsDEFERRED_SURFACES and DEFERRED_GLOBS
are 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 — ROOT comes from import.meta.url, and
trackedFiles() reads that root — so a cwd does not redirect it at another tree. The fixed
file was therefore copied into a detached worktree of PR #19259's head
(6d1272b3850e5c0e756745dc6486d20aa330653d, blob 5d78406d6 before, 8216826a5 while mutated)
and run from there; the probe was restored with git checkout HEAD -- scripts/pm/dispatch-gates.mjs
back to blob 5d78406d6, git diff HEAD empty. .changeset/test-abc.md did not need to exist.

probe tree 6d1272b385 --commands lines pnpm check:issue-citations rows
subject .changeset/test-abc.md, before 19 1
subject .changeset/test-abc.md, after 18 0
control packages/types/src/index.ts, before 44 1
control packages/types/src/index.ts, after 44 1

The subject's two command lists differ on exactly one line — diff reports hunk 13d12, deleting
the pnpm check:issue-citations line 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 topLevelDecls over every tracked JS/TS file under scripts/, keep the
non-callable declarations, test each name against the predicate, and price the arm by diffing
extractWatchHints against a build of this module whose predicate matches nothing. Attribution is
the 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 of
six internal bindings as the only difference from the shipped file (a throwaway copy, never
committed):

reading docblock before re-measured, no DEFERRED re-measured, with DEFERRED
tracked JS/TS files under scripts/ 230 287 287
top-level VALUE declarations 3,513 4,697 4,697
identifiers matching the predicate 64 73 75
of those, carrying a string literal 58 65 66
hints the set moves 14 16 24
files it moves them on 6 7 8
change no derivation / really leave one 8 / 6 13 / 11

Per-word tally, same ordering as the block: 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 EXCLUDES went 1 → 0 on its own. ⚠️ Every one of those numbers was already
stale before this card
: the middle column is the same tree with the OLD predicate, so the drift
from 230 / 3,513 / 64 / 58 / 14 is the tree moving, not the arm. Nothing reds when it does, which
is why the block now carries the tree ref it was read on.

DEFERRED matches exactly two declarations on this tree, both in check-issue-citations.mjs, and
both 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, so hintCovers still reaches a
test path — and 3 of which were fabricated leads: .changeset/** (the one the card measured),
plus scripts/** and docs/adr/**, which nobody had named. The gate stays reachable through
packages/**, packages/**/src/**/*.ts and its siblings, which is what keeps the control lit.

Re-verified after the merge of origin/main e6a03e6491: every figure above reproduces, and the
edit 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

command exit verdict line
node scripts/pm/dispatch-gates.mjs --self-test (this branch) 0 ✓ dispatch-gates self-test: 1867 cases pass.
node scripts/pm/dispatch-gates.mjs --self-test (pristine e6a03e6491) 0 ✓ dispatch-gates self-test: 1866 cases pass.
pnpm check:pm-dispatch-gates (detached, under the shared lock) 0 check:pm-dispatch-gates: the battery took 1012.6s on this box.
pnpm lint (repo-wide, eslint . --no-inline-config) 0 no output
the other 27 derived families 0 each listed below

Case count 1866 → 1867: the one new case is the DEFERRED entry in the named-spelling loop beside
the existing exclusion-vocabulary cases, ⛔ not at the tail of selfTest() — the tail regions that
PR #19162 (:22173:22203, now landed as e6a03e6) and PR #19024 (:23763) touch are untouched
here, and the merge of origin/main carrying #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 -naP over the changed file for the C0 range plus DEL exits 1 — none present.

Line budget and shape

scripts/pm/dispatch-gates.mjs 28,345 → 28,371 lines against origin/main e6a03e6491:
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 --stat
shows one file.

No changeset: scripts/ ships in no published package's files[] (checked across every non-private
manifest in the tree), and scripts/pm/** is on the non-publishing fast track. ⚠️ So Check Changeset
needs the skip-changeset label, and this dispatch forbids label writes to the dev — that one write
is 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 → 287 files, 3,513 → 4,697 declarations, 64 → 73 matches, 14 → 16 moved hints, and the
tally's EXCLUDES 1 → 0. The sibling gate keeps its own census as code with a measuredOn ref
(CENSUS_17512 in check-issue-citations.mjs, marked 「⛔ Readings, not a budget」); this one is
prose 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

…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>
@os-tesla os-tesla added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 20, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cf2c44b42877a9495ff9cc55a18d3ef4974a2e04

In-seat review by the domain:skills seat at the contract-review tier (served tier read at seating, #7623 marker 5738863635) — the lane's record for a scripts/pm/** landing (Tier S: the seat lands through the queue on this record + --pair 0 + every check green; check-governed-merges --pr 19308: 「NOT governed — ordinary queue landing」, so the record is the lane's own discipline). Clause-②: no on the claim (5748809084) and at line start in the PR body. Verified against GitHub (GET /pulls/19308/files: one file, scripts/pm/dispatch-gates.mjs +64 / −38; three commits — 6cd6c136 the fix, effedcc4 a merge of origin/main (PR #19162 landed mid-task, region-disjoint), cf2c44b4 the census tree ref — trailer pairs model-free) and the diff read in full, ⛔ not against the report (5749305232).

① Derived judgments

② Semver level

None — scripts/pm/** publishes nothing; skip-changeset applied by the seat (precedent PRs #19173 / #19280 / #19288) so this repo's Check Changeset exempts.

③ Boundary flags

Implemented-by: claude/issue-19260-dispatch-gates-deferred-exclusion
Reviewed-by: session_01W5y9kRg1YtYaMQYExVLRc2

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants