fix(devx): declare check:merge-driver's manifest population, and re-measure the bounded-subtree walk specimen - #15538
Merged
Conversation
…easure the bounded-subtree walk specimen Two cards on the dispatch derivation, folded because both land in `scripts/pm/dispatch-gates.mjs`. Card relations are declared once in the PR body; this branch squashes, so no commit here carries a closing keyword. ## The manifest population (#15501) `git-merge-regen --self-test` refuses a generator with no recorded merge disposition, and the population that refusal sweeps is the MANIFESTS: the root `package.json` plus every workspace member's, read for their `gen:` / `check:` rows. What the family declared to `scripts/pm/dispatch-gates.mjs` was the artifact paths `scripts/regen-artifacts.mjs` carries, imported one level down — the generators ALREADY routed. So the one class of card the refusal exists to catch (a card that ADDS a generator, touching a manifest and a new `scripts/*.mjs`) was the one class the derivation could not name, and the gate fired a cycle late, in CI, on every card of that shape. Measured on 615fac3, before: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.json → 0 lines naming check:merge-driver and after: `pnpm check:merge-driver` is listed, for the root manifest and for a member manifest alike. The repair is the population declaration, in the idiom the derivation already supports and beside the read it describes — `ROOT_FILE_WATCH_HINTS` for the root manifest in the subtree spelling `hintCovers` accepts, `DECLARED_WATCH_HINTS` for the member manifests as full-path glob literals, both following `check-workspace-manifest-cycles.mjs`, which declares the identical population. No hand list of generator names (that ledger is `regen-artifacts.mjs`'s, and this gate already reconciles it two ways), and no self-test case edited to pass. A twelfth self-test callee, `reconcileManifestPopulation`, holds both declarations against the enumerator's LIVE answer in both directions — every member manifest covered, no declared pattern covering zero, the root literal's collapsed form equal to the path the gate opens for the root owner — with its roster entry and floor (11 → 12), since nothing else in the repo can redden when a declaration read by another tool goes stale. Two dispatch-gates self-test cases per direction, pinned against the LIVE tree through the same discovery pass `derive` runs: a change set touching the root or a member manifest derives the gate, an unrelated brand-new `scripts/*.mjs` does not, and an in-place ablation shows it is the manifest declaration doing it. ## The bounded-subtree walk specimen (#15510) Two sentences cited `check-self-test-workflow-commands.mjs` as the LIVE specimen for `repoRootWalkSpelling`'s negative direction. The outcome they report is re-measured and unchanged — the limbs select five of the six candidates, and that gate is selected by none — but the REASON was written as the shape of that gate's walk, and a gate can stop holding a walk without the census moving. The census sentence now states the population (a bounded subtree, `scripts/`), which is what the remedy turns on; its limb attribution is corrected too, measured: three by limb A, one by B, one by C, not "limbs A/B". The fixture-side comment now names two measured specimens at two different walk roots — `check-self-test-wired.mjs` (`walkScripts`, seeded at `scripts/`) and `check-spec-parsed-alias.mjs` (`walkZodFiles`, seeded at `packages/spec/src`) — and two LIVE cases read them off their real source, so the claim cannot rot back into prose pointing at a file with no walk in it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…rge-driver-derivation
baozhoutao
marked this pull request as ready for review
September 4, 2026 20:17
Contributor
Author
|
ACCEPT — PM seat Reviewed against the three-dot diff
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15501
Fixes #15510
Two folded cards, both on
scripts/pm/dispatch-gates.mjs. Branched from615fac3a0; currentorigin/mainmerged in, so every reading below is from the tree at the head this PR carries.#15501 —
check:merge-drivercould not be routed to a card that ADDS a generatorgit-merge-regen --self-testrefuses a generator with no recorded merge disposition, and the population that refusal sweeps is the MANIFESTS: the rootpackage.jsonplus every workspace member's, read for theirgen:/check:script rows (reconcileGenerators, which builds[{ dir: '.' }, ...workspacePackages(REPO_ROOT)]). What the family declared to the derivation was the artifact pathsscripts/regen-artifacts.mjscarries, imported one level down — the generators ALREADY routed. A card that adds a generator touches a manifest and a newscripts/*.mjs, and neither is in that population until the card lands the very row it is being asked to add. So the one class of change the refusal exists to catch was the one class the derivation never named, and the gate fired a cycle late, in CI.The repair
The population declaration, in the idiom the derivation already supports, beside the read it describes — never a rule inside
dispatch-gates.mjs, and never a hand list of generator names (that ledger isregen-artifacts.mjs's, and this gate already reconciles it in both directions):ROOT_FILE_WATCH_HINTS = ['package.json/**']inscripts/git-merge-regen.mjs— the root manifest in the subtree spelling, becausehintCoversrefuses a bare single-segment literal as too generic and the glob suffix collapses back to the filename and matches it exactly. Same idiom ascheck-workspace-manifest-cycles.mjs'spnpm-workspace.yaml/**andcheck-turbo-task-graph.mjs'sturbo.json/**.DECLARED_WATCH_HINTS = ['packages/**/package.json', 'apps/**/package.json', 'examples/**/package.json']— the member manifests as full-path glob literals. Each carries its glob in a NON-FINAL segment, sohintCoversjudges it as a PATTERN reaching manifests only, never the thousands of files under those roots (checked directly:packages/**/package.jsoncoverspackages/spec/package.jsonandpackages/adapters/hono/package.json, and does NOT coverpackages/spec/src/index.tsor the rootpackage.json).A twelfth self-test callee in that file,
reconcileManifestPopulation, holds both declarations against the enumerator's LIVE answer in both directions — every member manifest covered, no declared pattern covering zero, no member pattern reaching the root, and the root literal's collapsed form equal tomanifestFor('.'), the path the gate really opens for the root owner. Its roster entry and floor came with it (SELF_TEST_BATTERY_FLOOR11 to 12,registerCaseas the first statement), since nothing else in this repo can redden when a declaration another tool reads goes stale.Two self-test cases per direction in
dispatch-gates.mjs, pinned against the LIVE tree through the same discovery passderiveruns, with the probe table pinned to its own length so an emptied table cannot run zero cases and read like a pass. No existing case was edited.Measurements
(a) The derivation, before and after, on a
package.jsonchange setnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- package.jsonBEFORE, at
615fac3a0: 9 commands, 0 lines naming merge-driver.AFTER: 10 commands. The full diff of the two command lists is one line:
An owner manifest routes too —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- packages/plugins/plugin-auth/package.jsonlistspnpm check:merge-driver; a brand-new unrelatedscripts/*.mjsdoes not (0 lines).Note the delta is exactly ONE family.
check:generatedandcheck:docsimportcheck-regen-pending.mjsand did not gain the manifests: the hint-import widening is one level, and these declarations live ingit-merge-regen.mjs.(b) The false-positive cost, over every currently OPEN PR
29 open PRs, file lists enumerated via REST, the derivation run per change set, before and after:
check:merge-driverExactly one PR newly derives the gate: #15392, which edits the root
package.json. That is 1 of 29, 3.4% — far below the third that would have made the manifest-wide hint the wrong trade and sent this back asneeds_decision. Only 3 open PRs touch any manifest at all (#15492, #15392, #15334); the other two already derived the gate through the artifact population.Price when it does fire, measured on this (shared, contended) box:
node scripts/git-merge-regen.mjs --self-testat 1.92 s / 1.94 s / 1.96 s over three runs, and the wholepnpm check:merge-driver— both self-tests — at 6.5 s.(c) The two population gates, before and after
pnpm check:declared-population-live— unchanged, and that is the honest reading: this family already declared a population that reached the tree, so the count cannot move.pnpm check:watch-hint-literal— two declarations added, on two rostered names:pnpm check:pm-dispatch-gates— the case count RISES by the added cases and no existing case was touched: 1402 to 1415 (+13).The ablation, both legs
Reword the root declaration back to the bare
'package.json'the pin exists to catch, and the new callee reds; restore, and it is green again. The mutation was proved on disk by grepping for the injected and the deleted text (1 / 0) and bygit hash-objectdiffering from the HEAD blob; the restore was proved bygit hash-objectequal to the HEAD blob ANDgit diff HEADempty. Absolute paths and anEXIT INT TERMtrap throughout.#15510 — the bounded-subtree-walk specimen, re-measured
Two sentences cited
check-self-test-workflow-commands.mjsas the LIVE specimen forrepoRootWalkSpelling's negative direction.The outcome sentences are re-verified and stand. Running
repoRootWalkSpellingover the six candidate gates on this tree: five are selected, that gate is selected by none, so the census of six is still a census of five.One outcome detail was stale and is corrected, measured. The prose said "limbs A/B select five". It is three by limb A (
check-nul-bytes,check-refd-timer-probe,check-closing-keyword-parity), one by limb B (check-watch-hint-literal) and one by limb C (check-comment-mask-corpus) — the last moved from B to C when limb B was tightened to refuse a path BUILD, and the census sentence was left behind.The REASON is repaired at the level that does not rot. The census sentence now gives the sixth gate's POPULATION (a bounded subtree,
scripts/), which is what its remedy turns on, rather than the SHAPE of a walk it can stop holding. On this tree it does still hold that walk; on PR #15506's head it holds none, and the sentence is true either way.The fixture-side comment now names two MEASURED live specimens at two different walk roots, so one file's repair cannot empty the claim again:
scripts/check-self-test-wired.mjswalkScripts(scriptsDir)join(ROOT, 'scripts')scripts/check-spec-parsed-alias.mjswalkZodFiles(SPEC_SRC)join(ROOT, 'packages/spec/src')Both read
nullfromrepoRootWalkSpellinghere and on PR #15506's head, and two new LIVE self-test cases read them off their real source rather than off a string typed in the file — a file that has gone is NOT MEASURED, never a quiet pass. The hand-written fixture case beside them is unchanged.Verification — every exit code captured before any pipe
All 25 other commands
dispatch-gatesderives for this change set at head4cfa319a7were run; all exited 0. Verdict lines from the load-bearing ones:The dispatch-gates self-test was run DETACHED, as
scripts/pm/check-dispatch-gates.mjs's own header instructs for an agent container, and both long runs went throughscripts/pm/os-verify-lock.sh; the lock's verdict lines:VERDICT command-exit 0 · held the lock 391s (6m31s) · waited 41sfor the first self-test run, andVERDICT command-exit 0 · held the lock 490s (8m10s) · waited 112s (1m52s)for the final-head run that carriedpnpm lintand the self-test together. Bothpnpm lintandpnpm check:pm-dispatch-gateswere run AFTER the last commit, on head4cfa319a7, so the greens above are greens on the tree this PR carries.scripts/-only change, publishing nothing from any package, soskip-changeset.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code