activator: the full local purge removes the seed marker (final review carry item)
PurgeLocalDBArtifacts' full form (a move's cut) removed the database and left its <db>.seed marker, so a marker could outlive its database and name a seed for whatever was put at that path later, which stale-journal recovery could take for the old seed's data. The full form now removes the marker with the database. purgeSeedTarget, the seed paths' own purge, already removed it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+38
−1
internal/activator/backend_exec.go+4 −1internal/activator/purgelocal_test.go+34 −0
diff --git a/internal/activator/backend_exec.go b/internal/activator/backend_exec.go| index 2c6a48317..efffad264 100644 |
| --- a/internal/activator/backend_exec.go |
| +++ b/internal/activator/backend_exec.go |
| @@ -2033,7 +2033,9 @@ func MoveReceiptsKey(host string) string { return "move-receipts/" + host + ".js |
| // absence is what got us here); a residence move's cut uses the full form: |
| // the activator's warm-cache trust is exactly right for crashes and exactly |
| // wrong after a cut, where a stale local file would resurrect a moved-away |
| -// instance's history. |
| +// instance's history. The full form removes the database's <db>.seed |
| +// marker too (final review carry item): a marker that outlived its |
| +// database would name a seed for whatever database is put there later. |
| func PurgeLocalDBArtifacts(dbPath string, full bool) { |
| for _, p := range []string{dbPath + "-wal", dbPath + "-shm"} { |
| _ = os.Remove(p) |
| @@ -2042,6 +2044,7 @@ func PurgeLocalDBArtifacts(dbPath string, full bool) { |
| _ = os.RemoveAll(filepath.Join(filepath.Dir(dbPath), ".activator-"+filepath.Base(dbPath)+"-litestream")) |
| if full { |
| _ = os.Remove(dbPath) |
| + _ = os.Remove(dbPath + SeedMarkerSuffix) |
| } |
| } |
diff --git a/internal/activator/purgelocal_test.go b/internal/activator/purgelocal_test.go| new file mode 100644 |
| index 000000000..9fc54c182 |
| --- /dev/null |
| +++ b/internal/activator/purgelocal_test.go |
| @@ -0,0 +1,34 @@ |
| +package activator |
| + |
| +import ( |
| + "os" |
| + "path/filepath" |
| + "testing" |
| +) |
| + |
| +// Final review carry item: the full purge (a move's cut) removes the |
| +// database and its <db>.seed marker with it; the partial purge (a restore's |
| +// pre-clean, where the database is already gone) keeps both as it found |
| +// them. |
| +func TestPurgeLocalDBArtifactsRemovesTheMarkerWithTheDatabase(t *testing.T) { |
| + for _, full := range []bool{false, true} { |
| + dir := t.TempDir() |
| + db := filepath.Join(dir, "a.shop.example.com.db") |
| + for _, p := range []string{db, db + "-wal", db + "-shm", db + SeedMarkerSuffix} { |
| + if err := os.WriteFile(p, []byte("x"), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + PurgeLocalDBArtifacts(db, full) |
| + for _, p := range []string{db + "-wal", db + "-shm"} { |
| + if _, err := os.Lstat(p); !os.IsNotExist(err) { |
| + t.Errorf("full %v: %s survived", full, p) |
| + } |
| + } |
| + for _, p := range []string{db, db + SeedMarkerSuffix} { |
| + if _, err := os.Lstat(p); os.IsNotExist(err) != full { |
| + t.Errorf("full %v: %s gone %v, want gone %v", full, p, os.IsNotExist(err), full) |
| + } |
| + } |
| + } |
| +} |