From 9d68d52feae944e648c0f946a5d1aea9558fc047 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 16 Jul 2026 23:02:30 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20hub=20v0.59.0=20+=20agent=20v0.89.0=20?= =?UTF-8?q?=E2=80=94=20REPORT/CONTEXT=20overwrite,=20capability=20map=20(p?= =?UTF-8?q?oke=20shipped,=20R-22=20F4=20closed,=20escrow=20live-reload),?= =?UTF-8?q?=20ROADMAP=20(R-22=20shipped,=20R-13=20first=20slice,=20R-3=20d?= =?UTF-8?q?eletion=20lifecycle)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTEXT.md | 13 +++ REPORT.md | 109 ++++++++---------- .../architecture/00-capability-map.md | 6 +- documentation/backlog/ROADMAP.md | 6 +- 4 files changed, 66 insertions(+), 68 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index e08aa86..b91d5ef 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -3,6 +3,19 @@ > 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-16 — DIRECTION-2a AGENT-PLANE POKE SHIPPED: hub v0.59.0 (LIVE).** The agent-plane + sibling of the v0.58.0 wait channel: `internal/poke` — a pinned-host-key SSH sender (wgsync + sibling) + fire-and-forget `Notifier` (`PokeHost`/`PokeAllHosts`, nil-safe, detached) that nudges a + box in seconds via a CONTENTLESS UDP poke relayed hub → ep0 `felhom-poke` forced-cmd → wg0-origin → + the box WG /32:51822. Wired at `applyPBSDR` (per-host) + `handleSetArtifacts` (fleet). ep0 surface + (`scripts/felhom-poke.sh`, non-root, no sudoers; runbook §11) + `Secret/agent-poke` + + `manifests/hub.yaml` (tag 0.59.0). **Deployed live** (ArgoCD Synced/Healthy, `agent-plane poke + enabled`); ep0 leg live-validated (`poke-fired`; non-WG + `rm -rf /` refused). Source note: the + MinAgent-floor save does not bump per-host generation (self-update rides signed-ops) → the fleet + poke accelerates the next report, not a delta. Pairs with felhom-agent v0.89.0 (the listener). + **OPEN:** the operator save→poke→tick round-trip + Scenario-3 latency await the demo agent reaching + 0.89.0 via the floor train (operator vouch). See REPORT.md. + - **2026-07-16 — DIRECTION-2 IMMEDIATE-SYNC SHIPPED: hub v0.58.0.** The hub→box "sync now" wait channel (option b of `documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md`). New `internal/intent` in-memory per-customer generation notifier (Bump/Wait/Close; coalesce-to-latest; diff --git a/REPORT.md b/REPORT.md index 15c61d8..f7ea3fb 100644 --- a/REPORT.md +++ b/REPORT.md @@ -2,69 +2,54 @@ > **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). -## Direction-2 immediate-sync — hub→box wait channel (hub v0.58.0) — 2026-07-16 +## Direction-2a agent-plane immediate-sync — poke sender + ep0 felhom-poke surface (hub v0.59.0) — 2026-07-16 -Implements option (b) of `documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md`. An -operator action on the hub now reaches the box in **seconds**. Pairs with controller **v0.140.0** -(the long-poll client). Baselines: felhom.eu `10e07f57` → hub **v0.58.0** (deployed); controller -`f665bbed` → **v0.140.0**. +**Baseline:** hub v0.58.0 → **v0.59.0** (LIVE on prod, ArgoCD Synced/Healthy). Pairs with felhom-agent +v0.89.0 (the poke listener). Green gate `go build ./... && go vet ./... && go test ./...` passes. +Design contract: `documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md` option (a). -### What shipped (hub) -- **`internal/intent` — in-memory operator-intent notifier.** Per-customer generation counter + - waiter registry: `Bump` advances + wakes (coalesce to LATEST — counter, not queue); `Wait` - returns on gen-change / ctx / maxHold / Close, with a pre-register gen-check closing the - bump-before-connect race; `Close` wakes all. In-memory BY DESIGN (a restart resets generations → - the box's `!=` compare costs exactly one harmless full-state report, never a storm). No - persistence, no schema. -- **`GET /api/v1/wait` (api/wait.go).** `checkAuthCustomer`; per-customer only (global key → 400; - customer resolved from the key, no `customer_id` param → A can't observe B). 240 s hold with a - **25 s heartbeat newline** — nginx's `proxy_read_timeout` is measured between upstream reads, so - the heartbeat keeps the default 60 s from firing → **no ingress annotation / manifest change**. - Contentless `{"gen":N}` completion; per-connection write-deadline via `http.NewResponseController` - (global `http.Server` timeouts untouched). -- **Intent bumps (web), fire-after-commit.** config create/update/delete, claim resend, offsite - re-issue (UI + re-enroll seam), offsite freeze/unfreeze, retrieval-password regen, block/unblock, - per-customer floor, global floor (bumps every config-managed customer), controller log-tail - request, controller log-bundle request (the AGENT ring rides the heartbeat envelope — deliberately - not bumped). -- **Wiring + shutdown.** One `intent.New()` shared by web + API; `intentHub.Close()` before - `server.Shutdown`. nil-safe (unset hub → wait 503, bumps no-op). +### What shipped +- **`internal/poke`** — the SSH poke SENDER (third sibling of `internal/wgsync`/`internal/tenantsync`): + a pinned-host-key (algorithm-pinned `ssh.FixedHostKey`) in-process client reusing the peersync + endpoint + host key, its OWN forced-command key. `Client.Poke(ctx, boxWGIP)` refuses any target + outside `10.77.0.0/24` BEFORE dialing, then SSHes to ep0's `felhom-poke` with the box WG /32 as the + command string (→ `$SSH_ORIGINAL_COMMAND`) which sends one empty UDP datagram to `:51822`. + `Notifier.PokeHost`/`PokeAllHosts` are fire-and-forget (detached goroutine, nil-receiver-safe) — a + poke never blocks or fails the operator save; a missing peer / SSH error is logged, the report cycle + reconciles. Tests: resolved-IP send, no-peer / store-error no-send, nil no-op, `Poke` pre-dial + non-WG refusal. +- **Wiring:** `Server.SetPoke`; `applyPBSDR` fires `PokeHost(host.HostID)` after each generation-bumping + descriptor save; `handleSetArtifacts` (MinAgent-floor save) fires `PokeAllHosts()`. `cmd/hub/main.go` + env-configures the sender (`POKE_SSH_KEY_FILE`, reusing `WG_ENDPOINT_SSH_ADDR`/`_HOSTKEY`/user). +- **ep0 surface:** `scripts/felhom-poke.sh` v1.0.0 — a NON-root (`felhom-peersync`, no sudoers grant) + forced command that validates `$SSH_ORIGINAL_COMMAND` to the WG /24 and sends one empty datagram + from wg0. Contentless + confined (the WG kernel refuses non-peer /32s — spike P1 EKEYREJECTED). + Runbook `documentation/runbooks/offsite-endpoint.md` §11. Port **51822** = shared cross-repo constant. +- **`manifests/hub.yaml`:** `POKE_SSH_KEY_FILE` env + optional `Secret/agent-poke` mounted 0400; image + tag `0.58.0`→`0.59.0`. -### Tests + red-proofs (all run-fail-reverted) -- intent: coalesce-to-latest (return the as-of-register snapshot → `TestWait_CoalescesBurstToLatestGen` - fails) and race-closer (drop the pre-register check → `TestWait_RaceCloser_BumpBeforeWaitNotLost` - fails). Plus timeout/ctx/close/isolation/stress. api/wait: 401/400/503, completes-on-bump, - timeout-with-heartbeats, race-closer, customer isolation. Full hub suite green (`go build/vet/test - ./...`); intent race-tested on the build server. +### Landmarks as found at source +- **Poke wiring is host-keyed** (`store.GetWGPeerForHost` → the box WG /32), the sibling of the + customer-keyed `internal/intent` long-poll (Direction-2b / controller plane). `SetHostDesired` bumps + the poked host's generation; the poked immediate report carries the advanced envelope generation → + the agent fetches the new descriptor. +- **Spec-vs-source note (source wins):** the MinAgent-floor / artifact-manifest save + (`handleSetArtifacts`) does NOT itself bump per-host desired generation — the agent self-update + dispatches via signed-ops on the next report — so `PokeAllHosts()` there accelerates the next report + cycle where the floor is applied, rather than delivering a desired-state delta. Kept the poke there + (the spec names this handler) and documented the semantics. -### Deploy (GitOps) -Image `felhom-hub:0.58.0` built + pushed on 180; `manifests/hub.yaml` bumped 0.57.0→0.58.0 (commit -`59eb1f8`); ArgoCD hard-refresh + patch-sync → **Synced/Healthy**, rollout complete, live image -`:0.58.0`, listening. Route smoke: `/api/v1/wait` → 401 (no/bad auth), 404 (wrong method). - -### Live validation (demo arc, customer `demo-felhom`) -- **Completion-on-difference path (the code path a bump triggers):** a box-side curl with a - mismatched `?gen=999999` returned `{"gen":0}` in **0.047 s** through the real ingress — sub-100 ms - wake delivery, DNS-only nginx path. -- **Hold proof / no-annotation claim:** a box-side curl at `?gen=0` (matching the current - generation) held cleanly for **240.04 s** (HTTP 200 → `{"gen":0}`, **10 heartbeat newlines** - observed) — **surviving well past nginx's 60 s default with the 25 s heartbeat, no ingress - annotation applied**. The controller's - own Waiter logged **zero wait-channel errors over the observation window** (it holds + reconnects - silently), a second confirmation. **No-annotation claim: HELD; the ingress fallback was NOT needed.** -- **Scenario C — hub restart resilience (live):** `kubectl rollout restart deploy/hub` mid-hold → - the box's Waiter logged **exactly one** WARN (`wait channel error: unexpected EOF — backing off`), - reconnected cleanly, and fired **0 out-of-cycle reports** (the generation was 0 before and after, - so there was correctly nothing to deliver — no restart storm). The immediate-probe endpoint - returned 200 the moment the pod was ready. (Cosmetic: the "recovered" INFO is deferred until the - reconnected 240 s hold completes, since `pollOnce` blocks — the reconnect itself is immediate.) - The "exactly one fire" restart case needs a prior non-zero generation, i.e. an operator bump. -- **Round-trip save→applied via the operator UI** requires an operator login (CC lacks the operator - password by standing constraint) — the mechanism is proven by the unit tests + the live - completion-path proof, and the delivery chain (report ACK → config_version → `ConfigRefresher` - re-pull + self-restart) is the already-live v0.139.0 / v0.26.0 path (unchanged). - -### Not changed / parked -Report ACK schema, the 15-min cycle, the 900 s agent poll, and the global `http.Server` timeouts are -byte-identical. The agent-plane ep0-relayed poke stays PARKED in the OOB / mutual-repair arc -(Viktor's ruling + spike P4). +### Live validation +- **Deployed:** hub 0.59.0 built + pushed on 180, manifest tag bumped + ArgoCD synced (Synced/Healthy, + rollout complete, image `:0.59.0`); after the Secret was created it logs `agent-plane poke enabled`. +- **ep0 poke surface installed live** (additive; `felhom-peersync` authorized_keys 2→3 lines, + peersync/tenantsync UNCHANGED; `/usr/local/bin/felhom-poke` 0755; `Secret/agent-poke` holds the + private key, the build-server copy shredded). +- **ep0 leg live-validated with the poke key:** a valid WG target (`10.77.0.2`) → `poke-fired`; + `8.8.8.8` AND `rm -rf /` as the SSH command → `refused non-WG target` (the forced command NEVER + executes client input, only reads it as an IP candidate). This proves the full hub→ep0→box datagram + path and the credential-theft blast radius (≤ "make a registered box tick"). +- **Not yet fired end-to-end from an operator save** (the config/floor save UIs are password-gated; CC + can't log in) and the box-side tick awaits the demo agent reaching v0.89.0 via the floor train + (operator vouch) — see the felhom-agent REPORT. Scenario 3's measured save-to-tick latency is the + operator follow-up. diff --git a/documentation/architecture/00-capability-map.md b/documentation/architecture/00-capability-map.md index d904a6d..7f50d12 100644 --- a/documentation/architecture/00-capability-map.md +++ b/documentation/architecture/00-capability-map.md @@ -32,8 +32,8 @@ | Appliance day-0 install: golden image → first boot → auto-confirm (zero clicks) → claimable box | installer, agent, hub, golden | **PROVEN-LIVE** (nested VM) | `DRILL-day0-vm-2026-07-12`, `DRILL-day0-take2-2026-07-12` | First firing on real customer hardware pending → R-1 | | BYO install: `--mode byo`, mandatory caps, host-mutation disclosure, coexistence guards | installer v1.15+, agent | **PARTIAL** | `DRILL-GL6-2026-07-08` (demo box); GL-8 coexistence fixes | Peti clean-slate reinstall on proxmox2 is the first real BYO run of the current path → R-1 | | Customer claim: one-time emailed code → customer sets own password (bcrypt, operator never sees it) | controller v0.122, hub v0.50 | **PROVEN-LIVE** (drill VM) | `DRILL-day0-vm-2026-07-12` §10/F-4 (gate ON via real edge; claimed, code consumed) | Never executed by a non-Viktor human → R-3. (Dropped mis-cited `CAMPAIGN-4` F-C — that is the escrow-claim 502, not password claim) | -| Escrow ceremony: customer-facing wizard, one-shot R claim, operator zero-knowledge | controller v0.127, agent v0.88 | **PROVEN-LIVE** (drill VM, endpoint-exact) | agent v0.88.0 REPORT (ceremony ~4s, one-shot claim 200→410, R absent from every payload); `SPIKE-controller-escrow-2026-07-13` | Endpoints driven on the drill VM; customer-facing **browser wizard** leg not yet live-validated. First supervised ceremony with a real customer pending → R-1 | -| DR tier by default: PBS + WireGuard base infra on every install, hub-controlled activation | installer v1.15, agent v0.86, hub v0.51 | **IMPLEMENTED** | `DRILL-day0-take2-2026-07-12` §2 (WG enabled both modes, PBS-DR descriptor auto-provisioned ~1s after WG registration, zero operator steps); ships installer v1.15/agent v0.86/hub v0.51 | Live only on demo/drill fleet. (Cited spike was slice-0 mechanics — shipped nothing; corrected. Candidate upgrade to PROVEN-LIVE — see REPORT) | +| Escrow ceremony: customer-facing wizard, one-shot R claim, operator zero-knowledge | controller v0.127, agent v0.88/0.89 | **PROVEN-LIVE** (drill VM, endpoint-exact) | agent v0.88.0 REPORT (ceremony ~4s, one-shot claim 200→410, R absent from every payload); `SPIKE-controller-escrow-2026-07-13` | Endpoints driven on the drill VM; customer-facing **browser wizard** leg not yet live-validated. First supervised ceremony with a real customer pending → R-1. **agent v0.89.0:** `/escrow/preflight` `pbs_storage_id` row now live-reloads (reads current agent.json) — a pbsdr convergence that seeds the id flips it green with NO service restart | +| DR tier by default: PBS + WireGuard base infra on every install, hub-controlled activation | installer v1.15, agent v0.86, hub v0.51 | **IMPLEMENTED** | `DRILL-day0-take2-2026-07-12` §2 (WG enabled both modes, PBS-DR descriptor auto-provisioned ~1s after WG registration, zero operator steps); ships installer v1.15/agent v0.86/hub v0.51 | Live only on demo/drill fleet. (Cited spike was slice-0 mechanics — shipped nothing; corrected. Candidate upgrade to PROVEN-LIVE — see REPORT.) **agent v0.89.0 closes the F4 non-default-storage-id gap (R-22):** the reconcile self-grants the ACL through the root wrapper on a pre-check 403 instead of dead-locking, so a descriptor-provisioned id (the demo's legacy `felhom-offsite`) converges hands-free — no more one-shot `pveum` grant | | Uninstall: KEPT-vs-WIPED statement, secret purge, enrolled-drive handling | installer | **PARTIAL** | `DRILL-GL6-2026-07-08` Phase 1/5 (KEPT-vs-WIPED printed verbatim; drive data intact ×3); GL-4 code | Secret purge (GL6-F1 `.bak` residue) fixed v1.12.0; enrolled-drive `mnt-*.mount` units survive (GL6-F2, open); cluster-aware `felhom_guests` guard + saferemove cost warning missing → R-9 | ## B. Apps & catalog @@ -106,7 +106,7 @@ | Scenario | Components | Status | Evidence | Gap / roadmap | |---|---|---|---|---| | Customer/host management: 8-tab detail, scoped auto-refresh, safe stale-host deletion, capability chips | hub v0.47–0.53 | **PROVEN-LIVE** | hub v0.53.0 dead-host roll-up live on the Peti cluster (proxmox1 down 23h); `CAMPAIGN-4-2026-07-13` (operator UI driven live); `DRILL-day0-take2` F-16 (offsite/freeze buttons live) | 8-tab render + capability chips are **render-test-validated** (hub UI is password-gated; CC cannot log in). (Cited "daily operator use" was a no-doc citation; `AUDIT-hub-gui-2026-06-30` predates these features at hub v0.25) | -| Config/state change round-trips in **seconds** (hub↔box immediacy; 15-min cycle stays the backbone): box→hub out-of-cycle report (Dir 1) + hub→box `GET /api/v1/wait` long-poll wake (Dir 2) | controller v0.139/140, hub v0.58 | **PARTIAL** | Transport proven live through the real DNS-only ingress: `SPIKE-immediate-sync-transport-2026-07-16` + hub v0.58.0 / controller v0.140.0 REPORTs — 240 s no-annotation hold (25 s heartbeat defeats nginx's 60 s `proxy_read_timeout`, no ingress change), 0.047 s wake-on-change, hub `rollout restart` = 1 WARN + 0-storm reconnect; Dir-1 2 s box→hub round-trip live in controller v0.139.0 | The operator-UI **save→apply** round-trip is not fired end-to-end live (hub UI password-gated; CC can't log in) → R-23; the wake transport and the ACK→config_version→`ConfigRefresher` delivery chain are each proven, only the UI-triggered bump leg is unexercised. Agent-plane (host-domain desired-state) poke is spiked-not-built → R-13 | +| Config/state change round-trips in **seconds** (hub↔box immediacy; 15-min cycle stays the backbone): box→hub out-of-cycle report (Dir 1) + hub→box `GET /api/v1/wait` long-poll wake (Dir 2) | controller v0.139/140, hub v0.58 | **PARTIAL** | Transport proven live through the real DNS-only ingress: `SPIKE-immediate-sync-transport-2026-07-16` + hub v0.58.0 / controller v0.140.0 REPORTs — 240 s no-annotation hold (25 s heartbeat defeats nginx's 60 s `proxy_read_timeout`, no ingress change), 0.047 s wake-on-change, hub `rollout restart` = 1 WARN + 0-storm reconnect; Dir-1 2 s box→hub round-trip live in controller v0.139.0 | The operator-UI **save→apply** round-trip is not fired end-to-end live (hub UI password-gated; CC can't log in) → R-23; the wake transport and the ACK→config_version→`ConfigRefresher` delivery chain are each proven, only the UI-triggered bump leg is unexercised. Agent-plane (host-domain desired-state) poke **first slice SHIPPED** (Direction-2a, agent v0.89.0 + hub v0.59.0, 2026-07-16): contentless ep0-relayed UDP poke → agent immediate desired-state cycle, per `SPIKE-immediate-sync-transport-2026-07-16` P4. **ep0 leg live-validated** (`felhom-poke` forced-cmd → `poke-fired`; non-WG target refused; hub logs `agent-plane poke enabled`); the full save→poke→tick round-trip awaits the demo agent reaching 0.89.0 via the floor train (R-13 first slice) | | Publish train: MinAgent floors, gated auto-Reissue, version channels, floor-field-LAST rules | hub v0.45/0.53, agent | **PARTIAL** | `runbooks/publish-train-rules.md`; demo-fleet updates proven | **Never proven on a real remote customer** — parked trains `RUNBOOK-publish-0.79/0.81/0.85-*` await Peti → R-1 | | Agent self-update: A/B slots, crash-loop auto-rollback, operator-signed | agent v0.70+ | **PROVEN-LIVE** (demo) | `SPIKE-agent-selfupdate-2026-07-05` | Remote-customer proof pending → R-1 | | Controller self-update: anonymous registry, no credentials in guest | controller v0.112 | **PROVEN-LIVE** (demo) | 07-10 arc | | diff --git a/documentation/backlog/ROADMAP.md b/documentation/backlog/ROADMAP.md index 774f915..1c425bc 100644 --- a/documentation/backlog/ROADMAP.md +++ b/documentation/backlog/ROADMAP.md @@ -22,7 +22,7 @@ |----|------|------|--------|--------------------------| | R-1 | **Peti convergence:** clean-slate proxmox2 reinstall (spec'd 07-15), first live auto-confirm, supervised escrow ceremony, execute parked publish trains (agent 0.81→0.88, controller → 0.137) | L | spec'd | Flips: publish train PARTIAL→PROVEN-LIVE; appliance/BYO/day-0 "real customer" notes; escrow ceremony. The single biggest unproven surface — an alpha where fixes can't ship remotely is dead. **Reinstall arc SHIPPED hub v0.57.0 (2026-07-16):** the clean-slate reinstall-of-existing-customer path is now first-class — claim re-issue (F2), offsite re-issue (F3), escrow-honesty-on-re-issue (2.3) all auto-fire on re-enrollment. Peti's proxmox2 clean-slate now walks a supported path | | R-2 | ~~Resolve ~215 lines of foreign WIP in felhom.eu clone (`hub/internal/notify/`, `store.go`, `hub/internal/claim/`)~~ | S | **killed** (2026-07-16) | Not a real issue: the "foreign WIP" was in-flight code from a concurrent CC session on the customer-claim arc, snapshotted before it committed. All of it landed cleanly — `notify/`+`claim/engine.go` in `6b40eb8` (v0.50.0), `store.go` in `a1d0450` (v0.54.0), plus follow-up `e205a2d`; v0.55.0 shipped. Working tree is clean, no stashes. Lesson already codified: never run two writing sessions on one felhom.eu clone (CLAUDE.md §git add -A) | -| R-3 | Friend-alpha onboarding runbook (generalized from `pilot/RUNBOOK-peti-return-2026-07-13`): hardware prep → golden → install → claim → ceremony → "first restore by the customer" scripted step | M | idea | Flips: "customer performs a restore" MISSING row; produces the tester-agreement sibling of `PETI-tester-agreement.md` | +| R-3 | Friend-alpha onboarding runbook (generalized from `pilot/RUNBOOK-peti-return-2026-07-13`): hardware prep → golden → install → claim → ceremony → "first restore by the customer" scripted step | M | idea | Flips: "customer performs a restore" MISSING row; produces the tester-agreement sibling of `PETI-tester-agreement.md`. **Next from-scratch rehearsal to include customer DELETE + re-create** — walks the never-validated deletion lifecycle (Hetzner sub-account deprovision? escrow blob cleanup? WG peer release?) | | R-4 | Claim-code deliverability: test-send to gmail.com / freemail.hu / citromail.hu / t-online.hu; tighten DMARC `p=none` → `p=quarantine` (pending since email.md 02-04) | S | idea | A claim code in spam bricks onboarding at step 1. Cheap, do before first invite | ## P2 — during alpha @@ -37,7 +37,7 @@ | R-10 | T-6E-1: DB-dump dir-fsync asymmetry (LOW, confirmed in 6E) | XS | idea | One-line hardening; batch with the next controller task | | R-11 | Tester-facing one-pager: what the box does, known limitations, how to report (channel decision: Messenger group?) | S | idea | Pairs with R-3 | | R-16 | Operator hygiene: campaign6 autofs orphan (clears on host reboot) + tied-CreatedAt flash duplicates (audiobookshelf/komga/romm) | XS | open | Viktor's own action items from 6D/6E | -| R-22 | **Agent-train: PBS-DR pre-check must not abort before the root-run apply grants the ACL (F4).** On a NON-DEFAULT PBS storage id, the agent's reconcile tick does a token-auth `GET /storage/` that 403s (the token has no ACL there yet) and aborts BEFORE its own root-run `felhom-pbs-apply grant` sets it — a bootstrap circularity. Fix: proceed to the root-run create+grant despite the pre-check 403 (or run the pre-check as root). | S | idea (agent) | From `tests/VALIDATION-n100-baremetal-2026-07-16.md` F4. Default-id customers unaffected (installer grants `felhom-pbs`); the demo's legacy `felhom-offsite` id hit it — unblocked live 2026-07-16 with a one-shot `pveum` grant. An installer fix is NOT viable (the id is descriptor-provisioned post-WG, not in the installer's config) | +| R-22 | **PBS-DR pre-check self-grant (F4).** On a non-default storage id the token-auth `GET /storage/` pre-check 403s (no ACL yet) and used to abort before the root-run `grant` that creates it. | S | **SHIPPED agent v0.89.0** (2026-07-16) | On a 403 the reconcile now self-grants via the root wrapper + re-reads, then converges. Red-proof `TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant`. Origin `tests/VALIDATION-n100-baremetal-2026-07-16.md` F4. | | R-17 | Old-box archive (u629193-sub1) retirement decision — 9/9 byte-identical restores verified | XS | awaiting-decision | Viktor ruling | | R-19 | Internet-outage customer-experience drill: pull WAN on demo, verify lan_resolver path, document what the customer actually sees/does | S | idea | Flips map row E "LAN access" IMPLEMENTED→PROVEN-LIVE | | R-20 | ~~Verify operator-key pinning is fully in the day-0 install flow~~ | XS | **closed** (2026-07-16) | Confirmed against `scripts/felhom-host-install.sh` source (not changelog): keys resolve at L1181–1219 (script constants `OPERATOR_KEY_*`, populated, `--operator-pubkey-file` override), pinned automatically by `step_agent_config()` "STEP 6/8" (L2044; python builds `authz.signers` L2146–2156, reinstall preserves existing), verified at L2332–2337 ("authz signers: N … operator-signed self-update armed"). No interactive prompt or post-install hand-edit — fully automatic. Doc-drift note: the L193–197 "EMPTY by default" comment is stale vs the now-populated constants (→ R-16 hygiene) | @@ -48,7 +48,7 @@ | ID | Item | Size | Status | Notes | |----|------|------|--------|-------| | R-12 | Cluster mode: agent-follows-guest, bind-mount reconciliation on HA migration | XL | idea | Scoped 07-15; interim = HA-group pin to one node. Driven by Peti's two-node cluster | -| R-13 | OOB management arc: dual-use existing WireGuard + hub desired-state channel as mutual-repair | L | idea | **Agent-plane immediate-sync nudge belongs here:** `SPIKE-immediate-sync-transport-2026-07-16` P4 measured the ep0-relayed poke (hub→ep0 forced-cmd→UDP→box, ~0.42 s, **zero** ep0/box infra change, cryptographically peer-confined) — the concrete "sync now" signal that reaches the **agent** (WG terminates host-side) for host-domain desired-state, reserved for this arc. Needs an agent train (UDP listener → immediate heartbeat). The controller-plane Direction-2 wait channel (hub v0.58 / controller v0.140) shipped this leg separately for the config puller | +| R-13 | OOB management arc: dual-use existing WireGuard + hub desired-state channel as mutual-repair | L | **first slice shipped (poke channel)** | **FIRST SLICE SHIPPED — the agent-plane poke channel (Direction-2a), agent v0.89.0 + hub v0.59.0 (2026-07-16):** the ep0-relayed contentless poke (hub→ep0 `felhom-poke` forced-cmd→UDP→box WG /32:51822, ~0.42 s, peer-confined, zero ep0/box infra change) now reaches the **agent** and fires an immediate desired-state cycle. ep0 leg live-validated; the full round-trip awaits the demo agent reaching 0.89.0 via the floor train. This is ONLY the listener+sender; the rest of the mutual-repair arc (self-heal actions over the channel) stays open. Per `SPIKE-immediate-sync-transport-2026-07-16` P4. The controller-plane Direction-2 wait channel (hub v0.58 / controller v0.140) shipped the config-puller leg separately | | R-14 | Headscale/WireGuard spike: Minecraft/gaming port connectivity (CGNAT-proof, sovereign DERP fallback) | M | idea | | | R-15 | Multi-user dashboard accounts (household members, roles) | L | idea | Single password is a stated alpha limitation (R-11) | | R-21 | **Bare-metal Felhom ISO** — per-PVE-release auto-install ISO for blank customer hardware → first-boot wrapper (invokes `felhom-host-install.sh`) → claim-code pairing (option C) | XL | **in-progress** (slice A shipped 2026-07-16; physical run 2026-07-16) | **PHYSICAL RUN 2026-07-16 (`tests/VALIDATION-n100-baremetal-2026-07-16.md`):** demo N100 reinstalled clean-slate from a pipeline ISO → chain reached **rc-0 first try on real hardware** (closes slice A's operator-gated boundary), serial-filter safety proven on metal, PBS-DR reconciler self-healed on the reused peer, DMI verdict = key on MAC+UUID. **F1 (HIGH, slice-B input):** this cheap AMI `AN3PLUS 0.01` firmware won't UEFI-boot the ISO's GRUB from USB (`relocation 0x0`) — SB-off/shim-bypass don't help; worked around live with a `grub-mkimage` loader built from the box's own GRUB. Pipeline must ship a firmware-compatible loader / PXE path. Reused-customer edges (F2 claim re-issue, F3 offsite re-issue, F4 non-default-storage-id ACL 403) feed R-1/Peti. UX: F6 drive-init doesn't mount+attach, F5 guest-RAM not configurable, F7 back-route. — **Slice A (build pipeline + first-boot bootstrap) DONE + validated on VM 310:** build gate/red-proof, disk-filter fail-safe, stub→retry-unit→real public-channel host-install fetch+invoke→retry, resume-decision, exactly-once, no-net retry+recovery all GREEN. Operator-gated remainder: host-install rc-0 terminal success (drill customer needs the password-gated create-UI). **Slice B** = physical N100 RUNBOOK (real DMI/firmware/USB/prior-LVM). **Slice C** = claim-pairing so the *distributed* ISO carries no retrieval passphrase (slice-A ISO is secret-bearing, supervised/single-use). Origin spike `audits/SPIKE-baremetal-iso-2026-07-16.md`: every mechanism GREEN on nested virt (VM 310 on felhom-pve). Zero-touch install BIOS **and** UEFI incl. **Secure Boot enforcing** (no MOK/keypress); first-boot hook `fully-up` = root + working pvesh/pct, exactly-once via `pending-first-boot-setup` flag; post-install **webhook** carries SMBIOS-UUID + management-MAC + host SSH keys → the unclaimed-appliance record; disk-filter installs only the target (canary byte-identical) and **fails-safe** on match-nothing / bad disk; `from-url` + `cert-fingerprint` **fails CLOSED**. OPEN (needs ONE real bare-metal run): vendor DMI serials (empty on virt), real firmware/NIC quirks, a pre-existing-LVM wipe step. Pipeline notes: assistant pairs to the ISO by Debian codename, ~11.5 s/ISO on DooPlex, **gate on `validate-answer` output not `$?`** (exit 0 on failure). Would flip a new capability-map MISSING row "customer self-installs on bare hardware" once spec'd. *(brief called this R-22)* |