Skip to content

fix(pm): dispatch-gates derives the published-README export gate from the claims a diff takes away - #31314

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-qoxaap
Nov 8, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-qoxaap

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #31292

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:dispatch-gates --commands 新增一条派生行,把 check:published-readme-exports(已发布 README 里写的 import 必须在包的已构建类型里存在)派给「真的可能让它变红」的改动:README 新写了 import 或成员调用、package.json 的发布字段(exports/types/files 等)变了、或某个被 README 引用的名字在源码里被删除或改名。
  • 为什么改:这道门禁在 CI 每个 PR 都跑,但本地派生清单从不列出它(dispatch-gates: 18 whole-root lint families are never derived by --commands for a diff outside their home package #31257 唯一剩下的一族);它读 68 个包的构建产物,按路径派生会派给几乎每张卡,所以改为按「README 声称的名字」与「这次改动删掉的声明」比对。
  • 风险与回滚:只动 scripts/pm/ 下的派生工具与自测,不改任何门禁检查什么,不动 workflow。最坏情况是多派一次(需要先构建)或漏派(即现状)。回滚即还原本 PR。
  • 先看:scripts/pm/dispatch-gates.mjs 的 takesAReadmeClaim 与 publishedReadmeReach;scripts/pm/dispatch-gates.data.mjs 新增的那一行(含构建前置命令)。
  • 两分钟验证:在本分支把 packages/core/src 里 ObjectKernel 类改名后跑 node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack,预期清单出现 pnpm check:published-readme-exports;还原后不再出现。
  • 需要你做的:无,席位落地。

What changes

check:published-readme-exports runs on every PR (lint.yml, after the workspace build) and --commands never handed it to a card: it sat in the artifact-roster block, and the seat ruling on #30763 keeps that block out of --commands wholesale. It is the one family #31257 left (PR #31291 closed the other seventeen), because its reach is the BUILT type entries of 68 packages that 129 published documents import from, so a path net would name it for nearly every card.

This PR adds one change-kind row, published-readme-claim, bound to a claim net: it reads what the published documents claim and asks whether the diff takes one of those claims away. Three limbs, each judged against the blob at the change set's merge base (the working tree is the head):

Limb Hit when Not a hit
DOCUMENT a published document (the gate's own definition) whose fenced code makes a claim the base did not: a new scoped or workspace import, a new name on one, a new receiver.path.member( site a prose-only edit, a foreign-scope import, a deleted document
MANIFEST a workspace member's package.json whose name, private, files, exports, types or typings changed; the workspace file's packages: globs a version bump, a dependency edit, workspace overrides
MEMBER a non-test TypeScript source in a reached package (the imported package or its workspace dependency closure) that declares a claimed name FEWER times than the base did, or drops an export * from line an addition, an edit around a claimed name, a comment, an unclaimed name, a new file, a test or dist file

The names are the gate's own, read by copies of its extraction (readFences, extractImports, memberCallSites, the one-hop const x = [await] [new] Name( receivers). Where a copy must choose, it is WIDER: every candidate receiver is kept where the gate drops a shadowed one. The gate's module loads typescript, so it is held to the copy through a spawned child in the self-test rather than imported (the no-install rule). The workspace parse is imported from scripts/workspace-enumerator.mjs, the module the gate itself uses (dependency-free, no path population).

The row's lead names the build prerequisite runnably, as lint.yml spells it (pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/*), and says that the gate's exit 3 on an unbuilt tree is NOT MEASURED, never green, so a --ran record carrying :: exit 3 reconciles it as NOT-MEASURED.

Recall, against the gate's own reader (at 7c523aa1)

  • Documents: the net reads exactly the gate's 129 (outsideUiTree(publishedDocs())), none differing.
  • Claims: the gate's two halves check 364 (package, name) claims; the net reads 377, and every one of the 364 is among them.
  • Declarations: each of the 364 names is declared, as the net's declaration reader sees it, by a non-test source in its package's closure (377 of 377 net claims too), so removing or renaming that declaration is a hit.

Precision, replayed over the last 500 first-parent commits on main

303 of them touched a reached package's TypeScript; 17 would derive the gate (10 by a removed claimed name, 7 by a manifest field, 3 by a new document claim). For comparison, a path net over the reached packages would have derived it for every one of the 303.

Tests

New cases in scripts/pm/dispatch-gates.self-test.mjs:

  • Limbs, through injected base/head readers: renaming a claimed class, removing a claimed method, deleting a file that declared one, and dropping an export * from line are hits; adding a method, a new file, removing an unclaimed name, a claimed name dropped from a comment only, a test file, a dist file and a path in no reached package are misses. A new scoped import and a new member call on a built receiver in a live published document are hits; a prose-only edit, a foreign-scope import and a deleted document are not. A changed exports and changed workspace globs are hits; a version bump and changed overrides are not. Each declaration shape the reader counts is pinned.
  • The reader held to the gate's own (spawned child): the same document set; every claim the gate checks is a claim the net reads; the gate's source still spells what the net copies (the excluded markdown, the skip dirs, the call-site matcher, the receiver declaration, the published-package test, the ui tree) and its fence languages are exactly the net's. A tree with no published document throws rather than reaching nothing.
  • The row: exactly one entry names the gate, bound to the claim net; the rendered lead names the gate runnably, its build prerequisite and exit 3 as NOT MEASURED; the build filter covers every package directory the documents reach; a docs page and an unrelated script derive no README gate through the whole table.
  • Slow tier: each of the gate's claims has a declaration the net reads in its package closure.
  • Pin moved: the inherited-population census row 748 → 751 (the import line and its two comment lines).

Ablation (one fast-tier run, four mutations through nested scripts/ablation-replace.mjs; restore proven four times: blob == HEAD e7178e75c1fe, git diff HEAD empty), 6 of 2205 cases red, each named:

  • the claimed-name filter replaced by "any removed name" ⇒ "MEMBER: removing an unclaimed name is not a hit";
  • the removal comparison < widened to <= ⇒ "MEMBER: adding a method is not a hit", "MEMBER: a claimed name dropped from a comment only is not a hit";
  • the document claim diff replaced by "any claim" ⇒ "DOCUMENT: a prose-only edit is not a hit", "DOCUMENT: a foreign-scope import is not a hit";
  • the manifest field comparison replaced by "any byte" ⇒ "MANIFEST: a version bump is not a hit".

Acceptance notes

  • Dispatch time. On a dispatch-time surface (no diff yet) the document and member limbs find nothing to compare and stay quiet; the gate is owed on the dev's re-derivation over the real diff. A path net would have answered the PM's question at the price of naming a build-requiring gate on nearly every card.
  • What the net cannot see: a file MOVED within a package reads as a removal from its old path (a wasted run, the safe direction); a claimed member a type inherits from outside the workspace has no workspace declaration to remove; a build config that stops emitting an entry is neither a manifest field nor a declaration.
  • Declaration reader is textual, so it over-counts (a line-leading call reads like a method), which only widens the net. The replay above is the price.
  • 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 (3 paths vs merge base 7c523aa10): 30 families, all run on head aa386612, each exit 0, 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)". The derivation does not name check:published-readme-exports for this diff, as intended: it touches no reached package, document or manifest.

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): 2205 cases pass; 9 slow section(s) deferred." (394 s)
  • End to end on the real CLI: with export class ObjectKernel renamed in packages/core/src/kernel.ts (uncommitted), --commands lists pnpm check:published-readme-exports; after git checkout HEAD -- packages/core/src/kernel.ts it does not.
  • Full battery (node scripts/pm/dispatch-gates.mjs --self-test, slow tier included) was running on aa386612 when this PR opened; its verdict is in the os-dev report on dispatch-gates: check:published-readme-exports is never derived by --commands for a package-source diff (the one family #31257 left) #31292. The slow section's check (every gate claim declared in its closure) was measured standalone at 377 of 377 net claims, none undeclared.
  • npx eslint --no-inline-config on the three files: 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; eslint on the three files passes.
  • 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

… the claims a diff takes away

check:published-readme-exports reads the built types of 68 packages through
the imports of 129 published documents, so a path net would hand it to nearly
every card and --commands never derived it. A new change-kind row reaches it
through what the documents claim: a new fenced import or member call in a
published document, a manifest field or workspace glob that moves the
population or a type entry, or a claimed name a reached package's source
declares fewer times than the merge base did. The row names the build the
gate needs, so an unbuilt run reads NOT MEASURED.

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

Copy link
Copy Markdown
Contributor Author

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

席位核对

Verdict: ACCEPT · head aa38661260d8d10e61039ee2359d0476932b7b3d · card #31292 · report 6715560427.

  • Shape: draft → main, Fixes #31292. 3 files, +524/-1, all inside the claim's surface: scripts/pm/dispatch-gates{,.data,.self-test}.mjs. NOT governed (0 of 3 paths hit the register).

  • Card's ask, read in the diff: one change-kind row, published-readme-claim, is bound to a diff-based claim net with three triggers:

    • a new claim in a published doc;
    • a changed manifest field or workspace glob;
    • a claimed name that a reached package declares fewer times than at the merge base.

    The row's lead names the build prerequisite and reads exit 3 as NOT MEASURED, so --ran cannot show a false green on an unbuilt tree.

  • Recall against the gate's own reader: 129/129 docs; all 364 gate claims fall inside the net's 377. Replay of the last 500 main commits: 17 derive the gate, against 303 that touch reached-package TS. That is precise, not a path net.

  • Seat ruling held (dispatch-gates --commands misses two families a diff can break: check:cli-command-ids (any quoted string) and check:adr-symbol-anchors (a retired export an ADR anchors) #30763): no wholesale emit, and --ran counts derived families only. On a dispatch-time surface with no diff, the net stays quiet by design. The dev's re-derivation on the real diff is where it fires, which matches the standing rule that the dev derives gates on the real diff.

  • Tests: the dev reports the self-test battery at 2515 pass, and ablation produced 6 named reds. The seat re-ran scripts/pm/dispatch-gates.self-test.mjs on this head in a clean worktree, exit 0.

  • Gates: check-runs on this head: 26 success, 20 skipped (path-gated), 0 failed. Local: 30 derived, 30 exit 0, 0 NOT MEASURED.

  • Changeset: scripts/pm/** ships in no package, so skip-changeset is correct. Clause-②: no, and no contract review is owed.

  • Landing: under 「按照 pm-dispatch 规则复核过的都授权」 (2026-10-25T11:35Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (2026-11-07T16:59Z): pr_ready + automerge_enable now.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 8, 2026 01:24
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 8, 2026 01:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit 300f740 Nov 8, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-qoxaap branch November 8, 2026 01:55
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: check:published-readme-exports is never derived by --commands for a package-source diff (the one family #31257 left)

1 participant