From 81954c3d2a7f65485b2e00cdc8af319e60394fe9 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 29 Jun 2026 23:32:11 +0200 Subject: [PATCH] docs: REPORT for v0.48.0 (agent reports served leaf fp, Part A) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ --- REPORT.md | 65 ++++++++++++++++++++----------------------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/REPORT.md b/REPORT.md index 4dc0b9c..fab1e2b 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,50 +1,31 @@ -# REPORT — F1: controller-swap verify hardening, v0.47.0 +# REPORT — agent reports served leaf fingerprint (hub re-key detection, Part A), v0.48.0 -**Task:** close F1 from the no-mercy testrun — a controller image with **no HEALTHCHECK that -crash-loops** could land a single "Running" inspect poll → the swap marked it healthy → **no -rollback** (alpine tagged as the controller passed in ~4 s, then `Restarting (0)`). +**Task:** the last self-health leg — the agent reports its served local-API **leaf fingerprint** so the +hub can detect an agent re-key fleet-wide (`host_leaf_changed`, hub v0.22.x). This repo = **Part A**. -**Baseline:** agent `main` @ `9b0d6c2` (live 0.46.0) → **v0.47.0** @ `3844df7`, sha `2d5a3ce4…`. +**Baseline:** agent `main` @ `3844df7` (v0.47.0) → **v0.48.0** @ `bf8e3be`, sha `a3dad050…`. -## The fix (`internal/localapi/controllerswap.go`) - -- **`controllerHealthy`** now reads a **4th** `docker inspect -f` field `{{.RestartCount}}`: - `running && RestartCount>0` → not-ok (a process that already crash-restarted isn't stably up, - regardless of healthcheck). It also returns a **`needsDwell`** signal for the no-healthcheck (`none`) - case. It stays a pure point-in-time predicate. -- **`verify`** adds a **stability dwell**: a no-healthcheck image must report ok on `verifyDwell` (=3) - **consecutive** polls before acceptance; a real `healthy` result is trusted immediately (Docker - already gated it). Any not-ok resets the dwell. Timeout → the existing rollback path runs. -- No change to `writeImage`, the sudoers grants, or the state-file/rollback orchestration. - -## §3 sudoers — no change (confirmed live) -The grant is `pct exec [0-9]* -- docker inspect -f *`; `*` spans the extended template. Live -`sudo -n -l` of the new `…|{{.RestartCount}}` template → **exit 0** (matches). No new grant. +## Change +- **`internal/hub/report.go`:** new `HostReport.LeafFingerprint string` (`json:"leaf_fingerprint"`) — + the SHA-256 of the leaf the agent currently serves. Empty when the local API is disabled (no leaf) → + the hub treats "" as unknown, never an alert. Not a secret (the fp is public; the token is never + reported). +- **`internal/hub/collect.go` + `cmd/felhom-agent/main.go`:** `Collector.SetLeafFingerprint(fp)` threads + the `fp` from `EnsureLeaf` (the SAME value the LOADED/REGENERATED log reports) into every report, next + to `Capabilities`. ## Tests (green: `go build/vet/test ./...`) -- **F1 red-proof** (`TestF1_Verify_CrashLoopRestartCountBlocks`): `RestartCount>0` → `verify` false + - `controllerHealthy` returns `(false, starting)`. **Companion:** the same image with `rc=0` + dwell=1 - **does** verify — proving the RestartCount check is what blocks the crasher (the old `running + none - → ok` shape had no guard). -- **Dwell** (`…DwellSingleOkThenCrash`): a single ok then a crash → `verify` false; **companion** - dwell=1 accepts the single ok. -- **Real healthcheck** (`…RealHealthcheckPassesPromptly`): `healthy` → `verify` true promptly, no false - rollback. Existing `RollbackOnUnhealthy` / `HealthyWithNoHealthcheck` stay green. +`TestCollect_LeafFingerprint`: the report carries the fp when set, and `""` when unset (local API +disabled) — companion proving the threading is what populates it. The golden + contract + field-names +tests updated; the cross-repo golden mirrors `leaf_fingerprint` (byte-identical with the hub copy). -## Live re-test (felhom-pve guest 9201) — the previously-found defect is now FIXED -Re-ran the exact F1 scenario: tagged alpine as `felhom-controller:9.9.9` (no healthcheck, crash-loops), -swapped to it via the real agent `/controller/swap`: -``` -swap state: failed error: "new controller did not become healthy within timeout" -controller-swap: rolled back to previous, controller healthy previous=…:0.91.0 -running controller: …:0.91.0 Up (healthy) -``` -**Before the fix** this image was marked `done` in ~4 s with no rollback; **now** the verify (polling -the `…|{{.RestartCount}}` template) never accepts it → 90 s timeout → **rollback to 0.91.0**. The -rollback's verify of the real 0.91.0 (which has a healthcheck) passed promptly — the real-controller -path is unaffected. Bad tag removed; final state: agent 0.47.0, controller 0.91.0 healthy, capabilities -45/45, channel up. +## Live validation (felhom-pve) — PASS +Deployed 0.48.0 (`leaf LOADED` fp `60b5974d…`, capabilities 45/45). The hub received the report with +`leaf_fingerprint: 60b5974d…` (confirmed in the hub DB), then — during the end-to-end re-key test — a +regenerated leaf (`82078fab…`) and its restore (`60b5974d…`) both rode the report stream and the hub +raised `host_leaf_changed` on the change (see felhom.eu/REPORT.md). Final state: leaf == pin +`60b5974d…`, capabilities 45/45, channel recovered. ## NOT changed -`writeImage`, the `FELHOM_CONTROLLERSWAP` grants, the state-file/rollback orchestration, the -capability/drive-gate/channel logic. Verify predicate + dwell only. +The pin/transport, the token store, the sudoers/capability surface, the swap, the channel-health logic. +Report contract + collector threading only.