- brainstorm settled: declarative and automatic at boot; v1 verbs are add and delete only; data/seed migrations deferred to v2 - added fields zero-fill by kind: the grammar has no field-default syntax and v1 refuses to grow compiler surface for it - same-kind delete+add refuses as a disguised rename; retype and vanished classes refuse by name - schema lives in the log itself: WO_WAL_SCHEMA head record, written by fresh-log open and compaction; name-keyed diff also closes the silent cid-renumbering hole - story is iteration 12, board row added, db2-migrate prefix claimed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 072e007b144ff6689b6ff920ea10665900c1a2ef)
3.7 KiB
3.7 KiB
| track | iteration | status | readiness |
|---|---|---|---|
| databasev2 | 12 | in-progress | ready |
databasev2 12 — schema migrations: add + delete, declarative, auto on boot
Part of Story — databasev2: the database beyond RAM. Spec:
2026-08-31-schema-migrations-design.md.A
@tableclass is the schema and the log is the database, but nothing compares them: today an added or deleted field turns a healthyWO_DATAinto "corruption" at boot, and reordering declarations decodes rows into the wrong class without a sound. Changing the class IS a migration — this iteration makes the runtime perform the two unambiguous ones and refuse the rest by name.
Decisions (locked in brainstorm, 2026-08-31)
- Declarative, automatic, at boot — the binary carries the schema; no
migration files, no offline tool. Chosen over migration-code-in-language
and an offline
--migratestep. - v1 verbs: add + delete only. Added fields take the kind's zero value — the grammar has no field-default syntax and v1 does not grow compiler surface. Deleted fields' values are freed and physically dropped at the rename swap.
- Same-kind delete+add in one step refuses — indistinguishable from a
rename, and the wrong guess destroys data. Two-step deploy or v2's
@renamed_from. - Data/seed migrations are v2 — the deploy guide's trap 1 (seed only fills an empty table) stays documented, not fixed.
Tasks
| # | Task | State |
|---|---|---|
| 1 | WO_WAL_SCHEMA record: encode/decode, emitted by fresh-log open and by compaction |
⬜ |
| 2 | boot diff: peek head, match by class/field NAME, classify migrate / refuse / legacy | ⬜ |
| 3 | migration pass: old-shape replay through a field map, compaction-style rewrite, rename swap | ⬜ |
| 4 | refusals: retype, same-kind delete+add, vanished class — house-style messages | ⬜ |
| 5 | tests: the acceptance list, incl. keys-resident, legacy log, crash injection | ⬜ |
| 6 | close out: story, status board, deploy-guide note | ⬜ |
Acceptance criteria
- Given a log written with shape A and a binary with one added field, when it boots, then it runs, the field reads zero-valued, and the log head carries the new schema.
- Given one deleted field, when it boots, then dropped values are freed (ASan-clean) and absent from the rewritten log.
- Given delete+add of the same kind, when it boots, then refusal naming both fields and the two-step alternative.
- Given a retype or a vanished class with rows, when it boots, then refusal naming class, field and both shapes.
- Given pure declaration reordering, when it boots, then no migration and every row in its right class (closes the silent cid-renumbering hole).
- Given a keys-resident table across a migration, when read, then folds resolve through new offsets; a delta on a deleted field is gone.
- Given a legacy log (no schema record) with an unchanged shape, when it boots, then today's behaviour byte-for-byte, and the next compaction writes the record.
- Given a kill between new-log write and rename, when the next boot runs, then it re-migrates from the intact old log.
Out of scope
- Rename — v2, via
@renamed_from; v1 refuses the ambiguous diff. - Retype/conversions — refusal costs one explicit backfill program.
- Dropped-class verdicts — refusing keeps the data; deletion is a decision, not a migration.
@seed(n)data migrations — deferred by decision.- Field-default syntax — zero-fill plus app code covers it.
- Index migrations — indexes rebuild at boot; refusal is about flags.