From 49bd6fdfa8f7a98be9fddcb712c40c6fe6b1843a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 01:23:25 +0000 Subject: [PATCH 1/3] test(client): pay the four auth suites' cold start at module scope, outside every clocked window The first case of auth-get-session-envelope, auth-rotated-session-token, organization-invitation-resend-team-placement and organization-invite-role-default paid the worker's one-time cold start (better-auth's lazy module graph, the sql.js WASM compile, first-use sync and sign-up) inside vitest's clocked test window. Each file now pays it once through its own arrangement at module scope, during collection, and pins that placement. auth-rotated-session-token's seven explicit 60_000 per-case timeouts are removed: they widened the window instead of moving the cost out of it. Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP Co-authored-by: Claude --- .../src/auth-get-session-envelope.test.ts | 64 ++++++++++- .../src/auth-rotated-session-token.test.ts | 101 +++++++++++++++--- ...n-invitation-resend-team-placement.test.ts | 83 +++++++++++++- .../organization-invite-role-default.test.ts | 83 +++++++++++++- 4 files changed, 308 insertions(+), 23 deletions(-) diff --git a/packages/client/src/auth-get-session-envelope.test.ts b/packages/client/src/auth-get-session-envelope.test.ts index 78724bbff50..9f34c71a5f4 100644 --- a/packages/client/src/auth-get-session-envelope.test.ts +++ b/packages/client/src/auth-get-session-envelope.test.ts @@ -45,8 +45,42 @@ // `data.token` is absent from the normalized body too, so a regression back // to `data.data?.token` cannot pass by accident, and the "enveloping it // would have fixed refreshToken" reading stays refuted in code. +// - `⑥ the cold start stays outside every clocked window` — pins WHERE the +// first scenario's one-time cost is paid (next section). +// +// ## [#20327] Why one throwaway scenario runs at MODULE SCOPE +// +// The first scenario in a worker pays a one-time cost no later one sees: +// better-auth's lazily imported module graph, sql.js's WASM compile, and the +// first-use costs of the sync and the sign-up. The phases are measured in +// `auth-login-register-envelope.test.ts`, which carries the same arrangement +// and the same fix. Here, idle on 4 vCPU at `c74de10a9`, that cost put the +// first case at 1035 ms against 110-330 ms for the six after it. On a loaded +// `Test Core` shard (PR #20325's run) the first case crossed vitest's 5000 ms +// `testTimeout` while every other case passed. Twenty-four CPU-bound busy +// loops on this box reproduce that exact signature on the first case. +// +// So the cost is now paid by a module-scope `await`, during COLLECTION, which +// no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies +// in `withTimeout(...)` and awaits the file import bare. This is the repo's +// convention: "clocked windows measure behaviour, never loading" (AGENTS.md, +// Build & Test; `check:test-source-alias`). The warm-up is the file's own +// `signedIn()`, the arrangement six of the seven cases run, so no list of +// loads can drift from what the cases really pay. +// +// ⛔ It shares nothing a case asserts on. Its engine and manager are its own +// and are closed before any case starts. Every case still builds a fresh +// engine, a fresh `AuthManager` and a fresh sign-up. What it leaves warm is +// process-level: the module registry, sql.js's compiled WASM and the JIT, +// which the first case used to leave to every later case. +// +// ⛔ Do not move it into a hook, and do not answer a recurrence by raising a +// timeout: vitest clocks a hook exactly as it clocks a test body, and a wider +// window only moves the cliff to a heavier shard. import { describe, it, expect, afterEach } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { ObjectQL } from '@objectstack/objectql'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; import { AuthManager } from '@objectstack/plugin-auth'; @@ -250,12 +284,21 @@ const principalFor = async (manager: AuthManager, token: string | undefined) => return session?.user?.id ?? null; }; -afterEach(async () => { +/** Close every engine a scenario opened: after each case, and after the warm-up. */ +const closeEngines = async (): Promise => { while (engines.length) { const engine = engines.pop(); await (engine as unknown as { close?: () => Promise })?.close?.().catch(() => {}); } -}); +}; + +// [#20327] The first scenario's one-time cost, paid during COLLECTION, which +// no vitest clock covers (header, last section). ⛔ It stays at module scope, +// and `⑥` below pins that. +await signedIn(); +await closeEngines(); + +afterEach(closeEngines); describe('[#16760] /get-session is lifted into the SessionResponse envelope it declares', () => { describe('① me() delivers the envelope it declares', () => { @@ -427,4 +470,21 @@ describe('[#16760] /get-session is lifted into the SessionResponse envelope it d expect(typeof res.data.session?.token).toBe('string'); }); }); + + describe('⑥ the cold start stays outside every clocked window', () => { + it('pays the first scenario at module scope, and no hook carries it', () => { + // [#20327] Read off this file's own text, so "do not move it into a + // hook" is an assertion rather than a sentence nobody reads. + const code = readFileSync(fileURLToPath(import.meta.url), 'utf8'); + + // Exactly one warm-up call, and it opens its own line at column 0. So it + // sits in no function body, which is what "paid during collection" + // reduces to. Comment lines start with `//` and cannot match. + expect(code.match(/^await signedIn\(\);$/gm) ?? []).toHaveLength(1); + + // ⛔ No `before*` hook may come back to carry it: vitest clocks a hook + // with `hookTimeout` exactly as it clocks a test body with `testTimeout`. + expect(code).not.toMatch(/^\s*before(All|Each)\s*\(/m); + }); + }); }); diff --git a/packages/client/src/auth-rotated-session-token.test.ts b/packages/client/src/auth-rotated-session-token.test.ts index 6eb528daede..7c02a381f50 100644 --- a/packages/client/src/auth-rotated-session-token.test.ts +++ b/packages/client/src/auth-rotated-session-token.test.ts @@ -41,8 +41,42 @@ // that header in the shared `fetch` wrapper instead of on the rotating routes // would rewrite the stored credential on an ordinary write — and every // assertion in ① and ② would stay green. +// - `④ the cold start stays outside every clocked window` — pins WHERE the +// first scenario's one-time cost is paid (next section). +// +// ## [#20327] Why one throwaway scenario runs at MODULE SCOPE +// +// The first scenario in a worker pays a one-time cost no later one sees: +// better-auth's lazily imported module graph, sql.js's WASM compile, and the +// first-use costs of the sync and the sign-up. The phases are measured in +// `auth-login-register-envelope.test.ts`, which carries the same arrangement +// and the same fix. Every case here used to carry an explicit `60_000` +// timeout, which WIDENED the first case's window around that cost instead of +// moving the cost out of it: every budget a cost is moved into can be +// exhausted by a heavier shard (`check:test-source-alias`, clocked-window +// rule). +// +// So the cost is now paid by a module-scope `await`, during COLLECTION, which +// no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies +// in `withTimeout(...)` and awaits the file import bare. This is the repo's +// convention: "clocked windows measure behaviour, never loading" (AGENTS.md, +// Build & Test). The warm-up is the file's own `signedIn()`, the arrangement +// every case runs, so no list of loads can drift from what the cases pay. With +// the cost moved out, the `60_000`s are gone and every case runs on the +// default `testTimeout`. +// +// ⛔ It shares nothing a case asserts on. Its engine and manager are its own +// and are destroyed before any case starts. Every case still builds a fresh +// engine, a fresh `AuthManager` and a fresh sign-up. What it leaves warm is +// process-level: the module registry, sql.js's compiled WASM and the JIT, +// which the first case used to leave to every later case. +// +// ⛔ Do not move it into a hook, and do not answer a recurrence by raising a +// timeout: vitest clocks a hook exactly as it clocks a test body. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { createHmac } from 'node:crypto'; import { ObjectQL } from '@objectstack/objectql'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; @@ -261,12 +295,8 @@ const signedCredentialFor = async ( return String(signed); }; -beforeEach(() => { - vi.spyOn(console, 'warn').mockImplementation(() => {}); - vi.spyOn(console, 'error').mockImplementation(() => {}); -}); -afterEach(async () => { - vi.restoreAllMocks(); +/** Destroy every engine a scenario opened: after each case, and after the warm-up. */ +const closeEngines = async (): Promise => { while (engines.length) { const e = engines.pop(); try { @@ -275,6 +305,21 @@ afterEach(async () => { /* noop */ } } +}; + +// [#20327] The first scenario's one-time cost, paid during COLLECTION, which +// no vitest clock covers (header, last section). ⛔ It stays at module scope, +// and `④` below pins that. +await signedIn(); +await closeEngines(); + +beforeEach(() => { + vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); +}); +afterEach(async () => { + vi.restoreAllMocks(); + await closeEngines(); }); // ─────────────────────────────────────────────────────────────────────────── @@ -335,7 +380,7 @@ describe("#16534 ① the card's own probe, with the manual re-set deleted", () = // status code: after the whole sequence the client is still holding a // credential that resolves to the same principal. expect(await principalForStoredToken(manager, client)).toBe(userId); - }, 60_000); + }); }); // ─────────────────────────────────────────────────────────────────────────── @@ -358,7 +403,7 @@ describe('#16534 ② one assertion per rotating route — all three', () => { expect(await principalForStoredToken(manager, client)).toBe(userId); // …and the one it replaced is genuinely gone. expect(await principalFor(manager, before)).toBeNull(); - }, 60_000); + }); it('changePassword WITHOUT revokeOtherSessions rotates nothing and stores nothing', async () => { // The other half of the same route: `token` is `null` there, and a client @@ -374,7 +419,7 @@ describe('#16534 ② one assertion per rotating route — all three', () => { expect(result.token).toBeNull(); expect(storedToken(client)).toBe(before); expect(await principalForStoredToken(manager, client)).not.toBeNull(); - }, 60_000); + }); it('twoFactor.verifyTotp on the enrolment lane — the body echoes the LIVE token', async () => { const { engine, manager, client, email } = await signedIn(); @@ -390,7 +435,7 @@ describe('#16534 ② one assertion per rotating route — all three', () => { // The row behind the replaced value was deleted, so asserting only "the // stored token changed" would not have been enough. expect(await principalFor(manager, before)).toBeNull(); - }, 60_000); + }); it('twoFactor.disable — the credential arrives ONLY in the `set-auth-token` header', async () => { // The route triage singled out: it answers `{ status: true }`, so an @@ -410,7 +455,7 @@ describe('#16534 ② one assertion per rotating route — all three', () => { expect(storedToken(client)).not.toBe(before); expect(await principalForStoredToken(manager, client)).toBe(userId); expect(await principalFor(manager, before)).toBeNull(); - }, 60_000); + }); }); // ─────────────────────────────────────────────────────────────────────────── @@ -439,7 +484,7 @@ describe('#16534 ③ the negative control — a NON-rotating route changes nothi // Still the same live session at the end of it — the invariant is "did not // move", not "was emptied". expect(await principalForStoredToken(manager, client)).not.toBeNull(); - }, 60_000); + }); it("verifyBackupCode's already-logged-in lane leaves the stored credential byte-identical", async () => { // `/two-factor/verify-backup-code` shares `AuthTwoFactorVerificationResult` @@ -472,5 +517,35 @@ describe('#16534 ③ the negative control — a NON-rotating route changes nothi expect(result.token).not.toBe(signed); expect(storedToken(bearerClient), 'verifyBackupCode moved the stored credential').toBe(signed); expect(await principalForStoredToken(manager, bearerClient)).toBe(userId); - }, 60_000); + }); +}); + +// ─────────────────────────────────────────────────────────────────────────── +describe('#16534 ④ the cold start stays outside every clocked window', () => { + it('pays the first scenario at module scope, no hook carries it, and no case widens its clock', () => { + // [#20327] Read off this file's own text, so "do not move it into a + // hook" is an assertion rather than a sentence nobody reads. + const code = readFileSync(fileURLToPath(import.meta.url), 'utf8'); + + // Exactly one warm-up call, and it opens its own line at column 0. So it + // sits in no function body, which is what "paid during collection" + // reduces to. Comment lines start with `//` and cannot match. + expect(code.match(/^await signedIn\(\);$/gm) ?? []).toHaveLength(1); + + // ⛔ No `before*` hook may come back to carry it: vitest clocks a hook + // with `hookTimeout` exactly as it clocks a test body with `testTimeout`. + // This file's one hook is the console-spy `beforeEach`. Each hook is read + // up to the next column-0 `});`, so a hook indented inside a `describe` + // reads on to that block's close and cannot hide a scenario from this. + const hooks = [...code.matchAll(/^\s*before(?:All|Each)\s*\(([\s\S]*?)^\}\);$/gm)].map( + (m) => m[1], + ); + expect(hooks).toHaveLength(1); + for (const body of hooks) expect(body).not.toMatch(/\b(signedIn|arrange)\(/); + + // ⛔ And no case widens its own clock again. Every case here used to end + // `}, 60_000);`, which kept the cold start inside a wider window instead + // of moving it out. + expect(code).not.toMatch(/^\s*\},\s*[\d_]+\s*\);$/m); + }); }); diff --git a/packages/client/src/organization-invitation-resend-team-placement.test.ts b/packages/client/src/organization-invitation-resend-team-placement.test.ts index c4397f6cf48..03cbf23ced6 100644 --- a/packages/client/src/organization-invitation-resend-team-placement.test.ts +++ b/packages/client/src/organization-invitation-resend-team-placement.test.ts @@ -54,9 +54,45 @@ * .test.ts) plus one team, deliberately not extracted: a shared harness that * one card's edit can reshape under another card's pins is the drift both * files exist to catch. + * + * ⑥ pins WHERE the first scenario's one-time cost is paid (next section). + * + * ## [#20327] Why one throwaway scenario runs at MODULE SCOPE + * + * The first scenario in a worker pays a one-time cost no later one sees: + * better-auth's lazily imported module graph, sql.js's WASM compile, and the + * first-use costs of the sync and the sign-up. The phases are measured in + * `auth-login-register-envelope.test.ts`, which carries the same arrangement + * and the same fix. Here, idle on 4 vCPU at `c74de10a9`, that cost put ① at + * 1245 ms against 307-411 ms for the four later cases that run `arrange()`. + * Under twenty-four CPU-bound busy loops, the load that reproduces CI's + * failure in the sibling suites, ① timed out at vitest's 5000 ms + * `testTimeout` while every other case passed. + * + * So the cost is now paid by a module-scope `await`, during COLLECTION, which + * no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies + * in `withTimeout(...)` and awaits the file import bare. This is the repo's + * convention: "clocked windows measure behaviour, never loading" (AGENTS.md, + * Build & Test; `check:test-source-alias`). The warm-up is the file's own + * `arrange()`, so no list of loads can drift from what the cases really pay. + * It needs the posture the `beforeAll` sets for the cases, so it holds that + * posture for itself and puts back what it found, even when it throws. + * + * ⛔ It shares nothing a case asserts on. Every case still builds a fresh + * engine, a fresh `AuthManager`, a fresh owner and a fresh organization. Its + * engine stays open, as every case's does (`arrange()` hands none back), so + * no case runs in a state no case ran in before: every case but the first + * already ran after an earlier scenario. What it leaves warm is + * process-level: the module registry, sql.js's compiled WASM and the JIT. + * + * ⛔ Do not move it into a hook, and do not answer a recurrence by raising a + * timeout: vitest clocks a hook exactly as it clocks a test body, and a wider + * window only moves the cliff to a heavier shard. */ import { describe, it, expect, expectTypeOf, vi, beforeAll, afterAll } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { ObjectQL } from '@objectstack/objectql'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; import { AuthManager } from '@objectstack/plugin-auth'; @@ -90,14 +126,17 @@ const IDENTITY_OBJECTS = Object.values( */ const PRIOR_POSTURE = process.env.OS_TENANCY_POSTURE; +/** Put back the posture this file found: after the warm-up, and after the file. */ +const restorePosture = (): void => { + if (PRIOR_POSTURE === undefined) delete process.env.OS_TENANCY_POSTURE; + else process.env.OS_TENANCY_POSTURE = PRIOR_POSTURE; +}; + beforeAll(() => { process.env.OS_TENANCY_POSTURE = 'isolated'; }); -afterAll(() => { - if (PRIOR_POSTURE === undefined) delete process.env.OS_TENANCY_POSTURE; - else process.env.OS_TENANCY_POSTURE = PRIOR_POSTURE; -}); +afterAll(restorePosture); let seq = 0; const nextEmail = (tag: string) => `os17274-${tag}-${++seq}-${Date.now()}@example.com`; @@ -151,6 +190,13 @@ async function arrange(): Promise { return { client, organizationId, teamId }; } +// [#20327] The first scenario's one-time cost, paid during COLLECTION, which +// no vitest clock covers (header, last section). It holds the posture the +// `beforeAll` above sets for the cases, and `.finally` puts back what it +// found. ⛔ It stays at module scope, and `⑥` below pins that. +process.env.OS_TENANCY_POSTURE = 'isolated'; +await arrange().finally(restorePosture); + /** What `invitations.list` answers for one invitation — the round-trip shape. */ type ListedInvitation = Awaited< ReturnType @@ -454,3 +500,32 @@ export async function resendTeamIdStaysDeclared17274(): Promise { // hands to `invite`: the round trip is a type-level fact, not a convention. expectTypeOf().toExtend(); } + +// ───────────────────────────────────────────────────────────────────────── +// ⑥ the cold start stays outside every clocked window +// ───────────────────────────────────────────────────────────────────────── + +describe('#17274 ⑥ the cold start stays outside every clocked window', () => { + it('pays the first scenario at module scope, and no hook carries it', () => { + // [#20327] Read off this file's own text, so "do not move it into a + // hook" is an assertion rather than a sentence nobody reads. + const code = readFileSync(fileURLToPath(import.meta.url), 'utf8'); + + // Exactly one warm-up call, and it opens its own line at column 0. So it + // sits in no function body, which is what "paid during collection" + // reduces to. Comment lines start with `//` or ` *` and cannot match. + expect(code.match(/^await arrange\(\)\.finally\(restorePosture\);$/gm) ?? []).toHaveLength(1); + + // ⛔ No `before*` hook may come back to carry it: vitest clocks a hook + // with `hookTimeout` exactly as it clocks a test body with `testTimeout`. + // This file's one hook is the posture `beforeAll`, which loads nothing. + // Each hook is read up to the next column-0 `});`, so a hook indented + // inside a `describe` reads on to that block's close and cannot hide a + // scenario from this. + const hooks = [...code.matchAll(/^\s*before(?:All|Each)\s*\(([\s\S]*?)^\}\);$/gm)].map( + (m) => m[1], + ); + expect(hooks).toHaveLength(1); + for (const body of hooks) expect(body).not.toMatch(/\barrange\(/); + }); +}); diff --git a/packages/client/src/organization-invite-role-default.test.ts b/packages/client/src/organization-invite-role-default.test.ts index 2aa56a7fbcf..9e1665b8ae1 100644 --- a/packages/client/src/organization-invite-role-default.test.ts +++ b/packages/client/src/organization-invite-role-default.test.ts @@ -58,9 +58,45 @@ * caller, while the default costs no type change at all. ⑤ is a compile-time * assertion that `role` is still optional, so that route cannot be taken later * by accident. + * + * ⑥ pins WHERE the first scenario's one-time cost is paid (next section). + * + * ## [#20327] Why one throwaway scenario runs at MODULE SCOPE + * + * The first scenario in a worker pays a one-time cost no later one sees: + * better-auth's lazily imported module graph, sql.js's WASM compile, and the + * first-use costs of the sync and the sign-up. The phases are measured in + * `auth-login-register-envelope.test.ts`, which carries the same arrangement + * and the same fix. Here, idle on 4 vCPU at `c74de10a9`, that cost put ① at + * 968 ms against 310-336 ms for ② and ③, which run the same `arrange()`. + * Under twenty-four CPU-bound busy loops, the load that reproduces CI's + * failure in the sibling suites, ① timed out at vitest's 5000 ms + * `testTimeout` while every other case passed. + * + * So the cost is now paid by a module-scope `await`, during COLLECTION, which + * no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies + * in `withTimeout(...)` and awaits the file import bare. This is the repo's + * convention: "clocked windows measure behaviour, never loading" (AGENTS.md, + * Build & Test; `check:test-source-alias`). The warm-up is the file's own + * `arrange()`, so no list of loads can drift from what the cases really pay. + * It needs the posture the `beforeAll` sets for the cases, so it holds that + * posture for itself and puts back what it found, even when it throws. + * + * ⛔ It shares nothing a case asserts on. Every case still builds a fresh + * engine, a fresh `AuthManager`, a fresh owner and a fresh organization. Its + * engine stays open, as every case's does (`arrange()` hands none back), so + * no case runs in a state no case ran in before: every case but the first + * already ran after an earlier scenario. What it leaves warm is + * process-level: the module registry, sql.js's compiled WASM and the JIT. + * + * ⛔ Do not move it into a hook, and do not answer a recurrence by raising a + * timeout: vitest clocks a hook exactly as it clocks a test body, and a wider + * window only moves the cliff to a heavier shard. */ import { describe, it, expect, expectTypeOf, vi, beforeAll, afterAll } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { ObjectQL } from '@objectstack/objectql'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; import { AuthManager } from '@objectstack/plugin-auth'; @@ -96,14 +132,17 @@ const IDENTITY_OBJECTS = Object.values( */ const PRIOR_POSTURE = process.env.OS_TENANCY_POSTURE; +/** Put back the posture this file found: after the warm-up, and after the file. */ +const restorePosture = (): void => { + if (PRIOR_POSTURE === undefined) delete process.env.OS_TENANCY_POSTURE; + else process.env.OS_TENANCY_POSTURE = PRIOR_POSTURE; +}; + beforeAll(() => { process.env.OS_TENANCY_POSTURE = 'isolated'; }); -afterAll(() => { - if (PRIOR_POSTURE === undefined) delete process.env.OS_TENANCY_POSTURE; - else process.env.OS_TENANCY_POSTURE = PRIOR_POSTURE; -}); +afterAll(restorePosture); let seq = 0; const nextEmail = (tag: string) => `os16582-${tag}-${++seq}-${Date.now()}@example.com`; @@ -155,6 +194,13 @@ async function arrange(): Promise { return { client, organizationId }; } +// [#20327] The first scenario's one-time cost, paid during COLLECTION, which +// no vitest clock covers (header, last section). It holds the posture the +// `beforeAll` above sets for the cases, and `.finally` puts back what it +// found. ⛔ It stays at module scope, and `⑥` below pins that. +process.env.OS_TENANCY_POSTURE = 'isolated'; +await arrange().finally(restorePosture); + // ───────────────────────────────────────────────────────────────────────── // ① the card's call, against the real vendor schema // ───────────────────────────────────────────────────────────────────────── @@ -328,3 +374,32 @@ export async function inviteRoleStaysOptional16582(): Promise { Exclude >(); } + +// ───────────────────────────────────────────────────────────────────────── +// ⑥ the cold start stays outside every clocked window +// ───────────────────────────────────────────────────────────────────────── + +describe('#16582 ⑥ the cold start stays outside every clocked window', () => { + it('pays the first scenario at module scope, and no hook carries it', () => { + // [#20327] Read off this file's own text, so "do not move it into a + // hook" is an assertion rather than a sentence nobody reads. + const code = readFileSync(fileURLToPath(import.meta.url), 'utf8'); + + // Exactly one warm-up call, and it opens its own line at column 0. So it + // sits in no function body, which is what "paid during collection" + // reduces to. Comment lines start with `//` or ` *` and cannot match. + expect(code.match(/^await arrange\(\)\.finally\(restorePosture\);$/gm) ?? []).toHaveLength(1); + + // ⛔ No `before*` hook may come back to carry it: vitest clocks a hook + // with `hookTimeout` exactly as it clocks a test body with `testTimeout`. + // This file's one hook is the posture `beforeAll`, which loads nothing. + // Each hook is read up to the next column-0 `});`, so a hook indented + // inside a `describe` reads on to that block's close and cannot hide a + // scenario from this. + const hooks = [...code.matchAll(/^\s*before(?:All|Each)\s*\(([\s\S]*?)^\}\);$/gm)].map( + (m) => m[1], + ); + expect(hooks).toHaveLength(1); + for (const body of hooks) expect(body).not.toMatch(/\barrange\(/); + }); +}); From 937e6e109551cce3be099f4d42d3dce045cb5403 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:19:07 +0000 Subject: [PATCH 2/3] test(client): record the measured residual and the CI readings in the suites' headers auth-rotated-session-token's first case is the card's whole probe; with the cold start moved out, what its window holds is its own work, and the header now says how that measured under load and where the lever is. The two organization suites' headers quote their own first-case readings from the red Test Core run instead of a claim about every other case. Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP Co-authored-by: Claude --- packages/client/src/auth-rotated-session-token.test.ts | 9 +++++++++ ...organization-invitation-resend-team-placement.test.ts | 8 +++++--- .../client/src/organization-invite-role-default.test.ts | 8 +++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/client/src/auth-rotated-session-token.test.ts b/packages/client/src/auth-rotated-session-token.test.ts index 7c02a381f50..3a232a01488 100644 --- a/packages/client/src/auth-rotated-session-token.test.ts +++ b/packages/client/src/auth-rotated-session-token.test.ts @@ -65,6 +65,15 @@ // the cost moved out, the `60_000`s are gone and every case runs on the // default `testTimeout`. // +// ⚠ What a window holds now is behaviour, and ① holds the most: the card's +// whole probe, a sign-up plus five more calls that each check a password or a +// TOTP code. It takes about 700 ms on 4 vCPU with the box otherwise quiet, and +// a warm-up that ran the WHOLE probe left it no faster than this one does, so +// none of that is loading. Under sixteen CPU-bound busy loops it took 4.1-4.2 +// s. Under twenty-four, the load that times out every one of these suites' +// cold first cases, it reached the 5000 ms budget on its own work. If CI ever +// reds it, the lever is that case's own work, ⛔ not a timeout. +// // ⛔ It shares nothing a case asserts on. Its engine and manager are its own // and are destroyed before any case starts. Every case still builds a fresh // engine, a fresh `AuthManager` and a fresh sign-up. What it leaves warm is diff --git a/packages/client/src/organization-invitation-resend-team-placement.test.ts b/packages/client/src/organization-invitation-resend-team-placement.test.ts index 03cbf23ced6..fc8d67ebd71 100644 --- a/packages/client/src/organization-invitation-resend-team-placement.test.ts +++ b/packages/client/src/organization-invitation-resend-team-placement.test.ts @@ -65,9 +65,11 @@ * `auth-login-register-envelope.test.ts`, which carries the same arrangement * and the same fix. Here, idle on 4 vCPU at `c74de10a9`, that cost put ① at * 1245 ms against 307-411 ms for the four later cases that run `arrange()`. - * Under twenty-four CPU-bound busy loops, the load that reproduces CI's - * failure in the sibling suites, ① timed out at vitest's 5000 ms - * `testTimeout` while every other case passed. + * On the `Test Core` shard where a sibling suite went red (PR #20325's run), + * ① took 3599 ms here against 486-820 ms for the later cases that run + * `arrange()`. Under twenty-four CPU-bound busy loops on this box, the load + * that reproduces that red, ① timed out at vitest's 5000 ms `testTimeout` in + * every run. * * So the cost is now paid by a module-scope `await`, during COLLECTION, which * no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies diff --git a/packages/client/src/organization-invite-role-default.test.ts b/packages/client/src/organization-invite-role-default.test.ts index 9e1665b8ae1..809bd274c29 100644 --- a/packages/client/src/organization-invite-role-default.test.ts +++ b/packages/client/src/organization-invite-role-default.test.ts @@ -69,9 +69,11 @@ * `auth-login-register-envelope.test.ts`, which carries the same arrangement * and the same fix. Here, idle on 4 vCPU at `c74de10a9`, that cost put ① at * 968 ms against 310-336 ms for ② and ③, which run the same `arrange()`. - * Under twenty-four CPU-bound busy loops, the load that reproduces CI's - * failure in the sibling suites, ① timed out at vitest's 5000 ms - * `testTimeout` while every other case passed. + * On the `Test Core` shard where a sibling suite went red (PR #20325's run), + * ① took 2849 ms here against 497-820 ms for the later cases that run + * `arrange()`. Under twenty-four CPU-bound busy loops on this box, the load + * that reproduces that red, ① timed out at vitest's 5000 ms `testTimeout` in + * every run. * * So the cost is now paid by a module-scope `await`, during COLLECTION, which * no vitest clock covers: `@vitest/runner@4.1.11` wraps hooks and test bodies From 220735eb3bbc427077a48db6ac2d1c7854d1b176 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:50:56 +0000 Subject: [PATCH 3/3] test(client): auth-get-session-envelope's warm-up runs the arrangement five of its seven cases run The header miscounted: two of the seven cases run `anonymous()`, not `signedIn()`. Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP Co-authored-by: Claude --- packages/client/src/auth-get-session-envelope.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/auth-get-session-envelope.test.ts b/packages/client/src/auth-get-session-envelope.test.ts index 9f34c71a5f4..96e852bd9e7 100644 --- a/packages/client/src/auth-get-session-envelope.test.ts +++ b/packages/client/src/auth-get-session-envelope.test.ts @@ -65,7 +65,7 @@ // in `withTimeout(...)` and awaits the file import bare. This is the repo's // convention: "clocked windows measure behaviour, never loading" (AGENTS.md, // Build & Test; `check:test-source-alias`). The warm-up is the file's own -// `signedIn()`, the arrangement six of the seven cases run, so no list of +// `signedIn()`, the arrangement five of the seven cases run, so no list of // loads can drift from what the cases really pay. // // ⛔ It shares nothing a case asserts on. Its engine and manager are its own