Skip to content

feat(runtime,cli,core): ADR-0119 D2 boot reconciliation + os migrate resume (#4617); retire IDataEngine.batch? (#4618) - #4687

Merged
os-zhuang merged 4 commits into
mainfrom
claude/focused-mendel-5akrna
Aug 2, 2026
Merged

feat(runtime,cli,core): ADR-0119 D2 boot reconciliation + os migrate resume (#4617); retire IDataEngine.batch? (#4618)#4687
os-zhuang merged 4 commits into
mainfrom
claude/focused-mendel-5akrna

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Two independent changes, one per commit, sharing a branch because this session has one designated branch. Review them separately — either can be dropped without touching the other. Say the word and I'll split the second into its own PR.


1. ADR-0119 D2 deliverable 3 (#4617)

The runner and sys_migration_journal landed in #4668. This is the discovery channel — what makes an interrupted run findable by someone who does not already know it happened. With it, #4617 is complete.

MigrationRecoveryPlugin (@objectstack/runtime) — at kernel:ready, scans for runs that started and never concluded, warning per run: chunks committed, chunks with unknown outcome (chunk_started with no chunk_done), whether a compensation was left half-finished, and the exact command that acts. Owns the migration-plans registry service.

os migrate resume (@objectstack/cli) — lists interrupted runs (read-only default, per #2186's "a bare command must never mutate by surprise"), or acts on one with --run under confirmation. Exits non-zero when a run ends failed, so a scripted recovery cannot move on from a migration that needs a human.

MigrationPlanRegistry (@objectstack/core) — where a resume finds the plan.

Boot discovers, the CLI acts

The design decision in this change, and deliberate rather than incidental.

Resuming is a large, irreversible, potentially hour-long write against production data. Doing that as an unrequested side effect of a process starting is the kind of behaviour an operator finds out about from a graph. It is also not always possible at boot: a resume needs the plan's live callbacks, and the package owning them may not be loaded in whichever process happened to restart first.

So boot surfaces the run and names the command; the command acts, under explicit operator intent. D2's per-plan onCrash policy still decides what acting means — resume forward, or unwind — it just does not decide when, and "when" is the part a human should own.

Deferring is safe precisely because of the runner's re-entrancy: started ∧ ¬done is durable, so a run stays exactly as recoverable an hour later as it was at boot. Nothing decays while the operator decides.

Why a plan registry exists

A journal cannot hold a plan. forward/compensate are functions and load() reads the live database, so none of it crosses a process boundary — which is why the journal records the plan hash, not the plan. Recovery needs the plan handed back by the code that owns it, and migration-plans is that seam.

A run whose plan no loaded package registers is reported, never silently skipped. "Nothing to resume" and "the code that owns this run is not here" are different facts, and only one is safe to ignore.

Degradation, deliberate in both directions

No engine or no journal object (a lean kernel) → skipped in silence: such a kernel has no interrupted runs, and a warning there would train operators to ignore this plugin's output — the one thing it cannot afford. A scan that fails is reported, because "I could not check" is not "there is nothing to find".

Verification

11 new runtime tests pin the split (boot writes nothing to the journal), the three states an operator must tell apart (clean / interrupted / half-unwound), and both degradation paths. 2 new core tests cover the registry, including that a re-registered id replaces rather than accumulates — with the journal's plan-hash check as the backstop against resuming a changed plan.


2. Retire IDataEngine.batch? (#4618)

batch? was declared on IDataEngine for as long as that contract existed and was never implemented by any engineObjectQL has no batch method, and there is no other engine in the tree — and never called: DataEngineRequest was imported by exactly one file, the contract declaring it.

Its whole specification was a three-word doc comment, which settles nothing about partial failure, ordering, cross-object references, rollback scope, or what transaction: false meant. Its neighbours getDefaultDriverName? / getDriverByName? earn their optionality by naming an implementer and a probing caller; this one named nothing.

Two tells that nobody ever designed against it:

  • DataEngineBatchRequestSchema.requests nested the request union recursively — a batch could contain batches — with no statement about what that meant for ordering or rollback.
  • The only test built an ad-hoc object literal carrying a batch property and asserted it was defined. That pins the type; it could not fail while the declaration existed, and would have passed unchanged for the member's whole life with no engine implementing it.

What it claimed is now covered by members that are real — ADR-0119 D1 made transaction contract-reachable, D4 made batchData's atomic honest, and the wire batch has always been POST {basePath}/batch. So this deletes a false affordance, not a capability (ADR-0049).

Scope calls

  • The wire batch is untouched. It validates with CrossObjectBatchRequestSchema / BatchUpdateRequestSchema from api/batch.zod.ts — a different schema that never had anything to do with the removed one.
  • DataEngineRequestSchema stays, minus its batch arm. Every remaining arm is now unread too (there is no Virtual Data Engine implementation, only this schema describing one) — flagged in-file and worth its own issue, but retiring a published wire protocol is a different decision from retiring batch? and does not belong in a change whose title promised something narrower.
  • Deliberately no retiredKey() tombstone. A tombstone delivers its prescription through a parse, and nothing ever parsed this schema — the playbook's third route ("a prescription nobody can receive is noise"). Its three authorable-surface.json baseline lines, its json-schema.manifest.json entry, and the now-stale docs-import-surface.baseline.json line are dropped here, deliberately. The enforced channel is tsc.

Registry entry data-engine-batch-retired carries the FROM → TO into the upgrade guide and spec-changes.json.


Verification (both commits)

  • pnpm typecheck122/122. pnpm lint clean. check:adr-anchors OK. check:generated — 8/8 current.
  • Full suites: core 458, runtime 1086, cli 705, spec 7307, platform-objects 258, metadata-protocol 216.
  • os migrate resume verified present in the built CLI with its flags (--database-url, --run, --yes, --json); oclif discovers it by pattern from dist/commands, so there is no manifest to regenerate.

One note from doing this: gen:api-surface reads dist, and an earlier regeneration here ran against an incomplete one — it silently dropped an unrelated ActivationEvent (type) export from ./studio and would have ratcheted a baseline exemption in for it. Caught and reverted by diffing the generated files against main; the artifacts in this PR come from a clean rebuild and contain only batch-related deletions. Worth knowing before the next spec removal.

Refs: ADR-0119 D2/D3, #4617, #4618, #4668, #4612, ADR-0049, ADR-0078


Generated by Claude Code

claude added 2 commits August 2, 2026 16:51
`batch?` was declared on `IDataEngine` for as long as that contract existed
and was never implemented by any engine — ObjectQL has no `batch` method and
there is no other engine in the tree — and never called: `DataEngineRequest`
was imported by exactly one file, the contract declaring the member.

Its whole specification was a three-word doc comment, which settles nothing
about partial failure, ordering, cross-object references, rollback scope, or
what `transaction: false` meant. Its neighbours `getDefaultDriverName?` /
`getDriverByName?` earn their optionality by naming an implementer and a
probing caller; this one named nothing.

The tell that nobody designed against it: `DataEngineBatchRequestSchema`
nested the request union recursively — a batch could contain batches — with
no statement about what that meant. The only test was a type pin asserting
the property was defined, which could not fail while the declaration existed.

What it claimed is now covered by members that are real: ADR-0119 D1 made
`transaction` reachable through the contract, D4 made `batchData`'s `atomic`
honest, and the wire batch has always gone through `POST {basePath}/batch`.
So this deletes a false affordance, not a capability (ADR-0049).

- Remove the member and the `DataEngineRequest` import from the contract.
- Remove `DataEngineBatchRequestSchema` and its arm from the request union.
  Keep the union itself: every remaining arm is now unread too, but retiring
  a published wire protocol is a different decision, tracked separately.
- Drop the type-pin test and the schema's self-parsing suite.
- Registry entry `data-engine-batch-retired` carries the FROM → TO for the
  upgrade guide and spec-changes.json.

Deliberately no `retiredKey()` tombstone: a tombstone delivers through a
parse, and nothing ever parsed this schema. Its three authorable-surface
baseline lines, its json-schema.manifest entry and the stale
docs-import-surface baseline line are dropped here, deliberately. The
enforced channel is tsc.

Refs: ADR-0119 D3, ADR-0049, ADR-0078, #4618, #4612

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx
… resume` (#4617)

Completes ADR-0119 D2. The runner and sys_migration_journal landed in #4668;
this is the discovery channel that makes an interrupted run findable by
someone who does not already know it happened.

- `MigrationRecoveryPlugin` (@objectstack/runtime) — at kernel:ready, scans
  for runs that started and never concluded and warns per run: chunks
  committed, chunks with UNKNOWN outcome (chunk_started with no chunk_done),
  whether a compensation was left half-finished, and the command that acts.
  Also owns the `migration-plans` registry service.
- `os migrate resume` (@objectstack/cli) — lists interrupted runs (read-only
  default, per #2186), or acts on one with --run under confirmation. Exits
  non-zero when a run ends `failed`, so a scripted recovery cannot move on
  from a migration that needs a human.
- `MigrationPlanRegistry` (@objectstack/core) — where a resume finds the plan.

Boot discovers, the CLI acts. Resuming is a large, irreversible, potentially
hour-long write against production data; doing that as an unrequested side
effect of a process starting is the kind of behaviour an operator finds out
about from a graph. It is also not always possible at boot — a resume needs
the plan's live callbacks, and the package owning them may not be loaded in
whichever process happened to restart first. The per-plan `onCrash` policy
still decides WHAT acting means; it does not decide WHEN, and "when" is the
part a human should own. Deferring is safe because of the runner's
re-entrancy: `started ∧ ¬done` is durable, so a run stays exactly as
recoverable an hour later as it was at boot.

The registry exists because a journal cannot hold a plan: forward/compensate
are functions and load() reads the live database, so none of it crosses a
process boundary — hence the journal stores the plan HASH. A run whose plan
no loaded package registers is REPORTED, never silently skipped: "nothing to
resume" and "the code that owns this run is not here" are different facts.

Degradation is deliberate in both directions. No engine or no journal object
(a lean kernel) → skipped in silence, because such a kernel has no
interrupted runs and a warning there would train operators to ignore this
plugin's output. A scan that FAILS is reported — "I could not check" is not
"there is nothing to find".

11 new runtime tests pin the split (boot writes nothing to the journal), the
three states an operator must tell apart, and both degradation paths; 2 new
core tests cover the registry.

Refs: ADR-0119 D2, #4617, #4668, ADR-0078

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 6:39pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/xl labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/core, @objectstack/runtime, @objectstack/spec.

126 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, packages/runtime, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/runtime, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/core, packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli, @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/core, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/core, packages/runtime, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/cli, @objectstack/core, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/core, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/core, @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v15.mdx (via @objectstack/core)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

claude added 2 commits August 2, 2026 17:24
…removal moved (#4618)

CI's "Spec property liveness" job runs four gates, not one. `check:liveness`
itself was fine; the two that broke are the ledgers that track the SHAPE of
the code the batch removal changed.

- `variant-docs.json`: the outer `DataEngineRequestSchema` union carried a
  `batch` variant, and a SECOND entry described the inner union inside
  `DataEngineBatchRequestSchema.requests` — the same member set minus `batch`.
  With the batch schema gone the inner union no longer exists and the outer
  one narrowed to exactly the inner one's old key, so the two entries collapse
  into one. Keeping both would have left an entry whose union is gone, which
  is what the gate reported.
- The strictness ledger's `data-engine.zod.ts` row (14 → 13 `z.object(` sites)
  and the `data/` section header it sums into (162 → 161).

Both are hand-maintained maps of the code, and the point of the gates is that
a map which drifts is worse than none because it gets followed. Verified by
running all 17 `@objectstack/spec` check:* scripts, not just the four in the
failing job.

Refs: #4618, ADR-0119 D3

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx
…-5akrna

# Conflicts:
#	docs/protocol-upgrade-guide.md
#	packages/spec/spec-changes.json
#	packages/spec/src/migrations/registry.ts
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 protocol:data size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants