Skip to content

Commit a3c2209

Browse files
committed
Merge origin/main into claude/issue-21913-principal-less-producers-services
Brings in the dogfood per-file cwd fix the check:pm-dispatch-gates self-test reads. No file of this branch is touched by the two incoming commits. Claude-Session: https://claude.ai/code/session_011K3zqE8Pv1Evw5hc8tZCnN Co-authored-by: Claude <noreply@anthropic.com>
2 parents e1e6c51 + 9e33ee7 commit a3c2209

32 files changed

Lines changed: 153 additions & 118 deletions

‎packages/qa/dogfood/test/per-file-cwd.global-setup.ts‎

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,34 @@
1010
// left by a developer's earlier run on a tree without this isolation, or by
1111
// a crashed run. The guard judges only what THIS run leaves, so an old
1212
// leftover never reds a run that wrote nothing.
13-
// 2. It creates ONE temporary root for the run and hands it to every worker
14-
// through `provide` / `inject`. Each test file makes its own working
15-
// directory under that root.
13+
// 2. It reserves a TAG for the run, `os-dogfood-run-XXXXXX`, as a directory
14+
// `mkdtempSync` creates under the system temp directory, and hands the tag
15+
// (a name, never a path) to every worker through `provide` / `inject`.
16+
// Each test file makes its own working directory directly under the system
17+
// temp directory, named `<tag>-file-XXXXXX`.
1618
//
17-
// At the END of the run it removes that root, and with it every per-file
18-
// directory. The removal is run-level, not per-file: on the `shared-showcase`
19-
// project (`isolate: false`) one memoized boot serves every file on a worker,
20-
// and its SQLite handles stay open in the directory of the file that booted it.
19+
// At the END of the run it removes every directory whose name starts with this
20+
// run's `<tag>-file-`, then the reservation itself. Another run's directories
21+
// carry another tag, so a concurrent run on the same machine is never touched.
22+
// The removal is run-level, not per-file: on the `shared-showcase` project
23+
// (`isolate: false`) one memoized boot serves every file on a worker, and its
24+
// SQLite handles stay open in the directory of the file that booted it.
25+
//
26+
// Why a tag and not a shared parent path (#21924): every `mkdtempSync` base in
27+
// this tree must be one the tree's scratch-directory scan can read, so that an
28+
// in-tree fixture root can never hide behind an expression
29+
// (`scripts/pm/dispatch-gates.mjs`, "no mkdtempSync site in this tree takes a
30+
// base the scan cannot read"). A path handed over through `inject()` is such an
31+
// expression. `join(tmpdir(), ...)` is not: it is outside the tree by
32+
// construction, whatever name follows it.
2133
//
2234
// ⛔ This teardown never JUDGES anything. On vitest 4.1.11 an error thrown from
2335
// a globalSetup teardown is printed as `error during close` and the run still
2436
// exits 0 (measured), so a guard placed here would be a false green. The guard
2537
// is a throwing `afterAll` in the per-file module, which fails a test file.
26-
import { mkdtempSync, rmSync } from 'node:fs';
38+
import { mkdtempSync, readdirSync, rmSync } from 'node:fs';
2739
import { tmpdir } from 'node:os';
28-
import { join } from 'node:path';
40+
import { basename, join } from 'node:path';
2941
import { fileURLToPath } from 'node:url';
3042
import type { TestProject } from 'vitest/node';
3143

@@ -34,19 +46,29 @@ const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
3446

3547
declare module 'vitest' {
3648
export interface ProvidedContext {
37-
/** The run's temporary root; each test file makes its working directory under it. */
38-
dogfoodCwdRoot: string;
49+
/** The run's tag; each test file makes its working directory as `join(tmpdir(), '<tag>-file-')`. */
50+
dogfoodRunTag: string;
3951
}
4052
}
4153

42-
let runRoot: string | undefined;
54+
/** The prefix of every per-file directory a run tagged `tag` creates under the system temp directory. */
55+
export function perFileDirPrefix(tag: string): string {
56+
return `${tag}-file-`;
57+
}
58+
59+
let reservation: string | undefined;
4360

4461
export function setup(project: TestProject): void {
4562
rmSync(join(PACKAGE_ROOT, '.objectstack'), { recursive: true, force: true });
46-
runRoot = mkdtempSync(join(tmpdir(), 'os-dogfood-run-'));
47-
project.provide('dogfoodCwdRoot', runRoot);
63+
reservation = mkdtempSync(join(tmpdir(), 'os-dogfood-run-'));
64+
project.provide('dogfoodRunTag', basename(reservation));
4865
}
4966

5067
export function teardown(): void {
51-
if (runRoot) rmSync(runRoot, { recursive: true, force: true });
68+
if (!reservation) return;
69+
const prefix = perFileDirPrefix(basename(reservation));
70+
for (const name of readdirSync(tmpdir())) {
71+
if (name.startsWith(prefix)) rmSync(join(tmpdir(), name), { recursive: true, force: true });
72+
}
73+
rmSync(reservation, { recursive: true, force: true });
5274
}

‎packages/qa/dogfood/test/per-file-cwd.setup.ts‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@
1919
// ## What it does
2020
//
2121
// At module top level, which runs before the test file's own imports, it makes
22-
// a directory under the run's temporary root and `chdir`s into it. `afterAll`
23-
// restores the previous working directory. The directories are removed at the
24-
// end of the run by the globalSetup, not here: the memoized `shared-showcase`
25-
// boot keeps its SQLite handles open in the first file's directory.
22+
// a directory directly under the system temp directory, named with the run's
23+
// tag (`<tag>-file-XXXXXX`), and `chdir`s into it. `afterAll` restores the
24+
// previous working directory. The directories are removed at the end of the
25+
// run by the globalSetup, which sweeps its own tag, not here: the memoized
26+
// `shared-showcase` boot keeps its SQLite handles open in the first file's
27+
// directory.
28+
//
29+
// The base is spelled `join(tmpdir(), ...)` on purpose (#21924): the tree's
30+
// scratch-directory scan must be able to read every `mkdtempSync` base, and a
31+
// path received through `inject()` is one it cannot read. Only the run's TAG
32+
// comes through `inject()`, as a name component, and it is refused below if
33+
// it could carry a separator.
2634
//
2735
// The invariant for every dogfood author: a file runs in its own temporary
2836
// cwd, so anything cwd-relative it writes is its own and disappears with the
@@ -38,26 +46,31 @@
3846
// what this run leaves.
3947
import { afterAll, inject } from 'vitest';
4048
import { existsSync, mkdtempSync, readdirSync } from 'node:fs';
49+
import { tmpdir } from 'node:os';
4150
import { join } from 'node:path';
4251
import { fileURLToPath } from 'node:url';
52+
import { perFileDirPrefix } from './per-file-cwd.global-setup.js';
4353

4454
/** `packages/qa/dogfood`, resolved from this module's own location. */
4555
const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
4656
/** What a file must never leave in the package directory. */
4757
const LEFTOVER = join(PACKAGE_ROOT, '.objectstack', 'data');
4858

49-
const runRoot = inject('dogfoodCwdRoot');
50-
if (!runRoot) {
59+
const runTag = inject('dogfoodRunTag');
60+
if (!runTag) {
5161
throw new Error(
52-
'per-file-cwd.setup.ts: no run root was provided. The globalSetup ' +
62+
'per-file-cwd.setup.ts: no run tag was provided. The globalSetup ' +
5363
'`test/per-file-cwd.global-setup.ts` must be wired in packages/qa/dogfood/vitest.config.ts; ' +
5464
'without it this file would run in the package directory.',
5565
);
5666
}
67+
if (/[\\/]|\.\./.test(runTag)) {
68+
throw new Error(`per-file-cwd.setup.ts: the run tag ${JSON.stringify(runTag)} is not a plain directory name.`);
69+
}
5770

5871
const previousCwd = process.cwd();
5972
const presentAtStart = existsSync(LEFTOVER);
60-
process.chdir(mkdtempSync(join(runRoot, 'file-')));
73+
process.chdir(mkdtempSync(join(tmpdir(), perFileDirPrefix(runTag))));
6174

6275
afterAll(() => {
6376
process.chdir(previousCwd);

‎packages/qa/dogfood/vitest.config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ runProjectCliOverridePreflight({
143143
// `.objectstack/data` exists in the package directory: that throw is the guard.
144144
// - The `globalSetup` below is ROOT-level: one run, one call, covering both
145145
// projects and each `OS_TEST_SHARD` slice (measured). It clears a stale
146-
// `.objectstack` at the start and removes the run's temporary root at the end.
146+
// `.objectstack` at the start and removes the run's per-file directories at the end.
147147
// Its teardown judges nothing, because a throw there exits 0 on vitest 4.1.11.
148148
// Both modules' headers carry the rest, including what a dogfood author owes.
149149
const PER_FILE_CWD = './test/per-file-cwd.setup.ts';

‎packages/spec/src/ui/dataset-filter-nested-relation-list.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const REFUSED: ReadonlyArray<readonly [
113113
// §1 Both carriers refuse, at the list's own path
114114
// ---------------------------------------------------------------------------
115115

116-
describe('#20080 §1 — both analytics carriers refuse a list inside a nested relation', () => {
116+
describe('§1 — both analytics carriers refuse a list inside a nested relation, at save', () => {
117117
it.each(REFUSED)('DatasetSchema.filter refuses %s', (_label, filter, issuePath) => {
118118
const issue = issueAt(DatasetSchema.safeParse(withScope(filter)), `filter.${issuePath}`);
119119
expect(issue.message).toMatch(/requires a single comparable value, but received an array/);
@@ -145,7 +145,7 @@ describe('#20080 §1 — both analytics carriers refuse a list inside a nested r
145145
// §2 The carrier prints the analytics door's sentence
146146
// ---------------------------------------------------------------------------
147147

148-
describe('#20080 §2 — the carrier refusal is the analytics door\'s sentence', () => {
148+
describe('§2 — the carrier refusal is the analytics door\'s sentence', () => {
149149
it.each(REFUSED)('%s', (_label, filter, issuePath, field, list, eq, holder) => {
150150
const door = analyticsDoorRefusal(field, eq ? { $eq: list } : list, holder);
151151
// The door's refusal is the ADR-0112 class-1 envelope the face throws.
@@ -175,7 +175,7 @@ describe('#20080 §2 — the carrier refusal is the analytics door\'s sentence',
175175
// §3 A list the shared schema already refuses is refused once, not twice
176176
// ---------------------------------------------------------------------------
177177

178-
describe('#20080 §3 — outside a nested relation the shared schema answers, once', () => {
178+
describe('§3 — outside a nested relation the shared schema answers, once', () => {
179179
it.each([
180180
['top level, implicit', { region: ['a'] }, 'region', /^The implicit-equality comparand on field "region"/],
181181
['top level, $eq', { region: { $eq: ['a'] } }, 'region.$eq', /^Operator "\$eq" on field "region"/],
@@ -192,7 +192,7 @@ describe('#20080 §3 — outside a nested relation the shared schema answers, on
192192
// §4 CONTROLS — accepted on both carriers, and kept
193193
// ---------------------------------------------------------------------------
194194

195-
describe('#20080 §4 — what a nested relation may still carry', () => {
195+
describe('§4 — what a nested relation may still carry', () => {
196196
const day = new Date('2026-07-01T00:00:00.000Z');
197197
it.each([
198198
['a scalar', { account: { region: 'a' } }],
@@ -217,7 +217,7 @@ describe('#20080 §4 — what a nested relation may still carry', () => {
217217
expect(measure.data!.filter).toEqual(filter);
218218
});
219219

220-
it('[#20116] $ne carrying a list inside a relation is refused on both carriers, in the door\'s $ne sentence', () => {
220+
it('$ne carrying a list inside a relation is refused on both carriers, in the door\'s $ne sentence', () => {
221221
// This row sat in the table above as "not yet at this save door (#20116)".
222222
// #20116 routes every slot this walk reaches through the query faces'
223223
// verdict, so the shape the analytics door refuses on chart is refused here.

‎packages/spec/src/ui/door-reachability.testkit.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const defOf = (s: unknown): unknown => (s as { _zod?: { def?: unknown } })?._zod
5858
// derivation or a coincidence. A zod upgrade that changes `clone()` semantics
5959
// changes the bridge's meaning, and that must be LOUD rather than silent.
6060
// ============================================================================
61-
describe('#5056 premise — which zod builders share the `_zod.def` object', () => {
61+
describe('the derived-clone bridge premise — which zod builders share the `_zod.def` object', () => {
6262
it('`.describe()` shares the def of the instance it was called on', () => {
6363
// THE mechanism behind #5056. `clone(inst)` without an explicit def reuses
6464
// `inst._zod.def`, so a described clone is def-IDENTICAL to its receiver.
@@ -104,7 +104,7 @@ describe('#5056 premise — which zod builders share the `_zod.def` object', ()
104104
// ============================================================================
105105
// 2. The three controls, on the instrument itself.
106106
// ============================================================================
107-
describe('#5056 controls — the walker finds doors, and only real ones', () => {
107+
describe('controls — the walker finds doors, and only real ones', () => {
108108
it('positive: live authoring roots resolve, and the graph is really walked', () => {
109109
const { verdict, nodeCount, rootCount } = measureDoors();
110110
expect(rootCount, 'every metadata type plus ObjectStackSchema').toBeGreaterThan(20);
@@ -153,7 +153,7 @@ describe('#5056 controls — the walker finds doors, and only real ones', () =>
153153
// ============================================================================
154154
// 3. The #5056 regression boundary itself.
155155
// ============================================================================
156-
describe('#5056 regression — the any-one-shared-property bridge stays dead', () => {
156+
describe('regression — the any-one-shared-property bridge stays dead; a share of the shape decides', () => {
157157
it('a 2-of-19 shared-leaf shape shares leaves with the live graph but is NOT derived from it', () => {
158158
// The reverse verification, standing rather than one-shot.
159159
//
@@ -213,6 +213,6 @@ describe('#5056 regression — the any-one-shared-property bridge stays dead', (
213213
label: I18nLabelSchema.describe('Display label'),
214214
});
215215
expect(cloneOverlap(allSharedLeaves), '2 shared of 2 keys').toBe(1);
216-
expect(verdict(allSharedLeaves), 'the residual false-reachable case — see #5828').toBe('derived-clone');
216+
expect(verdict(allSharedLeaves), 'the residual false-reachable case — no threshold excludes it').toBe('derived-clone');
217217
});
218218
});

‎packages/spec/src/ui/expression-scope-app-root.pin.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const sentenceContaining = (source: string, anchor: string): string => {
8181
/** Root tokens that are still true on these surfaces and must stay in place. */
8282
const SURVIVING_ROOTS = ['features', 'os.user'] as const;
8383

84-
describe('#17203 — no UI prose face advertises `app` as an expression-scope root', () => {
84+
describe('no UI prose face advertises `app` as an expression-scope root — the renderer no longer mounts it', () => {
8585
describe('published faces (read by authoring tools and republished into the reference docs)', () => {
8686
it('`PageComponentSchema.visibleWhen`.describe() does not name `app` among the mounted roots', () => {
8787
// ⚠️ NOT `.shape` — ADR-0089 D3a made this schema a `.strict().transform(…)`

‎packages/spec/src/ui/form-layout-inline-grid-retired.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const DOORS: readonly Door[] = [
5858
layoutPath: ['layout'],
5959
},
6060
{
61-
name: "ComponentPropsMap['object-form'] (the #5068 props gate's dispatch)",
61+
name: "ComponentPropsMap['object-form'] (the props gate's dispatch)",
6262
parse: (layout) => ComponentPropsMap['object-form'].safeParse({ objectName: 'crm_lead', layout }),
6363
layoutPath: ['layout'],
6464
},
@@ -86,7 +86,7 @@ const DOORS: readonly Door[] = [
8686
},
8787
];
8888

89-
describe('form `layout` — the retired arms are refused with the prescription (#20221)', () => {
89+
describe('form `layout` — the retired arms are refused with the prescription', () => {
9090
for (const door of DOORS) {
9191
describe(door.name, () => {
9292
it("'grid' → refused, prescribing 'vertical' and naming `columns` for multi-column", () => {
@@ -138,7 +138,7 @@ describe('form `layout` — the retired arms are refused with the prescription (
138138
});
139139
});
140140

141-
describe(`D2 conversion \`${CONVERSION_ID}\` (#20221)`, () => {
141+
describe(`D2 conversion \`${CONVERSION_ID}\``, () => {
142142
const conversion = CONVERSIONS_BY_MAJOR[18]!.find((c) => c.id === CONVERSION_ID);
143143

144144
it('is registered under protocol 18 and retired from the load path', () => {
@@ -217,7 +217,7 @@ describe(`D2 conversion \`${CONVERSION_ID}\` (#20221)`, () => {
217217
});
218218
});
219219

220-
describe('D3: the family entry and the chain step are registered (#20221)', () => {
220+
describe('D3: the family entry and the chain step are registered', () => {
221221
const step = MIGRATIONS_BY_MAJOR[18]!;
222222

223223
it(`the protocol-18 step wires \`${CONVERSION_ID}\``, () => {

‎packages/spec/src/ui/form-option-enum-derive.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ describe('ruling 乙 item 1, on real spec enums — each case with a firing and
284284
});
285285
});
286286

287-
describe('a spellable enum row with a labelled inline list (`object.sharingModel`, the #19331 shape)', () => {
287+
describe('a spellable enum row with a labelled inline list (`object.sharingModel`, as its form row writes it)', () => {
288288
const members = servedEnum('object', 'sharingModel');
289289
const LABELS: Record<string, string> = {
290290
private: 'Private — owner only',

‎packages/spec/src/ui/i18n-label-resolver.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ const PARITY_VECTORS: readonly ParityVector[] = [
278278
},
279279
];
280280

281-
describe('resolveI18nLabel — rule parity with objectui pickLocalized (#6765 / #6761 ruling B)', () => {
281+
describe('resolveI18nLabel — rule parity with objectui pickLocalized (ruled: one shared resolver, on the server)', () => {
282282
describe('the vector table really is the reference\'s behaviour', () => {
283283
it.each(PARITY_VECTORS)('$limb', ({ label, locale, pick }) => {
284284
// Asserted against the copied reference FIRST. If this row is wrong, the
@@ -326,7 +326,7 @@ describe('resolveI18nLabel — the producer-facing return shape', () => {
326326
expect(resolveI18nLabel({ en: '' }, 'en')).toBe('');
327327
});
328328

329-
it('composes with `??` into the producer call shape #6761 needs', () => {
329+
it('composes with `??` into the producer call shape the dataset compiler needs', () => {
330330
// `dataset-compiler.ts:374/406` today: `typeof d.label === 'string' ? d.label : d.name`,
331331
// which publishes the MACHINE NAME as a display title for a map label.
332332
const dimension = { name: 'owner', label: { en: 'Owner', 'zh-CN': '负责人' } as I18nLabel };
@@ -337,7 +337,7 @@ describe('resolveI18nLabel — the producer-facing return shape', () => {
337337
});
338338
});
339339

340-
describe('resolveI18nLabel — the rule departures converged with objectui#3907; one departure survives', () => {
340+
describe('resolveI18nLabel — the rule departures converged once objectui read only own, string-valued entries; one departure survives', () => {
341341
// Before objectui#3907 (landed as objectui PR #4359) this module's rule
342342
// documented two DELIBERATE narrowings of the reference. #4359 landed the
343343
// same two guards upstream, so the copied reference now answers identically
@@ -372,7 +372,7 @@ describe('resolveI18nLabel — the rule departures converged with objectui#3907;
372372
expect(resolveI18nLabel(offSpec, 'zh-CN')).toBe('Owner');
373373
});
374374

375-
it('refuses an off-contract scalar rather than stringifying it — the one departure objectui#3907 did NOT touch', () => {
375+
it('refuses an off-contract scalar rather than stringifying it — the one departure the objectui map-limb fix did NOT touch', () => {
376376
// `pickLocalized` accepts `unknown` and stringifies numbers/booleans
377377
// (`pickLocalized(42, 'en')` is `'42'`). This resolver's parameter is the
378378
// declared `I18nLabel`, so the shapes below are type errors — the
@@ -388,7 +388,7 @@ describe('resolveI18nLabel — the rule departures converged with objectui#3907;
388388
});
389389
});
390390

391-
describe('resolveI18nLabel — the type signature refuses the calls that caused #6761', () => {
391+
describe('resolveI18nLabel — the type signature refuses the calls behind the dropped dataset label', () => {
392392
// Reverse verification at the type level. `check:test-typecheck` compiles this
393393
// file (packages/spec/tsconfig.test.json), so each directive below is a REAL
394394
// check: delete the argument it guards and tsc goes red on the unused

0 commit comments

Comments
 (0)