hub v0.61.0 + felhom-tenantsync v1.1.0: Customer RESET (middle lifecycle tier)

One operator action returns a customer to pre-first-install: all operational
state dies (offsite repo, PBS namespace+backups, DR recipe, one-time secret,
claim state, retained escrow custody); identity + basic config + provenance +
events survive. Sits between host delete and customer Delete.

- store/customer_reset.go: customer_resets journal, live inventory, ack-gated
  purge (never touches identity/provenance/events), DeleteClaim.
- claim.ResetToUnclaimed: delete claim row -> fresh code next onboarding.
- offsite.Deprovision (idempotent) + OffsiteIdentifier + ClearProvisionedDescriptor.
- tenantsync.Deprovision + felhom-tenantsync.sh deprovision op (destroys ns +
  backup groups + token; shared user untouched; idempotent).
- web/customer_reset.go: GET reset -> inventory JSON; POST -> orchestration
  (external teardown FIRST, DB purge LAST; refuse-while-hosts; typed-id +
  separate escrow ack). Amber RESET card distinct from red Danger-zone Delete.
- Red-proofs: ack-gate + partial-failure resumability (both proven red);
  store ack-gating + journal round-trip; offsite idempotency + descriptor clear;
  RESET-card render. Green: build + vet + test.
This commit is contained in:
2026-07-17 13:09:04 +02:00
parent 6b1fbca51d
commit 4009401f46
17 changed files with 1193 additions and 13 deletions
+38
View File
@@ -1,5 +1,43 @@
# Felhom Hub — Changelog
## v0.61.0 — Customer RESET: the middle lifecycle tier (2026-07-17)
One operator action returns a customer to **pre-first-install**: every OPERATIONAL trace dies (offsite
repo, PBS namespace + backups, DR recipe, one-time secret, claim state, retained escrow custody), while
**identity and the basic config survive** (the `customer_configs` row, all provenance rows, and the
audit-event stream). It sits between the two existing tiers — *host delete* (< RESET) and *customer
Delete* (> RESET, the one true purge point). Viktor's rulings: (1) destroying retained escrow custody
gets its **own** separate acknowledgment; (2) RESET clears claim state (a fresh code next onboarding);
(3) RESET **refuses while any host row exists** (delete hosts first — reset never deletes hosts); (4)
the confirm surface shows a **live-counted** inventory.
Orchestration discipline (spec §3): external teardown FIRST, DB purge LAST (publish-last), every leg
idempotent → a partial run is simply re-run from the top; a failed external leg is a clean journal
entry and the DB purge (which erases the descriptors that say what still needs tearing down) is
withheld until every external leg is `ok`. Provenance + events are NEVER wiped.
- **Store** (`internal/store/customer_reset.go`, new): `customer_resets` journal table (per-attempt,
per-leg status, resumable); `CustomerResetInventory` (live counts: hosts, retained blobs via the
F-14 `host_deletions` UNION, dr_recipe/one-time-secret/claim presence); `Start/UpdateResetLeg/Finish/
LatestCustomerReset`; `PurgeCustomerResetDBState` (ack-gated escrow-blob delete + one-time-secret,
dr_recipe, log bundles — never touches identity/provenance/events); `DeleteClaim` primitive.
- **Claim** (`internal/claim/engine.go`): `ResetToUnclaimed` DELETES the claim row so `EnsureIssued`
mints a fresh first code on the next onboarding (no parallel revoked-flag, no stale generation).
- **Offsite** (`internal/offsite/offsite.go`): `Deprovision` DELETES the labelled sub-account/box
(idempotent — label-lookup, `len==0` = already gone); `OffsiteIdentifier` (preview name);
`ClearProvisionedDescriptor` (keeps the tier CHOICE `enabled/type/quota/box_type`, drops every
provisioned field). **PBS** (`internal/tenantsync/client.go` + `scripts/felhom-tenantsync.sh`
`deprovision` op): destroys the customer's namespace + all backup groups + token; the shared
`felhom@pbs` user is never touched; idempotent.
- **Web** (`internal/web/customer_reset.go`, new): `GET /configs/{id}/reset` → live inventory JSON;
`POST` → the orchestration (precondition + typed-id + escrow-ack gates BEFORE any write/external
call). A distinct **amber** RESET card on the customer page (separate from the red Danger-zone
Delete), with the typed-id confirm + the separate escrow-custody ack row.
- **Red-proofs**: ack-gate (defeat → reset proceeds & destroys blobs → FAIL); partial-failure
resumability (purge-not-withheld → DB purged despite external failure → FAIL); both proven red then
restored. Plus store ack-gating, journal round-trip, offsite Deprovision idempotency + descriptor
clear, and the RESET-card render test. Green: `go build ./... && go vet ./... && go test ./...`.
## v0.60.1 — host deletion DEMOTES escrow custody (never destroys) + S6b obsolete (2026-07-17)
Closes the deletion-path gap in v0.60.0's review: `DeleteHost(deleteEscrow=true)` was still DELETING