package store import ( "bytes" "testing" ) // R-1176 scenario A at the store. The first test is the C3 red-proof from the data-loss hunt // (audits/dataloss-hunt-2026-10-20/C3-K1-escrow-after-host-delete/), now asserting the household's package // reaches the rebuilt box — byte-identical, still sealed, with its hash. func carrySetupOldBox(t *testing.T, s *Store, cust, host, sha string, identity []byte) { t.Helper() if err := s.UpsertHost(&Host{HostID: host, CustomerID: cust, APIKey: "k-" + host}); err != nil { t.Fatal(err) } if _, _, err := s.SaveHostEscrow(host, []byte("k-blob-"+host), "fp-"+host, "zero_knowledge", "2026-10-18T00:00:00Z", sha); err != nil { t.Fatal(err) } if identity != nil { if err := s.SaveHostDRBundle(host, identity); err != nil { t.Fatal(err) } } } func TestR1176_RebuiltBoxGetsTheHouseholdsPackage(t *testing.T) { s := newTestStore(t) const cust = "Tester4" identity := []byte("sealed-identity-opening-the-old-repo") carrySetupOldBox(t, s, cust, "Tester4-old", "SHA_OLD", identity) if err := s.DeleteHost("Tester4-old", true); err != nil { t.Fatal(err) } if err := s.UpsertHost(&Host{HostID: "Tester4-new", CustomerID: cust, APIKey: "k2"}); err != nil { t.Fatal(err) } res, err := s.ReattachRetainedEscrow(cust) if err != nil { t.Fatal(err) } st, err := s.GetEscrowStatusForCustomer(cust) if err != nil || st == nil { t.Fatalf("the rebuilt box's ACK carries no escrow status (st=%v err=%v) — the R-1176 defect", st, err) } if !st.IdentityBlobPresent || st.ResticPwSHA256 != "SHA_OLD" { t.Fatalf("status = %+v, want identity present with the household's hash", st) } if res.Outcome != CarryDone || res.FromHost != "Tester4-old" || res.ToHost != "Tester4-new" || res.ResticPwSHA256 != "SHA_OLD" { t.Fatalf("carry result = %+v, want done old→new with SHA_OLD", res) } b, err := s.GetHostDRBundle("Tester4-new") if err != nil || b == nil || !bytes.Equal(b.IdentityBlob, identity) || !bytes.Equal(b.KEscrowBlob, []byte("k-blob-Tester4-old")) { t.Fatalf("the new host's package is not the household's bytes: %+v err=%v", b, err) } // One sealed package exists once: the retained copy is gone, the current row holds it. if ret, _ := s.ListSupersededEscrow("Tester4-old"); len(ret) != 0 { t.Fatalf("retained rows left under the deleted host: %d, want 0 (moved, not copied)", len(ret)) } // Idempotent: the second report changes nothing. if res2, err := s.ReattachRetainedEscrow(cust); err != nil || res2.Outcome != CarryNothing { t.Fatalf("second call = %+v err=%v, want nothing", res2, err) } // And the carried package is never lost: deleting the new host (escrow acknowledged) demotes it again. if err := s.DeleteHost("Tester4-new", true); err != nil { t.Fatal(err) } if ret, _ := s.ListSupersededEscrow("Tester4-new"); len(ret) != 1 || !bytes.Equal(ret[0].IdentityBlob, identity) { t.Fatalf("after deleting the rebuilt host, its retained rows = %d, want the household's package", len(ret)) } } // The rehearsal's own shape: the old box (with a package) deleted acknowledged, then a second box (never made a // package) deleted un-acknowledged, then a third box. The un-acked deletion has nothing and must not hide the older one. func TestR1176_AnUnackedLaterDeletionDoesNotHideThePackage(t *testing.T) { s := newTestStore(t) const cust = "Tester4" carrySetupOldBox(t, s, cust, "Tester4-816e21", "SHA_OLD", []byte("id")) if err := s.DeleteHost("Tester4-816e21", true); err != nil { t.Fatal(err) } if err := s.UpsertHost(&Host{HostID: "Tester4-648b80", CustomerID: cust, APIKey: "k2"}); err != nil { t.Fatal(err) } // (the hub of the rehearsal did not carry — simulate by deleting before any report) if err := s.DeleteHost("Tester4-648b80", true); err != nil { t.Fatal(err) } if d, _ := s.LatestHostDeletion(cust); d == nil || d.EscrowAcked { t.Fatalf("precondition: the latest deletion must be un-acked, got %+v", d) } if err := s.UpsertHost(&Host{HostID: "Tester4-third", CustomerID: cust, APIKey: "k3"}); err != nil { t.Fatal(err) } res, err := s.ReattachRetainedEscrow(cust) if err != nil || res.Outcome != CarryDone || res.FromHost != "Tester4-816e21" { t.Fatalf("carry = %+v err=%v, want the 816e21 package carried", res, err) } } func TestR1176_RefusedWhileTheCustomerHasAnotherLiveHost(t *testing.T) { s := newTestStore(t) const cust = "C" carrySetupOldBox(t, s, cust, "C-old", "SHA_OLD", []byte("id")) if err := s.DeleteHost("C-old", true); err != nil { t.Fatal(err) } for _, h := range []string{"C-a", "C-b"} { if err := s.UpsertHost(&Host{HostID: h, CustomerID: cust, APIKey: "k" + h}); err != nil { t.Fatal(err) } } res, err := s.ReattachRetainedEscrow(cust) if err != nil || res.Outcome != CarryRefusedOtherLiveHost || len(res.LiveHosts) != 2 || res.FromHost != "C-old" { t.Fatalf("carry = %+v err=%v, want refused with both live hosts named", res, err) } for _, h := range []string{"C-a", "C-b"} { if e, _ := s.GetHostEscrow(h); e != nil { t.Fatalf("%s got a package while the carry was refused", h) } } if ret, _ := s.ListSupersededEscrow("C-old"); len(ret) != 1 { t.Fatalf("the retained package moved although refused: %d rows", len(ret)) } } func TestR1176_NeverAnotherCustomersPackage(t *testing.T) { s := newTestStore(t) carrySetupOldBox(t, s, "A", "A-old", "SHA_A", []byte("id-A")) if err := s.DeleteHost("A-old", true); err != nil { t.Fatal(err) } if err := s.UpsertHost(&Host{HostID: "B-new", CustomerID: "B", APIKey: "kb"}); err != nil { t.Fatal(err) } if res, err := s.ReattachRetainedEscrow("B"); err != nil || res.Outcome != CarryNothing { t.Fatalf("customer B got %+v err=%v — another customer's package", res, err) } if e, _ := s.GetHostEscrow("B-new"); e != nil { t.Fatal("B-new holds a package it must never see") } } func TestR1176_NotFromAHostDeletedWithoutAcknowledgement(t *testing.T) { s := newTestStore(t) const cust = "C" // A host with no escrow row deletes un-acked; a stray retained row under its id (a supersession earlier in its // life) must not be carried — only an acknowledged deletion's package is. if err := s.UpsertHost(&Host{HostID: "C-old", CustomerID: cust, APIKey: "k"}); err != nil { t.Fatal(err) } carrySetupOldBox(t, s, cust, "C-old", "SHA1", []byte("id1")) if _, _, err := s.SaveHostEscrow("C-old", []byte("k2"), "fp2", "zk", "2026-10-19T00:00:00Z", "SHA2"); err != nil { t.Fatal(err) // SHA1's package retained under C-old } if _, err := s.db.Exec(`DELETE FROM host_escrow WHERE host_id = 'C-old'`); err != nil { t.Fatal(err) } if err := s.DeleteHost("C-old", false); err != nil { t.Fatal(err) } if err := s.UpsertHost(&Host{HostID: "C-new", CustomerID: cust, APIKey: "k3"}); err != nil { t.Fatal(err) } if res, err := s.ReattachRetainedEscrow(cust); err != nil || res.Outcome != CarryNothing { t.Fatalf("carry = %+v err=%v, want nothing from an un-acknowledged deletion", res, err) } } func TestR1176_ABoxWithItsOwnPackageIsNeverOverwritten(t *testing.T) { s := newTestStore(t) const cust = "C" carrySetupOldBox(t, s, cust, "C-old", "SHA_OLD", []byte("id-old")) if err := s.DeleteHost("C-old", true); err != nil { t.Fatal(err) } carrySetupOldBox(t, s, cust, "C-new", "SHA_NEW", []byte("id-new")) if res, err := s.ReattachRetainedEscrow(cust); err != nil || res.Outcome != CarryNothing { t.Fatalf("carry = %+v err=%v, want nothing", res, err) } if e, _ := s.GetHostEscrow("C-new"); e == nil || e.ResticPwSHA256 != "SHA_NEW" { t.Fatalf("the new box's own package was replaced: %+v", e) } } func TestR1176_APackageWithNoIdentityIsNotCarried(t *testing.T) { s := newTestStore(t) const cust = "C" carrySetupOldBox(t, s, cust, "C-old", "", nil) // a pre-v0.93.0 shape: K only, no repository password if err := s.DeleteHost("C-old", true); err != nil { t.Fatal(err) } if err := s.UpsertHost(&Host{HostID: "C-new", CustomerID: cust, APIKey: "k"}); err != nil { t.Fatal(err) } if res, err := s.ReattachRetainedEscrow(cust); err != nil || res.Outcome != CarryNothing { t.Fatalf("carry = %+v err=%v, want nothing", res, err) } }