Skip to content

Commit 6410029

Browse files
test(runtime): reuse the census transports, pay first boots at module scope, and declare two budgets (#23181 slice 19) (#28081)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DWbd6ULfZV9gLaW3DrBhtG4)_ Part of #23181 (slice 19, `@objectstack/runtime`). The card is a series and stays open. Clause-②: no Metadata-protocol: none Test-only diff: `skip-changeset`. Three files under `packages/runtime/src/`, all `*.test.ts`; `@objectstack/runtime`'s `files[]` ships `dist` only. ## 维护者速读 - 改了什么:runtime 包 3 个测试文件。`meta-list-projection-parity` 的普查用例改为每个调用者复用一对已启动的 dispatcher/RestServer;`mcp-package-object-discovery` 在模块顶层先付两种形态的首次启动;它和 `stored-metadata-reader-contexts.pin` 在第 2 行加 `// test-budget:` 声明。只动测试文件。 - 为什么改:卡 #23181 要求单个测试文件不超过 10 s;这三个文件在最近一次计时刷新里分别是 13.60、14.93、16.31 s。 - 风险与回滚:不改断言、超时与分层;复用前后 6,136 次读取逐字节一致(一次性证明,见下);整包 8,834 个用例名前后一致。回滚即 revert 本 PR。 - 先看:`packages/runtime/src/domains/meta-list-projection-parity.test.ts` 的 `censusPair`(复用的依据写在注释里);`mcp-package-object-discovery.test.ts` 第 2 行与模块顶层预热那一行。 - 两分钟验证:`pnpm --filter @objectstack/runtime exec vitest run --project repo --maxWorkers=2 src/domains/meta-list-projection-parity.test.ts`,预期 879 个用例全绿,文件耗时约为 main 上的一半。 - 需要你做的:无,席位落地。 ## What changes The newest per-file reading is the weekly shard-timings refresh of 2026-10-26 (job 121418010419 in run 40275747937, refresh PR #25960's window, hourly runs 40200279306 to 40272142430); no refresh has run since. It lists three `@objectstack/runtime` files over 10 s: `src/stored-metadata-reader-contexts.pin.test.ts` 16.31 s, `src/domains/mcp-package-object-discovery.test.ts` 14.93 s and `src/domains/meta-list-projection-parity.test.ts` (repo project) 13.60 s, all in run 40200279306. The card's two older runtime rows, `src/declared-capability-absence-warn-once.test.ts` and `src/stored-metadata-body-boundary.pin.test.ts`, are not in that list and read 0.1 s and 3.7 s here, so this slice leaves them alone. **Fixture reuse: `meta-list-projection-parity`.** Its four census blocks (row A, the item read, the layered route, the book tree) booted a fresh dispatcher and a fresh `RestServer` (with its `registerRoutes()`) for every caller of every cell. The file booted a dispatcher 3,179 times, nearly all in these blocks, and a `RestServer` as often; in one instrumented run the boots took 1.53 s (`RestServer`) and 0.42 s (dispatcher) of a 3.73 s file. They now read through one pair per caller (`censusPair`), booted on its first cell. Neither transport keeps an answer between two GET reads (`RestServer`'s `execCtxMemo` is a `WeakMap` keyed on the request object; the dispatcher's `membershipCache` is keyed on an environment no read here names), and every protocol-double read clones its fixture. The blocks that assert on a double's calls, or boot with options, still boot their own. **Module-scope first boots: `mcp-package-object-discovery`.** Each shape's first kernel boot was its slow one (276 and 163 ms against 45 to 131 ms for the other 22, isolated), paid inside a `beforeAll`. One module-scope line boots and shuts down the first describe's own fixture in each shape, before any hook runs (AGENTS.md: clocked windows measure behaviour, never loading). **Budget headers (ruling record 6181973363, letter B).** Two files stay over 10 s on CI by the measured ratio here (CI read these three at about 3.5 to 5 times the local file time), and each boots the real stack per fixture: one line at line 2, `// test-budget: N — spawns the real producer once per single-cause fixture: REASON`, N = the slowest CI reading x 1.25, rounded up to the next 10 s. | file | change | slowest CI reading (s) | budget | |:--|:--|--:|--:| | `src/domains/meta-list-projection-parity.test.ts` | census reuse | 13.60 | none (expected under 10 s) | | `src/domains/mcp-package-object-discovery.test.ts` | module-scope first boots + header | 14.93 | 20 | | `src/stored-metadata-reader-contexts.pin.test.ts` | header only (its first loads were already paid at module scope) | 16.31 | 30 | The class follows the precedent `packages/verify/src/held-session-vendor-admin-doors.pin.test.ts` set for one in-process boot followed by per-case door runs. ## Tests Per-file readings, vitest's own file line (what the refresh lane reads) and process wall, 3 isolated runs each, `--maxWorkers=2`, on a shared 4 vCPU container, before (`bc496bf7`, the three files checked out from the base) and after (`1a5d0aea`): | file | before r1/r2/r3 (s) | after r1/r2/r3 (s) | wall before / after (s) | |:--|:--|:--|:--| | meta-list-projection-parity | 3.58 / 3.10 / 2.96 | 1.50 / 1.37 / 1.40 | 21.1, 20.0, 19.0 / 17.2, 17.5, 17.3 | | mcp-package-object-discovery | 2.75 / 2.93 / 2.72 | 2.49 / 2.77 / 2.43 | 19.4, 19.5, 18.6 / 18.8, 19.0, 18.3 | | stored-metadata-reader-contexts.pin | 3.33 / 3.18 / 2.92 | 3.10 / 2.89 / 3.06 | 23.1, 23.4, 21.2 / 22.3, 21.8, 21.3 | The wall is mostly the package's import graph (transform about 11 s, import about 15 s per isolated run), which collection pays and no clock in the refresh lane reads. Whole package, both vitest projects, before and after: `local` `Test Files 483 passed (483)`, `Tests 7870 passed | 19 skipped (7889)`; `repo` `Test Files 3 passed (3)`, `Tests 948 passed (948)`, the same on both sides. Name census over the two JSON reports: 8,834 test names before and after, 0 lost, 0 new, 0 with a changed status. <details><summary>The reuse proof (one-time, not kept)</summary> A temporary wrapper made every census read go through both the reused pair and a freshly booted pair, compared the two answers as JSON (status, code, body, items, item and the four headers), and threw on any difference. Run on the after tree: `Tests 879 passed (879)`, 6,136 reads compared, 0 diverged. Reverted before the readings above; `git diff HEAD` was empty after. </details> <details><summary>Header parse</summary> Both lines parse through `readTestBudget` (`scripts/measure-test-shard-timings.mjs`): budgets 20 and 30 at line 2, class `spawns the real producer once per single-cause fixture`; `meta-list-projection-parity` declares none. </details> `pnpm --filter @objectstack/runtime typecheck`: VERDICT command-exit 0 (`check:test-typecheck: OK`, 188 errors / 68 pinned signatures held, unchanged). No ablation: no tested behaviour changed. ## Acceptance notes - The newest reading also lists `domain:server` packages that are not on the card's list: `@objectstack/plugin-audit` (`src/parent-record-read-leaf.integration.test.ts` 35.78 s, `src/comment-parent-pair.integration.test.ts` 28.07 s), `@objectstack/metadata` (`src/serializers/typescript-serializer-annotation.test.ts` 23.74 s), `@objectstack/plugin-pinyin-search` (`src/boot-reconcile.integration.test.ts` 17.47 s), `@objectstack/plugin-security` (16.02, 13.48 s), `@objectstack/plugin-auth` (13.74, 13.38 s). Noted for the card's list; out of this slice. - `mcp-package-object-discovery` may still read over 10 s on CI after its warm-up (local cut about 8 %), which is why it carries a header; `meta-list-projection-parity` carries none, and the next refresh is the reading that says whether it is under. - Branch `claude/project-thread-388dl6` is the card thread's platform-assigned branch; claim 6520622267 names it. ## Verification Local, on `1a5d0aea`: `node scripts/pm/dispatch-gates.mjs --commands` derived 49 families; each run with its exit captured by redirect before any pipe, plus `pnpm lint`. 48 exit 0 and `pnpm lint` exit 0; `pnpm check:dual-build-cjs-loads` exit 3 (packages outside the runtime closure have no `dist`), NOT MEASURED here and CI's. `--ran`: `49 derived famil(ies) accounted for — 48 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)`, exit 0. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cwb48o186kZzEGZ9a1)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cwb48o186kZzEGZ9a1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8cb8c7f commit 6410029

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

‎packages/runtime/src/domains/mcp-package-object-discovery.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — spawns the real producer once per single-cause fixture: twenty-four in-process kernel boots, one per describe (each shape × registration, package state and artifact fixture), each answering its block's bridge and door reads; slowest CI reading 14.93 s (run 40200279306, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up; read before this file paid each shape's first boot at module scope
23

34
/**
45
* [#25120] The `/mcp` bridge sees the objects a PACKAGE contributes: listed,
@@ -275,6 +276,13 @@ const REGISTRATIONS = [
275276
{ label: 'installed after the boot, as marketplace install registers it', when: 'install' },
276277
] as const;
277278

279+
// [#23181] Pay each shape's first boot at module scope, never in a hook: vitest
280+
// clocks a hook as it clocks a test, and each shape's first boot was the slow
281+
// one (276 and 163 ms against 45 to 131 ms for the rest, isolated). The throwaway kernel is
282+
// the first describe's own fixture, booted and shut down before any hook runs,
283+
// so it leaves nothing behind that a later boot does not already inherit.
284+
for (const { environmentId } of SHAPES) await shutdown(await bootKernel(environmentId, 'boot', packageOf('qa_pkg')));
285+
278286
for (const { shape, environmentId } of SHAPES) {
279287
for (const { label, when } of REGISTRATIONS) {
280288
describe(`[#25120] the /mcp bridge and a package's objects — ${shape}, ${label}`, () => {

‎packages/runtime/src/domains/meta-list-projection-parity.test.ts‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,24 @@ function bootRest(callerName: CallerName, opts: Boot = {}) {
484484
return { read, protocol };
485485
}
486486

487+
/**
488+
* [#23181] The census blocks (row A, the item read, the layered route, the book
489+
* tree) read every cell through ONE booted pair per caller, booted on its first
490+
* cell and reused after. They booted a fresh pair per caller per cell, about
491+
* 3,100 boots, and `bootRest`'s `registerRoutes()` was half the file's time.
492+
* Reuse changes no answer: neither transport keeps one between two GET reads
493+
* (`RestServer`'s per-request memo is keyed on the request object, and the
494+
* dispatcher's membership cache on an environment no read here names), and
495+
* every protocol-double read clones its fixture. The blocks that assert on a
496+
* double's calls, or boot with options, still boot their own.
497+
*/
498+
const censusPairs = new Map<CallerName, { dispatcher: ReturnType<typeof bootDispatcher>; rest: ReturnType<typeof bootRest> }>();
499+
function censusPair(who: CallerName) {
500+
let pair = censusPairs.get(who);
501+
if (!pair) censusPairs.set(who, (pair = { dispatcher: bootDispatcher(who), rest: bootRest(who) }));
502+
return pair;
503+
}
504+
487505
const text = (a: Answer): string => JSON.stringify(a.body ?? null);
488506
const names = (items: any[] | undefined): string[] => (items ?? []).map((i: any) => i?.name);
489507
const brief = (a: Answer) => ({
@@ -636,8 +654,8 @@ describe('[#20320] row A: the dispatcher lists what RestServer lists — every t
636654
it(`GET /meta/${type} ${probe.label}`, async () => {
637655
const mismatches: unknown[] = [];
638656
for (const who of LIST_CALLERS) {
639-
const dispatcher = await bootDispatcher(who).read(`/meta/${type}`, probe.query, probe.headers);
640-
const rest = await bootRest(who).read(`/meta/${type}`, probe.query, probe.headers);
657+
const dispatcher = await censusPair(who).dispatcher.read(`/meta/${type}`, probe.query, probe.headers);
658+
const rest = await censusPair(who).rest.read(`/meta/${type}`, probe.query, probe.headers);
641659
const same = dispatcher.status === rest.status
642660
&& dispatcher.code === rest.code
643661
&& JSON.stringify(dispatcher.items) === JSON.stringify(rest.items)
@@ -996,8 +1014,8 @@ describe('[#20408] the item read: the dispatcher answers what RestServer answers
9961014
it(`GET ${path} ${probe.label}`, async () => {
9971015
const mismatches: unknown[] = [];
9981016
for (const who of ITEM_CALLERS) {
999-
const dispatcher = await bootDispatcher(who).read(path, probe.query, probe.headers);
1000-
const rest = await bootRest(who).read(path, probe.query, probe.headers);
1017+
const dispatcher = await censusPair(who).dispatcher.read(path, probe.query, probe.headers);
1018+
const rest = await censusPair(who).rest.read(path, probe.query, probe.headers);
10011019
if (!sameAnswer(dispatcher, rest)) mismatches.push(mismatchOf(who, dispatcher, rest));
10021020
}
10031021
expect(mismatches).toEqual([]);
@@ -1059,8 +1077,8 @@ describe('[#20478] GET /meta/:type/:name/layers: the dispatcher serves the route
10591077
it(`GET ${path}/layers ${probe.label}`, async () => {
10601078
const mismatches: unknown[] = [];
10611079
for (const who of ITEM_CALLERS) {
1062-
const dispatcher = await bootDispatcher(who).read(`${path}/layers`, probe.query, probe.headers);
1063-
const rest = await bootRest(who).read(`${path}/layers`, probe.query, probe.headers);
1080+
const dispatcher = await censusPair(who).dispatcher.read(`${path}/layers`, probe.query, probe.headers);
1081+
const rest = await censusPair(who).rest.read(`${path}/layers`, probe.query, probe.headers);
10641082
if (!sameAnswer(dispatcher, rest)) mismatches.push(mismatchOf(who, dispatcher, rest));
10651083
}
10661084
expect(mismatches).toEqual([]);
@@ -1226,8 +1244,8 @@ describe('[#20408] GET /meta/book/:name/tree: the dispatcher serves the route Re
12261244
it(`GET /meta/book/${book}/tree ${probe.label}`, async () => {
12271245
const mismatches: unknown[] = [];
12281246
for (const who of TREE_CALLERS) {
1229-
const dispatcher = await bootDispatcher(who).read(`/meta/book/${book}/tree`, probe.query, probe.headers);
1230-
const rest = await bootRest(who).read(`/meta/book/${book}/tree`, probe.query, probe.headers);
1247+
const dispatcher = await censusPair(who).dispatcher.read(`/meta/book/${book}/tree`, probe.query, probe.headers);
1248+
const rest = await censusPair(who).rest.read(`/meta/book/${book}/tree`, probe.query, probe.headers);
12311249
if (!sameAnswer(dispatcher, rest)) mismatches.push(mismatchOf(who, dispatcher, rest));
12321250
}
12331251
expect(mismatches).toEqual([]);

‎packages/runtime/src/stored-metadata-reader-contexts.pin.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 30 — spawns the real producer once per single-cause fixture: one in-process boot of the real stack, then a sandboxed body or host handler run through the real doors per reader context, verb and caller; slowest CI reading 16.31 s (run 40200279306, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up; the first loads already paid at module scope
23

34
/**
45
* The in-process reader contexts over the stored-metadata-body family

0 commit comments

Comments
 (0)