Evening 2026-10-18: fresh install proven (R-1148), OnlyOffice walk (R-1018), one-sign-in spike (R-1100 READY), Redis volumes (R-1117), CI rows, golden 0.331.0 baked; register 168 -> 168 (closed 6, opened R-1149..R-1154)
gates / gates (push) Successful in 7m34s

This commit is contained in:
2026-10-18 23:12:21 +02:00
parent b9b56c47c0
commit e4f022a0d9
90 changed files with 1634 additions and 22 deletions
+12 -1
View File
@@ -26,6 +26,17 @@
---
## 2026-10-18 (evening) — a fresh install from scratch, OnlyOffice saves, Redis without a volume, CI mail link (controller v0.331.0, catalog `30eea97`)
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1148** | **Installer 1.34.0 is published (operator yes, 2026-10-18) but its default path has not run on a fresh install.** Step 4b now vaults FIRST, then sets the password, then checks a PVE login (R-1145); the SC2066 loop became an `if`. Proven: the harness (52/0, the R1145 cases run the real step with fake commands), `test_hostinstall.py` 40/0, a real `--rotate-recovery-only` on demo-hp (vault time moved, new value 200, old 401). Not proven: a whole fresh install on a machine, where step 4b runs inside the full sequence and `https://127.0.0.1:8006` must answer during it. (P3) | CLOSED 2026-10-18 — **proven on a fresh install:** VM 9302 on demo-hp from the public ISO 1.29.0, which fetched installer **1.34.0** at run time; throwaway customer Tester4 (the operator's). Step 4b in the full sequence: „vaulted at the hub (stored) — now setting it” → chpasswd → „[OK] … the PVE login takes it”, 18:03:43 UTC, the hub's `host_recovery.set_at` the same second; CC's own PVE login from DooPlex: vaulted value 200, the installer's value 401. The manifest named the vouched agent 0.159.2 and golden 0.329.0 (sha-verified); the box came up on 0.329.0 and moved to the floor 0.330.0 by itself (`initiated_by: auto-floor`) 2 min 47 s after the bind; claimed, BentoPDF installed and healthy; turned off by the household's button (deliberate). VM destroyed by name. **The operator keeps Tester4 as the standing fresh-install customer** (2026-10-18); before its next install the old host `Tester4-31fa0e` must be deleted on the Hosts page, or the enroll reuses it and skips the re-issues (memory „reinstall without deleting the host”). | `audits/newbox-2026-10-18/` |
| **R-1018** | **The OnlyOffice links (FileBrowser, Nextcloud) have no recorded live proof since the move to Proxmox.** `controller/internal/integrations/` holds two handlers; no audit after the Proxmox move shows a document opened from FileBrowser or Nextcloud in OnlyOffice (searched 2026-10-14). The media-app links of `15` §6 will copy this code, including its revoke-on-stop rule (`lifecycle.go:10-13`). (P3) | CLOSED 2026-10-18 — **walked live and fixed, controller v0.331.0 (floor):** on 0.330.0 FileBrowser → OnlyOffice never loaded a document (OnlyOffice fetched the public `files.<domain>` URL: 403) and Nextcloud → OnlyOffice lost every save (`http://onlyoffice:80cache/…` — no slash); switching the link on or off left files.<domain> 502 for 3.5–4 min. Fixed: FileBrowser `server.internalUrl: http://filebrowser:80`, Nextcloud `DocumentServerInternalUrl` with its slash, the R-1126 gate after any FileBrowser restart. On rc.1 both paths open, edit, save and read back; the revoke-on-stop rule holds (no editor in FileBrowser, Nextcloud 404); the stop's outage ≈ 2 s. Each fix red-proofed. | `audits/onlyoffice-2026-10-18/` |
| **R-1117** | **Immich's and Nextcloud's Redis keep a volume they do not need** (upstream runs both without one; queues and caches only). Deferred from R-1096 on purpose: removing a volume from the compose leaves the old volume on installed boxes, and the nightly dump / an old backup's restore for a volume the compose no longer declares is unchecked. The RDB switch (catalog `5d9450b`) already removed the full-disk danger. (P4) | CLOSED 2026-10-18 — **measured, then changed (catalog `30eea97`):** both Redis services run with `--save ""`, tmpfs `/data`, no volume. On scratch 9202 with the new shape: both apps healthy, Immich's forced metadata job ran (counter 0 → 11, 0 failed), Nextcloud `cron.php` rc 0; the nightly dump skips the dropped volume; a second-drive restore from a copy made WITH the volume succeeded and, by design, brought back that copy's compose. The old volume stays attached under the tmpfs; **a removal left it behind — fixed in controller v0.331.0** (the removal deletes volumes still labelled with the app's project; red-proofed). | `audits/redis-volumes-2026-10-18/` |
| **R-1135** | **CI's failure mail links the run at the in-cluster Gitea address** (`http://gitea.gitea-system.svc.cluster.local:3000/...`), which a reader outside the cluster cannot open. Seen in the forced test mail 2026-10-17 (job 1997). (P4) | CLOSED 2026-10-18 — the alarm link is now `https://gitea.dooplex.hu/<repo>/actions/runs/<GITHUB_RUN_ID>` in all four workflows (felhom.eu `1fe9f848`, controller `615940d`, agent `79ba884`, catalog `c97eb1c`). The run NUMBER was a second defect: Gitea 1.26 reads it as an id first and opened an unrelated run (felhom.eu runs/1116 → id 1116 = #613). Proof: forced job 2067 (catalog) at 18:46:01Z, one mail (Gmail, 18:46:02Z), link `…/actions/runs/2067`, also printed as `ALARM-LINK` in the job log. | helper evidence in the session scratchpad `p5/`; job 2067 |
| **R-1136** | **felhom.eu CI runs take ~6.9 minutes and complete, so the 5-minute job timeout R-265 assumes does not hold there** (runs 1996, 1997; `script-tests` alone ~356 s). Found by the R-1129 proof helper 2026-10-17. (P4) | CLOSED 2026-10-18 — measured: the runner's real limit is act_runner 0.6.1's default `runner.timeout` **3 h** (`act-runner-config` sets only capacity, fetch_timeout, fetch_interval); the job-level `timeout-minutes: 5` is parsed and NOT enforced by act_runner 0.6.1 (only step-level is) — 231 felhom.eu jobs ran past 300 s and passed (longest 529 s, job 2038). `script-tests` (~7 min) is far from 3 h: no speed-up. R-265 corrected; the step-level timeout is R-1152. | act_runner v0.6.1 `config.go:128`, `act/runner/runner.go:164`, `step.go:206` |
| **R-1137** | **A CI rerun may overwrite a failed job's log** — failure mails arrived for controller commits `c02c9d83` and `29a3ce8c` while their jobs (1944, 1959) now read green. UNPROVEN (a reading, 2026-10-17). (P4) | CLOSED 2026-10-18 — **confirmed:** a rerun replaces the job record the `jobs` endpoint shows (same id, `run_attempt` 2, the rerun's conclusion and log); the first attempt survives only as a task in `actions/tasks` (`status` failure) and its log is not readable by the API (controller job 1944 = tasks 1962 fail + 1964 ok; 1959 = 1978 fail + 1980 ok). The pull check in felhom.eu `CLAUDE.md` now reads `actions/tasks` when `run_attempt` > 1 (`b9b56c47`). A scratch-repo red run was not possible: the admin token lacks `write:user` and push-to-create is off — measured on the two real cases instead. | felhom.eu `CLAUDE.md` CI pull-check item 3 |
## 2026-10-18 (day) — hub 0.158.0 live, „anyway" restarts the apps, vault-first installer 1.34.0, the estimator's Immich figure (controller v0.330.0, agent v0.159.2)
| Row | What | Closed | Evidence |
@@ -1254,7 +1265,7 @@ Nine rows closed. **Full original text: `git show <this commit>^ -- documentatio
| **R-258** | **C3 — the customer's per-app backup tick is green on the PRESENCE of a restore point, and its only red condition is a GLOBAL one.** | **CLOSED 2026-08-08 — controller v0.210.0.** `appDumpVerdict` reads THIS app's own dump result; three states, no icon when nothing is known. **Recency deliberately not added** — see the observation in the follow-through section | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-259** | **C4 — a disk read that FAILS renders as „0.0 GB / 0.0 GB (0%)" in the nominal colour, on the dashboard's most-looked-at meter.** | **CLOSED 2026-08-08 — controller v0.210.0.** `readDiskUsage` reports success; `SystemInfo.DiskKnown`/`HDDKnown`; the template draws no figure, no percentage and no meter fill when unknown. **The hub leg is deliberately NOT fixed and is now R-266** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-260** | **C5 — the agent reports at least eight decision-bearing facts the hub models NOWHERE, and the sharpest one blinds the check that answers „can the operator get into this box".** | **CLOSED 2026-08-08** — the class is GATED (G-1, `scripts/wire_contract_gate.py`) and the sharpest instance is fixed (hub v0.99.0). The remaining unconsumed facts are **R-264, OPEN** — allowlisted with reasons, which is not the same as decided. See the follow-through section below | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-265** | **A CI run can fail with NO LOG PERSISTED, and the alarm mail then points the operator at a log that does not exist.** | **CLOSED 2026-08-08 — `timeout-minutes: 5` on the gates job, and the alarm mail now states elapsed seconds and qualifies its own "names itself in the run log" sentence.** ⚠ **The unknown is NOT closed and must not be read as closed:** whether the `if: failure()` alarm fires for a REAPED job is still unverified. The timeout makes the reap unreachable in practice; it does not answer what happens inside one | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-265** | **A CI run can fail with NO LOG PERSISTED, and the alarm mail then points the operator at a log that does not exist.** | **CLOSED 2026-08-08 — `timeout-minutes: 5` on the gates job, and the alarm mail now states elapsed seconds and qualifies its own "names itself in the run log" sentence.** ⚠ **The unknown is NOT closed and must not be read as closed:** whether the `if: failure()` alarm fires for a REAPED job is still unverified. The timeout makes the reap unreachable in practice; it does not answer what happens inside one **⚠ CORRECTED 2026-10-18 (R-1136): the `timeout-minutes: 5` this row closed on has NEVER been in force.** act_runner 0.6.1 parses a job-level `timeout-minutes` and does not enforce it (only step-level is), and 231 felhom.eu jobs ran past 5 minutes and passed (longest 529 s, job 2038). The limits really in force are the runner's 3 h default (`runner.timeout` not set in `act-runner-config`) and Gitea's server-side reapers (defaults: 10 min with no task update, 3 h endless; `app.ini` not read). A hung step on a live runner runs up to 3 h. The unknown stays open: whether `if: failure()` runs for a reaped job. Step-level timeout: R-1152. | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-267** | **The Configuration page is 2.6× faster and is still ~10 s, and the remaining cost is ONE Gitea call whose latency swings 20× with load.** Shipped in 0.100.2, v0.100.0. | **CLOSED 2026-08-08 — hub v0.101.0 + a registry prune.** **Final: cold 5.4 s, warm 0.14 s** (was 26.2 s). Three serialisation legs took it to 9.85 s mean, the 60 s in-memory memo took the warm path to a quarter-second, and the prune halved what remains of the cold path. **⚠ TWO CORRECTIONS TO THIS ROW'S OWN EARLIER TEXT, because both were wrong and both mattered.** **(1) "Only 50 generic versions exist" WAS NOT A COUNT, IT WAS A PAGE LIMIT.** `?type=generic&limit=1000` returns at most 50; the 50 I measured was exactly the cap, and three older agent versions (0.81.0, 0.80.0, 0.79.0) only became visible after the first 30 deletions moved them onto page one. **An unpaginated listing is not evidence of a total** — this repo's own "an empty listing is not evidence of emptiness" rule, walked into while measuring. **(2) THE OPERATOR'S "REDUCE THE NUMBER OF ARTIFACTS" WAS THE BETTER CALL AND MY MEASUREMENT SAID OTHERWISE.** I reported it helps "sub-linearly" and "is not the lever". Measured after: trimming to 10+10 took the COLD load from 13.4 s to 5.4 s — a 2.5× improvement on the path the memo cannot help, because the fan-out is per-version. Recorded rather than quietly dropped (the R-96 standing rule). **Pruned to the newest 10 per package on the operator's rule**, with the live-vouched golden/agent/floor asserted into the KEEP set before a single DELETE was issued; 33 deletions, all HTTP 204, and golden 0.210.0 / agent 0.128.0 / agent 0.127.0 verified still fetchable afterwards. `drill-r50` runs agent 0.113.0, now deleted — flagged to the operator first; it is a disposable nested drill VM and only its re-download path is gone | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-268** | **A live per-guest local-API token was printed into a session transcript.** Shipped in 169.254.253. | **CLOSED — ROTATED + PROVEN LIVE 2026-08-09** (rehearsal pre-phase, `audits/REHEARSAL-byo-reinstall-2026-08-09.md` §3) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-273** | **RANK 1 — the hub vouched an agent version that was never git-tagged, and every install fleet-wide now fails at step 5/8.** Shipped in 0.127.0, 0.128.0, v0.127.0. | **CLOSED 2026-08-09 — tag pushed, install PROVEN** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
+14 -14
View File
@@ -123,16 +123,15 @@ stopping line that lies.
| **R-993** | Install & onboarding | P3 | **Operator: approve the minimum machine in `documentation/audits/sizing-2026-10-13/SIZING.md` §5** (the input R-991's hardware page needs). MEASURED 2026-10-13 on the N100 demo box (61 apps deployed and removed through the product, 15 min at rest each; heavy apps with one real action). Proposal: **minimum** x86-64, 4 cores (N100 class), **8 GB**, 256 GB SSD; **recommended 16 GB**, 512 GB SSD; out: ARM / Raspberry Pi, Synology-QNAP, routers, cloud VPS. Worked from the floor (2.5 GB for the whole box with no apps) + Immich, Jellyfin, Nextcloud, Home Assistant, Vaultwarden, Paperless-ngx at their peaks (5.0 GB) + 50 % headroom ≈ 12.3 GB. 8 GB runs that set without Immich; 16 GB runs all of it twice over. Data for the estimator: `sizing.json`. **2026-10-13 (estimator session) — the installer accepts a host BELOW this minimum:** `autosize_guest_caps` has no floor on the host, only on the guest; on a 4 GB box (MemTotal ≈ 3.5 GiB) the guest gets host − 1 GB ≈ 2.4 GB and Proxmox keeps ≈ 1 GB, where it used ≈ 2.0 GB on the N100 (SIZING.md §1). Whatever minimum is approved, nothing enforces it at install today. The three-verdict wording of §5 now matches the estimator page (`/gepigeny`, R-997). | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's decision | Operator: approve or change §5. If nothing is done: no hardware page; a buyer with a Raspberry Pi can still download the installer (R-991 stays open) | operator |
| **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC |
| **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator |
| **R-1148** | Install & onboarding | P3 | **Installer 1.34.0 is published (operator yes, 2026-10-18) but its default path has not run on a fresh install.** Step 4b now vaults FIRST, then sets the password, then checks a PVE login (R-1145); the SC2066 loop became an `if`. Proven: the harness (52/0, the R1145 cases run the real step with fake commands), `test_hostinstall.py` 40/0, a real `--rotate-recovery-only` on demo-hp (vault time moved, new value 200, old 401). Not proven: a whole fresh install on a machine, where step 4b runs inside the full sequence and `https://127.0.0.1:8006` must answer during it. | OPEN — filed 2026-10-18 | — | One fresh appliance install with 1.34.0 on a scratch VM on demo-hp (the ISO proof-VM shape): step 4b's three lines, the vault time, a PVE login with the vaulted value; then discard the unclaimed appliance (the operator's press) | CC |
| **R-1154** | Install & onboarding | P3 | **A reinstall while the customer's OLD host record still exists skips both clean-slate re-issues (the claim code and the off-site credential) in silence.** `handleHostEnroll` returns the existing host early (mint-once-reuse), so `reissueOnReenroll` is never reached; its own comment assumes „the clean-slate flow deletes the stale host” — nobody does on a plain reinstall. Proven live on the demo-hp reinstall 2026-08-21 (CC memory), never filed. Now recurring: the operator keeps Tester4 as the standing fresh-install customer (2026-10-18), and its old host `Tester4-31fa0e` stays after each teardown. Filed 2026-10-18. | OPEN — filed 2026-10-18 | — | Decide: the enroll re-issues when the old host is stale or off, or the teardown deletes the host first (operator, session only); a test on the enroll path | CC |
## Apps & catalog — 16 rows (P3 7, P4 9)
## Apps & catalog — 15 rows (P3 6, P4 9)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1091** | Apps & catalog | P3 | **Catalog copy that is wrong or stale at the pinned tag, found writing the guides — each needs an operator word or a live check, so none was changed tonight.** From source at each app's tag (evidence per app in `audits/app-guides-2026-10-15/B/`): (1) and (2) **DONE 2026-10-16** (operator decision D12: reword): calcom's use case now names Apple Calendar or a CalDAV calendar instead of Google/Outlook, and bentopdf's tagline and use case say the browser does the work and the file stays on the device (catalog CHANGELOG 2026-10-16); (3) **sabnzbd**, **nzbget** use_cases say Radarr/Sonarr/Jellyseerr hand them jobs — the box wires only qBittorrent and Transmission (`mediawire.go`), and a hand link needs a path mapping; (4) **onlyoffice** first_steps 3–4 tell the household to wire Nextcloud by hand — the integration toggle does it (`internal/integrations/onlyoffice_nextcloud.go`); (5) **glance**, **homepage** first_steps send the household into config files in a Docker volume no household tool reaches; (6) **lubelogger** starts with only the „Dashboard" tab and in miles (shipped `appsettings.json`), so first step 3 cannot be done as written; (7) **home-assistant** first_steps promise auto-discovered devices — the app runs on a Docker bridge, where mDNS/SSDP were measured deaf (spike R-6); (8) **homebox** and **gitea** `add_people` name menus that moved or read differently at the tag; **bookstack** `default_creds` still says `admin@admin.com / password`, which after_install replaces; **dawarich** names the API key's page „Settings" (source suggests the account page); **code-server** says „install your tools" with no sudo; **komga** names the discontinued Tachiyomi; **seerr** `docs_url` points at Overseerr; **wanderer**'s comment still says it cannot close sign-up (R-714 is fixed in the same file); (9) **nzbget** and **grafana** deploy-field descriptions say the password can be changed later — nzbget re-applies its env password at every start and grafana ignores the field after first start (R-1086 is the qBittorrent/Transmission side). | OPEN — filed 2026-10-15 | — | Live-check (5)–(7) and (9) on 9202; fix (3), (4), (8) as copy with the freeze re-admitted; re-run the guide label check for any app whose copy moves | CC |
| **R-1092** | Apps & catalog | P4 | **Three apps show no guide: plant-it and home-assistant carry one HIDDEN (`checked_version: ""`), emby has none.** plant-it: `lifecycle: abandoned`, and its pinned image does not exist upstream, so there is no screen to check labels against. home-assistant: its frontend follows the browser language and the Hungarian strings are fetched at build time, not kept in git, so the Hungarian labels cannot be checked against source (the draft is written; the shipped frontend in the image could check them). emby: its server's web UI is in no public source at 4.11.0.4. Evidence: `audits/app-guides-2026-10-15/B/`. The other 62 templates show a checked guide (controller ≥ 0.315.0). | OPEN — filed 2026-10-15 | — | home-assistant: check the Hungarian labels against the frontend files in the pinned image (read-only, on a box running it), then set checked_version; emby: label-check against the image's own web files the same way; plant-it: nothing while abandoned | CC |
| **R-676** | Apps & catalog | P3 | **[P3-LOW] Watch: immich's first start restarted 12 times — decision 28's crash-loop stop (6 in 10 min) would stop it.** From the 2026-09-17 chaos night (DB connection dropped during the first-start geocoding import on a 6 GB guest; it did not recover that night). No healthy app in any drill evidence restarts on a first start (1831 samples, 40 live containers), so the threshold stands; this row exists so the first immich install under v0.269.x is watched. `audits/night-2026-09-24/A3/40-first-start-restarts.txt` **2026-09-25 night (read from source, v0.271.0): a DEPLOY's first start is NOT covered by decision 28's suppression** — `Deploying` clears when `compose up -d` returns (`deploy.go` "Clear deploying flag"), and `ObserveUnhealthy` then samples the app; an automatic update's step, verify and undo ARE covered (`Updating`, pinned by `TestD28_NoCrashLoopStopDuringAnAutomaticStep`). So a first start that restarts ≥ 6 times in 10 min is stopped — which R-676 already accepts for a broken first start; a healthy slow first start would be stopped too. **-- 2026-09-30: the first-start restarts are explained.** immich's first-start geodata import OOM-kills its database at 512M on a guest with no swap (R-732, measured: 61–104 kills); the 2026-09-17 chaos-night case (DB connection dropped during the import on a 6 GB guest) fits it. Fixed in the catalog (`56c4888`, 768M). The watch itself (decision 28 on a DEPLOY's first start) is unchanged. | **OPEN — P3; owner: CC (watch)** | — | — | CC |
| **R-1018** | Apps & catalog | P3 | **The OnlyOffice links (FileBrowser, Nextcloud) have no recorded live proof since the move to Proxmox.** `controller/internal/integrations/` holds two handlers; no audit after the Proxmox move shows a document opened from FileBrowser or Nextcloud in OnlyOffice (searched 2026-10-14). The media-app links of `15` §6 will copy this code, including its revoke-on-stop rule (`lifecycle.go:10-13`). | **OPEN** — filed 2026-10-14 | — | One live walk on a Tier-0 box: install OnlyOffice + FileBrowser, open a document, save, read it back; then stop one app and record what the link does | CC |
| **R-1028** | Apps & catalog | P3 | **Plex's claim code can run out during the install, and the box cannot recover it.** plex.tv's code lives 4 minutes from the moment the household copies it; the clock covers the wizard, the image pull (`plexinc/pms-docker` is 351 MB, measured) and the first start. Plex reads `PLEX_CLAIM` only on its first run, and the field is `locked_after_deploy` — so a late code leaves an unclaimed server and the only way back is remove + install again. Reported by the operator during the 2026-10-14 test; on Tester 1's line the pull+start took 8.5 s and the claim worked (`audits/media-all-drives-2026-10-14/``G-auth/G3-plex-claim.txt`), so it bites on a slow line or a slow hand. **K6, 2026-10-14 night (`audits/media-drive-events-2026-10-14/``K6-1-unclaimed-plex.txt`):** an unclaimed Plex starts with no code at all, and `POST /myplex/claim?token=` on the RUNNING server reaches plex.tv (a wrong code → 500 after plex.tv's 401). So the box can pull + start first and ask for the code after: no race. A real claim needs the household's code — the morning step on the throwaway `plex-k6` on Tester 1. Narrowed 2026-10-14 night. **PROVEN 2026-10-15 morning (`K6-2-claim-after-start.txt`):** the operator's real code, given to the RUNNING unclaimed `plex-k6` → 200, `claimed` 0→1, `PlexOnlineToken` written, and the open door closed (no token → 401). `ADVERTISE_IP` was unset and the claim did not need it; whether clients find the server without it was not measurable (the throwaway had no published port). **BUILT 2026-10-15 (v0.313.0):** `after_start` field — not asked at install; the Plex page asks for the code once Plex runs and claims the running server; the gate opens only when Plex says claimed. Proven up to the code on 9201 (G5). | **NARROWED** — the dashboard claim with a real code is the last step | a household Plex code | Claim through the Plex page with a real code (G5c); then close | CC |
| **R-1029** | Apps & catalog | P3 | **Plex's folder picker shows the drives as identical „media” folders.** With one bind per drive at the same path (`/mnt/felhom-drives/<drive>/userdata/media`), the leaf of every library folder is `media`; the drive's name sits two levels up, so in Plex's picker the household sees several folders all called „media” (reported by the operator during the 2026-10-14 test on Tester 1). It conflicts with `15` §2 reason 2 (the same path everywhere). | **READY** — filed 2026-10-14 | the layout decision (STATUS D9) | Decide in `15` §2: the players (`:ro`, never linked) may get a readable path per drive (e.g. `/media/<drive label>`) without breaking the hardlink rule, which binds only the download/library apps | CC |
| **R-1071** | Apps & catalog | P3 | **Jellyfin cannot use films that sit on a NAS share (the pilot's OMV), and the pilot's wish is exactly that.** MEASURED 2026-10-15: an OMV SMB share added as „Hálózati tárhely” mounts and verifies (`/mnt/felhom-drives/omv-filmek`) — but only with a user name AND a WRITABLE share (read-only → refused „az alkalmazások nem tudnak rá írni”) — and the household's „Újraindítás most” binds only local drives into Jellyfin (`per-drive binds 1 [/mnt/felhom-drives/filmlemez/userdata/media]`). By design: R-108 (a NAS is browse-only, never an app's namespace); `media_library.go` skips `Kind == "network"`. A design decision, not a defect (R-370). Evidence: `audits/omv-vm-2026-10-15/B6-omv-share.txt`. | WAITING-ON-OPERATOR | Operator: should a media app read a NAS share read-only (a new bind kind), or is the answer „put the films on a drive the box owns”? | Operator rules; if yes, a design note in `15-media-library.md` first. | Operator |
@@ -142,9 +141,9 @@ stopping line that lies.
| **R-995** | Apps & catalog | P4 | **Removing an app through the product can leave unnamed Docker volumes and an empty import folder behind.** MEASURED 2026-10-13 on demo-felhom after 61 removals (`documentation/audits/sizing-2026-10-13/METHOD.md` „Teardown”, `evidence/leftover-volumes-after-bench.txt`): 8 unnamed volumes (4–72 KB; immich 1, romm 2, privatebin 3, onlyoffice 2 — volumes the images declare themselves) and `userdata/import/{paperless,calibre,grimmory}` (empty, created by the deploy). The removal answered 200 and its own leftover check was clean, because it looks only at labelled volumes. No data risk; disk and tidiness only. Removed by name on the bench. | **READY** — filed 2026-10-13 | — | Make the removal account for the containers' unnamed volumes (and an empty import folder it created), with a test that deploys an image that declares a VOLUME | CC |
| **R-1067** | Apps & catalog | P4 | **The catalog still carries `pi_compatible:` on every template, and nothing reads it any more.** controller v0.315.0 removed the „Pi kompatibilis" badge and the „csak x86" tag from every page (Raspberry Pi is no longer supported, so one was noise and the other was a warning on almost every app). The `ResourceHints.PiCompatible` struct field and the catalog key were deliberately LEFT: a box still on 0.314.0 reads the key, and removing it would make that box show „csak x86" on every app. **Gated on the floor:** once the fleet floor is at or above 0.315.0, drop `pi_compatible:` from the templates and the field from `stacks.ResourceHints` in the same pair of commits. Harmless until then — an unread key costs nothing. | **BLOCKED** — filed 2026-10-15. Checked 2026-10-16 (apps burndown): the floor is 0.321.0 and demo-hp, Tester 1 and the N100 run 0.321.0, but **Tester-2 still reports 0.292.0** (its newest report 2026-10-04; hub DB with WAL) — so nothing removed. | every box on ≥ 0.315.0 — today Tester-2 | Drop the catalog key and the struct field; grep the hub and the website for Pi wording at the same time (none found 2026-10-15) | CC |
| **R-1043** | Apps & catalog | P4 | **Sonarr, Radarr and Prowlarr build their login redirect as an absolute `http://` URL.** They trust forwarded headers only from their „Trusted networks” (the household set `192.168.0.0/24`; traefik is `172.18.x`), so they ignore `X-Forwarded-Proto: https` and answer `Location: http://<name>/login` to an https visitor. Since controller v0.312.0 that hop is sent back to https by traefik (a GET, no password), so it is cosmetic; a template could seed traefik's network as trusted. (`audits/https-only-2026-10-14/``1A-3-4.txt`) | **OPEN** — filed 2026-10-14 | — | Seed the docker network as a trusted proxy in the *arr templates (with the R-1030 seed), measure the redirect | CC |
| **R-1100** | Apps & catalog | P3 | **One sign-in for the dashboard and FileBrowser (proxy auth) — spike first.** Since controller 0.320.0 every app with a folder has „Fájlok ↗", and the box can show FileBrowser's password — but FileBrowser still asks for its OWN login the first time, so the household signs in twice. FileBrowser Quantum has a proxy-auth method (`auth.methods.proxyAuth`, header-trusted) that could take the dashboard session through traefik; nobody has measured it. Design: `architecture/02-controller-module-map.md` „Logins and folders". | OPEN — filed 2026-10-16 (task logins-folders). | — | Spike on demo-hp: proxy auth at the pinned tag (source + measured), a forwardAuth in traefik that answers only for a dashboard session, what a stranger reaching `files.<domain>` with a forged header gets, and what happens to FileBrowser's shares. | CC |
| **R-1100** | Apps & catalog | P3 | **One sign-in for the dashboard and FileBrowser (proxy auth) — spike first.** Since controller 0.320.0 every app with a folder has „Fájlok ↗", and the box can show FileBrowser's password — but FileBrowser still asks for its OWN login the first time, so the household signs in twice. FileBrowser Quantum has a proxy-auth method (`auth.methods.proxyAuth`, header-trusted) that could take the dashboard session through traefik; nobody has measured it. Design: `architecture/02-controller-module-map.md` „Logins and folders". | **READY** — spike done 2026-10-18 (`audits/proxyauth-spike-2026-10-18/DESIGN-NOTE.md`): Traefik forwardAuth with `authResponseHeaders` strips a client's header (any case, twice) and its own value wins — measured; the config key is `auth.methods.proxy` (not `proxyAuth`); **but an app container reaching `filebrowser:80` directly logs in with a forged header (measured 200)**, and the dashboard cookie is host-only. Build = a files-only ticket cookie + a per-box secret header name. Filed 2026-10-16. | — | Build per the design note (controller only, ~one release), or decide not to | CC |
| **R-1099** | Apps & catalog | P4 | **The N100 may hold RomM games in the old upload place, now shown under a fake „roms” platform.** Until 2026-10-16 RomM's own upload wrote to the drive's `roms/roms/<platform>/`; after the mapping fix (catalog `fb18471`, R-1089) those files are at RomM's `library/roms/roms/…`. Measured on 9201: such a game showed as missing in its platform and again under a platform „roms” (`audits/romm-ghost-2026-10-16/B/b-after.txt`). Files put in with the file manager are not affected — they are now found. The N100 runs RomM (hub `/apps/romm`); CC does not log in to the lived-in box. | OPEN — filed 2026-10-16 | Operator: look at `roms/roms/` on the N100's drive in the file manager | If it holds platform folders: move them up one level into `roms/`, then run „Scan” in RomM; if it is empty, close | Operator |
| **R-1117** | Apps & catalog | P4 | **Immich's and Nextcloud's Redis keep a volume they do not need** (upstream runs both without one; queues and caches only). Deferred from R-1096 on purpose: removing a volume from the compose leaves the old volume on installed boxes, and the nightly dump / an old backup's restore for a volume the compose no longer declares is unchecked. The RDB switch (catalog `5d9450b`) already removed the full-disk danger. | DEFERRED — filed 2026-10-17 | — | Measure the dump and an old-backup restore without the volume on 9202; then the template change + an orphan-volume note | CC |
| **R-1153** | Apps & catalog | P4 | **FileBrowser's preview of a .docx answered 500 while the OnlyOffice link was on** (`/api/resources/preview`, scratch 9202, controller 0.330.0, 2026-10-18). FileBrowser builds an office preview through OnlyOffice's converter from `server.internalUrl` when it is set (`backend/http/preview.go:243` at 1.5.6-stable) and from the request's host when not — so 0.331.0's `internalUrl` (R-1018) may already fix it. Not re-checked: OnlyOffice was removed after the walk. | OPEN — filed 2026-10-18 | — | One preview request on a box with 0.331.0 and the link on | CC |
## App updates — 4 rows (P3 4)
@@ -156,12 +155,12 @@ stopping line that lies.
| **R-683** | App updates | P3 | **[P3-LOW] Watch: after a power cut during an update's health check, the hold named an HOUR-OLD second-drive copy, not the one the update's own backup should have just made.** 2026-09-24 chaos round 3 (nextcloud, `backup_max_age: 1m`): no `backing-up` phase was seen and the hold named Tier 2 at 13:04 for an update pressed at 14:04; the pre-cut controller log was lost with the container (the runner now saves it at arm time — R-320). Round 11, the same action without a power cut, named a fresh 14:34 copy and logged the Tier-2 copy. The sentence was TRUE (it named the copy it offered); the question is why the update did not back up first. Not reproduced; watch the next power-cut drill. `audits/night-2026-09-24/E/round-03*.json`, `E/round-11-controller-pre.log` | **OPEN — P3; owner: CC (watch)** | — | — | CC |
| **R-785** | App updates | P3 | **[P3-LOW] SparkyFitness is pinned 11 releases and a major behind upstream (v0.17.3; upstream v1.7.3, v1.6.0 dated 2026-07-24).** READ 2026-10-01 (`audits/visitors-2026-10-01/C/bench/C1-previous-tag.txt`). **Needs:** an update walk 0.17 → 1.x through the ladder (bench + box), after R-784 is decided. | **OPEN — rank P3-LOW; owner: CC (after R-784)** | — | — | CC |
## Backup & restore — 28 rows (P2 3, P3 14, P4 11)
## Backup & restore — 30 rows (P2 3, P3 16, P4 11)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-978** | Backup & restore | P3 | **Starting the escrow ceremony again can make the customer's written-down recovery code useless: the new package replaces the old one before the new code is confirmed.** It can happen to an ordinary customer who starts and walks away. Fix: keep the previous package until the new code is confirmed. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Fix in the agent with a test | CC |
| **R-973** | Backup & restore | P3 | **A compromised controller can make the whole-guest backup run again and again, and each run prunes older restore points: the local tier directly, and the off-site tier through the store's own nightly keep-last-2.** So `07` §8 row 10 (ransomware) is weaker than it reads. The second reader confirmed the local half; the off-site half is inferred. Fix: a cadence or daily cap on the local-API backup route, and off-site snapshots that a nightly prune cannot remove. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Add the cap with a test; decide the off-site retention shape | CC |
| **R-978** | Backup & restore | P3 | **Starting the escrow ceremony again can make the customer's written-down recovery code useless: the new package replaces the old one before the new code is confirmed.** It can happen to an ordinary customer who starts and walks away. Fix: keep the previous package until the new code is confirmed. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | **OPEN — the fix is in agent `main`, unreleased** (`8561dc8`, 2026-10-18): the new package reaches the hub only when its code is claimed; an abandoned second ceremony leaves the first code opening the hub's package (tests + four red-proofs). Residual R-1151. Rides the next vouched agent release. | — | Release with the operator's yes; then close | CC |
| **R-973** | Backup & restore | P3 | **A compromised controller can make the whole-guest backup run again and again, and each run prunes older restore points: the local tier directly, and the off-site tier through the store's own nightly keep-last-2.** So `07` §8 row 10 (ransomware) is weaker than it reads. The second reader confirmed the local half; the off-site half is inferred. Fix: a cadence or daily cap on the local-API backup route, and off-site snapshots that a nightly prune cannot remove. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | **OPEN — the local-half fix is in agent `main`, unreleased** (`f17e197`, 2026-10-18): a cadence cap on `POST /backup` — daily tier ≤ 3 successful runs per rolling 24 h, the weekly off-site tier one per 6 days; a refusal is 429, logged, persisted across a restart, and a CRITICAL degraded status for 24 h (operator-only `agent_capability_degraded`, wording R-1150). *Decided by CC unattended — operator may reverse:* the numbers. **Off-site half, design line (not built):** ep0 prunes the namespace keep-last 2 — a count, so each extra run pushes out the oldest; with the cap a pre-compromise copy survives ≥ ~6 days and each extra attempt alarms; to make it unfloodable, ep0's prune moves to time buckets (e.g. keep-weekly 2) — an ep0/hub prune-job change, the operator's; check that DooPlex's pull copy of ep0 is keep-weekly, not keep-last. Local keep-last 3: R-1149. | — | Release with the operator's yes; decide the off-site prune shape | CC |
| **R-232** | Backup & restore | P2 | **DooPlex's backup makes every copy inside the same box — and nothing tells anyone when it fails.** Surveyed read-only 2026-08-06 (`audits/RECON-dooplex-backup-2026-08-06.md`). **What works:** five sets, 14/14 successful runs in 14 days; a file was restored from the `data` repo and matched the live original **byte for byte**; every set except two is cross-disk; k3s is integrity-checked on every run. **What the matrix exposes, ranked:** (a) **`notify_failure` is a no-op** — `NOTIFY_ON_FAILURE=true` but `NOTIFY_WEBHOOK_URL` is commented out, so a failed backup notifies **nobody**; the project already has a working Resend path that CI uses. Cheapest item, and it makes every other failure visible. (b) **Nothing leaves the box** — no rclone, no remote repo, no off-site target anywhere; Longhorn's target is `nfs://192.168.0.180:` pointing at DooPlex itself, and the only outbound-looking cron pulls *inbound* from Hetzner for a different project. The machine that runs the hub managing the customers' off-site chain has no off-site copy of its own. (c) **The backup tree is a single writable path** and the restic repos are not append-only — one bad script or ransomware destroys every copy at once. (d) **Two same-disk sets**: `.claude-memory` and the PostgreSQL dumps, whose source directory sits *inside* the backup tree. (e) **Longhorn `retain=1`** — one generation per volume, so a corruption noticed a day late has no earlier copy. (f) **`/opt/backup/docs/BACKUP-RESTORE.md` does not exist** though the systemd unit advertises it. (g) **`secrets/restic-repo` has never held a snapshot** — `backup-secrets.sh` contains no `restic` call; the secrets are GPG files on `sda1` only. (h) **No restore has ever been run** beyond today's single-file probe — the matrix's "ever demonstrated?" column is otherwise entirely empty. **Not a finding:** the restic passphrase. The on-box copy is on `sdb1`, a different disk from the backups, and the **operator holds an offline copy out of band** — so a disk loss is recoverable. The narrow residual is that it is operator-held rather than system-held, unlike the customer case's hub-vaulted escrow, so it should be confirmed current and findable by someone else. **Nothing was changed by the recon.** | **NARROWED 2026-10-08 — owner Viktor.** (a) DONE with the operator's yes in chat: `notify_failure` now mails admin@felhom.eu through Resend; proven by one test mail that reached the inbox (`audits/day-2026-10-08/r232/`; no backup was started). (b) partly: the hub database leaves DooPlex nightly to ep0 (R-173); everything else stays on the box. (c)–(h) unchanged. **2026-10-09 (operator yes in chat): (b) and (h) DONE for Gitea and the secrets.** Nightly 00:20 `felhom-dooplex-offsite` pushes Gitea (614 MB of repositories, the `gitea` database dump taken first, `app.ini`) and the nightly GPG secrets export, encrypted with a NEW key, to ep0 `operator` (`host/dooplex-gitea`) on the hub-DB write-only token — no change on ep0; first push 57 s; restore test weekly (manifest, `git fsck` every repo, `pg_restore --list`), run once: 27 805 files, 10 repos OK; `DooplexGiteaOffsiteStale`/`DooplexGiteaRestoreTestStale` in force (`absent()` included, promtool + 2 red-proofs); every backup unit now mails on failure (`OnFailure=`, dry run reached the inbox). **First real Gitea restore:** into a throwaway on the bench, 10/10 repos, product `main` = live, a file byte for byte, a login (`runbooks/gitea-restore.md`). The hub came back too (R-173, closed). **The registry is left out on purpose:** its images rebuild from the code. **Correction to the recon §7:** the API mirror holds ONE repo (`homelab-manifests`), not all — Gitea had a single copy, on DooPlex. `audits/dooplex-survival-2026-10-09/` **LEFT:** (c) append-only for the on-box tree, (d)–(g) as written; the paper copy of the new key (operator). **READY** for the rest | — | — | operator |
| **R-893** | Backup & restore | P3 | **After a failed OFF-SITE replay, the rollback pours the NEWER pre-restore copy over the OLDER volume just put back.** Read in source 2026-10-06 (R-638 option A, not measured): `internal/backup/offbox_reconstitute.go` writes the undo copy from the live (newer) database, replaces the volumes with the snapshot's older tars, then — when the replay fails — `rollbackSafetyDump` loads that newer dump over the older database volume. The loader only drops what the dump knows, so tables the newer migration removed stay; and when the snapshot's older definition was written, the rollback branch does not put the newer definition back, so the older app starts on rolled-back data; non-database volumes stay at the snapshot's state. An order change cannot fix it (the only undo is a logical dump, and its volume was replaced). Known limit in `07` §6.3. **-- 2026-10-08 design:** `audits/day-2026-10-08/design-R-893.md` — re-verified; and the screen's „your data is back as it was" (`err.backup.db_restore_failed_rolled_back`) is false for files, other volumes and the app version. Question D8 on STATUS's decision sheet. **-- 2026-10-08 14:16 operator ruling D8 (`09` §3 decision 192):** yes, both, in that order — first the app stays stopped for support (option C), next „put back exactly as it was" (option A). | **NARROWED — 2026-10-09: the first half DELIVERED in controller 0.304.0. Slice 0 (the live measurement) NOT run: 9202 has no off-site, and a dump cannot be cut by hand inside an encrypted off-site copy on Tester 1; it needs a scratch off-site repo — a plan, not a finding.** **NARROWED 2026-10-08 — the first half (D8 option C, the app held stopped) is built on controller main and ships tomorrow; what stays open is the second half, „put back exactly as it was” (option A: a pre-restore copy of the app's volumes and placed files, a fit check, disk for one copy; the slice-0 measurement on 9202 first).** **OPEN — filed 2026-10-06** **2026-10-06 night: verified in source, no code** — `offbox_reconstitute.go:758` (undo dump from the live DB), `:778-860` (files and volumes from the snapshot), `:804` (the snapshot's definition is written when its version differs), `:898` (the rollback loads the newer dump over the older volume; nothing writes the live definition back). Not a reorder fix: it needs R-638 option B (a rebuilding loader) or a pre-restore volume copy (disk cost; R-685's class). Which state a household gets after a failed off-site replay is the operator's call. Next: the 9202 measurement, then the design. | a design: R-638 option B (a loader that rebuilds instead of overlays) or a pre-restore volume copy | Measure it once on 9202 (a forced replay failure after an off-site restore over a migrated app); then a design for the operator | CC |
| **R-895** | Backup & restore | P2 | **The hub's clean-up-window check trusts the snapshot counts the box sends, so a broken-into box (or past-dated fakes added through the add-only key) can shrink the real off-site history without an alarm.** READ 2026-10-06 night in source (R-822's design): the before/after comparison uses counts the box itself reports (`hub/internal/offsitekeys/service.go:284`, `:343`); new fakes keep the count level. Decision 68 already accepts a box-trusted count. | **NARROWED 2026-10-18 — slice 2 built, not deployed** (hub v0.158.0): the 07:10 audit compares the `snapshots/` listing with yesterday's by name — files gone that no clean-up window explains → `offsite_snapshots_vanished` (error), files no reported box run explains → `offsite_snapshots_unexplained` (warning); operator-only, hub-origin (not in `allowedEventTypes`). First listing = baseline; a move-aside or RESET drops it; vanishing judged on a pinned tier only; files younger than 1 h judged the next day. Red-proofed twice (`snapshot_watch_test.go`). **Not built:** the window's before/after on the hub's own count (design §3.1) and refusing a window while unexplained files exist (§3.2). Unmeasured: the provider shell's `ls -l` time zone (± 3 h slack until then). **Deployed 2026-10-18 12:23 (hub v0.158.0).** The first listing (tomorrow 07:10) is a baseline; the first verdict is 2026-10-20 07:10. **Known limit (independent review 2026-10-18, F2):** `_unexplained` is explained by the box's OWN reported `offsite.last_run`, so whoever holds a box's key can silence it; `_vanished` is judged on the hub's own listing and is not affected. | the operator's yes to deploy hub v0.158.0 | Deploy; read the first 07:10 lines (newest upload vs the box's last run = the time-zone calibration), shrink the slack; then decide §3.1/§3.2 | CC |
@@ -188,13 +187,15 @@ stopping line that lies.
| **R-878** | Backup & restore | P4 | **A catch-up (R-871) runs the database-dump leg in the DAY, and that leg stops an app with a volume for its copy — the household may notice the stop, and a large volume makes it longer.** MEASURED 2026-10-05 on demo-felhom: the catch-up at 08:25:02 stopped opengist, copied 182.5 KB, started it again — about 1 s, then a few seconds of `health: starting`; the night does exactly the same, unseen. Nothing measured for a large volume. Fix direction (if it matters): skip the volume copy of a running app in a DAYTIME catch-up and leave it to the next night, or warn. `audits/catchup-2026-10-05/partA/live-demo-felhom.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A LIVE MEASUREMENT** — a timed catch-up on 9202 for an app with a multi-GB volume, then the skip-or-warn choice. | — | measure a large volume first | CC |
| **R-32** | Backup & restore | P4 | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** The rehearsal's S7 said in advance that an orphan card would BE a finding — and one appeared (16:58:14). Cause: RESET's `"hetzner":"ok"` leg destroys the sub-account, but **a Hetzner sub-account is an access-control object, not a data object** — its directory survives, so re-enabling offsite recreated an account over the previous lifecycle's ciphertext, encrypted under a key that same RESET had destroyed. **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-21, size M, roadmap state `idea`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** **2026-10-06 night: design written** (`audits/night-burndown-2026-10-06/design-R-32.md`) — pick A: purge the repo + set-aside copies through the sub-account's own password login BEFORE deleting it (no main-account credential); the „repo data destroyed" comment at `hub/internal/offsite/offsite.go:276-280` is false for the shared tier; part (3) waits on a read-only measurement (`du` on port 23). Operator: agree to route A. **2026-10-07 07:58: `09` §3 decision 167 — option A yes, and delete the old RESET leftovers once.** **2026-10-07 (Part D step 2, read only):** the hub shows bx11 holding 3.3 GB of data while the four live customers account for ~0.6 GB → **~2.7 GB belongs to no live customer**; no live customer shows a set-aside folder, so the leftovers are in homes whose sub-account is gone, reachable only by the pool box's MAIN account. **The one-time clean-up is STOPPED:** no main-account login for bx11 is held on DooPlex (the API token in the credentials file sees no box). Needs: the main-account login, or the operator lists and deletes in the Hetzner console (`audits/day-2026-10-07/D/D2-listing-readonly.txt`). **2026-10-07: option A DELIVERED (hub 0.142.0):** RESET purges the repo and every `.orphaned-*` through the sub-account's own login before deleting it, and keeps the sub-account if the purge fails (red-proved, `audits/day-2026-10-07/D/`). Not yet seen live (no scratch RESET today). **The one-time clean-up of the ~2.7 GB of leftovers is STOPPED:** it needs the pool box's main-account login (see above). **2026-10-07 14:43 — `09` §3 decision 173: leave the ~2.7 GB of old leftovers for now; re-ranked P2 → P4.** Option A itself is delivered (hub 0.142.0). What stays open is ONLY the leftovers: they were computed (box total minus live customers), never listed, and deleting them needs the pool box's MAIN login, which reaches every customer's data. **Blocker: a safe main-account path.** | — | **Ruling from the run (three parts, deliberately separate):** (1) because RESET destroys custody, the ciphertext it leaves behind is unrecoverable **BY DESIGN** → RESET gains a **main-account purge of the customer base dir** (the existing operator ack already covers it); (2) the **move-aside guard STAYS** for reinstall-*without*-RESET — there custody survives and the card's "history recoverable" promise is true (R-26 depends on exactly that); (3) the operator **Restic tab shows per-customer directory bytes vs attributed snapshot bytes**, so dead data cannot hide. Measured on the pool box that night: **49 M attributed** (2 snapshots, 48.717 MiB) against **1.4 G + 3.0 M unattributed** across TWO `.orphaned-*` dirs. Evidence `restic-and-pool.txt` | CC |
| **R-1087** | Backup & restore | P4 | **Whole-box copies left on the OLD local target are never pruned after the target moves.** MEASURED 2026-10-15 (storage-identity build B space check): the N100's host root (`local`) holds three copies from 2026-07-28 (5.5–5.7 GB each, ≈ 16.8 GB); its agent's `local_backup_target` is now `felhom-backup` (the USB disk, retention 3), and retention prunes only the current target. On a small-root box such leftovers would starve the local copies (R-548). Evidence: `audits/storage-identity-buildB-2026-10-15/space/felhom-pve-9201.txt`. | OPEN | — | Decide whether the agent prunes its own `vzdump-lxc-<vmid>-*` files on a former target (after a newer copy exists on the new one), or the target move asks; until then, the N100's three July files can be removed by hand with the operator's word. | CC |
| **R-1149** | Backup & restore | P3 | **The local whole-guest tier prunes keep-last 3, so even with the R-973 cadence cap a compromised controller can replace all three restore points within one day.** A time-bucketed prune (`--prune-backups keep-daily=3`) has the same disk cost and cannot be flooded within a day. It changes `07` §6's retention, so it is the operator's call. Found by the R-973 helper 2026-10-18. | OPEN — filed 2026-10-18 | — | Operator decides keep-last vs keep-daily; then the agent's vzdump argument | operator + CC |
| **R-1151** | Backup & restore | P3 | **The new escrow package now uploads when its code is claimed (R-978), but the wizard's typed-back check is the browser's alone:** a household that opens the new code and closes the page without writing it down has still replaced the hub's package, and the first code no longer opens it. Gate the upload on the typed-back confirmation with a new controller → agent call. Found 2026-10-18. | OPEN — filed 2026-10-18 | — | Controller + agent: the confirm call; red-proof the abandoned-after-claim case | CC |
## Storage & devices — 10 rows (P3 4, P4 6)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
| **R-1073** | Storage & devices | P3 | **Felhom in a virtual machine sees no disk health: SMART is UNKNOWN for its own system disk and any passed disk.** MEASURED 2026-10-15 on the OMV-VM copy: every storage target reported `smart.health = UNKNOWN`, all counters null (the guest sees QEMU disks). On the pilot's box the NVMe and the „old, not stable” Toshiba would be watched by OMV only, and Felhom's drive-health alarms (R-330/R-332's whole point) cannot fire. Evidence: the host report in `audits/omv-vm-2026-10-15/README.md` (memory/disk section) and `C-hub-events-Tester4.txt`. | WAITING-ON-OPERATOR | Operator: is a VM install a supported shape, and if so, is „OMV watches SMART” acceptable or must Felhom say on the dashboard that it cannot watch the disks? | Operator rules; then CC adds a household line on the Meghajtók page when SMART is unavailable. | Operator |
| **R-1093** | Storage & devices | P3 | **An EMPTY partition asks for the typed „data will be erased" confirmation.** MEASURED 2026-10-16 (build B, scratch VM, agent 0.158.1): `POST /api/storage/init` for a blank part → `needs_confirmation`, reason „device has a partition table" — `lsblk` reports the PARENT disk's table (PTTYPE) for a partition, so the agent's probe reads an empty part as data-bearing. Safe direction (more friction, never less), but `16` §6 says an empty part is self-serve and the wizard shows „adatot tartalmaz". Evidence: `audits/storage-identity-buildB-2026-10-15/live/12-partitions-format.txt`. | OPEN | — | In the agent's device probe, ignore a PTTYPE that a partition inherits from its parent (judge only the part's own signature and children); a red-proved test with a blank part fixture. Next agent release. | CC |
| **R-1093** | Storage & devices | P3 | **An EMPTY partition asks for the typed „data will be erased" confirmation.** MEASURED 2026-10-16 (build B, scratch VM, agent 0.158.1): `POST /api/storage/init` for a blank part → `needs_confirmation`, reason „device has a partition table" — `lsblk` reports the PARENT disk's table (PTTYPE) for a partition, so the agent's probe reads an empty part as data-bearing. Safe direction (more friction, never less), but `16` §6 says an empty part is self-serve and the wizard shows „adatot tartalmaz". Evidence: `audits/storage-identity-buildB-2026-10-15/live/12-partitions-format.txt`. | **OPEN — the fix is in agent `main`, unreleased** (`f7b7308` + `0963bae`, 2026-10-18): lsblk's inherited PTTYPE is ignored only when `blkid -p` on the partition exits 0 with `PART_ENTRY_NUMBER` and the same scheme; every error path stays data-bearing (14-row test, three red-proofs; no sudoers change). Live on demo-hp (a loop file in `/root`, removed): the empty GPT part → blkid rc 0 with only `PART_ENTRY_*`; ext4 → `TYPE`; a table inside → `PTTYPE=dos` (`audits/agent-fixes-2026-10-18/r1093-loop-check.txt`). Not walked: `/api/storage/init` on a VM disk. | — | Release with the operator's yes; one init call on a VM with a spare disk | CC |
| **R-1062** | Storage & devices | P4 | **The N100's USB drive cannot be split in two without losing the box's whole-guest backup target.** The operator asked (2026-10-15 16:31) for two halves for multi-drive tests. Read first (`audits/n100-lived-in-2026-10-15/B/`): no family files — 2.7 GB nightly vzdumps of guest 9201 (`dump/`, PVE storage `felhom-backup` = the whole-guest backup target), 3.1 GB app backups + an off-site restore copy, 422 MB leftover app data of removed apps, empty media folders. The agent refuses a decommission of a backup target (`refuseIfBackupTarget`) and the N100 has no other drive to move that role to. Nothing changed. | **WAITING-ON-OPERATOR** — filed 2026-10-15 | operator decision | Keep one drive (CC's pick: the lived-in box keeps its local whole-guest backup), or give a word to drop that backup leg / add a second drive first | operator |
| **R-1064** | Storage & devices | P4 | **Removing a storage path does not mention the cross-drive backup copies it holds.** demo-hp 2026-10-15: `/mnt/sizing-bench` removed through `/settings/storage/remove` (302, „Adattároló eltávolítva") while calibre-web's and paperless-ngx's `cross_drive.destination_path` pointed at it; the copies stay on disk, the records still name the removed path. The in-use check counts HDD_PATH only (`appsUsingPath`). | **OPEN** — filed 2026-10-15 | — | Read what tonight's tier-2 run (01:30) does with the two records; then: name the copies in the removal's confirmation (or count them as in use) | CC |
| **R-330** | Storage & devices | P3 | **Disk health Phase 2 — the three SMART attributes the wire does not carry.** The failing drive's most telling counter was **187 `Reported_Uncorrect`**, sitting at normalized **1** against threshold **0** with a raw count of **1001** — one point from failing and structurally unable to get there. Also wanted: **199 `UDMA_CRC_Error_Count`** (cabling) and **188 `Command_Timeout`**. None are on the agent→controller wire today, so v0.215.0's ladder could not use them. Phase 2 also persists periodic SMART **samples** (the right home is `metrics.MetricsStore`, NOT the Phase-1 state file, which is one record per disk and must stay that way). **This is a declared WIRE change, so under the G-1 gate the hub must model the new fields in the SAME session** — that is precisely why it was kept out of Phase 1, where it would have turned a one-word severity fix into a three-repo change | **READY** — **2026-10-12: the wire half is CLOSED in this row** — released in agent v0.150.0 (boxes run 0.154.0), controller and hub decode 187/188/199. **Only open work:** the verdict slice (use the three counters in the disk verdict and the household's words) and persisting samples. **READY (M) — NEW 2026-08-14** **2026-10-06 night: the wire half BUILT on main (unreleased):** the agent sends SMART 187 `reported_uncorrect`, 188 `command_timeout` (raw, vendor-packed on some drives — carried as reported), 199 `udma_crc_errors` (pointer + omitempty: unknown is absent, never 0); the controller decodes them and the hub's host-report mirror models them (G-1 wire gate green; it convicted the agent half alone). **Carried only** — no verdict, banner, mail or alarm reads them (`TestR330_CountersChangeNoVerdictYet`); using them changes what a household is told and is the next slice, with persisting samples. Red-proofs `audits/night-burndown-2026-10-06/r330/`. Ships with agent v0.150.0 and the next controller and hub releases. | R-328 (closed) | The verdict slice: 187/188/199 into the disk verdict with tests; then persist samples | CC |
@@ -247,7 +248,7 @@ stopping line that lies.
| **R-1061** | Box system & updates | P4 | **A per-customer floor reached Tester 1 only at its next 15-minute report, not in seconds.** Set 2026-10-15 12:55:51 UTC for tester-1, demo-hp and demo-felhom together (hub 0.153.0, CC's token): demo-hp and demo-felhom ran 0.314.0 within 13 s (the hub's wake); Tester 1 moved at 13:09:35, its regular report (`audits/gate-restore-2026-10-15/release/03-boxes-move.txt`). It had restarted at 12:54:29 onto rc.1 and logged „hub wait channel active". Why the wake did not reach it is not read (the wake line is DEBUG). No harm: the floor arrived. | **OPEN** — filed 2026-10-15 | — | Read Tester 1's DEBUG ring at the next floor change; compare with the hub's bump log | CC |
## Monitoring & notifications — 11 rows (P2 1, P3 7, P4 3)
## Monitoring & notifications — 12 rows (P2 1, P3 7, P4 4)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -262,6 +263,7 @@ stopping line that lies.
| **R-285** | Monitoring & notifications | P4 | **A planned, supervised reinstall pages the operator as if the machine had died — there is no notion of expected downtime anywhere.** During the 2026-08-09 rehearsal the hub sent, all `status: sent` to the operator channel: `host_stale` 08:58 UTC, `node_stale` 09:00, **`host_down` 09:28 (error)**, **`node_down` 09:30 (error)**, `host_leaf_changed` 09:31, `host_recovered` 09:31, `node_recovered` 09:34, `offsite_delivery_stuck` 09:34 — eight operator mails for work that was deliberate, attended and announced. **This is the OPPOSITE gap from the one R-281 filed:** the alarms are not missing, they are indiscriminate. `host_stale` at 30 min and `host_down` at 60 min (`monitor/host_staleness.go:22-23`, `downAfter = 2 * threshold`) cannot distinguish a wiped-on-purpose box from a dead one, and `host_leaf_changed` firing on a reinstall is correct-but-expected. **Note the interaction with the mute used on 2026-08-09 evening:** blocking a customer silences everything, so today the only two settings are *page me for planned work* and *tell me nothing at all*. **What is owed is a middle:** a maintenance window, or an operator-set expected-downtime flag, that suppresses staleness and leaf-change while leaving genuine faults audible | **READY (M) — NEW 2026-08-09** | — | The evidence is the operator's mailbox plus `events`/`notification_log` for 2026-08-09 | CC |
| **R-1005** | Monitoring & notifications | P4 | **A recovery mail says how long the box was „stale", not how long it was gone: after a 51-minute power cut demo-hp's `host_recovered` read „was stale for 8m" and `node_recovered` „23m".** An operator reading only the recovery mail under-reads the outage (`audits/night-power-2026-10-13/` R6). Same kind, the other way: the off-site mail of 2026-10-14 02:18 was titled „3 of 3 apps failed to back up (offsite run)" while 11 other apps were backed up in that run — only the 3 flagged apps were counted. | **READY** — filed 2026-10-14 | — | CC: add the time since the last report before the gap to both recovery messages, and count every app of the run in the off-site subject, with a test that asserts each text; next hub release | CC |
| **R-1049** | Monitoring & notifications | P3 | **A pulled drive mails the operator „unexpectedly disconnected” (error), and nothing ever mails that it came back.** MEASURED 2026-10-14 night (`audits/media-drive-events-2026-10-14/K8-hub-mails.txt`, the hub DB with its WAL from the running pod, and the mailbox as the second channel): two pulls → two `storage_disconnected` mails; the hub RECEIVED both `storage_reconnected` and both `health_recovered` (severity info) and logged no notification for any of them — they are not in `recoveredPairedDownTypes` (`hub/internal/notify/dispatcher.go`), so the severity gate drops them. The R-97a shape: told it broke, never told it healed — the operator cannot tell a returned drive from one still gone without opening the hub. **BUILT 2026-10-15 — hub v0.151.0 (`12552aca`): both pairs in `recoveredPairedDownTypes`, cooldowns ended by the next loss (`TestR1049_DriveBackMailsBothEdges`, red-proved). Not deployed: the operator's „yes” first (decision 162); the image build waits for it too.** | **WAITING-ON-OPERATOR** — hub v0.151.0 code pushed, not built, not deployed | the operator's „yes” to the hub deploy | On the „yes”: build + deploy hub v0.151.0; read a „came back” mail on the next pull | CC |
| **R-1150** | Monitoring & notifications | P4 | **A refused whole-guest backup (the R-973 cap) reaches the operator as `agent_capability_degraded`, whose text reads „agent privileged capability degraded”.** True but misleading for a refused backup; on the night path the controller may also mail its own backup failure. Add a dedicated operator-only type (`whole_guest_backup_refused`: `allowedEventTypes` + `operatorOnlyEvents`) or adjust the text, in the next hub release (a two-repo change: the agent's status name stays). Found 2026-10-18. | OPEN — filed 2026-10-18 | — | Next hub release | CC |
## Hub & operator — 12 rows (P3 6, P4 6)
@@ -301,7 +303,7 @@ stopping line that lies.
| **R-990** | Business & legal | P4 | **Operator: read `documentation/audits/forum-research-2026-10-12/FELHOM.md` §5 and rule on each recommendation.** READ-ONLY research 2026-10-12: three years (#7943–#11092, 2,908 posts, 195 people, 82 beginner threads) of the public PROHARDVER! topic „Home server / házi szerver építése”, tagged and counted by person (`FINDINGS.md`, method and spot-check in `METHOD.md`; no username or post text in git). Ten ranked recommendations, none acted on; the top three: a „which machine works” page (8 of 33 beginners who name their hardware own only a Synology, a router disk or a Raspberry Pi — the installer cannot run there; the same gap is R-989's first recommendation from a different source, and the shipped-page half is R-991), „you do not choose an operating system — we do” (the biggest beginner question, 20 of 82 threads), and „bring your old office PC” as the cheap entry. Catalog gap (≥ 3 people): a DLNA server (7), an ad/DNS blocker (5), a camera recorder (5). | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator's reading | Operator: rule on each of the ten (yes / no / later). If nothing is done: nothing changes; the research ages | operator |
| **R-1035** | Business & legal | P4 | **Did the „felhő tárhely” page earn anything? Two dated checks.** **Baseline (reported by the operator from Search Console, 2026-10-14, last 3 months):** 1 click, 48 impressions, CTR 2.1 %, average position **73.6**; „felhő tárhely” 19 impressions, „felhőtárhely” 5, „felhő alapú tárhely” 3. CC measured separately that the phrase „felhő tárhely” appeared **0 times** on the home, FAQ and backup pages — Google was showing the home page for those queries by accident. `/sajat-felho-tarhely` + `/en/own-cloud-storage` published **2026-10-14**; full baseline, price sources and the power provenance in `audits/seo-2026-10-14/BASELINE.md`. **What to measure, both times:** impressions and average position for „felhő tárhely” and „saját felhő tárhely”, plus the new page's own impressions, clicks and position. **A null result at +4 weeks means nothing on its own** — a new page on a site at position 73.6 takes weeks to months to move, and „felhő tárhely” alone is held by Microsoft, Google and the big list sites; the page aims at the two longer phrases. That is why there are two checks and not one, and why neither should be read as a verdict on the copy. **There is no Search Console connector in this project, so CC cannot read these numbers** — the operator reads them, CC records and compares. **Blocked until the operator asks for indexing** (Search Console → URL-ellenőrzés → the URL → „Indexelés kérése”): until that happens, or Google finds the page itself, both checks read zero for a reason that is not the page. **This is a slow channel and does not replace talking to people.** | **WATCHING** | operator: request indexing, then supply the Search Console read | 2026-11-11: first read (+4 weeks); 2026-12-09: second (+8 weeks), compared with the first | operator + CC |
## Process & tooling — 33 rows (P3 9, P4 24)
## Process & tooling — 31 rows (P3 9, P4 22)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -335,9 +337,7 @@ stopping line that lies.
| **R-1127** | Process & tooling | P4 | **The controller's and the catalog's decoy suites run only when someone starts a full gate run by hand** — neither the pre-push hook nor CI (`--fast`) runs them (too slow, or they edit real files), so a decoy that goes quiet is again seen only by chance (R-1115's shape). The agent's runs in CI. Found by the R-1116 helper 2026-10-17. | OPEN — filed 2026-10-17 | — | A scheduled full run (CI cron or the nightly), with a mail on failure | CC |
| **R-1128** | Process & tooling | P4 | **The controller's decoy suite edits real tracked files (`handler_debug.go`, `update.go`, the locale bundles, templates) and plants `.go` files, with no lock** — a full run while another session edits the controller tree can overwrite that session's edits. felhom.eu's suite took an `flock` after the 2026-10-08 clobber. Found by the R-1116 helper 2026-10-17. | OPEN — filed 2026-10-17 | — | Run the suite in a scratch copy of the tree, or take the felhom.eu-style lock | CC |
| **R-1123** | Process & tooling | P4 | **`reuse_refs_check.py` treats every git directory beside the repo as a sibling repo**, including stray clones in the workspace (`felhom.eu-media`, `felhom.eu-sizing-push`, `felhom.eu-wt-sizing`), so a citation can resolve against a stale clone and pass. Found by the R-1116 helper 2026-10-17 (it also makes the agent suite 20 s here vs 5 s in CI). | OPEN — filed 2026-10-17 | — | Search only the four known repo names (or the main clones), with a decoy | CC |
| **R-1135** | Process & tooling | P4 | **CI's failure mail links the run at the in-cluster Gitea address** (`http://gitea.gitea-system.svc.cluster.local:3000/...`), which a reader outside the cluster cannot open. Seen in the forced test mail 2026-10-17 (job 1997). | OPEN — filed 2026-10-17 | — | Use the public Gitea URL in the alarm step's link | CC |
| **R-1136** | Process & tooling | P4 | **felhom.eu CI runs take ~6.9 minutes and complete, so the 5-minute job timeout R-265 assumes does not hold there** (runs 1996, 1997; `script-tests` alone ~356 s). Found by the R-1129 proof helper 2026-10-17. | OPEN — filed 2026-10-17 | — | Measure the runner's real timeout; correct R-265's assumption or speed up `script-tests` | CC |
| **R-1137** | Process & tooling | P4 | **A CI rerun may overwrite a failed job's log** — failure mails arrived for controller commits `c02c9d83` and `29a3ce8c` while their jobs (1944, 1959) now read green. UNPROVEN (a reading, 2026-10-17). | OPEN — filed 2026-10-17 | — | Confirm with one deliberate red + rerun on a scratch repo; if true, the CI check must read the first attempt | CC |
| **R-1152** | Process & tooling | P4 | **felhom CI's 5-minute job timeout is inert** (act_runner 0.6.1 does not enforce a job-level `timeout-minutes`, R-1136), so a hung step runs up to the runner's 3 h. Move it to a step-level `timeout-minutes` on each workflow's gate step (e.g. 20 min for felhom.eu, which runs ~7 min), measured first: a step timeout fails the job AND the `if: failure()` alarm still runs. Needs a scratch repo (the admin token cannot create one — the operator, or a token with `write:user`). Found 2026-10-18. | OPEN — filed 2026-10-18 | — | Measure on a scratch repo, then the four workflows | CC |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.