hub v0.60.1: host deletion demotes escrow custody (never destroys) + customer-delete purge point + S6b obsolete
- DeleteHost(deleteEscrow) demotes current host_escrow into host_escrow_superseded (copy-before-delete, same tx), spares existing; one shared demoteCurrentEscrowTx (reused by SaveHostEscrow). F-14 provenance/gate unchanged. - DeleteCustomerConfig now purges both escrow tables for all the customer's hosts incl. already-deleted (F-14 provenance UNION) — the one true purge point. - Wording: checkbox/refusal/Danger-zone → demotion. S6b OBSOLETE. Red-proofs TestDeleteHost_Demotes + TestDeleteCustomer_Purges + wording guard.
This commit is contained in:
@@ -2,39 +2,56 @@
|
||||
|
||||
> **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md).
|
||||
|
||||
## Offsite continuity Part B — superseded-escrow retention (hub v0.60.0) — 2026-07-17
|
||||
## Hub v0.60.1 — host deletion demotes escrow custody (never destroys) + S6b verdict — 2026-07-17
|
||||
|
||||
**Baseline:** hub v0.59.0 → **v0.60.0** (LIVE on prod, ArgoCD Synced/Healthy). Closes the data-loss
|
||||
half of the reinstall-orphaned-repo incident
|
||||
(`documentation/audits/DIAGNOSE-offbox-repo-orphaned-2026-07-17.md`): the destructive
|
||||
`SaveHostEscrow` `ON CONFLICT` overwrite meant a new escrow blob DESTROYED the old passphrase's only
|
||||
copy (18 snapshots unrecoverable). Viktor's ruling — data protection first — is now enforced. Pairs
|
||||
with controller v0.142.0 (Part A). Green: `go build ./... && go vet ./... && go test ./...`.
|
||||
**Baseline:** hub v0.60.0 (`106c337`) → **v0.60.1** (LIVE, ArgoCD Synced/Healthy). Hub-only; no
|
||||
controller/agent change. Closes the deletion-path gap found in the v0.60.0 review. Green:
|
||||
`go build ./... && go vet ./... && go test ./...` + hub confirm gate.
|
||||
|
||||
### Schema decision: history table (not a flag)
|
||||
`host_escrow_superseded` — an append-only, one-row-per-superseded-blob table. A flag couldn't hold N
|
||||
retained blobs, and Viktor's ruling is to keep ALL (no pruning); a history table is the only shape
|
||||
that does. `SaveHostEscrow` now, in one tx: reads the current row; if it seals a DIFFERENT
|
||||
`restic_pw_sha256`, COPIES it into the history table BEFORE the current row is overwritten; then upserts
|
||||
the new row. Same-sha re-upload (idempotent re-ceremony) → refresh only, no supersede row. Returns
|
||||
`superseded bool`.
|
||||
### Scenario A — host delete demotes, never destroys (`store.DeleteHost`)
|
||||
The v0.60.0 code deleted `host_escrow` + `host_escrow_superseded` on `deleteEscrow=true`. Now it
|
||||
DEMOTES the current row into `host_escrow_superseded` (copy-BEFORE-delete, same tx) and SPARES existing
|
||||
superseded rows. Extracted THE one escrow row-copy routine `demoteCurrentEscrowTx` (INSERT…SELECT) and
|
||||
refactored `SaveHostEscrow` to use it too — one copy routine, not two (rule 2). The F-14 provenance row
|
||||
+ gate semantics are unchanged (comments reworded: demotion, not destruction). Edge: no escrow row →
|
||||
unchanged; the `ErrHostEscrowPresent` refusal without the flag is unchanged.
|
||||
**Red-proof (run-fail-revert):** `TestDeleteHost_DemotesEscrowNeverDestroys` — pre-fix (delete both) →
|
||||
retained=0 → FAIL; fixed → both blobs (demoted current + spared superseded) retrievable, host gone.
|
||||
|
||||
### What ships (retention only — the guided-recovery flow is R-26, NOT built)
|
||||
- `SaveHostEscrow` rewrite + `CountSupersededEscrow` / `ListSupersededEscrow` (the latter seeds R-26).
|
||||
The hub still never decrypts; the ACK + restore-serving read the CURRENT row (`GetHostEscrow`) —
|
||||
**unchanged**. `DeleteHost(deleteEscrow=true)` also drops the retained rows.
|
||||
- Upload handler: `escrow_superseded` hub-internal audit event (tied to the owning customer) + a log
|
||||
line with the retained count. Operator host-detail DR/Backup panel shows "N superseded escrow
|
||||
blob(s) retained". Registered the controller-pushed `offbox_repo_orphaned` / `offbox_repo_reset` in
|
||||
`allowedEventTypes` + `customerMessages`.
|
||||
- **Red-proof** `TestSaveHostEscrow_RetainsSuperseded` (pre-fix destructive overwrite → old blob gone →
|
||||
FAIL; fixed → retained + retrievable via `ListSupersededEscrow`; same-sha idempotent = no supersede).
|
||||
### Scenario B — customer delete is the purge point (`store.DeleteCustomerConfig`)
|
||||
**What it did before:** ONLY `DELETE FROM customer_configs` — it touched neither hosts nor escrow
|
||||
(so deleting a customer left hosts + escrow blobs orphaned — an offboarding gap, see Observations).
|
||||
**Now:** one tx that purges `host_escrow` AND `host_escrow_superseded` for all the customer's hosts —
|
||||
INCLUDING already-deleted hosts (resolved via the F-14 `host_deletions` provenance `UNION`), so a
|
||||
host-delete-then-customer-delete ordering leaves nothing orphaned. Danger-zone copy states it.
|
||||
**Red-proof:** `TestDeleteCustomer_PurgesEscrowCustody` (pre-fix config-only → blobs survive → FAIL).
|
||||
|
||||
### Deploy
|
||||
Built `felhom-hub:0.60.0`, bumped `manifests/hub.yaml`, ArgoCD Synced/Healthy, rollout complete
|
||||
(`:0.60.0`). The `CREATE TABLE IF NOT EXISTS host_escrow_superseded` migration runs at startup
|
||||
(idempotent). ACK semantics unaffected (current-row reads only).
|
||||
### Scenario C — wording (`host_detail_body.html`, `hosts.go`, `customer_unified.html`)
|
||||
Host-delete escrow checkbox → "Move key escrow to retained custody (required when escrow present) +
|
||||
remove DR bundle for this host"; the 409 refusal message → "…move it to retained custody…"; the
|
||||
customer Danger-zone copy → "…permanently removes the retained recovery-key custody (escrow blobs)…
|
||||
This is the one true purge point; host deletion only demotes custody, never destroys it." Guard test
|
||||
`TestHostDeleteEscrowLabel_DemotionWording`.
|
||||
|
||||
### NOT done (registered as R-26)
|
||||
The guided "recover old history with the old recovery code" flow — Part B ships retention + visibility
|
||||
only; R-26 registers the recovery flow that consumes the retained blobs.
|
||||
### Scenario D — S6b necessity verdict: **OBSOLETE**
|
||||
**Verdict + landmarks:** re-enrolling an existing host_id upserts cleanly — `store.UpsertHost` is
|
||||
`INSERT … ON CONFLICT(host_id) DO UPDATE` (`internal/store/store.go:2099`), and
|
||||
`handleAdminCreateHost` (`internal/api/handler.go:913`) calls it with NO duplicate refusal (it even
|
||||
mints a fresh api_key). Combined with the v0.57.0 re-enroll arc (claim/offsite/escrow-honesty
|
||||
auto-fire on re-enrollment, `reissueOnReenroll` `handler.go:1020`), no manual stale-host deletion is
|
||||
required before re-enroll. Since S6b is not a literal runbook step, the verdict is recorded here + in
|
||||
ROADMAP R-3 ("re-enrollment upserts cleanly; S6b obsolete; escrow cascade now defined; the rehearsal
|
||||
live-confirms it"). Scenario A also makes the funnel harmless either way (deletion demotes, not
|
||||
destroys).
|
||||
|
||||
### Deploy verification
|
||||
`felhom-hub:0.60.1` built + pushed; `manifests/hub.yaml` bumped; ArgoCD Synced/Healthy; rollout
|
||||
complete (`:0.60.1`). The `demoteCurrentEscrowTx` path is INSERT…SELECT (atomic within the delete tx).
|
||||
|
||||
### Observations
|
||||
- **Customer-delete offboarding gap (unchanged this task, by scope):** `DeleteCustomerConfig` still
|
||||
does NOT delete the customer's HOST rows or the broader offboarding (Hetzner sub-account, WG peer,
|
||||
Storage-Box data) — only the escrow tables + the config row. That non-escrow lifecycle stays the
|
||||
delete/re-create rehearsal's question (ROADMAP R-3).
|
||||
- No pruning added (Viktor's data-first ruling); the ACK assembly + the upload supersede path are
|
||||
untouched.
|
||||
|
||||
Reference in New Issue
Block a user