test(dogfood): every test file runs in its own temporary working directory - #21919
Conversation
…ctory Showcase boots write .objectstack/data/showcase_external.db relative to the cwd: the declared external datasource auto-connects (SQLite creates the file) and onEnable provisions its federated tables there. From the package directory 92 files left it behind, 7 of them populated, so a later boot's federated state depended on shard composition and file order. A setupFiles module, wired in both projects, chdirs each file into its own directory under a run-level temporary root and restores the cwd in afterAll, where it throws when packages/qa/dogfood/.objectstack/data exists. A root globalSetup clears a stale .objectstack at the start and removes the run root at the end; its teardown judges nothing. Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-authored-by: Claude <noreply@anthropic.com>
… root Under the per-file temporary cwd, probeOrganizations() defaulted its host root to that temp directory, so its skip text told the reader to declare @objectstack/organizations in a /tmp directory's package.json (measured). The module-level probe now passes this package's root, resolved from the module's own location. Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 76c0afd216a96d371d1e40d30d8a3b31dd5ddc40 && git checkout 76c0afd216a96d371d1e40d30d8a3b31dd5ddc40
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e6dc7a240617eaeef9a64e788bf6e5561c107f1b 967ce88af337772d0a40b7d6dfaebfac1b901207 && git checkout -B drift-repro e6dc7a240617eaeef9a64e788bf6e5561c107f1b && git merge --no-ff 967ce88af337772d0a40b7d6dfaebfac1b901207
node scripts/docs-audit/affected-docs.mjs --json e6dc7a240617eaeef9a64e788bf6e5561c107f1b |
Fixes #21914
Clause-②: no
What changes
Every dogfood test file now runs in its own temporary working directory. The suite fails when any file leaves
.objectstack/datainpackages/qa/dogfood.test/per-file-cwd.setup.ts(new) is asetupFilesentry, wired explicitly in BOTH projects ofvitest.config.ts, because inline projects inherit nothing from the root block.shared-showcasekeepsisolate: false; the module still runs once per file there.chdirs into it.afterAllit restores the previous cwd. ThatafterAllis also the guard: it THROWS whenpackages/qa/dogfood/.objectstack/dataexists. The message names the directory, its entries and the remedy. It also says the named file may be a concurrent one on another worker rather than the writer, and whether the directory was already present when the file started.test/per-file-cwd.global-setup.ts(new) is a root-levelglobalSetup. It runs once per run, covering both projects and eachOS_TEST_SHARDslice (measured).packages/qa/dogfood/.objectstack, so a developer's earlier run never reds the suite.provide/inject.shared-showcaseboot keeps its SQLite handles open in the directory of the file that booted it.vitest.config.tswires the two modules. A header section explains why there are two halves and why the guard is not in the teardown.test/enterprise-organizations.ts: the module-levelprobeOrganizations()now passes this package's root ashostRoot, resolved from the module's location (new URL('..', import.meta.url)), not the cwd. This was measured to be needed; see Evidence.No per-file edits. The five files the card names, and the other 87 measured writers, are covered by the module with no change of their own. Test isolation only:
@objectstack/dogfoodisprivate: true, so no published package moves and there is no changeset (skip-changeset).The invariant for every dogfood author
mkdtemp/chdirof its own.new URL('..', import.meta.url),import.meta.dirname), never fromprocess.cwd(). The cwd is a temporary directory.packages/qa/dogfood/.objectstack/datafails the run. That happens through an absolute path built from the package root, or through aprocess.chdir()back to the package directory before a boot. The fix is to write relative to the file's own cwd.chdirinto a temp dir of their own still work, because they restore to the per-file directory. Their ownchdiris now redundant and harmless.Why (measured)
A per-file probe over the whole suite measured 92 test files leaving
.objectstack/data/showcase_external.dbin the package directory, not the five the card names:showcase-demo-personas-loginableandshowcase-demo-personas-membership, which passonEnablein the bundle.onEnableor not.A later boot on the same runner found or missed the federated tables depending on which files ran before it, and that ordering is how PR #21905 went red only on dogfood shard 3/3. The seat chose this route (one module) and this guard (comment
6004950414on #21914), on the dev's measurement (comment6004909676).Evidence
All runs are at head
967ce88a, under the shared verify lock, from a clean package directory.Whole suite:
pnpm --filter @objectstack/dogfood testgaveTest Files 205 passed | 1 skipped (206)andTests 1591 passed | 9 skipped (1600). Afterwardspackages/qa/dogfood/.objectstackdoes not exist, and noos-dogfood-run-*root is left in the temp dir.CI's three-shard split: CI's dogfood leg exports
OS_TEST_SHARD=k/3andvitest.config.tsturns it into vitest'sshard. Here each shard ran asOS_TEST_SHARD=k/3 pnpm --filter @objectstack/dogfood test: the same vitest selection, without turbo, so no cached replay. Each exited 0 and left no.objectstack:The three add up to the whole run: 206 files, 1600 tests.
Ablation (H4) through
scripts/ablation-replace.mjs, wrap mode. The centralprocess.chdir(...)was replaced by the baremkdtempSync(...): anchor count 1 to 0, blob0991eb9ctoee5a65be.showcase-external-autoconnectandshowcase-searchran:Test Files 2 failed (2),Tests 8 passed (8), exit 1. Each failed in the guard:.../packages/qa/dogfood/.objectstack/data exists after this test file ran. Entries: showcase_external.db(plus-shm/-walfor the shared-showcase file).0991eb9c), andgit diff HEADis empty.2 passed, exit 0, and left nothing.Stale directory:
.objectstack/data/x.dbwas planted, then 9 files were run. Result:Test Files 9 passed (9), exit 0, nothing left (the globalSetup cleared it).Census: those 9 files are the 7 populated-fixture writers plus
showcase-searchandshowcase-permission-zoo, bothshared-showcasefiles.hostRootline, measured both ways, runningrls-multitenant,org-create-default-teamandenterprise-organizations.test:4d07dc29), the skip text readnot resolvable from /tmp/os-dogfood-run-.../file-...and told the reader to declare the package in that temp directory'spackage.json.967ce88a), the text namespackages/qa/dogfood/.@objectstack/organizations.Guard placement: a throwing
globalSetupteardown was measured on vitest 4.1.11 to printerror during closeand still exit 0, a false green. So the guard is the per-fileafterAll. (A teardown that setsprocess.exitCode = 1does exit 1, but the summary still reads all-passed.)Typecheck and lint:
pnpm --filter @objectstack/dogfood typecheckis green, andtsc --listFilesincludes both new modules andenterprise-organizations.ts.pnpm lintexits 0.Gates: 130 commands at
967ce88a, the dispatch list pluspnpm check:dispatcher-error-vocabularyfromdispatch-gates --commands.dispatch-gates --ran:48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED.check:dual-build-cjs-loadsandcheck:published-readme-exportsfirst exited 3 (dist prerequisite: 7 packages unbuilt). After building those 7, both exit 0.check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths) are re-run with this PR's context; the results are in the report on the card.Open PRs that add dogfood files
chdirshowcase-public-form-withdrawal-layers.dogfood.test.ts.objectstack/datain the package directory.organization-delete-federated-fixture.dogfood.test.tsonEnablechdiris redundant.external-import-code-datasource-namespace.dogfood.test.ts(also edits threeexternal-*files)onEnabledatasource-contractless-credentials.dogfood.test.tsflow-node-config-values-at-registration.dogfood.test.tsNone of these files reads a package-relative path through
process.cwd(). Only their ownprevCwdcaptures do.Acceptance notes
schemaMode: 'external',allowWrites: false). Its auto-connect CREATES a missing.objectstack/data/showcase_external.db, plus-wal/-shm(measured on 85 harness boots).showcase-external.datasource.tssays that if the fixture file cannot be opened, "the boot stops with that as the reason rather than serving a showcase whose federation pages are quietly dead".bootStack, never at a public door (os start/os dev), so it stays here.bootStack(..., { multiTenant: true })also defaults itshostRootto the cwd:rls-multitenant.dogfood.test.ts:79, andattachments-permission-matrix.dogfood.test.ts:766throughbootFixture. Both are gated onorganizationsAvailable, which is false in this repository because no framework package may declare@objectstack/organizations(ADR-0132). A run that declares it in this package would need those boots to pass the package root too. Carrier: whoever declares it.chdirinexternal-validate-sees-runtime-save,external-import-destructive-remedy, PR fix(runtime,service-datasource): an import over a code-defined datasource is held to its package's ADR-0028 namespace #21906's file and PR fix(objectql): the cascade skips a federated object's injected tenant anchor #21917's file is now redundant. It is left untouched and can be removed once fix(runtime,service-datasource): an import over a code-defined datasource is held to its package's ADR-0028 namespace #21906 lands. Carrier: thedomain:cliseat.Generated by Claude Code