test(client): pay four auth suites' cold start at module scope, outside every clocked window - #20366
Merged
objectstack-fleet[bot] merged 4 commits intoSep 28, 2026
Conversation
…utside 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 <noreply@anthropic.com>
… 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 <noreply@anthropic.com>
…ient-suites-cold-start
…t 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 <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing 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): |
objectstack-fleet
Bot
deleted the
claude/issue-20327-client-suites-cold-start
branch
September 28, 2026 04:01
This was referenced Sep 28, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…stem-* migration entries states each lesson in words, not tracker numbers (stage 3) (objectstack-ai#20384) Part of objectstack-ai#20233 Clause-②: no **Stage 3 of a staged card.** The card stays open for later stages; this PR carries no closing keyword. Text only: no entry id, `surface`, `from` / `to`, conversion or matching logic moves, and the chain rewrites exactly what it rewrote before. ## What this does `os migrate meta` prints every ADR-0087 semantic entry it crosses as one block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's `reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's runtime-string rule applies to all of it: 「Runtime strings — refusal prose, prescriptions, anything an author is shown — carry no tracker number (`pnpm check:doc-authoring`): the lesson goes into the text.」 Form **D** of ruling C+D on the parent card sets the shape: the lesson in words, and no number, dead or alive. This stage covers the next three families by site count, `driver-`, `kernel-` and `system-`: **132 sites → 0** in the three prose fields. None of the 26 entries carries a tracker id in `surface` (ruling A of the stage-1 ACCEPT, `5858839916`, is checked and has nothing to do here). Each site now says what the cited ruling, measurement or fix decided. ADR ids stay. `registry.ts`, `spec-changes.json` and `docs/protocol-upgrade-guide.md` are regenerated from the entries (`gen:migration-registry`, `gen:spec-changes`, `gen:upgrade-guide`), never hand-edited. The stage-1 pin now holds `engine-`, `ui-`, `plugin-`, `driver-`, `kernel-` and `system-`. ## Census — tracker ids in the author-shown fields **Instrument.** The stage-2 AST instrument, unchanged: a TypeScript-AST walk over every `packages/spec/src/migrations/entries/**/*.ts`. For each `entry` object literal it evaluates the string value of `replacement`, `reason`, `acceptanceCriteria` and (counted separately) `surface`, joining string literals with `+`, then counts `#` followed by 4 or 5 digits at a word boundary. **Validated first** by reproducing the stage-1 readings on the stage-1 tree (`443b2f4fdc`, extracted with `git archive`): `driver-` 7 entries / 44 sites (0 / 44 / 0, 25 distinct), `kernel-` 9 / 44 (1 / 41 / 2, 11 distinct), `system-` 10 / 44 (0 / 42 / 2, 6 distinct), `engine-` 5 / 67, whole tree 266 entries / 1,016 sites / 9 `surface` sites — every figure equal to the stage-1 census. **Tree measured:** `objectstack-ai/objectstack` at `569d4d2dbf` (this branch's base). Unevaluable fields: 0. **Controls, same run.** - **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites (replacement 1, reason 6), the reading stages 1 and 2 took. - **Dark (comment lines):** 794 `//` lines in entry files carry a tracker id, and none is counted. Comment lines belong to the sibling card, and ⛔ this PR touches none (794 before and after). - **Dark (field boundary):** the 7 `surface` sites left in the tree (other families) count 0 in the three-field total and 7 in the `surface` column. **Re-measured on the base, matching the stage-1 census:** `driver-` 7 entries, **44** sites (replacement 0 / reason 44 / acceptanceCriteria 0), 25 distinct ids; `kernel-` 9 entries, **44** (1 / 41 / 2), 11 distinct; `system-` 10 entries, **44** (0 / 42 / 2), 6 distinct. 37 distinct ids across the three (the families share `objectstack-ai#14478`, `objectstack-ai#15939`, `objectstack-ai#17635` and `objectstack-ai#3733`). `surface`: 0 in all three. Whole tree: 300 entries, **843** sites, 7 `surface` sites. **After this PR:** `driver-` 0, `kernel-` 0, `system-` 0; `engine-`, `ui-`, `plugin-` still 0; whole tree **843 → 711** sites; `surface` 7 (unchanged, other families). | entry | sites (replacement / reason / acceptanceCriteria) | |---|---| | `17.driver-aggregate-undeclared-key-aliases-removed` | 6 (0 / 6 / 0) | | `17.driver-capabilities-inert-bits-removed` | 4 (0 / 4 / 0) | | `18.driver-options-timeout-to-timeout-ms` | 1 (0 / 1 / 0) | | `17.driver-sql-distinct-bare-filter-typed` | 9 (0 / 9 / 0) | | `18.driver-sql-unresolvable-where-column-refused` | 14 (0 / 14 / 0) | | `18.driver-sql-upsert-cross-row-identity-merge-refused` | 9 (0 / 9 / 0) | | `18.driver-turso-config-local-path-wasm-retired` | 1 (0 / 1 / 0) | | `18.kernel-compatibility-matrix-estimated-migration-time-unit-in-key` | 5 (0 / 5 / 0) | | `18.kernel-context-preview-mode-retired` | 5 (1 / 4 / 0) | | `18.kernel-event-bus-retention-unit-in-key` | 3 (0 / 3 / 0) | | `18.kernel-health-check-and-hot-reload-durations-unit-in-key` | 7 (0 / 5 / 2) | | `18.kernel-package-lifecycle-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.kernel-plugin-health-report-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.kernel-plugin-security-durations-unit-in-key` | 4 (0 / 4 / 0) | | `18.kernel-runtime-config-timeout-unit-in-key` | 11 (0 / 11 / 0) | | `18.kernel-startup-orchestrator-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-cache-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-collaboration-durations-unit-in-key` | 4 (0 / 4 / 0) | | `18.system-failover-health-check-interval-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-metrics-jsdoc-durations-unit-in-key` | 12 (0 / 12 / 0) | | `18.system-metrics-window-durations-unit-in-key` | 5 (0 / 3 / 2) | | `18.system-object-storage-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-registry-config-durations-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-tracing-otel-exporter-durations-unit-in-key` | 5 (0 / 5 / 0) | | `18.system-tracing-span-duration-unit-in-key` | 3 (0 / 3 / 0) | | `18.system-worker-queue-rate-limit-duration-unit-in-key` | 3 (0 / 3 / 0) | | **total, 26 entries** | **132 (1 / 127 / 4)** | ## Every citation read, and what the text now says I read each cited issue or PR myself with single-card REST reads: the body, and the comments where a ruling or a measurement lives. Ids are in code spans so this body posts no cross-references. All 36 bare ids were resolved against this repository, because every sentence that cites one is about this repository's code; the one cross-repo id is `cloud#1651`. | cited | what it decided (read) | how the text now carries it | |---|---|---| | `objectstack-ai#3733` | The pruned `cached` field key: measured, the parse succeeded and the removed key was dropped without a word; the orphan schema was deleted. | "an earlier field-key prune measured exactly that — the parse succeeded and the removed key was dropped without a word" (health-check, OTel exporter) | | `objectstack-ai#3821` | The sharing-rule page: an unsortable query fell through to an empty page, and the driver fix made an unsortable query lose its ORDER BY, not its rows. | "the unknown-column recovery ladder (an unsortable query loses its ORDER BY, not its rows)"; "the ladder's own premise — rows matter more than their order"; "the ladder's recoveries" | | `objectstack-ai#4484` | `IDataDriver.findStream` removed: no production caller, two of three implementations buffered the whole set, and no tombstone because nothing parses a driver object. | "Retiring `IDataDriver.findStream` (it had no production caller, and two of its three implementations read the whole result set into memory …)"; "(`IDataDriver.findStream`, removed with no tombstone because nothing parses a driver object)"; by entry id in the `distinct` entry | | `objectstack-ai#4583` | The datasource ledger's dead keys removed; `capabilities.*` went as a whole block (11 of 11 unread). | "was retired separately, as a whole block nothing read" | | `objectstack-ai#4634` | Audit of all 34 `DriverCapabilities` bits: 3 live, 31 dead and tombstoned. | the entry already states the audit ("the follow-up audit checked every bit"); the trailing id is dropped | | `objectstack-ai#4914` | Maintainer, 2026-08-04: remove `manifest.loading` and `PluginHotReloadSchema`; keep `HotReloadConfigSchema`, the side with an implementation (`HotReloadManager`), as the start point. | "kept twice: as the hot-reload vocabulary that had an implementation when the manifest-side copy was removed, …" | | `objectstack-ai#4984` | An org-axis red-line gate read only aliases the schema rejects while its own fixtures spelt them: tests green, rule dead. | "the family of the org-axis red-line gate that read only rejected aliases while its own fixtures spelt them, so its tests stayed green and the rule stayed dead" | | `objectstack-ai#5181` | Narrow the query parameter of `IDataDriver`'s methods (`DriverQuery`, no redundant `object`). | "neither the narrowing of `IDataDriver`'s query parameters to `DriverQuery` nor the follow-through …" | | `objectstack-ai#5499` | Maintainer, 2026-08-05: freeze investment in `driver-memory` / `driver-mongodb`; fully lifted 2026-08-11 (comments `5249019855`, `5252526378`). | "the maintainer's 2026-08-05 investment freeze on driver-memory, which was lifted on 2026-08-11" | | `objectstack-ai#5540` | Remove `IStorageService.list(prefix)`: zero consumers, and the two adapters answered differently and both incompletely. | "(the zero-consumer `IStorageService.list`, whose two adapters answered differently and both incompletely)" | | `objectstack-ai#6011` | Maintainer: close the `ctx.user` `roles` alias now. | "(the `ctx.user` `roles` alias, closed at once on the maintainer's word rather than given a window)" | | `objectstack-ai#6075` | **404** — see Acceptance notes. | "the follow-through that brought five drivers' implementations in line" | | `objectstack-ai#6320` | `distinct`'s third argument meant different things on memory and sql; the sql half was dispatched, the memory half held under the freeze. | "(the measurement that found the two drivers reading this argument differently split the fix: the sql half is this entry, and the memory half was held back by that freeze)" | | `objectstack-ai#6321` | `query.aggregate` / `agg.func` are undeclared aliases whose only writers are driver fixtures; order: re-spell the fixtures, delete the aliases, then narrow the signature. | "The removal ran in a fixed order — the fixtures re-spelt first, the two alias branches deleted second, the parameter narrowed to `DriverQuery` last — because the reverse order yields red nobody can explain." | | `objectstack-ai#6404` (PR) | Executed that order and narrowed `aggregate`'s query parameter to `DriverQuery`. | the same sentence | | `objectstack-ai#7929` | Maintainer, 2026-08-12, ruling B: `driver-sql`'s filter refusal stops echoing `$field` operands, for every caller; the full diagnostic goes to the server log. | "the same predicate-text disclosure shape the driver's field-reference filter refusals had already been made to stop echoing (the full diagnostic goes to the server log, never the response)"; "that disclosure shape closed on the last dialect" | | `objectstack-ai#8371` | Ruled option 2: a dotted filter key whose head is a relation, a formula or a scalar is refused at both doors; a structured head stays unjudged. | "the axis owned by the dotted-filter verdict, which refuses a dotted key whose head is a relation, a formula or a plain column at the protocol and engine doors" | | `objectstack-ai#8592` | Measured on live MySQL: knex compiles the named conflict target away. | stated by the entry ("knex drops the named keys before the statement leaves the process"); the trailing id is dropped | | `objectstack-ai#8621` | Option A: a pre-flight refusal when no unique index backs the caller-named conflict target. | "Two earlier pre-flight refusals closed the half where no unique index backed a caller-named target …" | | `objectstack-ai#8622` | `id` becomes insert-only on the merge path: a merge on a non-primary conflict key was measured rewriting the existing row's primary key. | "`id` is insert-only on the merge path (made so once a merge on a non-primary conflict key was measured rewriting the existing row's primary key)" | | `objectstack-ai#8755` | Ruling option A: a pre-flight refusal when a second unique key could absorb a backed, caller-named target. | "… and the half where a rival unique key could absorb a caller-named one" | | `objectstack-ai#8790` | Maintainer, 2026-08-15: refuse both halves with `INVALID_FILTER` / 400, naming the column. | "Ruled by the maintainer on 2026-08-15: refuse BOTH halves …"; "Recover-both was excluded by the ruling's own argument" | | `objectstack-ai#8807` | Maintainer, 2026-08-15: an upsert must never modify a row whose identity the caller did not supply and whose conflict key it did not name; enforcement delegated, blanket refusal excluded. | "Ruled by the maintainer on 2026-08-15, as a contract principle …" (the principle itself was already quoted verbatim) | | `objectstack-ai#8926` | Maintainer, 2026-08-16, option A: MySQL's spelling joins the one shared predicate (envelope and recoveries together). | "Addendum 2026-08-16." — the paragraph already states option A | | `objectstack-ai#9061` (PR) | Implemented that option A. | the same | | `objectstack-ai#11825` | Maintainer, 2026-08-25: retire the declarative `AdvancedPluginLifecycleConfig` container; the classes stay a host-driven library. | "… and as a host-driven library when the declarative lifecycle config container was retired" | | `objectstack-ai#11846` | **404** — see Acceptance notes. | "maintainer ruling 2026-08-27 (Option A: remove)"; "(as the removal ruling recorded)" | | `objectstack-ai#14478` | Ruling B, 2026-09-02: a no-baseline gate plus an ADR-0087 rename of every offender (`DriverOptions.timeout` among the seven named); ruling B again, 2026-09-05: the population is every authored and every runtime-emitted duration, minus exemptions declared on the schema. | "Maintainer ruling B on duration units (2026-09-02, its population widened on 2026-09-05 to every authored and every runtime-emitted duration, bar the exemptions a schema declares on the key itself)"; "the duration-unit rule (…)" | | `objectstack-ai#14519` | The two tenant timeouts published a describe naming no unit (the unit sat in the JSDoc only); folded into the rename. | "the unit-nowhere shape (no unit in the name or in the published describe, first measured on two tenant timeouts)" | | `objectstack-ai#15626` (PR) | Landed the gate and the seven founding renames, the tenant `idleTimeout` → `idleTimeoutSeconds` among them. | "The tenant half was already renamed, in the same change that landed the duration gate itself" | | `objectstack-ai#15678` | `kernel/`: the 14 remaining duration keys carry their unit in the key name. | "the kernel-directory duration renames" / "the kernel-directory round"; trailing ids dropped | | `objectstack-ai#15679` | `system/`: the 15 remaining duration keys carry their unit in the key name; `size` got an honest name. | "the system-directory duration round"; trailing ids dropped | | `objectstack-ai#15939` | The gate did not read JSDoc. Ruled 2026-09-07: refuse the JSDoc / describe divergence. Ruled A 2026-09-11: remediate the population per file first, land the widened gate last. | "Director-seat ruling A of 2026-09-11 on the JSDoc-channel finding … a duration key whose JSDoc names a unit its describe does not is refused, and the keys in that shape are remediated per file before that refusal lands" | | `objectstack-ai#16024` | Maintainer, 2026-09-06, per key: forward `timeout`, remove `localPath` and `wasm`. | "ruled per key by the maintainer on 2026-09-06, once all three of this package's unread config keys had been measured" | | `objectstack-ai#17635` (PR) | The widened gate: refuse a duration key whose JSDoc names a unit its describe does not; landed last. | "lands that widened gate last, into a tree already clean" | | `objectstack-ai#18669` | Ruling A, 2026-09-17: rename `FileValue.duration` and `estimatedMigrationTime`, each with an ADR-0087 entry; no new closed type, no narrowing of stored data. | "Maintainer ruling A of 2026-09-17 on the last two duration keys no closed duration type could express …" | | `cloud#1651` | **Not readable from this session** — see Acceptance notes. | "cloud — a census closed 2026-08-26: OS_PREVIEW_MODE there is a routing-only switch …" | No call-shaped token moves: a `name(` census over `registry.ts` is identical before and after (297 distinct tokens), so textual call-spelling ratchets read the same. ## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`, widened `COVERED_PREFIXES` is now `engine-`, `ui-`, `plugin-`, `driver-`, `kernel-`, `system-`. The pin still spawns the real CLI (`os migrate meta --from 16 --to 18`) once, locates each covered block **verbatim** in stdout, and asserts the printed block — `surface` included — carries no `#` plus 4 or 5 digits. Anti-vacuity: - the `REWRITTEN` floor rises from 29 to **55** ids: the 26 entries of this stage (7 `driver-`, 9 `kernel-`, 10 `system-`) are added, and every covered prefix must still select at least one entry; - presence in stdout is asserted before cleanliness (the `driver-sql-unresolvable-where-column-refused` reason carries two blank-line paragraph breaks, and its block is found verbatim); - the detector is exercised on both sides first (lit on 4 and 5 digits, dark on 3, 6 and `ADR-0112`). The file keeps its stage-1 name; the header lists the six covered families. ## Ablation — the widened pin can fail on a `driver-` block From committed state, HEAD `1c0dc7ad54`, with `scripts/ablation-replace.mjs` in wrap mode (it owns the restore trap) and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is built from the generated `registry.ts`, so that is the file mutated. - **Mutation.** In `registry.ts`, the reason of `driver-sql-upsert-cross-row-identity-merge-refused`: anchor `pre-flight refusals closed the half` → `pre-flight refusals (objectstack-ai#8621) closed the half`. The tool read anchor 1 → 0 and replacement 0 → 1, blob `b41e1d44` → `8f8d226e`. - **Mutate leg** (one lock turn: build, preflight, pin). Spec build exit 0. Preflight: marker present in 4 built files. Pin: **red**, `1 failed | 2 passed` — `driver-sql-upsert-cross-row-identity-merge-refused: the printed guidance cites a tracker id: expected 'objectstack-ai#8621' to be undefined`. - **Restore.** Tool-proven: blob `b41e1d44` == HEAD, `git diff HEAD` empty. - **Restore leg.** One lock turn, taken on the second try (the first waited out its 540 s budget, exit 99, NOT MEASURED, the tree already restored). Spec build exit 0. The `--absent` preflight found the marker in none of 222 built files, with the working tree clean against HEAD. Pin: **green**, `3 passed`. ## Verification Final head **`1c0dc7ad54`** for every line below; each heavy run went through `scripts/pm/os-verify-lock.sh` (one turn, `VERDICT command-exit 0`, per-step exits recorded separately). - **Build:** `pnpm exec turbo run build --concurrency=2 --filter='@objectstack/cli^...'` gives `Tasks: 55 successful, 55 total`. - **Pin and its neighbour:** `pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`, `Tests 10 passed | 1 skipped` (the skip is the default-range file's own pre-existing `skipIf`). - **Spec tests that read these entries or the registry:** `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations src/kernel/preview-mode-retirement.test.ts scripts/build-schemas-check-mode.test.ts` plus the 19 other spec test files that read `MIGRATIONS_BY_MAJOR`, the registry or an entry file: `Test Files 24 passed`, `Tests 696 passed`. - **CLI unit:** `test/vitest-tiers-partition.test.ts` and `src/utils/spec-release-changes.test.ts`: `Test Files 2 passed`, `Tests 28 passed`. - **The call-spelling census that reads `registry.ts`:** `pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.ts` gives 15 passed. - **Typecheck:** `pnpm --filter @objectstack/spec typecheck` exits 0 (test layer: 53 files / 255 errors held in its ledger); `pnpm --filter @objectstack/cli typecheck` exits 0 (test layer: 3 files / 28 errors held, unchanged). - **Gate families:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derives **89** families at `1c0dc7ad54` (after `git fetch origin main`). `--ran` over the recorded exit codes reads **89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN**, all exit 0. They include `check:doc-authoring` ("16466 customer-facing string(s) across 1135 spec sources clean"), `check:issue-citations`, `check:migration-registry` ("registry.ts is current (300 semantic, 219 retired-key, 199 retired-def)"), `check:spec-changes`, `check:upgrade-guide`, `check:generated` ("All 15 generated artifacts are up to date"), `check:duration-unit-keys`, `check:nul-bytes`, `check:adr-0087-registration` and `check:changeset-no-major`. - `check:dual-build-cjs-loads` refused first with `PREREQUISITE NOT MET` (exit 3: twelve packages outside the CLI closure had no `dist/`). Those `dist/` directories were written later in the same pass (04:48–04:49Z, inside the `check:type-check-debt` run, whose re-measure builds them); re-run at the same head it exits 0 (104 entries / 66 packages / 659 CJS files). The reconciled list takes that latest run. - **Lint (a proven narrowing, not the repo-wide run, which is CI's):** `eslint --no-inline-config --format json` over the 28 changed `.ts` files reports 28 files, 0 errors, 0 warnings. - The population is read from `eslint.config.mjs`: `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 28 are in it (no file-ignored warning). - Invariance: the config enables no type-aware linting (no `parserOptions.project`, no typed rules), so a text edit cannot move the verdict on a file it does not touch. - **Mergeability:** see Acceptance notes (driver-free `merge-tree` against `862b6ce869` exits 0). ## Acceptance notes - **Two dead ids, rewritten from the code on `main`.** `objectstack-ai#6075` and `objectstack-ai#11846` answer 404 on both the issues and the pulls endpoint, re-probed with a 200 control (`objectstack-ai#14478`). - `objectstack-ai#6075` (`distinct`'s "never reached it" sentence): `packages/drivers/driver-sql/CHANGELOG.md` (commit `d367f03`) and `sql-driver-query-signature.test.ts` record what it did — the five drivers' implementations followed `IDataDriver`'s `DriverQuery` narrowing. The sentence now says exactly that. - `objectstack-ai#11846` (preview mode): `packages/spec/CHANGELOG.md` (commit `0c2334f`), `packages/spec/src/kernel/context.zod.ts` and `preview-mode-retirement.test.ts` record the 2026-08-27 ruling (Option A: remove), the three-repo zero-consumer measurement and the re-declare-fresh condition. Dropped because `main` does not state them: "decision-inbox batch 2" and "all four decision facets pointed the same way". "The objectstack-ai#11846 card records the measurement" (objectui leg) now reads "zero consumers, measured when the removal was ruled", which is what the changelog and the test header say. - **One cross-repo id this session cannot read.** `cloud#1651` answers 403 here: `objectstack-ai/cloud` is not attached to this session (`add_repo` refused: no access). It is neither confirmed nor refuted, so its sentence was rewritten from what `main` records about it — `packages/spec/CHANGELOG.md` (`0c2334f`: closed 2026-08-26 with positive controls, `RuntimeMode` zero hits, `ArtifactKernelFactory` 20+ hits and never touching `previewMode`) and `context.zod.ts` (`OS_PREVIEW_MODE` there is routing-only). The cloud-side detail `main` does not state — `previewMode` "only as a local variable" whose effect is adding wildcards to "CSRF" trusted origins — is dropped; the parenthesis that replaces it describes this repository's own `serve.ts` (the one reader of `OS_PREVIEW_MODE` here only widens better-auth's trusted origins to preview-domain wildcards), which is measured on `main`. - **Decision-batch numbers went too (invisible to the regex).** Nineteen sites cited a decision batch as `#` plus two or three digits (`objectstack-ai#43` ×13, `objectstack-ai#115` ×4, `objectstack-ai#151` ×1, `objectstack-ai#158` ×1). They are numbers an author is shown and cannot follow, so each is dropped. One consequence worth naming: 13 entries said `Maintainer ruling B on objectstack-ai#14478 (2026-09-02, decision batch objectstack-ai#43)`, which fused two rulings on the same card — B of 2026-09-02 (the gate and the no-baseline rename) and B of 2026-09-05, decided in that batch (the population: every authored and every runtime-emitted duration, minus schema-declared exemptions). The sentence now names both dates. The `objectstack-ai#158` sentence (the agreement shape ruled an offence on 2026-09-18) is corroborated by `.changeset/18075-agreement-shape-is-an-offence.md` on `main`. - **"issue NNNN" / "PR NNNN" spellings, checked by hand.** No bare-number spelling exists in these 26 entries' author-shown text; the two `PR` citations (`PR objectstack-ai#6404`, `PR objectstack-ai#9061`) were `#`-spelled, so the instrument saw them and they are gone. The only `#` left in these 26 files is on `//` comment lines (sibling card's surface), including a `Prime Directive objectstack-ai#13` reference. - **A citation whose page says something narrower than the text.** `kernel-health-check-and-hot-reload-durations-unit-in-key` called `shutdownTimeout`'s shape "the objectstack-ai#14519 unit-nowhere shape". `objectstack-ai#14519`'s keys carried their unit in the JSDoc; "unit nowhere" is the gate's name for it (`check-duration-unit-keys.ts` header: "no unit ANYWHERE (the objectstack-ai#14519 shape)"), because the gate did not read JSDoc. The sentence now says what the shape is — no unit in the name or in the published describe — and that it was first measured on two tenant timeouts. - **A comment that my text edit makes slightly stale.** `18.system-metrics-window-durations-unit-in-key.ts` carries a `//` comment saying its acceptanceCriteria sentence "is objectstack-ai#15679's, left word for word". That sentence now says "that JSDoc-channel gap is filed as a finding of its own" where it said "is objectstack-ai#15939": same content, no number. The comment is the sibling card's surface (comment lines), so it is untouched here. - **Three "card" references re-anchored.** Removing an id left "the same card" in the Turso entry pointing at nothing; it now says "the same measurement". The kernel entries' "renamed by this same card" carry no number and were not otherwise rewritten, so they are left. - **Cross-PR check: no open PR adds or edits a `driver-`, `kernel-` or `system-` semantic entry.** Read at 2026-09-28T04:0xZ: the 18 open PRs' file lists (`GET /pulls/{n}/files`) carry 0 files matching `migrations/entries/semantic/NN.(driver|kernel|system)-*`. The Version Packages PR (`objectstack-ai#17076`) lists more than 1,000 files; the 1,100 rows read carry no entry file, and it is the bot-generated release PR. Nothing in flight will be held by the widened pin on arrival. - **`main` moved 4 commits past the base** (`862b6ce869`: `objectstack-ai#20364`, `objectstack-ai#20341`, `objectstack-ai#20366`, `objectstack-ai#20352`); none touches `packages/spec/src/migrations/` or the pin. A driver-free bare-clone `merge-tree --write-tree` of this head against `862b6ce869` exits 0 with no conflicted path, so `registry.ts` needs no merge, and `main` was not merged in. - **Generated projections** (`spec-changes.json`, `docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1 and 2; their `--check` legs are green. Only the three `driver-` entries registered at protocol 17 appear in them, which is why those diffs are small. - **No other test pins these entries' text.** A `git grep` of test files for the 26 entry ids finds one (`preview-mode-retirement.test.ts`), which names the entry in a comment and reads no prose; a grep of tests for the 37 cited numbers finds only comment lines. So no test needed re-pinning this stage (stage 2's `migrations.test.ts` case has no counterpart here). ## Line budget Entry files: **352 changed lines** (+228 / −124) across 26 files, against the stage-1 ≈400 budget. The whole diff is **776 lines** (+516 / −260) in 31 files. Of the rest, `registry.ts` is 352, the two projections are 18 (`spec-changes.json` 12, the upgrade guide 6), the widened pin is 33 and the changeset is 21. --- _Generated by [Claude Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #20327
Clause-②: no
Four
packages/clientsuites 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 requiredTest Core (5/6)shard of PR #20325, whose diff does not reach the package. This PR ports PR #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.tsalso loses its seven explicit60_000per-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.tsfiles.Premise check (on
origin/mainatc74de10a9)Holds. In
auth-get-session-envelope.test.ts, the first case callsawait signedIn()inside itsit(:262-:264), and every case builds a freshSqliteWasmDriverengine, a realAuthManager, 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:Test Core (5/6)runauth-get-session-envelopeorganization-invitation-resend-team-placementorganization-invite-role-defaultCensus: every
packages/client/src/*.test.ts(50 files)The criterion (dispatch, Zone 2): a suite that boots an engine (a
SqliteWasmDriverengine, a realAuthManager, or a real sign-up) inside its first clockedit, or in abeforeAll/beforeEach.auth-get-session-envelopesignedIn()/anonymous(): fresh engine + realAuthManager; default 5000 ms; the first case paid the cold startauth-rotated-session-tokensignedIn(); an explicit60_000on all 7 cases, which widened the first case's window instead of moving the cost out60_000s removedorganization-invitation-resend-team-placementarrange()(fresh engine, realAuthManager, real sign-up, an org and a team); default 5000 ms;①paid the cold startorganization-invite-role-defaultarrange(); default 5000 ms;①paid the cold startauth-login-register-envelope80c29a14); not editedclient.hono,client.batch-transaction,client.data-prefix,client.environment-scoping,client.metadata-prefixLiteKernel+ ObjectQL +SqliteWasmDriverREST/Hono server booted ONCE per file (per describe in the two*-prefixfiles) as a shared fixture in abeforeAllwith an explicit30_000hookTimeout; noAuthManager, no sign-upit, 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-carriersSqliteWasmDriverengine + the real protocol +RestServerbooted insideit, but only from the 14th case on (Part 2); its first 13 cases are mock-fetch; an explicit60_000on all 7 engine cases; noAuthManager, no sign-upitis 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. Its60_000s widen windows that measured under 1.3 s. Noted, not filed.i18n-wire-dialectLiteKernel+HonoServerPluginin abeforeAll, no driver, no authfetch, source-text reads, or type-level assertions;client.test.ts's sign-up cases are fetch-level;oauth-applications-*andorganization-get-active-member-addressingdrive doublesCount: 4 included, 1 already fixed, 45 excluded.
What changed
Per file, the same three moves as PR #20272:
auth-get-session-envelope:await signedIn(); await closeEngines();auth-rotated-session-token:await signedIn(); await closeEngines();process.env.OS_TENANCY_POSTURE = 'isolated';thenawait arrange().finally(restorePosture);.arrange()needs the posture the file'sbeforeAllsets for its cases, and the module scope runs before any hook, so the warm-up holds the posture itself and.finallyputs back what it found, even if it throws.closeEnginesis the oldafterEachloop byte for byte (inauth-rotated-session-tokentheafterEachkeeps itsvi.restoreAllMocks()and callscloseEngines);restorePostureis the oldafterAllbody byte for byte.⑥, or④inauth-rotated-session-token), read off the file's own text:auth-get-session-envelope, nobefore*hook at all (as in PR test(client): pay the auth-envelope suite's cold start at module scope, outside every clocked window #20272's⑦);beforeEach, the posturebeforeAll), exactly one hook, and no hook body calls the arrangement. A hook indented inside adescribeis read on to that block's column-0 close, so it cannot hide one;auth-rotated-session-token, also no}, N);per-case timeout, so the widened posture cannot come back.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
AuthManagerand 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 inwithTimeout(...)(dist/chunk-artifact.js:672,:724,:1787) and awaitsrunner.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:
c74de10a949bd6fdfaauth-get-session-envelopeauth-rotated-session-tokenorganization-invitation-resend-team-placementorganization-invite-role-defaultUnder load, PR #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 5000mson 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 its60_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.
auth-get-session-envelopeauth-rotated-session-tokenorganization-invitation-resend-team-placementorganization-invite-role-defaultauth-get-session-envelopeauth-rotated-session-tokenorganization-invitation-resend-team-placementorganization-invite-role-defaultEvery 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: the60_000s are gone, and its first case is heavy by its own workTriage note 3 and the dispatch both rule out a raised timeout, so all seven
60_000s 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).①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.①auth-get-session-envelope's same-work cases ran 2.9-5.9x their idle time)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 fournode 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 anchorawait signedIn();+ newline +await closeEngines();+ 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 againstHEADand an emptygit diff HEAD(24 of 24), and a second check after each leg read4/4 blob == HEAD blob, git diff HEAD = 0 bytes(12 of 12 legs). The files run from source, so nodist/preflight applies.Verification
All at
220735eb3: the fix49bd6fdfa, two commits that only edit header comments, and a merge oforigin/mainatd498113b5. 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 at5a9a01d00and then run again in full at220735eb3, after the last commit corrected a count in a comment. Both runs gave the same verdicts.pnpm install --frozen-lockfile, then the dependency closurepnpm 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 --listFilesOnlycompiles 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.check:dual-build-cjs-loadsandcheck:type-check-debt. Both exited 3 (PREREQUISITE NOT MET): they need every package'sdist/, the whole./packages/*build. A declared narrowing; CI runs both. This diff is four test files, which no build emits (thedist/check is under Changeset), and@objectstack/client's test-layer debt ledger is empty and re-measured just above at 0 errors.check:skill-examplesexited 3 until its prerequisite was met: firstpackages/client-react/distwas absent, thenpackages/client/distwas older than an editedsrc/file. Afterpnpm turbo run build --filter=@objectstack/client-reactandpnpm --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.check:nul-bytesalso exit 0).Changeset
skip-changeset. The diff is four*.test.tsfiles.@objectstack/client'sfiles[]isdist,README.md,CHANGELOG.md. Afterpnpm --filter @objectstack/client buildat220735eb3, strings unique to these test files (closeEngines,restorePosture,cold start stays outside,OS_TENANCY_POSTURE) have 0 hits underpackages/client/dist/, while the positive controlObjectStackClienthits 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 (changePasswordwithrevokeOtherSessions, andtwoFactor.disable) reached 4.7-4.8 s at 24 loops.meta-delete-item-carriers.test.tscarries an explicit60_000on 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.client.*suites boot a shared server in abeforeAllwith an explicit30_000hookTimeout. 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.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.Generated by Claude Code