hub v0.60.1: host deletion demotes escrow custody (never destroys) + customer-delete purge point + S6b obsolete

- DeleteHost(deleteEscrow) demotes current host_escrow into host_escrow_superseded (copy-before-delete, same tx), spares existing; one shared demoteCurrentEscrowTx (reused by SaveHostEscrow). F-14 provenance/gate unchanged.
- DeleteCustomerConfig now purges both escrow tables for all the customer's hosts incl. already-deleted (F-14 provenance UNION) — the one true purge point.
- Wording: checkbox/refusal/Danger-zone → demotion. S6b OBSOLETE. Red-proofs TestDeleteHost_Demotes + TestDeleteCustomer_Purges + wording guard.
This commit is contained in:
2026-07-17 11:25:38 +02:00
parent 106c3379b0
commit 2752e12acc
12 changed files with 310 additions and 59 deletions
@@ -0,0 +1,30 @@
package web
import (
"strings"
"testing"
)
// Scenario C (v0.60.1) — the host-delete escrow checkbox must tell the truth: host deletion DEMOTES
// the blob to retained custody, it does not destroy it. Guards the wording against a regression.
func TestHostDeleteEscrowLabel_DemotionWording(t *testing.T) {
b, err := templateFS.ReadFile("templates/host_detail_body.html")
if err != nil {
t.Fatal(err)
}
src := string(b)
if !strings.Contains(src, "Move key escrow to retained custody") {
t.Error("host-delete escrow checkbox must use demotion wording (retained custody)")
}
if strings.Contains(src, "Also delete the key escrow") {
t.Error("host-delete escrow checkbox still uses destruction wording (regression)")
}
// The customer Danger-zone copy names the permanent purge of retained custody (the one true point).
cb, err := templateFS.ReadFile("templates/customer_unified.html")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(cb), "retained recovery-key custody") {
t.Error("customer Danger-zone copy must name the permanent removal of retained recovery-key custody")
}
}
+1 -1
View File
@@ -485,7 +485,7 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
if err := s.store.DeleteHost(hostID, deleteEscrow); err != nil {
if errors.Is(err, store.ErrHostEscrowPresent) {
s.logger.Printf("[WARN] host delete refused: %s has key escrow (acknowledgement missing)", hostID)
http.Error(w, "This host has a key escrow (+ DR bundle). Tick the escrow acknowledgement to delete it too — nothing deleted.", http.StatusConflict)
http.Error(w, "This host has a key escrow (+ DR bundle). Tick the escrow acknowledgement to move it to retained custody — nothing deleted.", http.StatusConflict)
return
}
s.logger.Printf("[ERROR] host delete %s: %v", hostID, err)
@@ -706,7 +706,7 @@
Customer Info header — endpoints and confirm() handlers unchanged. -->
<section class="card">
<h2>Danger zone</h2>
<p class="text-muted">Blocking hides the customer from the Dashboard (reports are still accepted); deleting removes the managed configuration permanently.</p>
<p class="text-muted">Blocking hides the customer from the Dashboard (reports are still accepted); deleting removes the managed configuration permanently — and permanently removes the retained recovery-key custody (escrow blobs) for this customer's hosts. This is the one true purge point; host deletion only demotes custody, never destroys it.</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">
{{if .IsBlocked}}
<form method="POST" action="/customers/{{.CustomerID}}/unblock" style="display:inline">
@@ -284,7 +284,7 @@
<p id="host-delete-impact-{{.HostID}}" style="margin: 0 0 0.5rem; font-size: 0.9em;">&hellip;</p>
<label id="host-delete-escrow-row-{{.HostID}}" style="display: none; margin: 0 0 0.5rem; font-size: 0.85em;">
<input type="checkbox" id="host-delete-escrow-{{.HostID}}">
Also delete the key escrow + DR bundle for this host
Move key escrow to retained custody (required when escrow present) + remove DR bundle for this host
</label>
<p style="margin: 0 0 0.5rem; font-size: 0.85em; color: var(--text-2);">Type the host id to confirm:</p>
<form method="POST" action="/hosts/{{.HostID}}/delete" id="host-delete-form-{{.HostID}}" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">