Migrations
There are no hand-written migration files. The schema is code, versioned with @vN. Deploy a new schema head; each tenant applies it lazily on first touch.
Fingerprint apply-on-touch (Slice A)
Every tenant records the fingerprint of the last schema IR it applied (SchemaLog). On a data-touch path the engine compares that fingerprint to the deployed registry:
- Match → no-op (catalog-only check; no data-page reads).
- Differ → metadata-only apply: ensure
@indexB-trees exist, write the new fingerprint, persist. Studio'spending_migrationclears after that first touch.
Idle tenants never pay. Adding a field with a default, growing an enum, or adding @index does not rewrite pages — storage is dynamic; values are interpreted through the schema at read time.
Metadata vs @breaking
Most diffs are metadata-only. Destructive or narrowing changes require an explicit annotation plus a transform — the compiler refuses to infer data loss:
schema Orders @v4 @breaking(from: @v3) {
id: Id
total: Money<usd>
} transform { drop region }@breakingwithouttransform { … }is a parse error.- Today
transformsupportsdrop <field>(old values stay in page history; reads ignore the dropped field because it is absent from the schema shape). - Slice A still treats the drop as metadata: fingerprint bumps, indexes are ensured, pages are not rewritten for the drop.
What compacting does / doesn't today
| Operation | Today |
|---|---|
| Lazy apply-on-touch (fingerprint + indexes) | Yes — Slice A |
eelden compact / Studio compact | Yes — rewrites live rows for a tenant (space reclaim) |
| History-compacting rewrite that purges dropped fields from page history | Not yet — see Status |
Until history compaction for @breaking lands, dropped field bytes can remain in versioned page history even after the live schema no longer exposes them.
Drift vs migration
| Migration | Drift | |
|---|---|---|
| What moves | The declared schema (fingerprint) | Stored values disagreeing with the declared type |
| How you see it | pending_migration until first touch | Values typed unknown at stage boundaries |
| Rewrite? | Slice A never rewrites values to force conformance | Narrow with is / as — Types |
The two are deliberately independent: migrating the schema does not coerce stored data into the new shape.