SQLite Online Vacuum Snapshot & Disaster Recovery Rehearsal #36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implement the robust SQLite online snapshot mechanism (
VACUUM INTO ?or SQLite Online Backup API) to an isolated staging directory, write atomic snapshot manifests with SHA-256 integrity checksums, and construct an automated disaster recovery rehearsal test suite verifying schema integrity, foreign key consistency, operation reconciliation, and temporal observation gap reporting when restored alongside differently-timed external calendar backups (Scenarios D10, D11, D12).Background & Technical Rationale
The WAL Backup Pitfall (Scenario D10)
In SQLite Write-Ahead Logging (
WAL) mode (PRAGMA journal_mode=WAL), transactions are written to the-walsidecar file and flushed according toPRAGMA synchronous=FULL. If a backup tool or operator executes a standard file copy of the main database (cp life.sqlite3 /backups/), the resulting file will be inconsistent, corrupted, or missing recently committed transactions whose pages reside exclusively in the WAL.Copying the live database file alone is an explicitly rejected operational procedure. Valid snapshots must be produced using SQLite's native Online Backup API (
sqlite3_backup_*) or an atomic, consistentVACUUM INTO '<staging_path>'query executed over an active database connection. This produces a single, self-contained, clean SQLite database file containing all committed transactions without interrupting concurrent read queries.Multi-System Asynchrony & Observation Gaps (Scenario D11)
The
lifesystem architecture intentionally couples an append-only local SQLite database (storing operations, proposals, and timeline events) with an external CalDAV store (Radicale) and a semantic memory bank (Hindsight).Because atomic distributed transactions across disparate systems do not exist, SQLite and Radicale backups will inevitably have different capture timestamps:
When executing disaster recovery:
coverage: [{state: "incomplete", ...}]). History is not assumed to be zero.preparedorunknownoperations, inspect Radicale state using known operation IDs, and retain uncertain attribution where conclusive evidence is missing.Integrity Verification & Privacy Erasure (Scenario D12)
Restoration must never assume a snapshot file is valid. Before any service process is permitted to serve traffic or append new operations:
PRAGMA integrity_checkandPRAGMA foreign_key_check.Exact Technical References & Specifications
1. Operations Security: Backup & Restore Contract
From
docs/technical/operations-security.md(lines 51–66):2. Privacy Erasure & Zombie Data Prevention
From
docs/technical/operations-security.md(lines 68–72):3. Verification Scenarios D10, D11, D12
From
docs/technical/verification.md(lines 143–145, 155):Normative Verification Principle (lines 155):
Formal Citations
docs/technical/operations-security.md:docs/technical/timeline-consistency.md:docs/technical/verification.md:What to Build
1. SQLite Online Snapshot Generator (
src/storage/backup.rs)Implement the snapshot engine:
create_snapshot(db_conn: &mut SqliteConnection, staging_dir: &Path) -> Result<SnapshotManifest, BackupError>:snapshot_<TIMESTAMP>_<UUID>.sqlite3in the designated staging directory (outside source and active data directories).integrity_checkreturnsokandforeign_key_checkreturns zero constraint violation rows.schema_migrations.snapshot_manifest.jsoncontaining:2. Snapshot Verification & Restoration Validator (
src/storage/restore.rs)Implement the pre-restore validation pipeline:
validate_snapshot(manifest_path: &Path, snapshot_path: &Path) -> Result<(), RestoreError>:snapshot_manifest.json. If missing or malformed, returnRestoreError::MissingManifest(Scenario D12).RestoreError::ChecksumMismatch(Scenario D12).PRAGMA integrity_checkandPRAGMA foreign_key_check. If failed, returnRestoreError::CorruptedDatabase.db_schema_versionis supported by current binary migrations.restore_into(snapshot_path: &Path, target_data_dir: &Path) -> Result<(), RestoreError>:/var/lib/life-service/life.sqlite3.-walor-shmsidecar files exist in the target directory.3. Disaster Recovery Rebaseline & Gap Reporter (
src/recovery/disaster.rs)Implement post-restore reconciliation:
operationstable in restored database for operations withstate IN ('prepared', 'unknown').uidand inspect remote ETags.needs_reconciliationwhere uncertain.sync_token = NULL).life_eventscovering[snapshot_capture_time, restore_start_time]withkind: "observation_gap"andevidence_class: "confirmed_service", recording the gap interval and collection coverage inafter_json(docs/technical/timeline-consistency.md:43–44).4. Automated Disaster Recovery Rehearsal Suite (
tests/disaster_recovery_rehearsal.rs)Build the integration test suite validating Scenarios D10, D11, and D12:
life.sqlite3-wal.create_snapshot(...).PRAGMA wal_checkpoint(TRUNCATE).T_1.T_2: add 2 events directly to Radicale, simulate host crash.T_1while Radicale remains atT_2.T_1andT_2are discovered and recorded as external observations.[T_1, T_2]is recorded in the timeline.ChecksumMismatch/CorruptedDatabase.snapshot_manifest.json. Assert restoration validator aborts immediately withMissingManifest.Acceptance Criteria
Observable Assertions & Verification
VACUUM INTO ?while WAL contains un-checkpointed transactions produce a single, self-contained SQLite file containing all committed transactions (Scenario D10).life.sqlite3file directly during write operations is documented as an explicitly rejected, unsafe procedure (Scenario D10).PRAGMA integrity_checkandPRAGMA foreign_key_checkbefore traffic is admitted (Scenario D11).tests/disaster_recovery_rehearsal.rs) passes and demonstrates complete recovery of all state and gap reporting.Notes
VACUUM INTOthrough the singleDatabaseWorkerconnection; the naive main-file-only copy is rejected with evidence (the test shows the copy loses the WAL-resident commit the snapshot keeps).recover_pending_operations, then invalidates sync cursors and records oneobservation_gap; fabricated deletion is asserted absent by query and by an exact event-kind set.Blocked by
Issue #036: SQLite Online Vacuum Snapshot & Disaster Recovery Rehearsalto SQLite Online Vacuum Snapshot & Disaster Recovery Rehearsal