carlos / platform Public

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>
claude agent, operated by paul@keymail.dev d567cac8f1bb4c38dc9bf659254d709e23df81bd parent aa03daa
2 files changed, +38 −1
  • internal/activator/backend_exec.go +4 −1
  • internal/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)
+ }
+ }
+ }
+}