hub: drop the retained recovery package when the box declares its set-aside history deleted (R-241)
The hub half of the controller's abandonment countdown, and the ONLY reason felhom.eu was touched for R-241 at all. A customer who abandons their old off-site history gets a 14-day countdown. At the end of it the controller deletes the set-aside restic store and then DECLARES offsite.abandon_purge_requested in its report until the retained sealed package that protected that store is gone too. Removing only one half leaves a state that asks a question nobody can answer: a package that opens nothing, or ciphertext nobody can ever decrypt. store.PurgeSupersededEscrowForCustomer is the one place R-198's retention is ever undone, and its doc comment says why that is legitimate here. It NEVER touches host_escrow - the current package covers the key the box is using now and is what makes its live backups recoverable. Only host_escrow_superseded rows go. The handler acts on the box's DECLARATION, never an inference, on the same principle as offsite.state: the hub cannot see that a remote store was deleted and the box can. It is placed immediately BEFORE the ACK is built, deliberately. GetEscrowStatusForCustomer is read after it runs, so the SAME response that carries the request's effect is what closes the box's two-phase commit - no second round-trip, and no window in which the box believes it is still owed. The declaration repeats on every report until that ACK stops reporting a superseded package, so a lost request retries by itself. A purge failure is logged at ERROR and never swallowed: the box keeps declaring, so it retries, but an operator must be able to see that the two halves are apart right now. An idempotent re-declaration (already purged, the box has not yet seen the confirming ACK) logs at DEBUG and is not an error. Audit event offsite_abandon_purged is hub-internal, like the pbsdr_* and offsite_selfheal_* events - allowedEventTypes governs the box-pushed POST /event surface, not this. No agent change. No deletion has been performed against any real store. Green: go build, go vet, go test ./... all clean in hub/; repo gates OK.
This commit is contained in:
@@ -3557,3 +3557,32 @@ func (s *Store) GetHostLeafFingerprints() ([]HostLeafRow, error) {
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// PurgeSupersededEscrowForCustomer deletes the RETAINED (superseded) escrow rows for every host of a
|
||||
// customer, and reports how many went. R-241 (v0.98.0), and it is the ONE place the retention added
|
||||
// by R-198 is ever undone.
|
||||
//
|
||||
// ⚠ WHY THIS EXISTS AT ALL, given R-198 was written to STOP a supersession destroying the key that
|
||||
// opens an earlier history. Because the customer has now asked for that history to go. The controller
|
||||
// runs a 14-day countdown after an explicit, twice-confirmed abandonment, deletes the set-aside
|
||||
// restic store at the end of it, and then DECLARES `offsite.abandon_purge_requested` in its report
|
||||
// until this runs. Removing the store while keeping its sealed package leaves a package that opens
|
||||
// nothing; removing the package while keeping the store leaves ciphertext nobody can ever decrypt.
|
||||
// Both are states that ask a question with no answer, which is exactly what R-241 was.
|
||||
//
|
||||
// IT NEVER TOUCHES `host_escrow` — the CURRENT package, which covers the key the box is using now,
|
||||
// is what makes the box's live off-site backups recoverable. Only the retained rows go.
|
||||
//
|
||||
// The caller is the report handler, acting on the box's own declaration: the hub cannot see that a
|
||||
// remote store was deleted and must not infer it.
|
||||
func (s *Store) PurgeSupersededEscrowForCustomer(customerID string) (int64, error) {
|
||||
res, err := s.db.Exec(`
|
||||
DELETE FROM host_escrow_superseded WHERE host_id IN (
|
||||
SELECT host_id FROM hosts WHERE customer_id = ?
|
||||
UNION SELECT host_id FROM host_deletions WHERE customer_id = ?
|
||||
)`, customerID, customerID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("purge superseded escrow for %s: %w", customerID, err)
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user