i18n slice 1 release C (controller v0.250.0): R-556 CLOSED, live evidence, R-565..R-568, decision 6 superseded
gates / gates (push) Successful in 23s

- audits/i18n-slice1-2026-09-17/C/: live before/after/en/back on demo-hp (CSRF redacted), hub report
  hu/en/hu, red-proofs, the switch fixture diff (89 of 89), green gate.
- 10-localisation.md: §2.2 executeTemplateLang facts, §2.3 what stays Hungarian + the English test's
  ASCII blind spot, §3 decision 6 SUPERSEDED 2026-09-17, §5 formal ceiling 16 and its under-count,
  English retrieval stems; §10 slice 1 done; §11 decision 6 struck.
- Register: R-556 closed to CLOSED-ITEMS; R-516 extended; R-565 (ASCII-only Hungarian invisible to the
  English page test), R-566 (three app-name page titles), R-567 (wizard nav highlight), R-568 (disk
  rows reorder). 260 -> 263 open.
- Capability map row, STATUS (needs you: the floor delivers the switch).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 18:50:16 +02:00
parent bc15153e0f
commit 5b6ade033f
57 changed files with 28747 additions and 22 deletions
+6
View File
@@ -26,6 +26,12 @@
---
## 2026-09-17 — localisation slice 1: the whole dashboard in English (controller v0.248.0–v0.250.0)
| Row | What | Closed | Full text |
|---|---|---|---|
| **R-556** | **Localisation slice 1 — the other 31 dashboard templates in English, Hungarian byte-identical (P3).** Closed in controller **v0.248.0** (`ff68b0b`, apps and settings), **v0.249.0** (`11b790e`, backups) and **v0.250.0** (`ac149c2` + `be2efe5`, storage, sharing, sign-in, claim, guest share, catch-all, debug, and the switch for everyone). Every page's parity fixtures were captured from its UNCONVERTED template before conversion (`6be55a0`, `f8ebc47`, `0555091`); 106 states. `TestI18nParityCoversEveryMarker` (every marker rendered by a case); English page mask narrowed to ≥ 2 words / ≥ 12 chars; retrieval-promise gate scans English; `executeTemplateLang` for the six pages outside the dashboard chrome (no session CSRF, no escrow reminder — `TestI18nDirectRenderPagesHaveNoAdminChrome`); `TestHandlerTitleKeysMatchHungarianTitle`. Decision 6 (switch hidden) superseded: the switch is on every dashboard page; the 89 layout fixtures re-captured and each equals its predecessor plus exactly one switch form. **Proven live on demo-hp 9201** for each release: Hungarian pages equal their previous-version fetch apart from live numbers (and, in C, the switch form); `POST /settings/language` made them English and back; the hub stored `hu`, `en`, `hu`. Startup parse: hu ~24–27 ms, en ~20–28 ms. **Reasoning kept:** a word a page COMPARES stays unconverted until the comparison is language-neutral (R-563); a fixture is never regenerated to make a conversion pass — the two re-captures in this slice (C's case titles, the switch) were captured from the tree that defines the truth and their diffs were proven to be exactly the intended change. Found: R-563, R-564, R-565, R-566, R-567, R-568; R-516 extended. `audits/i18n-slice1-2026-09-17/{A,B,C}/` | **CLOSED 2026-09-17 — PROVEN-LIVE** | full text: `git show bc15153:documentation/backlog/OPEN-ITEMS.md` |
## 2026-09-16 — the drill: the tunnel answers from outside
| ID | Title | Shipped | Evidence |
+5 -2
View File
@@ -711,7 +711,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **R-508** | **[P2-MEDIUM] Customer `tester-1` has no registered e-mail, so neither the self-bind link nor the setup code can reach a volunteer.** MEASURED 2026-09-14: the edit form's `email` value is empty; on bind the hub logged `[ERROR] [claim] claim code generated (gen 1) but customer tester-1 has NO registered email — deliver via resend after setting one`. A volunteer onboarded on this record would sit at „A szerver beállítása" with no code. **What it needs:** the operator sets the volunteer's address on the record before sending the guide (day-0 A.2). The hub's customer page could warn when a record with an unclaimed box has no e-mail — the log line exists, the page says nothing. | **WAITING-ON-OPERATOR — rank P2-MEDIUM; owner: operator (record), CC (page warning)** |
| **R-509** | **[P1-HIGH] A box installed for an EXISTING customer never gets the self-bind e-mail the console tells the volunteer to open.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1): customer `tester-1` now has `tester1@felhom.eu` registered; the box registered as appliance 28 at 17:44:53Z and its console says „Nyisd meg az e-mailben kapott linket"; **ten minutes later the mailbox (read through the Gmail connector) held 0 messages to that address.** Cause, from source: the hub auto-sends the link only at customer creation (`hub/internal/web/configs.go:725`) and at RESET completion (`customer_reset.go:162`); a customer whose e-mail was added later, or whose previous box was destroyed, never receives one unless the operator presses „Send self-bind link". The volunteer guide's operator prerequisites do not list that press. Intervention **I1** of the big night (the operator's button pressed). **Fix shape (for the operator to choose):** send the link when an unclaimed appliance registers and a customer with no host is waiting, or add the press to the guide's operator prerequisites (day-0 A.2). **SHIPPED hub v0.114.0 (2026-09-15), NOT YET PROVEN BY A REAL MAIL:** triggers added — e-mail set/changed on a customer with no box, and host delete — each re-checking no bound host; every send recorded as `selfbind_link_sent` and shown on the Setup tab. Unit-proven with a red-proof (`TestSelfBind_EmailSetOnWaitingCustomerSendsLink`). The live check with the Gmail-read mailbox was NOT run: it needs a throwaway customer, and deleting one runs the RESET cascade (ep0 `deprovision` + Cloudflare), which is fenced without the operator's word. **Closes on:** one real mail from either trigger. | **READY — rank P1-HIGH; owner: CC (hub fix) · operator (which fix shape)** |
| **R-511** | **[P2-MEDIUM] A customer whose box is rebuilt keeps its ep0 PBS token, and then the DR tier can be neither provisioned nor re-issued: the hub's error advises the one action that refuses.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, `tester-1`, DR tier ticked): on the new box's WireGuard registration the hub logged `[ERROR] pbsdr auto-provision for tester-1 (WG-registration hook): the endpoint already holds a PBS token for tester-1 but the hub has no descriptor — use the explicit "Re-issue PBS credentials" action — save the customer config to retry`. The operator's `POST /configs/tester-1/pbsdr-reissue` → **400 `No provisioned PBS DR tier for this customer`** (`hub/internal/web/pbsdr.go` ~411). The token was left by the doorstep walk's host delete (a host delete does not deprovision tenancy; only RESET does, which also removes the tunnel). So a box rebuilt for an existing customer — the reinstall journey — has no whole-guest off-site tier and no button that restores it. **Fix shape:** let re-issue adopt an existing endpoint token when the descriptor is absent (the message already assumes it does), or have host delete offer to drop the PBS token. **SHIPPED hub v0.114.0 (2026-09-15):** re-issue ADOPTS the endpoint token when the descriptor is absent and the DR flag is on (re-key + descriptor rebuilt from the endpoint + `pbsdr_adopted` audit row); DR flag off still refuses, endpoint untouched (both pinned, red-proofed). **NOT proven on tester-1's real state:** re-issue needs an enrolled host and tester-1 has none. **ep0 cleanup DONE on the operator's yes (2026-09-15 08:33Z):** the one doorstep snapshot `ns tester-1 / ct/9201/2026-09-14T16:04:53Z` (logical 1 928 820 672 B) forgotten via the local PBS API; namespace lists empty; token `felhom@pbs!tester-1` kept; nothing outside tester-1 read or touched (`D3-*` in `audits/evidence-p1fixes-2026-09-15/`). The token-only release on host delete was NOT built → R-526. **Closes on:** adopt ending in a descriptor the next tester-1 box consumes **CONSEQUENCE MEASURED 2026-09-16:** the code fix shipped for this row is sound and currently INERT - the adopt path runs and the ENDPOINT refuses it for a missing `Datastore.Modify` grant (**R-534**). On the drill box the result is that a fresh install has NO off-site tier at all, and read-only listing of ep0 shows `ns/tester-1/ct` empty before and after the whole drill. This row stays open until R-534's grant is given and a re-issue is seen to succeed. **CLOSED 2026-09-16 — the adopt path is PROVEN LIVE.** The rebuilt-customer case reproduced by itself on a fresh box (the WG-registration hook refused exactly as this row describes and named the Re-issue action), and the adopt then completed: reissue ok → pbsdr ADOPTED (gen 2) → the box consumed the single-use secret two seconds later. The code shipped 2026-09-15 was sound; what made it inert was the ep0 grant (R-534), now given. | **CLOSED 2026-09-16 — proven live on a fresh box** |
| **R-516** | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). | **READY — rank P3-LOW; owner: CC (controller + catalog)** |
| **R-516** | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). **Extended 2026-09-17 by slice 1 (controller v0.248.0–v0.250.0):** the converted copy now counts **16** formal forms (`HU_FORMAL_CEILING` 6 → 10 → 12 → 16, each raise stated, Hungarian unchanged by rule) — and the count is an UNDER-count: the gate's stem list sees 4 on the release C pages (storage, drive wizards, debug), while a wider list („írja be”, „adja meg”, „adjon hozzá”, „válassza ki”, „biztosan eltávolítja”, „engedélyezze”, „hozzon létre”, „kattintson”) finds at least **22** keys there alone (login „Adja meg a jelszavát”, the NAS guide, the storage confirms). Widen the stems when this row is worked; the ceiling rises with them. | **READY — rank P3-LOW; owner: CC (controller + catalog)** |
| **R-518** | **[P2-MEDIUM] „Mentés most" on the whole-system backup stops every app for about eight minutes while the page promises „csak néhány másodpercre".** MEASURED 2026-09-14 (BIGNIGHT, VM 333, 12 apps): the button's call quiesced all 12 stacks at 19:03:23Z (first stopped 19:03:27Z); the local vzdump ran 19:03:49 → 19:09:59Z; the controller then kept the apps stopped for the second (PBS) tier and restarted them at 19:10:09Z after it failed, the last started 19:11:12Z (`phase4/guest-backup-quiesce-log.txt`) — **≈ 7 m 45 s** with every app answering 404. The page under the button: „Pillanatkép-mód: az alkalmazások csak néhány másodpercre állnak le." A household pressing it at dinner loses every app for the length of the dump, and longer on a bigger box. **Fix shape:** state the real expected downtime (it scales with data), or quiesce per tier and not across a second tier's attempt; do not start a tier whose storage is absent (see R-517). **NARROWED 2026-09-15 (controller v0.243.0 + agent v0.131.0):** a tier whose storage the agent reports absent is skipped before anything stops (`backup_tier_skipped`, once per absence; unknown never skipped), and the button copy now says „általában néhány perc, nagyobb adatnál több". Unit-proven with red-proofs. **Still open:** quiesce per tier, so a slow second tier does not keep every app down. | **READY — rank P2-MEDIUM; owner: CC (controller)** |
| **R-519** | **[P2-MEDIUM] After a backup torn by a power cut, an app's restore point carries the new database dump's time while its files are from the previous run — and no customer screen says the run was interrupted.** MEASURED 2026-09-14 (BIGNIGHT F2, VM 333): „Mentés most" 19:40:03Z; the power was cut 19:40:08Z while adventurelog was stopped for its volume dump. On disk afterwards, `backups/primary/adventurelog`: `db-dumps/adventurelog-postgres.sql` **19:40:07**, `volume-dumps/*.tar` **19:00:35**, `manifest.json created_at 19:02:34Z`; bookstack the same shape (sql 19:40:07, tars 19:00:48). `GET /api/backup/snapshots` for both → `time 2026-09-14T19:40:07Z, helyi` (`phase5/F2/units-on-disk.txt`, `backup-honesty.txt`). `/backups/apps` shows „Utolsó adatbázis mentés 2026-09-14 21:40 · … OK" and every app „Utolsó: 5 perce"; `/backups` and `/dashboard` contain no word of an interruption (fragments `megszakad|sikertelen|nem sikerült` = 0; control „hiba" appears in the standing warning text). The controller itself knew: `[appstop] crash recovery: an app-data backup (volume dump) … was interrupted … restarting them: [adventurelog]` and pushed `backup_failed (error)` to the hub. A household restoring „the 21:40 backup" gets 21:00 files for BookStack's uploads. **Fix shape:** date a point by the oldest part it contains (or mark it partial) and show the interrupted run on the backups page until the next complete one. **F6 (drive unplugged 1 s into a backup, 20:37:59Z) adds three facts:** the run skipped four apps' volume dumps („Skipping volume dump for immich — drive disconnected", also jellyfin, nextcloud, paperless-ngx) and still reported `db_dump {"count":6, "success":true}`; nextcloud's point is dated 20:37:59Z (its SQL finished before the unplug) beside 19:01 volume tars; and a torn `immich-postgres.sql.tmp` (20:38:00) plus F3's `pre-restore-…-nextcloud-mariadb.sql.tmp` are left in the units on the drive. Immich's point correctly stayed at 19:02:34Z (the `.tmp` was not promoted). `/backups/apps` fragments `kihagy|sikertelen|részleges` = 0 (`phase5/F6/`). | **READY — rank P2-MEDIUM; owner: CC (controller)** |
| **R-520** | **[P3-LOW] A power cut during a guarded Update leaves no record that an update was running, so whether the journal resumes or aborts honestly is unmeasured for a real version change.** MEASURED 2026-09-14 (BIGNIGHT F3, VM 333): the only Update the catalog allowed after the Phase 4 revert was a same-version one on nextcloud; power was cut 2 s after `update nextcloud: phase pulling` (safety dump written, pin advanced to the unchanged definition). After boot: no log line resumes, aborts or names the interrupted update; `app.yaml` `pinned_images` = `installed_images`, no hold, no verdict; page „Fut · Naprakész". Nothing wrong was produced — and nothing could have been, with identical images. **What it needs:** the same cut on a real bump (a throwaway app with a one-step catalog move on a scratch branch or the scratch guest), asserting the page's message and the pin after boot. | **READY — rank P3-LOW; owner: CC (drill)** |
@@ -734,7 +734,6 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **R-553** | **[P3-LOW] Four places decide BEHAVIOUR by matching Hungarian wording — each breaks the day the words change, in either language.** FOUND 2026-09-17 by the i18n inventory (`audits/I18N-INVENTORY-2026-09-17.md` §3), read at source: (1) `controller/internal/api/router.go:478` picks **400 vs 500** for a deploy error by `strings.Contains(err, "kötelező"/"memória")`; (2) `internal/backup/offbox.go:186` classifies the off-site **quota** failure by „tárhelykeretet" in a lower-cased error; (3) `internal/web/alerts.go:208` moves a disk warning inline-vs-top-banner by „meghajtón"/„adattároló"/„meghajtó"; (4) `internal/web/handlers.go:927` compares a **persisted** off-site warning against `offboxStaleWarningMarker` („nincs mentésre jelölt alkalmazás") — a warning written in one language is compared in another. (A fifth hit, `offbox_handlers.go:132`, is a detector false positive.) Templates: zero (detector decoy-tested). **Fix shape:** a typed error / sentinel / reason code at each producer, the display text derived from it, one red-proofed test per site asserting the consequence (status code, quota class, placement, substitution) with the wording changed. **Blocks localisation slice 2 (R-557)** — it goes first. | **READY - rank P3-LOW; owner: CC** |
| **R-554** | **[P3-LOW] Delete the first-boot setup wizard — obsolete by design, still reachable.** OPERATOR DECISION 2026-09-17 (localisation starter, decision 4: „out of scope, obsolete"). `02-controller-module-map.md` L56 calls `internal/setup/` obsolete; `cmd/controller/main.go` L322 still enters it when `setup.NeedsSetup(cfg)` — `customer.id` empty after bootstrap ingestion, or a `.needs-setup` marker (`internal/setup/setup.go` L17-25). Ingestion leaves `customer.id` empty on a missing/invalid `bootstrap.json`, a failed hub pull, or a failed merge/write/reload (`internal/bootstrap/bootstrap.go` L109-163) — so a box whose first boot cannot reach the hub shows a household an 8-page wizard (95 Hungarian strings, its own template set and CSRF). **Fix shape:** decide what such a box shows instead (a single „cannot reach Felhom yet, retrying" page — needs no decision beyond copy), then delete `internal/setup/` and `runSetupMode`; red-proof that a failed ingestion renders the waiting page, not a 404. **Check first** whether any drill/golden path still relies on `.needs-setup`. | **READY - rank P3-LOW; owner: CC** |
| **R-555** | **[P3-LOW] The wire-contract gate counts a field as received when its name appears in a Go COMMENT on the receiving side.** FOUND 2026-09-17 by CC adding the report's `language` field (controller v0.247.0): `scripts/wire_contract_gate.py` passed WITHOUT an allowlist entry, because `receiver_tokens()` tokenises whole files and the word „language" occurs hub-side only in a comment (`hub/internal/web/configs.go:558`, „this page's existing language"). The shape is the gate's own named failure class (name-for-fact, R-421): any English tag name that also appears in hub prose passes unread. The field was allowlisted by hand with this row named. **Fix shape:** strip `//` and `/* */` comments (and template `{{/* */}}`) before tokenising; add the decoy „a tag whose name appears only in a receiver comment must convict"; expect a handful of currently-passing tags to surface — each is a finding, not noise. | **READY - rank P3-LOW; owner: CC** |
| **R-556** | **[P3-LOW] Localisation slice 1 — the other 31 dashboard templates in English, Hungarian byte-identical.** PLAN 2026-09-17 (`architecture/10-localisation.md` §10). ~1 500 strings, ~600 of them inside `<script>`. Per page: capture parity fixtures from the UNCONVERTED template, run `controller/scripts/i18n_extract.py`, **review by eye what it left** (its ASCII-only word list missed 8 strings on the first three pages), translate, parity + English page tests green. Also: show the language switch to every household (§3, decision 6); give the retrieval-promise gate English stems (it scans Hungarian only); widen the English page test past its fixture-string masking (§2.3). Cost 12–16 CC-hours over three releases. **PROGRESS 2026-09-17 — release A shipped (controller v0.248.0):** dashboard, stacks, logs, monitoring, import, export, deploy and the three settings pages converted against fixtures captured unconverted (`6be55a0`); marker-coverage test; English mask narrowed; English retrieval stems; live on demo-hp with Hungarian identical apart from live measurements (`audits/i18n-slice1-2026-09-17/A/`). Next: release B (backups), release C (storage, sharing, sign-in pages, the switch). **PROGRESS — release B shipped (controller v0.249.0):** backups_apps, backups_remote, backups_restore, restore wizard, escrow, tier2_config, recovery (routed through `executeTemplateLang`) converted against fixtures captured unconverted (`f8ebc47`); live on demo-hp, Hungarian identical apart from relative ages; hub saw `en` then `hu` (`audits/i18n-slice1-2026-09-17/B/`). Found R-564. | **IN PROGRESS - rank P3-LOW; owner: CC** |
| **R-557** | **[P3-LOW] Localisation slice 2 — Go-side customer strings follow the language.** PLAN 2026-09-17 (10 §10). Inventory §2.2: 947 shown + 184 error literals in 84 files; 237 format strings, 111 concatenations, 42 numeric `%d` (English plurals). Flash messages travel inside the redirect URL (`?flash=<Hungarian>`) and must become keys; `cloudflare/countries.go` (113 country names); alert texts; handler errors printed with `err.Error()`. **After R-553** (the four compare-not-show sites). Cost 16–20 CC-hours. | **READY - rank P3-LOW; owner: CC** |
| **R-558** | **[P3-LOW] Localisation slice 3 — the hub's customer e-mails follow the household's language; the operator sets it at customer creation.** PLAN 2026-09-17 (10 §3, §10), operator decision 2. The box already reports `"language"` (controller v0.247.0); nothing hub-side reads it. Build: a per-customer language on the hub (default hu) set at creation and rendered into `controller.yaml` beside `customer.*` (`hub/internal/configgen/configgen.go`), used by the box only while `settings.json` has no choice; the dispatcher picks the language the box REPORTS; English for the 39 `customerMessages`, the 4 severity labels, the body wrapper, the claim/re-enroll/reset/claimed/self-bind mails and the public bind page (inventory §2.3). Delete the `language` allowlist entry in `wire_contract_gate.py` when the field is read. Cost 6–8 CC-hours, one hub + one controller release. | **READY - rank P3-LOW; owner: CC** |
| **R-559** | **[P3-LOW] Localisation slice 4 — the console banner and the download page in English.** IN SCOPE — operator ruling 2026-09-17 evening („yes", 10 §11 1b). PLAN 2026-09-17 (10 §10, open decision 1b): the starter listed them; the operator's scope ruling names „the controller, emails, guide, app catalog" and not them. Inventory §2.4: 34 banner lines (`scripts/iso/felhom-bootstrap.sh`, printf to the console; `/etc/issue` byte-coupled to `iso/pkg/debian/postinst`, console font avoids ő/ű) and 32 strings on `website/letoltes.html`. Cost 4–6 CC-hours plus an ISO release train. | **READY - rank P3-LOW; owner: CC** |
@@ -743,6 +742,10 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **R-562** | **[P3-LOW] Dates and sizes are not formatted for any locale — and the Hungarian pages disagree with themselves.** FOUND 2026-09-17 by the i18n inventory §2.8: the two template date layouts differ (`2006. 01. 02. 15:04` Hungarian vs `2006-01-02 15:04` ISO); 10 layout literals in `internal/web` Go and 25 elsewhere pick formats ad hoc; sizes print a decimal POINT (`%.1f GB`, 4 helpers) where Hungarian uses a comma; `timeAgo`/`nextRunLabel`/`pruneLabel` produce Hungarian words outside the three converted pages. Not changed by v0.247.0 (Hungarian bytes are frozen by the parity rule). **Fix shape:** one date and one size formatter per language in `internal/i18n`, the Hungarian output deliberately changed in ONE reviewed release with the parity fixtures re-captured for that release only and the change named in its CHANGELOG. Needs an operator word on the Hungarian format (comma, date style). | **READY - rank P3-LOW; owner: CC** |
| **R-563** | **[P3-LOW] `backups_remote.html` decides its progress poll by reading its OWN Hungarian status text — `v.textContent.indexOf('Fut')` — so an English page would never see a running off-site backup on load.** FOUND 2026-09-17 by the slice-1 comparison sweep (R-556, `audits/i18n-slice1-2026-09-17/A/A1-comparison-sweep.txt`): accented Hungarian in template/JS comparisons measured zero, but the ASCII-only word „Fut" is compared at `backups_remote.html` L288 against the stat value rendered at L48 („Fut…"). Same class as R-553 (behaviour decided by wording), one layer up in the browser. **Not converted in slice 1**: „Fut…" at L48 stays Hungarian on the English page so the poll keeps working. **Fix shape:** render a language-neutral marker (`data-status="running"`) beside the text and test that instead; then convert the word; a render test asserting the attribute. | **READY - rank P3-LOW; owner: CC** |
| **R-564** | **[P3-LOW] The retrieval-promise gate's Hungarian stems cannot see a SPLIT verb — „csak akkor állíthatók vissza", „hozod vissza" — so those Hungarian sentences were never scanned; the English translation exposed them.** FOUND 2026-09-17 by slice 1 release B (R-556): after the gate learnt English (`EN_PATTERNS`), seven English retrieval phrases on `backups_remote`, `backups_escrow`, `backups_restore` and `backups_restore_wizard` had NO Hungarian registration, because their Hungarian carries the verb particle after the verb („A távoli mentések csak akkor állíthatók vissza …", „a távoli mentések CSAK ezzel a kóddal állíthatók vissza", „csak a hiányzó fájlokat hozod vissza"). The stems (`visszaállíthat`, `visszaszerezhet`, `visszahozhat`, `visszanyit`) match only the joined form. The seven were registered in English with reasons (none is a false promise: two are preconditions, five describe the action on the same page). **Fix shape:** add split-form patterns to the Hungarian scan (`állíthatók? vissza`, `(hoz|szerez|nyit)\w* vissza`), register the Hungarian occurrences found, decoy with a planted split-verb promise. | **READY - rank P3-LOW; owner: CC** |
| **R-565** | **[P3-LOW] The English page test sees only ACCENTED Hungarian: an ASCII-only Hungarian word left in a template passes it on the English page.** FOUND 2026-09-17 by slice 1 release C (R-556, controller v0.250.0): after the extractor and the tests were green, a by-eye review of the English renders found six Hungarian fragments still in JavaScript strings — „, majd a(z)” and „FIGYELEM:” in the storage decommission dialog, „jelenlegi:” on the drive-init list, the uptime units „mp” and „p” and the count word „ db” on the debug page. All six were converted by hand; **no test failed on any of them**, because `TestI18nEnglishPages` looks for Hungarian letters and the extractor's ASCII word list (`i18n_extract.py` `ASCII_HU`) is used by neither test nor gate. Release B's review had found more of the same kind (Konfig, Megtartva, helyi, pl., Befejezve, automatikus, jelenleg:, kedd/szerda/szombat, szint). **Fix shape:** run the ASCII word list over the English renders in `TestI18nEnglishPages` (after the data mask), with a negative control on an English sentence and a decoy planting „mp” in an English value; extend the list with the words releases B and C found. | **READY - rank P3-LOW; owner: CC** |
| **R-566** | **[P3-LOW] Three page titles are built in Go around an app name and stay Hungarian in the English browser tab: „<app> — Naplók”, „<app> — Telepítés” / „— Beállítások”, „2. mentés beállítása — <app>”.** FOUND 2026-09-17 by slice 1 release C while giving every static title a key (`TestHandlerTitleKeysMatchHungarianTitle` pins those): `handlers.go` logs (L413) and deploy (L435–439), `tier2_config_handler.go` L33 concatenate the app name into the Hungarian title, so `TitleKey` (one static message) cannot carry them. The page BODIES are English. Belongs to slice 2 (R-557, Go-side strings): a title key with a parameter (`page.title.logs` = „{{.App}} — Naplók”, expanded Go-side) and the pin test extended to the three sites. | **READY - rank P3-LOW; owner: CC** |
| **R-567** | **[P3-LOW] The two drive wizard pages (/storage/init, /storage/attach) do not highlight the Tárhely menu group — the sidebar reads as if the household left the storage section.** FOUND 2026-09-17 by slice 1 release C: the release C parity cases first used page name `storage` for the wizards; re-captured with the handler's real page name (`storage_handlers.go` `storageWizardPageHandler` passes the TEMPLATE name, `storage_init`/`storage_attach`, as `Page`) the fixtures lost `nav-group is-open` and the `active` link. Present since the wizards shipped; not caused by localisation. **Fix shape:** pass `Page` `storage` (or teach the layout's storage group both names), with a render assertion that /storage/init carries the open storage group. | **READY - rank P3-LOW; owner: CC** |
| **R-568** | **[P3-LOW] The dashboard's drive-health rows swap order between visits — the same two disks, listed in a different order a minute apart.** MEASURED 2026-09-17 on demo-hp 9201 during slice 1 release C's live proof: `/dashboard` fetched on 0.249.0 listed „KXG50PNV1T02 NVMe TOSHIBA 1024GB” then „SanDisk X600 M.2 2280 SATA 128GB”; fetched on 0.250.0 a minute later, the reverse (`audits/i18n-slice1-2026-09-17/C/live/hu-before-vs-after.txt`). `diskHealthRows` (`disk_health.go` L135–141) keeps the agent's response order and does not sort; the agent's order is therefore not stable. Cosmetic, but a household that reads „the second disk” finds a different one. **Fix shape:** sort the rows controller-side by a durable key (device path or serial), with a test that feeds two orders and expects one. | **READY - rank P3-LOW; owner: CC** |
| **R-537** | **[P1-HIGH] The app-backup page labels the tier-1 backup „DB + Konfig + Adatok" and prints the app's data-drive size next to it — but the tier-1 unit contains NO drive-side app data at all.** MEASURED 2026-09-16 on the drill box (fresh install, controller 0.243.0, one drive, tier 2 and tier 3 both „Nincs beállítva"): five photos (3 000 000 B) were uploaded into Nextcloud through its own WebDAV interface, then the customer-visible „Mentés most" was pressed (`POST /api/backup/run` → 200, the unit grew 25 337 B → 978 MB). The resulting unit's `manifest.json` lists `db-dumps` + three **docker volume** dumps and nothing else; listing the 781 MB `nextcloud_nextcloud_html.tar` (29 346 entries, positive control `version.php` = 3 hits) gives **`Fotok` = 0 and `nyaralas` = 0**, and `./data/` is the empty bind-mount point. A `find` over the whole `backups/` tree for `*appdata*` / `*Fotok*` returns nothing. The page nevertheless renders „1. mentés … DB + Konfig + Adatok" and „Nextcloud Adatlemez 65.1 MB" — a size measured on exactly the data it does not copy (`internal/web/handlers.go:1176-1178`, `BackupContents`). **This is a truth defect, not a design defect:** `07-backup-architecture.md` §6.2 places nextcloud's file leg at **Tier 2 and Tier 3 only**, and its „[FACT] What the whole-guest tiers do NOT carry" says `mp8 /mnt/felhom-drives` is out of vzdump scope (confirmed live: „excluding bind mount point mp8 … (not a volume)"). So on a one-drive box with no off-site tier — the state every fresh install starts in — the household's files are in **no backup**, while the page says „Adatok". Same family as R-517/R-518. **Fix shape:** render tier-1 contents from the capture set actually written (`ComputeCaptureSet`), so a unit with no file leg reads „DB + Konfig" and the drive size is not shown beside it; and say on the page that the app's files need tier 2 or tier 3. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** The contents label is computed PER TIER from what that tier captures: Tier 1 says „Adatok" only when the app's data really is in the volumes the unit captured, and a class-A app carries one sentence saying where its files ARE protected. Proven on demo-hp through the page the customer opens: Paperless-ngx reads „1. mentés … DB + Konfig" with „Az alkalmazás fájljait a távoli másolat (és a második meghajtó) védi …", while its „2. mentés" row still reads „DB + Konfig + Adatok". Red-proof: restoring the old app-shaped label fails `TestAppBackupRows_Tier1LabelDoesNotClaimFilesItCannotHold`. **RE-PROVEN 2026-09-16 on a FRESH box** (installed from the built ISO 1.28.0, controller 0.244.0, off-site on by default): the Nextcloud row read „1. mentés … DB + Konfig" with the new sentence, „2. mentés … Nincs 2. (off-drive) másolat", „3. mentés Sikeres restic → …your-storagebox.de"; „DB + Konfig + Adatok" appeared ZERO times while the local unit held no file leg. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** |
| **R-538** | **[P1-HIGH] A tier-1 app restore reports plain success and leaves Nextcloud listing files whose bytes were never in the backup — and it destroys the app's own trash, the customer's last copy.** MEASURED 2026-09-16 on the drill box, F10 („a child deletes the photo folder"): the five photos were deleted through Nextcloud (DELETE 204, PROPFIND 404), then restored through the page exactly as a customer would (`POST /backup/restore` `stack_name=nextcloud` `snapshot_id=helyi` → 302, finished in **35 s**, „A(z) nextcloud: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult."). Afterwards the folder is back and **lists all five photos**, and **none of them opens**: `GET nyaralas-1..5` = 404 / 503×4 with `Sabre\DAV\Exception\NotFound`, while the positive controls at the same moment pass (`status.php` 200, WebDAV PUT 201, GET 200). Cause: the replayed MariaDB dump (11:01:45Z) knows the photos, the bytes live on `mp8` and were never captured (R-537). **Worse:** the bytes were still on the drive in Nextcloud's own trash (`appdata/nextcloud/admin/files_trashbin/files/Fotok.d1789556707/nyaralas-1..5.jpg`, all five present) and the restored database no longer references them — the trash listing comes back **empty**, so „restore from trash", the one route that would have worked, is gone. The customer is left with five unopenable photos, a success message, and no warning. **Fix shape:** before replaying a database whose app has an uncaptured file leg, refuse or warn („ennek az alkalmazásnak a fájljai nincsenek ebben a mentésben — a visszaállítás után a fájlok hiányozni fognak"); and never present a DB-only restore of a class-A app as a complete one. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** A unit restore refuses before anything is touched when the unit cannot return the app's drive-side files, and names the route that can. Fired live on demo-hp: `POST /backup/restore` for paperless-ngx → 302 with „Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza …", and the app read `running` before AND after, so nothing was stopped and no trash was made unreachable. The database-and-settings-only path exists as a separately worded second step. Red-proof: disabling the guard fails `TestUnitRestore_RefusesWhenTheUnitCannotHoldTheFiles`. **RE-PROVEN 2026-09-16 on a FRESH box, and this time the refusal had somewhere to point:** after five photos were deleted, `POST /backup/restore` was refused with „…a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: … „Teljes visszaállítás (fájlok + adatbázis)"", the app read `running` before AND after, and the wastebasket was untouched. The off-site route then returned all five photos — 200 with the exact uploaded sizes and sha256 IDENTICAL to the originals, 5/5, with a negative control. Evidence: `audits/evidence-backup-promise-2026-09-16/phaseE-photos.txt`. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** |
| **R-525** | **[P3-LOW] FileBrowser has its own login; putting it behind the dashboard session (traefik forwardAuth or Quantum proxy auth) is a new mechanism nobody has measured.** Filed 2026-09-15 by the P1-fixes task (B.5). R-513 closed the default-password hole with a generated password; a household still has two logins. **What it needs:** a spike on a scratch guest — forwardAuth to the controller session, and what FileBrowser Quantum does with a trusted header. | **READY — rank P3-LOW; owner: CC (spike)** |