docs: REPORT v0.82.0 (version channel deployed) + REUSE version-channel seam

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 15:45:04 +02:00
parent fa9c7fe198
commit 461eaf42c1
2 changed files with 30 additions and 49 deletions
+29 -49
View File
@@ -1,58 +1,38 @@
# REPORT — agent v0.81.0: NAS verify-before-commit (retry=0 + classifier + detached verify job) — DEPLOYED # REPORT — agent v0.82.0: local-API version channel (X-Felhom-Agent-Version) — DEPLOYED
**Date:** 2026-07-11 · **Class:** implementation (agent half of the cross-repo "NAS **Date:** 2026-07-11 · **Class:** implementation (agent slice of the NAS/coupling backlog-clear
verify-before-commit + page redesign" task). Baseline v0.80.0 (`300f06722b`) → **v0.81.0** task, Part B). Baseline v0.81.0 (`1e60e88`) → **v0.82.0** (`fa9c7fe`), live on felhom-pve.
(`added9d`), live on felhom-pve. Pairs with controller v0.113.0 + host-install v1.13.0. Pairs with controller v0.115.0 (version-aware Supports) + hub v0.45.0.
**Full task-wide record (red-proof outcomes, live AE transcripts, teardown):**
`felhom-controller/REPORT.md`. Evidence base: SPIKE-nas-verify-2026-07-11 (b57f6c1).
## What shipped (agent) ## What shipped
- **`retry=0` in the production NFS option string** (`netmount.go mountOptions`, Q4-vi): a dead-NAS - **`X-Felhom-Agent-Version` on EVERY local-API response** (`localapi/server.go`: the mux is wrapped
on-demand access fails clean in ~3.8 s (ENODEV) instead of a 91 s wedge; verify failures classify so the header is set on all routes + all statuses, INCLUDING auth failures and 404s — the
as `No route to host` instead of a diagnostic-free systemd timeout. SMB unchanged (mount.cifs controller learns the agent version passively from ordinary traffic). Value = `main.version` via
rejects `retry=`). Installed units are NOT rewritten (none existed on the demo anyway). the new `localapi.Options.AgentVersion`. No new routes, no envelope changes, **no sudoers changes**.
- **`internal/storage/netverify.go`** — `ClassifyNetVerifyFailure(journalTail, tcpReachable)`: - The controller (v0.115.0) reads it and capability-gates by version comparison; header-less
pure, first-match table on the Q4 VERBATIM substrings → `unreachable | nfs_export | smb_auth | (≤0.81.0) agents keep working — the controller falls back to the v0.114.0 route probe unchanged.
smb_share | timeout | mount_failed`; `nfs_export` deliberately merges not-found/not-permitted
(NFSv4 identical strings). String-based BY DESIGN — every mount failure exits rc=32.
- **Detached verify job** (`localapi/netverifyjob.go`, formatjob shape but IN-MEMORY single slot —
a restart empties the slot; `GET /netstorage/verify-status` phase `none` is the CONTROLLER's
rollback signal, Scenario F): add = decode → role-gate → **sync fast-fail** (full spec validation
+ 2 s TCP pre-probe; unreachable = HTTP 502 `{code:unreachable}`, NOTHING installed) → SMB creds
(0600) → EnsureNetworkMount → detached verify: trigger read through the automount, success judged
from **/proc/mounts ONLY** (§8: EACCES on a 0700 export is a GOOD mount), failure = journal
classification + **auto-rollback** (RemoveNetworkMount + creds). Single-flight: second add = 409.
- **Unprivileged journal read** (`journalctl -u <unit> -n 20 -o cat`, NO sudo, NO new sudoers
grant): needs the `systemd-journal` group (host-install v1.13.0; existing hosts:
`usermod -aG systemd-journal felhom-agent`). Unavailable ⇒ degrade to `mount_failed` + hint,
still rolled back.
- New exports: `storage.NetworkMountedAt`, `storage.NetworkEndpointReachable`. REUSE.md updated
(helpers + the in-memory-vs-persisted job pattern row + the four Server net-verify seams).
## Tests + red-proofs (agent side; outcomes verbatim in felhom-controller/REPORT.md) ## Tests + red-proof
A1 retry=0 (revert → fail) · A2 classifier table incl. merged nfs_export + empty-journal `TestVersionHeader_OnEveryResponse` (authed / unauthed / 404 all carry the header) +
degradation (exit-code mutant → every non-generic row fails) · A3 rollback effects via fakes `TestVersionHeader_OmittedWhenUnset`. Red-proof: dropping the Handler() wrap (return the bare mux) →
(drop rollback → `removed=[]` + creds linger) · A4 §8 truth table (readability mutant → both rows every row fails with an empty header (observed: `X-Felhom-Agent-Version = "", want "9.9.9-test"`),
fail) · A5 pre-probe zero-install (skip probe → 200-with-install) · A6 single-flight + no-job shape reverted → green. Full `go build && go vet && go test ./...` PASS (the known
(drop check → 409 lost). Green gate `go build && go vet && go test ./...` PASS (known TestGenerateRecoveryCode wordlist flake did not recur this run).
TestGenerateRecoveryCode wordlist flake hit once, clean on re-run).
## Deployed + verified (felhom-pve) ## Deployed + verified
`.bak-0.80.0` kept → `install -m0755``usermod -aG systemd-journal felhom-agent` felhom-pve: `.bak-0.81.0` kept → `install -m0755`restart; `felhom-agent --version` → 0.82.0;
`systemctl restart felhom-agent`. Verified: `felhom-agent 0.81.0`; `id felhom-agent` clean start journal (enrolled drive re-bound, local-api listening). Wire proof: an authed
`…,999(systemd-journal)`; clean start journal (enrolled drive re-bound, local-api listening, `GET /storage` to the live agent returns `x-felhom-agent-version: 0.82.0`. **NOT published**
desired-state gen 10). Live proof the journal path works: Scenario A's failure detail carried the demo-deploy only; Peti stays 0.81.0 (the live probe-fallback path). Hub now shows demo agent 0.82.0,
REAL `mount.nfs4 … reason given by server: No such file or directory` lines read unprivileged. Peti 0.81.0.
**NOT published:** 0.81.0 is deployed on the demo only — no Gitea publish, Day-0 manifest untouched ## Observations (documented, NOT acted on)
(the publish train is the operator's separate decision); Peti's box untouched.
## Observations - **Q1c FAIL surfaced during this task's Part E** (supervised, agent 0.82.0 live): a NAS automount
trigger does NOT survive a guest reboot — the guest sees an empty dir; the agent has no
- Pre-existing (2026-07-08, unrelated): `lanresolver: cannot list provisioned guests: permission network-mount reassert on guest reboot (unlike drive `ReassertGuestBinds`). Follow-up spec'd:
denied` — `/var/lib/felhom-agent/guests` is root-0700 under the non-root agent. `felhom.eu/documentation/backlog/FOLLOWUP-nas-automount-guest-reboot-reassert.md`. NOT this
- `RemoveNetworkMount` leaves the empty mountpoint dir + a `not-found failed` residual unit listing release — a distinct agent task.
(until `reset-failed`/reboot) — cosmetic, documented in the task report.
+1
View File
@@ -103,6 +103,7 @@
| Injectable seam funcs on Server | internal/localapi/server.go (`reresolveWipe`, `deviceDurableID`, `boundCheck`, net-verify: `netTrigger`/`netMounted`/`netJournal`/`netReachable`) | prod default wired in `NewServer`; tests override — no real /dev, /proc/mounts, journalctl or TCP in tests | | Injectable seam funcs on Server | internal/localapi/server.go (`reresolveWipe`, `deviceDurableID`, `boundCheck`, net-verify: `netTrigger`/`netMounted`/`netJournal`/`netReachable`) | prod default wired in `NewServer`; tests override — no real /dev, /proc/mounts, journalctl or TCP in tests |
| Detached IN-MEMORY verify job (single slot, deliberately unpersisted) | internal/localapi/netverifyjob.go | claim slot sync (single-flight 409) → detached pipeline off baseCtx → auto-rollback on fail; restart ⇒ slot empty ⇒ the CALLER rolls back (Scenario F) — contrast formatjob (persisted+recovered) | | Detached IN-MEMORY verify job (single slot, deliberately unpersisted) | internal/localapi/netverifyjob.go | claim slot sync (single-flight 409) → detached pipeline off baseCtx → auto-rollback on fail; restart ⇒ slot empty ⇒ the CALLER rolls back (Scenario F) — contrast formatjob (persisted+recovered) |
| Optional dependency degradation | internal/localapi/server.go (`Options`) | nil dep ⇒ endpoint answers "not configured" (503), never a crash | | Optional dependency degradation | internal/localapi/server.go (`Options`) | nil dep ⇒ endpoint answers "not configured" (503), never a crash |
| Version channel (v0.82.0) | internal/localapi/server.go (`Options.AgentVersion`; `Handler()` mux wrap) | sets `X-Felhom-Agent-Version` on EVERY response (all routes/statuses, incl. auth-fail/404) — the controller's capability-comparison source; empty version ⇒ header omitted |
| Root-file install via random temp | internal/localapi/intermediary.go (`installSharedParentUnit`) | `stageTemp` (os.CreateTemp) → sudoers-globbed `install -m` → pinned destination | | Root-file install via random temp | internal/localapi/intermediary.go (`installSharedParentUnit`) | `stageTemp` (os.CreateTemp) → sudoers-globbed `install -m` → pinned destination |
| Detached destructive job + restart recovery | internal/localapi/formatjob.go | persist `running` → run off baseCtx → record outcome; recovery re-resolves durable id, never a path | | Detached destructive job + restart recovery | internal/localapi/formatjob.go | persist `running` → run off baseCtx → record outcome; recovery re-resolves durable id, never a path |
| Signed-op verify pipeline | internal/authz/verifier.go (`Verify`) | armor→namespace→key-material allowlist→crypto over RAW bytes→blob→target→window→nonce LAST | | Signed-op verify pipeline | internal/authz/verifier.go (`Verify`) | armor→namespace→key-material allowlist→crypto over RAW bytes→blob→target→window→nonce LAST |