Skip to content

Commit 27a8b33

Browse files
test(dogfood): the cold-boot catalog file takes its database roots under tmpdir() and removes them itself (#22416)
Fixes #22400 Clause-②: no ## What this changes One test file, `packages/qa/dogfood/test/security-catalog-cold-boot-environment-holder.dogfood.test.ts`, in the private `@objectstack/dogfood` package: - `databaseFile()` takes its `mkdtempSync` base from `join(tmpdir(), 'catalog-cold-boot-')`. Before, the base was `join(process.cwd(), 'catalog-cold-boot-')`. - The per-file cwd sweep no longer covers these directories, so the file records every root `databaseFile()` creates and removes them in a new `afterAll`. That hook runs after `afterEach` has stopped the last kernel. A refused boot leaves no kernel to stop, but its directory is still removed. This follows the harness's own `BootOptions.databaseFile` contract: "Callers own the file's lifetime (create it under a temp dir, delete it after)". - The header comment now describes the new placement and why the base is spelled `tmpdir()`. The old text ("the files live in this test file's own working directory, which the dogfood run removes at its end") is no longer true. Why: `scripts/pm/dispatch-gates.mjs`'s scratch-directory scan must be able to read every `mkdtempSync` base. `process.cwd()` is not a base it reads, so the site came back UNRESOLVED and the `pm dispatch-gates self-test` case failed on `main`. This uses the same remedy as PR #21935, which fixed the same mechanism on `per-file-cwd.setup.ts`: the base is made readable at the site. Per triage, `dispatch-gates.mjs` is not touched (the guard is not loosened, and `process.cwd()` is not taught to the scan), and `per-file-cwd.setup.ts` is not touched. ## Evidence ### `pnpm check:pm-dispatch-gates` (run detached, followed with `tail --pid`) | tree | final line | |---|---| | `main` `83e7ae93a` (unfixed, in a separate worktree pinned there) | `✗ dispatch-gates self-test: 1 of 2011 case(s) failed.` (the failing case: `✗ no mkdtempSync site in this tree takes a base the scan cannot read — UNRESOLVED: packages/qa/dogfood/test/security-catalog-cold-boot-environment-holder.dogfood.test.ts:108 (a base this scan cannot read: process.cwd())`) | | this branch `d8eee2191` | `✓ dispatch-gates self-test: 2011 cases pass.` | On this branch the case itself reads `✓ no mkdtempSync site in this tree takes a base the scan cannot read` (log line 1533 in both runs). The wrapper's own battery reads `✓ check:pm-dispatch-gates --self-test: the exit contract holds in all three directions.` on both trees. The unfixed run ends `ELIFECYCLE Command failed with exit code 1.`, and the fixed run prints no such line. Battery time on this shared box: 781.7s before and 774.7s after. The same scan, read directly through `exposedScratchDirs()` on both trees: `sites=1625 inTree=63 exposed=0`, with `unresolved(mkdtempSync)` going from 1 to 0 and `unresolved(all)` from 289 to 288. Site and in-tree counts are unchanged, so the one site moved to "outside", not out of the scan. ### The dogfood file itself (through `scripts/pm/os-verify-lock.sh`, on `d8eee2191`) `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/security-catalog-cold-boot-environment-holder.dogfood.test.ts` ``` Test Files 1 passed (1) Tests 4 passed (4) os-verify-lock: VERDICT command-exit 0 ``` A `catalog-cold-boot-*` listing under `tmpdir()` (`/tmp` here), taken before and after the run, with a poller sampling `/tmp` every 100 ms during it: ``` before (2026-10-09T04:02:10Z): count=0 seen during the run: 2026-10-09T04:02:32.669Z /tmp/catalog-cold-boot-6eM9D3 2026-10-09T04:02:36.183Z /tmp/catalog-cold-boot-KGxFei 2026-10-09T04:02:37.484Z /tmp/catalog-cold-boot-eGpgIr 2026-10-09T04:02:39.290Z /tmp/catalog-cold-boot-PzYAkQ (distinct seen=4, one per case) after (2026-10-09T04:02:41Z): count=0 ``` So the four roots really are created under `tmpdir()` and none survives the file. Nothing else removes them: the dogfood globalSetup teardown only sweeps its own `os-dogfood-run-TAG-file-` prefix. `pnpm --filter @objectstack/dogfood typecheck` gives `VERDICT command-exit 0`. The package's `tsconfig.json` includes `test/**/*`, and `tsc --noEmit --listFiles` names this file once. ### Derived gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 53 commands from this diff (1 path vs merge base `83e7ae93a`). Each was run on `d8eee2191` with its exit code recorded, then reconciled with `--ran`: ``` Run reconciliation — 53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 53 derived famil(ies) accounted for — 53 run, 0 NOT-MEASURED (a DERIVED zero — all 53 recorded an exit code and none of them is 3). ``` On its first pass, `pnpm check:dual-build-cjs-loads` exited 3 (`PREREQUISITE NOT MET`): eight packages outside the dogfood closure had no `dist/`. They were built through the lock (44 of 44 turbo tasks, all cache hits), and the re-run exited 0: `✓ check:dual-build-cjs-loads — 107 published require entry point(s) across 66 package(s) load`. The record above carries that re-run's code. The other 52 exited 0 on the first pass. ESLint, narrowed to the one changed file: `eslint --no-inline-config --format json` gives 1 file, 0 errors, 0 warnings, and the file is not ignored. The resolved config sets neither `parserOptions.project` nor `projectService`, so linting is not type-aware and this diff cannot change the verdict on any file it does not touch. The repo-wide `pnpm lint` is CI's. ## Acceptance notes - No changeset: `@objectstack/dogfood` is `private: true` and the diff is one test file. - The full dogfood suite is not run locally. CI's `Dogfood Regression Gate` runs it. Locally this file ran on its own, as above. - The workflow-valued families the derivation names as NOT MEASURED (`check-shard-attestation --emit …`, `check-test-completeness "$RUNNER_TEMP/…"`, `check-issue-citations --census`) take a value that exists only in a CI run. They are CI's. --- _Generated by [Claude Code](https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 961d365 commit 27a8b33

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

‎packages/qa/dogfood/test/security-catalog-cold-boot-environment-holder.dogfood.test.ts‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,20 @@
3838
// - CONTROL: a package whose names the environment does not hold boots on a
3939
// database whose environment holds others, and restarts.
4040
//
41-
// Each case boots its own database file. A refused boot leaves no kernel to
42-
// stop, so the files live in this test file's own working directory, which the
43-
// dogfood run removes at its end, rather than being removed here.
41+
// Each case boots its own database file, in a directory of its own directly
42+
// under the system temp directory. A refused boot leaves no kernel to stop, but
43+
// its directory can still be removed, so this file removes every directory it
44+
// created in its `afterAll`, once `afterEach` has stopped the last kernel. The
45+
// harness leaves a `databaseFile`'s lifetime to its caller.
46+
//
47+
// The base is spelled `join(tmpdir(), ...)` on purpose: the tree's
48+
// scratch-directory scan must be able to read every `mkdtempSync` base
49+
// (`scripts/pm/dispatch-gates.mjs`, "no mkdtempSync site in this tree takes a
50+
// base the scan cannot read"), and `process.cwd()` is not a base it reads.
4451

4552
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
46-
import { mkdtempSync } from 'node:fs';
53+
import { mkdtempSync, rmSync } from 'node:fs';
54+
import { tmpdir } from 'node:os';
4755
import { join } from 'node:path';
4856
import { composeStacks, defineStack } from '@objectstack/spec';
4957
import { ObjectSchema, Field } from '@objectstack/spec/data';
@@ -104,15 +112,25 @@ const environmentConflicts = (set: string, position: string) => [
104112
{ catalogType: 'permission', name: set, incomingPackageId: ADDON_ID, existingHolder: { kind: 'environment' } },
105113
];
106114

107-
/** A fresh database file in this test file's working directory (see the header). */
108-
const databaseFile = () => join(mkdtempSync(join(process.cwd(), 'catalog-cold-boot-')), 'deployment.db');
115+
/** Every directory `databaseFile()` created; the `afterAll` below removes them (see the header). */
116+
const createdRoots: string[] = [];
117+
118+
/** A fresh database file in a directory of its own under the system temp directory (see the header). */
119+
const databaseFile = () => {
120+
const root = mkdtempSync(join(tmpdir(), 'catalog-cold-boot-'));
121+
createdRoots.push(root);
122+
return join(root, 'deployment.db');
123+
};
109124

110125
describe('ADR-0048 N.3: a package-held position or permission-set name the environment catalog holds refuses the cold boot, as it refuses a hot install', () => {
111126
let stack: VerifyStack | undefined;
112127
afterEach(async () => {
113128
await stack?.stop();
114129
stack = undefined;
115130
});
131+
afterAll(() => {
132+
for (const root of createdRoots.splice(0)) rmSync(root, { recursive: true, force: true });
133+
});
116134

117135
// The built-in control saves under two built-in position names, which the
118136
// platform's package registers, so the save needs the documented hatch. The

0 commit comments

Comments
 (0)