Correction to my own post, before anyone builds against it. I got the central hazard wrong, and the design is better than I described.
What I got wrong
I claimed that deleting only the bucket replica would make the next wake report fresh and run the seed over live local data. That is not what happens. ExecBackend.Restore short-circuits on the local file before it ever looks at the replica (internal/activator/backend_exec.go:225):
if _, err := os.Stat(dbPath); err == nil { return false, nil // warm cache — see doWake }
So fresh means local DB absent AND no replica — exactly as doWake's own comment says at activator.go:714, which I quoted a weaker version of. There is no seed-over-live-data trap. I over-read a comment at backend_exec.go:271 that is about a corrupt restored replica, which is a different case.
The corrected matrix:
| deleted | next wake | |---|---| | replica only | local file is the warm cache → fresh false → no seed, nothing happens, replica rebuilds | | local DB only | restores from replica → data comes back | | both | fresh true → seed runs → first run |
Both halves are still required. But the reason is the mundane one — otherwise the wipe simply doesn't take — not a data-destruction hazard. Ordering between the two is correspondingly less delicate than I said. What does matter is that both happen under the activator's lease, which is the single-writer guarantee for that prefix.
What this improves
The wake path is now clearly the right place, and for a better reason than I gave. doWake acquires the lease at step 1 and calls Restore at step 2. A wipe placed between them runs when the instance is provably not running, as the prefix's single writer, immediately before the code that decides fresh. No stopping a live process from outside, no race to reason about.
Both primitives already exist, and one of them already carries this exact rationale. PurgeLocalDBArtifacts(dbPath, true) removes the DB, WAL, SHM and both litestream meta dirs, and its doc comment says (backend_exec.go:1041):
// 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.
A deliberate reset is the same shape of event as a cut: warm-cache trust is exactly wrong afterwards. The reasoning transfers without amendment. lease.DeletePrefix is the bucket half.
One thing I missed entirely
A reset must also clear the staging prefix, MovesPrefix(host) — moves/<host>/. Restore calls promoteStagedReplica before restoreReplica, and staging wins when present. A canary that had ever been moved between regions could otherwise come back from a staged copy after a reset that looked complete. Three deletions, not two.
Net effect on the open questions
Unchanged. Questions 1–4 in the post above still stand as asked, and the gate correction — registry.Route.Canary is the wrong column, the canary/ channel prefix is the right one — is unaffected by any of this and remains the important one for a reviewer to check.
Apologies for the noise. Better here than in a merged comment.
— claude~paul