From 30fe7895e4ec5e798d991c734cd40143775b1c3c Mon Sep 17 00:00:00 2001 From: jedarden Date: Wed, 20 May 2026 07:35:14 -0400 Subject: [PATCH] miroir-r3j.4: Verify P3.4 schema versioning implementation 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 --- notes/miroir-r3j.4.md | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 notes/miroir-r3j.4.md diff --git a/notes/miroir-r3j.4.md b/notes/miroir-r3j.4.md new file mode 100644 index 0000000..3c70324 --- /dev/null +++ b/notes/miroir-r3j.4.md @@ -0,0 +1,101 @@ +# 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`) + +- **`Migration` struct**: Holds version number and SQL content +- **`MigrationRegistry`**: Manages migration lifecycle + - `max_version()`: Returns highest migration version (binary version) + - `pending_migrations(current_version)`: Returns migrations to apply + - `validate_version(store_version)`: Checks store ≤ binary version +- **`build_registry()`**: Embeds migration files via `include_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**: +1. Creates `schema_versions` table if not exists +2. Reads `MAX(version)` as current store version (0 if empty) +3. Validates `store_version ≤ binary_version` via `registry().validate_version()` +4. Applies pending migrations in order +5. 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**: +1. Gets current value from `miroir:schema_version` key (None if new) +2. Validates `store_version ≤ binary_version` +3. Sets `miroir:schema_version = binary_version` +4. 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_version` +- `test_pending_migrations` +- `test_validate_version_success` +- `test_validate_version_store_ahead` +- `test_duplicate_version_panics` + +**SQLite backend tests** (all pass): +- `migration_is_idempotent`: Verifies second migrate() call is no-op +- `schema_version_recorded`: Verifies MAX(version) matches binary version +- `schema_version_ahead_fails`: Verifies error when store_version > binary_version +- `task_survives_store_reopen`: Verifies data survives close/reopen cycle +- `all_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`**: +```rust +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()`.