The setup code and the owner passphrase now follow the household's language,
one word longer in English so the entropy never drops (setup 3 hu / 4 en,
passphrase 5 hu / 6 en). List and count are chosen together so a caller cannot
pair an English list with a Hungarian count. Hungarian is byte-unchanged.
Three claims in the row were wrong and are recorded as such:
- the RECOVERY CODE is minted by felhom-agent from the EFF list and has
always been English; the hub does not own it and no row was added.
- no claim mail states a word count; the only count wording was the bind
page's passphrase hint, whose English half is now count-free.
- the proposed phone-safe filter removes 68% of the list (5270 of 7772
words) and was measured, then declined, with the reason in source.
Also: guide_quote_gate binds the English volunteer guide's three quoted
messages to the controller's English bundle — nothing did, so the guide would
have gone on quoting Hungarian after the fix. Seven decoys, all convicting,
including the name-for-fact one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
| the **claim page's messages** („Hibás vagy lejárt kód", „A jelszónak legalább 12 karakter…", the lockout) | composed sentences passed into page data, the R-573 shape one screen earlier | **R-596 (P1)** |
| the **setup code itself** (and the recovery code and owner passphrase) | one Hungarian wordlist, not chosen per language | **R-597** |
| the **Backup page's two protection warnings** and its two target names | composed sentences in `backup_handlers.go` / `backup_target_offer.go` | **R-598** |
@@ -771,11 +771,14 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server`
| **R-593** | **[P3-LOW] papra's session-signing key is described as „the app's subdomain".** FOUND 2026-09-20 translating the catalog (R-560 slice 5, batch 1). `templates/papra/.felhom.yml``deploy_fields[AUTH_SECRET].description` reads **„Az alkalmazás aldomainje"** — the sentence that belongs on `SUBDOMAIN`, on a field that signs sessions. `SUBDOMAIN` itself has NO description at all in that file, so this is a copy-paste that landed one field too low and took the original with it. A customer opening papra's install page reads a wrong explanation under a key they must not regenerate. **A localisation release may not change Hungarian bytes (10 §1)**, so it was not fixed there; and translating a wrong sentence faithfully would have shipped the error in a second language, so **that one field was left untranslated** — it falls back to the Hungarian exactly as today, and it is the ONE string keeping papra at 13/14 and the catalog ceiling off zero. **Fix shape:** move the sentence to `SUBDOMAIN` and give `AUTH_SECRET` its own („A munkamenetek aláírásához használt kulcs" or similar), re-capture that app's two entries in `scripts/copy_freeze/hu.json` in the same commit with the reason, then add the English. Owned by R-516 as a Hungarian-words change. | **READY - rank P3-LOW; owner: CC (catalog)** |
| **R-594** | **[P3-LOW] The catalog copy gate can CONVICT a retrieval promise but has no way to REGISTER a true one.** FOUND 2026-09-20 translating batch 3 (R-560 slice 5). Vaultwarden's invite step and its sign-up setting both ended „…can open an account", and the English retrieval-promise pattern reads `can … open` as the claim that sealed backups can be opened. **The conviction was a FALSE POSITIVE** — opening an account is not opening a backup — and the two sentences were reworded to „can sign up", which is also the better copy, so nothing is blocked today. **The gap is structural.** The shared vocabulary this gate copies (`scripts/customer_copy_vocab.py`) states the design explicitly: these stems are NOT banned, because each carries a claim that is sometimes TRUE, and *"an occurrence must be REGISTERED with a reason in the consuming gate's allowlist"*. The hub gate has `ALLOWLIST_EN`; `app-catalog-felhom.eu/scripts/check-copy-i18n.py` has none, so the only ways past it are to reword or to bypass the gate — and a catalog app whose English genuinely says a file can be restored (a backup app, a versioned document store) has no honest third option. **Fix shape:** an `ALLOWLIST_EN` of `(app, path, reason)` in the gate, a decoy proving a REGISTERED occurrence passes and an unregistered one still convicts, and a check that every entry still matches something (a stale allowlist entry was R-299's shape, and the retrieval gate has gone red on stale entries before). | **READY - rank P3-LOW; owner: CC (catalog)** |
| **R-595** | **[P2-MEDIUM] The catalog's new copy gate could not RUN in CI at all — six pushes red, six alarm mails, while the local hook was green.** FOUND 2026-09-20 by reading the operator's inbox at the start of localisation slice 6 — **not** by anything in the session that caused it, which is the point worth keeping. Every one of slice 5's six catalog pushes (`e81d41e`, `d9aa02a`, `0695d8e`, `5fe70d1`, `1f80650`, `de392cd`) produced *[felhom CI] gates FAILED*, and the alarm's own text says what that means: *"If the local pre-push hook was GREEN for this commit, then CI and the hook disagree — that is a finding about the gates themselves, not about CI, and it outranks whatever the push was for."***Cause, found by CONTRAST:**`check-copy-i18n.py` imports PyYAML and is the only gate in that repo importing anything outside the standard library; `.gitea/workflows/gates.yml` states in its own header that the runner is *"a host-mode container with python3 and git and nothing else"*. The gate raised `ImportError` before checking anything, so the runner exited non-zero on every push, clean ones included. **FIXED the same session (catalog `18a6d2d`, CI job 791 SUCCESS, verified by id):** a DEGRADED MODE rather than a skip — without PyYAML the gate runs the check that needs no parser and matters most (every frozen Hungarian string must still occur verbatim in its app's bytes) and prints in full what it did NOT check. Measured first: 1 030 of 1 032 frozen strings appear byte-for-byte in the raw files; the two that do not are romm help_texts whose YAML escapes an inner double quote, so the escaped spelling is accepted too — 1 032 of 1 032 found, so the degraded check convicts nothing honest. Five new decoy cases run with PyYAML shadowed by a module that refuses to import, i.e. what CI actually executes. **The general form, and the reason this is P2 rather than P3:** a new gate is written and tested on the machine that has every library, and the runner deliberately has none. **Nothing in the pre-push hook can catch that** — the hook runs on the same rich machine. The only detector is the alarm mail, and it worked; what failed is that six of them went unread for two hours inside the session that caused them. | **CLOSED 2026-09-20 — degraded mode; CI job 791 green** |
| **R-596** | **[P1-HIGH] The claim page — the FIRST screen an English household touches — is English chrome with HUNGARIAN messages, and two of them are quoted in the guide as English.** FOUND 2026-09-20 on a fresh install by the slice-6 drill, **seen on screen, not read in source**. The page itself is English (*"Set up the server"*, *"Enter the setup code you got by e-mail…"*, *"Set up and sign in"*, *"Did not get the code? Ask for a new one"*). Its MESSAGES are not: a short password answered **„A jelszónak legalább 12 karakter hosszúnak kell lennie"** and a mistyped code answered **„Hibás vagy lejárt kód"**. **`internal/web/claim.go` carries SIXTEEN raw Hungarian literals**, every one of them a message this page shows: L281/L334 („A beállító állapot most nem olvasható…"), L286, L310/L444 („Érvénytelen űrlap…"), L317/L321/L359 („Túl sok próbálkozás — próbáld újra 15 perc múlva."), L338, L362 („Hibás vagy lejárt kód"), L368, L372 („A két jelszó nem egyezik"), L379/L384, L448, L523, L563. **Why every earlier slice missed it:** they are not error VALUES (slice 2 converted 179 of those) and not template text (slice 1 converted that — which is why the chrome IS English); they are composed sentences passed into `handleClaimPage(w, r, <msg>, "")` as page data. **The same shape as R-573's two banners**, one screen earlier in the journey. **It ranks P1 because of WHERE it is:** a household that cannot read „Hibás vagy lejárt kód" cannot tell a typo from a dead code, on the one screen that stands between them and their box — and the English guide, written from the bundle rather than from the screen, promises them *"Wrong or expired code"* and *"Too many attempts — try again in 15 minutes."*, which the product does not say. **Fix shape:** keys for all sixteen, `handleClaimPage` taking a key + args instead of a sentence, and a render case per message in both languages. |**READY — rank P1-HIGH; owner: CC (controller)** |
| **R-597** | **[P2-MEDIUM] The setup code is three Hungarian words, inside an otherwise fully English e-mail, sent to a household the hub knows is English.** FOUND 2026-09-20 by the slice-6 drill. The mail is English end to end (slice 3 working); the code it carries was **`képző-szkítia-ásatás`** — 20 characters, 3 words, **5 of them outside ASCII** (ő, í, á×2, é). An English speaker must copy three words they cannot read, spell or say aloud, and type them into a box on a keyboard that has no ő. They can paste — until the day they read the code to someone over the telephone, which is precisely what a three-word code is FOR. **The same generator feeds the recovery code (10 words) and the owner passphrase (5 words)**, so the fault is one wordlist wide, not one mail wide: this walk saw the passphrase too and it is Hungarian. **Fix shape:** an English wordlist chosen per `customer.language`, with the same word count and the same entropy, and a test that pins BOTH lists' entropy and that no word in either needs a character outside the reader's keyboard. **Not a rename of the existing words** — a second list. |**READY — rank P2-MEDIUM; owner: CC (hub)** |
| **R-598** | **[P2-MEDIUM] The Backup page's two protection warnings — the ones that say whether the household's files are safe — are Hungarian on an English dashboard.** FOUND 2026-09-20 by the slice-6 drill on a fresh box, and confirmed on the demo box. Of 73 lines on `/backups` exactly four are Hungarian: **„Csak egy másolat készül (nincs második meghajtó) — a 3-2-1 mentéshez csatlakoztasson egy második meghajtót vagy offsite tárolót"**, **„A rendszermentés jelenleg ugyanazon a lemezen van, mint a rendszer — így hibás fájlok ellen véd, lemezhiba ellen nem"**, and the two backup-target names **„Helyi tároló (local)"** and **„Biztonsági szerver – külön hardver (PBS)"**. They come from `internal/web/backup_handlers.go` (12 Hungarian literals) and `internal/web/backup_target_offer.go` (10) — again composed sentences handed to the page, the R-573/R-596 shape. **It matters more than its line count:** those two warnings are the only place the product tells a household that one copy on one disk is not protection, and the volunteer guide's §9 sends every tester to exactly this page to read exactly these two sentences. **Fix shape:** keys + args for both files, with the retrieval-promise gate run over the English (these sentences are about what a backup does and does not protect). |**READY — rank P2-MEDIUM; owner: CC (controller)** |
| **R-596** | **[P1-HIGH] The claim page — the FIRST screen an English household touches — is English chrome with HUNGARIAN messages, and two of them are quoted in the guide as English.** FOUND 2026-09-20 on a fresh install by the slice-6 drill, **seen on screen, not read in source**. The page itself is English (*"Set up the server"*, *"Enter the setup code you got by e-mail…"*, *"Set up and sign in"*, *"Did not get the code? Ask for a new one"*). Its MESSAGES are not: a short password answered **„A jelszónak legalább 12 karakter hosszúnak kell lennie"** and a mistyped code answered **„Hibás vagy lejárt kód"**. **`internal/web/claim.go` carries SIXTEEN raw Hungarian literals**, every one of them a message this page shows: L281/L334 („A beállító állapot most nem olvasható…"), L286, L310/L444 („Érvénytelen űrlap…"), L317/L321/L359 („Túl sok próbálkozás — próbáld újra 15 perc múlva."), L338, L362 („Hibás vagy lejárt kód"), L368, L372 („A két jelszó nem egyezik"), L379/L384, L448, L523, L563. **Why every earlier slice missed it:** they are not error VALUES (slice 2 converted 179 of those) and not template text (slice 1 converted that — which is why the chrome IS English); they are composed sentences passed into `handleClaimPage(w, r, <msg>, "")` as page data. **The same shape as R-573's two banners**, one screen earlier in the journey. **It ranks P1 because of WHERE it is:** a household that cannot read „Hibás vagy lejárt kód" cannot tell a typo from a dead code, on the one screen that stands between them and their box — and the English guide, written from the bundle rather than from the screen, promises them *"Wrong or expired code"* and *"Too many attempts — try again in 15 minutes."*, which the product does not say. **Fix shape:** keys for all sixteen, `handleClaimPage` taking a key + args instead of a sentence, and a render case per message in both languages. **CLOSED 2026-09-21, controller v0.259.0.****The row over-counted and mis-counted.** Fifteen literal sites reach that page, carrying **nine** distinct messages (four are repeats); the sixteenth, L286 `data["Title"]`, is **DEAD** — `claim.html` is a standalone page with its own bundle-backed `<title>`, and `.Title` is read only by `layout.html`, which this page never includes. It was **deleted, not translated**: a translated dead field would have read for ever after as evidence that this page's title is decided in the handler. L523 (`--print-reset-code` stdout, operator-facing) and L563 (the `claim_lockout` event, whose customer text the HUB already localises as `mail.event.claim_lockout`) are wire copy and were correctly left alone. Fourteen live sites now go through `s.msg(r, "claim.msg.*")`. **The anonymous cookie-less page's language chain was an unpinned assumption and is now a test** (`langFor` → `settings.GetLanguage` → `configLanguage` ← `cfg.Customer.Language`). **Proven LIVE on guest 9201:**`felhom_lang=en` → "Invalid form — reload the page." and "Too many attempts — try again in 15 minutes."; `felhom_lang=hu` → the byte-identical Hungarian. The lockout **proved itself unasked** — Hungarian attempts locked out the English request from the same source, demonstrating live that the counter is per source and not per language. Red-proofed by restoring the wrong-code literal (the test convicted on both halves: the English absent AND the Hungarian present). | **CLOSED 2026-09-21 — controller v0.259.0, proven live** |
| **R-597** | **[P2-MEDIUM] The setup code is three Hungarian words, inside an otherwise fully English e-mail, sent to a household the hub knows is English.** FOUND 2026-09-20 by the slice-6 drill. The mail is English end to end (slice 3 working); the code it carries was **`képző-szkítia-ásatás`** — 20 characters, 3 words, **5 of them outside ASCII** (ő, í, á×2, é). An English speaker must copy three words they cannot read, spell or say aloud, and type them into a box on a keyboard that has no ő. They can paste — until the day they read the code to someone over the telephone, which is precisely what a three-word code is FOR. **The same generator feeds the recovery code (10 words) and the owner passphrase (5 words)**, so the fault is one wordlist wide, not one mail wide: this walk saw the passphrase too and it is Hungarian. **Fix shape:** an English wordlist chosen per `customer.language`, with the same word count and the same entropy, and a test that pins BOTH lists' entropy and that no word in either needs a character outside the reader's keyboard. **Not a rename of the existing words** — a second list. **CLOSED 2026-09-21, hub v0.119.0.****One third of the row was wrong: the recovery code was never Hungarian.**`felhom-agent` mints it (`internal/escrow`) from the **EFF large wordlist** and always has — ten English words, ≈129 bits. The hub does not own that secret and no row was opened for it: a second definition here is the drift `backupTargetAbsentText` already demonstrates across two repos. The two the hub DOES mint now follow the household: setup code 3 hu words (44.6 bits) → **4 en words (51.7)**, owner passphrase 5 hu (74.3) → **6 en (77.5)**, list and count chosen together by `RandomPassphraseFor(lang, use)` so a caller cannot pair an English list with a Hungarian count. **The floor is computed from the embedded lists at test time, not compared with a constant** — red-proofed at 3 English words (38.77 vs 44.56). Hungarian is byte-unchanged, and the list length is pinned so a swap cannot move it quietly. **The task's proposed "read it over the phone" filter was MEASURED and NOT adopted** — it removes 5270 of 7772 words (68%, 12.92 → 11.29 bits/word) and would make this list stricter than the one the product already uses for the code a household writes on paper during a disaster; what it reached for is kept as an assertion (`TestEnglishListIsTranscribable`: 3-9 lower-case ASCII letters, no digit, no separator). Decision recorded in source, **operator may reverse**. Also: **no claim mail ever stated a word count** — the only count wording was the bind page's passphrase hint, whose English half is now count-free. | **CLOSED 2026-09-21 — hub v0.119.0** |
| **R-598** | **[P2-MEDIUM] The Backup page's two protection warnings — the ones that say whether the household's files are safe — are Hungarian on an English dashboard.** FOUND 2026-09-20 by the slice-6 drill on a fresh box, and confirmed on the demo box. Of 73 lines on `/backups` exactly four are Hungarian: **„Csak egy másolat készül (nincs második meghajtó) — a 3-2-1 mentéshez csatlakoztasson egy második meghajtót vagy offsite tárolót"**, **„A rendszermentés jelenleg ugyanazon a lemezen van, mint a rendszer — így hibás fájlok ellen véd, lemezhiba ellen nem"**, and the two backup-target names **„Helyi tároló (local)"** and **„Biztonsági szerver – külön hardver (PBS)"**. They come from `internal/web/backup_handlers.go` (12 Hungarian literals) and `internal/web/backup_target_offer.go` (10) — again composed sentences handed to the page, the R-573/R-596 shape. **It matters more than its line count:** those two warnings are the only place the product tells a household that one copy on one disk is not protection, and the volunteer guide's §9 sends every tester to exactly this page to read exactly these two sentences. **Fix shape:** keys + args for both files, with the retrieval-promise gate run over the English (these sentences are about what a backup does and does not protect). **CLOSED 2026-09-21, controller v0.259.0.** The row's count of `backup_handlers.go` was 12; **nine are code and three are Hungarian inside COMMENTS**. The offer file's ten is right. `degradedMessageFor` now returns a **KEY** — the decision stays language-free and in one place, the words are chosen by the caller that knows the reader — and `buildTierViews` / `backupTargetLabel` / `loadGuestBackup` take the language the way `buildDataPathCards` already did. **The English is asserted to carry the same NEGATION the Hungarian does** ("protects against corrupted files, **but not** against a disk failure"); an English sentence that promised disk-failure protection would be worse than leaving it Hungarian. **Proven LIVE on guest 9201** for the two tier names ("Local storage (felhom-backup)", "Backup server – separate hardware (PBS)"); **the two warnings themselves were NOT walked live** — that box is healthy and a healthy box renders nothing by design, and producing the state would mean un-assigning a live backup target. They are covered by render tests through the real handler in both states. **An apostrophe cost a render:** the first English absent-drive sentence never matched because `html/template` escapes `'` to `'` — caught by the test, not by review. | **CLOSED 2026-09-21 — controller v0.259.0; the two warnings proven by render test, not live** |
| **R-599** | **[P3-LOW] A drill's teardown is blocked for 30 minutes by design, and nothing says so.** FOUND 2026-09-20 tearing the slice-6 drill down. VM destroyed at 17:12Z; the hub then refused **both**`POST /configs/<id>/delete` (409, *host … is ONLINE*) and the host delete (`deletable:false`) — correctly, because an online host would receive permanent 401s. But "online" is not a liveness probe: it is a **report-staleness window**, and the window is **45 minutes** — `manifests/hub.yaml` sets `alerting.stale_threshold: "45m"`, which `hostStatus()` reads (`ok` under it, `stale` over, `down` at 2x). A machine that no longer exists therefore reads ONLINE for three quarters of an hour. **Measured the boring way, and worth recording:** this row first said 30 minutes, because `monitor/host_staleness.go`'s literal default says 30m — the DEPLOYED value is in the manifest, and the 409s kept coming after the half hour was up. Reading a default and calling it the live value is the same mistake in a smaller coat. **The consequence is not theoretical:** a session that destroys its VM and then tears down the hub side walks away believing the delete failed, or leaves the customer behind — and the 2026-09-14 drill's teardown had the same shape without recording this. **Fix shape (smallest first):** the 409 body says *how long* it will refuse ("the last report was N minutes ago; deletion opens at HH:MM"), and `runbooks/target-selection.md`'s drill section names the wait. A force flag is NOT proposed — the refusal is right, only silent about its own clock. | **READY — rank P3-LOW; owner: CC (hub)** |
| **R-600** | **[P2-MEDIUM] "Full teardown" is logged while the deleted box's WireGuard peer is still configured on ep0.** FOUND 2026-09-20 by the slice-6 drill's teardown, **measured on ep0 rather than inferred from the hub**. The customer delete cascade finished at 19:41:54 with `customer DELETE cascade COMPLETE for drill-en-0920 (journal #18) — full teardown`, and every hub-side row was gone (0 configs, 0 hosts, 17 residue rows purged, PBS tenancy deprovisioned, escrow demoted). **Three minutes later `wg show wg0 allowed-ips` on ep0 still listed `10.77.0.5/32`** — the drill box's peer — because `wgsync` pushes on its own cycle. **Watched to the end rather than assumed: the peer was gone by 17:47:46Z — it outlived the *full teardown* line by about 6 minutes.** (My first estimate said ~35, read off the gap between two log lines; the sync runs oftener and only LOGS when something changes. That is the second time in this session that a period inferred from two log lines was wrong — the other was the delete's own staleness window. **A period read off two log lines is not a measurement.**) **The 2026-09-14 drill's findings say "the teardown removes it through the host delete"; measured, the host delete removes the hub's RECORD and the peer goes on the next push.** The mechanism is not broken — it is asynchronous, and the log line claims a completeness it does not yet have. **Why it is P2 rather than P3:** a session that tears down, reads *full teardown*, and leaves is the normal case; the peer outlives it by minutes, and the third teardown layer is the one the workspace rules single out as the one that gets forgotten. Six minutes is short — but the session that reads *full teardown* and leaves has no way to know it is six and not six hours. **Fix shape (smallest first):** the cascade triggers a wgsync push before it logs COMPLETE, or the log line says what is still pending and when ("wg peer removal queued; next push in N min"). A session should not have to read ep0 to know whether a teardown finished. | **READY - rank P2-MEDIUM; owner: CC (hub)** |
| **R-601** | **[P2-MEDIUM] `demo-hp` has been unreachable since at least this morning — offline on the tailnet for 30 days by Tailscale's own count, and not on the LAN either.** FOUND 2026-09-21 while looking for guest 9201 to ship controller v0.259.0. **What was tried, in order:**`ssh demo-hp` (tailnet `100.76.96.79`) → connection timed out; `sshpass` with the hub-vaulted G1 break-glass password → same timeout; `demo-hp-lan` (`192.168.0.87` via `ProxyJump felhom-pve`) → *No route to host*; `ping 100.76.96.79` → 100% loss; `tailscale status` from the DooPlex pod → **`demo-hp … offline, last seen 30d ago, tx 6084 rx 0`**; `ip neigh` on felhom-pve → `192.168.0.87 … FAILED`. The box answers on no route this session has. **The 30-day figure is Tailscale's and should not be believed on its own** — the slice-6 drill ran its nested drill VM on demo-hp on 2026-09-20 and tore it down, which is not consistent with a box that has been dark for a month; the likelier reading is that its tailscale link has been down for 30 days while the box was reached another way, and the box itself went down more recently. **Either way it is off now.** It also means the fleet's 0.258.0 box is the one that is dark and `demo-felhom` (0.257.0 until today) is the one that is up — so **the fleet is one box, not two, until someone looks at it.** Needs a person: it is a physical machine. | **READY — rank P2-MEDIUM; owner: OPERATOR (physical)** |
| **R-602** | **[P3-LOW] The language a signed-in page uses is NOT the language a cookie asks for, and a live probe that forgets this reports a fixed defect as unfixed.** FOUND 2026-09-21 verifying R-598 on guest 9201. `GET /backups` with `felhom_lang=en` returned the **Hungarian** page. That is correct — `langFor` step 2 says a request carrying a session reads the household's saved setting and deliberately ignores the visitor cookie, so a signed-in family never sees a language a previous visitor picked on the sign-in page of the same browser — but it means **the cookie is the right instrument for the anonymous claim/login/bind pages and the wrong one for every page behind auth**, where `?lang=` is. A session that had run only the cookie probe would have concluded R-598 was still open and fixed it a second time. **This is a documentation gap, not a code defect**, and it is the kind that costs a whole session: nothing in `10-localisation.md` §2.2 or in any runbook tells a prober which instrument to use where. **Fix shape:** four lines in `10-localisation.md` §2.2 — a table of surface → language instrument — and a pointer from the live-validation section of the workspace rules. Recorded meanwhile in `audits/i18n-closing-2026-09-21/live/backups-page.md`. | **READY — rank P3-LOW; owner: CC (docs)** |
| **R-603** | **[P3-LOW] An English string containing an apostrophe silently never matches on a rendered page, and a `strings.Contains` assertion reads exactly like a missing sentence.** FOUND 2026-09-21 while writing the R-598 render tests. `backup.target.absent` was first written as *"The system backup's drive cannot be reached…"*; `html/template` escapes `'` to `'`, so the page carried the sentence and every assertion for it failed. **The failure mode is the expensive part:** the test said *"the English absent-drive copy never reached the page"*, which is indistinguishable from the handler not being wired — and the obvious next move is to go and re-fix the handler. Reworded to avoid the possessive, and all 23 new English values were then swept for `' " < > &` (zero). **The Hungarian bundle has never hit this** because Hungarian copy uses „quotes" and few apostrophes; **English copy will hit it again.****Fix shape:** either a bundle gate that refuses an HTML-escapable character in a value destined for a page (and an allow-list for the ones that legitimately need one), or a test helper that compares against `html.EscapeString(want)` so the assertion cannot be fooled. The gate is the better shape — the helper only protects tests that remember to use it. | **READY — rank P3-LOW; owner: CC (controller)** |
| **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)** |
button to press; the customer page shows when it went out (R-509, 2026-09-15).
2. **Creates the Cloudflare tunnel and enters its token** on the customer's page — without it the
dashboard address does not open (R-494).
3. **Hands over the five-word "Owner passphrase" in person or in a message.** No e-mail contains it.
3. **Hands over the "Owner passphrase" in person or in a message.** No e-mail contains it. It is
six English words for an English account, five Hungarian ones for a Hungarian account (R-597).
## What you will need
- A machine where **all data will be erased** (the install overwrites the chosen disk completely).
- A USB stick of at least 2 GB.
- A network cable to your router.
- The **Owner passphrase** (5 words) you received from Felhom.
- The **Owner passphrase** (a few hyphen-joined words) you received from Felhom.
- The e-mail account you gave to Felhom.
## 1. Download the installer (~1 minute)
@@ -77,7 +78,7 @@ Write the **pairing code** down. It is printed once in each language; both are t
(valid for 7 days).
2. Enter:
- the **pairing code** from the box's screen;
- the **Owner passphrase** (5 words) you received from the Felhom operator.
- the **Owner passphrase** you received from the Felhom operator.
3. Leave the box switched on. The next e-mail arrives in **about 3 minutes**.
*(Note: the box's screen keeps showing the pairing code after it is connected — this is a known
@@ -90,8 +91,10 @@ fault, and you do not need to connect it again.)*
If the address does not open, tell the operator.
3. On the **"Set up the server"** page enter the **setup code** and choose a password of **at least
12 characters**. This becomes your dashboard password.
**The code is three Hungarian words, with accents** (like `képző-szkítia-ásatás`) — copy and paste
it from the e-mail rather than typing it. This is a known fault (R-597).
**The code is the words in your e-mail, joined by hyphens** (for example
`abacus-wreath-ratio-abdomen`). You can type it or paste it; capitals and spaces instead of
hyphens are accepted. If it looks like Hungarian words with accents, tell the operator — an
English account should receive English words (R-597, fixed 2026-09-21).
4. If the code did not arrive: **"Did not get the code? Ask for a new one"** — it always goes to the
same e-mail address.
@@ -138,11 +141,13 @@ Once you are done, the bar disappears and the remote backup starts by itself.
## 9. Backups
- **Backup → Overview:** you will see two yellow warnings. **They are still in Hungarian** on an
otherwise English page (a known fault, R-598), and they say: *only one copy is being made, there is
no second drive*, and *the system backup is on the same disk as the system, so it protects against
bad files but not against a disk failure*. **Both are true**: until there is a second drive or a
remote backup, this does not protect you against a disk failure.
- **Backup → Overview:** you will see two yellow warnings, in English. One says only one copy is
being made and there is no second drive. The other is, word for word:
> The system backup is currently on the same disk as the system — so it protects against corrupted files, but not against a disk failure. Attach a second drive for full protection.
**Both are true**: until there is a second drive or a remote backup, this does not protect you
against a disk failure. (They were Hungarian until 2026-09-21 — R-598, now closed.)
- **Backup → Apps → Back up now:** about 20 seconds, after which the date updates.
- *An individual app's "Backup 2 settings" page may say that its data is "already part of the full
system backup (PBS)" — this is not true on every box (known fault). The Overview page is the
@@ -165,14 +170,16 @@ same version it was before. You will have to sign in to the dashboard again.
## 13. If you mistype the code
The setup page rejects it and you can type it again. **At the moment those particular messages are
still in Hungarian**, even though the rest of the page is English — a wrong code answers
„Hibás vagy lejárt kód" (*wrong or expired code*) and, after **five** wrong attempts, the page locks
for 15 minutes with „Túl sok próbálkozás — próbáld újra 15 perc múlva." (*too many attempts, try
again in 15 minutes*). This is a known fault (R-596). You can ask for a new code with the
The setup page rejects it and you can type it again. A wrong code answers
**"Wrong or expired code"**, and after **five** wrong attempts the page locks for 15 minutes with
**"Too many attempts — try again in 15 minutes."** A password under twelve characters answers
**"The password must be at least 12 characters long"**. You can ask for a new code with the
"Did not get the code? Ask for a new one" link, and the sign-in page's "Forgot password" link leads
to the same place.
*(These messages were Hungarian until 2026-09-21 and were the one screen that stopped the first
English walk — R-596, now closed.)*
## If you get stuck
Write to **support@felhom.eu**, and send a screenshot if you can.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.