Skip to content

Commit 32e03d2

Browse files
test(spec): six retirement walks skip generated json-schema, unmatched pattern runs and a repeated corpus walk (#23575)
Part of #23180 Clause-②: no This round's claim (`6181109416`) covers seven `src/` retirement rows. Six of them spent most of their time in one tree walk over `packages`, `examples`, `skills`, `content` and `scripts`. This PR cuts that work inside the walk. Every assertion is unchanged and no verdict changes. Each of the six now takes about 1.0 to 1.8 s locally, down from 2.2 to 3.8 s. The seventh row, `compliance-families-retirement.test.ts`, is left as it was: its floor is one case that imports the `system` barrel. The diff is test files only: 6 files, +54 / -15. It adds or changes no `expect(`, `it(` or `describe(` line (counted: 0). No production source changes. ## Rows All readings are local, taken through `scripts/pm/os-verify-lock.sh` on a shared box, so they are shared-box seconds. The spec package was built, so `packages/spec/json-schema/` was on disk, as it is in CI's `Test Core`, which builds before it tests. Command: `env -u AI_AGENT -u CLAUDECODE -u CLAUDE_CODE -u CURSOR_AGENT NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2 FILE --reporter=default --reporter=json --outputFile.json=OUT`. Each figure is the vitest module line: the sum of the file's case durations, which is the number the battery records. | row (card's CI reading) | before, 3 runs (ms) | after, 3 runs (ms) | cases, every run | change | this PR | |:--|:--|:--|--:|:--|:--| | `src/api/rest-api-config-dead-keys-retirement.test.ts` (17.8 s) | 2744 / 2794 / 2907 | 1263 / 1359 / 1465 | 23/23 | walk skips `json-schema/` | targeted | | `src/data/cube-member-inner-name-retirement.test.ts` (17.6 s) | 3509 / 3750 / 3730 | 1769 / 1730 / 1747 | 20/20 | walk skips `json-schema/` | targeted | | `src/qa/requires-plugins-retirement.test.ts` (12.4 s) | 2472 / 2923 / 2825 | 1293 / 1249 / 1324 | 4/4 | walk skips `json-schema/` | targeted | | `src/shared/retired-key-migrate-sentence.test.ts` (12.1 s) | 3406 / 3493 / 3340 | 1087 / 986 / 994 | 15/15 | corpus read once | targeted | | `src/integration/connector-resilience-keys-retirement.test.ts` (11.9 s) | 2565 / 2319 / 2368 | 1559 / 1556 / 1508 | 21/21 | required-literal guard | targeted | | `src/data/cube-refresh-key-retirement.test.ts` (11.9 s) | 2198 / 2232 / 2187 | 1081 / 1097 / 1064 | 21/21 | required-literal guard | targeted | | `src/system/compliance-families-retirement.test.ts` (14.6 s) | 4193 / 4299 / 4423 | 4587 / 4338 / 4391 | 10/10 | none | left at its floor | - Before: base `b57228913a`. The third before-run restored the base content of the six files into the tree and then restored HEAD, proven by an empty `git diff HEAD` and a clean porcelain. - After: `2add893d1d`. One more run at the final head `4c48c63724` (that commit merged with `origin/main` `7ffc57846a`, which touches no `packages/spec` file) gave 1297 / 1831 / 1262 / 1028 / 1508 / 1128 ms in the table's order, and compliance 4327 ms. - The untouched compliance file is the box-load control: it reads the same before and after. ### Why the dispatch's bar does not settle these rows The dispatch's bar is a module line of 4 s or less on three runs, on the assumption that CI is about 2x slower. All six rows already met that bar BEFORE this change (the highest before-reading is 3750 ms), so for this class the bar cannot tell a repaired row from an untouched one. Measured against the card's own CI readings, this class runs 3.5x to 6.4x slower in the battery than here: | row | CI / local median, before | after median × that ratio | |:--|--:|--:| | rest-api | 17.8 s / 2.79 s = 6.4x | 8.7 s | | cube-member | 17.6 s / 3.73 s = 4.7x | 8.3 s | | requires-plugins | 12.4 s / 2.83 s = 4.4x | 5.7 s | | migrate-sentence | 12.1 s / 3.41 s = 3.5x | 3.5 s | | connector-resilience | 11.9 s / 2.37 s = 5.0x | 7.8 s | | cube-refresh | 11.9 s / 2.20 s = 5.4x | 5.8 s | Every projection is under 10 s. The CI effect is NOT MEASURED. The card's pin decides each row: the next refresh PR's list no longer names the file. So this PR targets six rows and takes none off the card by itself. ### `compliance-families-retirement.test.ts` is left as it was - Its case "runtime namespace agrees with the compiler view" runs `await import('./index')` on the `system` barrel. That case took 2915 to 3115 ms in all six runs, out of a module line of 4193 to 4587 ms. - The barrel's import graph is 312 modules, about 87 beyond what the file's static imports already load. So the cost is vitest's transform of an import graph that is itself the test's subject. That is the mechanism of #23534's second class, at the size of one domain barrel rather than the whole package. - Even with its walk (1052 to 1317 ms) at zero, the file projects to about 10.5 s at its 3.4x ratio. - Per the claim, it is reported with its floor and left as it is. ## The three changes, and why none changes a verdict 1. **The generated `json-schema/` tree, in three walks** (rest-api, cube-member, requires-plugins). - Each filesystem walk now lists `packages/spec/json-schema/` in `EXCLUDED_PREFIXES`. The entry and its comment match the ones `cube-refresh-key-retirement.test.ts` and `connector-resilience-keys-retirement.test.ts` already carry. - The tree is gitignored `gen:schema` output. The walk reached it only because it walks the filesystem: turbo's input hashing never sees it, and an unbuilt checkout does not have it. - It is 1,537 files and 45.5 M chars. It was also most of what these walks lexed: 34 to 39 M of the 62 to 67 M chars each one handed to its character lexer. - Of the 27 five-root filesystem or git walkers in `packages/spec`, 21 other files already exclude this tree or list through git. 2. **A required-literal guard** (connector-resilience, cube-refresh). - Every alternative of every `AUTHORING` pattern requires a literal: `refreshKey` in one file, a `LEAF_KEYS` or `NAMES` alternative in the other. - `judge()` now runs the patterns only when the stripped text holds one of those literals. The patterns no longer run over every file. - The guard reads the STRIPPED text, never the raw text, because stripping an inline code span can join a literal. - The connector list is derived from the same `LEAF_KEYS` and `NAMES` strings the patterns are built from. 3. **The migrate-sentence corpus, read once.** - Four cases call `judgeTree()` or `claimTree()`, and each call used to walk, read and reconstruct all three corpora again: about 0.8 s each time. - The walk, read and reconstruct now happen once per file run. Every case still runs its own judgement over that input. - The exported `judgeMigrateSentences` and `findWithdrawnClaims` keep their signatures and bodies. Each now delegates to an internal function that takes the reconstructed text. ## Equivalence readings The harnesses were instrumented copies placed next to each file and run with `--project local -t ZZ-EQUIV`. All six were removed afterwards, and the tree ended at porcelain 0. Nothing from them is committed. - **cube-refresh.** The guarded and unguarded `judge()` were run over every file in the radius with NO exclusions: 11,792 files, 270.6 M chars. Both found 4 matches (the conversion registry, the batch-D pin, the pin itself and the copy). Index and matched text agreed on every file, with 0 differences. - **connector-resilience.** The same comparison over 11,640 files: 3 matches each, 0 differences. - **rest-api, cube-member, requires-plugins.** Each walk was run with the base prefix list and with the new one. - The new walk dropped exactly 1,537 files, all under `packages/spec/json-schema/`. It dropped 0 files elsewhere and added 0. - Offenders were identical both ways. Seen from the copy, the original pin's own fixtures count as offenders, so this comparison is not vacuous. - Anti-vacuity counters, old to new: `bearing` 18 to 15 (floor 5); `bagBearing` 286 to 273 (floor 20); `suites` 1 to 1 (floor 0); `visited` about 11.5k to 10.0k (floor 1000). - **migrate-sentence.** The old per-call walk and the shared corpus gave 182 judged sites each, 182 ok. The output was JSON-identical on all three `judgeTree()` calls, and `claimTree()` was identical too (0 claims). The corpus is 1,498 files from the `spec`, `lint` and `driver-turso` corpora. ## Ablations (one per semantic shortcut; each run through `node scripts/ablation-replace.mjs`) The expected direction was red each time, and each run went red. No `dist/` is in any of these paths: the subjects live in the test files themselves. - **A1, cube-refresh guard.** `'refreshKey'` was changed to `'refreshKeyZ'`. Anchor 1 to 0, blob `c958c19a5f55` to `e0251312a8ea`. Vitest went 1 failed / 20 passed: the anti-vacuity case reported `expected null not to be null`. The restore left the blob equal to HEAD `c958c19a5f55`, with `git diff HEAD` empty. - **A2, connector guard.** The `LEAF_KEYS` half of `AUTHORING_LITERALS` was dropped. Anchor 1 to 0, blob `75b4db70535f` to `e00ed55551c0`. Vitest went 1 failed / 20 passed: a leaf-key positive returned null. The restore left the blob equal to HEAD, with `git diff HEAD` empty. - **A3, corpus memo.** The memo was made to hand later callers `corpusMemo.slice(0, 1)`. Anchor 1 to 0, blob `afb7e349b3ae` to `d3352368d35f`. Vitest went 2 failed / 13 passed: `expected 0 to be greater than or equal to 50`, and `lint: expected 0 to be greater than or equal to 1`. The restore left the blob equal to HEAD, with `git diff HEAD` empty. - The withdrawn-claim case stayed green under the broken memo. The anti-vacuity floors are what catch a memo that hands later cases less. - **No ablation for the `json-schema/` exclusion.** It is a declared narrowing of the population, not a shortcut inside it. Ablating it on this tree is a no-op, because no offender lives in that directory. The population reading above shows exactly what it removes. ## Publish No published file changes, so `skip-changeset`. `pnpm pack` of `@objectstack/spec` 18.0.0-next.1 gave 2,091 files, with 0 `*.test.ts`. Files containing `AUTHORING_LITERAL`, `reconstructedCorpus`, `corpusMemo`, `judgeReconstructed` and `withdrawnClaimsIn`: 0 each. Positive controls: `RestApiConfigSchema` in 24 files, `retiredKey` in 180. ## Gates All gates were run at the final head `4c48c63724`. Each exit code was captured before any pipe. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 76 commands. All 76 were run in sequence. - 74 exited 0. - 2 are NOT MEASURED: `pnpm check:dual-build-cjs-loads` and `pnpm check:lean-entry-closure` both exited 3 (PREREQUISITE NOT MET). They load other packages' built `dist/` (hono, account, objectql), and only spec, formula and lint were built here. Neither can read a test file. CI runs both. - The `--ran` verdict: `76 derived famil(ies) accounted for — 74 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`, exit 0. - `pnpm --filter @objectstack/spec typecheck` exited 0. Its `check:test-typecheck` reported `52 file(s) / 246 error(s) / 135 pinned signature(s) held`. `tsc -p tsconfig.test.json --listFilesOnly` lists all six edited files, and none of them is in `test-typecheck-debt.json`. - `eslint --no-inline-config` on the 6 files exited 0. - Declared narrowing: the spec package's full `test` and `test:repo` projects were not run locally, because they carry the 900 s and 493 s rows. The seven claimed files were run, as above. CI runs both projects. ## Acceptance notes - Three five-root filesystem walkers in `packages/spec` still read `json-schema/`, and they are outside this claim. `src/kernel/metadata-type-registry-allow-org-override-absence.test.ts` is one of them, and it is another row on this card (11.4 s). The other two are `src/contracts/record-share-guest-recipient-census.test.ts` and `src/ui/view-data-api-provider-retirement.test.ts`. This is a cost observation, not a defect, so no card is filed. - One option was measured and not taken. Reading files as latin1 for the ASCII prefilters, and decoding as UTF-8 only on a hit, cut the bare read from about 1.05 s to 0.60 s per walk in a scratch benchmark. It would make the three lexer walks a little faster, but it adds an encoding argument to every reader. It is left for a later round, if the refresh still lists rest-api or cube-member. - This round's measurements were taken in the same session as the claim. No CI log or artifact was readable from this container, because the blob host is refused, so every CI number above is the card's. --- _Generated by [Claude Code](https://claude.ai/code/session_0121ooBYRmS8ziFXm9ai5YHm)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c980d11 commit 32e03d2

6 files changed

Lines changed: 54 additions & 15 deletions

‎packages/spec/src/api/rest-api-config-dead-keys-retirement.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ describe('tree-scoped absence: no `api` block inside the declared radius still a
319319
const EXCLUDED_PREFIXES = [
320320
// Release-owned prose records the removal; never edited by a code PR.
321321
'content/docs/releases/',
322+
// GITIGNORED build output (`packages/spec/json-schema/`), reached only
323+
// because this is a FILESYSTEM walk. Its source is `rest-server.zod.ts`.
324+
'packages/spec/json-schema/',
322325
];
323326
/** tsup's own bundle of `tsup.config.ts`, written and deleted mid-build. */
324327
const TSUP_BUNDLED_CONFIG = /\.bundled_[^./]+\.mjs$/;

‎packages/spec/src/data/cube-member-inner-name-retirement.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ describe('tree-scoped absence: no cube member inside the declared radius still c
404404
// shape, not an authoring, and the two `name` rows are the ones the
405405
// tombstone route requires to STAY.
406406
'packages/spec/liveness/',
407+
// GITIGNORED build output (`packages/spec/json-schema/`), reached only
408+
// because this is a FILESYSTEM walk. Its source is `analytics.zod.ts`.
409+
'packages/spec/json-schema/',
407410
];
408411
/** tsup's own bundle of `tsup.config.ts`, written and deleted mid-build. */
409412
const TSUP_BUNDLED_CONFIG = /\.bundled_[^./]+\.mjs$/;

‎packages/spec/src/data/cube-refresh-key-retirement.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,16 @@ describe('tree-scoped absence: nothing inside the declared radius still authors
364364
* inside a fenced example is still caught.
365365
*/
366366
const stripInlineCode = (text: string): string => text.replace(/`[^`\n]*`/g, '');
367+
/**
368+
* Every `AUTHORING` pattern requires this literal, so a stripped text that
369+
* lacks it matches none of them: the patterns run only where it appears.
370+
* Read on the STRIPPED text, never the raw one — stripping can join a
371+
* literal across an inline code span.
372+
*/
373+
const AUTHORING_LITERAL = 'refreshKey';
367374
const judge = (text: string): RegExpExecArray | null => {
368375
const stripped = stripInlineCode(text);
376+
if (!stripped.includes(AUTHORING_LITERAL)) return null;
369377
for (const re of AUTHORING) {
370378
const m = re.exec(stripped);
371379
if (m) return m;

‎packages/spec/src/integration/connector-resilience-keys-retirement.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,17 @@ describe('tree-scoped absence: nothing inside the declared radius still authors
435435
* stripped, so an authoring inside a fenced example is still caught.
436436
*/
437437
const stripInlineCode = (text: string): string => text.replace(/`[^`\n]*`/g, '');
438+
/**
439+
* Every alternative of every `AUTHORING` pattern requires one of these
440+
* literals — a `LEAF_KEYS` or a `NAMES` alternative — so a stripped text that
441+
* holds none of them matches no pattern: the patterns run only where one
442+
* appears. Read on the STRIPPED text, never the raw one — stripping can join
443+
* a literal across an inline code span.
444+
*/
445+
const AUTHORING_LITERALS = [...LEAF_KEYS.split('|'), ...NAMES.split('|')];
438446
const judge = (text: string): RegExpExecArray | null => {
439447
const stripped = stripInlineCode(text);
448+
if (!AUTHORING_LITERALS.some((literal) => stripped.includes(literal))) return null;
440449
for (const re of AUTHORING) {
441450
const m = re.exec(stripped);
442451
if (m) return m;

‎packages/spec/src/qa/requires-plugins-retirement.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ describe('tree-scoped absence: no scenario inside the declared radius still auth
9393
const EXCLUDED_PREFIXES = [
9494
// Release-owned prose records the removal; never edited by a code PR.
9595
'content/docs/releases/',
96+
// GITIGNORED build output (`packages/spec/json-schema/`), reached only
97+
// because this is a FILESYSTEM walk. Its source is `testing.zod.ts`.
98+
'packages/spec/json-schema/',
9699
];
97100
/** tsup's own bundle of `tsup.config.ts`, written and deleted mid-build. */
98101
const TSUP_BUNDLED_CONFIG = /\.bundled_[^./]+\.mjs$/;

‎packages/spec/src/shared/retired-key-migrate-sentence.test.ts‎

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,14 @@ const lineAt = (segments: Array<{ start: number; line: number }>, at: number): n
235235
return line;
236236
};
237237

238+
type Reconstructed = ReturnType<typeof reconstruct>;
239+
238240
/** Every `os migrate meta --from <N>` prescription in one file, with its verdict. */
239241
export function judgeMigrateSentences(raw: string, file = '<inline>'): JudgedSite[] {
240-
const { merged, segments } = reconstruct(raw);
242+
return judgeReconstructed(reconstruct(raw), file);
243+
}
244+
245+
function judgeReconstructed({ merged, segments }: Reconstructed, file: string): JudgedSite[] {
241246
const judged: JudgedSite[] = [];
242247
for (const m of merged.matchAll(MARKER)) {
243248
const at = m.index ?? 0;
@@ -257,7 +262,10 @@ export function judgeMigrateSentences(raw: string, file = '<inline>'): JudgedSit
257262
* that names `--from <N>`.
258263
*/
259264
export function findWithdrawnClaims(raw: string, file = '<inline>'): Array<{ file: string; line: number; excerpt: string }> {
260-
const { merged, segments } = reconstruct(raw);
265+
return withdrawnClaimsIn(reconstruct(raw), file);
266+
}
267+
268+
function withdrawnClaimsIn({ merged, segments }: Reconstructed, file: string): Array<{ file: string; line: number; excerpt: string }> {
261269
return [...merged.matchAll(WITHDRAWN_CLAIM)].map((m) => {
262270
const at = m.index ?? 0;
263271
return {
@@ -276,28 +284,33 @@ function* walk(dir: string): Generator<string> {
276284
}
277285
}
278286

279-
function judgeTree(): JudgedSite[] {
280-
const all: JudgedSite[] = [];
287+
/**
288+
* The corpora, walked, read and reconstructed ONCE per run of this file: four
289+
* cases below ask `judgeTree()` or `claimTree()` about the same tree, and each
290+
* used to repeat the whole walk. Only the INPUT is shared — every case still
291+
* judges it for itself, with the functions the inline cases below exercise.
292+
*/
293+
let corpusMemo: Array<{ file: string; text: Reconstructed }> | undefined;
294+
function reconstructedCorpus(): Array<{ file: string; text: Reconstructed }> {
295+
if (corpusMemo) return corpusMemo;
296+
const files: Array<{ file: string; text: Reconstructed }> = [];
281297
for (const corpus of CORPORA) {
282298
for (const file of walk(corpus.root)) {
283299
const rel = path.relative(corpus.root, file);
284300
if (corpus.outOfScope.has(rel)) continue;
285-
all.push(...judgeMigrateSentences(fs.readFileSync(file, 'utf8'), `${corpus.name}:${rel}`));
301+
files.push({ file: `${corpus.name}:${rel}`, text: reconstruct(fs.readFileSync(file, 'utf8')) });
286302
}
287303
}
288-
return all;
304+
corpusMemo = files;
305+
return files;
306+
}
307+
308+
function judgeTree(): JudgedSite[] {
309+
return reconstructedCorpus().flatMap(({ file, text }) => judgeReconstructed(text, file));
289310
}
290311

291312
function claimTree(): Array<{ file: string; line: number; excerpt: string }> {
292-
const all: Array<{ file: string; line: number; excerpt: string }> = [];
293-
for (const corpus of CORPORA) {
294-
for (const file of walk(corpus.root)) {
295-
const rel = path.relative(corpus.root, file);
296-
if (corpus.outOfScope.has(rel)) continue;
297-
all.push(...findWithdrawnClaims(fs.readFileSync(file, 'utf8'), `${corpus.name}:${rel}`));
298-
}
299-
}
300-
return all;
313+
return reconstructedCorpus().flatMap(({ file, text }) => withdrawnClaimsIn(text, file));
301314
}
302315

303316
describe('`os migrate meta` sentences are the house sentence, across corpora', () => {

0 commit comments

Comments
 (0)