Refuse a backup destination an unplugged volume left empty (closes #239)
check / check (push) Waiting to run

Every backup writes vaultik-destination.txt at the destination root.
While the local index holds snapshots, a destination without it is
not present: snapshot create refuses before writing, prune and
snapshot purge fail, and snapshot list and snapshot remove act as for
a destination that cannot be listed. remote nuke deletes it last. The
file:// backend no longer creates its destination directory and checks
for it on every write; that protects a directory on a removable
volume, not a mount point.

Fixtures standing for a written destination carry the marker; two
tests check they get past it.

Judgement call: the marker is rewritten on every backup.
Deviation: for the complexity limit, snapshot name selection moved
into snapshotNamesToCreate and the --force/--json test into
confirmRemoveSnapshot, both unchanged.

Model: opus-5-5
This commit is contained in:
2026-10-14 00:25:13 +00:00
parent 9fd88a34c7
commit 1984d5fd22
41 changed files with 595 additions and 121 deletions
+7 -1
View File
@@ -101,7 +101,8 @@ func TestEntryPruneJSONStdoutIsExactlyOneDocument(t *testing.T) {
// writeHermeticPruneConfig builds a config over a temp directory with an
// empty destination directory and, if seedStale is set, creates the
// index database up front with one snapshot record that has no
// counterpart on the destination store. Returns the config path.
// counterpart on the destination store, and gives the destination the
// marker a backup writes. Returns the config path.
func writeHermeticPruneConfig(t *testing.T, seedStale bool) string {
t.Helper()
@@ -131,6 +132,11 @@ func writeHermeticPruneConfig(t *testing.T, seedStale bool) string {
if seedStale {
seedStaleSnapshotRecord(t, indexPath)
// While the local index holds a snapshot, prune fails on a
// destination without the marker a backup writes.
require.NoError(t, os.WriteFile(
filepath.Join(storeDir, "vaultik-destination.txt"), nil, 0o600))
}
return configPath