hub v0.137.0 source + burn-down round 2 in felhom.eu: R-277 R-581 R-600 R-544 R-855 R-134 R-92 R-292 R-599 R-725 R-728 (hub), R-819 R-857 R-555 R-364 R-587 (gates/tools), R-571 R-129 R-124-runbook (docs); 28 rows closed incl. catalog + agent v0.147.0 rows, R-350 merged into R-132, R-888 opened, R-887 mechanism (249 -> 222)
gates / gates (push) Successful in 2m3s
gates / gates (push) Successful in 2m3s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -895,7 +896,7 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
|
||||
}
|
||||
if status := s.hostStatus(host.LastReportAt); status == "ok" {
|
||||
s.logger.Printf("[WARN] host delete refused: %s is online", hostID)
|
||||
http.Error(w, "Host is ONLINE — deletion is refused (a live agent would receive 401s permanently).", http.StatusConflict)
|
||||
http.Error(w, "Host is ONLINE — deletion is refused (a live agent would receive 401s permanently)."+s.deletionOpensAt(host.LastReportAt, time.Now()), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
if confirm := strings.TrimSpace(r.FormValue("confirm_host_id")); confirm != hostID {
|
||||
@@ -904,6 +905,8 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
|
||||
return
|
||||
}
|
||||
deleteEscrow := r.FormValue("delete_escrow") == "1"
|
||||
// R-544: read whether an escrow exists BEFORE the delete, so the log can say what happened to it.
|
||||
escrowBefore, escErr := s.store.GetHostEscrow(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)
|
||||
@@ -914,7 +917,7 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] host deleted: %s (escrow deleted: %v)", hostID, deleteEscrow)
|
||||
s.logger.Printf("[INFO] host deleted: %s (%s)", hostID, hostDeleteEscrowEffect(escrowBefore != nil, escErr))
|
||||
// R-509: the customer record stays and now waits for a box → send the connect link.
|
||||
if host.CustomerID != "" {
|
||||
s.autoMintSelfBindIfWaiting(host.CustomerID, "host delete")
|
||||
@@ -922,6 +925,38 @@ func (s *Server) handleHostDelete(w http.ResponseWriter, r *http.Request, hostID
|
||||
http.Redirect(w, r, "/hosts", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// hostDeleteEscrowEffect states what a host delete did to the key escrow, in operator words (R-544).
|
||||
// A host delete NEVER destroys escrow: the store demotes it to retained custody, and only the customer
|
||||
// delete purges it. The old line printed the form flag ("escrow deleted: true"), which read as a
|
||||
// household's last key being destroyed.
|
||||
func hostDeleteEscrowEffect(hadEscrow bool, lookupErr error) string {
|
||||
switch {
|
||||
case lookupErr != nil:
|
||||
return "escrow state unknown before the delete; any escrow is demoted to retained custody, never destroyed"
|
||||
case hadEscrow:
|
||||
return "escrow custody demoted to retained (host delete); the customer delete is the only purge"
|
||||
default:
|
||||
return "no key escrow held"
|
||||
}
|
||||
}
|
||||
|
||||
// deletionOpensAt is the tail of an ONLINE refusal (R-599): "online" is a report-staleness window, not a
|
||||
// liveness probe, so a box that no longer exists still reads online until its last report is older than
|
||||
// the CONFIGURED stale threshold. Say when that was and when the refusal ends, so a teardown waits
|
||||
// instead of concluding the delete is broken. Empty when there is no last report.
|
||||
func (s *Server) deletionOpensAt(lastReport *time.Time, now time.Time) string {
|
||||
if lastReport == nil {
|
||||
return ""
|
||||
}
|
||||
age := now.Sub(*lastReport)
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
opens := lastReport.Add(s.staleThreshold).UTC()
|
||||
return fmt.Sprintf(" Its last report arrived %d min ago (%s UTC); deletion opens at %s UTC, once the host has been silent for the stale threshold (%s).",
|
||||
int(age.Minutes()), lastReport.UTC().Format("15:04"), opens.Format("15:04"), s.staleThreshold)
|
||||
}
|
||||
|
||||
// handleHostDetail renders the read-only per-host detail page (audit F-M1). GET only.
|
||||
func (s *Server) handleHostDetail(w http.ResponseWriter, r *http.Request, hostID string) {
|
||||
host, err := s.store.GetHost(hostID)
|
||||
|
||||
Reference in New Issue
Block a user