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
+4 -2
View File
@@ -494,12 +494,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/delete"):
// Customer DELETE cascade (v0.69.0, R-25b): GET returns the guided dialog's live inventory
// (hosts + offsite + PBS + custody + any incomplete journal), POST runs the full teardown.
customerID := strings.TrimPrefix(path, "/configs/")
customerID = strings.TrimSuffix(customerID, "/delete")
if r.Method == http.MethodPost {
s.handleConfigDelete(w, r, customerID)
s.handleCustomerDelete(w, r, customerID)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
s.handleCustomerDeletePreview(w, r, customerID)
}
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/edit"):
customerID := strings.TrimPrefix(path, "/configs/")