Files
felhom.eu/documentation/architecture/capability-map-detail/E.md
T
admin 0d463a859e
gates / gates (push) Successful in 6m12s
answers 2026-10-12 Parts D, F, G: controller 0.308.0 delivered, the capability map restructured, the notification design; 128 -> 97
- 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
2026-10-12 15:54:44 +02:00

24 KiB
Raw Blame History

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, 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.

E1 — Remote access via Cloudflare Tunnel + Traefik (per-app subdomains)

Map row: 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)


E2 — LAN access when internet is down (lan_resolver)

Map row: 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


E3 — Phone photo backup

Map row: 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):


E4 — Documents/OCR

Map row: 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


E5 — Files from Windows Explorer / Mac Finder (SMB server)

Map row: 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


E6 — Media to TV via DLNA

Map row: 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


E7 — File access via browser

Map row: 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


E8 — Indítópult (app launcher) — one-tap grid of the household's openable apps

Map row: 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


Map row: 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)


E10 — Forgot dashboard password → instant reset code

Map row: 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):


E11 — The dashboard in English — the household picks its language; Hungarian unchanged

Map row: 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


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 · 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


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 · 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


E14 — Multiple household users / per-person accounts

Map row: 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


E15 — A second login step for the dashboard (a TOTP code or a passkey)

Map row: 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)


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 · 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)


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 · 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


E18 — WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer)

Map row: 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


E19 — The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page

Map row: 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


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 · 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


E21 — Break-glass management-plane recovery

Map row: 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