From 3efb1b1af1843bfb84ff673c05de4b8f681ee0a2 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 13 Jul 2026 15:02:44 +0200 Subject: [PATCH] docs: hub v0.53.0 closing bundle - CHANGELOG + CONTEXT (both operator rulings verbatim) + take-two F-14 disposition (SHIPPED, gated on escrow-acked deletion provenance) --- CONTEXT.md | 22 +++++++++++ .../audits/DRILL-day0-take2-2026-07-12.md | 2 +- hub/CHANGELOG.md | 37 ++++++++++++++++++- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index b75b6a1..da6aff8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -3,6 +3,28 @@ > Created with the REUSE.md rollout (2026-07-03). Authoritative history: `hub/CHANGELOG.md` (hub), > `website/CHANGELOG.md`, `scripts/CHANGELOG.md`; end-of-task detail in `REPORT.md`. +- **2026-07-13 — CLOSING BUNDLE SHIPPED: hub v0.53.0** — the last engineering items on the + pre-tester board: F-14 gated auto-Reissue (deletion provenance `host_deletions` in-tx + + `token_exists` gate → the EXISTING Reissue op + `pbsdr_auto_reissue` audit event; no record / + un-acked → refusal byte-unchanged), dead-host roll-up honesty (customer status = worst of + controller-derived + per-host `hostStatus` on dashboard//configs/detail, cause chip names the + host; the Peti 23h-masking shape is the pinned fixture), operator bearer OUT OF GIT + (`Secret/report-api` secretKeyRef non-optional + `REPORT_API_KEY` env override + + `scripts/manifest_bearer_gate.py`; VALUE ROTATION = supervised step in + documentation/runbooks/secrets.md — the git-history copy is dead only after it). + **Two operator rulings (2026-07-13), recorded verbatim — future sessions must treat these as + settled:** + 1. ***F-14:** auto-Reissue of offsite credentials is permitted ONLY when the hub's own deletion + record shows the tenancy's owning host was removed through the escrow-ack flow — acknowledged + destruction is not silent re-keying. No record → manual Re-issue stays the only path.* + 2. ***Roll-up:** a customer's status may never look better than its worst expected host. Single + staleness definition (`hostStatusOf`) — no second threshold anywhere.* + Gate additions: `python scripts/manifest_bearer_gate.py` after ANY manifests/ change (64-hex + bearer shapes; felhom.secret.yaml carve-out = visible KNOWN-BACKLOG line). GOTCHA: the repo + .gitignore `*secret*` pattern silently un-tracks any "secret"-named script — hence the + "bearer" name. The controller repo's controller.yaml.example carried the REAL bearer literal — + scrubbed (felhom-controller 51c871a). + - **2026-07-13 — POLISH BATCH SHIPPED: hub v0.52.0 (+ controller v0.123.0)** — take-two F-15 CLOSED (reset-request response carries the rotated code hash — ACK shape/guarantee — emailed codes work immediately; live 1 s apply + first-try accept on Viktor's exact failure path) and diff --git a/documentation/audits/DRILL-day0-take2-2026-07-12.md b/documentation/audits/DRILL-day0-take2-2026-07-12.md index ee8b866..2f6a908 100644 --- a/documentation/audits/DRILL-day0-take2-2026-07-12.md +++ b/documentation/audits/DRILL-day0-take2-2026-07-12.md @@ -59,7 +59,7 @@ capability chip table (v0.51.0) live. | # | Sev | Finding | Direction | |---|---|---|---| -| **F-14** | MEDIUM | Host delete + re-enroll while the ep0 tenancy survives = DR re-attach dead-end: auto-provision AND config save hard-error `token_exists`; "Re-issue PBS credentials" 400s (requires the descriptor the deleted host took with it). Recovery today = manual ep0 root token-delete (done during this reset) | candidate: flag-ON + no-descriptor + token-exists ⇒ auto-Reissue (re-adopt; the old secret is unrecoverable anyway) — needs an operator ruling vs the never-silently-re-key law | +| **F-14** | MEDIUM | Host delete + re-enroll while the ep0 tenancy survives = DR re-attach dead-end: auto-provision AND config save hard-error `token_exists`; "Re-issue PBS credentials" 400s (requires the descriptor the deleted host took with it). Recovery today = manual ep0 root token-delete (done during this reset) | **SHIPPED 2026-07-13** (hub v0.53.0, operator ruling): auto-Reissue permitted ONLY when the hub's own deletion record (`host_deletions`, written in the DeleteHost tx) shows the owning host was removed through the escrow-ack flow — acknowledged destruction, not silent re-keying; no record (incl. THIS drill's pre-record reset) / un-acked → the refusal + manual path, byte-unchanged. Scenario A/B tests + red-proofs; live validation = the next real host-reset cycle (fixtures carry it until then) | | **F-15** | MEDIUM/UX — **operator-flagged must-fix** | Claim/reset code NOT immediately usable: the hub rotates the hash at reset-request, but the box learns it only on its next report ACK (~15 min). Viktor hit it live ("Hibás vagy lejárt kód" with a fresh code) | **SHIPPED 2026-07-13** (hub v0.52.0 + controller v0.123.0): the reset-request response carries the rotated hash, applied via the ACK's generation-guarded consumer. Live re-run of the exact failure path: applied 1 s after the request, code accepted first try | | **F-16** | LOW | Native `confirm()` on hub buttons (offsite/PBS re-issue, freeze) froze CC's browser automation — the hub-side siblings of drill F-11 | **SHIPPED 2026-07-13** (hub v0.52.0 + controller v0.123.0): inline "Igen/Mégse" two-step everywhere, both repos; `hub_confirm_gate.py`/`native_confirm_gate.py` enforce zero native confirms. Live: the offsite re-issue completed under automation without freezing | | obs. | — | A re-provisioned guest over an EXISTING offsite repo needs the old repo password (gone with the deleted escrow) or a repo wipe — this IS the S5 DR-restore scenario, already queued as its own drill; the wipe was the correct reset action here, not a product gap | S5 drill | diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index b1dfdcd..38656c4 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,6 +1,41 @@ # Felhom Hub — Changelog -## v0.52.0 — polish batch: F-15 instant reset codes + F-16 inline confirms (2026-07-13) +## v0.53.0 — closing bundle: F-14 gated auto-Reissue + dead-host roll-up honesty + bearer out of git (2026-07-13) + +The last engineering items on the pre-tester board. Two operator rulings in force (CONTEXT.md): +F-14 auto-re-issue only on a recorded escrow-acked deletion; customer status never better than +its worst expected host. + +- **F-14 deletion provenance + gated auto-Reissue** (take-two MEDIUM: host delete + re-enroll + with a surviving ep0 tenancy = DR re-attach dead-end, `token_exists` on both auto-provision + and config save). New `host_deletions` table — host_id, customer_id, deleted_at, + `escrow_acked` (= ack given over a PRESENT escrow row) — written INSIDE the DeleteHost + transaction; NO backfill (pre-record deletions keep the manual path by design). The provision + atom, on `token_exists`, reads the customer's MOST RECENT deletion record: escrow_acked → + invoke the EXISTING tenantsync Reissue op, store the `pbsdr_auto_reissue` audit event + ("Previous key destroyed (acknowledged deletion) — credentials re-issued automatically."), + proceed; no record / un-acked → the pre-existing refusal byte-unchanged (never-silently-re-key + law). Red-proofs: provenance-write drop → scenario-A fails; gate bypass → scenario-B's + zero-reissue assertions fail (silent re-key visible as a 303). +- **Dead-host roll-up honesty** (drill-1 observation, live on the Peti cluster: proxmox1 down + 23h behind a GREEN customer row — controller reports ride the internet, independent of the + agent). Customer status on the dashboard, /configs list and customer detail (header + strip) + is now `worst(controllerDerived, hostStatusOf(each expected host))` via THE single staleness + definition (`Server.hostStatus`; no second threshold anywhere): any host down/stale caps the + customer at WARN with a cause chip naming the host ("host down: "); pending hosts worsen + only after the customer has ever reported (onboarding exclusion). The three inlined + controller-status chains collapsed into `controllerStatus()` (rollup.go). Display + + derivation only — HostStalenessChecker alerting untouched. Red-proof: fold removal → the + exact Peti fixture renders green → TestRollup_DeadHostMasking fails. +- **Operator bearer out of git** (the two publish runbooks' ROTATION item): `manifests/hub.yaml` + no longer commits `report_api_key` — the Deployment injects `REPORT_API_KEY` from out-of-band + `Secret/report-api` (deliberately NOT `optional:` — a missing Secret fails Ready instead of + booting an unauthenticatable hub); main.go gains the env override (RESEND_API_KEY twin). New + gate `scripts/manifest_bearer_gate.py` blocks bearer-shaped (64-hex) literals in manifests/ + (red-proven: reintroduction → exit 1). The controller repo's example-config copy of the + literal is scrubbed. The exposed git-history value dies with the SUPERVISED rotation — + procedure + full consumer list in documentation/runbooks/secrets.md §"Operator/global bearer + key" (per-customer/per-host keys unaffected). Hub half of the polish batch (take-two findings F-15/F-16). Companion: controller v0.123.0.