Repository navigation
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 |
…cratch-dir scan can read (objectstack-ai#21935) Fixes objectstack-ai#21924 Clause-②: no ## What changes `main`'s hourly `Lint & Type Check` run is red on one `dispatch-gates` self-test case (run `37394652870` at `be97cf3c93`, `Lint & Repo Gates`, step `PM dispatch-gates self-test`): > ✗ no mkdtempSync site in this tree takes a base the scan cannot read — UNRESOLVED: packages/qa/dogfood/test/per-file-cwd.setup.ts:60 (a base this scan cannot read: inject('dogfoodCwdRoot')) PR objectstack-ai#21919 added that site for objectstack-ai#21914. The guard is right: a `mkdtempSync` base handed over through `inject()` is an expression the tree's scratch-directory scan cannot read. So the fix is at the site, and nothing in the guard or the scan changes. - **`packages/qa/dogfood/test/per-file-cwd.setup.ts`**: each file's directory is now `mkdtempSync(join(tmpdir(), perFileDirPrefix(runTag)))`. The base is `tmpdir()`, outside the tree by construction, and the scan reads it as such. - Only the run's TAG crosses `inject()` (key renamed `dogfoodCwdRoot` to `dogfoodRunTag`), and only as a name component. - A tag carrying a path separator or `..` is refused. - **`packages/qa/dogfood/test/per-file-cwd.global-setup.ts`**: - At start it clears a stale `.objectstack` (unchanged). It reserves the run's tag as `mkdtempSync(join(tmpdir(), 'os-dogfood-run-'))` (unchanged expression) and provides its basename. - At END it removes every system-temp entry named with this run's prefix (`perFileDirPrefix`, exported here and imported by the setup module, so the prefix has one spelling), then the reservation. - The removal stays run-level, so the memoized `shared-showcase` boot keeps its handles until the run ends. A concurrent run carries another tag and is never touched. - The teardown still judges nothing. - **`packages/qa/dogfood/vitest.config.ts`**: one comment line ("removes the run's per-file directories at the end"). Unchanged from objectstack-ai#21914: every file still runs in its own temporary cwd; the cwd is restored in `afterAll`; and the throwing `afterAll` guard on `packages/qa/dogfood/.objectstack/data` is untouched. Out of scope here: the rule, the case and `scripts/pm/dispatch-gates.mjs` are untouched. Nothing is skipped or recorded as an exception. ## Evidence - **Reproduced first**, on `origin/main` `faf8dce4` with no change: `pnpm check:pm-dispatch-gates` gave `✗ dispatch-gates self-test: 1 of 1976 case(s) failed.`. The one red case is the line quoted above, at `per-file-cwd.setup.ts:60`. The battery took 821.9s. - **At head `2edc5d59`**: `pnpm check:pm-dispatch-gates` gave `✓ no mkdtempSync site in this tree takes a base the scan cannot read` and `✓ dispatch-gates self-test: 1976 cases pass.` (780.2s). - **The case still fails on a planted unreadable base.** A second worktree was checked out at `2edc5d59`, and `scripts/ablation-replace.mjs` (wrap mode) replaced the site with `process.chdir(mkdtempSync(join(inject('dogfoodRunTag'), 'file-')));`. The anchor went from 1 to 0, and the blob from `dc1d3de3` to `51346b9f`. - Result: `✗ dispatch-gates self-test: 1 of 1976 case(s) failed.`, and the red case was exactly `... UNRESOLVED: packages/qa/dogfood/test/per-file-cwd.setup.ts:73 (a base this scan cannot read: inject('dogfoodRunTag'))`. - Restore was proven: blob after restore equals HEAD (`dc1d3de3`), `git diff HEAD` is empty, and `git status --porcelain` is empty. The second worktree was then removed. - **objectstack-ai#21914's behaviour, re-proven at `2edc5d59`**: - Whole dogfood suite, `pnpm --filter @objectstack/dogfood test`: `Test Files 208 passed | 1 skipped (209)`, `Tests 1606 passed | 9 skipped (1615)`. Afterwards `packages/qa/dogfood/.objectstack` does not exist. Zero `/tmp/os-dogfood-run-*` entries before the run and zero after it. - H4 ablation through `scripts/ablation-replace.mjs`: the central `process.chdir(...)` was dropped (blob `dc1d3de3` to `ad6684f2`) and two files were run, `showcase-external-autoconnect` and `showcase-search`. Result: `Test Files 2 failed (2)`. Both failed in the guard: `.../packages/qa/dogfood/.objectstack/data exists after this test file ran. Entries: showcase_external.db` (plus `-shm` / `-wal` on the shared-showcase file). - The restore was proven (blob equals HEAD, `git diff HEAD` empty). The restored leg gave `2 passed`, exit 0, and left nothing in the package dir. Zero temp entries were left after each leg, the red one included. - **Gates**: `dispatch-gates --commands` over the 3 changed paths derived 47 families. All 47 ran at `2edc5d59`, and `dispatch-gates --ran` reported `47 derived famil(ies) accounted for — 47 run, 0 NOT-MEASURED`. - `check:dual-build-cjs-loads` first exited 3 (prerequisite: `packages/apps/studio/dist` missing), then exited 0 after building it. - `pnpm lint` exits 0, and `pnpm --filter @objectstack/dogfood typecheck` is green. - `check:pm-dispatch-gates` is path-scoped, so this PR's CI may not schedule it, which is how the red reached `main`. It was run here in full at the head, as above. ## Acceptance notes - Open PR objectstack-ai#21930 also edits `packages/qa/dogfood/vitest.config.ts`, in the `isolated` project's alias block (about line 295). This PR changes one comment line near line 146. The hunks do not overlap. - How this red reached `main`: objectstack-ai#21919's own CI did not schedule `check:pm-dispatch-gates`, because that battery is path-scoped and the battery's live-tree half reads every tracked source. This is noted, not filed: the scoping is the merge-group ruling that `lint.yml` documents, and the hourly full run caught the red as designed. --- _Generated by [Claude Code](https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU)_ Co-authored-by: Claude <noreply@anthropic.com>
…rries the credential mask and omits internal fields (objectstack-ai#21928) Fixes objectstack-ai#21867 Clause-②: no ## What this changes Ruling A on objectstack-ai#21867 (director's record 5995381726, alignment note 6005796816): mask at the source. `RecordChangeTrigger.buildContext` (`packages/triggers/trigger-record-change/src/record-change-trigger.ts`) now projects both roots it hands a flow, `record` and `previous`, through the one helper `omitInternalFieldsFromWriteResponse` (`@objectstack/core`, `packages/core/src/utils/internal-write-response.ts`), with the trigger object's definition. A credential-class field (every `secret` field, and every `password` field outside the exempt `managedBy` buckets, per ADR-0100 and `isMaskedOnReadFieldType`) carries `SECRET_MASK`, or `null` when unset. A field declared `internal: true` is omitted. `params` is the same object as `record`, so it inherits the projection. - **Applied last.** The projection runs after hydration, after declared-field materialisation and after the decoupling copy, so no later layer brings a clear value back. It runs in place on the decoupled copies only, so the engine's `ctx.result` / `ctx.previous` / `ctx.input`, which are shared with every other binding and hook on the write, are never touched. - **The definition is read regardless of ground truth.** Materialisation is gated on persisted state; the mask is not. A new private `readObjectDefinition` reads the engine's optional `getObject` accessor. When the definition cannot be resolved (accessor absent, no answer, or a throw), the flow still dispatches unmasked, and `readObjectDefinition` logs that once per object at error through the plugin logger, naming the object. The bind-time existence probe only warns and still binds; nothing upstream refuses an unknown object. - **Downstream inherits it, with no second copy.** The variables map (`record`, `$record`, `previous`), `SuspendedRun.context`, the persisted `variables_json` / `context_json`, the run read doors, and the run a resume rehydrates, in-process and after a restart. ⛔ No mask in `service-automation` or in the suspended-run store. ⛔ No other variable is filtered (objectstack-ai#7900 stands). ## Premises verified before writing (at `origin/main` `dcb11c2ec9`) 1. **The definition is reachable in `buildContext`.** `this.engine.getObject` is already read there for materialisation. Re-check grep: 9 hits in `record-change-trigger.ts`. 2. **The projection is the last overlay.** The last layers are materialisation, then `decoupleFromEngineState` on both roots, then the return. The projection sits between the decoupling and the return. 3. **No shipped flow reads a credential-class field off its trigger record.** The card's grep over `examples/**/*flow*` and `examples/**/flows/**` returns zero hits (`git grep` exit 1). Control: the same paths carry `record.FIELD` reads in 4 files, so the zero is not a dead pattern. The only example object with `password` / `secret` fields is `showcase_field_zoo`. Its one record-change flow (`showcase_approver_bindings`, `status: 'draft'`) reads neither field. 4. **Only the trigger's own record enters here.** `get_record` and the other CRUD nodes (`service-automation/src/builtin/crud-nodes.ts`) read through `data.find` / `data.findOne`, the engine's generic read path, which ADR-0100 already masks. Nothing here touches those nodes. ## Pins - `packages/triggers/trigger-record-change/src/trigger-record-credential-mask.test.ts` (unit, fake engine, 13 cases): - `password` and `secret` carry the mask on `record` and on `previous`, and the `internal` field is omitted. - An ordinary field keeps its value, and `params` is the same object as `record`. - An unset credential reads `null`. - The engine's hook objects stay whole. - Insert events are masked too. - A `better-auth`-managed `password` keeps the read path's exemption. - `afterDelete` (record from the prior row) and `beforeUpdate` (payload over the prior row) are masked on both roots. - Each of the three unresolved-definition shapes (accessor absent, no answer, a throw) logs one error naming the object, while the flow runs on both writes. - A resolved definition logs no error. - `packages/qa/dogfood/test/flow-trigger-record-credential-mask.dogfood.test.ts`. A real boot: `bootStack` with automation, a file-backed database, the real crypto provider and the record-change trigger. It uses one object with an ordinary field, a `password` field, a `secret` field and an `internal` field, and one `record-after-update` flow that pauses at a `screen` node. The cases: - The scene is armed: the engine write result holds the stored values. - The paused row's `variables_json` and `context_json` carry the mask for both credential fields and omit the internal field (`record`, `$record`, `previous`), with no stored credential spelling anywhere in either column. - The data door over that row serves the same. - `GET /automation/:name/runs/:runId` shows the same. - After the resume, a node reading `record.CREDENTIAL_FIELD` / `previous.CREDENTIAL_FIELD` stores the mask, while the ordinary field stores its value. - The privileged `resolveSecretField` path still returns the plaintext. - A second suite pauses, stops the kernel, cold-boots a second kernel over the same file and resumes there. The post-pause node again stores the mask. - QA checklist: `automation.paused-run-trigger-record-masked` in `docs/qa/platform-checklist/areas/automation.json`. This is the item triage named as missing on the path "approvals and automation — flows run: errors, pauses and schedules". It covers reading a paused run's stored state as a non-privileged holder. `automated.ref` names the dogfood pin, and a `knownGaps` line says the pin reads as the admin. ## Upgrade text - Changeset `.changeset/21867-flow-trigger-record-credential-mask.md`: `@objectstack/trigger-record-change` minor, `@objectstack/spec` patch. It carries the `!` banner, FROM → TO and the one-line handling: a flow that needs a credential uses a privileged binder, never the trigger record. - It names the record-vs-previous credential comparison: a condition comparing the two sees two equal masks whenever the field is set on both sides, so a credential change is detected through a privileged binder. - It carries a "Runs stored before this release" paragraph: paused runs, and terminal runs that keep a restorable snapshot, created before the upgrade are resumed, cancelled or purged after upgrading. There is no migration and no scrub. - ADR-0087 semantic entry `packages/spec/src/migrations/entries/semantic/18.flow-trigger-record-credential-masked.ts`, a sibling of `18.by-id-write-unreadable-row-not-found`. It is registered through `gen:migration-registry` (`registry.ts`) and declared in the changeset as `registered flow-trigger-record-credential-masked`. - `packages/triggers/trigger-record-change/vitest.config.ts`: the alias moves to the anchored array form and gains `@objectstack/spec/data` and `@objectstack/core` to source; the `check-test-source-alias` registry entry for this package drops `@objectstack/core`. ## Verification Round 1 readings are at head `67ce8a46a2` unless marked. Round 2 readings are in their own block below, at head `4f287e072f`. - **Ablation.** The two projection calls were replaced via `scripts/ablation-replace.mjs`, wrap mode, with an EXIT/INT/TERM restore. On-disk proof: anchor 1 → 0, marker 0 → 1, blob `d0702684cb19` → `27a41720a0ab`. The dogfood project aliases `@objectstack/trigger-record-change` to source, and the plugin is passed in `extraPlugins` from that import, so no dist hop applies. - Unit pin: 3 red, 4 green. The four that stay green: ordinary value, `params` identity, unset reads null, hook objects whole. All four hold without a mask too. - Dogfood pin: 5 red, 2 green. The two that stay green: armed scene, privileged path. - Restore: blob == HEAD `d0702684cb19`, and `git diff HEAD` is empty. - **Tests.** - `@objectstack/trigger-record-change` `pnpm test`: 11 files, 108 tests, green at `67ce8a46a2`. - `@objectstack/core` `pnpm test`: 77 files, 2177 tests, green. - `@objectstack/service-automation` vitest: 173 files, 2112 tests, green. - Dogfood pin: 7/7 green, at `48e0b1cc35` (trigger source unchanged since). - `@objectstack/spec` `src/migrations`: 3 files, 179 tests, green. - **Typecheck.** - `@objectstack/trigger-record-change` `typecheck`, including `tsconfig.test.json`: green. `--listFiles` counts the new test file once. - `@objectstack/dogfood` `typecheck`: green, and it covers the new file. - `@objectstack/spec` `typecheck` (src, scripts, test layer): green. - **Gates.** - `@objectstack/spec` `check:generated`: all 15 artifacts up to date. - `check:adr-0087-registration`: green. It reads the changeset as `[BREAKING+bang] registered flow-trigger-record-credential-masked`. - `check:platform-checklist`: green. - `dispatch-gates.mjs --ran`: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN. - **Lint.** The run was narrowed to the 6 changed `.ts` files, under `eslint --no-inline-config --format json`: 6 files, 0 errors, 0 warnings. - The population comes from eslint's own config: the two non-code files (`.changeset/*.md` and `automation.json`) answer "File ignored because no matching configuration was supplied". - `--print-config` shows `parserOptions` without `project`, so type-aware linting is off. This diff cannot move any untouched file's verdict. ### Round 2, at head `4f287e072f` `origin/main` was merged in as a merge commit (`baa4b2fe6c`; the branch was 9 behind). - **Build and tests.** - Closure build `pnpm --workspace-concurrency=2 --filter '@objectstack/trigger-record-change...' build`: exit 0. - `@objectstack/trigger-record-change` `pnpm test`: 11 files, 114 tests, green. The mask file has 13 cases. - `@objectstack/trigger-record-change` `typecheck` (`tsc --noEmit && tsc --noEmit -p tsconfig.test.json`): exit 0 for both. - **Ablation 1, the core alias resolves to source.** Via `scripts/ablation-replace.mjs`, an early return was planted in `omitInternalFieldsFromWriteResponse` (`packages/core/src/utils/internal-write-response.ts`), with core `dist` not rebuilt (marker: 0 hits in `packages/core/dist`). Landed: anchor 1 → 0, blob `2a6a48c04fdb` → `d51283c8f5e6`. Result: 5 red, 8 green; the red ones are the masking cases, the new `afterDelete` and `beforeUpdate` included. Restore: blob == HEAD `2a6a48c04fdb`, `git diff HEAD` empty. A first attempt was refused by the tool as a no-op (the replacement contained the anchor); it measured nothing and was redone with a non-overlapping replacement. - **Ablation 2, the log pin can fail.** The error branch's condition was replaced with `false`. Landed: anchor 1 → 0. Result: 3 red (the absent, no-answer and throw cases), 10 green. Restore: blob == HEAD `04e3ca86825f`, `git diff HEAD` empty. - **Gates, each exit 0.** `check:adr-0087-registration` (reads `[BREAKING+bang] registered flow-trigger-record-credential-masked`; `--self-test` 441 assertions), `check-adr-0087-registration --base origin/main`, `check-changeset-no-major --base origin/main`, `check-empty-changeset --base origin/main`, `check:changeset-gate-self-tests`, `check:test-source-alias` (73 packages with tests scanned, 60 registered), `check:nul-bytes`, `check-scripts-symbol-anchors`, `check-published-list-mirrors`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:issue-citations`, `check:logger-receiver-detach`, `check-changeset-fixed`, `check:published-files`. - `@objectstack/spec` `check:generated` after the main merge: all 15 generated artifacts up to date, against the spec `dist` built post-merge. - NOT MEASURED: `check:console-injection`. It skipped, because there is no `packages/console/dist` in this worktree. - The rest of the `dispatch-gates` derivation (109 commands over the whole PR diff, mostly round-1 spec and dogfood families) was not re-run this round; CI owns it. - **Lint.** Narrowed to the 4 files changed this round (`record-change-trigger.ts`, `trigger-record-credential-mask.test.ts`, `vitest.config.ts`, `scripts/check-test-source-alias.mjs`), under `eslint --no-inline-config --format json`: 4 files, 0 errors, 0 warnings. All 4 are in eslint's own config, per `--print-config`, which also shows `parserOptions.project` and `projectService` undefined, so type-aware linting is off and this diff cannot move any untouched file's verdict. ## Acceptance notes - The claim's file surface names `packages/triggers/trigger-record-change/src`. This PR also touches that package's `vitest.config.ts` (the alias above) and adds one dogfood test file under `packages/qa/dogfood/test/`, as the dispatch asked. Round 2 also touches `scripts/check-test-source-alias.mjs`, a registry narrowing only (this package's entry drops `@objectstack/core`). - During the second full gate pass, `packages/plugins/plugin-approvals/dist` and `packages/plugins/plugin-auth/dist` were found without `.d.ts` (written mid-pass). `check:dts-closure` and `check:dual-build-cjs-loads` went red as a result. A rebuild of those two packages restored them, and both gates read green. Neither package is in this diff. Which step wrote them was not established. - Carrier: none; noted here only, not filed. In `buildContext`, the materialisation read of `getObject` (gated on ground truth) is not wrapped in try/catch. A `getObject` that throws therefore fails the dispatch before the mask runs, and the handler logs "execution failed". So `readObjectDefinition`'s throw branch is reachable only on an update or delete with no prior row. This behaviour predates the PR and was left untouched, because the dispatch said dispatch behaviour must not change. No public entry point is shown to throw from `getObject`. - Of the three operator actions for runs stored before this release, purging is the only one that leaves no clear value behind; resuming an old paused run can still write its clear values into the run's step log. A follow-up edit to the changeset should list purge first. - `48c162ed06` ports the three dogfood test-infra files of open PR objectstack-ai#21935 (`packages/qa/dogfood/test/per-file-cwd.setup.ts`, `per-file-cwd.global-setup.ts`, `packages/qa/dogfood/vitest.config.ts`), byte-identical, to clear the `PM dispatch-gates self-test` red that `main` has carried since objectstack-ai#21919. It is a no-op once objectstack-ai#21935 lands. --- _Generated by [Claude Code](https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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