hub v0.70.1: the ghost customer's Delete button must exist (Danger-zone render gate split)

This commit is contained in:
2026-07-22 09:23:37 +02:00
parent 2cf37f263b
commit f4c2c4151b
7 changed files with 199 additions and 4 deletions
+20
View File
@@ -283,6 +283,12 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
Overrides map[string]interface{}
IsBlocked bool
// Deletable (v0.70.1) gates the Danger-zone card. It is the exact negation of the delete
// preview's 404 predicate (customer_delete.go: cfg == nil && no hosts && residue empty) —
// one truth, not a lookalike. Before v0.70.1 the card sat inside {{if .HasConfig}}, so the
// entire v0.70.0 ghost-delete path was implemented but unreachable (dead UI).
Deletable bool
HasReports bool
Customer *store.CustomerSummary
Report map[string]interface{}
@@ -397,6 +403,19 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
staleSinceReset = customer == nil || !customer.ReceivedAt.After(*cr.CompletedAt)
}
// v0.70.1: the Danger-zone render gate. Hosts are already fetched above for the Host tab —
// only the residue count is an extra read, and it runs ONLY on the ghost shape (config-less,
// hostless), never on the hot normal path. A lookup error logs and leaves Deletable=false:
// fail toward HIDING a destructive control, never toward showing one on unknown state.
deletable := cfg != nil || len(hostViews) > 0
if !deletable {
if residue, err := s.store.CustomerResidue(customerID); err != nil {
s.logger.Printf("[ERROR] CustomerResidue %s: %v", customerID, err)
} else {
deletable = residue.Total() > 0
}
}
// R-36 interim (v0.67.0): enabled-but-unprovisioned is a real, stable state — the same predicate
// the offsite re-issue handler already uses to refuse ("No provisioned offsite tier").
var offsiteView struct {
@@ -420,6 +439,7 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
Config: cfg,
Overrides: overrides,
IsBlocked: cfg != nil && cfg.Status == "blocked",
Deletable: deletable,
HasReports: customer != nil,
Customer: customer,