chore(cli,pm): root the NODE_ENV serve fixture at packages/cli/tmp/, with the leftover hazard pinned - #12741
Merged
Merged
Conversation
…with the hazard pinned Three parts, indivisible by the ruling: the outlier fixture's root moves from packages/cli/test/ to packages/cli/tmp/ where its three siblings already sit; the bespoke .gitignore entry written for the old root is deleted; and the fixture's "why this root" docblock is rewritten so the move does not land under a rationale that no longer explains it. The docblock now separates the two reasons that were previously conflated. The static-import constraint is real and rules out os.tmpdir(), but measured, it does NOT distinguish the two in-tree roots — packages/cli/test/ and packages/cli/tmp/ have the same node_modules ancestor. The root choice is a convention (three of four fixtures, zero bespoke ignore surface), not a technical necessity. dispatch-gates' self-test gains the property that makes the move worth making: a leftover fixture at an uncovered in-tree root reaches the derived change set and names gate families of its own, while one under packages/cli/tmp/ is invisible to the same derivation in the same repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Aug 27, 2026
os-litant
marked this pull request as ready for review
August 27, 2026 16:12
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 #12632
Option A, as ruled on 2026-08-27:
packages/cli/tmp/is the convention for in-treetest fixtures under
packages/cli. The three parts land together, because any two ofthem are worse than the state before.
serve-node-env-production-default.e2e.test.tsrooted its fixture at
packages/cli/test/; it now roots atpackages/cli/tmp/, whereits three siblings already sit (
init-scaffold-authoring-rules,init-template-comments-self-contained,serve-no-artifact).mkdirSync(…, { recursive: true })is added ahead of
mkdtempSync, matching the siblings —tmp/is not tracked, so itneed not exist.
packages/cli/test/tmp-node-env-default-*/plus its comment. Left behind, it would not merely be a dead rule: it would be an ignore
entry pointing at a path nothing writes any more.
root — the failure mode the card was filed to end.
The docblock's actual correction
The old comment gave one reason: the generated config does a real, static
import { AuthPlugin } from '@objectstack/plugin-auth', which resolves only from adirectory with a
node_modulesancestor. That reason is true, and it rules outos.tmpdir(). It does not choose between the two in-tree roots — and the new commentsays so in as many words rather than re-deriving the root from it, which would swap one
false reason for another.
Measured here, with the instrument calibrated in both directions (probe module placed
inside each fixture directory so its own URL is the resolution parent):
packages/cli/tmp/(new)plugin-auth/dist/index.mjspackages/cli/test/(old)os.tmpdir()ERR_MODULE_NOT_FOUNDThe two in-tree roots are indistinguishable to the constraint. So the root choice is a
convention — three of four fixtures, and zero bespoke ignore surface — and the docblock
records it as one.
A first attempt at that probe reported all three roots failing, including the old root
that demonstrably works in CI. The cause was the instrument, not the tree: Node 22 ignores
import.meta.resolve's second argument without an experimental flag, so every caseresolved from the probe script's own directory. That reading is discarded, not reconciled.
The property this exists to keep closed
A leftover fixture in the tracked tree with no ignore rule is not inert.
scripts/pm/dispatch-gates.mjsderives every dispatch's change set from git includinguntracked files, so an unignored leftover joins it and inflates the gate list every other
seat is handed.
Reproduced on this tree, red before green, each leg restored under a trap and proven by
blob hash:
Step 1 answers the card's re-check directly: the bespoke entry was live, not a dead
rule. Steps 2 minus 3 isolate the leftover's own cost: 20 gate families the branch's
real diff does not implicate. Step 4 is the fix.
The card carries 18 from PR #12631's measurement. I could not reproduce 18 on this
tree in any leftover shape:
objectstack.config.tsalone gives 17,package.jsonalonegives 17, both together give 20. Reported as measured and not reconciled — this tool's own
premise is that the family inventory expires same-day (176 families across 28 workflow
files today).
The pin
Four cases added to
dispatch-gates.mjs's own--self-test, which CI runs unconditionallyvia
check:pm-dispatch-gates. They build a throwaway git repo seeded with this repo'sreal
.gitignore— an excerpt would pin the excerpt — plant the same fixture shape atboth roots, and run the real derivation once.
The control is load-bearing and is deliberately the fixture's former root: asserting
only that the covered path is absent would pass against a
.gitignorethat ignoreseverything, or against a plant that never happened. So the uncovered path must come back
visible, and must come back naming gate families.
Falsified in both directions, each ablation flipping exactly its own assertion and leaving
the others green:
✗ the CONTROL reproduces the hazard) andso does the no-bespoke-entry assertion; the coverage assertion stays green.
tmp/rule: the coverage assertion fails; the control stays green.Verification
Union measured on
f4c7a6764, the final commit. 21 green:check:pm-dispatch-gates(723 cases) ·check-self-test-wired·check:cli-test-child-env·check:nul-bytes·check-comment-mask-adoption·check:entry-guard·check:parse-guard·check:test-source-alias·check:type-check-coverage·check:cross-package-test-inputs·check-ci-filter-parity·check:published-files·check:agent-test-spelling·check:pnpm-filter-targets·check:bash32-floor·check:cli-command-ids·check:slot-lookup·check:page-declaration-shape·check:objectql-double-limit·check:type-source-resolution·check-plugin-teardown-shape.check:type-check-debtexits 1 as a prerequisite refusal, not a failure: it names 30workspace dependencies with no built type entry point and refuses to measure a different
world. Recorded as NOT MEASURED. Its sibling
check:type-check-coverageran and reports OK.Declared narrowing. The moved fixture's own three legs were not run locally. They
spawn the built CLI, whose dependency closure is 57 build tasks, and the container's
foreground window is ~10 minutes — one attempt at the gate union was already killed at the
cap. What the move can break is the static import resolving from the new root, and that is
measured above on a real Node resolution with a failing control. The e2e itself is left to
CI, which builds the closure before running it.
No changeset: nothing here reaches a published package's emitted program.
packages/clicompilesinclude: ["src"]withrootDir: "src"and publishesfiles: ["dist", "README.md", "CHANGELOG.md"], so a file undertest/is outside both thecompiled program and the tarball;
.gitignoreandscripts/pm/are repo-root tooling inno package. Labelled
skip-changeset.Generated by Claude Code
Generated by Claude Code