Repository navigation
Commit df8aac4
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XAViUhQDMdVeNR9YVhJW1y5)_
Part of #23181 (slice 12, claim 6417640032). The card stays open: this
PR cuts real work in five of the eight
`packages/cli/src/commands/migrate/*.integration.test.ts` rows, and
closes none of the 10 s rows outright. The routes that could close them
are in "Not taken" below.
Clause-②: no
Metadata-protocol: none
`skip-changeset`: a test-only diff. The five files are
`*.integration.test.ts` under `packages/cli/src/`, and
`@objectstack/cli` publishes `files: [dist, README.md, CHANGELOG.md]`;
the build program does not compile test files.
## The reading used
The newest refresh with a per-file list is PR #25960 (14 hourly
full-battery runs, `measure-test-shard-timings --diff`); #26089 landed
from the same list. This family's rows in it, CI wall per file:
| file | CI s (run) | spawns of the CLI per file |
|:--|--:|:--|
| `data-commands.absent-database` | 394.05 (`40200279306`) | 43 CLI + 2
seed boots |
| `object-scope` | 180.37 (`40204927516`) | 18 CLI + 1 seed + 17
reader/writer children |
| `plan.boot-parity` | 118.02 (`40204927516`) | 6, through tsx |
| `plan.telemetry-sibling` | 113.36 (`40200279306`) | 5, through tsx |
| `unmapped-columns` | 98.92 (`40204927516`) | 10 CLI + 1 seed + 2
reader children |
| `plan.declared-default-datasource` | 95.65 (declared 80) | 4, through
tsx |
| `plan.nullable-tenant-columns` | 62.72 (`40204927516`) | 4 (1 serve
boot, 3 plans), through tsx |
| `resume.recorded-by` | 43.98 (`40200279306`) | 4 crash children + 2
serve boots, resume in-process |
The family's ninth row, `plan.deferred-reads` (18.22 s), is left for a
later slice: the claim caps a slice at about 8 files.
## Mechanism: the order's hypothesis does not hold here
The order's usual shape, a per-case boot, is not present. Every file
already builds its fixture once, in a `beforeAll` it owns or in a case
that owns its fixture alone. Each file's cost is the number of CLI
spawns times the per-spawn floor, run one after another. On this box (4
cores, idle, inside the verify lock):
- `node bin/run-dev.js --version`: 4.93 to 4.99 s; `migrate resume
--json` on an absent database: 5.25 to 6.06 s.
- `tsx bin/run-dev.js --version`: 8.28 to 8.44 s; the same `migrate
resume`: 9.35 to 10.09 s.
- One `node -e` child that opens the database with `driver-sql` and
reads it: 0.45 to 0.54 s.
Most of the 5 s floor is oclif importing every command from `src/`,
because no `oclif.manifest.json` ships for the source entry. That is
product code and outside this claim.
## What changed
1. **Four files spawn the source entry with this process's `node`, not
the `tsx` binary** (`plan.boot-parity`, `plan.telemetry-sibling`,
`plan.declared-default-datasource`, `plan.nullable-tenant-columns`). The
entry (`bin/run-dev.js`), its `src/` and the `NODE_ENV=development` it
pins are unchanged. This is how the family's other spawners already
spawn it (`data-commands.absent-database`, `object-scope`,
`unmapped-columns`, and `resume.recorded-by` for `serve`). The `tsx`
wrapper added a second process and its own transform, about 3.4 s per
spawn. Fixture configs still load through the CLI's own
`bundle-require`. `vitest-tiers.ts` still files all four as integration
(they value-import `node:child_process` and name `run-dev`).
2. **`object-scope` reads and writes its fixture database in-process**
through `better-sqlite3`, as `plan.boot-parity` and
`plan.telemetry-sibling` already do. It used one `node` child per read:
16 `readState` calls and 1 off-shape write. Each query is the same
column list and `ORDER BY id`, and the connection is opened read-write
as knex opened it.
Every assertion is unchanged. The diff touches no `expect`, no case and
no timeout.
## Measured
Same box, before (`a335f1de`) and after (`54d51cc3`), N=3 per file, one
file per `vitest run --project integration --maxWorkers=1` under
`os-verify-lock`. These are process wall and user CPU, which include the
hook (the JSON reporter's per-file time leaves out `beforeAll`).
| file | before wall s (3 runs) | after wall s (3 runs) | median change
| user CPU s, medians |
|:--|:--|:--|--:|:--|
| `plan.boot-parity` | 63.16 · 67.17 · 63.06 | 40.54 · 37.84 · 41.89 |
−36% | 87.5 → 56.2 |
| `plan.telemetry-sibling` | 64.47 · 67.21 · 62.03 | 53.08 · 38.21 ·
42.63 | −34% | 89.6 → 58.4 |
| `plan.declared-default-datasource` | 63.37 · 63.40 · 63.33 | 48.20 ·
47.17 · 51.16 | −24% | 89.4 → 69.3 |
| `plan.nullable-tenant-columns` | 42.54 · 42.74 · 42.72 | 28.34 · 29.28
· 27.87 | −34% | 59.2 → 39.1 |
| `object-scope` | 108.79 · 117.71 · 112.76 | 106.64 · 100.00 · 105.03 |
−7% | 157.6 → 149.4 |
CPU falls with wall, so this is work removed, not work packed.
**Projection, not proof.** Scaling by each file's own CI-to-local ratio
(CI reading ÷ local before-median): `plan.declared-default-datasource` ≈
73 s, under its declared 80 s budget, so the next refresh should stop
listing it. That is the one row this PR is expected to close. The others
project to `plan.boot-parity` ≈ 76 s, `plan.telemetry-sibling` ≈ 75 s,
`plan.nullable-tenant-columns` ≈ 42 s and `object-scope` ≈ 168 s, all
still over 10 s.
**Isolation.** Two shuffled runs of the five files, all green:
- `--sequence.shuffle --sequence.seed=23181`: 5/5 files, 27/27 tests.
- `--sequence.seed=7` with an execution-order reporter: file order
nullable → boot-parity → object-scope → declared-default → telemetry,
and `object-scope`'s 20 cases ran out of definition order; 27/27 passed.
The hook files' cases only read what a hook recorded. `plan.boot-parity`
and `plan.telemetry-sibling` build a fresh `mkdtemp` fixture per case.
## Not taken: the routes that would close the remaining rows
None of these is inside the claim. They are open questions on the card
for the seat.
- **A. Spawn the built entry (`bin/run.js`).** Its floor was measured at
2.46 to 2.66 s against 5.45 to 6.07 s (`vitest.config.ts`); slice 9
reads 2.7 to 3.0 against 10.0 to 10.3 s. It changes the `NODE_ENV`
posture and makes `dist/` a prerequisite, so it needs a ruling. Slice 9
raises the same question.
- **B. Run a file's independent spawns concurrently.** Measured on
`data-commands.absent-database` (a local experiment, not committed):
pool 1 → 263.1 s wall, 431.9 s CPU; pool 2 → 152.4 s, 422.5 s; pool 4 →
120.7 s, 433.4 s. Wall falls while CPU stays flat, so this packs the
work rather than removing it. `vitest.config.ts` rejects the same trade
for `maxWorkers` because it spends cores that belong to the shard.
- **C. A declared budget** (ruling record 6181973363 letter B, class
"spawns the real producer once per single-cause fixture").
`plan.declared-default-datasource` and `audit-mail-bodies.archive` in
this directory already use it. It conflicts with the card's "runs under
10 s".
## Gates
All at head `54d51cc3d`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 58 commands over the 5-path change
set. Each one's exit code was captured by redirecting, then reading
`$?`, with no pipe. **All 58 exit 0.** `--ran`: "Run reconciliation — 58
derived, 58 run, 0 NOT-MEASURED, 0 UNRUN", exit 0.
- Six gates first exited 3 (PREREQUISITE NOT MET), and all six exit 0 on
re-run: `check-plugin-teardown-shape --self-test` (shallow clone; fixed
by fetching its positive control `621a4876`), and
`check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage`,
`check:i18n-walk-parity`, `check:type-check-debt` (no built `dist/`;
fixed by a full `turbo run build`, 121/121 tasks).
- `eslint --no-inline-config packages/cli`: exit 0.
- `pnpm --filter @objectstack/cli typecheck` (`tsc --noEmit` +
`check:test-typecheck`): `VERDICT command-exit 0`.
- The five files, `vitest run --project integration` (the integration
tier is this diff's own layer): 5/5 files and 27/27 tests in every run
above (3 ordered runs and 2 shuffled).
- `packages/cli/dist` after the build contains no test file
(`dist/commands/migrate` has 0 `*.test.*`).
## Acceptance notes
- `unmapped-columns` has the same reader-child shape (2 children, about
1 s of its about 99 s). It is left as it is: its readers compare
BLOB-carrying rows byte for byte, which is not worth the risk for one
second.
- `data-commands.absent-database`, `unmapped-columns` and
`resume.recorded-by` already spawn through `node`. Inside this claim
their only lever is B, which is not taken.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01XYjtBuXuGmMz385DUttmzN
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XYjtBuXuGmMz385DUttmzN)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4f48d2b commit df8aac4
5 files changed
Lines changed: 61 additions & 37 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
125 | 126 | | |
126 | 127 | | |
127 | 128 | | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
148 | 137 | | |
149 | 138 | | |
150 | 139 | | |
| |||
186 | 175 | | |
187 | 176 | | |
188 | 177 | | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
189 | 182 | | |
190 | | - | |
191 | | - | |
192 | | - | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
193 | 194 | | |
194 | 195 | | |
195 | 196 | | |
| |||
296 | 297 | | |
297 | 298 | | |
298 | 299 | | |
299 | | - | |
300 | | - | |
| 300 | + | |
301 | 301 | | |
302 | 302 | | |
303 | 303 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
60 | 67 | | |
61 | | - | |
62 | 68 | | |
63 | 69 | | |
64 | 70 | | |
| |||
98 | 104 | | |
99 | 105 | | |
100 | 106 | | |
101 | | - | |
| 107 | + | |
102 | 108 | | |
103 | 109 | | |
104 | | - | |
| 110 | + | |
105 | 111 | | |
106 | 112 | | |
107 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
58 | 65 | | |
59 | | - | |
60 | 66 | | |
61 | 67 | | |
62 | 68 | | |
| |||
134 | 140 | | |
135 | 141 | | |
136 | 142 | | |
137 | | - | |
| 143 | + | |
138 | 144 | | |
139 | 145 | | |
140 | 146 | | |
| |||
145 | 151 | | |
146 | 152 | | |
147 | 153 | | |
148 | | - | |
| 154 | + | |
149 | 155 | | |
150 | 156 | | |
151 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
35 | 42 | | |
36 | | - | |
37 | 43 | | |
38 | 44 | | |
39 | 45 | | |
| |||
93 | 99 | | |
94 | 100 | | |
95 | 101 | | |
96 | | - | |
| 102 | + | |
97 | 103 | | |
98 | 104 | | |
99 | 105 | | |
100 | 106 | | |
101 | 107 | | |
102 | 108 | | |
103 | | - | |
| 109 | + | |
104 | 110 | | |
105 | 111 | | |
106 | 112 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
49 | 56 | | |
50 | | - | |
51 | 57 | | |
52 | 58 | | |
53 | 59 | | |
| |||
116 | 122 | | |
117 | 123 | | |
118 | 124 | | |
119 | | - | |
| 125 | + | |
120 | 126 | | |
121 | 127 | | |
122 | 128 | | |
| |||
130 | 136 | | |
131 | 137 | | |
132 | 138 | | |
133 | | - | |
| 139 | + | |
134 | 140 | | |
135 | 141 | | |
136 | 142 | | |
| |||
0 commit comments