Skip to content

fix(pm): dispatch-gates derives fifteen whole-root lint gates from the diffs that can move them - #31291

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-m6273z
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-m6273z

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #31257

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:dispatch-gates --commands 新增 15 条「按门禁自己的扫描范围 + 自己的第一道过滤」判定的派生行。改到某道全仓 lint 门禁真正会读、且能通过它自身预过滤的文件时(例如含 getService 的插件源码、含 .json( 的路由文件、新的 sql-driver-*.ts 分片),就把那道门禁派给卡。另有 2 道(check:stack-collection-maps、check:filter-alias-parity)经实测只读自己源码里写死的文件,路径派生早已覆盖,无需新行。
  • 为什么改:这 18 道门禁在 CI 里每个 PR 都跑,但本地派生清单在改动不在它们「主包」时从不列出它们,开发者报「派生门禁全绿」后 CI 才红(fix(scenarios): run grading reads the card's today, app validation codes, overstep probes, scope figures and served keys #31214 就踩过一次)。
  • 风险与回滚:只动 scripts/pm/ 下的派生工具与自测,不改任何门禁检查什么,不动 workflow;最坏情况是多派生一道几秒的门禁,或漏派生(即现状)。回滚即还原本 PR。
  • 先看:scripts/pm/dispatch-gates.mjs 的 POPULATION_NETS 与 reachesPopulationNet;scripts/pm/dispatch-gates.data.mjs 新增的 15 条 CHANGE_KIND_ROWS。
  • 两分钟验证:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack packages/services/service-settings/src/settings-service-plugin.ts packages/drivers/driver-sql/src/sql-driver-crud.ts,预期多出 pnpm check:init-service-contract、check:settings-bind-window、check:startup-registry-verdict 等(main 上没有)。
  • 需要你做的:无,席位落地。

What changes

Eighteen lint steps run on every PR (lint.yml, no paths: filter) over a whole-root population, and --commands never handed them to a card whose diff sat outside their home package: eleven print in the declared-WIDE block and seven in the artifact-roster block, and the seat ruling on #30763 keeps both blocks out of --commands wholesale. This PR closes them family by family.

Fifteen get a change-kind row (option a), each bound to its own population net: the gate's own walk (roots, kept file names, skipped names and directories, read off its source) plus the gate's own first filter, the cheap pre-filter most of them run before their AST pass. Two gates expose their per-file reader and keep no ledger, so the net imports it instead of copying it (check:runner-env-posture: isProductSource + findRunnerEnvReads; check:error-code-casing: findViolations); both modules are dependency-free, so the tool still runs with no install. A deleted population file is judged on the content it HAD (HEAD's blob, else the blob before the deleting commit), so a move or delete that strands a ledger row or a provider still counts, while a path git never held does not.

Gate Block Net (population · filter) Hits / population at 07559ce Recall vs the gate's own pass Local wall
check:init-service-contract WIDE packages non-test .ts · PREFILTER_TOKENS (getService ×3, providesServices) 217 / 4498 --list 31 files, 0 missed 2.5 s
check:settings-bind-window WIDE same · + trigger 657 / 4498 --list 11, 0 missed 4.2 s
check:startup-registry-verdict WIDE same · registry probes, open registries, providesServices 222 / 4498 --list 29, 0 missed 3.7 s
check:optional-error-sink WIDE packages non-test .ts · CHANNEL_PREFILTER 1070 / 4498 --list 204, 0 missed 5.3 s
check:resume-authority-declared WIDE packages + examples · defineActionDescriptor 19 / 4691 --list 16, 0 missed 1.1 s
check:route-envelope WIDE packages non-test .ts · -routes.ts / off-convention / domains by path, .json( by content 160 / 4498 MODULES 56 files, 0 missed 3.8 s
check:verify-stand-in WIDE packages + examples (tests included) · guarded names read from eslint.config.mjs, verify src by path 83 / 12003 the census pre-filter itself 2.0 s
check:wildcard-fallthrough WIDE packages non-test .ts · a quoted /* 143 / 4498 --list 7, 0 missed 2.0 s
check:live-db-isolation WIDE packages, apps, examples (tests included) · OS_TEST_{MYSQL,POSTGRES}_URL 101 / 12010 65 live suites, 0 missed 5.9 s
check:runner-env-posture WIDE the gate's isProductSource · its findRunnerEnvReads (imported) 0 / 4357 the gate's own reader 8.7 s
check:meta-type-normalized WIDE packages/rest/src non-test, path only 56 / 56 the gate's own 56 files 0.8 s
check:tenant-chokepoint roster sql-driver-*.ts listed in the driver-sql src dir, path only 36 / 36 the gate's listing (39 = 3 drivers + 36) 1.0 s
check:authz-resolver roster packages non-test TS · both grant tables named 62 / 4489 its queriesAllGrantTables needs both 2.3 s
check:error-code-casing roster packages .ts/.tsx · its findViolations (imported) 4 / 11748 the gate's own reader (4 exempt) 12.6 s
check:object-def-param-keys roster tracked .ts under a package src · a class or an as any 1248 / 4236 --list 244, 0 missed 7.0 s

Two are closed by measurement (option c): check:stack-collection-maps and check:filter-alias-parity read only files their own source spells as literals (8 and 3), walk no root, and the path derivation already matches the gate for every one of those files. The census's "whole-root walk" signature for filter-alias-parity was its AST walk generator.

One is not closed here: check:published-readme-exports (see Acceptance notes).

Why

#31214 paid a CI round trip on check:error-code-provenance, a family of the same class; PR #31254 fixed three and #31257 holds the rest. A dev report could read "all derived gates green" while CI went red on any of these.

Tests

New cases in scripts/pm/dispatch-gates.self-test.mjs, table-driven over the fifteen nets (NET_CASES, one entry per net, held to POPULATION_NETS key for key):

  • Per net: a real diff shape is a hit (a plugin init lookup, a settings trigger, a hasService probe, an optional-error sink type, a pausing descriptor, a c.json( writer, a stand-in call in a verify test, a wildcard mount, a live suite's URL read, a runner-variable branch, a new REST source, the next sql-driver-* split, both grant tables, a lowercase code, a class extending the SQL driver); the gate's own walk exclusion is a miss; a population file without the filter is a miss; a deleted file is judged on its prior content (hit and miss); a path git never held is a miss; a path outside the roots is a miss; the copied spellings are still in the gate's source (and for startup/sink the token SET equals the gate's own Map/Set); exactly one row names the gate, bound to this net; a live specimen derives the gate through the whole table; the rendered lead says it needs no build and why no card had it.
  • Imported readers are identical to the gates' own functions; the verify net reads the guarded names off the ESLint config and throws on a config without the table; a docs-only page derives none of the fifteen.
  • Closures: for both roster gates, every source file the gate reads is a literal the path derivation already matches (8/8, 3/3), and the gate walks no root.
  • Slow tier: each net discriminates on the live tree, and every file each gate's own --list names is inside its net (7 gates, 0 missed).
  • Pins moved: the inherited-population census row 744 → 748 (four import lines above it); two whole-table section-count pins (i18n bundle, metadata form) now read through the entries naming check:i18n, because the optional-sink and object-def nets fire on the same live files for their own reason.

Ablation (one fast-tier run, three mutations through nested scripts/ablation-replace.mjs, restore proven twice: blob == HEAD c0eee2c17012, git diff HEAD empty), 47 of 2174 cases red, every one named:

  • walk exclusion removed from inNetPopulation ⇒ red "…the gate's walk excludes X, so the net does too" for the 11 nets that exclude by file name.
  • token filter replaced by true in reachesPopulationNet ⇒ red "a population file without the gate's filter is not a hit" for the 9 token nets (the pattern and imported-reader nets are unaffected by that line, as designed).
  • prior-content read replaced by "any absent path counts" ⇒ red "a deleted file is judged on the content it had" and "a path git never held is not a hit" for all 13 content nets.
  • also red: the existing "a test path emits the convention section" count pin (the widened walk lets nets fire on test files).

Acceptance notes

  • check:published-readme-exports: not derived; for a follow-up card. Its population is the 129 published markdown docs of public packages outside ui/, plus the BUILT type entries of the 77 targets (68 packages) their 268 import statements reach. A path net over those packages is close to every package's source, so a precise net needs a member-name reader (the names each doc imports, matched against declarations in the reached package). The gate also needs those packages built (exit 3 on an unbuilt worktree). Left out rather than shipped imprecise.
  • What a net cannot see: a gate that follows an import out of the judged file (settings-bind-window resolves a hook name through one) can be moved by the imported file alone; and a new token in a gate's pre-filter widens the gate before the net, until the self-test's spelling pin reds on it.
  • Precision trade: optional-error-sink (24% of its population) and object-def-param-keys (30%) are as wide as the gates' own first filters; both are about 5–7 s and unconditional in CI.
  • No .github/** edit; no change to what any gate checks.

Verification

Gate union from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this diff: 30 families, run on head 81c8a785 (after merging origin/main), reconciled with --ran: "30 derived famil(ies) accounted for — 30 run, 0 NOT-MEASURED (a DERIVED zero — all 30 recorded an exit code and none of them is 3)".

30 gates, exit codes captured before any pipe
  • node scripts/check-closing-keyword-parity.mjs (and --self-test), check-comment-mask-corpus.mjs, check-declaration-mirrors.mjs (and --self-test), check-self-test-wired.mjs (and --self-test), check-self-test-workflow-commands.mjs (and --self-test), check-whole-set-label-write.mjs (and --self-test), ci/plan-test-core-shards.mjs --self-test: exit 0 each
  • pnpm check:agent-test-spelling, check:bash32-floor, check:ci-filter-parity, check:cli-command-ids, check:cross-package-test-inputs, check:declared-population-live, check:driver-memory-census, check:entry-guard, check:gitlink-declared, check:nul-bytes, check:parse-guard, check:pnpm-filter-targets, check:pnpm-flag-spellings, check:ratchet-remedy-authority, check:refd-timer-probe, check:scripts-symbol-anchors, check:watch-hint-literal: exit 0 each
  • pnpm check:pm-dispatch-gates: exit 0, "✓ dispatch-gates self-test (fast tier): 2174 cases pass; 8 slow section(s) deferred." (464.8 s)
  • Full battery (node scripts/pm/dispatch-gates.mjs --self-test): on head 81c8a785, "✓ dispatch-gates self-test: 2483 cases pass." (fast 2174 cases in 422.9 s, slow 309 cases in 1282.6 s).
  • npx eslint --no-inline-config on the three files: exit 0. node scripts/check-nul-bytes.mjs: exit 0. No package touched, so no package lint/test/typecheck is owed.
  • CI families are read from this PR's check-runs, not re-run here.

skip-changeset: scripts/pm/** ships in no package.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (eslint on the three files, exit 0).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. Publishes nothing (scripts/pm/**), so skip-changeset; both declaration lines are filled in.
  • 6. packages/spec not touched.
  • 7. No hand edits to generated docs, changelogs or release notes.
  • 8. Title, body and comments are in English (the maintainer brief excepted, per the dispatch contract).

Generated by Claude Code

… population nets

Each net is the gate's own walk plus its own first filter (or its own
per-file reader, imported, where the gate exposes one), bound to a
change-kind row, so a diff that can move the gate derives it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nVPVu6jXRZdmdUb8w
Real diff shapes per net, the gate's own exclusions and filters as misses,
the copied spellings held to each gate's source, the two closed roster
families measured, and a slow-tier --list parity per net. Two section-count
pins now read through their own entries, and the census line moves 744 -> 748.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nVPVu6jXRZdmdUb8w
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:devx#3 · os-project-manager · session_01FEkQ5njkh3xbcDN6EtSfw7

席位核对

Verdict: ACCEPT · head 81c8a785e3bd2872ac7341f762fcaac63c82a4f4 · card #31257 · report 6714099288.

  • Shape: draft → main, Fixes #31257; 3 files, +526/-3, all inside the claim's surface (scripts/pm/dispatch-gates{,.data,.self-test}.mjs; check-dispatch-gates.mjs untouched, no pin moved). NOT governed. The head merges origin/main (2 commits) before the gate loop.
  • Card's ask, read in the diff: 17 of the 18 families are closed.
    • 15 get a change-kind row (option a), each bound to the gate's own population net. That is its walk read off its source plus its own first filter (PREFILTER_TOKENS, CHANNEL_PREFILTER, the FACTORY include, the .json( include, the census-guarded names). For check:runner-env-posture and check:error-code-casing, the gate's own per-file reader is imported (both dependency-free). A deleted population file is judged on its prior blob.
    • 2 are closed by measurement (option c): check:stack-collection-maps and check:filter-alias-parity read only literal files (8 and 3) that the path derivation already matches, and walk no root.
    • check:published-readme-exports is not closed. Its reach is the built type entries of 68 packages, so a precise net needs a member-name reader. It is filed as dispatch-gates: check:published-readme-exports is never derived by --commands for a package-source diff (the one family #31257 left) #31292, so Fixes #31257 stands.
  • Seat ruling held: no wholesale emit; --ran counts derived families only.
  • Tests: self-test 2483 cases pass (fast 2174, slow 309). Ablation turned 47 named cases red, and the restore blob equals HEAD.
  • Gates: check-runs on this head: 26 success, 20 skipped (path-gated), 0 failed, 0 pending. Local: 30 derived, 30 exit 0, 0 NOT MEASURED.
  • Changeset: scripts/pm/** ships in no package ⇒ skip-changeset correct. Clause-②: no.
  • Noted, not filed: check:settings-bind-window resolves a hook name through an import, so a diff to only the imported file can move it while the net reads the judged file only (stated in the engine docblock). A self-test pin that counts every change-kind line on a live test path would red for an unrelated reason if a future net keeps test files.
  • Landing: under 「按照 pm-dispatch 规则复核过的都授权」 (2026-10-25T11:35Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (2026-11-07T16:59Z): pr_ready + automerge_enable now.

Generated by Claude Code

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dispatch-gates: 18 whole-root lint families are never derived by --commands for a diff outside their home package

1 participant