feat(escrow): v0.138.0 — "awaiting hub confirmation" waiting state

After a completed escrow ceremony the Távoli mentés page showed the yellow
"Helyreállítási kód szükséges" card for ~15 min until the next hub-report ACK
flipped pending→escrowed. Phase-0 diagnosis (read-only) = verdict A (report-cycle
lag), already resolved on the demo box (escrow_state:"escrowed"); hub Hypothesis B
verified false (SaveHostEscrow ON CONFLICT already clears stale_at on upload) → no
hub change.

- settings.OffboxTarget.CeremonyCompletedAt: stamped on the recovery-code claim,
  zeroed on the auto-confirmer Flip + the deprecated manual confirm; persisted.
- web/handlers.go: offboxCeremonyWaitState + escrowCeremonyGraceWindow (35m).
- backups_remote.html: info "megerősítésre vár, legfeljebb 15 perc" card → warn
  "a megerősítés nem érkezett meg" past the window. Existing branches untouched.
- backups_escrow.html: "Mi történik ezután?" note on the wizard's final step.
- Test web/escrow_wait_state_test.go (truth table + red-proof recorded in REPORT).

No scheduler/agent/hub/endpoint changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qDiBqKKQ5vPB5fXBqu7Kp
This commit is contained in:
2026-07-16 18:44:13 +02:00
parent e99c675fe4
commit 120332103a
13 changed files with 283 additions and 61 deletions
@@ -7,6 +7,7 @@ import (
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
"golang.org/x/crypto/bcrypt"
)
@@ -271,6 +272,18 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
return
}
s.logger.Printf("[INFO] [web] escrow recovery code claimed (one-shot; not logged)")
// v0.138.0: stamp the ceremony-completed time so /backups/remote shows the "awaiting hub
// confirmation" card during the report-cycle gap before the auto-confirmer flips to escrowed
// (Phase-0 verdict A: the yellow "szükséges" card during that ~15-min wait was the real gap).
// Only while pending — never re-stamp an already-escrowed target. Best-effort: a stamp failure
// must not fail the claim (the code is already revealed and the blob already uploaded).
if err := s.settings.UpdateOffboxStatus(func(o *settings.OffboxTarget) {
if o.EscrowState != "escrowed" {
o.CeremonyCompletedAt = time.Now().Format(time.RFC3339)
}
}); err != nil {
s.logger.Printf("[WARN] [web] escrow claim: ceremony timestamp not persisted: %v", err)
}
escrowJSON(w, http.StatusOK, map[string]any{"recovery_code": code}, "")
code = "" // drop the reference promptly (GC caveat: best-effort)
_ = code