Uh oh!
There was an error while loading. Please reload this page.
Derive a gate's program-text reads as family inputs, so a staged gate reaches the family that runs a copy of it - #13129
Merged
Conversation
This was referenced Aug 29, 2026
os-elon
marked this pull request as ready for review
August 29, 2026 06:14
os-elon
enabled auto-merge
August 29, 2026 06:14
This was referenced Aug 29, 2026
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 29, 2026
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 freeto 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#13000
scripts/pm/dispatch-gates.mjscould not see the dependency where one gate script opens another script's source at a path anchored to its own location — the shapescripts/objectui-changeset-digest.mjsuses to stage a copy ofscripts/check-adr-0087-registration.mjsinto a throwaway repo and run it. A PR that added an import to the staged gate broke the digest's self-test withERR_MODULE_NOT_FOUND, and the derivation had scoredcheck:objectui-changesetsilent for that diff.The classification came first, and it decided the mechanism
The card's fence was explicit:
mkdtempis not the same as stages a sibling gate, and the class size decides between a registry and a convention-trigger. Measured on this tree (5404 tracked sources, 181 discovered families):mkdtempSynccall sitesreadFileSyncinside a write-ish call's args)The two are
scripts/objectui-changeset-digest.mjsandscripts/sync-template-versions.mjs. Everything else in the 8 either mutates a fixture in place, copies apackage.json, or reads a gate's source to assert on it (all twelvedispatch-gates.mjssites are that last kind — live-tree contact, which is a separate card and is not touched here).Why neither mechanism the card proposed
anchoredReadTargetsfindsreadFileSync/copyFileSyncsites, skips those inside comments and inside string literals by position, and resolves the first argument with the module's existingresolvePathExpression. An expression it cannot read returnsunknownand contributes nothing; a resolved path the repo does not track contributes nothing. It reports 5.(c) has (a)'s precision and (b)'s zero maintenance, and it is available only because the tool already owns the resolver —
scratchDirSitesInSourcereads path expressions for a different question and pays for this half.The target restriction, and what it deliberately declines
readProgramTargetsInSourcekeeps only PROGRAM-TEXT targets. The wider reading — every tracked file a gate opens at an anchored path — is not wrong: measured at 81 pairs, 62 of them leads no other key gives, and every one is a file some gate really opens. It is a bigger card. 34 of the 62 land on four files (package.json+12,.github/workflows/lint.yml+9,packages/spec/package.json+8,turbo.json+5), which would take a root-manifest card from 5 leads to 17, and this file's header prices that direction. The line drawn instead is statable: a gate that opens another file's PROGRAM TEXT depends on that program; a gate that opens data it parses is the other question. The refused half is 79 anchored reads of tracked data, and the self-test pins that it is non-vacuous, so a future card widening it reds a case rather than discovering the boundary.Does #13049's mechanism generalise? No — and that was worth checking
PR #13049 taught the derivation to follow
needs.JOB.outputs.NAMEback to itsdorny/paths-filterblock. That hop is declared to declared: a path population already sitting in YAML the tool parses, one reference away. This card's hop is imperative JavaScript that produces no declared population at all, so nothing in #13049 reaches it. The generalisation that does exist is one door over:firstPartyImportTargetsalready follows a gate's./sibling.mjsimport by reading source text, and this is the same species — an undeclared input, spelled in code instead of in an import statement. It is placed beside it for that reason.Worth naming: the staging literal never became a watch hint because
extractWatchHintsrunsmaskSelfTestsfirst, and the staging sits inside the digest's--self-test— which is the invocation CI runs. The two scans want opposite things from the same bytes, so this one masks comments only, and a self-test case pins that the family has no hint covering the staged gate, so the main case cannot pass on a key it is not testing.Verification
The reconstructed original failure, same tree, two tool versions (the diff is one path,
scripts/check-adr-0087-registration.mjs— the original PR's edit was adding an import to it):Whole-tree blast radius, every tracked file crossed with every family, with the new key on and off:
The five are
scripts/check-adr-0087-registration.mjsandscripts/bump-objectui.sh(both tocheck:objectui-changeset),scripts/invoked-as.mjstocheck:template-version-sync,apps/docs/source.config.ts, andpackages/spec/src/meta-spelling/meta-url-data.generated.ts. Zero re-attribution is structural, not luck: the key is consulted last incoveringKey, so it can only fill a hole.Ablation — predicted before running, both legs hit exactly. Predictions were written to a file first; each leg starts from a proven-HEAD tree, the mutation is confirmed on disk by fixed-string counts of both the removed anchor and an injected marker plus a blob-hash change, and the restore is proven by blob hash equal to the HEAD blob plus an empty
git diff HEAD. No build leg applies: the tool is executed from source by path, never through a packageexports, so there is nodist/copy that could serve a stale mutation.coveringKeydiscoverFamiliesA first attempt at leg A was VOID and is reported as such: the on-disk proof used
grep -con an anchor carrying regex metacharacters, the instrument refused rather than reporting, and because that leg returned before its restore, leg B then ran on a tree still carrying mutation A. Both readings were discarded and both legs re-run from a hash-proven HEAD withgrep -cF. The numbers above are from the clean run.Gates. Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon the final commit (noSTALE TREEline; tree atorigin/mainadf70f763b). Union run at8b4156139f, which is the final head.check-test-completeness.mjsis NOT MEASURED, not red: run standalone it printscheck-test-completeness: usage: check-test-completeness.mjs turbo-test-log ...and exits 1 because it consumes a turbo test log CI produces. That false red is already recorded as #13110.Every exit code above was captured by redirecting to a file first and reading
$?before any pipe.Scope
Seven new self-test cases; 879 total. No changeset: the diff is one CI-internal PM tooling file and publishes nothing, and the changeset requirement is a label opt-out in
pr-automation.ymlrather than a path exemption, so this PR carriesskip-changeset.#12983is untouched here — it remains open and is a separate card about that gate's header prose, not about the derivation. One side-finding was recorded as #13126: editing a first-party module a gate script IMPORTS still names no family, measured at 228 unreached (family, module) pairs; it is deliberately not addressed in this PR because 228 pairs need their own pricing.Generated by Claude Code
Generated by Claude Code