Skip to content

fix(runtime,cli): a plain os dev self-heals safe drift and provisions the telemetry sibling on the standalone stack (#21733) - #21766

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-21733-standalone-dev-automigrate
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-21733-standalone-dev-automigrate

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

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 fix(auth): TOTP enrollment names the deployment app name as its issuer, not Better Auth #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 (better-sqlite3 → wasm auto-fallback doesn't cover the persistent-file / --artifact dev path (only the zero-config :memory: branch) #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

claude added 5 commits October 4, 2026 14:59
…emetry sibling on the standalone path

A plain `os dev` on a non-host config composes the standalone stack, whose
`default` datasource definition never carried `autoMigrate: 'safe'` — that
decision lived only inline in the CLI's config-load fallback. Move it to ONE
home in `@objectstack/runtime` (`devAutoMigrateConfig`) that both hosts and the
telemetry sibling read, keyed on an explicit `dev: true` so a one-shot command
boot never auto-applies whatever NODE_ENV says. Extract the telemetry provision
into `provisionTelemetryDatasource` and run it on the standalone path too.

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
…ence, host parity and the telemetry provision

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
…f-heals and provisions telemetry; production does not migrate

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
…l and telemetry sibling

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
… every spawn a readable env literal

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/runtime, touching 11 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/runtime/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/environment-routing.mdx (via createStandaloneStack (symbol, a top-level function))
  • content/docs/data-modeling/drivers.mdx (via createStandaloneStack (symbol, a top-level function))
  • content/docs/deployment/single-project-mode.mdx (via createStandaloneStack (symbol, a top-level function))
  • content/docs/plugins/index.mdx (via createStandaloneStack (symbol, a top-level function))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/src/index.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: os serve (command, 30 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 43 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b7a13c762fc39bfdf5b1d393b8334eb53a22e498 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from de86f6ffc84aa9f350bac87462ae51b521bd3651 — the merge of head 3e2810854492a6d573a1288bf26af756d5382059 into base b7a13c762fc39bfdf5b1d393b8334eb53a22e498, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin de86f6ffc84aa9f350bac87462ae51b521bd3651 && git checkout de86f6ffc84aa9f350bac87462ae51b521bd3651
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b7a13c762fc39bfdf5b1d393b8334eb53a22e498 3e2810854492a6d573a1288bf26af756d5382059 && git checkout -B drift-repro b7a13c762fc39bfdf5b1d393b8334eb53a22e498 && git merge --no-ff 3e2810854492a6d573a1288bf26af756d5382059

node scripts/docs-audit/affected-docs.mjs --json b7a13c762fc39bfdf5b1d393b8334eb53a22e498

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b7a13c762fc39bfdf5b1d393b8334eb53a22e498 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 2 commits October 4, 2026 17:08
…; DefaultDatasourcePluginOptions.dev arms the step-down only

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3e2810854492a6d573a1288bf26af756d5382059
Local-runs: none

PR #21766 for card #21733, judged on the net diff against the merge base b7a13c762f (14 files, +1143/−69, equal to the PR file list), the card body with all six comments (triage grade 5979802093, the claim 5981147390 as edited, round-1 report 5982374446, REWORK 5982402012, round-2 report 5982653479, ACCEPT 5982670474), the PR body, and the check-runs on this head. Every check-run is completed; the latest run per name is success, or skipped by its own path filter (Auto Label, Check PR Size, Packed-tarball smoke, Build Docs, Console Pin Gate). None failed, none is in progress. Lint and Repo Gates (the job that runs check:cli-test-child-env and check:pm-dispatch-gates), Test Core 1/6 through 6/6 plus the aggregate, the four Type Check jobs, Check Changeset and Governed Surface Queue Guard are all green. The diff touches no governed surface and the head repo is the base repo.

① Derived judgments

  1. One home for the condition — right. devAutoMigrateConfig(driver, dev) in packages/runtime/src/dev-auto-migrate.ts is the only place the dev self-heal is decided. A grep of autoMigrate over packages/cli/src and packages/runtime/src at the head finds code on exactly two lines, both in that module; every other hit is a comment. service-datasource reads cfg.autoMigrate as a passthrough, not a condition. The readers all spread the answer rather than restate it: the standalone default definition (standalone-stack.ts, config: { ...driverConfig, ...devSelfHeal }), every resolveStorageDefinition arm (mongodb, sqlite, sqlite-wasm, postgres, mysql, turso, and the dev-default :memory: sqlite arm, each spreading devAutoMigrateConfig(KIND, isDev)), and the telemetry sibling (devAutoMigrateConfig('sqlite', opts.dev)). The merge-base inline isDev ? { autoMigrate: 'safe' } : {} at storage-driver.ts:370 is gone.

  2. Kind set equals the spec contract — right. { sqlite, postgres, mysql } is exactly the set whose connection schema declares autoMigrate (sqlite.zod.ts:78, postgres.zod.ts:272, mysql.zod.ts:124; no other driver schema declares it). dev-auto-migrate.test.ts holds the helper equal to the contract over every BUILTIN_DRIVER_IDS entry through getDriverConfigJsonSchemaById, so a driver cannot silently gain a key it ignores or lose one it honours.

  3. No kind gains or loses relative to the host path before this PR, dev or production — right. At the merge base the host path gave 'safe' under isDev to the sqlite, postgres, mysql and dev-default :memory: arms and nothing to mongodb, sqlite-wasm, turso; nothing in production. After the diff the host path answers identically, and the untouched storage-driver.test.ts (lines 173-177 and 269) still pins that answer, green. The standalone path, which carried no autoMigrate for any kind before, now carries 'safe' for exactly sqlite, postgres and mysql under an explicit dev: true; dbDriver is the same canonical id each arm sets driverId from, so the decision keys on the kind the definition is built for. dev-self-heal-host-parity.test.ts drives both real entry points over 6 kinds × dev/production and holds all 12 rows equal. The retired memory engine is refused on both paths before either decision runs.

  4. The one-shot fence — right. createStandaloneStack reads the decision under cfg.dev === true; factoryDev (cfg.dev ?? NODE_ENV === 'development') still drives only the sqlite step-down. bootSchemaStack (schema-migrate.ts) passes no dev to createStandaloneStack, and all 17 one-shot commands that boot (migrate/*, meta/resync, secret/orphans, secret/rewrap, storage/orphans) go through it. resolveStorageDefinition has exactly one caller, serve.ts, so no one-shot command reaches the host-path injection either. The SQL driver's own default is autoMigrate ?? 'off' (sql-driver.ts:6344) and it ignores 'safe' under NODE_ENV=production. So under any NODE_ENV a one-shot boot carries no autoMigrate. schema-migrate.dev-self-heal-fence.integration.test.ts pins the non-deferred bootSchemaStack and os migrate plan on a staged safe drift under NODE_ENV=development, with the dev: true serving boot as the positive control on the same staged file.

  5. No existing caller that relied on the NODE_ENV default changes behaviour — right. Callers of createStandaloneStack at the head: serve.ts (passes dev: isDev), schema-migrate.ts (no dev), default-host.ts (forwards the caller's dev; its only caller is serve.ts, passing dev: isDev), and one test fixture (no dev). The standalone path never carried autoMigrate, so every no-dev caller is unchanged, and the step-down keeps its default. isDev in serve.ts is flags.dev || NODE_ENV === 'development', exactly the value the host path already used, so os serve under NODE_ENV=development is a dev serving boot on both paths as before. The docs embedder example (single-project-mode.mdx) passes no dev. One derived change on an existing public key: an embedder that already passes dev: true to createStandaloneStack now gets autoMigrate: 'safe' on a SQL default datasource. That is the documented dev behaviour reaching every dev host, not a new member; the StandaloneStackConfigSchema.dev TSDoc now states it, and the runtime minor covers it.

  6. Telemetry on the standalone path follows the same rule — right. provisionTelemetryDatasource is the one provision; serve.ts calls it through one closure from both the config-load fallback and the standalone branch, and it reads resolveTelemetryDbPath (unchanged) plus the shared self-heal decision. The standalone primary is standaloneTelemetryPrimary(input), which is resolveStandaloneDatabase(input).sqliteFile when driver === 'sqlite': the same pre-boot resolution createStandaloneStack builds its definition from, over the same input, with the same kind gate the host path's sqliteFilePath has (native sqlite only; the sqlite-wasm arm and the dev-default :memory: set none). On the createDefaultHostConfig branch the only field that differs between the input and what createStandaloneStack finally sees is artifactPath, resolved internally; both chains are explicit, then OS_ARTIFACT_PATH, then cwd/dist/objectstack.json, the artifact rung returns nothing for a missing file (resolve-project-database.ts:235), and the empty-boot stub declares no datasource, so the database answer cannot differ.
    The one widening outside dev: on the standalone path, os serve or os start with a file-backed native sqlite primary and OS_TELEMETRY_DB=PATH now provisions the sibling (with autoMigrate off, pinned by telemetry-datasource.provision.integration.test.ts) where before this PR the standalone path provisioned nothing in any mode. That is the documented rule (cli.mdx lines 246-251: dev default-on for a file-backed SQLite database, OS_TELEMETRY_DB=0 opts out, OS_TELEMETRY_DB=PATH in any mode including serve), it is what the host path already did, and it is explicit opt-in only, so the production default is unchanged. Judged right. The PR body's "Production boots and one-shot command boots do not change" is loose on this opt-in; the changeset, the text that ships, states the opt-in explicitly, so no correction to the shipped text is owed.

  7. Published surface is exactly the two runtime exports — right. @objectstack/runtime has one entry (exports["."], src/index.ts), and the diff adds exactly devAutoMigrateConfig and type DevAutoMigrateConfig there. StandaloneStackConfigSchema and DefaultDatasourcePluginOptions change TSDoc only; no exported schema or config key gains a member; no accept set moves. No API-report baseline exists for runtime to regenerate. @objectstack/cli publishes ., ./console and ./hook-body, and its src/index.ts re-exports commands only, so provisionTelemetryDatasource, ProvisionTelemetryDatasourceOptions and standaloneTelemetryPrimary in utils/telemetry-datasource.ts are not public.

  8. scripts/check-cli-test-child-env.mjs loosens nothing — right. The only edit is inside the self-test's exact-set population pin: one new path with a reason comment, and the case title's count (six to seven). Exact equality is retained, the ratchet baseline scripts/cli-test-child-env.baseline.json is untouched, and no DELIBERATE or DELIBERATE_REROUTE entry is added. The admitted spawner hands every child NODE_ENV: undefined through childEnv, which is what the gate measures. This is the gate's designed admission path, and Lint and Repo Gates is green on the head.

  9. Fences of the claim held — right. The drift and plan wording is unchanged; content/docs/deployment/cli.mdx is untouched and both of its promises are now true on the standalone path; packages/spec and packages/services/service-datasource are untouched; what 'safe' admits is untouched.

② Semver level

.changeset/21733-standalone-dev-self-heal.md declares @objectstack/runtime: minor and @objectstack/cli: patch, with Clause-②: yes (widening) line-initial on its own line; no major, no skip-changeset. Runtime: two new exports on its only entry, plus the new behaviour under the existing dev: true key — a widening, for which minor is the floor and the match. CLI: a fix restoring documented behaviour, adding no public surface — patch matches. The claim (as re-declared by the seat), the PR body and the changeset all carry Clause-②: yes (widening); Check Changeset is green on the head. Right.

③ Boundary flags

  • Dev open_questions: [] in both reports — nothing to answer.
  • Round-1 deviation, the runtime barrel export: re-declared yes (widening) with runtime minor — concur (②).
  • Round-1 deviation, the check-cli-test-child-env.mjs census edit: accepted — concur (① 8).
  • Round-1 deviation, no main merge before the PR: R3 done — merge commit 07745648c0 has parents c20d26dd76 and b7a13c762f, the first-parent chain to the head is intact, no rebase. Resolved.
  • Round-1 deviation, the restart pin at the .integration tier: accepted — the CLI's tier populations are derived by the vitest-tiers walk and Test Core is green on the head. Resolved.
  • Round-1 NOT MEASURED, check:pm-dispatch-gates: it runs in CI's Lint and Repo Gates job, green on this head, and the diff does not touch its inputs. Resolved by the check-run.
  • Round-2 deviation, R4 rode with R1 in 3e28108544: comment-only, present in the diff. Fine.
  • Round-2 deviation, PR body left for the seat: applied — the live body's second line is Clause-②: yes (widening). Resolved.
  • Round-2 deviation, full suites and ablation legs carried from round 1: the head's own check-runs (Test Core, the four Type Check jobs, Lint and Repo Gates) answer the suites; the ablation legs are the dev's measurement rather than a gate, and the three pins they reddened are in the diff. Accepted.
  • PR-body acceptance notes: the DefaultDatasourcePluginOptions.dev TSDoc (comment only, in the diff) and the artifact-fallback branch (verified in ① 6). Noted.
  • Escalations: none. One prose note, not escalated: the PR body's blanket "Production boots ... do not change" understates the explicit OS_TELEMETRY_DB=PATH opt-in now reaching the standalone path under serve (① 6); the changeset states it correctly.

Implemented-by: claude/issue-21733-standalone-dev-automigrate
Reviewed-by: session_016GiHYRmLSNWTfbX9gVQkpz

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 17:54
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 17:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 025008a Oct 4, 2026
50 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21733-standalone-dev-automigrate branch October 4, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants