Repository navigation
Commit 025008a
Fixes #21733
Clause-②: yes (widening)
## What this changes
A plain `os dev` on a non-host config (every fresh scaffold) boots
through the standalone stack, and that path now gets the two dev
behaviours `content/docs/deployment/cli.mdx` already documents:
1. **Dev schema self-heal.** On restart, safe drift (`autoMigrate:
'safe'`) is applied. The card's staged non-NULL-safe unique index comes
back NULL-safe, and the drift and plan lines saying "auto-applied at
boot under dev autoMigrate: 'safe'" are now true. Their wording is
unchanged.
2. **Telemetry sibling.** The `DB.telemetry.EXT` sibling datasource is
provisioned next to a file-backed SQLite primary under the same
`resolveTelemetryDbPath` rule. `OS_TELEMETRY_DB=0` opts out.
Production boots and one-shot command boots do not change.
## Route (A2): one decision, in `@objectstack/runtime`
- **New home.** `packages/runtime/src/dev-auto-migrate.ts` exports
`devAutoMigrateConfig(driver, dev)`. It decides two things in one place:
- when: an explicit dev boot;
- for which kinds: those whose spec connection contract declares
`autoMigrate`, i.e. sqlite, postgres and mysql.
- **Three readers, none with its own condition:**
- the standalone stack's `default` definition (`standalone-stack.ts`);
- every arm of `resolveStorageDefinition` (`storage-driver.ts`, the
host-config path);
- the telemetry sibling's driver.
- **Why runtime.** The dependency direction allows cli → runtime only,
and `service-datasource` and `spec` are out of lane. The move adds two
exports to `@objectstack/runtime`'s only entry, `devAutoMigrateConfig`
and `DevAutoMigrateConfig`. That is this PR's published-surface
widening, hence `Clause-②: yes (widening)` and a `minor` on runtime (see
the changeset).
- **Per-kind behaviour is unchanged.** The host path already gave
`'safe'` to exactly sqlite, postgres, mysql and the dev-default
`:memory:` sqlite, and none to sqlite-wasm, mongodb or turso. Two pins
hold this:
- `dev-auto-migrate.test.ts` holds the helper's set equal to the spec
contract;
- `dev-self-heal-host-parity.test.ts` drives both real entry points for
all 6 kinds × dev/production. All 12 rows agree.
## One-shot fence (A3)
- **The risk.** `bootSchemaStack` (`schema-migrate.ts`, every `os
migrate *` / `os meta *` / `os secret *` boot) passes no `dev`. So
`factoryDev` falls back to `NODE_ENV === 'development'`.
- **The fix.** The standalone stack therefore reads the decision under
`cfg.dev === true` only. The step-down keeps its `NODE_ENV` default. `os
dev`, `os serve` and `os start` always pass `dev` explicitly, from
`serve.ts` and through `createDefaultHostConfig`.
- **Measured** by ablation leg C below: with the read keyed on
`factoryDev` under `NODE_ENV=development`:
- a **non-deferred** one-shot boot applied the staged safe drift (fence
pin red, `expected [] to include 'safe:recreate_index'`);
- `os migrate plan` stayed write-free, because its deferred DDL never
reaches the reconcile (pin green).
- **Pins** are in their own file,
`schema-migrate.dev-self-heal-fence.integration.test.ts`:
- plan lists the drift as `safe` and leaves the staged file
byte-identical;
- a non-deferred `bootSchemaStack` leaves the drift in place;
- the serving declaration (`dev: true`) heals the same file, as a
positive control.
## Telemetry on the standalone path (A4)
- **Shared provision.** The inline provision in `serve.ts` became
`provisionTelemetryDatasource` (`utils/telemetry-datasource.ts`). Both
serving paths call it through one closure:
- the host branch keys it on `resolution.sqliteFilePath`;
- the standalone branches key it on `standaloneTelemetryPrimary(input)`,
which is the runtime's own pre-boot `resolveStandaloneDatabase` over the
same input the stack was handed. Only the native `sqlite` kind counts,
as on the host path.
- **Banner.** It needed nothing new. `servedSqliteFilePath` on this path
is still read from the booted driver, and the plugin roster gains
`TelemetryDatasource` exactly as on the host path.
- **Falsifier.** No deliberate omission was found: no comment, test or
ruling in `standalone-stack*`, `default-host*`, the CLI tests or
ADR-0057/0062. ADR-0062 records the telemetry sibling as a pre-built
`DriverPlugin`, and it stays one.
## Reproduction (A1), origin/main `8cbba54491`, built CLI (`bin/run.js`)
The scaffold is one object `scaf_item` with `qa_code: { unique: true }`,
`sharingModel: 'private'`, and no plugins.
- **Boot 1** (`os dev --no-watch -d file:A.db`) created the NULL-safe
index ``CREATE UNIQUE INDEX `uniq_scaf_item_organization_id_qa_code` ON
`scaf_item` (COALESCE(`organization_id`, '__global__'), `qa_code`)``. No
`A.telemetry.db` was created.
- **Stage.** The index was restaged as `CREATE UNIQUE INDEX
uniq_scaf_item_organization_id_qa_code ON scaf_item (organization_id,
qa_code)`. `os migrate plan --database-url file:A.db` then listed `✓
scaf_item [uniq_…] [recreate_index]` under Safe, and the file was
byte-identical afterwards.
- **Two plain `os dev` restarts** each logged `[schema-drift] …
(auto-applied at boot under dev autoMigrate: 'safe')`, and the index
stayed bare both times.
- **Control.** The same staged file under `OS_MODE=off` logged
`[schema-drift] auto-reconciled recreate_index on
scaf_item.organization_id`, the index came back NULL-safe, and
`A.telemetry.db` was created.
- **After the fix,** the same staged file with a plain `os dev` logs
`auto-reconciled recreate_index`, the index comes back NULL-safe, and
`A.telemetry.db` is created. It holds `sys_audit_log`, `sys_job_run`,
`sys_notification` and 4 more tables.
## Tests
Full suites and lint ran at `c20d26dd76` (round 1). Round 2 merged
`origin/main` `b7a13c762f` and reran the targeted set at `3e28108544`,
listed under the table.
| Run | Result |
|---|---|
| `pnpm --filter @objectstack/runtime exec vitest run --project local
--maxWorkers=2` (full runtime suite) | 324 files, 4617 passed, 19
skipped |
| `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` (full) | 258 files, 3777 passed |
| `… --project integration --maxWorkers=2 --shard=1/4 … 4/4` (full, 93
files) | 24/23/23/23 files; 169+267+238+223 passed, 2 skipped |
| `pnpm --filter @objectstack/runtime typecheck` / `@objectstack/cli
typecheck` (incl. test layers) | green |
| `pnpm lint` (full, `eslint . --no-inline-config`) | exit 0, 99 s;
rerun at `3e28108544`: exit 0, 104 s |
Round 2 at `3e28108544`, after the merge and a full turbo rebuild of
`./packages/*` and `./packages/*/*` (71 tasks):
- `pnpm --filter @objectstack/runtime typecheck` and `@objectstack/cli
typecheck`, including the test layers: green.
- runtime `src/default-datasource-plugin.test.ts` and
`src/dev-auto-migrate.test.ts`: 2 files, 19 passed.
- the four CLI pins below, on the rebuilt packages: 4 files, 13 passed.
The new pins:
- `packages/runtime/src/dev-auto-migrate.test.ts`: contract equality,
plus the `default` definition under dev, production, and the
`NODE_ENV=development` default.
- `packages/cli/src/utils/dev-self-heal-host-parity.test.ts`: both
hosts, 6 kinds × dev/production.
-
`packages/cli/src/utils/schema-migrate.dev-self-heal-fence.integration.test.ts`:
the one-shot fence, in its own file.
-
`packages/cli/src/utils/telemetry-datasource.provision.integration.test.ts`:
the provision, the `OS_TELEMETRY_DB=0` opt-out, production opt-in only
by explicit path, and `standaloneTelemetryPrimary`.
- `packages/cli/test/dev-standalone-self-heal.integration.test.ts`: the
card's restart pin on the **built** CLI.
- fresh `os dev` with `OS_TELEMETRY_DB=0`: NULL-safe index, no sibling;
- restart on the restaged bare index: NULL-safe again,
`auto-reconciled`, `A.telemetry.db` present;
- production `os serve` (`NODE_ENV` unset) on the restaged file: the
drift is reported and left bare.
## Reverse verification (A5): `scripts/ablation-replace.mjs`, rebuilt,
`ablation-dist-preflight` both legs
All three legs follow the same sequence:
1. The anchor hits once, and the blob changes.
2. `dist/` carries the marker (preflight exit 0).
3. Measure.
4. Restore: the blob equals HEAD and `git diff HEAD` is empty.
5. Rebuild, then preflight `--absent`: the marker is gone from `dist/`
and the tree is clean.
| Leg | Mutation (`standalone-stack.ts` / `serve.ts`) | Blob | Red |
Green |
|---|---|---|---|---|
| A: the standalone read of the decision | `config: { ...driverConfig,
...devSelfHeal }` → `...(String('ABLATED_21733_A') ? {} : devSelfHeal)`
| `69295ae4dcb6` → `3a20c5f19924` → `69295ae4dcb6` | restart NULL-safe
pin; fence positive control; host parity; runtime dev-definition ×2 |
production pin; fresh pin; telemetry pin; both one-shot fence cases;
`storage-driver.test.ts` (host path); runtime contract, production and
NODE_ENV cases |
| B: the standalone telemetry provision | `standaloneSqlitePrimary =
await standaloneTelemetryPrimary(standaloneInput);` →
`String('ABLATED_21733_B') ? undefined : …` | `d5486ac90717` →
`42870a5cbc82` → `d5486ac90717` | telemetry-sibling pin | self-heal,
opt-out, production pins |
| C: the one-shot fence (A3) | `devAutoMigrateConfig(dbDriver, cfg.dev
=== true)` → `… String('ABLATED_21733_C') ? factoryDev : cfg.dev ===
true` | `69295ae4dcb6` → `22ebf7f09362` → `69295ae4dcb6` | non-deferred
one-shot fence; runtime NODE_ENV-default case | `os migrate plan`
byte-identical; serving positive control |
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `3e28108544` (merge base `b7a13c762`, 14
paths) derives the same 86 families as round 1. All 85 runnable families
were rerun on that head and passed. That includes
`check:changeset-no-major --base origin/main` (no `major`) and
`check:adr-0087-registration` (no declared-breaking changeset).
Round-1 notes, at `c20d26dd76`:
- **Rerun after a prerequisite.** `check:dual-build-cjs-loads` and
`check:i18n-coverage` first answered `PREREQUISITE NOT MET` (exit 3) on
unbuilt packages. They were rerun green after building those packages.
- **Rerun after a fix.** `check:cli-test-child-env` went red on the new
built-entry spawner and was rerun green. The fix is in the second
deviation below.
- **NOT MEASURED: `check:pm-dispatch-gates`.** Its self-test and its
bare check each ran past the 10-minute foreground cap at 590 s on this
shared box, with every printed case passing. It gates
`scripts/pm/dispatch-gates.mjs`, which this diff does not touch. CI runs
it.
- **Carried.** `check:pm-dispatch-gates` is carried NOT MEASURED into
round 2. Its inputs (`scripts/pm`, `.github`, `.claude`, the root
`package.json`) are byte-unchanged between `c20d26dd76` and
`3e28108544`.
- **Reconciled.** `--ran` at `3e28108544` reports `86 derived famil(ies)
accounted for — 85 run, 1 NOT-MEASURED`.
- **Ledger blind spot.** `git grep autoMigrate` over
`packages/spec/liveness/**` and `*.ledger.*` found 0 hits; the control
term `sqlite` hits `datasource.json`. No symbol was renamed or removed.
## Deviations for the seat
- **`@objectstack/runtime` gains two exports.** `devAutoMigrateConfig`
and its `DevAutoMigrateConfig` type, on the package's only entry. This
is the widening declared above.
- No key is added to `StandaloneStackConfigSchema` or to any other
exported type or schema, and no accept set moves.
- The `dev` key's TSDoc now states its narrower self-heal meaning: an
explicit `true` only.
- The changeset is `minor` on `@objectstack/runtime` and `patch` on
`@objectstack/cli`.
- **`scripts/check-cli-test-child-env.mjs` census.** Its pinned
built-entrypoint population (formerly "exactly the six files") now
admits `dev-standalone-self-heal.integration.test.ts`, with a comment
saying why. The production leg is only reachable through `bin/run.js`
with `NODE_ENV` unset. The gate's 152 self-test cases pass.
- **origin/main merged.** `b7a13c762f` came in through a merge commit
(`07745648c0`, no rebase), with no conflicts. One file overlaps,
`serve.ts`, where #21752 adds the AuthPlugin `appName`; its hunks are
disjoint from this diff. After the merge, the branch's diff against
`main` was the same 13 files with the same +1134/−68 as before; round
2's R4 TSDoc edit makes it 14 files, +1143/−69. The rebuilt `serve.js`
carries both sides.
## Acceptance notes
- **`DefaultDatasourcePluginOptions.dev` TSDoc**
(`packages/runtime/src/default-datasource-plugin.ts`), comment only,
fixed in round 2. It used to say `dev` arms the "self-heal
passthroughs". It now says what `dev` arms, the sqlite step-down
(#2229), and that the self-heal rides in the definition's
`config.autoMigrate`, which `devAutoMigrateConfig` decides.
- **Artifact-fallback branch.** The `createDefaultHostConfig` branch
(`os start` with no config, `os dev -a`) gets both behaviours too. Its
telemetry primary is resolved from the same input handed to
`createDefaultHostConfig`. The empty-boot stub declares no datasource,
so the database answer cannot differ. This is commented at the call
site.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 50b5e03 commit 025008a
14 files changed
Lines changed: 1143 additions & 69 deletions
File tree
- .changeset
- packages
- cli
- src
- commands
- utils
- test
- runtime/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
2583 | 2584 | | |
2584 | 2585 | | |
2585 | 2586 | | |
| 2587 | + | |
| 2588 | + | |
| 2589 | + | |
| 2590 | + | |
| 2591 | + | |
| 2592 | + | |
2586 | 2593 | | |
2587 | 2594 | | |
2588 | 2595 | | |
| |||
2741 | 2748 | | |
2742 | 2749 | | |
2743 | 2750 | | |
2744 | | - | |
| 2751 | + | |
2745 | 2752 | | |
2746 | 2753 | | |
2747 | 2754 | | |
| |||
2755 | 2762 | | |
2756 | 2763 | | |
2757 | 2764 | | |
2758 | | - | |
| 2765 | + | |
| 2766 | + | |
| 2767 | + | |
| 2768 | + | |
| 2769 | + | |
| 2770 | + | |
| 2771 | + | |
| 2772 | + | |
| 2773 | + | |
2759 | 2774 | | |
2760 | 2775 | | |
2761 | 2776 | | |
| |||
2779 | 2794 | | |
2780 | 2795 | | |
2781 | 2796 | | |
| 2797 | + | |
| 2798 | + | |
| 2799 | + | |
| 2800 | + | |
2782 | 2801 | | |
2783 | 2802 | | |
2784 | 2803 | | |
| |||
3190 | 3209 | | |
3191 | 3210 | | |
3192 | 3211 | | |
| 3212 | + | |
| 3213 | + | |
| 3214 | + | |
| 3215 | + | |
| 3216 | + | |
| 3217 | + | |
| 3218 | + | |
| 3219 | + | |
| 3220 | + | |
| 3221 | + | |
| 3222 | + | |
| 3223 | + | |
| 3224 | + | |
| 3225 | + | |
| 3226 | + | |
| 3227 | + | |
| 3228 | + | |
| 3229 | + | |
| 3230 | + | |
| 3231 | + | |
3193 | 3232 | | |
3194 | 3233 | | |
3195 | 3234 | | |
| |||
3234 | 3273 | | |
3235 | 3274 | | |
3236 | 3275 | | |
3237 | | - | |
| 3276 | + | |
3238 | 3277 | | |
3239 | 3278 | | |
3240 | 3279 | | |
| |||
3257 | 3296 | | |
3258 | 3297 | | |
3259 | 3298 | | |
3260 | | - | |
3261 | | - | |
3262 | | - | |
3263 | | - | |
3264 | | - | |
3265 | | - | |
3266 | | - | |
3267 | | - | |
3268 | | - | |
3269 | | - | |
3270 | | - | |
3271 | | - | |
3272 | | - | |
3273 | | - | |
3274 | | - | |
3275 | | - | |
3276 | | - | |
3277 | | - | |
3278 | | - | |
3279 | | - | |
3280 | | - | |
3281 | | - | |
3282 | | - | |
3283 | | - | |
3284 | | - | |
3285 | | - | |
3286 | | - | |
3287 | | - | |
3288 | | - | |
3289 | | - | |
3290 | | - | |
3291 | | - | |
3292 | | - | |
3293 | | - | |
3294 | | - | |
3295 | | - | |
3296 | | - | |
3297 | | - | |
3298 | | - | |
3299 | | - | |
3300 | | - | |
3301 | | - | |
3302 | | - | |
3303 | | - | |
3304 | | - | |
3305 | | - | |
| 3299 | + | |
| 3300 | + | |
| 3301 | + | |
| 3302 | + | |
| 3303 | + | |
3306 | 3304 | | |
3307 | 3305 | | |
3308 | 3306 | | |
| |||
3332 | 3330 | | |
3333 | 3331 | | |
3334 | 3332 | | |
| 3333 | + | |
| 3334 | + | |
| 3335 | + | |
| 3336 | + | |
| 3337 | + | |
| 3338 | + | |
3335 | 3339 | | |
3336 | 3340 | | |
3337 | 3341 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
0 commit comments