Skip to content

test(client): pay the auth-envelope suite's cold start at module scope, outside every clocked window - #20272

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20242-client-auth-envelope-cold-start
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20242-client-auth-envelope-cold-start

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20242

Clause-②: no

The first case of packages/client/src/auth-login-register-envelope.test.ts paid the worker's one-time cold start inside vitest's 5000 ms testTimeout. It timed out on Test Core (5/6) (run 36320810893, head 3d14f9e75), on a PR whose diff does not reach this suite. This PR moves that cost out of every clocked window. No budget changes. The diff is one test file, +81 / -2.

Premise check (done first, on origin/main at fdb26698f)

Holds. Every case builds a fresh ObjectQL over a new SqliteWasmDriver(':memory:'), runs syncSchemas, constructs a real AuthManager, and performs a real better-auth sign-up. The file set no timeout and had no warm-up. premise_still_valid: true.

Where the first case's time went

Phase probe (a throwaway file, never committed): four successive scenarios in one worker, idle, 4 vCPU, at fdb26698f.

phase first scenario later scenarios
getAuthInstance() (better-auth's lazily imported module graph) 510 ms 10-11 ms
engine.init() (sql.js compiles its WASM once) 85 ms 1-2 ms
syncSchemas() 182 ms 81-100 ms
the sign-up 261 ms 117-127 ms

So about 850 ms of the first case's window was one-time cost that no other case pays. Idle, the first case took 855-964 ms, against 222-249 ms for the two other cases doing the same single sign-up.

The failing CI run's own totals point the same way. Against this tree idle (--maxWorkers=2), its client package read tests 38.71s vs 23.39s and import 202.17s vs 102.68s. Test bodies ran about 1.5x slower there, while the first case ran more than 5x slower. The term that crossed the budget was cold loading, not the behaviour the case asserts.

The route, and why it is neither of the two the dispatch offered

The dispatch offered (a) a beforeAll warm-up with its own explicit timeout, or (b) an explicit per-case timeout. Both keep the cost inside a clock. @vitest/runner@4.1.11, as installed in this tree, wraps hooks and test bodies in withTimeout(...), while collectTests() awaits runner.importFile(filepath, "collect") bare (dist/chunk-artifact.js).

The repo already recorded what (a) costs. scripts/check-test-source-alias.mjs's clocked-window section describes a cold load moved into a beforeAll (hookTimeout 10000) that then hit 20.26 s on a merge-queue shard. Its rule, repeated in AGENTS.md (Build & Test), is "clocked windows measure behaviour, never loading". packages/cli/src/commands/datasource/envelope-unwrap.test.ts is the in-repo precedent for the same class. So the cost is now paid by a module-scope await, during collection, which no vitest clock covers.

What the warm-up runs: one whole throwaway scenario, the file's own registered(), not a list of loads. A load-only warm-up (engine plus getAuthInstance()) left the next scenario at 344-409 ms against 212-269 ms, because the sign-up path has first-use costs of its own. The full scenario left none: 258-284 ms against 214-289 ms. Naming the file's own arrangement also avoids a specifier list that could drift.

What changed

  • One module-scope await registered(); await closeEngines();.
  • The old afterEach body becomes closeEngines, byte-for-byte the same loop, reused by the warm-up.
  • A header section with the measurements above.
  • ⑦, a placement pin. It reads this file's own text and asserts exactly one column-0 await registered(); and no before* hook. So "do not move it into a hook" is an assertion.

No assertion in cases ①-⑥ changed. There is no skip, no retry, no quarantine and no timeout anywhere.

The warm-up shares nothing a case asserts on. Its engine and manager are its own and are closed before collection ends. Every case still builds a fresh engine, a fresh AuthManager and a fresh first sign-up. What it leaves warm is process-level: the module registry, sql.js's compiled WASM and the JIT. The first case used to leave exactly that to the fourteen after it. Before the fix every other case was already a "warm" case, so no case now runs in a state no case ran in before.

Before / after at CI's own budget

The failure reproduces at the real 5000 ms budget under CPU contention: 24 CPU-bound node -e 'for(;;){}' loops on 4 vCPU, their PIDs recorded and killed by trap. On the unmodified base tree (fdb26698f) this load gave CI's exact signature, 1 failed | 14 passed, on the first case, Test timed out in 5000ms, in 2 of 2 runs.

Measured as 10 interleaved pairs: an ABLATED leg (the fix deleted), then the FIX leg (the committed file), same command, same load. The box is shared and its background load drifts, so interleaving puts the drift on both sides equally.

./node_modules/.bin/vitest run --maxWorkers=2 --reporter=verbose src/auth-login-register-envelope.test.ts
N = 10 each, 5000 ms, 24 busy loops runs red first-case timeouts first case where every failure landed
ablated (module-scope warm-up deleted) 10 / 10 9 / 10 4788-5402 ms first case x9, ⑦ pin x10, one ⑤ case x1 (pair 10)
fix (a05c2e2da) 1 / 10 0 / 10 1461-2804 ms both ⑤ cases, pair 10 only

Pair 10 ran on a heavier box: vitest wall 196-203 s against 129-141 s for pairs 1-9. Unlocked work belonging to another worktree was running beside it (read from the process table, not killed). There, the ablated leg failed the first case plus one ⑤ case.

A separate, unpaired series of 10 fix runs under the same 24 loops shows the same split. Runs 1-5, at the calibration's box load (wall 130-151 s), were 5 of 5 green. Runs 6-10, with that extra unlocked load present (wall 185-200 s), were 5 of 5 red, every one on the two ⑤ cases (5002-5124 ms) and none on the first case (case 1 there: 1851-2936 ms).

Idle, after the fix, 10 of 10 runs are green (16 tests). The first case takes 238-352 ms, level with its same-work siblings at 197-291 ms.

Margin, and its basis

Each case's window now holds only its own behaviour: a fresh-engine sync plus real scrypt sign-ups and logins. The heaviest cases are the first two in ⑤, each doing two sign-ups and a login.

basis heaviest per-case window margin to 5000 ms
idle, this box, after the fix 502-689 ms 7.3x
CI's failing run: test bodies about 1.5x idle (package totals above) about 0.75-1.0 s (inferred, not measured) about 5x
24 busy loops, pairs 1-9 (the load that reproduces CI's failure 9 / 10 when ablated) 3031-4108 ms 1.2-1.6x
24 busy loops plus the extra unlocked load (pair 10, unpaired runs 6-10) 5002-5138 ms reached

So this does not raise a number that the next slower runner will exceed: no number was raised. The fix takes about 850 ms idle of one-time loading out of the first case's window. Under the synthetic CPU starvation that reproduces the failure, that is more than 3 s. The first case, with 0 timeouts in 20 loaded fix runs, is no longer where the file fails.

What a much slower runner reaches next is the ⑤ cases' own behaviour. On this box that took about 1.4x more load than the load that reproduces CI's failure. That is a real per-case cost the case exists to pay, and it is recorded in Acceptance notes rather than papered over.

Ablation (commit first, restore proven)

The fix was committed (a05c2e2da) before any mutation. Each ablated leg went through node scripts/ablation-replace.mjs --file ABS_PATH --anchor 'await registered();\nawait closeEngines();\n' --delete -- ... (WRAP mode, restore armed on EXIT, INT and TERM).

For every leg the tool reported: anchor x1 -> x0, blob 261d72a2e -> dbec8e3fc, and "ok mutation landed". The wrapped command itself printed warm-up lines=0 read off the disk before vitest started.

After every leg, and at the end of each chunk, the restore was proven by blob hash (git hash-object of the file == git rev-parse HEAD:PATH == 261d72a2e, an empty hash reading as failure) and by git diff HEAD = 0 bytes. That is 14 of 14 proofs. The tree was clean before and after. The test file is run from source, so no dist/ preflight applies.

Verification

All at 6b7a46bbc (after merging origin/main; the branch delta against it is exactly this one file, +81 / -2). Every command's exit code was captured before any pipe.

  • The dependency closure was built first: pnpm turbo run build --filter='@objectstack/client...' --concurrency=2, 33 of 33 tasks.
  • pnpm --filter @objectstack/client exec vitest run --maxWorkers=2 src/auth-login-register-envelope.test.ts: Tests 16 passed (16), exit 0.
  • pnpm --filter @objectstack/client exec vitest run --maxWorkers=2: Test Files 50 passed (50), Tests 637 passed (637), exit 0.
  • pnpm --filter @objectstack/client typecheck: exit 0, check:test-typecheck: OK, 0 files / 0 errors. tsc -p tsconfig.test.json --listFilesOnly does compile this file (1 hit), so that green covers it.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran: 52 derived famil(ies) accounted for — 50 run, 2 NOT-MEASURED, and every one of the 50 run exited 0.
    • NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt. Both exited 3 (PREREQUISITE NOT MET) because they need every package's dist/, i.e. the whole ./packages/* build. This is a declared narrowing, and CI runs both.
    • Why this diff cannot move either: the one file is a test, which no build emits (the client dist/ check is under Changeset below), and @objectstack/client is in neither type-check debt ledger. Its own test-layer ledger is empty and was re-measured just above at 0 errors.
    • check:skill-examples first exited 3 because packages/client-react/dist was absent. After pnpm turbo run build --filter=@objectstack/client-react the same command exited 0: 259 prose examples across 3 surfaces.
  • node scripts/check-issue-citations.mjs --base origin/main (the real board check): exit 0. 4 citations judged, all resolve.
  • pnpm lint (eslint . --no-inline-config, the whole repo, not a narrowing): exit 0.
  • Control-byte self-scan of the changed file: no hits.

Changeset

skip-changeset. The diff is one *.test.ts file. @objectstack/client's files[] is dist, README.md, CHANGELOG.md. After pnpm turbo run build --filter='@objectstack/client...', strings unique to this test file (closeEngines, the ⑦ title) have 0 hits under packages/client/dist/, while the positive control ObjectStackClient hits all 4 emitted artifacts (index.js, index.mjs, index.d.ts, index.d.mts). No published byte changes.

Acceptance notes

  • Census (triage note 3): other packages/client suites that build a fresh engine plus a real sign-up per case. None of them is edited here.
    • src/auth-get-session-envelope.test.ts: 7 cases, all 7 through signedIn() / anonymous() on a fresh SqliteWasmDriver engine with syncSchemas and a real AuthManager. Timeout posture: vitest default 5000 ms, no warm-up. Its first case pays the same cold start this PR moves; same exposure.
    • src/auth-rotated-session-token.test.ts: 7 cases, all 7 through signedIn(). Timeout posture: an explicit 60_000 on all 7 cases. The cold start still sits in its first case's window, widened rather than moved, which is the posture check:test-source-alias's header says relocates the cliff.
    • src/organization-invitation-resend-team-placement.test.ts: 11 cases, 5 of them through arrange() (fresh engine, real AuthManager, real sign-up). Default 5000 ms. Its beforeAll/afterAll only set and restore OS_TENANCY_POSTURE and load nothing. The first case, ①, calls arrange() and so pays the cold start.
    • src/organization-invite-role-default.test.ts: 6 cases, 3 through arrange(), same shape and posture as the one above; its first case ① pays the cold start.
    • The client.hono / client.batch-transaction / client.data-prefix / client.environment-scoping / client.metadata-prefix suites build a SqliteWasmDriver engine but perform no sign-up. client.test.ts has no real AuthManager (its sign-up cases are fetch-level). Out of this shape.
  • Residual in this file: the first two ⑤ cases each build two scenarios (registered() then signedIn(), i.e. two sign-ups plus a login), which makes them the file's heaviest windows. Under 24 busy loops plus extra unlocked load they reached 5002-5138 ms (6 of 20 loaded fix runs). CI has not shown them failing. If it ever does, the lever is that case's own work, because signedIn() already contains a registration. ⛔ Not a timeout. Noted, not filed.
  • When a case times out, vitest does not cancel its body. The orphaned sign-in then ran against an engine afterEach had already closed, and logged [ObjectQL] No driver available for object 'sys_session' (seen only in loaded runs where a ⑤ case had already timed out). It is noise after a timeout, not a second failure. Noted, not filed.
  • The measurement box was shared. The verify lock serialises locked runs only, and another worktree's unlocked build and gate work ran beside pair 10 and the unpaired runs 6-10. The interleaved design is what keeps the before/after comparison fair under that drift.

Generated by Claude Code

…e, outside every clocked window

The first case of auth-login-register-envelope.test.ts paid the worker's
one-time cold start (better-auth's lazily imported module graph, sql.js's
WASM compile, first-use costs on the sign-up path) inside vitest's 5000 ms
testTimeout. A module-scope await of one throwaway scenario now pays it
during collection, which vitest clocks against nothing. Every case still
builds its own fresh engine, AuthManager and first sign-up. A new case
pins the placement.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 17bd31877109b7cc692e7e54c4fe39f82a5c32d5 → packageMentionDocs.

@github-actions github-actions Bot added the tests label Sep 27, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 27, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 18:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 80c29a1 Sep 27, 2026
40 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20242-client-auth-envelope-cold-start branch September 27, 2026 18:29
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…de every clocked window (objectstack-ai#20366)

Fixes objectstack-ai#20327

Clause-②: no

Four `packages/client` suites paid the worker's one-time cold start
inside their first clocked test window: better-auth's lazily imported
module graph, the sql.js WASM compile, and the first-use costs of the
sync and the sign-up. One of them, `auth-get-session-envelope.test.ts`,
timed out at 5000 ms on the required `Test Core (5/6)` shard of PR
objectstack-ai#20325, whose diff does not reach the package. This PR ports PR objectstack-ai#20272's
shape to all four: each file pays its own arrangement once at module
scope, during collection, which no vitest clock covers, and pins that
placement. `auth-rotated-session-token.test.ts` also loses its seven
explicit `60_000` per-case timeouts. No other number is raised, and
there is no retry, skip or quarantine. The diff is four test files.

This card carries the family (triage note 2): the census below covers
all 50 `packages/client/src/*.test.ts` files.

## Premise check (on `origin/main` at `c74de10a9`)

Holds. In `auth-get-session-envelope.test.ts`, the first case calls
`await signedIn()` inside its `it` (`:262`-`:264`), and every case
builds a fresh `SqliteWasmDriver` engine, a real `AuthManager`, and five
of the seven perform a real sign-up. `premise_still_valid: true`.

The red CI run itself (job `108717176436`) shows the same shape in two
more files of the family:

| suite, in the red `Test Core (5/6)` run | first case | later cases
doing the same arrangement |
|:---|---:|---:|
| `auth-get-session-envelope` | timed out at 5000 ms | 583-1255 ms |
| `organization-invitation-resend-team-placement` | 3599 ms | 486-820 ms
|
| `organization-invite-role-default` | 2849 ms | 497-820 ms |

## Census: every `packages/client/src/*.test.ts` (50 files)

The criterion (dispatch, Zone 2): a suite that boots an engine (a
`SqliteWasmDriver` engine, a real `AuthManager`, or a real sign-up)
inside its first clocked `it`, or in a `beforeAll`/`beforeEach`.

| files | shape | verdict |
|:---|:---|:---|
| `auth-get-session-envelope` | 7 cases, all through
`signedIn()`/`anonymous()`: fresh engine + real `AuthManager`; default
5000 ms; the first case paid the cold start | **included** |
| `auth-rotated-session-token` | 7 cases, all through `signedIn()`; an
explicit `60_000` on all 7 cases, which widened the first case's window
instead of moving the cost out | **included**; the `60_000`s removed |
| `organization-invitation-resend-team-placement` | 5 of 11 cases
through `arrange()` (fresh engine, real `AuthManager`, real sign-up, an
org and a team); default 5000 ms; `①` paid the cold start | **included**
|
| `organization-invite-role-default` | 3 of 6 cases through `arrange()`;
default 5000 ms; `①` paid the cold start | **included** |
| `auth-login-register-envelope` | same shape | already fixed by PR
objectstack-ai#20272 (`80c29a14`); not edited |
| `client.hono`, `client.batch-transaction`, `client.data-prefix`,
`client.environment-scoping`, `client.metadata-prefix` | a `LiteKernel`
+ ObjectQL + `SqliteWasmDriver` REST/Hono server booted ONCE per file
(per describe in the two `*-prefix` files) as a shared fixture in a
`beforeAll` with an explicit `30_000` hookTimeout; no `AuthManager`, no
sign-up | **excluded**: the claim's file surface is suites that boot
inside their first clocked `it`, and a hook-booted shared fixture is a
different shape (the boot IS the fixture). Reach: in the red CI run all
five passed; under 24 busy loops on this box all five passed (1 run).
Their hook duration is NOT MEASURED: vitest's JSON file span does not
include it. Noted, not filed. |
| `meta-delete-item-carriers` | a `SqliteWasmDriver` engine + the real
protocol + `RestServer` booted inside `it`, but only from the 14th case
on (Part 2); its first 13 cases are mock-fetch; an explicit `60_000` on
all 7 engine cases; no `AuthManager`, no sign-up | **excluded**: its
first clocked `it` is a mock case, so it is outside the claim's file
surface as written. Its first engine case is not where a cold start sits
heavily: 137 ms idle against 38-194 ms for the later engine cases, and
810 ms against 312-1204 ms under 24 busy loops. Its `60_000`s widen
windows that measured under 1.3 s. Noted, not filed. |
| `i18n-wire-dialect` | a `LiteKernel` + `HonoServerPlugin` in a
`beforeAll`, no driver, no auth | **excluded**: boots no engine |
| the other 38 | mock `fetch`, source-text reads, or type-level
assertions; `client.test.ts`'s sign-up cases are fetch-level;
`oauth-applications-*` and `organization-get-active-member-addressing`
drive doubles | **excluded**: no engine |

Count: 4 included, 1 already fixed, 45 excluded.

## What changed

Per file, the same three moves as PR objectstack-ai#20272:

- A module-scope warm-up that runs the file's OWN arrangement, not a
list of loads:
- `auth-get-session-envelope`: `await signedIn(); await closeEngines();`
- `auth-rotated-session-token`: `await signedIn(); await
closeEngines();`
- the two organization suites: `process.env.OS_TENANCY_POSTURE =
'isolated';` then `await arrange().finally(restorePosture);`.
`arrange()` needs the posture the file's `beforeAll` sets for its cases,
and the module scope runs before any hook, so the warm-up holds the
posture itself and `.finally` puts back what it found, even if it
throws.
- The teardown the warm-up reuses is extracted without changing it:
`closeEngines` is the old `afterEach` loop byte for byte (in
`auth-rotated-session-token` the `afterEach` keeps its
`vi.restoreAllMocks()` and calls `closeEngines`); `restorePosture` is
the old `afterAll` body byte for byte.
- A placement pin per file (`⑥`, or `④` in
`auth-rotated-session-token`), read off the file's own text:
  - exactly one column-0 warm-up line, so it sits in no function body;
- in `auth-get-session-envelope`, no `before*` hook at all (as in PR
objectstack-ai#20272's `⑦`);
- in the three files that keep a legitimate hook (the console-spy
`beforeEach`, the posture `beforeAll`), exactly one hook, and no hook
body calls the arrangement. A hook indented inside a `describe` is read
on to that block's column-0 close, so it cannot hide one;
- in `auth-rotated-session-token`, also no `}, N);` per-case timeout, so
the widened posture cannot come back.
- A header section per file with that file's own readings.

No assertion in an existing case changed. The warm-up shares nothing a
case asserts on: 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, the JIT), which the first
case used to leave to every later case. In the two organization suites
the warm-up's engine stays open, as every case's does there (`arrange()`
hands none back and those files close none), so no case runs in a state
no case ran in before.

Why module scope and not a hook or a timeout: `@vitest/runner@4.1.11`,
as installed here, wraps hooks and test bodies in `withTimeout(...)`
(`dist/chunk-artifact.js:672`, `:724`, `:1787`) and awaits
`runner.importFile(filepath, "collect")` bare (`:2457`). The repo's rule
is "clocked windows measure behaviour, never loading" (AGENTS.md, Build
& Test; `check:test-source-alias`).

## Before / after, at CI's own 5000 ms budget

Idle (4 vCPU, the box otherwise quiet), first case against the later
cases of the same file:

| suite | base `c74de10a9` | fix `49bd6fdfa` |
|:---|---:|---:|
| `auth-get-session-envelope` | 1035 vs 110-330 ms | 316 vs 90-277 ms |
| `auth-rotated-session-token` | 1713 vs 302-665 ms | 809 vs 278-581 ms
|
| `organization-invitation-resend-team-placement` | 1245 vs 307-411 ms |
332 vs 284-399 ms |
| `organization-invite-role-default` | 968 vs 310-336 ms | 382 vs
329-340 ms |

Under load, PR objectstack-ai#20272's method: CPU-bound `node -e 'for(;;){}'` loops on
4 vCPU, PIDs recorded and killed by trap. One vitest run per leg over
the four files, `--maxWorkers=2`. On the base tree, 24 loops gave CI's
exact signature, `Test timed out in 5000ms` on the first case, in all
three default-budget suites (1 run); `auth-rotated-session-token`'s
first case took 10314 ms there, green only because of its `60_000`.

Measured as interleaved pairs: an ABLATED leg (the four warm-ups deleted
from the committed files) and a FIX leg (the committed files), same
command, same load, order alternated between pairs. The box is shared,
so interleaving puts its drift on both sides.

```
./node_modules/.bin/vitest run --maxWorkers=2 --reporter=verbose --reporter=json --outputFile.json=RUN.json FOUR_FILES
```

| 24 busy loops, N = 4 pairs | ablated: first-case timeouts | fix:
first-case timeouts | fix: first case | fix: heaviest other case |
|:---|---:|---:|---:|---:|
| `auth-get-session-envelope` | 4 / 4 | **0 / 4** | 1969-2536 ms |
1979-2751 ms |
| `auth-rotated-session-token` | 4 / 4 | **4 / 4** (see below) |
5034-5319 ms | 3440-4771 ms |
| `organization-invitation-resend-team-placement` | 4 / 4 | **0 / 4** |
1577-3095 ms | 2523-3086 ms |
| `organization-invite-role-default` | 4 / 4 | **0 / 4** | 2359-2575 ms
| 2056-2271 ms |

| 16 busy loops, N = 2 pairs | ablated: first-case timeouts | fix:
first-case timeouts | fix: first case |
|:---|---:|---:|---:|
| `auth-get-session-envelope` | 1 / 2 (4816, 5134 ms) | 0 / 2 |
1320-1523 ms |
| `auth-rotated-session-token` | 2 / 2 (5053, 5229 ms) | 0 / 2 |
4125-4185 ms |
| `organization-invitation-resend-team-placement` | 1 / 2 (4752, 5113
ms) | 0 / 2 | 1853-1904 ms |
| `organization-invite-role-default` | 0 / 2 (4663, 4903 ms) | 0 / 2 |
1429-1611 ms |

Every ablated leg also reddened all four placement pins (4 of 4, in all
6 ablated legs). Every fix leg's pins passed. In one ablated leg,
`organization-invitation-resend-team-placement`'s second case also timed
out (5121 ms): vitest does not cancel a timed-out body, so the first
case's orphaned body was still running beside it. No fix leg showed
that.

## `auth-rotated-session-token`: the `60_000`s are gone, and its first
case is heavy by its own work

Triage note 3 and the dispatch both rule out a raised timeout, so all
seven `60_000`s are removed. With the cold start moved out, six of the
seven cases fit the default budget at both loads. The first case (`①`)
does not fit at 24 busy loops, and that is its own behaviour, not
loading:

- `①` is the card's whole probe: a sign-up plus five more calls that
each check a password or a TOTP code (login, enable, verifyTotp,
disable, deleteUser).
- A throwaway variant whose module-scope warm-up ran that WHOLE probe
left `①` no faster: 637-716 ms against 690-709 ms for the committed
warm-up (3 runs each, idle, the file alone). So no loading is left in
its window.

| basis | `①` | margin to 5000 ms |
|:---|---:|---:|
| idle, fix | 690-809 ms | 6.2-7.2x |
| the red CI run's own slowdown on warm cases
(`auth-get-session-envelope`'s same-work cases ran 2.9-5.9x their idle
time) | about 2.0-4.8 s (inferred, not measured) | about 1.0-2.5x |
| 16 busy loops, fix (2 runs) | 4125-4185 ms | 1.2x |
| 24 busy loops, fix (4 runs) | 5034-5319 ms | reached, 4 of 4 |

Before this PR the same case held a 10.3 s window at 24 busy loops (cold
start plus this work) under its `60_000`. The file's header records this
residual and names the lever: the case's own work, not a timeout. The
open question in the report asks the seat whether to accept it as is.

## Ablation (fix committed first, restore proven)

The fix was committed (`49bd6fdfa`) before any mutation. Each ablated
leg nested four `node scripts/ablation-replace.mjs --file ABS_PATH
--anchor ANCHOR --delete -- ...` calls (WRAP mode, restore armed on
EXIT, INT and TERM), one per file:

- `auth-get-session-envelope`, `auth-rotated-session-token`: the anchor
`await signedIn();` + newline + `await closeEngines();` + newline;
- the two organization suites: the posture line + newline + `await
arrange().finally(restorePosture);` + newline.

In every leg the tool reported, per file, anchor x1 to x0, a changed
blob, and "ok mutation landed" (24 of 24), and the leg printed `warm-up
lines=0 (of 4 files)` read off the disk before vitest started. After
every leg the tool proved each restore by blob hash against `HEAD` and
an empty `git diff HEAD` (24 of 24), and a second check after each leg
read `4/4 blob == HEAD blob, git diff HEAD = 0 bytes` (12 of 12 legs).
The files run from source, so no `dist/` preflight applies.

## Verification

All at `220735eb3`: the fix `49bd6fdfa`, two commits that only edit
header comments, and a merge of `origin/main` at `d498113b5`. The branch
delta against it is exactly the four test files, +321 / -23. Every exit
code was captured before any pipe. The union below was first run at
`5a9a01d00` and then run again in full at `220735eb3`, after the last
commit corrected a count in a comment. Both runs gave the same verdicts.

- After the merge: `pnpm install --frozen-lockfile`, then the dependency
closure `pnpm turbo run build --filter='@objectstack/client...'
--concurrency=2`, 33 of 33 tasks.
- `pnpm --filter @objectstack/client exec vitest run --maxWorkers=2`:
`Test Files 50 passed (50)`, `Tests 641 passed (641)` (637 before, plus
the 4 pins), exit 0.
- `pnpm --filter @objectstack/client typecheck`: exit 0,
`check:test-typecheck: OK`, 0 files / 0 errors. `tsc -p
tsconfig.test.json --listFilesOnly` compiles all four files (4 hits), so
that green covers them.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--ran RAN_LIST` (every line with its recorded exit code): `53 derived
famil(ies) accounted for — 51 run, 2 NOT-MEASURED`, 0 UNRUN, and each of
the 51 run exited 0.
- NOT MEASURED: `check:dual-build-cjs-loads` and
`check:type-check-debt`. Both exited 3 (PREREQUISITE NOT MET): they need
every package's `dist/`, the whole `./packages/*` build. A declared
narrowing; CI runs both. This diff is four test files, which no build
emits (the `dist/` check is under Changeset), and
`@objectstack/client`'s test-layer debt ledger is empty and re-measured
just above at 0 errors.
- `check:skill-examples` exited 3 until its prerequisite was met: first
`packages/client-react/dist` was absent, then `packages/client/dist` was
older than an edited `src/` file. After `pnpm turbo run build
--filter=@objectstack/client-react` and `pnpm --filter
@objectstack/client build`, the same command exited 0: 259 prose
examples across 3 surfaces.
- `node scripts/check-issue-citations.mjs --base origin/main`: exit 0.
It judged 0 files: test files are on its deferred list.
- `pnpm lint` (`eslint . --no-inline-config`, the whole repo, not a
narrowing): exit 0.
- Control-byte self-scan of the four files: no hits (`check:nul-bytes`
also exit 0).

## Changeset

`skip-changeset`. The diff is four `*.test.ts` files.
`@objectstack/client`'s `files[]` is `dist`, `README.md`,
`CHANGELOG.md`. After `pnpm --filter @objectstack/client build` at
`220735eb3`, strings unique to these test files (`closeEngines`,
`restorePosture`, `cold start stays outside`, `OS_TENANCY_POSTURE`) have
0 hits under `packages/client/dist/`, while the positive control
`ObjectStackClient` hits all 4 emitted artifacts (`index.js`,
`index.mjs`, `index.d.ts`, `index.d.mts`). No published byte changes.

## Acceptance notes

- **`auth-rotated-session-token.test.ts` `①` residual** (section above):
at 24 busy loops it reaches the 5000 ms budget on its own work, 4 of 4
runs; at 16 loops it fits with 1.2x. CI has not run it on the default
budget before this PR. If CI reds it, the lever is that case's own work
(for example splitting the probe, which would change the card's own
probe and needs a ruling), not a timeout. Its second-heaviest cases
(`changePassword` with `revokeOtherSessions`, and `twoFactor.disable`)
reached 4.7-4.8 s at 24 loops.
- **`meta-delete-item-carriers.test.ts`** carries an explicit `60_000`
on all 7 of its engine cases, the posture the clocked-window rule warns
relocates a cliff. Measured, those windows are small (under 1.3 s at 24
busy loops) and its first engine case holds little one-time cost, so it
is no timeout risk today. Outside this card's surface. Noted, not filed.
- **The five hook-booted `client.*` suites** boot a shared server in a
`beforeAll` with an explicit `30_000` hookTimeout. They passed in the
red CI run and under 24 busy loops here. Their hook time is not
measured. Outside this card's surface. Noted, not filed.
- The two organization suites never close their engines, before or after
this PR (`arrange()` hands none back). The warm-up adds one more open
in-memory engine per file, as each of their cases does. Noted, not
filed.
- The measurement box was shared: the verify lock serialises locked runs
only, and other worktrees' work ran beside some legs (the one-minute
load average read 2.1-26 at pair starts). The interleaved pairs, with
the order alternated, are what keep the before/after comparison fair
under that drift.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants