Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/verify/src/action-flow-elevated-door.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ import type { Flow } from '@objectstack/spec/automation';

import { bootStack, type VerifyStack } from './harness.js';
import { isVerifyRefusal } from './handle.js';
import { warmUpBoot } from './boot-warm-up.testkit.js';

// Booting the full in-process stack runs well past vitest's 5s default.
const BOOT_TIMEOUT = 120_000;

// The boot's first load, paid during collection rather than in the
// `beforeAll` below (see `boot-warm-up.testkit.ts`): the same options and the
// same `requires` as this file's own boot.
await warmUpBoot({ automation: true }, { requires: ['automation', 'triggers'] });

const LEDGER = 'fda_ledger';
const WATCH = 'fda_watch';

Expand Down
6 changes: 6 additions & 0 deletions packages/verify/src/automation-trigger-elevated-door.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ import type { Flow } from '@objectstack/spec/automation';

import { bootStack, type VerifyStack } from './harness.js';
import { isVerifyRefusal } from './handle.js';
import { warmUpBoot } from './boot-warm-up.testkit.js';

// Booting the full in-process stack runs well past vitest's 5s default.
const BOOT_TIMEOUT = 120_000;

// The boot's first load, paid during collection rather than in the
// `beforeAll` below (see `boot-warm-up.testkit.ts`): the same options and the
// same `requires` as this file's own boot.
await warmUpBoot({ automation: true }, { requires: ['automation', 'triggers'] });

const LEDGER = 'etd_ledger';
const WATCH = 'etd_watch';

Expand Down
78 changes: 78 additions & 0 deletions packages/verify/src/boot-warm-up.testkit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Test scaffolding: a test file that boots a real stack pays the boot's FIRST
* load at module scope, with `await warmUpBoot(...)`, never inside a clocked
* window.
*
* The convention is the repo's: "clocked windows measure behaviour, never
* loading — a test that boots a real plugin chain pays its first load at
* module top" (AGENTS.md, Build & Test; `scripts/check-test-source-alias.mjs`
* carries the measurement behind it). `@vitest/runner` wraps hooks and test
* bodies in `withTimeout(...)` and reports their duration as the file's time,
* while it awaits the module's own evaluation during COLLECTION, which no
* clock covers.
*
* Why this package needs it: the first `bootStack` in a test file is 2 to 3 s
* slower than every later one in that file (measured per boot, isolated,
* 4 vCPU: 2.7 to 4.2 s against 0.75 to 1.25 s), and the difference is first
* use, not the boot: the default datasource's drivers and better-auth's
* entries are `import()`ed lazily from inside the boot, sql.js compiles its
* WASM, and every path runs cold. Without this, that cost lands in whichever
* `beforeAll` or `it` boots first, and the hourly timing table charges it to
* the file. A warm-up took 1.2 to 3.8 s off the clocked time of each of the 24
* files of this package that the table listed (isolated, one run each).
*
* Why a WHOLE throwaway boot rather than a list of module-top imports: the
* lazily loaded modules (`@objectstack/driver-sql`,
* `@objectstack/driver-sqlite-wasm`, `better-auth` and its plugins) are not
* this package's dependencies, so importing them here would be an undeclared
* import, and a list would drift as the boot changes. Running the boot, a
* sign-in and a sign-up pays all of it through this package's own harness.
*
* ⛔ It shares nothing a case asserts on. It boots an app of its own (no
* objects), on its own in-memory database, and stops it before the file's
* first hook runs. What stays warm is process-level: the module registry,
* sql.js's compiled WASM and the JIT.
*
* ⛔ Call it at module scope, on a line of its own, never in a hook: vitest
* clocks a hook with `hookTimeout` exactly as it clocks a test with
* `testTimeout`. And never answer a recurrence by raising a timeout.
*
* ```ts
* await warmUpBoot({ automation: true });
* ```
*/

import { bootStack, type BootOptions } from './harness.js';

/** What the throwaway app declares, beside its manifest. */
export interface WarmUpShape {
/**
* The capability tokens the file's own app `requires`, so the providers its
* boot mounts are loaded here too (see `required-providers.ts`).
*/
requires?: readonly string[];
}

/**
* Boot a throwaway app with `opts`, sign the seeded admin in, sign one member
* up, and stop. Pass the options (and the `requires`) the file's own first
* boot uses, so the same lazily loaded code is paid for here.
*/
export async function warmUpBoot(opts: BootOptions = {}, shape: WarmUpShape = {}): Promise<void> {
// A fresh configuration object per call: `bootStack`'s instance rule keys on
// identity, and nothing here may be shared with the file's own boots.
const config = {
manifest: { id: 'com.example.boot-warm-up', namespace: 'warmup', version: '0.0.1', type: 'app', name: 'Boot Warm-up' },
objects: [],
...(shape.requires ? { requires: [...shape.requires] } : {}),
};
const stack = await bootStack(config, opts);
try {
await stack.signIn();
await stack.signUp('boot-warm-up@verify.test');
} finally {
await stack.stop();
}
}
6 changes: 6 additions & 0 deletions packages/verify/src/delete-behavior.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ import {
formatDeleteBehaviorReport,
type DeleteBehaviorReport,
} from './delete-behavior.js';
import { warmUpBoot } from './boot-warm-up.testkit.js';

// Booting the full in-process stack runs well past vitest's 5s default.
const BOOT_TIMEOUT = 120_000;
const RUN_TIMEOUT = 120_000;

// The boot's first load, paid during collection rather than in the first
// `beforeAll` (see `boot-warm-up.testkit.ts`). Both boots below use the
// default options.
await warmUpBoot();

/** One holder per shape the family probes, and one per shape it names instead. */
const app = {
manifest: { id: 'com.example.delete-behavior', namespace: 'dbp', version: '0.0.1', type: 'app', name: 'Delete Behaviour Fixture' },
Expand Down
23 changes: 20 additions & 3 deletions packages/verify/src/handle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// permission check at all), so it is pinned with a control that FIRES: the
// admin, on the identical call, is admitted by both doors.

import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';

import { bootStack, bootStackOnce, type VerifyStack, type BootOptions } from './harness.js';
import { isVerifyRefusal } from './handle.js';
Expand All @@ -28,13 +28,18 @@ import {
STAGE_FORECAST,
today,
} from './handle.fixture.js';
import { warmUpBoot } from './boot-warm-up.testkit.js';

// Booting the full in-process stack runs well past vitest's 5s default.
const BOOT_TIMEOUT = 120_000;

/** A module constant, so `bootStackOnce` can key the shared boot on it. */
const BOOT_OPTIONS: BootOptions = { automation: true };

// The boot's first load, paid during collection rather than in the shared
// boot's `beforeAll` (see `boot-warm-up.testkit.ts`).
await warmUpBoot(BOOT_OPTIONS);

let stack: VerifyStack;
let admin: string;
let member: string;
Expand All @@ -61,10 +66,22 @@ afterAll(async () => {
* `packages/objectql/src/registry.ts`, `definition: { ...schema, name: fqn }`).
* So the fixture is BUILT AGAIN: a fresh module instance of `./handle.fixture.js`
* runs its builder once more, and every nested definition is new.
*
* The fresh instance is the fixture module alone, under a specifier of its own
* (the module runner keys a module by its id, query included). Its imports
* resolve to the instances this file already holds, as they did for the
* original. `vi.resetModules()` would also give a fresh instance, but it drops
* the whole registry, so the re-import re-evaluated the fixture's entire graph
* (`@objectstack/spec` and the rest) and the boot after it re-loaded every
* module it imports lazily: 1.2 to 1.6 s more per call, isolated, inside the
* case's clock.
*/
let freshInstances = 0;
async function freshHandleFixtureStack(): Promise<typeof handleFixtureStack> {
vi.resetModules();
return (await import('./handle.fixture.js')).handleFixtureStack;
freshInstances += 1;
const specifier = `./handle.fixture.js?instance=${freshInstances}`;
const fresh = (await import(/* @vite-ignore */ specifier)) as typeof import('./handle.fixture.js');
return fresh.handleFixtureStack;
}

/** Unique per run, so list assertions never see another test's rows. */
Expand Down
6 changes: 6 additions & 0 deletions packages/verify/src/harness.served-composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ import { CAPABILITY_PROVIDERS } from '@objectstack/core';
import { composeStacks, defineStack } from '@objectstack/spec';
import { PLATFORM_ALWAYS_ON_CAPABILITIES } from '@objectstack/spec/kernel';
import { bootStack, type VerifyStack } from './harness.js';
import { warmUpBoot } from './boot-warm-up.testkit.js';

const BOOT_TIMEOUT = 120_000;

// The boot's first load, paid during collection rather than in the first
// case's clock (see `boot-warm-up.testkit.ts`). Every boot below uses the
// default options.
await warmUpBoot();

const svcManifest = { id: 'com.example.svc.svc', name: 'SVC Service', namespace: 'svc', version: '1.0.0', type: 'module' };
const appManifest = { id: 'com.example.svc.app', name: 'SVC App', namespace: 'svc', version: '1.0.0', type: 'app' };
const note = {
Expand Down
6 changes: 6 additions & 0 deletions scripts/check-verify-stand-in-erasure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ const NOT_A_STAND_IN = {
fillRelationalRefs:
'takes one `CrudCase` derived by `deriveCrudCases` — a data record this package built, with no ' +
'external implementer.',
warmUpBoot:
'takes the same `BootOptions` that `bootStack` in this same package takes, and hands them straight ' +
'to it. It is test scaffolding (`boot-warm-up.testkit.ts`): `index.ts` does not re-export it and it ' +
'is absent from `dist`. It CHECKS nothing — a throwaway boot, sign-in and sign-up paid at a test ' +
"file's module scope — so no conformance's compile-time half lives in that parameter, and an " +
'assertion at a call site would delete no check.',
};

/** True when `node` is, or wraps, any type assertion. Mirrors the ESLint rule. */
Expand Down
Loading