Skip to content

Explicit owner-driven migration, replacing lazy migrate-on-read/commit-on-write#87

Merged
cuibonobo merged 1 commit into
mainfrom
claude/issue-47-design-review-3nw4ha
Jul 12, 2026
Merged

Explicit owner-driven migration, replacing lazy migrate-on-read/commit-on-write#87
cuibonobo merged 1 commit into
mainfrom
claude/issue-47-design-review-3nw4ha

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

Implements #47: replaces the lazy migration model (transform-on-read + commit-on-next-write) with explicit, owner-driven migrateAll(), plus the mechanical fixes the RFC calls out.

  • migrateAll() is the only thing that ever changes a record's disk-state typeId. update() no longer silently commits a pending migration as a side effect of an unrelated content edit — it validates the merge-patched content against the record's own current stored type and writes back at the same typeId.
  • get()/query() return records exactly as stored by default, fixing the spec/impl divergence where query() never migrated but get() did (the bug that made query({ filter: { typeId } }) silently miss not-yet-migrated records). presentAt: 'latest' is an explicit, never-persisted opt-in supported by both.
  • Stale-writer signal: presentAt: 'latest' throws StackMigrationError (replacing a console.warn + raw record) when a record's version can't be reconciled with what this app instance has registered/defined — covering both a stale binary reading a newer record and an app that knows a later type exists but has no migration path to reach it.
  • Filter.baseId matches every version of a type family, resolved client-side via listTypes() before hitting the adapter/wire layer — no adapter or wire changes needed.
  • Grants match by baseId instead of exact typeId, so a version bump no longer orphans existing grants.
  • RecordVersion gains typeId, threaded through sqlite-shared (DDL + row mapper), wire-types, and adapter-api.
  • migrateAll() validates each migrated record's content against the target schema before committing, aborting immediately on the first failure rather than partially applying a buggy migration function.

Also updates docs/spec.md's Type migrations, Queries, Grant, and Versions sections, and adds a new "Additive evolution within a version" section capturing the governance rule from the issue's two amendment comments (when a version bump is warranted vs. additive-in-place).

Test plan

  • pnpm -r build — all 9 packages build clean
  • pnpm -r typecheck — clean
  • pnpm -r test — all packages pass (core: 278 tests, including new coverage for presentAt: 'latest', baseId filtering, grant-by-family matching, and migrateAll() abort-on-invalid-migration)
  • pnpm -r lint — clean
  • pnpm format:check — clean

🤖 Generated with Claude Code


Generated by Claude Code

…n-read/commit-on-write (#47)

Implements the #47 RFC: migrateAll() is now the only thing that ever
changes a record's disk-state typeId. get()/query() return records
exactly as stored by default; presentAt: 'latest' is an explicit,
never-persisted opt-in supported by both (fixing the get()/query()
migration divergence). update() validates against the record's own
current type and never migrates as a side effect.

Mechanical fixes bundled with the model change:
- Filter.baseId matches every version of a type family, resolved
  client-side via listTypes() before hitting the adapter/wire layer.
- Grants match by baseId instead of exact typeId, so a version bump
  no longer orphans existing grants.
- RecordVersion gains typeId, threaded through sqlite-shared,
  wire-types, and adapter-api.
- migrateAll() validates each migrated record against the target
  schema before committing, aborting immediately on the first
  failure rather than partially applying a buggy migration.
- presentAt: 'latest' throws StackMigrationError (replacing a
  console.warn + raw record) when a record's version can't be
  reconciled with what this app instance has registered/defined —
  covering both a stale binary reading a newer record and an app
  that knows a later type exists but has no path to reach it.

Updates docs/spec.md's Type migrations, Queries, Grant, and Versions
sections to match, and adds a Additive evolution within a version
section documenting the governance rule from the issue's amendments.
@cuibonobo cuibonobo merged commit 6a1d362 into main Jul 12, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/issue-47-design-review-3nw4ha branch July 12, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants