11 KiB
Tasks — Migration adapters completion
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
Audit Note (2026-05-02)
Status: DEFERRED. None of the proposed additive surfaces exist: no migrationEvents shared bus, no MigrationLockLike / MigrationStoreLike interfaces (existing MigrationLockAdapter / MigrationStoreAdapter shapes are untouched), no BatchTargetProgress / BatchTargetStatus / TargetConfigFactory types, no GetAllRecordsFn, no ValidationError alias, no registerMigrationErrorCodes() named export. Lifecycle event-key aliases (migration:started, migration:applied, migration:completed, migration:rolled-back) are absent — code only emits migration:before / migration:after / migration:failed / migration:plan. 0% of work is done.
1. Spec authoring
- 1.1 Read
../suaveplan/enterprise/migration.mdend-to-end - 1.2 Read
openspec/specs/migration/spec.md(does not exist yet — confirm and start fresh) - 1.3 Read
openspec/AGENTS.mdSections 13 and 14 - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.5 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.6 Draft
specs/migration/spec.mdfromopenspec/templates/spec-delta.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles
2. Implementation — lock/migration-lock
- 2.1 Create
packages/database/migration/src/lock/migration-lock/migration-lock.tsexporting theMigrationLockLikeinterface (acquire(lockId, ttlMs),release(lockId),extend(lockId, ttlMs)) - 2.2 Re-export the existing
MigrationLockAdapterfrom this module under the same name for back-compat - 2.3 Add a
migration-lock.mdsibling doc covering both interface contracts (≥200 words, basic + advanced examples) - 2.4 Update
MemoryMigrationLockto implementMigrationLockLikealongside the existingMigrationLockAdaptershape (additive methods only) - 2.5 Update
lock/index.tsto exportMigrationLockLikefrom the package root viaindex.ts
3. Implementation — store/migration-store
- 3.1 Create
packages/database/migration/src/store/migration-store/migration-store.tsexporting theMigrationStoreLikeinterface (list,append,markFailed,markApplied,getLatestVersion) - 3.2 Re-export the existing
MigrationStoreAdapterfrom this module under the same name for back-compat - 3.3 Add a
migration-store.mdsibling doc (≥200 words, basic + advanced examples) - 3.4 Update
MemoryMigrationStoreto implementMigrationStoreLikealongside the existingMigrationStoreAdaptershape (additive methods only) - 3.5 Update
store/index.tsto exportMigrationStoreLikefrom the package root viaindex.ts
4. Implementation — migration-events
- 4.1 Create
packages/database/migration/src/migration-events/migration-events.tsexportingmigrationEvents: EventBus<MigrationEventMap>pluscreateMigrationEvents()factory - 4.2 Extend
MigrationEventMapintypes/migration.types.tswith the lifecycle keys:"migration:started","migration:applied","migration:failed","migration:completed","migration:rolled-back"(additive — preservemigration:before/migration:after/migration:plan) - 4.3 Update
MigrationExecutorto emit both legacy AND canonical lifecycle keys for one release; add// genesis: legacy emit removed in follow-up changecomment (no@deprecatedper CLAUDE.md rule) - 4.4 Add a
migration-events.mdsibling doc (≥200 words, basic + advanced examples) covering the event-bus subscription pattern - 4.5 Wire
migrationEventsintoindex.tsexports
5. Implementation — batch types
- 5.1 Add
BatchTargetStatus = "pending" | "running" | "completed" | "failed"type alias totypes/migration.types.ts - 5.2 Add
BatchTargetProgressinterface (targetId,status,results,error?,startedAt?,completedAt?) - 5.3 Add
TargetConfigFactory = (targetId: string) => MigrationRunnerConfig | Promise<MigrationRunnerConfig>type alias - 5.4 Update
BatchMigratorConfigto accept either the existingtargetsarray OR aconfigFactory+targetIdspair (additive) - 5.5 Update
BatchMigratorto emitBatchTargetProgressupdates via an optionalonProgress?: (progress: BatchTargetProgress) => voidcallback - 5.6 Re-export the new types from
index.ts - 5.7 Update
batch-migrator.mdsibling doc to cover the new factory pattern
6. Implementation — status GetAllRecordsFn
- 6.1 Add
GetAllRecordsFn = () => Promise<MigrationRecord[]>type alias tostatus/migration-status.ts - 6.2 Update
MigrationStatusDashboardconstructor to accept an optionalgetAllRecords?: GetAllRecordsFnparameter; falls back tostore.list()when absent - 6.3 Re-export
GetAllRecordsFnfromindex.ts - 6.4 Update
migration-status.mdsibling doc to describe the injection point
7. Implementation — ValidationError re-export
- 7.1 Add
export type { MigrationValidationError as ValidationError }totypes/migration.types.ts - 7.2 Update
index.tsto export bothMigrationValidationErrorandValidationError
8. Implementation — MIGRATION_CODES registration
- 8.1 Confirm the side-effect import in
index.tsalready triggersdefineCodesregistration on load - 8.2 Export an idempotent
registerMigrationErrorCodes()function fromerrors/migration-error.tsfor downstream consumers that bundle without side-effect imports - 8.3 Re-running
registerMigrationErrorCodes()MUST NOT throw — guard with the registry's existing dedup - 8.4 Re-export
registerMigrationErrorCodesfromindex.ts
9. Tests
- 9.1
lock/migration-lock/migration-lock.test.ts— acquire returns true on uncontested lockId, false when contested; release frees lockId; extend refreshes TTL; multiple lockIds isolated; expired locks auto-release - 9.2
lock/migration-lock/migration-lock.spec-conformance.test.ts— pinsMigrationLockAdapterback-compat alias; verifiesMemoryMigrationLocksatisfies BOTH interfaces - 9.3
store/migration-store/migration-store.test.ts— empty list initial; append → list returns single; markApplied flips status; markFailed records error; getLatestVersion returns latest by id sort - 9.4
store/migration-store/migration-store.spec-conformance.test.ts— pinsMigrationStoreAdapterback-compat alias; verifiesMemoryMigrationStoresatisfies BOTH interfaces - 9.5
migration-events/migration-events.test.ts— every lifecycle key emits with the correct payload shape; multiple subscribers receive in registration order - 9.6
migration-events/migration-events.spec-conformance.test.ts— listener throwing does not abort emit loop; off() removes specific listener; clear() removes all - 9.7
batch/batch-migrator/batch-migrator.test.ts—BatchTargetProgresstransitions pending → running → completed; failure transitions to failed and isolates from siblings;onProgresscalled per transition - 9.8
status/migration-status/migration-status.test.ts—GetAllRecordsFninjected source returned fromgetHistory(); absent factory falls back tostore.list() - 9.9
errors/migration-error.spec-conformance.test.ts—ErrorCodes.has("MIGRATION.LOCK_HELD")is true after package import; secondregisterMigrationErrorCodes()call does not throw - 9.10 Every
it(...)in 9.1–9.9 carries{ timeout: 5000 }(or 30000 for genuinely async)
10. Documentation
- 10.1 Sibling
.mdfor every new implementation.ts(migration-lock.md,migration-store.md,migration-events.md); each ≥200 words with basic + advanced examples - 10.2 Update
packages/database/migration/README.md— add Modules links for the three new files; add Quick-start snippet showingmigrationEventssubscription - 10.3 Update
CLAUDE.mdpackage listing line for@suaveplan/migrationif the public-surface description has changed
11. Pre-archive verification (the 5-gate triple)
- 11.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the whole monorepo - 11.2
bunx turbo lint typecheck test --filter=@suaveplan/migration— green - 11.3
bunx turbo test:coverage --filter=@suaveplan/migration— 100/100/100/100 - 11.4
bun scripts/openspec-conformance.ts @suaveplan/migration— exit 0, noerrorfindings - 11.5
bunx turbo build --filter=@suaveplan/migration— green
12. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 12.1 Generics threaded;
expectTypeOfassertions on every literal-type claim (lifecycle key strings,BatchTargetStatusunion) - 12.2 Zod schemas wired at every declared boundary (
MigrationLockLike/MigrationStoreLikeconsumers do not need Zod — interfaces are TS-only; verify schemas are still wired inMigrationRunnerOptionsSchemaetc.) - 12.3 Every error code in
errors/migration-error.tsthrown by at least one production path - 12.4 Strict-mode behaviors tested both on and off (lock TTL expiry honoured under
enableTtlEnforcement: trueand bypassed whenfalse) - 12.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider - 12.6 Lifecycle dedup tested where claimed (idempotent
registerMigrationErrorCodes) - 12.7 No retroactive tick — every box above reflects work done in this change
13. Archive
- 13.1 Fold spec delta into
openspec/specs/migration/spec.md(will be the first time this canonical spec exists) - 13.2 Move
openspec/changes/migration-adapters-completion/→openspec/archive/<yyyy-mm-dd>-migration-adapters-completion/ - 13.3 Refresh
MANIFEST.mdif package set or deps changed (no expected change here)