feat(hub): v0.69.0 — customer DELETE is the guided full-teardown cascade (R-25b)

POST /configs/{id}/delete now runs hosts -> RESET -> purge behind three
acknowledgements, a typed customer-id, a stale-preview check and the
ONLINE-host refusal (every gate before any write, so a refusal has zero
side effects). The shallow handleConfigDelete is gone.

Two invariants are asserted, not just commented: ruling 3 is preserved by
construction (leg 2 never sees a host row) and retained escrow custody is
purged exactly once, in leg 3 (leg 2 runs with purgeEscrow=false).

handleCustomerReset's committed half was extracted as commitCustomerReset;
the standalone RESET path is byte-identical to v0.68.1 and its suite is
untouched. Five red-proofs run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J55BQE1gE2V4ffud5jweGS
This commit is contained in:
2026-07-21 19:31:48 +02:00
parent f59aa97d0c
commit 61dbd870c3
13 changed files with 1087 additions and 208 deletions
+5 -12
View File
@@ -833,18 +833,11 @@ func (s *Server) handleOffsiteFreeze(w http.ResponseWriter, r *http.Request, cus
http.Redirect(w, r, "/customers/"+customerID+"?flash="+flash+"#tab=edit", http.StatusSeeOther)
}
// handleConfigDelete deletes a customer config.
func (s *Server) handleConfigDelete(w http.ResponseWriter, r *http.Request, customerID string) {
if err := s.store.DeleteCustomerConfig(customerID); err != nil {
s.logger.Printf("[ERROR] Failed to delete config %s: %v", customerID, err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
s.logger.Printf("[INFO] Customer config deleted: %s", customerID)
s.bumpIntent(customerID) // Direction-2: wake any still-holding wait so it completes promptly
http.Redirect(w, r, "/configs?flash=deleted", http.StatusSeeOther)
}
// The shallow customer DELETE that used to live here (a bare DeleteCustomerConfig behind a native
// confirm) was REPLACED in v0.69.0 by the guided full-teardown cascade — see web/customer_delete.go
// (R-25b). The route is unchanged (POST /configs/{id}/delete); what changed is that it now tears the
// hosts, the offsite repo, the PBS namespace and the tunnel/zone down before purging the record,
// behind three acknowledgements and a typed customer-id. Do NOT reintroduce a shallow delete path.
// handleConfigPreview returns the generated YAML for a customer config.
func (s *Server) handleConfigPreview(w http.ResponseWriter, r *http.Request, customerID string) {