# REPORT — Hub v0.85.0: the Network card (2026-07-31)

**Overwritten** per the standing rule. Pairs with **agent v0.119.0** (see `felhom-agent/REPORT.md`);
neither half is useful alone. Live evidence:
`documentation/audits/host-addresses-visible-2026-07-31.md`.

> The earlier v0.84.0 work this file previously described (break-glass Console access card) shipped,
> deployed and validated in the same session; its durable record is `hub/CHANGELOG.md` v0.84.0 plus
> `CONTEXT.md` ruling **S-4** and `documentation/backlog/` **R-133**.

## 0. Follow-up shipped the same session — hub v0.86.0 (Copy without reveal)

**The operator hit a real defect in the v0.84.0 Console access card and it cost a login.** Copy was
`disabled` until a Reveal, so clicking it did **nothing, silently**; the clipboard kept its previous
contents — **another host's console password** — which was pasted into demo-hp's PVE login. It failed
with `password check failed for user (root)`, which reads exactly like a stale credential and sends
you diagnosing the wrong thing.

Diagnosis, read-only first: the vaulted password **matched the box's `/etc/shadow` hash**, the account
was unlocked and non-expiring, no TFA, no `pam_faillock` — and then it **minted a real PVE ticket**
(`POST /api2/json/access/ticket` → HTTP 200, `root@pam`, 367-char ticket). So the credential was
always good; only the clipboard was wrong. **That also closes the leg §7 of this report listed as
operator-only** — the capability-map row now records it.

**v0.86.0** makes Copy work without revealing (the *safer* default — the secret never renders, so it
cannot be shoulder-surfed or screenshotted) and closes three silent-failure branches in one eight-line
function: the disabled no-op, a missing `navigator.clipboard`, and an **ignored `writeText()`
rejection** that let the operator believe a refused write had succeeded. The success message now names
the host, because the clipboard is fleet-wide and "copied" alone cannot say for which box.

Tests 566 → 568, both pinning the regression; red-proof: re-adding `disabled` reproduces the shipped
bug. Deployed and live-verified (`Synced/Healthy`, image `:0.86.0`, button served without `disabled`,
retrieval URL defined exactly once, all four outcome messages present). Commits `670ec35` + `80f4739`.

## 1. Baselines

| Repo | `main` @ start of this half | Version before → after |
|---|---|---|
| felhom.eu (hub) | `b4edc087fa9a` | hub **v0.84.0 → v0.85.0** |
| felhom-agent | `6b5dade4dcc5` | agent **0.118.1 → 0.119.0** |

## 2. The ask, and what it turned out to be

*"Show the LAN and WireGuard IPs on the hosts page — it looks like it is not visible anywhere."*
Nearly exact, and the two halves were very different jobs:

- **WireGuard** — the hub already had it (`wg_peers.assigned_ip`, `GetWGPeerForHost`, **no UI
  consumer**). It was visible in exactly one place, `/offsite`'s peer table keyed by pubkey, so you
  could go peer→host and never host→peer.
- **LAN** — **not renderable at all.** `HostReport`/`HostMetrics` carried no address of any kind, so
  this needed a cross-repo agent release, not an edit. Confirmed against the struct and against a
  live box before proposing anything.

Operator chose: both together as one arc, with the `addresses[]` (iface + cidr) shape.

## 3. Files (hub half)

- `hub/internal/web/hosts.go` — `parseHostAddresses`, `hostAddressView`, `hostNetworkView`,
  `hostNetwork`, `minAgentForAddresses`, `hostDetailData` +1 key
- `hub/internal/web/templates/host_detail_body.html` — the Network card
- `hub/internal/web/hosts_network_test.go` (**new**)
- `hub/internal/api/testdata/host-report.golden.json` — the cross-repo contract, moved in lockstep
- `hub/CHANGELOG.md`, `REUSE.md`, `manifests/hub.yaml`
- `documentation/architecture/00-capability-map.md` (new row),
  `documentation/audits/host-addresses-visible-2026-07-31.md` (new)

## 4. Commits pushed to `main`

| Repo | Commit | What |
|---|---|---|
| felhom-agent | `14642e3` | v0.119.0 — the host report carries the box's addresses |
| felhom-agent | `4663df7` | REPORT |
| felhom.eu | `e07d90f` | hub v0.85.0 — Network card |
| felhom.eu | `37f7ff6` | manifests: hub 0.84.0 → 0.85.0 |

## 5. Tests + red-proofs (hub half)

Suite **559 → 566** (+7). `go build ./... && go vet ./... && go test ./...` **rc=0**, run separately
from the commit per standing rule 1.

| Test | Asserts | Result |
|---|---|---|
| `TestNetwork_A_LANAddressRendered` | the LAN address + its interface reach the page | PASS |
| `TestNetwork_B_WireGuardAllocatedAndConfirmed` | WG shown once, marked confirmed, not duplicated as a plain row | PASS |
| `TestNetwork_C_AllocatedButBoxDoesNotHoldIt` | the drift case renders `not confirmed by the box` | PASS |
| `TestNetwork_D_OldAgentSaysUnknownNotNone` | an old agent renders UNKNOWN, never "no addresses" | PASS |
| `TestNetwork_E_NoPeerAllocated` | no peer → says so; the box's wg address is not silently dropped | PASS |
| `TestNetwork_F_NoReportYet` | never-reported host says "waiting", not "no addresses" | PASS |
| `TestParseHostAddresses_RealWire` | parses the real wire; empty/malformed degrade without panic | PASS |

**The report fixture is the REAL wire** — the `addresses` block copied out of
`felhom-agent --selftest=hub` on demo-felhom running 0.119.0. A hand-written shape would have proved
only that the parser matches my own idea of the format.

| # | Mutation | Observed | Reverted |
|---|---|---|---|
| A | `hostDetailData` ships an empty `hostNetworkView{}` (the inert-seam shape) | `FAIL … the LAN address is not on the host page — the feature shows nothing` | yes |
| B | `WGConfirmed` set unconditionally from the allocation | `FAIL … an allocated-but-unheld peer renders as healthy — the drift is invisible` | yes |
| D | delete the `AgentTooOld` branch | `FAIL … an old agent's silence is not explained` | yes |

**Red-proof A took two attempts, and the first one proved nothing.** The initial mutation
(`_ = network` inside a map literal) did not compile, so the "failure" was a build error, not a test
result. Recorded because a red-proof that fails to build is indistinguishable from one that lands if
you only read the exit code.

## 6. Deployed

Image `gitea.dooplex.hu/admin/felhom-hub:0.85.0` built on DooPlex and pushed; `manifests/hub.yaml`
bumped in git; ArgoCD hard-refreshed and **deliberately synced** (never `kubectl set image`).

- ArgoCD app `felhom`: **Synced / Healthy**
- `deploy/hub` image: **`gitea.dooplex.hu/admin/felhom-hub:0.85.0`**
- Rollout: `deployment "hub" successfully rolled out` (rc=0)
- Startup log: `2026/07/31 08:50:57 [INFO] felhom-hub 0.85.0 starting`

**One sequencing note worth keeping:** the first sync fired before ArgoCD had re-read git, so it
reported `Synced/Healthy` while still running 0.84.0 — a green pair that meant nothing. The fix is to
wait until `.status.sync.revision` equals the pushed commit **before** trusting the sync, which the
final run did.

## 7. Live validation

**Method: endpoint-level** (no browser on DooPlex), against the deployed hub, cross-checked
against `ip addr` read directly over SSH on both boxes.

| Host | Agent | Network card, live | Matches ground truth? |
|---|---|---|---|
| `demo-felhom-8363b5` | 0.119.0 | WG **10.77.0.2 confirmed**; `vmbr0 192.168.0.162/24`, `tailscale0 100.70.170.35/32` + ULA | **yes, exactly** |
| `demo-hp-bb76ea` | 0.119.0 | WG **10.77.0.3 confirmed**; `vmbr0 192.168.0.87/24`, `tailscale0 100.76.96.79/32` + ULA | **yes, exactly** |
| `drill-r50-0a4f9a` | 0.113.0 | WG 10.77.0.4 **allocated**; *"agent (0.113.0) does not report its addresses — they are unknown, not absent. Needs 0.119.0 or newer."* | correct UNKNOWN rendering |
| `sess-f-2670b5` | old | same UNKNOWN rendering | correct |

Correctly absent from every card: `lo`, `vmbr9`'s `169.254.253.1` island literal, every `fe80::`.

**A fifth host 404'd and it was not a bug.** `sess-g-e687c3` appeared in a host list I read 30
minutes earlier and returned 404 when I fetched its page; the concurrent session had deleted it as
part of its scratch-customer cleanup. Chased to ground before reporting rather than filed as a defect.

## 8. Not yet validated / not covered

- **A box with two LAN bridges** — handled by design (one row per interface) and unit-tested; no such
  box exists to observe.
- **A real WireGuard drift** — the `not confirmed` branch is unit-tested and red-proofed but never
  seen in production, because no live peer is currently unapplied.
- **IPv4-only / IPv6-only hosts** — every box here is dual-stacked via tailscale's ULA.

## 9. Teardown

This run provisioned nothing — no guest, no VM, no scratch customer, no external resource. The only
mutations are two in-place agent upgrades (previous binaries retained as `.bak-0.118.1` on both
boxes), the Day-0 manifest agent vouch, and the hub image bump.

## 10. Backlog

- **Opened: none.** **Closed: none.**
- **R-115 / R-121 touched, not closed.** This release walked all three legs deliberately —
  deployed, **published** (round-trip verified) and **vouched** (read back) — so no new drift
  instance was created. The finding those rows record is that nothing *notices* when the legs
  diverge, which is unchanged. One small contribution: the Network card now surfaces
  "this host's agent is older than 0.119.0" per host, which is a per-field hint, not the
  installed-vs-vouched checker R-121 asks for.
- **Concurrent session:** every path was staged explicitly; no foreign WIP was swept into any commit.
