docs: hub v0.59.0 + agent v0.89.0 — REPORT/CONTEXT overwrite, capability map (poke shipped, R-22 F4 closed, escrow live-reload), ROADMAP (R-22 shipped, R-13 first slice, R-3 deletion lifecycle)

This commit is contained in:
2026-07-16 23:02:30 +02:00
parent 319ba10d9f
commit 9d68d52fea
4 changed files with 66 additions and 68 deletions
+13
View File
@@ -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;
+47 -62
View File
@@ -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 `<ip>: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.
@@ -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.470.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 | |
+3 -3
View File
@@ -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/<id>` 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/<id>` 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 L11811219 (script constants `OPERATOR_KEY_*`, populated, `--operator-pubkey-file` override), pinned automatically by `step_agent_config()` "STEP 6/8" (L2044; python builds `authz.signers` L21462156, reinstall preserves existing), verified at L23322337 ("authz signers: N … operator-signed self-update armed"). No interactive prompt or post-install hand-edit — fully automatic. Doc-drift note: the L193197 "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)* |