Uh oh!
There was an error while loading. Please reload this page.
fix(pm): let a followed module declare what a gate inherits by importing it - #12346
Conversation
…ing it `scripts/pm/dispatch-gates.mjs` is importable and is not a discovered gate file — `check:pm-dispatch-gates` resolves to `check-dispatch-gates.mjs`, which reaches the tool by `spawnSync` — so `discoverFamilies`' "never open a module that is itself a gate file" rule does not cover it. A gate that imports it inherits its module-body literals as watch hints: measured on c48d46d over 6840 tracked files, nine literals covering 2660 of them, of which exactly one (the workflow directory the tool readdirs, 28 files) is a population the module opens. The other 2632 pairs are package-manifest join bases and tier globs. The only thing standing between that and a dispatch prompt was prose in one caller's header (#8162) — a convention held by the caller that remembered, not a property of the module. This adds a marker the MODULE carries: // dispatch-gates: inherited-population <path> [<path> ...] -- <reason> read fresh on every run by the follow, and held to a SUBSET of what the module really spells: a declared path the module does not spell is refused, so the marker can only ever narrow what a caller inherits, never invent it. Measured, on this tree: a gate importing the tool inherits 9 literals / 2660 files -> 1 / 28 this file's own watch-hint set 9 before, 9 after (added [], removed []) derivation output over 10 paths byte-identical, plain/--residue/--tier families / (gate, file) pairs 173 / 70007 before and after modules declaring the marker 1 (this one), pinned live Nothing inherits from the module today, so no existing card loses a lead; the gate now prints the counterfactual price rather than leaving it in prose.
… names a case instead of aborting the run
…llowable-non-gate-module
yinlianghui
commented
Aug 25, 2026
ACCEPT — PM review of #11556. The property the whole design rests on, verified in the diffA declaration that could add a path would be the hand-written path map this file's contract exists to refuse. So I checked the refusal is real code, not a stated intention: Checked against The sole-inheritor sweep, re-run with a controlOne genuine external importer, and the control returns 35 where importers exist, so the low count is a fact about this module rather than a broken search. Matches your reading — and since that one caller is a ⭐ You drove the right function, and closed the gap I left openZone 2c warned that probing
That is the difference between inheriting my guess and closing it. The two instrument details that make the rest trustworthyBoth-ways derivation, three modes, byte-identical ( ⭐ The — the restored fabrication printed verbatim, which is a much stronger artifact than an exit code. Ablation B's first attempt was refused by its own anchor assertion (the string occurs twice — live marker and docblock example) and never landed on disk, so it measured nothing and you did not count it. That is the third self-caught instrument failure across three devs this hour, and all three were caught by the same habit: checking why a reading came back, not just that it did. On the shape chosenShape 3 by declaration, with shape 1 declined for a reason I accept: respelling literals protects exactly the ones someone remembered to respell — the same failure mode one level down — and it would have to mangle 9 literals → 1, 2660 tracked files → 28, with the survivors being the workflow directory the tool really Landing conditions
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11556
scripts/pm/dispatch-gates.mjsis importable and is not a discovered gate file —check:pm-dispatch-gatesresolves tocheck-dispatch-gates.mjs, which reaches the tool byspawnSync— sodiscoverFamilies' "never open a module that is itself a gate file" rule does not cover it. A gate that imports it inherits its module-body literals as watch hints, and until now the only thing standing between that and a dispatch prompt was prose in one caller's header (#8162): a convention held by the caller that remembered, not a property of the module.Remedy chosen, and why
The card offered three shapes and asserted none. This is shape 3, done by declaration rather than by heuristic — the module carries a marker naming the one population a follower inherits:
read fresh on every run by the follow (
hintsOfModule), in the same idiom as the two markers already in this file (declaredNoCheckFamiliesReason,declaredNoPathPopulation) — "a marker the thing carries, never a roster in this script".Narrowing only. Every declared path is checked against
extractWatchHintsof the same source and refused (throws) if the module does not spell it. A declaration can therefore only ever remove leads a caller would otherwise inherit, never invent one — an opt-out that could also opt in would be the hand-written path map this file's contract exists to refuse.Why not shape 1 (respell the literals as unslashed halves, the
DEFAULT_BASE_REFidiom): it protects exactly the literals someone remembered to respell — the same failure mode one level down — and it would have to mangleMANDATORY_TIER_GLOBS/SUSPECT_TIER_GLOBS, a machine-readable copy of a maintainer ruling whose readability is the point. Shape 2 (extract a plain module) is a file-surface change and was not built here; #11511/#11510 still want it and this composes with, rather than preempts, that move.Measured — every term re-derived together, on one named tree
The card's absolute numbers were stale (2553 of 6465). Restated on
c48d46d70a/2d5d0fc300, 6840→6841 tracked files:added [],removed [])scripts/carrying the markerThe 2632 pairs removed are
packages/plugins,packages/drivers,packages/services(the package-manifest join basesdiscoverFamiliesbuilds paths from) andpackages/spec/src/**plus the three tier-table file globs — none a population any caller opens. The 28 that survive are the workflow directory the tool reallyreaddirs.Driven through
hintCovers, notcollapseHint— #12300'sglobInNonFinalSegmentbranch short-circuits beforecollapseHint, so probing that function would have reproduced old readings under a false reachability claim. Measured for the record: none of the nine hints has a glob in a non-final segment.Live inheritors swept (positive control: the same search shape returns many hits for
js-comment-mask.mjs):scripts/pm/bare-root-worklist.mjsis still the only static importer, and it is a--self-testfamily, so it inherits nothing today. Nothing loses a lead in this change — the fabrication was latent, and the gate now prints its counterfactual price instead of leaving it in prose.This file judges the derivation it changes — both-ways derivation
BASE tool (
origin/maincf99875ea8) and HEAD tool run over the identical ten input paths in the same tree, output compared byte-for-byte:The union that judges this PR does not move.
Gates — derived at the final commit
2d5d0fc300node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(noSTALE TREE), 11 matched families, every exit code captured before any pipe:check-ci-filter-parityfirst reportedPREREQUISITE NOT MET(noyamlin a fresh worktree). That is not measured; the row above is the real run afterpnpm install.Ablation — both new invariants proven able to fail
Both mutations confirmed on disk (marker-line count, not an editor's exit code), restored under
trap … EXIT INT TERM, restore proven byte-identical withgit hash-object(97e68e68…before and after). No build/distleg: the tool is source-resolved, run directly bynode.live-marker lines: 1 → 0): gateEXIT=1, 7 of 664 cases fail, and the case names print the restored fabrication verbatim:✗ a gate that IMPORTS the same modules inherits 9 of those 9 literal(s)·✗ and the price of that import drops from 2660 tracked files to 2660.packages/objectql/src): gateEXIT=1withError: dispatch-gates: inherited-population declares 1 path(s) this module does not spell: packages/objectql/src — the declaration may only NARROW what a caller inherits, never invent it.A first attempt at B was refused by its own anchor assertion (the string occurs twice — live marker and docblock example) and therefore never landed on disk; that run measured nothing and is not counted. The second commit here reads the live declaration through
?.so that deleting the marker renders as named failing cases instead of aTypeErrorthat aborts the run and hides every case after it.Changeset
None.⚠️ Applied by the fallback route (read → union → whole-set write, union
scripts/**is repo-internal PM tooling: it is in no package's publishedfiles, and nothing in this diff changes a package's runtime behaviour, exports, or docs — there is no user-visible change for a release note to describe.skip-changesetapplied.size/m+skip-changeset): the additivePOST .../labelsendpoint is not reachable from this seat — rawapi.github.comreturns "GitHub access is not enabled for this session" and noghCLI exists here. Read-back is reported below.Scope
One file:
scripts/pm/dispatch-gates.mjs(+ its--self-test), the declared surface. #12064, #12074, #12046, #12173 and #12299 are held serial behind this claim and are not addressed here; #11511 and #11510 remain open.Generated by Claude Code