hub v0.72.0 — R-70 + R-71c: offsite delivery-state detector, card, stuck event, R-39(a)-guarded self-heal restage
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
This commit is contained in:
@@ -518,6 +518,80 @@ type configFormView struct {
|
||||
Error string
|
||||
CSRFField template.HTML
|
||||
PBSDR pbsDRView
|
||||
Delivery *deliveryView
|
||||
}
|
||||
|
||||
// deliveryView is the R-70 customer-card rendering of offsite.DeliveryStateFor — the hub's real
|
||||
// delivery knowledge replacing the static "delivered to the controller once" copy that let a
|
||||
// burned credential hide for 2 days (DIAG-f10-demo-hp-offsite-2026-07-23). All strings are
|
||||
// precomputed operator-tier English (this page's existing language).
|
||||
type deliveryView struct {
|
||||
State string // offsite.DeliveryState (template branch key + test anchor)
|
||||
Badge string // short badge text
|
||||
BadgeClass string // n-ok | n-warn | n-neutral
|
||||
Line string // the state sentence, with age ("mióta" — every state carries its timestamp)
|
||||
StaleLine string // non-empty ONLY for applied+stale-staged (the demo-felhom shape)
|
||||
}
|
||||
|
||||
// agoHuman renders a duration as a coarse operator-friendly age.
|
||||
func agoHuman(d time.Duration) string {
|
||||
switch {
|
||||
case d < time.Minute:
|
||||
return "under a minute"
|
||||
case d < time.Hour:
|
||||
return fmt.Sprintf("%d min", int(d.Minutes()))
|
||||
case d < 48*time.Hour:
|
||||
return fmt.Sprintf("%.1f h", d.Hours())
|
||||
default:
|
||||
return fmt.Sprintf("%d days", int(d.Hours()/24))
|
||||
}
|
||||
}
|
||||
|
||||
// deliveryViewFor derives the card view; nil for brand-new configs (nothing staged yet) or on a
|
||||
// detector error (the card then simply omits the state line — never a fabricated one).
|
||||
func (s *Server) deliveryViewFor(customerID string) *deliveryView {
|
||||
if customerID == "" {
|
||||
return nil
|
||||
}
|
||||
now := time.Now()
|
||||
status, err := offsite.DeliveryStateFor(s.store, customerID)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] delivery view %s: %v", customerID, err)
|
||||
return nil
|
||||
}
|
||||
v := &deliveryView{State: string(status.State)}
|
||||
age := agoHuman(now.Sub(status.Since))
|
||||
switch status.State {
|
||||
case offsite.DeliveryApplied:
|
||||
v.Badge, v.BadgeClass = "applied", "n-ok"
|
||||
v.Line = "offsite active on the box (last report " + age + " ago)"
|
||||
if !status.StaleStagedSince.IsZero() {
|
||||
v.StaleLine = fmt.Sprintf("Note: an unconsumed one-time secret has been staged since %s (%s ago) — superseded by the working install (key-auth-first never consumes); harmless, replaced by the next re-issue.",
|
||||
status.StaleStagedSince.UTC().Format("2006-01-02 15:04 UTC"), agoHuman(now.Sub(status.StaleStagedSince)))
|
||||
}
|
||||
case offsite.DeliveryConsumedAwaitingApply:
|
||||
// Amber past 30 min: consume→apply is a seconds-scale hop; half an hour of it is the
|
||||
// burned-credential shape taking form (the monitor turns it into an event at 1 h).
|
||||
v.Badge = "consumed"
|
||||
if now.Sub(status.Since) > 30*time.Minute {
|
||||
v.BadgeClass = "n-warn"
|
||||
v.Line = fmt.Sprintf("password consumed %s ago and the box still reports no offsite target — likely burned mid-apply; Re-issue delivers a fresh one", age)
|
||||
} else {
|
||||
v.BadgeClass = "n-neutral"
|
||||
v.Line = "password consumed — apply in progress (" + age + ")"
|
||||
}
|
||||
case offsite.DeliveryStagedAwaitingConsume:
|
||||
v.Badge, v.BadgeClass = "staged", "n-neutral"
|
||||
v.Line = "one-time password staged " + age + " ago — the box consumes it on its next config refresh"
|
||||
if now.Sub(status.Since) > 30*time.Minute {
|
||||
v.BadgeClass = "n-warn"
|
||||
v.Line = "one-time password staged " + age + " ago and NOT yet consumed — the box has not re-pulled its config (is it reporting?)"
|
||||
}
|
||||
default: // DeliveryNoSecret
|
||||
v.Badge, v.BadgeClass = "missing", "n-warn"
|
||||
v.Line = "offsite is enabled but no credential is staged — needs attention (Re-issue stages a fresh one)"
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// configFormData assembles the config form's view model. overrides carries SUBMITTED form values
|
||||
@@ -538,6 +612,7 @@ func (s *Server) configFormData(r *http.Request, isNew bool, cfg *store.Customer
|
||||
Error: errMsg,
|
||||
CSRFField: s.csrfField(r),
|
||||
PBSDR: s.pbsDRViewFor(cfg.CustomerID, cfg.DRTier),
|
||||
Delivery: s.deliveryViewFor(cfg.CustomerID),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user