0d463a859e
gates / gates (push) Successful in 6m12s
- closed: R-922 R-255 R-266 R-333 R-577 R-554 R-562 (controller 0.308.0 on demo-hp, demo-felhom, Tester 1), R-288 (map 212 KB -> 48 KB, detail per section) - R-521 narrowed to F7; R-388 design in 08 §8.1 (two questions for the operator) - privacy draft: why the registered address stays (S1); 02 module map: setup wizard deleted - STATUS + REPORT-answers-2026-10-12.md; docs.md routing line names capability-map-detail/ Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
365 lines
24 KiB
Markdown
365 lines
24 KiB
Markdown
# Capability map — detail, section E. Access, networking & household use
|
||
|
||
> **What this file is.** The long text of every row of section E of
|
||
> [`00-capability-map.md`](../00-capability-map.md), moved here **verbatim** on 2026-10-12 (R-288,
|
||
> R-290 folded in; `09` §3 decisions 214 and 229) from the map at commit `1b45bb4d`. Each entry is one
|
||
> former table row, its cells laid out one per paragraph; the cell text itself was not edited. The
|
||
> map row carries the capability's ONE status word and its dated evidence pointer; this file keeps
|
||
> the argument, the corrections and the history. **Where this text and the map row disagree on the
|
||
> status, the map row is current** (see the map header for the words that were normalised).
|
||
>
|
||
> A new correction goes **under the entry it corrects, dated** — never into the map row.
|
||
|
||
<a id="e1"></a>
|
||
## E1 — Remote access via Cloudflare Tunnel + Traefik (per-app subdomains)
|
||
|
||
*Map row: [E1](../00-capability-map.md#e1) · former map line 184.*
|
||
|
||
**Scenario (as written):** Remote access via Cloudflare Tunnel + Traefik (per-app subdomains)
|
||
|
||
**Components (as written):** cloudflared, traefik
|
||
|
||
**Status (as written):** **PROVEN-LIVE**
|
||
|
||
**Evidence (as written):** `CAMPAIGN-2` T-FLT-CF
|
||
|
||
**Gap / roadmap (as written):** Per-customer zone-scoped CF tokens (blast-radius ruling)
|
||
|
||
---
|
||
|
||
<a id="e2"></a>
|
||
## E2 — LAN access when internet is down (lan_resolver)
|
||
|
||
*Map row: [E2](../00-capability-map.md#e2) · former map line 185.*
|
||
|
||
**Scenario (as written):** LAN access when internet is down (lan_resolver)
|
||
|
||
**Components (as written):** agent
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence (as written):** —
|
||
|
||
**Gap / roadmap (as written):** Never drilled as a customer experience ("net down — can I reach my photos?") → R-19
|
||
|
||
---
|
||
|
||
<a id="e3"></a>
|
||
## E3 — Phone photo backup
|
||
|
||
*Map row: [E3](../00-capability-map.md#e3) · former map line 186.*
|
||
|
||
**Scenario (as written):** Phone photo backup
|
||
|
||
**Components (as written):** immich (classified)
|
||
|
||
**Status (as written):** **PROVEN-LIVE**
|
||
|
||
**Evidence (as written):** 6D end-to-end restore proof
|
||
|
||
**Gap / roadmap (as written):**
|
||
|
||
---
|
||
|
||
<a id="e4"></a>
|
||
## E4 — Documents/OCR
|
||
|
||
*Map row: [E4](../00-capability-map.md#e4) · former map line 187.*
|
||
|
||
**Scenario (as written):** Documents/OCR
|
||
|
||
**Components (as written):** paperless-ngx (classified)
|
||
|
||
**Status (as written):** **PROVEN-LIVE**
|
||
|
||
**Evidence (as written):** `CAMPAIGN-6C` 4P-1 (deploy paperless-ngx, ingest 3 docs via consume flow, OCR + PDF/A ~90s) + 4P-2/3/5
|
||
|
||
**Gap / roadmap (as written):** Consume-folder ingestion awkward without SMB → R-7
|
||
|
||
---
|
||
|
||
<a id="e5"></a>
|
||
## E5 — Files from Windows Explorer / Mac Finder (SMB server)
|
||
|
||
*Map row: [E5](../00-capability-map.md#e5) · former map line 188.*
|
||
|
||
**Scenario (as written):** Files from Windows Explorer / Mac Finder (SMB **server**)
|
||
|
||
**Components (as written):** controller v0.145.0 + `felhom-samba:1.0.0`
|
||
|
||
**Status (as written):** **PROVEN-LIVE**
|
||
|
||
**Evidence (as written):** `felhom-controller` REPORT.md (v0.144.0) + `controller/sharing.md`; transport verdict `audits/SPIKE-lan-discovery-2026-07-18.md`
|
||
|
||
**Gap / roadmap (as written):** „Megosztás" page: enable + one household password + shares (new folder or picked existing, per-share read-only). Fourth protected infra stack (host-net, smbd+**nmbd**+wsdd). Live on demo: 445 reachable, NetBIOS `FELHOM` resolves, write/read byte-compare PASS, write to a read-only share REFUSED, SMB writes land as uid 1000. **Explorer leg PASSED 2026-07-18 (Viktor):** Network → FELHOM → both shares open; a real Explorer *save* into `dokumentumok` landed owned uid 1000, and a write into the read-only `filmek` was refused by Windows with the folder left untouched. Share data RIDES BOTH BACKUP TIERS (R-7b, controller v0.145.0, Model B′ sibling shares source): tier-2 cross-drive legs + an offsite `_shares` restic snapshot carrying the share definitions and the credential copy, with a „Megosztások" restore. **All four legs PROVEN-LIVE on demo 2026-07-18** — tier-2 tree md5-verified; offsite snapshots `e0b9d723` (Viktor 12:18:16Z) and `4e2b15ec` both carrying manifest + `passdb.tar`; restore round-trip returned a deleted probe file byte-identical and a deleted share DEFINITION with its original flags without overwriting live files; samba liveness → hub-accepted `health_critical`. Remaining human leg: SMB positive auth with the real household password
|
||
|
||
---
|
||
|
||
<a id="e6"></a>
|
||
## E6 — Media to TV via DLNA
|
||
|
||
*Map row: [E6](../00-capability-map.md#e6) · former map line 189.*
|
||
|
||
**Scenario (as written):** Media to TV via DLNA
|
||
|
||
**Components (as written):** —
|
||
|
||
**Status (as written):** **MISSING**
|
||
|
||
**Evidence (as written):** —
|
||
|
||
**Gap / roadmap (as written):** Jellyfin app exists; DLNA/SSDP unvalidated → R-6, R-8
|
||
|
||
---
|
||
|
||
<a id="e7"></a>
|
||
## E7 — File access via browser
|
||
|
||
*Map row: [E7](../00-capability-map.md#e7) · former map line 190.*
|
||
|
||
**Scenario (as written):** File access via browser
|
||
|
||
**Components (as written):** FileBrowser (infra app, auto-mount sync)
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence (as written):** FileBrowser runs healthy + userdata-bound (`storage-lifecycle-acceptance-2026-06-15`, `CAMPAIGN-3`)
|
||
|
||
**Gap / roadmap (as written):** Actual browse/download **through FileBrowser** is exercised in no doc. (Cited `CAMPAIGN-2` T-PAGE-ALL renders only the controller dashboard pages, not FileBrowser.) Demoted. **2026-07-26, controller v0.172.0 (R-75) — status DELIBERATELY UNCHANGED.** The canonical drop-zone now has its own FileBrowser source („Beolvasás" → `/srv/beolvasas`, a separate bind of `<system namespace>/userdata/import`) and the app page carries a per-app deep link into it. Verified live on demo-hp: the source and bind are in the generated config, the app page renders `https://files.enkisfelhom.hu/files/Beolvas%C3%A1s/paperless`, and a file written through FileBrowser's OWN mount was consumed and deleted by paperless in ~30 s. **That is still not a browse.** Nothing in this arc drove the FileBrowser HTTP UI — no browser exists on DooPlex — so the row's standing caveat survives intact and the upgrade to PROVEN-LIVE remains unearned. What it would take: a human click-through, or an authenticated `/api/resources` round-trip against the live instance. See `controller/import-and-data-paths.md`
|
||
|
||
---
|
||
|
||
<a id="e8"></a>
|
||
## E8 — Indítópult (app launcher) — one-tap grid of the household's openable apps
|
||
|
||
*Map row: [E8](../00-capability-map.md#e8) · former map line 191.*
|
||
|
||
**Scenario (as written):** **Indítópult (app launcher) — one-tap grid of the household's openable apps**
|
||
|
||
**Components (as written):** controller v0.163.0
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence (as written):** New FIRST sidebar page `/launcher`: colored tiles (deterministic slug color or `.felhom.yml` `brand_color`) + white glyph/monogram, one per openable app (tile ⟺ „Megnyitás" — subdomain presence is the single criterion; controller excluded). Operational → `<a target=_blank>` to the public URL; stopped → greyed + state badge, no link. `/` stays the Vezérlőpult. Endpoint-level + render-test verified; `felhom-controller/REPORT.md` (2026-07-24)
|
||
|
||
**Gap / roadmap (as written):** Live operator click-through of a real tile → app pending (browser automation not available on DooPlex). Follow-up: curate `brand_color` for top catalog apps (R-72). Sharing the launcher outside the household is now the capability-URL guest link — see the row below
|
||
|
||
---
|
||
|
||
<a id="e9"></a>
|
||
## E9 — Indítópult megosztás (vendég link) — capability URL `/s/<token>` serves a standalone read-only guest launcher (no account, no admin session); optional per-share password; QR
|
||
|
||
*Map row: [E9](../00-capability-map.md#e9) · former map line 192.*
|
||
|
||
**Scenario (as written):** **Indítópult megosztás (vendég link)** — capability URL `/s/<token>` serves a standalone read-only guest launcher (no account, no admin session); optional per-share password; QR
|
||
|
||
**Components (as written):** controller v0.165.0
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence and gap (as written — this row's text carries a `|` outside code, so its last cells are kept together as they stood):** 160-bit `crypto/rand` token, constant-time match (empty stored = disabled = byte-identical to the mux default 404); guest headers `noindex`/`no-referrer`/`no-store`; optional SEPARATE bcrypt share password + its own per-IP attempt map; signed cookie `HMAC(token`\ | `passwordHash)` keyed with `session_secret` (rotate-token OR change-password invalidates all cookies); token redacted in logs (`/s/<redacted>`). Groups A–G (14 tests) + 3 red-proofs; **§13 endpoint-level live validation on 9201 all-pass** (`felhom-controller/REPORT.md` 2026-07-24). **Design ruling: member accounts SUPERSEDED by this capability-URL model; per-member tile visibility parked under the SSO arc (R-15).** | Full operator browser click-through + a validation doc pending → then PROVEN-LIVE. Accepted residuals: link-preview crawlers fetch once (noindex prevents indexing); reverse-proxy/CF access logs hold the path (ops-tier); the modal link carries the request Host (LAN-IP admin ⇒ LAN-IP link)
|
||
|
||
---
|
||
|
||
<a id="e10"></a>
|
||
## E10 — Forgot dashboard password → instant reset code
|
||
|
||
*Map row: [E10](../00-capability-map.md#e10) · former map line 193.*
|
||
|
||
**Scenario (as written):** Forgot dashboard password → instant reset code
|
||
|
||
**Components (as written):** controller v0.123, hub
|
||
|
||
**Status (as written):** **PROVEN-LIVE**
|
||
|
||
**Evidence (as written):** `DRILL-day0-take2-2026-07-12` F-15 (live re-run of the exact failure path: hash applied 1s after request, code accepted first try)
|
||
|
||
**Gap / roadmap (as written):**
|
||
|
||
---
|
||
|
||
<a id="e11"></a>
|
||
## E11 — The dashboard in English — the household picks its language; Hungarian unchanged
|
||
|
||
*Map row: [E11](../00-capability-map.md#e11) · former map line 194.*
|
||
|
||
**Scenario (as written):** **The dashboard in English** — the household picks its language; Hungarian unchanged
|
||
|
||
**Components (as written):** controller v0.247.0 (spike) → v0.250.0 (slice 1, R-556 CLOSED)
|
||
|
||
**Status (as written):** **PARTIAL — PROVEN-LIVE for every dashboard template** (all 36 templates carry their copy in the bundles; the switch is on every dashboard page)
|
||
|
||
**Evidence (as written):** `audits/i18n-slice1-2026-09-17/C/live/` (and `A/`, `B/`, `audits/i18n-2026-09-17/live/`) — on demo-hp 0.250.0 each Hungarian page equals its 0.249.0 fetch once the new switch form is removed (live numbers aside; login, claim, catch-all byte-identical); `POST /settings/language` made every page English and back; the hub stored `hu`, `en`, `hu` (16:45:10Z, 16:46:06Z, 16:46:43Z). Unit: `TestI18nParity` (106 states vs fixtures from unconverted templates), `TestI18nParityCoversEveryMarker`, `TestI18nEnglishPages`, `TestDirectRenderHandlersFollowLanguage`, all red-proofed. Design `architecture/10-localisation.md`
|
||
|
||
**Gap / roadmap (as written):** Go-side messages and three app-name page titles (R-557 after R-553, R-566), hub e-mails (R-558), console/download page (R-559), catalog copy (R-560), guide + English stranger drill (R-561); ASCII-only Hungarian invisible to the English page test (R-565). **Hungarian households see the switch only once the fleet floor reaches 0.250.0**
|
||
|
||
---
|
||
|
||
<a id="e12"></a>
|
||
## E12 — The box tells visitors apart — a stranger's wrong passwords lock only the stranger (dashboard, setup gate, apps that read the visitor from the right)
|
||
|
||
*Map row: [E12](../00-capability-map.md#e12) · former map line 195.*
|
||
|
||
**Scenario (as written):** **The box tells visitors apart — a stranger's wrong passwords lock only the stranger** (dashboard, setup gate, apps that read the visitor from the right)
|
||
|
||
**Components (as written):** controller v0.286.1 (`felhom-tunnel`, traefik trust of cloudflared only, `clientaddr.go`), catalog router resets
|
||
|
||
**Status (as written):** **PARTIAL**
|
||
|
||
**Evidence (as written):** `audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt` (real tunnel: the dashboard counter keyed on the visitor `37.191.56.193`, locked after 5 while forging a new X-Forwarded-For each try); `A/L1-9202-live.txt` (simulated tunnel: the household from another address in at once); `A/bookstack-3.6.txt`
|
||
|
||
**Gap / roadmap (as written):** The second OUTSIDE address on the real tunnel was refused by Cloudflare before the box → R-779; right-walking app settings → R-776; Emby/Jellyfin LAN rights → R-777
|
||
|
||
---
|
||
|
||
<a id="e13"></a>
|
||
## E13 — The family gate — a permanent door with each family member's own login in front of an app (Grimmory with its e-reader exceptions, MeTube with none)
|
||
|
||
*Map row: [E13](../00-capability-map.md#e13) · former map line 196.*
|
||
|
||
**Scenario (as written):** **The family gate — a permanent door with each family member's own login in front of an app** (Grimmory with its e-reader exceptions, MeTube with none)
|
||
|
||
**Components (as written):** controller v0.287.0 (`internal/family`, `stacks/family_gate.go`, the Család card), catalog `family_gate:` + `check-family-gate.py`, golden 0.287.0
|
||
|
||
**Status (as written):** **PROVEN** on 9202 through the product: a stranger reached nothing (36 requests, LAN + simulated tunnel), members with their own logins, reset/remove/logout end access at the next request, a stranger's guesses lock only the stranger, each exception keeps the app's own login and a look-alike stays gated, the controller down → error never the app; survives an update and a remove + restore. Demo boxes on 0.287.0. Not yet: a household's phone through the real internet.
|
||
|
||
**Evidence (as written):** `audits/family-gate-2026-10-02/A/items.txt`, `B/box/`
|
||
|
||
**Gap / roadmap (as written):** R-780 closed; R-775 (narrowed), R-796, R-797
|
||
|
||
---
|
||
|
||
<a id="e14"></a>
|
||
## E14 — Multiple household users / per-person accounts
|
||
|
||
*Map row: [E14](../00-capability-map.md#e14) · former map line 197.*
|
||
|
||
**Scenario (as written):** Multiple household users / per-person accounts
|
||
|
||
**Components (as written):** —
|
||
|
||
**Status (as written):** **MISSING**
|
||
|
||
**Evidence (as written):** —
|
||
|
||
**Gap / roadmap (as written):** Single dashboard password; acceptable for alpha → R-15
|
||
|
||
---
|
||
|
||
<a id="e15"></a>
|
||
## E15 — A second login step for the dashboard (a TOTP code or a passkey)
|
||
|
||
*Map row: [E15](../00-capability-map.md#e15) · former map line 198.*
|
||
|
||
**Scenario (as written):** A second login step for the dashboard (a TOTP code or a passkey)
|
||
|
||
**Components (as written):** —
|
||
|
||
**Status (as written):** **MISSING**
|
||
|
||
**Evidence (as written):** —
|
||
|
||
**Gap / roadmap (as written):** One password, one bcrypt hash (`controller/internal/web/auth.go:37-44`) → R-811 (added 2026-10-03)
|
||
|
||
---
|
||
|
||
<a id="e16"></a>
|
||
## E16 — The household can leave Felhom, or outlive it — the box runs without the hub, the household owns its domain, tunnel and off-site account, and can export everything
|
||
|
||
*Map row: [E16](../00-capability-map.md#e16) · former map line 199.*
|
||
|
||
**Scenario (as written):** The household can leave Felhom, or outlive it — the box runs without the hub, the household owns its domain, tunnel and off-site account, and can export everything
|
||
|
||
**Components (as written):** —
|
||
|
||
**Status (as written):** **MISSING** (as a written answer)
|
||
|
||
**Evidence (as written):** the LOST-hub half only: `_recovery-inventory-2026-07-28.md` §D2.4, `07` §8 row 11b
|
||
|
||
**Gap / roadmap (as written):** Leaving and hand-over are answered nowhere → R-810 (spike, added 2026-10-03)
|
||
|
||
---
|
||
|
||
<a id="e17"></a>
|
||
## E17 — The host agent cannot reach root without the operator key: exact sudo patterns, no agent-written file installed where root reads it without a content check, the agent binary only by an operator-signed update checked as root
|
||
|
||
*Map row: [E17](../00-capability-map.md#e17) · former map line 200.*
|
||
|
||
**Scenario (as written):** **The host agent cannot reach root without the operator key: exact sudo patterns, no agent-written file installed where root reads it without a content check, the agent binary only by an operator-signed update checked as root**
|
||
|
||
**Components (as written):** agent **v0.146.1** (R-861; delivered by a step bundle, R-880)
|
||
|
||
**Status (as written):** **PROVEN-LIVE on both demo boxes (2026-10-05) — with three named residuals**
|
||
|
||
**Evidence (as written):** `audits/hub-safety-2026-10-05/partF/` (real sudo: 23 of 29 attacks allowed before, 0 after; 64 capability commands allowed; `sudo -l` 93/93 on demo-hp and demo-felhom after the bundle; capability probe 67/67; a staged unit over `/etc/sudoers.d` refused live)
|
||
|
||
**Gap / roadmap (as written):** `03` §3.1: the controller-swap image ref (guest-scoped), the felhom-op SSH key (hub-delivered, unsigned; felhom-op's sudo is scoped), the escrow ceremony relays R → R-861
|
||
|
||
---
|
||
|
||
<a id="e18"></a>
|
||
## E18 — WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer)
|
||
|
||
*Map row: [E18](../00-capability-map.md#e18) · former map line 201.*
|
||
|
||
**Scenario (as written):** WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer)
|
||
|
||
**Components (as written):** agent v0.72, hub v0.35
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence (as written):** `SPIKE-oob-wg-operator-peer-2026-07-05`, `SPIKE-felhom-sshd-2026-07-05`
|
||
|
||
**Gap / roadmap (as written):** Mutual-repair desired-state arc not built → R-13. **CHECKED 2026-08-08 (R-260) and this row was NOT claiming something untrue** — it claims the capability is implemented, never that it is monitored, so no correction was owed. What WAS untrue is narrower and sat one layer down: **the hub's own OOB health check could not see whether the operator's key was installed.** `HostOOBRow` mirrored five of the agent's eight OOB fields, so `operator_key_configured` — emitted every heartbeat since agent v0.72.0, i.e. from this row's own vintage — was discarded by `encoding/json` on arrival, and `oobDegraded` returned `ok` for a box with felhom-sshd active, reachable, a valid config, a configured peer and **no operator key at all**. `operator_peer_configured`, which it did read, only says the peer IP is in desired-state — that OOB is MEANT to work, not that entry is possible. Fixed hub v0.99.0; the missing key now degrades and the alert NAMES it; a stanza too old to carry the field is reported distinctly and is never a silent ok. Pinned end-to-end from raw report JSON by `TestHostOOB_MissingOperatorKey_EndToEnd` and `TestHostOOB_NoKeyField_IsNotSilentlyOK_EndToEnd`
|
||
|
||
---
|
||
|
||
<a id="e19"></a>
|
||
## E19 — The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page
|
||
|
||
*Map row: [E19](../00-capability-map.md#e19) · former map line 202.*
|
||
|
||
**Scenario (as written):** The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page
|
||
|
||
**Components (as written):** agent **v0.119.0**, hub **v0.85.0**
|
||
|
||
**Status (as written):** **PROVEN-LIVE** (2026-07-31)
|
||
|
||
**Evidence (as written):** `audits/host-addresses-visible-2026-07-31.md`
|
||
|
||
**Gap / roadmap (as written):** Before this the LAN IP was **not reportable at all** — `HostMetrics` carried no address of any kind — and the WG IP existed only in `/offsite`'s peer table keyed by pubkey (peer→host, never host→peer). New wire field `addresses[]`, one row per (interface, address); `IsGlobalUnicast()` is the whole filter, chosen by MEASURING both demo boxes, and it needs no veth/fwbr denylist because that plumbing carries no IP. Rendered live on both 0.119.0 hosts matching their `ip addr` ground truth exactly. **Two honesty properties carry the risk and are both red-proofed:** WireGuard shows the hub ALLOCATION and whether the box CONFIRMS holding it (allocation alone cannot tell a live tunnel from a peer never applied), and an agent below 0.119.0 renders **UNKNOWN, never "no addresses"** — proven live on `drill-r50-0a4f9a` (0.113.0). **Not covered:** a two-LAN-bridge box and a real WG drift, neither of which exists to observe
|
||
|
||
---
|
||
|
||
<a id="e20"></a>
|
||
## E20 — The operator can see whether a managed host's guests still have working networking — and how often the watchdog had to repair them
|
||
|
||
*Map row: [E20](../00-capability-map.md#e20) · former map line 203.*
|
||
|
||
**Scenario (as written):** The operator can see whether a managed host's **guests still have working networking** — and **how often the watchdog had to repair them**
|
||
|
||
**Components (as written):** agent **v0.92.0** (emitter, 2026-07-21), hub **v0.104.0** (reader, 2026-08-13)
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence and gap (as written — this row's text carries a `|` outside code, so its last cells are kept together as they stood):** `backlog/OPEN-ITEMS.md` R-319; `hub/internal/web/hosts_guestnet_test.go` (7 tests, fixtures copied verbatim from `demo-felhom-8363b5`'s live `host_reports` row) | The agent emitted `guest_net` on every heartbeat for **twenty-three days** while the string occurred **nowhere** in `felhom.eu/hub/` — stored as raw text in `report_json`, read by nothing (R-260/R-264, the first of that census's readers to be built). **The fact that carries the risk is `heals_last_hour`, not `state`:** a guest the watchdog keeps repairing is healthy at every instant anyone looks, so rendering the state alone would give it a green tick — the failed-disk-drawn-as-a-healthy-empty-disk shape. `heal_succeeded` is decoded beside it, because six FAILED repairs is a guest that is down while six successful ones is a nuisance. **Unknown is never drawn as healthy:** three absences, three sentences (agent < 0.92.0; a capable agent that sent nothing; a guest whose own state the watchdog did not assert), and a malformed stanza degrades to unknown without a 500. **Three red-proofs, each mutation asserted applied by grep before its run**, including the one that matters — removing the unknown branches and watching a silent machine render as healthy. **Positive control that it is WIRED and not merely written: the wire-contract gate's checked-tag count rose 182 → 190** as the eight `guest_net` allowlist entries were deleted (an allowlisted tag is skipped, so leaving them would have meant these fields were never checked) | **IMPLEMENTED, not PROVEN-LIVE, and the distinction is the honest half.** Every scenario is proven against the real wire in tests, and the healthy case renders correctly for the live fleet — but **no machine has ever been observed with a climbing repair count on this card**, because neither demo box has needed a repair since the watchdog shipped. The signal this card exists for has therefore never been seen firing on hardware. It moves to PROVEN-LIVE the first time a real repair count is watched appearing. **No alarm was added, deliberately** (R-319): the incident behind this was about nobody being able to SEE the condition, and a new email on a fleet of two demo machines is untested noise — revisit when a third machine exists or when a count is seen climbing
|
||
|
||
---
|
||
|
||
<a id="e21"></a>
|
||
## E21 — Break-glass management-plane recovery
|
||
|
||
*Map row: [E21](../00-capability-map.md#e21) · former map line 204.*
|
||
|
||
**Scenario (as written):** Break-glass management-plane recovery
|
||
|
||
**Components (as written):** agent v0.71, hub v0.84
|
||
|
||
**Status (as written):** **IMPLEMENTED**
|
||
|
||
**Evidence (as written):** `runbooks/break-glass.md`
|
||
|
||
**Gap / roadmap (as written):** hub v0.84.0 adds an **operator-SESSION** retrieval path (host page → Console access → Reveal; `POST /hosts/{id}/reveal-recovery-credential`, CSRF-gated, writes a customer-visible `recovery_credential_revealed` event) beside the pre-existing **global-key** one (`GET /api/v1/admin/hosts/{id}/recovery-credential`), which is untouched and stays the route for when the hub UI itself is down. **The credential half is now PROVEN (2026-07-31):** the vaulted `demo-hp-bb76ea` password was verified against the box's own `/etc/shadow` hash AND minted a real PVE ticket — `POST /api2/json/access/ticket` → **HTTP 200, `root@pam`, 367-char ticket**, the exact API the login form submits to. Still IMPLEMENTED rather than PROVEN-LIVE overall, because the path has not been exercised on a REAL lockout (SSH was available throughout). Discovered during that check: the card's Copy button shipped `disabled` until a Reveal and silently no-opped, leaving ANOTHER host's password in the clipboard — fixed in hub v0.86.0. **Sealed at rest since hub v0.135.0 (R-133 CLOSED):** the off-site seal and key; live 2026-10-05: 4 legacy rows sealed at start-up, 0 left plain, and the demo-hp reveal still minted a PVE ticket (HTTP 200; a wrong password 401) — `audits/hub-safety-2026-10-05/partB/`. A database backup now needs `OFFSITE_SECRET_KEY` too → R-173
|
||
|
||
---
|