Uh oh!
There was an error while loading. Please reload this page.
Derive a gate for the surface the program it RUNS reads - #14012
Conversation
scripts/pm/dispatch-gates.mjs already follows a gate to a module it IMPORTS and appends that module's declared population to the gate's own. It never followed the other edge by which a gate reaches a program in this repo: spawning it. check:pm-dispatch-gates runs the tool itself, and running it readdirs .github/workflows -- so a workflows-only diff derived 18 families, none of them the one gate whose subject is the file being added. The follow now traverses the exec edge through the same three pieces as the import edge: declaredInheritedPopulation narrows what a follower inherits, hintOrigin labels it, and a target that is itself a gate file is refused. resolvePathExpression gains one hop so a join component spelled as a named constant resolves, which is the idiom a gate uses to name the program it runs. Measured over 196 families x 7673 tracked files: +32 covered pairs, 0 lost, 0 re-attributed, exactly one verdict moved. Self-test 1073 -> 1090 cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
ACCEPT — Zone 1.1 — the fork answered NO, so the card is liveOn current ⭐⭐ Zone 1.2 — answered STRUCTURALLY, and I checked the diff for the shape I bannedI ruled that a delivery which adds a gate name to a table is rejected. It adds none. I grepped the patch: 5 added lines mention ⭐ And one of those assertions is an anti-gaming control I did not ask for: coveringKey(pmEntry,pmSurface[0])?.via===`gate source via the program it runs, ${PM_TOOL}`with the reason stated inline: "Without this half the case above passes on any key at all, including one the gate already had — which is exactly the reading that would let someone 'fix' this by widening an unrelated literal." ⇒ The test pins matched for the right reason, not merely matched. That is the difference between a pin and a rubber stamp, and it is the direct answer to "why can this class not recur under a different gate name." ⭐⭐ The mechanism: the class closes at the EDGE, and the file had already said soThe general rule the card asked for already existed in this file — ⇒ ⭐ The PR closes the follow over the exec edge through those same three pieces, adding no fourth mechanism. So any gate that runs an in-tree program inherits that program's declared population forever, with no list to maintain. That is a class fix, ⛔ not an instance fix. ⭐⭐ My Zone 2 A was FALSIFIED — and the falsification is worth more than the fixI hypothesised a gate's read surface is mechanically derivable from source, and told the dev to try to break that first because everything depended on it. Measured:
⇒ The rule as the card literally states it — "a gate whose read surface includes a surface must be derived for it" — is not mechanically reachable in general. The reachable reformulation, and the one shipped, is per-EDGE: close the follow over every edge by which a gate reaches another program, and let the followed program declare what a follower inherits. ⇒ That reshapes the follow-up sweep from a hunch into a criterion: judge it per edge (which gates reach a program this follow does not traverse), ⛔ not per read-surface. Blast radius — the deliverable, over the whole fleet196 families × 7673 tracked files, counted through
29 of the 32 new pairs are that one family's, one per workflow file. ⇒ A change to a shared resolver that moves exactly the row it was meant to move. ⭐ Reflexivity, which I asked for because the diff edits the deriving tool: Self-test 1073 → 1090, and a pre-existing case was REPAIRED rather than leftThe 17 new cases pin the family being present for the surface, ⛔ not that derivation ran — including non-vacuity in both directions and a live refusal case. ⛔ No ablation was run, and none is claimed — with a reason I accept: the regression is a presence assertion whose failure direction is pinned directly (silent vs matched on a named family), and that pre-existing case going red mid-work is observed proof these cases can fail. ⇒ Better than a ceremonial mutation, and honestly labelled either way.
|
Status — CI is now green (29 checks, zero pending, zero failed, latest-run-per-check-name). Arming is BLOCKED on the MCP write limiter, ⛔ not on anything in this PR.
⛔ Not routed around: REST ⇒ Retrying on an interval. Sibling PR #14009 is in the same state. Both are reviewed, ACCEPTed, green and governed-surface-clear; the only thing outstanding is the un-draft. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#13511
The defect, re-derived against current
origin/mainTriage's fork question, answered first because it could have ended this card. On
c0770d0b7, for a workflow-only surface (PR #13500's exact one,.github/workflows/merged-branch-reaper.yml), both input modes agree:So #13501's always-runs tail does not fold this card in — the tail answers "which unconditional steps does the derivation name nothing for", and this gate is a named, discoverable, paths-filtered family.
lint.yml:670spells itpnpm check:pm-dispatch-gates, so the #13462 harvest seam could not have dropped it either. Live defect, confirmed by measurement rather than by taxonomy.Its verdict was
silent, and the residue says exactly why:Its read surface is
.github/workflows/**— it runs the tool, and the tool readdirs that directory. Nothing it declares could ever cover a path there.The design question the card poses, answered structurally
⛔ This PR does not add
check:pm-dispatch-gates— or any gate name — to any table. The family prohibition is the reason, and the answer has to say why this class cannot recur under the next gate's name.The general rule already exists in this file.
firstPartyImportTargetsplushintsOfModulefollow a gate to a module it IMPORTS and append that module's declared population to the gate's own;declaredInheritedPopulationlets the followed module narrow what a caller inherits;entry.hintOriginlabels the inherited hint so it never travels as a claim the gate made itself. It is a closure over an EDGE, not a list of gates.It was implemented over one edge.
readProgramTargetsInSource' docblock already names all three and says they are one fact:#13000added the second (stage) for the IDENTITY key only. Execute was never implemented on either axis — andcheck-dispatch-gates.mjsreaches the tool byspawnSync, which is why this one gate fell through. The tool's own module body has said so since #11556, in the comment above itsinherited-populationline: "which reaches the tool byspawnSync, so the follow's 'never open a module that is itself a gate file' rule does not cover it."⇒ This PR closes the follow over the exec edge, through the same three pieces, adding no fourth mechanism. The class closes because the unit is the edge: any gate that runs an in-tree program inherits that program's declared population, today and for every gate written after this one, with nothing to keep in step. There is no name here to forget to add.
One supporting change, in the resolver rather than the follow:
resolvePathExpressionread ajoin()BASE throughctx.namesbut required every COMPONENT after it to be written out as a literal. That refusedjoin(ROOT, TOOL)— the idiom a gate uses to name the program it runs — so the whole expression came backunknown.componentLiteraladds one hop, to a literal only, refusing a rebound or unbound name.What was NOT changed, deliberately
scripts/pm/dispatch-gates.mjshas carriedinherited-population .github/workflowssince [finding] dispatch-gates.mjs is a followable non-gate module, so its join bases and tier globs are inheritable — 2553 fabricated pairs for the next gate that imports it #11556, written for importers; this edge is what lets a caller that SPAWNS it read that declaration too. Nine literals spelled, one inherited — the eight join bases and tier globs stay refused, which is the [finding]dispatch-gates.mjshas 28 self-test cases and no CI job runs any of them #8162 fabrication this file already priced at 2632 pairs.scripts/docs-audit/affected-docs.mjsis spawned by two families and is itself a gate file, so it is refused on the run edge exactly as on the import edge. Live, and pinned.execSync,exec) are not scanned: they take a command STRING, whichresolvePathExpressionrefuses by construction, so admitting them would be a scan that cannot return a target.Blast radius — the deliverable, not a footnote
Over 196 discovered families x 7673 tracked files, counted through
coveringKey(the key the printed block renders from), before vs after, both readings taken from a worktree atc0770d0b7:The one verdict is
check:pm-dispatch-gates,silent->matched. 29 of the 32 new pairs are its — one per workflow file, which is the defect exactly. The other 3 arrive through the component hop, on the read key next door. The component hop's own cost across every consumer of the shared resolver: anchored read targets 73 -> 101, of program text 5 -> 15, and the scratch-dir scan does not move at all (17 in-tree / 154 unresolved, both unchanged) because its bases are anchors, not named constants.This is the direction the card worried about in reverse: a rule that named twenty gates for every diff would be useless in a different way. It names one more gate, for the 29 files that gate reads.
The regression pins the DIRECTION, not that derivation ran
The defect does not crash and does not report nothing — it hands a dev a coherent, plausible, INCOMPLETE list, so every "it derived something" assertion passes straight through it. The 17 new cases assert the family's PRESENCE for the surface:
--self-test: 1073 cases -> 1090, all green. One existing case was repaired rather than left: the reconstruction invariant ("a family's hints are exactly own PLUS imported") modelled one edge and reportedoff: check:pm-dispatch-gatesthe moment the other fired; it now models both, which is what makes it an invariant rather than a description of one edge.Gates — derived for this diff, and all of them run
Every reading below is from the final commit,
8448b11d8.comm -23(derived minus run) is empty; 14 derived, 14 run.13 green, quoting each gate's own verdict line, not a bare exit code:
✓ dispatch-gates self-test: 1090 cases pass.(pnpm check:pm-dispatch-gates, the reflexive one)✓ check:entry-guard: 196 scripts/ file(s) — every entry guard goes through invoked-as.mjs✓ check:parse-guard: 195 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.✓ check-watch-hint-literal: 37 declaration(s) across 4 rostered name(s)✓ check-self-test-wired: every one of the 159 script(s) CI runs that ship a --self-test has that self-test run by CI.✓ check-bash32-floor,✓ check-cli-command-ids,✓ check-agent-test-spelling,✓ check-pnpm-filter-targets,✓ check-shard-attestation,OK: check-ci-filter-parity,OK: check-cross-package-test-inputs(x2 spellings)One is NOT MEASURED, by its own declaration rather than by my judgement —
node scripts/check-test-completeness.mjsexits 3 withPREREQUISITE NOT MET, because it grades a savedturbo run testlog CI passes it and the derived family names it with no argument. Its own text says: "the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."Reflexivity. This diff edits the deriving tool, so: the change alters nothing it derives for its own diff —
--commands scripts/pm/dispatch-gates.mjsis byte-identical before and after (14 lines), because that path was already matched through the gate's own declared literal. Also run beyond the derived set, since the diff edits a gate script:pnpm check:nul-bytesgreen, plus a direct control-byte scan of the edited file (clean).Changeset: none. The diff is
scripts/**only, which matches no workspace publish glob, and the root package isprivate.skip-changesetapplied additively.Governed surfaces: clean — the diff is one file,
scripts/pm/dispatch-gates.mjs. Nodocs/adr/**, no.claude/**, noskills/**, noAGENTS.md, noCLAUDE.md.Out of scope, reported not built
The sweep for OTHER gates whose read surface exceeds their derived surface is triage's deliberate follow-up and is not in this PR. What this work measured about it, for whoever picks it up: 41 of the 196 families spell no path literal at all, and across the scanned corpus 154 of 171 path expressions resolve to nothing readable. ⇒ a general rule spelled as "derive every gate's read surface" is not mechanically reachable; the rule that IS reachable — and is the one shipped here — is "close the follow over every edge by which a gate reaches another program, and let the followed program declare what a follower inherits."
Generated by Claude Code