Commit a0fd273
committed
feat(core,platform-objects,spec): ADR-0119 D2 — the migration-journal runner (#4617)
A migration killed mid-run is now either resumable to completion or
compensable to clean, with journal rows proving which.
ADR-0119 D1 made `engine.transaction()` contract-reachable, which answers
multi-write atomicity that fits in one transaction. Migration-class work does
not fit: a million-row backfill cannot hold one write-lock, driver-memory's
`beginTransaction` deep-clones the whole database, `transaction()` binds the
default driver only, and a KILLED process defeats in-process rollback
entirely. So the unit of atomicity is the chunk, and durability across chunks
is a journal.
- `runMigrationJournal` (@objectstack/core): preflight dry-run across every
step before any step writes; chunked writes each inside `engine.transaction()`;
LIFO compensation newest-first on failure; re-entrant forward recovery under
a per-plan `onCrash` policy; at-least-once with an `attempt` counter, reusing
bulk-write.ts's delivery contract rather than re-deriving it.
- `sys_migration_journal` (@objectstack/platform-objects): rows keyed
(run_id, seq) under a unique index, registered unconditionally beside
sys_migration so recovery is discoverable with zero host wiring (ADR-0078).
Distinct in grain from sys_migration, which holds one verdict per named
migration; this holds many rows per run.
- Row contract + object-name constant in @objectstack/spec/system, so core's
runner writes the journal without depending on platform-objects.
The invariant carrying the design: `chunk_done(i)` is written INSIDE the
chunk's transaction so `done ⇔ committed` holds by construction, while
`chunk_started(i)` is written autonomously before it. That asymmetry gives
`started ∧ ¬done` exactly one meaning — outcome unknown — which is the only
state a crash leaves and the only state recovery reasons about.
The runner refuses rather than degrades: no rollback capability, a failed
preflight, an uncompensable plan declaring onCrash:'compensate', or a resume
whose plan hash disagrees with the journal. A compensation failure halts and
is journalled, and the run ends `failed` rather than `compensated` — a
database in a state no clean story covers must not be reported as a tidy
rollback.
`engineCanRollBack` is now shared: the two-level probe was the same condition
in this runner and in batchData's atomic gate, and two copies drift by one
clause and leave one caller believing it has atomicity it does not have. It
moves to @objectstack/core as a type predicate; metadata-protocol imports it.
Boot reconciliation and `os migrate resume` land separately; the discovery
primitive they consume, `findInterruptedRuns`, is exported here.
Docs: ADR-0118 (plugin-reachable transactions) is renumbered ADR-0119. It
merged a day after an unrelated ADR-0118 (非用户 actor 的平台契约), and the
earlier merge holds the number. Its Status line now cites the implementing
PR and its tests instead of a dangling "this PR", and records that D2/D3
remain unimplemented.
Refs: ADR-0119 D2, #4617, #4612, ADR-0034, ADR-0060, ADR-0078, ADR-0117 D8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx1 parent 462b713 commit a0fd273
25 files changed
Lines changed: 1607 additions & 53 deletions
File tree
- .changeset
- content/docs/references/system
- docs/adr
- packages
- core/src
- utils
- metadata-protocol/src
- objectql/src
- platform-objects/src
- system
- spec
- src
- api
- contracts
- system
- constants
- scripts
| 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 | + | |
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
38 | | - | |
| 37 | + | |
| 38 | + | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| |||
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
156 | 177 | | |
157 | 178 | | |
158 | 179 | | |
| |||
Lines changed: 3 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
33 | 38 | | |
34 | 39 | | |
35 | 40 | | |
| |||
0 commit comments