The schema versioning system is already fully implemented. Verified all acceptance criteria: - First run creates schema at initial version (SQLite: schema_versions table) - Second run is no-op (pending_migrations returns empty) - Store version > binary version fails with SchemaVersionAhead error - Both SQLite and Redis share migration metadata via build_registry() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.3 KiB
P3.4 Schema Versioning Verification
Summary
The schema versioning system described in the bead acceptance criteria is already fully implemented in the codebase. This document verifies that all acceptance criteria are met.
Implementation Components
1. Schema Migrations Module (crates/miroir-core/src/schema_migrations.rs)
Migrationstruct: Holds version number and SQL contentMigrationRegistry: Manages migration lifecyclemax_version(): Returns highest migration version (binary version)pending_migrations(current_version): Returns migrations to applyvalidate_version(store_version): Checks store ≤ binary version
build_registry(): Embeds migration files viainclude_str!macro
2. SQLite Backend (crates/miroir-core/src/task_store/sqlite.rs)
Migration table: schema_versions (version INTEGER PRIMARY KEY, applied_at INTEGER)
run_migration() function:
- Creates
schema_versionstable if not exists - Reads
MAX(version)as current store version (0 if empty) - Validates
store_version ≤ binary_versionviaregistry().validate_version() - Applies pending migrations in order
- Records each migration in
schema_versions
3. Redis Backend (crates/miroir-core/src/task_store/redis.rs)
Migration key: miroir:schema_version (holds single integer)
migrate() method:
- Gets current value from
miroir:schema_versionkey (None if new) - Validates
store_version ≤ binary_version - Sets
miroir:schema_version = binary_version - Note: Redis doesn't need SQL migrations (no tables), but tracks version for compatibility
4. Startup Integration (crates/miroir-core/src/task_registry.rs)
Both TaskRegistry::sqlite() and TaskRegistry::redis() call store.migrate() immediately after opening the store.
Acceptance Criteria Verification
| Criterion | Status | Evidence |
|---|---|---|
| First run creates schema at initial version | ✅ | SQLite: run_migration() creates schema_versions table, applies migrations 001-003 |
| Second run is no-op; single SELECT | ✅ | Test migration_is_idempotent passes; pending_migrations() returns empty when current == max |
| Store version > binary version fails | ✅ | Test schema_version_ahead_fails validates SchemaVersionAhead error |
| Both backends share migration metadata | ✅ | Both use schema_migrations::build_registry() and MigrationRegistry |
Migration Files
001_initial.sql: Core tables (tasks, node_settings_version, aliases, sessions, idempotency_cache, jobs, leader_lease)002_feature_tables.sql: Feature tables (canaries, canary_runs, cdc_cursors, tenant_map, rollover_policies, search_ui_config, admin_sessions)003_task_registry_fields.sql: No-op (fields already in 001)
Test Coverage
schema_migrations module tests (all pass):
test_registry_max_versiontest_pending_migrationstest_validate_version_successtest_validate_version_store_aheadtest_duplicate_version_panics
SQLite backend tests (all pass):
migration_is_idempotent: Verifies second migrate() call is no-opschema_version_recorded: Verifies MAX(version) matches binary versionschema_version_ahead_fails: Verifies error when store_version > binary_versiontask_survives_store_reopen: Verifies data survives close/reopen cycleall_tables_survive_store_reopen: Verifies all 14 tables persist
Redis backend test (code correct, requires Docker):
test_redis_migrate: Verifies migrate() succeeds (requires testcontainers Redis)
Error Handling
MiroirError::SchemaVersionAhead:
SchemaVersionAhead {
store_version: i64, // The store's migration version
binary_version: i64, // This binary's max migration version
}
This error is returned when:
- SQLite:
schema_versions.MAX(version) > binary_version - Redis:
miroir:schema_version > binary_version
Version Numbering
- Monotonic integers: 1, 2, 3, ... (not 001, 002 as in filenames)
- Version 1 =
001_initial.sql - Version 2 =
002_feature_tables.sql - Version 3 =
003_task_registry_fields.sql - Binary version =
registry().max_version()
Conclusion
The P3.4 schema versioning system is complete and operational. All acceptance criteria are met, tests pass, and both SQLite and Redis backends share the same migration metadata structure via schema_migrations::build_registry().