Mark a snapshot complete only after its metadata export succeeds (closes #177)
check / check (pull_request) Successful in 3m11s

finalizeSnapshotMetadata marked the snapshot complete and then exported its
metadata. A crash after completion but before/during the export left the
local index showing the snapshot as complete while the destination had no
manifest or database, and PruneDatabase (which drops only NULL completed_at
rows) kept it: a silently unrestorable snapshot.

Reorder so completion is recorded last. CompleteSnapshot is split into
PopulateSnapshotBlobs (before the export, which reads snapshot_blobs) and
MarkSnapshotComplete (after it). An interrupted export now leaves the
snapshot incomplete, so the next run's PruneDatabase drops it and re-backs-up
the data. The reverse tiny window leaves a fully restorable snapshot at the
destination that the index reports honestly as remote-only.

Update REPOSTRUCTURE.md guarantee 4 and the ARCHITECTURE.md flow to the new
order. Add a fault-injection test driving the full create path.

Model: opus-4-8
This commit is contained in:
2026-09-22 17:44:03 +00:00
parent 1548c0f933
commit 3a1a0d7927
5 changed files with 263 additions and 22 deletions
+14 -10
View File
@@ -284,8 +284,10 @@ Manages snapshot lifecycle and metadata export.
Key methods:
- `CreateSnapshot(ctx, hostname, version, commit)` → Create snapshot record
- `CompleteSnapshot(ctx, snapshotID)`Mark snapshot complete
- `PopulateSnapshotBlobs(ctx, snapshotID)`Record every blob the snapshot references
- `ExportSnapshotMetadata(ctx, dbPath, snapshotID)` → Export to S3
- `MarkSnapshotComplete(ctx, snapshotID)` → Record completion, only after a successful export
- `CompleteSnapshot(ctx, snapshotID)` → Convenience: populate blobs, then mark complete (no export between)
### `internal/database`
SQLite database for local index. Single-writer mode for thread safety.
@@ -335,16 +337,18 @@ CreateSnapshot(opts)
├─► SnapshotManager.UpdateSnapshotStatsExtended()
├─► SnapshotManager.CompleteSnapshot()
├─► SnapshotManager.PopulateSnapshotBlobs() // record referenced blobs
─► SnapshotManager.ExportSnapshotMetadata()
├─► Copy database to temp file
├─► Clean to only current snapshot data (VACUUM)
├─► Compress binary SQLite with zstd
├─► Encrypt with age
├─► Upload db.zst.age to storage
└─► Upload manifest.json.zst to storage
─► SnapshotManager.ExportSnapshotMetadata()
├─► Copy database to temp file
├─► Clean to only current snapshot data (VACUUM)
├─► Compress binary SQLite with zstd
├─► Encrypt with age
├─► Upload db.zst.age to storage
└─► Upload manifest.json.zst to storage
└─► SnapshotManager.MarkSnapshotComplete() // only after the export succeeds
```
## Deduplication Strategy