Skip to content

Commit 609d07a

Browse files
test(metadata-protocol): lock-one-resolution runs as four shards under the 10 s budget (#23182 row 1, step B) (#23790)
Part of #23182 (row 1) Clause-②: no This PR is step B of row 1 of #23182: `@objectstack/metadata-protocol`, `src/protocol.lock-one-resolution.test.ts`. Step A (#23755, `4fe8b3def0`) took each read once and left the single file at about 8 s alone and 21 to 28 s in a CI-shaped package run. This step splits the 16 320 generated rows over four shard files, as the seat answered in comment 6204945326 (A+B, K=4). It changes test files and one ledger row, and no production file. #23182 stays open: this PR does not close row 1 by itself (its pin is the next refresh's list, owed to the refresh), and rows 5 and 6 are separate. ## What the split is | file | holds | tests | |:--|:--|--:| | `protocol.lock-one-resolution.kit.test.ts` (new) | the shared helper: table, oracle, engine and registry doubles, door, reads, `readScenario`, `agree`, the partition, and the shard-set pin | 2 | | `protocol.lock-one-resolution.shard-1.test.ts` to `shard-4.test.ts` (new) | one call each, `registerRowShard()`; the shard a file is comes from its own name | 4080 each | | `protocol.lock-one-resolution.test.ts` (kept) | the 40 named tests (completeness, subset, lit controls, pins 2 to 10), unchanged, importing the helper | 40 | - **Titles are unchanged.** The 16 360 tests of the single file all run under the same full titles (describe, then test): compared as sets, base 16 360 unique titles, branch 16 362 unique, 0 only in base, and the only two in the branch alone are the new pin's. No shard prefix was needed, so there is no old-to-new mapping. - **The partition.** The rows that share a read request differ only in `requestScope` and `operation`, which no read takes: one read group is a row's title with those two axes blanked (`readGroupOf`). Groups are numbered in the table's own order, and group n is run by shard (n mod 4) + 1. A group is never split, so step A's four-to-one read memo holds inside every shard. It is a pure function of the table, so a row, slice or axis added later lands in a shard by the same rule. Each shard holds 1 020 groups, 4 080 rows. - **The pin (`kit`, always run: it is a test file).** Two tests. The first: the shards' rows are the table's, once each (counted with repeats, as a set, and by identity against the table's titles), no shard is empty, and every read group is whole, in one shard, and of exactly scope times verb rows. The second: the shard files on disk are exactly the four expected names, and each one calls `registerRowShard()` on a line of its own. A shard file whose name is not one of the four fails at collection (the count is read from the file name, so a rename cannot leave rows unrun). - **Why the helper is a `.test.ts` file.** Three gates read engine doubles and each walks `*.test.ts` files only (`check:engine-double-contract`, `check:where-matcher`, `check:objectql-double-limit`). A double in a `*.testkit.ts` file would have left all three, and `check:engine-double-contract` would have called the leaving row the #9680 defect. Sharing a double through a test file is already this repository's practice (`impersonation-bearer-rotation.test` is imported by fifteen plugin-auth tests). What an import must not do is run the helper's own tests in the importer: the helper registers its two tests only when it is the file vitest was started on (`expect.getState().testPath`). - **The ledger row moves with the double.** `scripts/engine-double-contract.pinned.json` changes by one row, the `findOne` row, from `protocol.lock-one-resolution.test.ts` to the kit. `check:engine-double-contract --write` printed "922 (file, verb) row(s), 1 added or grown, 1 lost"; the one lost is the old path, and the double it names is the one the new row names, so coverage is unchanged. `check:where-matcher` and `check:objectql-double-limit` read the moved double and report none new. ## Wall time Vitest module line in ms, a shared 4-core box under `os-verify-lock`, default reporter, `--maxWorkers=2`, each file alone. BASE is the single file on `origin/main` `6362c66ca8` (a copy run beside the branch's files); the branch readings are at `c19398bd2f`, and the final commit `7686410907` changes comments only. | | BASE | AFTER | |:--|:--|:--| | single file, 3 runs | 8289 / 8016 / 8397 | n/a | | shard 1 | n/a | 2609 / 2532 / 2582 | | shard 2 | n/a | 2622 / 2404 / 2697 | | shard 3 | n/a | 2505 / 2374 / 2581 | | shard 4 | n/a | 2528 / 2497 / 2672 | | the 40 named tests, the pin | n/a | 238, 101 | | all six files together, `--maxWorkers=4` (each shard beside the others) | n/a | 4648 to 4867 per shard | | whole package, `--maxWorkers=8` (2x oversubscribed), the slowest file | 21305 | 3739 to 3966 per shard (named tests 714, pin 253) | - **No shard is near 4 s alone**, so K stays at 4. K=6 would be about 1.7 s a shard by proportion; that is not measured. K is one constant (`ROW_SHARDS`) plus the shard files, and the pin stays red until the two agree. - **The cost of five more files.** The whole-package run took 198 s at BASE and 211 s on the branch, with the vitest import phase at 1395 s and 1483 s of worker time; 241 files and 20 579 tests against 236 and 20 577. That is one reading each, on a shared box, and not a figure to quote closer than "a few percent". - **CI after the split: NOT MEASURED here** (job logs redirect to a blob host the egress proxy refuses). The card's 113 s CI reading was 5.4 times the local reading of the single file at that time; at that ratio one shard would read about 14 s, and at this box's 8-worker reading (1.5 times alone) about 4 s. The next refresh PR's list is the reading to trust. - **Test count:** 16 362 pass in the six files, 16 360 plus the pin's two. ## Ablation: the rows still go red on the same titles, and the pin goes red on a lost row Each leg mutated production source through `scripts/ablation-replace.mjs` (anchor 1 to 0, a unique replacement, blob changed on disk), ran a copy of the single file on `origin/main` and the six branch files in one vitest run, and restored the path to its HEAD blob with `git diff HEAD` empty. The subject is imported by relative path from `src`, so no dist is in play. Red sets are compared by full title. | leg | mutation | red, single file | red, shard set | sets | |:--|:--|--:|--:|:--| | A1 | the item lock joins only the first shipping package's answer (`item-lock.ts`) | 2384 | 2384 | equal | | A2 | the write doors ignore the stored rows (`item-lock.ts`) | 1823 | 1823 | equal | | A3 | the served body no longer carries the resolved lock family (`item-lock.ts`) | 7744 | 7744 | equal | | A4 | an organization-scoped write is not refused first (`protocol.ts`, temporary, restored) | 8160 | 8160 | equal | The pin stays green under all four, since none of them touches the partition. These counts are the ones step A measured. The pin's own legs (kit tests plus the four shards; the first three by deleting, renaming or editing a tracked file and restoring it with `git checkout HEAD`): | leg | change | what goes red | |:--|:--|:--| | P1 | `shard-4` deleted | the pin's file test (3 files on disk, 4 expected); the three shards left stay green | | P2 | `shard-2` renamed to `shard-9` | the pin's file test, and `shard-9` itself at collection (the name is not one of the four) | | P3 | `shard-3` loses its `registerRowShard()` call | the pin's file test (names the file), and `shard-3` ("No test suite found") | | M1 | the partition drops 169 rows (index mod 97) | the pin's row test: "expected [ ...(16151) ] to have a length of 16320 but got 16151"; every row that did run is green | | M2 | the partition numbers groups by full row, splitting the read groups | the pin's row test: 4080 read groups split over shards | | M3 | the partition uses three shards of four | the pin's row test ("shard 4 runs no row") and `shard-4` at collection | M1 is the case the pin exists for: a lost quarter-percent of the table, with every remaining test green. ## Verification at `7686410907` - **Typecheck:** `pnpm --filter @objectstack/metadata-protocol typecheck` exits 0; `tsc --listFiles` names all six files. - **Lint (narrowed to the six files):** `eslint --no-inline-config --format json`: 6 files, 0 errors, 0 warnings. `--print-config` returns a config for the kit, with parserOptions `ecmaVersion` and `sourceType` only: type-aware linting is off, so this diff cannot move any other file's result. - **Gates:** `dispatch-gates` over the 7 changed paths derives 59 commands. All 122 distinct commands (the 59, plus the 53 artifact-roster and 11 wide-population families it names, one shared) ran at this head after a full `turbo run build` (74 tasks): 118 exit 0 and 4 exit non-zero. The four are PR-context guards that judged nothing and say so (`check-closing-target-claim`, `check-single-claim-paths`, `check-partof-closing-keyword`, `check-governed-queue-guard`): NOT MEASURED, not red. `dispatch-gates --ran` with exit codes: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN. - **Stale tree:** the derivation reports this tree 5 commits behind `origin/main`, with two files it derives from changed upstream (`scripts/check-route-envelope.mjs` and an adr-anchors file for `packages/rest/src/discovery-routes.ts`: the `check:route-envelope` family, whose population is non-test sources in `packages/rest`, which this diff does not touch). I did not merge `main`, as the card says. - **Bytes:** a control-byte scan of the six files and the ledger finds none; `check:nul-bytes` exits 0. ## No changeset This diff changes only test files under `packages/metadata-protocol/src` and one repository ledger under `scripts/`; the package's `files` (`dist`, `README.md`, `CHANGELOG.md`) ships none of it. `skip-changeset` is the label, as on step A. ## Acceptance notes - Step A's note still holds inside each shard: up to four rows' doors run on one shared protocol instance, in sequence, after its reads. The split adds no new coupling. - Each shard's partition depends on the table's order (group number in table order); the pin is computed from the same function, so a reorder moves rows between shards without losing any. - Each extra test file imports `protocol.ts` once (see the import-phase reading above); that cost is carried by this split and is the price the seat's answer named. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b485122 commit 609d07a

7 files changed

Lines changed: 867 additions & 633 deletions

‎packages/metadata-protocol/src/protocol.lock-one-resolution.kit.test.ts‎

Lines changed: 769 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#23182 row 1, step B] Shard 1 of 4 of the lock family's generated table: the
5+
* rows of read groups numbered 0 modulo 4, from the helper. This file is
6+
* its call and nothing else; the shard it is comes from its own name, and
7+
* `protocol.lock-one-resolution.kit.test.ts` pins that the four files exist.
8+
*/
9+
import { registerRowShard } from './protocol.lock-one-resolution.kit.test.js';
10+
11+
registerRowShard();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#23182 row 1, step B] Shard 2 of 4 of the lock family's generated table: the
5+
* rows of read groups numbered 1 modulo 4, from the helper. This file is
6+
* its call and nothing else; the shard it is comes from its own name, and
7+
* `protocol.lock-one-resolution.kit.test.ts` pins that the four files exist.
8+
*/
9+
import { registerRowShard } from './protocol.lock-one-resolution.kit.test.js';
10+
11+
registerRowShard();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#23182 row 1, step B] Shard 3 of 4 of the lock family's generated table: the
5+
* rows of read groups numbered 2 modulo 4, from the helper. This file is
6+
* its call and nothing else; the shard it is comes from its own name, and
7+
* `protocol.lock-one-resolution.kit.test.ts` pins that the four files exist.
8+
*/
9+
import { registerRowShard } from './protocol.lock-one-resolution.kit.test.js';
10+
11+
registerRowShard();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#23182 row 1, step B] Shard 4 of 4 of the lock family's generated table: the
5+
* rows of read groups numbered 3 modulo 4, from the helper. This file is
6+
* its call and nothing else; the shard it is comes from its own name, and
7+
* `protocol.lock-one-resolution.kit.test.ts` pins that the four files exist.
8+
*/
9+
import { registerRowShard } from './protocol.lock-one-resolution.kit.test.js';
10+
11+
registerRowShard();

0 commit comments

Comments
 (0)