diff --git a/REPORT-romm-ghost-tags-2026-10-16.md b/REPORT-romm-ghost-tags-2026-10-16.md new file mode 100644 index 00000000..8f150594 --- /dev/null +++ b/REPORT-romm-ghost-tags-2026-10-16.md @@ -0,0 +1,80 @@ +# REPORT — RomM finds its games · Ghost does not lock the household out · the floor refuses withdrawn builds (2026-10-16) + +Daytime, operator present. A second session works in this repo today, so this is a dated report, not `REPORT.md`. +Design read: `architecture/09-update-architecture.md` (§0, decisions 4 and 250), `05-hub-architecture.md` (floor +serving), `02-controller-module-map.md` (app e-mail), `07-backup-architecture.md` (RomM's optional legs). +Evidence: `documentation/audits/romm-ghost-2026-10-16/` (A1, A2, B, C1, C2, teardown). + +## 1. RomM (R-1089) — measured, fixed, both scenarios pass + +- **A1 (9201):** a dummy file where the first steps said (`roms/gba/`) → „No platforms found”, 0 games. One folder + deeper (`roms/roms/gba/`) → found. Cause: RomM 5.3.1 scans `library/roms//`; the drive's `roms/` was + mounted as `library/`. +- **Fix (catalog `fb18471`):** `roms` → `library/roms`; new drive folder `roms-bios` → `library/bios` (operator ruling); + backup and folder labels for it; first steps corrected; stale `admin / admin` comment replaced. +- **Scenario A** (fresh install): the file at `roms/gba/` is found. **Pass.** +- **Scenario B** (old install, correction, restart): the old-steps file is found; the drive is unchanged byte for byte; + the data tab shows both folders backed up. **Pass.** A game uploaded through RomM under the old mapping becomes + „missing” and reappears under a fake platform „roms” (it sits in `roms/roms//`). +- **BIOS:** a file in `roms-bios/psx/` is read as psx firmware. +- **Who runs RomM:** demo-felhom (N100) and demo-hp only (hub `/apps/romm`, read only). No customer box. + +## 2. Withdrawn controller builds (R-1085) — refused at input and at serve time, live + +- **Hub v0.155.0** (`b17e9e99`, deployed `f1e1c8a6` after the operator's yes). One list + (`hub/internal/store/spoiled_tags.go`: `0.315.0`, `0.316.3`). Both floor forms and the impact probe refuse a listed + tag; `ResolveManagedFloor` holds one that arrives any other way (stored value, override, config/env default), with its + own reason and one `[WARN]` per change. The Configuration and customer pages say „withdrawn". +- **The hand deploy:** a marked never-deploy line and a chained pre-check in the `felhom-build-deploy` skill; a test pins + that both equal the hub's list. +- **Tests:** store, forms, probe, both branches of both page notices, the real report handler, the skill parity. + **Ten red-proofs, each seen failing** (`B/hub-redproofs.txt`). Full hub `go build && go vet && go test`: green. +- **Live (`B/hub-live-check.txt`):** ArgoCD Synced/Healthy at `f1e1c8a6`, pod on `felhom-hub:0.155.0`. Impact probe for + `0.316.3` → `valid:false, spoiled:true`; control `0.319.0` → valid. **POST `0.316.3` to the global floor with CC's + token → 303 `flash=floor_spoiled`; the floor read back is still `0.319.0`**; the hub logged one `[WARN] … REFUSED`. + Boxes kept being served `0.319.0`. + +## 3. Ghost (R-1090) — measured, fixed, both ways pass + +- **C1:** Ghost 6.67.0 skips the e-mailed code only on the first-ever sign-in; the next new device got **500 „Failed to + send email”** — locked out. +- **Fix (catalog `fb18471`):** the device check is off by default and turned on with the mail settings by a new + `smtp_mapping` when app e-mail is on. One FAQ entry. +- **Scenario E, e-mail off:** a new device signs in with the password alone (201). +- **Scenario E, e-mail on:** 403 `2FA_NEW_DEVICE_DETECTED`; the shim relayed (hub 200); the mail arrived; the code from + it was accepted and the device signed in (200). + +## 4. Versions, commits, teardown, register + +- Catalog: `fb18471` (RomM + Ghost), `806d2d0` (report, box-walk fix). felhom.eu: `b17e9e99` (hub code, skill, `09`, + evidence), `f1e1c8a6` (manifest), and this commit (register, STATUS, report, last evidence). Hub v0.154.0 → **v0.155.0**. + Controller and agent: not touched. +- **CI by job id:** felhom.eu `b17e9e99` job 1934 success; catalog `fb18471` job 1931 success; the later pushes are + listed in the session's final note. +- **Teardown:** machine (9201) — RomM and Ghost removed, the drive's `roms/` as found, app e-mail back off, two empty + helper folders removed; kept on purpose: two `kept/romm//` folders (the product's „start fresh" result, 8 KB). + The pre-existing broken RomM was removed by operator ruling. Host — nothing. Hub — no record created; the hub + deploy is the only hub change. +- **Register:** **175 rows before, 176 after; 3 closed (R-1085, R-1089, R-1090), 4 opened (R-1096, R-1097, R-1098, + R-1099).** STATUS: one line for the operator (R-1099). System poster: no poster fact changed — none of the three + updates was needed. + +## 5. Observations + +- The standing RomM on 9201 crash-looped on Redis files damaged by the 2026-09-24 disk-full — FILED: R-1096. +- RomM keeps saves, states and screenshots in an anonymous volume (`/romm/assets`), outside the backup — FILED: R-1097. +- The hub build script does not refuse a version that already exists in the registry (the controller's does) — + FILED: R-1098. Checked by hand for 0.155.0 today (not found; control 0.154.0 found). +- The N100 may hold RomM uploads in the old place, now under a fake „roms" platform — FILED: R-1099 (operator, STATUS). +- The box walk made a drive folder for a value the caller passed — NOT-A-FINDING: small, fixed (catalog `806d2d0`). +- A drill fixture folder (`DRILL-2026-08-21`) on 9201's drive shows as a RomM platform — NOT-A-FINDING: another + session's test file on a test drive, left untouched. +- The task gave the new flash text in Hungarian and English — NOT-A-FINDING: the hub's operator pages are English only, + so only the English text was used, like every neighbouring flash. +- The task asked for a RomM FAQ line if a rescan is needed after the mount change — NOT-A-FINDING: a rescan does not + fix it (the files must move), and no customer runs RomM; R-1099 covers the one box that may. +- RomM's remove answered 502 while another session restarted 9201's controller; the remove had completed — + NOT-A-FINDING: the proxy's answer during a deliberate controller restart. +- 9201's controller moved `0.318.0` → `0.319.0-rc.1` during the test — NOT-A-FINDING: another session's release work. + +`unproven.py --summary`: unchanged — 35 of 55 claims not walked. diff --git a/STATUS.md b/STATUS.md index 89f12bee..27f8281a 100644 --- a/STATUS.md +++ b/STATUS.md @@ -50,6 +50,7 @@ | D9 | R-1062 | The N100's USB drive is also its whole-box backup drive. Split it in two anyway? | **No — keep one drive**; the lived-in box keeps its nightly whole-box backup | Split: the box loses its local whole-box backup (or add a second drive first) | it stays one drive; multi-drive tests stay on Tester 1 | — | ## Waiting on you — do +- **Look at `roms/roms/` on the N100's drive in the file manager (R-1099).** RomM now scans `roms//`. A game uploaded through RomM before today sits one folder deeper: if there are platform folders in `roms/roms/`, move them up into `roms/` and press „Scan” in RomM. If not: those games show under a platform called „roms”. - **Nézd meg a Calibre-Web oldalát a demo-hp-n** (`felhom.enkisfelhom.hu`, controller 0.319.0, minden dobozon kiadva). A fejléc, a fülsor és minden rész most külön panel (az „A” stílus). A belépési adatok egy panelben vannak, egymás mellett. A Calibre-Web jelszaváról a doboz nem tud, mert az alkalmazás előbb települt, mint hogy a sablonban jelszó lett volna — ezt most egy mondat mondja ki. Ha nem tetszik, írd meg, mit változtassak. Ha nem nézed meg: semmi nem romlik el. - **Discard two unclaimed boxes and delete Tester4** (hub, browser only): the build B scratch VMs registered as unclaimed boxes — registration ids 40 (`b43afa10…`) and 41 (`9c78ea30…`); both VMs are destroyed. Tester4 (the throwaway customer) is switched off the household way. If not: two dead entries stay in the Hosts list and Tester4 stays in Customers. - System poster needs a refresh: one-disk boxes now have a data area („Belső tárhely") on the system disk, outside the whole-guest backups (facts file updated 2026-10-16). diff --git a/documentation/audits/romm-ghost-2026-10-16/B/hub-build-0.155.0.txt b/documentation/audits/romm-ghost-2026-10-16/B/hub-build-0.155.0.txt new file mode 100644 index 00000000..df1e3941 --- /dev/null +++ b/documentation/audits/romm-ghost-2026-10-16/B/hub-build-0.155.0.txt @@ -0,0 +1,145 @@ +[STEP] 1/3 — Pulling latest code (the tree must be clean and at origin/main)... + +[INFO] ╔══════════════════════════════════════╗ +[INFO] ║ felhom-hub build ║ +[INFO] ╚══════════════════════════════════════╝ +[INFO] Version: 0.155.0 (build-hub.sh v1.24.0) +[INFO] Commit: b17e9e99 +[INFO] Source: /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea0beee5-5628-47eb-8358-0fda9995b9cd/scratchpad/hubclone/hub +[INFO] Build: /mnt/5_hdd/felhom.eu/build/felhom-hub/workspace +[INFO] Image: gitea.dooplex.hu/admin/felhom-hub:0.155.0 + +[STEP] 2/3 — Syncing source to workspace... +[INFO] Source synced to /mnt/5_hdd/felhom.eu/build/felhom-hub/workspace +[INFO] App assets synced (263 files) +[INFO] Package structure verified ✓ +[INFO] Running go mod tidy... +[STEP] 3/3 — Building Docker image... +[INFO] Building for current platform + pushing... +#0 building with "default" instance using docker driver + +#1 [internal] load build definition from Dockerfile +#1 transferring dockerfile: 1.03kB done +#1 DONE 0.0s + +#2 [internal] load metadata for docker.io/library/alpine:3.21 +#2 ... + +#3 [auth] library/alpine:pull token for registry-1.docker.io +#3 DONE 0.0s + +#4 [internal] load metadata for docker.io/library/golang:1.24-alpine +#4 DONE 0.5s + +#2 [internal] load metadata for docker.io/library/alpine:3.21 +#2 DONE 1.0s + +#5 [internal] load .dockerignore +#5 transferring context: 2B done +#5 DONE 0.0s + +#6 [builder 1/6] FROM docker.io/library/golang:1.24-alpine@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 +#6 resolve docker.io/library/golang:1.24-alpine@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 0.0s done +#6 DONE 0.0s + +#7 [stage-1 1/5] FROM docker.io/library/alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 +#7 resolve docker.io/library/alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 0.0s done +#7 DONE 0.0s + +#8 [internal] load build context +#8 transferring context: 22.16MB 0.3s done +#8 DONE 0.3s + +#9 [builder 2/6] WORKDIR /app +#9 CACHED + +#10 [builder 3/6] COPY go.mod go.sum* ./ +#10 CACHED + +#11 [builder 4/6] RUN go mod download || true +#11 CACHED + +#12 [builder 5/6] COPY . . +#12 DONE 0.5s + +#13 [builder 6/6] RUN go mod tidy && CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=0.155.0 -X main.BuildTime=2026-10-16T10:27:51Z" -o /felhom-hub ./cmd/hub/ +#13 0.588 go: downloading gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 +#13 0.674 go: downloading github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e +#13 0.674 go: downloading modernc.org/fileutil v1.3.40 +#13 0.752 go: downloading golang.org/x/term v0.27.0 +#13 1.407 go: downloading modernc.org/goabi0 v0.2.0 +#13 1.407 go: downloading modernc.org/ccgo/v4 v4.30.1 +#13 1.408 go: downloading golang.org/x/tools v0.38.0 +#13 1.408 go: downloading modernc.org/cc/v4 v4.27.1 +#13 1.568 go: downloading modernc.org/opt v0.1.4 +#13 1.568 go: downloading modernc.org/sortutil v1.2.1 +#13 1.568 go: downloading modernc.org/token v1.1.0 +#13 1.568 go: downloading modernc.org/strutil v1.2.1 +#13 1.677 go: downloading modernc.org/gc/v2 v2.6.5 +#13 1.677 go: downloading golang.org/x/mod v0.29.0 +#13 1.677 go: downloading modernc.org/gc/v3 v3.1.1 +#13 1.853 go: downloading github.com/hashicorp/golang-lru/v2 v2.0.7 +#13 2.297 go: downloading golang.org/x/sync v0.17.0 +#13 DONE 28.9s + +#14 [stage-1 2/5] RUN apk add --no-cache ca-certificates tzdata +#14 CACHED + +#15 [stage-1 3/5] COPY --from=builder /felhom-hub /usr/local/bin/felhom-hub +#15 DONE 0.1s + +#16 [stage-1 4/5] RUN mkdir -p /data /etc/felhom-hub +#16 DONE 0.4s + +#17 [stage-1 5/5] COPY assets/ /usr/share/felhom/assets-seed/ +#17 DONE 0.2s + +#18 exporting to image +#18 exporting layers +#18 exporting layers 1.7s done +#18 exporting manifest sha256:85993ce3107c07224e3b2879ac5923484faae39bc2855d45727b08388e6072f9 done +#18 exporting config sha256:b9b65f8d1a9f22c7db3e3380dac44cd225e6f74d7c6fc54fc06ac5ac67922285 done +#18 exporting attestation manifest sha256:e008c6587c24824a95cbd92de610e8d88690f621c25bd6a58091058cf4cee387 0.0s done +#18 exporting manifest list sha256:cbb985f112ea91faca35753e00b2050afa62f8f43959cd92f1a572a83332357c done +#18 naming to gitea.dooplex.hu/admin/felhom-hub:0.155.0 done +#18 unpacking to gitea.dooplex.hu/admin/felhom-hub:0.155.0 +#18 unpacking to gitea.dooplex.hu/admin/felhom-hub:0.155.0 0.4s done +#18 DONE 2.2s +[INFO] Pushing... +The push refers to repository [gitea.dooplex.hu/admin/felhom-hub] +230e7dbd455c: Waiting +ae65be320b40: Waiting +578a00837f0f: Waiting +16333ee0c00f: Waiting +b0bdb16b2ff6: Waiting +8473a613e3d1: Waiting +230e7dbd455c: Waiting +ae65be320b40: Waiting +578a00837f0f: Layer already exists +16333ee0c00f: Layer already exists +b0bdb16b2ff6: Waiting +8473a613e3d1: Waiting +8473a613e3d1: Pushed +230e7dbd455c: Pushed +b0bdb16b2ff6: Pushed +ae65be320b40: Pushed +0.155.0: digest: sha256:cbb985f112ea91faca35753e00b2050afa62f8f43959cd92f1a572a83332357c size: 856 + +[INFO] ╔══════════════════════════════════════╗ +[INFO] ║ Build complete ✓ ║ +[INFO] ╚══════════════════════════════════════╝ +[INFO] Image: gitea.dooplex.hu/admin/felhom-hub:0.155.0 +[INFO] Size: 27M + +[INFO] Image pushed to registry. + +[INFO] Deploy to k3s — GitOps ONLY. The manifest in git is the source of truth: +[INFO] 1. bump the image tag in /tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/ea0beee5-5628-47eb-8358-0fda9995b9cd/scratchpad/hubclone/manifests/hub.yaml to 0.155.0, commit, push +[INFO] 2. hard-refresh + sync the ArgoCD app (auto-sync is OFF — the sync is deliberate): +[INFO] sudo kubectl -n argocd annotate application felhom argocd.argoproj.io/refresh=hard --overwrite +[INFO] sudo kubectl -n argocd patch application felhom --type merge \ +[INFO] -p '"{"operation":"initiatedBy":{"username":"cc"}}"' -p '"{"operation":"sync":{"syncStrategy":{"apply":{}}}}"' +[INFO] +[INFO] NEVER `kubectl set image` or `kubectl apply` by hand — ArgoCD reverts it on the next +[INFO] sync, so the change looks applied, works for a while, and then silently disappears. + diff --git a/documentation/audits/romm-ghost-2026-10-16/B/hub-live-check.txt b/documentation/audits/romm-ghost-2026-10-16/B/hub-live-check.txt new file mode 100644 index 00000000..985c2d1b --- /dev/null +++ b/documentation/audits/romm-ghost-2026-10-16/B/hub-live-check.txt @@ -0,0 +1,13 @@ +2026-10-16 10:40:28 UTC +argocd: sync=Synced health=Healthy rev=f1e1c8a60c208da70a71f7f34661e9b36123dd1b +pod hub-55b766cf4-lmtqr gitea.dooplex.hu/admin/felhom-hub:0.155.0 ready=true +impact probe v=0.316.3: {"below":0,"spoiled":true,"valid":false,"version":"0.316.3"} +impact probe v=0.319.0 (control): {"below":1,"spoiled":false,"valid":true,"version":"0.319.0"} +POST /configuration/global-floor min_controller_version=0.316.3 (CC token): HTTP/2 303 location: /configuration?flash=floor_spoiled +GET /configuration: 200 +global floor read back (DB value): '0.319.0' | withdrawn notice on page: False +hub log: +2026/10/16 12:40:34 [INFO] managed floor SERVED for demo-hp: floor 0.319.0, agent requirement "0.158.1" from declared (golden 0.318.0) +2026/10/16 12:40:36 [INFO] managed floor SERVED for tester-1: floor 0.319.0, agent requirement "0.158.1" from declared (golden 0.318.0) +2026/10/16 12:40:38 [WARN] global floor "0.316.3" REFUSED: a withdrawn controller build (built from a working copy holding another session's uncommitted files) — nothing saved (R-1085) +2026/10/16 12:40:38 [INFO] access token "cc-releases-2": POST /configuration/global-floor → 303 diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index 12031e64..fa0e2e18 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -26,6 +26,16 @@ --- +## 2026-10-16 — RomM, Ghost, withdrawn controller builds (catalog `fb18471`, hub v0.155.0) + +| Row | What | Closed | Evidence | +|---|---|---|---| +| **R-1089** | **RomM never found the household's games: the first steps put them where the scan does not look.** (P2) | CLOSED 2026-10-16 — **measured first** on demo-hp 9201: a file at the drive's `roms/gba/` gave „No platforms found”, one folder deeper it was found. Catalog `fb18471`: `${USERDATA_PATH}/roms:/romm/library/roms`, firmware in a new drive folder `roms-bios` → `library/bios` (operator ruling: on the drive and in the backup), first steps and compose comment corrected. Scenario A (fresh) and B (old install + correction + restart) pass; the drive unchanged; the data tab lists both folders as backed up. **Reasoning kept:** a game uploaded through RomM under the OLD mapping sits in `roms/roms//` and shows under a fake „roms” platform after the fix — no customer runs RomM; the N100 is R-1099. | full text: `git show f1e1c8a6:documentation/backlog/OPEN-ITEMS.md`; `audits/romm-ghost-2026-10-16/A1/`, `A2/`, `B/` | +| **R-1090** | **Ghost locked a household out on a second device: the new-device code goes by e-mail, and the box wired none.** (P3) | CLOSED 2026-10-16 — **measured first**: Ghost 6.67.0 skips the code only on the first-ever sign-in; the next new device got 500 „Failed to send email”. Catalog `fb18471`: the check is OFF by default (`security__staffDeviceVerification`), and a new `smtp_mapping` turns the mail AND the check on with app e-mail. Scenario E both ways on 9201: off → password alone (201); on → 403 code asked, the mail relayed and delivered, the code accepted (200). One FAQ entry. **Reasoning kept:** Ghost's mailer upgrades to STARTTLS when offered and refuses the shim's self-signed certificate, so it uses the plaintext listener, as cal.com does. | full text: `git show f1e1c8a6:documentation/backlog/OPEN-ITEMS.md`; `audits/romm-ghost-2026-10-16/C1/`, `C2/` | +| **R-1085** | **The floor could still select a withdrawn controller build (0.315.0, 0.316.3).** (P3) | CLOSED 2026-10-16 — **hub v0.155.0** (operator yes, deployed `f1e1c8a6`): one list, refused by both floor forms and the impact probe, HELD at serve time however it was stored (old value, override, config/env default), one `[WARN]` per change, „withdrawn” on the pages; the hand deploy reads the same list from the `felhom-build-deploy` skill, pinned by a parity test. Ten red-proofs seen failing. **Live:** a POST of 0.316.3 with CC's token → `flash=floor_spoiled`, the floor read back unchanged (0.319.0). **Reasoning kept:** the check is NOT in `normalizeFloorInput`, which also validates agent and golden numbers; a new spoiled build is added to the list and the skill in the same commit. | full text: `git show f1e1c8a6:documentation/backlog/OPEN-ITEMS.md`; `audits/romm-ghost-2026-10-16/B/hub-redproofs.txt`, `hub-live-check.txt`; `09-update-architecture.md` §0 | + +--- + ## 2026-10-16 — the app-guides night, closed out (catalog `5cc9e0e`, felhom.eu scripts guard) | Row | What | Closed | Evidence | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 623b2dc8..a7602686 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -124,12 +124,10 @@ stopping line that lies. | **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 | -## Apps & catalog — 21 rows (P2 2, P3 11, P4 8) +## Apps & catalog — 22 rows (P2 1, P3 12, P4 9) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| -| **R-1089** | Apps & catalog | P2 | **RomM may never find the household's games: the first steps put them where the scan probably does not look.** FOUND 2026-10-15 night writing the RomM guide, from RomM 5.3.1's source — NOT measured. RomM's default layout is `library/roms//`; the compose mounts the drive's `userdata/roms` as `/romm/library`, so the first steps' `roms/gba/`, `roms/snes/` land at `library/gba`, outside the scan's `library/roms`. The same first steps send the household to a „Settings" menu and a left-hand „Scan" the new default UI (5.3.1) no longer has (both are in the profile menu now), and the compose comment's default login `admin / admin` is stale (the first run asks for a password). The guide written tonight gives no folder path. Draft and source note: `audits/app-guides-2026-10-15/B/romm-labels.txt`. | OPEN — filed 2026-10-15 | — | On 9202: install RomM, put one legal ROM where the first steps say, scan, read the library; then fix the path (copy or compose) and the two labels | CC | -| **R-1090** | Apps & catalog | P3 | **Ghost may lock a household out of its own blog on a second device: new-device sign-in asks for a code by e-mail, and the box wires no e-mail for Ghost.** FOUND 2026-10-15 night from Ghost 6.67.0's source (`ghost/core` defaults: `security.staffDeviceVerification: true`) — NOT measured. The same missing mail makes staff invitations and newsletters impossible; the guide leaves those out and says so. Related: the first-admin audit already marks Rallly and Outline sign-in as needing e-mail (`app-catalog-felhom.eu/FIRST-ADMIN.md`). | OPEN — filed 2026-10-15 | — | On 9202: set Ghost up, sign in from a second browser, see whether a code is asked; if so, switch the app's e-mail on or set the flag off in the template | CC | | **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-981** | Apps & catalog | P3 | **The homepage app gets the Docker socket (that is root in the guest) and has no login, no setup gate and no family gate.** Fix: remove the socket mount, or put homepage behind the family gate. 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 | — | Change the catalog template; prove on 9202 | CC | @@ -149,6 +147,9 @@ stopping line that lies. | **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 | the fleet floor reaching 0.315.0 | 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:///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-1095** | Apps & catalog | P4 | **`/apps/{slug}/install` still opens for an app that is INSTALLED, and shows the install form read-only.** Found 2026-10-16 (app page tuning, controller v0.319.0): the settings tab stopped rendering the install form, but `appInstallPageHandler` renders `deploy` for any app, so a typed or stale `/install` address shows „ — Telepítés" with disabled fields and no button. Its secret guards hold (R-254/R-709 tests; parity cases `deploy_installed*`). | OPEN | the task forbade route changes | a 302 from `/apps/{slug}/install` to `/apps/{slug}/settings` for an installed app (the `redirectStackDeployGET` shape); then the deployed-only branches of `app_fields.html` can go | CC | +| **R-1096** | Apps & catalog | P3 | **After a disk-full, an app's damaged Redis file keeps the app down for good, and nothing says what to do.** FOUND 2026-10-16 on demo-hp 9201: the standing RomM (installed 2026-08-21) was stopped; the dashboard's Start re-created it, `romm-redis` crash-looped (unhealthy), the box stopped it again (`crash_loop`, `09` §3 decision 28) and mailed `app_start_failed`. Its Redis volume dates from the 2026-09-24 thin-pool fill (R-672): Docmost's damaged append-only file was repaired by hand that day with `redis-check-aof --fix` (`audits/r672-2026-09-24/B7-redis-aof-fix.txt`); RomM's was not (the copy `romm_romm_redis_data.pre-aof-fix-20260924T135937Z` is still on the box). The Redis log was not read: the box removed the containers first. That RomM was then removed by operator ruling (`audits/romm-ghost-2026-10-16/A1/`). | OPEN — filed 2026-10-16 | — | List the catalog apps whose Redis runs with `--appendonly yes` (romm does); for each, decide whether its Redis needs persistence at all; reproduce on 9202 by damaging an append-only file, and make the box repair a copy or say what to do | CC | +| **R-1097** | Apps & catalog | P3 | **RomM keeps the household's saves, save states and screenshots in an anonymous Docker volume, outside the backup.** FOUND 2026-10-16 from RomM 5.3.1's source and the running container: `ASSETS_BASE_PATH = /romm/assets` (`backend/config/__init__.py:39`); the image declares `VOLUME /romm`; the compose mounts nothing at `/romm/assets`, and `docker inspect` on 9201 showed an anonymous volume at `/romm`. Compose keeps an anonymous volume across a recreate, so an update does not lose it; a remove + reinstall or a restore does, and the app backup (named volumes + drive folders) probably does not hold it — NOT measured. Moving it to a named volume or the drive would hide what a box already has, so a fix needs a one-time copy. | OPEN — filed 2026-10-16 | — | Measure on 9202: a save in a browser game, back up, restore, read the save; then a named `romm_assets` volume with a one-time copy from the anonymous one | 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//`; 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 | ## App updates — 4 rows (P3 4) @@ -316,7 +317,6 @@ stopping line that lies. | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| | **R-1083** | Process & tooling | P3 | **The scratch box 9202 cannot use any feature that needs the agent: its `bootstrap.json` has no `local_api` block, so its controller never gets an agent client.** MEASURED 2026-10-15 (storage-identity build A): 9202 put on controller 0.317.0 by hand to show that a NAS share no longer hides the one-copy notice (R-1075); its `bootstrap.json` (read on the box) holds no `local_api`, so the controller has no agent to ask and keeps the old `st_dev` rule — the check could not tell old from new — 9202 was put back on 0.314.0. Every agent-coupled feature (drives, same-disk, format, network storage) is untestable on the scratch box today, and the NAS leg of build A is proven by a red-proofed unit test only. Evidence: `audits/storage-identity-buildA-2026-10-15/9202-upgrade.txt`. | OPEN | — | Give 9202 a local-API token the normal way (the agent mints it for its guest), or record in `runbooks/target-selection.md` that 9202 is controller-only; then show the NAS notice live. | CC | -| **R-1085** | Process & tooling | P3 | **Registry tag `felhom-controller:0.316.3` holds another session's uncommitted code; it must never be offered to a box — and the floor can still offer it.** Today the update button offers the highest tag (0.317.0), so nothing reaches it — but any path that picks a lower or specific tag could. The paths that choose a controller version (`architecture/09-update-architecture.md` §0 and decision 250 item 1; felhom-controller `internal/selfupdate/updater.go`), read 2026-10-15 night: (1) the household's button and the daily auto-update — `TriggerUpdate` takes the HIGHEST semver tag in the registry: **cannot** select 0.316.3 while a higher tag exists; (2) the floor, global or per customer — `MaybeAutoUpdate` targets the floor EXACTLY, and the hub's floor save accepts any semver (`hub/internal/web/configs.go` `normalizeFloorInput`): **can** select 0.316.3, and 0.315.0, if anyone types it; (3) the golden — its version is the controller it bakes, and the artifact save refuses a golden older than the newest controller CHANGELOG version (`runbooks/RUNBOOK-manual-build.md` §4): cannot, today; (4) a hand deploy that names a tag (the guest bootstrap deploy in the `felhom-build-deploy` skill): **can**. The fact itself is on file in R-1081 since `bf99798c` (21:48, three minutes after the reviewer checked); this row is the route to the tag, which R-1081 does not cover. Static reading only. | OPEN — filed 2026-10-15 night (the reviewer's item, app-guides session); no code changed tonight | — | A list of spoiled tags (0.315.0, 0.316.3) refused by both floor forms and named in the deploy runbook, with a decoy that types 0.316.3 into the floor | CC | | **R-1081** | Process & tooling | P4 | **Two published controller tags hold content that is not what their number says: `0.315.0` and `0.316.3`.** Both were caused by CC on 2026-10-15, and both by the same shape — a guard that was not on the path the work takes. **`0.316.3`:** the build directory's `build.sh` is a symlink into the SHARED clone's working copy, which predated the `FELHOM_REPO_DIR` override, so the override was silently ignored and the build copied that tree — which held 31 uncommitted files of another session's physical-disk storage work. The banner printed `Commit: 0225255` with no `Repo:` line. **Nobody deployed it; that was luck, not a control.** Fixed at the root in controller v0.316.4: `build.sh` now refuses `--push` from a tree that is dirty, not at `origin/main`, or not a git checkout, with four test cases red-proved. **Neither tag is re-pushed or deleted** — a version number is pushed once, and that applies to numbers CC spoiled. **No box runs either:** demo-hp 9201 is on 0.317.0, the floor is 0.314.0, and the button offers the highest tag. **`0.315.0`:** Caused by CC on 2026-10-15 while proving R-1080's own fix: the guard was added to `controller/build.sh` in the repo, but the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a **separate copy taken 2026-07-18** and had no guard. Running the refusal against a real existing tag therefore rebuilt and re-pushed 0.315.0 — a fourth time — with v0.316.0's code under it (digest `8887343011d6` → `b809201841f7`). **Fixed at the root in the same session:** the build directory's script is now a symlink to the repo's, the old copy kept beside it as `build.sh.stale-copy-2026-07-18`, and the refusal was then proven to fire from the documented path. **No box is affected:** nothing runs 0.315.0 (demo-hp 9201 is on 0.316.2), the floor is 0.314.0, and the household button offers the highest tag. What remains is two tags that must never be deployed and are no longer reproducible from the registry. | **OPEN** — filed 2026-10-15; nothing to do unless someone needs one of those two images, and then the answer is the current release | — | Leave them. If an audit ever needs the real 0.315.0 or 0.316.3, build it from the matching commit under a NEW number and say so | CC | | **R-992** | Process & tooling | P3 | **The drill-catalog route that `09` §6.5 documents cannot be followed: `admin/app-catalog-drill` is PRIVATE and no box has catalog credentials.** Measured 2026-10-12 while publishing the Usenet pair: the box's clone fails with `fatal: could not read Username for 'https://gitea.dooplex.hu'` (exit 128), the Gitea API reports the drill repo `private=true` and the live catalog `private=false`, and NEITHER the current controller.yaml on 9202 NOR any of its 30 saved copies has ever carried `git.username` or `git.token` (the controller supports both — `internal/config/config.go` GitConfig). The route worked for the campaigns of 2026-10-10 and the torrent pair earlier the same day, so the repository's visibility changed between then and now. **Worked around, not fixed:** that campaign staged its two templates in the box's own catalog cache (`audits/usenet-2026-10-12/tools/stage_in_cache.py`), which is where the sync copies a catalog change anyway, with the sync interval raised to 12h for the duration and rescan used in place of sync. The real fix is an operator decision: either a read-only catalog credential on the scratch box (a secret on a Tier-0 guest) or making the drill repository readable. Until then every new-app campaign hits this. | WAITING-ON-OPERATOR | the operator's choice between a box credential and a readable drill repo | Decide; then either set `git.username`/`git.token` on 9202 from the credentials store, or flip the drill repo, and correct `09` §6.5 either way | operator | | **R-1017** | Process & tooling | P3 | **A comment names a guard test that does not exist.** `felhom-controller/controller/internal/appbackup/paths.go:58` says „`TestNoBareDrivePathToUserdataDir` is the guard that keeps the count from growing”; no test of that name (or `NoBareDrive*`) exists in the repo (grep 2026-10-14; positive control: `TestRemoveStack_R800_UserdataKeptIsNamed` is found). A comment asserting an invariant with no test pinning it — the class in the `felhom-testing` skill. | **READY** — filed 2026-10-14 | — | Write the guard (a test that fails when a bare drive path reaches `UserdataDir`), red-proof it, or correct the comment | CC | @@ -345,6 +345,7 @@ stopping line that lies. | **R-1025** | Process & tooling | P4 | **A power drill on demo-hp cuts Tester 1 too, and nothing warns the session using it.** 2026-10-14 17:25 CEST another session cut demo-hp's plug (`plugcut.py --plug hp`, the Immich drill) while this session ran downloads on Tester 1 (VM 341 runs on demo-hp); Tester 1 rebooted and came back by itself. The drill's fence checks Tester 1 for backups and package jobs only. | **OPEN** — filed 2026-10-14 | — | Add „a test runs on Tester 1” to the fence (a marker file the testing session writes), or name the rule in `runbooks/target-selection.md` | CC | | **R-1039** | Process & tooling | P4 | **qBittorrent stalls on the LAST piece of an archive.org torrent; Transmission does not.** Seen three times on 2026-10-14 (Tester 1 S2 and S3, demo-hp 9201 live read-back): the film reaches 99.994 %, the piece shared with archive.org's side files (`__ia_thumb.jpg`, `_meta.sqlite`, `_meta.xml`) never arrives, qBittorrent's log shows „500 Internal Server Error” from the archive.org web seed, while Transmission finishes the same torrent and curl range requests on the same files answer 206. A household that uses archive.org torrents with qBittorrent hits it too; our tests finish only with a local peer. | **OPEN** — filed 2026-10-14 | — | Note it in qBittorrent's onboarding record; for test films use Transmission, or a public-domain torrent with real peers | CC | | **R-1078** | Process & tooling | P4 | **A controller comment still describes `mp1 /mnt/sys_drive` as a separate volume.** `C/cmd/controller/main.go:2546` („the SYSTEM-DATA volume (`mp1`, `/mnt/sys_drive`)…”) — `mp1` was removed by R-165; `sys_drive` is a folder of `mp0` (measured on all three boxes, `audits/storage-identity-2026-10-15/README.md`). The same stale cell in `07-backup-architecture.md` §6.1 was corrected in this session. Not fixed here: this run allowed no product-repo change. | READY | — | Correct the comment in the next controller change. | CC | +| **R-1098** | Process & tooling | P3 | **The hub build does not refuse a version that already exists in the registry.** `scripts/build-hub.sh` v1.24.0 refuses a dirty tree or one behind `origin/main`, but not an existing tag; the controller's `build.sh` refuses a second push of a tag (`09-update-architecture.md` §0). Found 2026-10-16 building hub 0.155.0; checked by hand that time (`docker manifest inspect`: 0.155.0 absent, control 0.154.0 present; `audits/romm-ghost-2026-10-16/B/hub-build-0.155.0.txt`). | OPEN — filed 2026-10-16 | — | Give `build-hub.sh` the controller's refusal (registry check; an unreachable registry refuses), with test cases seen failing; check the agent build the same way | CC |