hub v0.60.0: offsite continuity Part B — superseded-escrow retention (data-first)
- host_escrow_superseded table + SaveHostEscrow retains a different-sha old blob before overwrite (tx); same-sha idempotent (no supersede row); returns superseded bool. ACK/restore read the current row unchanged. CountSuperseded/ListSuperseded; DeleteHost drops retained rows. - escrow_superseded audit event + operator retained-count on host detail; register offbox_repo_orphaned/reset. Red-proof TestSaveHostEscrow_RetainsSuperseded.
This commit is contained in:
@@ -406,6 +406,9 @@ func (s *Server) hostDetailData(host *store.Host, r *http.Request) map[string]in
|
||||
"NeedsDRMigration": capabilitiesNeedDRMigration(capabilities),
|
||||
"DRPresent": drBundle != nil,
|
||||
"EscrowPresent": escrow != nil,
|
||||
// v0.60.0 Part B: retained superseded escrow blobs (data-first — old passphrases stay
|
||||
// R-recoverable). Operator-only surface.
|
||||
"SupersededEscrowCount": func() int { n, _ := s.store.CountSupersededEscrow(host.HostID); return n }(),
|
||||
// v0.46.0 Diagnostics: pending log pulls + received/blocked bundles (72 h TTL).
|
||||
"LogBundles": s.hostLogBundleRows(host),
|
||||
"CSRFToken": s.getCSRFToken(r),
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestHostDelete_EscrowAckRequired(t *testing.T) {
|
||||
if err := st.UpsertHost(&store.Host{HostID: "esc-host", CustomerID: "c2", APIKey: "k"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SaveHostEscrow("esc-host", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
if _, err := st.SaveHostEscrow("esc-host", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func TestHostDelete_ImpactJSON(t *testing.T) {
|
||||
if err := st.UpsertHost(&store.Host{HostID: "imp-host", CustomerID: "c4", APIKey: "SECRET-KEY"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SaveHostEscrow("imp-host", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
if _, err := st.SaveHostEscrow("imp-host", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.UpsertGuestFromReport(&store.Guest{GuestID: store.GuestID("imp-host", 100),
|
||||
@@ -159,7 +159,7 @@ func TestHostDelete_HappyPath(t *testing.T) {
|
||||
if err := st.UpsertHost(&store.Host{HostID: "dr-drill", CustomerID: "c5", APIKey: "k"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SaveHostEscrow("dr-drill", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
if _, err := st.SaveHostEscrow("dr-drill", []byte("blob"), "fp", "p", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rr := postHostDelete(t, s, "dr-drill", url.Values{
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestHandleHostDetail(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// DR + escrow present (escrow row must exist before the DR bundle UPDATE).
|
||||
if err := st.SaveHostEscrow("demo-felhom-01", []byte("opaque-escrow"), "fp", "posture", "2026-06-01T00:00:00Z", ""); err != nil {
|
||||
if _, err := st.SaveHostEscrow("demo-felhom-01", []byte("opaque-escrow"), "fp", "posture", "2026-06-01T00:00:00Z", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SaveHostDRBundle("demo-felhom-01", []byte("opaque-identity"), `{"v":1}`); err != nil {
|
||||
|
||||
@@ -390,7 +390,7 @@ func TestPBSDR_F14AutoReissueOnAckedDeletion(t *testing.T) {
|
||||
if err := st.UpsertHost(&store.Host{HostID: "peti-00-dead", CustomerID: "peti", APIKey: "oldkey"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.SaveHostEscrow("peti-00-dead", []byte("opaque"), "fp", "posture", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
if _, err := st.SaveHostEscrow("peti-00-dead", []byte("opaque"), "fp", "posture", "2026-07-01T00:00:00Z", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.DeleteHost("peti-00-dead", true); err != nil {
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">Key Escrow</span>
|
||||
<span class="value">{{if .EscrowPresent}}<span style="color: var(--green)">present</span>{{else}}<span class="text-muted">none</span>{{end}}</span>
|
||||
<span class="value">{{if .EscrowPresent}}<span style="color: var(--green)">present</span>{{else}}<span class="text-muted">none</span>{{end}}{{if gt .SupersededEscrowCount 0}} <span class="text-muted">· {{.SupersededEscrowCount}} superseded escrow blob(s) retained</span>{{end}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user