Repository navigation
tooling(dispatch-gates): the STALE TREE reading keeps only the moved paths the derivation actually opened - #26585
Conversation
…paths the derivation actually opened `DERIVATION_SURFACE` stays the pathspec roots the staleness diff is scoped to, but `scripts/` also holds generated records nothing derives from (the SDUI manifest record, the ADR anchor files), regenerated by landings in other lanes, so every such landing read as a STALE TREE over a pm-only tree whose families had not moved. `baseDrift` now splits the moved paths against `derivationInputs` — the files the discovery really opened: every workflow, the composite actions it followed, the root manifest, each family's gate files, imports, spawned programs, path reads and package manifests, plus this engine's own module closure — into `changed` (inputs, the only ones that decide loud vs quiet) and `ignored` (under the roots, not inputs), and the quiet rendering names what it set aside. A caller may hand in `inputs` itself; a foreign checkout with no discovery keeps the raw superset reading. Self-test: a record-only drift reads FRESH; a gate-script, workflow or root manifest drift still reads STALE; live-tree pins on the measured set. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude <noreply@anthropic.com>
…ixture it reads The two cases read `loudText` above its declaration, a temporal-dead-zone ReferenceError that only the slow tier reaches (the base-drift section is slow), so the fast-tier `check:pm-dispatch-gates` run stayed green. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude <noreply@anthropic.com>
… it compares against The same slow-tier-only temporal-dead-zone shape as the previous commit: the case read `live` two lines above its declaration. Every identifier the new drift cases read is now checked to be declared above its first read. Claude-Session: https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Director seat (summon #52, ledger objectstack#12708), by the maintainer's word 「#26566、#26568、#26570、#26585 也要追加复审」: the PR is an open draft at this head with no review on it; this record is read-only — the diff, card #26334, the head's check runs. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Retracted: this comment was a duplicate post of the director seat's contract-review record on this PR; the record of record is comment 6439771311 above (same head, same verdict). Nothing else changes. Generated by Claude Code |
维护者速读改了什么: 席位核对ACCEPT ·
Generated by Claude Code |
Requested by Warren · project thread
Fixes #26334
Clause-②: no
Metadata-protocol: none
维护者速读
scripts/pm/dispatch-gates.mjs的 STALE TREE 读数只再数派生真正打开过的文件(derivationInputs,从 discovery 实测:全部 workflow、跟过的 composite action、根package.json、每个门禁族的脚本/import/spawn/按路径读取/包清单,加引擎自身闭包);scripts/下没人读的生成记录(sdui 清单记录、ADR 锚点)移动时不再误报,安静行点名被排除的路径。scripts/下的生成记录,pm-only 树因此每次都读到假 STALE TREE(卡上实测派生集 52 不变),警告被忽视就失去意义。scripts/pm/dispatch-gates.mjs的derivationInputs/liveDerivationInputs(排除规则与降级方向)、baseDrift的narrowedhunk、driftLines安静分支;然后dispatch-gates.self-test.mjs的 base drift 段。origin/main且只差一次 ui 落地的 pm 检出跑node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack,预期 stderr 的 banner 是 "none of the commit(s) this tree can SEE touched … 1 path(s) under the surface moved too, none of them an input of this answer: scripts/sdui-manifest.record.json",不再出现 STALE TREE(--tier不做派生,仍列原始清单并注明未收窄)。What changes
DERIVATION_SURFACEstays the pathspec roots the staleness diff is scoped to (.github/workflows,.github/actions,package.json,scripts). What is new is that the moved paths under those roots are narrowed to the files the derivation actually opened, measured rather than listed — the "narrowed surface" option of the two the order named, chosen because its self-test can prove the negative from the discovery itself instead of from a hand list that would rot the next time a generated record lands underscripts/.derivationInputs()(new, exported): the measured set — every workflow file, every composite action the follow opened, the rootpackage.json, and for every discovered family its gate files, first-party imports, spawned programs, program-text reads and package manifests, plus the engine's own module closure (this file, its first-party imports — the data module two levels down from every family — and the battery it loads byimport()). Memoised per discovery, which is memoised per tree, so a derivation that already discovered pays nothing more.narrowToDerivationInputs(moved, inputs)(new, exported): the pure split intochanged(inputs) andignored(under the roots, not inputs).inputsnull keeps everything — the superset reading.baseDrift({ cwd, inputs }): reads the same three-dot diff, then narrows it by theinputsits CALLER names — a set, or a function asked only once something moved under the roots. The reading gains anignoredfield besidechanged(null= not narrowed), and onlychangeddecides loud vs quiet. The CLI's deriving modes (--commands,--ran, the human list) hand inliveDerivationInputs, which costs nothing extra because the derivation discovers the same tree through the same memo;--tierreads no workflow by contract, so it hands in nothing and its loud reading says it was not narrowed. A first draft letbaseDriftdiscover on its own: a battery of--tierchildren on a stale tree then paid a ~14 s discovery each and the full self-test ran 24 minutes — so the choice moved to the caller. A discovery that throws degrades to no narrowing (over-report, never under-report).driftLines: the quiet branch names what it set aside (N path(s) under the surface moved too, none of them an input of this answer: …, first three named), so the quiet reads as a measurement and not as the silence a level tree gets. A reading that set nothing aside renders byte-identically to before.--commandsderives the same 30 families on a51cd08 and on 4b203a9.The card's named specimen,
scripts/sdui-manifest.sources/**, had already been retired onmainby e931621 (2026-10-27T01:15Z, #26367) before the seat's premise check — see Acceptance notes — so the live specimens arescripts/sdui-manifest.record.json(moves on every ui landing that regenerates the manifest) andscripts/adr-anchors/*.json(moves on refactor landings); the fix closes the class rather than the one path.Tests
node scripts/pm/dispatch-gates.mjs --self-test(both tiers) on head 4b203a9:✓ dispatch-gates self-test: 2224 cases pass.(fast 1935 case(s) in 353.9s · slow 289 case(s) in 974.3s).pull_request/merge_group(scripts/pm/check-dispatch-gates.mjs, #22991) — so this PR's check-runs do not exercise the new temporary-repository and LIVE cases; this local two-tier run is their evidence, and the hourlyschedulerun is the first CI run that will. The full run also caught two slow-tier-only ordering bugs in the first draft of these cases (a case reading a fixture above its declaration); fixed in 9a12ebe and 4b203a9, and every identifier the new cases read was then checked declared-before-read. New cases in the base-drift section, one per direction the order asked for and the live pins the measured set needs:scripts/sdui-manifest.sources/core.jsonunder the roots; the raw reading reports it (the departure pin: what used to go loud); narrowed by the inputs the same tree readschanged: [],ignored: [that record], and renders the quiet two-line shape naming the set-aside record, noSTALE TREE; a foreign checkout handed no inputs is not narrowed.scripts/check-thing.mjsstill goes loud and is named alone (Stale here: scripts/check-thing.mjs) with the record still set aside; a workflow (.github/workflows/lint.yml) and the rootpackage.jsonmoving upstream go loud, both named.scripts/(265 of 442 on 4b203a9), so the narrowing is not vacuous;liveDerivationInputs()answers that same set; andscripts/sdui-manifest.record.jsonis tracked and NOT an input.Not narrowed to what the answer is made of … A --commands or --ran run measures the inputs and lists only those., a narrowed one does not.Acceptance notes
scripts/sdui-manifest.sources/does not exist onmain— retired by e931621 (2026-10-27T01:15Z, "retire the per-package SDUI manifest records", ci(sdui): retire the per-package SDUI manifest records; CI's regeneration holds freshness (#26137 PR B) #26367), after the card was filed (2026-10-26T21:5xZ) and before the seat's read-only premise check at b53322d (git ls-tree b53322d5 scripts/listsscripts/sdui-manifest.record.json,check-sdui-manifest.mjs,gen-sdui-manifest-node.mjsand nosdui-manifest.sources). The mechanism the card names holds unchanged: the wholescripts/root was counted, and generated records under it still move on landings in other lanes —scripts/sdui-manifest.record.jsonat 1bbb7bf (ui),scripts/adr-anchors/*.jsonat b7f8a2e, 2e7127b (refactor) — and the derivation opens none of them (measured: 265 of 442 trackedscripts/**files are inputs on 4b203a9). The card's "Wanted" is met for the class;premise_still_valid: truewith that correction.DERIVATION_SURFACE— gate scripts resolved through package manifests (packages/spec/scripts/check-*.ts,packages/lint/scripts/*.mjs),packages/spec/package.json, and program-text reads such as.claude/skills/pm-dispatch/SKILL.md. The diff stays scoped to the roots, as the ruling's "nothing wider" asks, so an upstream change to one of those still reads as off-surface, exactly as before this PR. Widening the diff to the measured set would be the honest next step; it is a separate decision.fix:remedy in the third unmeasured door still prints the rawgit diff --name-only … -- ROOTScommand: it is the superset a reader can run by hand; the narrowing is applied on top of it and is not something a shell one-liner can reproduce.dispatch-gates.self-test.mjsupstream counts as an input (the engine closure includes the battery) — a conservative choice: it does not move the answer, butcheck:pm-dispatch-gatesruns it, and over-reporting is this file's degraded direction.Verification
Three real landings replayed on throwaway checkouts (a
--sharedclone of this worktree withrefs/remotes/origin/mainpinned at the landing and HEAD at its parent; "before" = that tree's own engine,--tier; "after" = this PR's engine module closure committed on top, run in a deriving mode,--commands):1bbb7bf — a ui landing whose only movement under the roots is
scripts/sdui-manifest.record.json(the card's class)Before:
After:
b7f8a2e — a refactor landing whose only movement under the roots is an ADR anchor record
Before:
STALE TREE … 1 file(s) it derives from CHANGED/Stale here: scripts/adr-anchors/packages__objectql__src__engine-execute-transactions.ts.json.After:
none of the commit(s) this tree can SEE touched what this answer derives from … 1 path(s) under the surface moved too, none of them an input of this answer: scripts/adr-anchors/packages__objectql__src__engine-execute-transactions.ts.json.e931621 — the sentinel retirement itself: two workflows and three gate scripts moved beside 27 records (the control that must stay STALE)
Before:
STALE TREE … 32 file(s) it derives from CHANGED/Stale here: .github/workflows/ci.yml .github/workflows/lint.yml scripts/check-changeset-no-major.mjs scripts/check-sdui-manifest.mjs scripts/gen-sdui-manifest-node.mjs scripts/sdui-manifest.record.json … +26 more.After:
STALE TREE … 5 file(s) it derives from CHANGED/Stale here: .github/workflows/ci.yml .github/workflows/lint.yml scripts/check-changeset-no-major.mjs scripts/check-sdui-manifest.mjs scripts/gen-sdui-manifest-node.mjs— the verdict for a real derivation-input change is unchanged; only the records left the list.The
--ranreconciliation on this branch, head 4b203a9 (no drift line: the tree was level with the fetchedorigin/mainwhen it ran):Derived gates — 30 families, run locally on 4b203a9, reconciled with
--rancheck:dual-build-cjs-loadsis not in this card's derived list.--tieron each throwaway tree now prints the raw list plus the not-narrowed line (e.g. 32 files on e931621), by design. No package is touched (no ① closure, no ② package suite);pnpm lintas a repo-wide sweep is CI's run —eslint scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.self-test.mjsexits 0 locally.Human Minimum
packages/specholds no runtime logic. (n/a — no schema touched)pnpm lintpasses. (eslint on the two files, exit 0; the repo sweep is CI's)main, one pull request.scripts/pm/**publishes nothing —skip-changeset)packages/spectouched: its generated artifacts are regenerated and committed. (n/a)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQwxbZ5vD35MzQ8UqhhTJB