From 5a654ebc9a16f15cd5795ceb26fc6c9db3d6c961 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 18 Sep 2026 17:47:58 +0200 Subject: [PATCH] slice 4: bilingual console + the English download page is live (R-559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The image is BUILT but NOT PUBLISHED, and that is deliberate: publishing to iso.felhom.eu is public and irreversible, and the runbook needs a proof install on BOTH menu entries plus a reboot against the uploaded bytes. That is supervised, so I stopped there. felhom-installer-1.29.0-pve9.2-1.iso, sha256 c67ceaa3…fb02, with every mechanically checkable criterion passing (G1, G2, G5, G6, G7, G9, G16 — including both payload files byte-identical to repo HEAD). The download pages still name 1.28.0, the image that IS published. Pointing them at a file that is not there would hand every reader a 404. A new site gate refuses the two pages naming different installer files or checksums, so whoever publishes 1.29.0 cannot update one and forget the other. Measured rather than read: the pairing banner is 24 rows on a 25-row console. One row of margin — so the height is now pinned, because two more lines push the HUNGARIAN code at row 5 off the top, and a banner whose code has scrolled away is furniture. R-587: two root-password files from July sit in the directory the public ISO is published from. Both 404 on the bucket (against a 200 control), so nothing leaked — but the only thing keeping them off is an --include pattern they miss by an accident of naming. A pattern that protects by coincidence is not a control. R-588: release records live in two different places, which made me wrongly conclude 1.28.0's gate had never been run. It had. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- REPORT.md | 132 ++++++++---------- documentation/architecture/10-localisation.md | 38 ++++- .../audits/i18n-slice4-2026-09-18/README.md | 87 ++++++++++++ .../bound-banner-as-painted.txt | 15 ++ .../etc-issue-as-written.txt | 11 ++ ...installer-1.29.0-pve9.2-1.iso.manifest.txt | 26 ++++ ...elhom-installer-1.29.0-pve9.2-1.iso.sha256 | 1 + .../pairing-banner-as-painted.txt | 21 +++ documentation/backlog/OPEN-ITEMS.md | 4 +- scripts/iso/test/bootstrap-modes.sh | 14 ++ 10 files changed, 267 insertions(+), 82 deletions(-) create mode 100644 documentation/audits/i18n-slice4-2026-09-18/README.md create mode 100644 documentation/audits/i18n-slice4-2026-09-18/bound-banner-as-painted.txt create mode 100644 documentation/audits/i18n-slice4-2026-09-18/etc-issue-as-written.txt create mode 100644 documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.manifest.txt create mode 100644 documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.sha256 create mode 100644 documentation/audits/i18n-slice4-2026-09-18/pairing-banner-as-painted.txt diff --git a/REPORT.md b/REPORT.md index 99fc1a60..227fd8ed 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,99 +1,77 @@ -# REPORT — localisation slice 3 Part A: the hub's e-mails follow the household's language +# REPORT — slice 4: the console banner and the download page (R-559) -**hub v0.118.0** · felhom.eu base `20aafc3dec20` · 2026-09-18 · R-558 (Part A), R-555 closed +**ISO 1.29.0 source + the English download page** · base `183727db9c44` · 2026-09-18 ## Claims in the task that turned out wrong, named first -1. **"`customerMessages` 40 entries (L70)"** — **39 entries, at L69.** -2. **"`customers.language`, `appliances.reported_language`"** — **neither table exists.** There is no - `customers` table and no `appliances` table. The real ones are **`customer_configs`** (the customer - record) and **`reports`** (the box's heartbeat, one row per report). The columns added are - `customer_configs.language` and `reports.language`. -3. **"no `ALTER TABLE customers ADD` found by grep — the migration pattern is something else; if it - is not obvious, stop and report."** The pattern is completely obvious and there are ~20 instances: - `s.db.Exec("ALTER TABLE ADD COLUMN NOT NULL DEFAULT ")`, error deliberately - ignored so it is idempotent. The grep failed only because it named a table that does not exist. -4. **"`renderBackupRunFailures` gains a `lang` parameter"** — it is **operator-only and already - English**. Its single caller is `FormatOperatorEmail`. Untouched. -5. **"`message_customer`'s length is capped like `message`"** — **`message` has no length cap.** Both - are bounded only by the 1 MB `LimitReader` on the request body. I wrote a cap, then removed it: a - byte-count truncation would also cut a UTF-8 sequence in half. -6. **"`python3 scripts/hub_gates.py` (or the runner the hub uses — name it)"** — there is no - `hub_gates.py`. The hub's gates run from **`scripts/repo_gates.py`**, this repo's single runner. -7. **Line numbers** were mostly off by one or two (`customerMessages` 69 not 70, `severityLabels` 158 - not 159, `FormatCustomerEmail` 166 not 167). +1. **"the GRUB menu has one entry"** — the **release** image has **two** (graphical + text mode). The + single-entry template is the appliance image. Both updated. +2. **"27 Hungarian printf lines"** — **25**. +3. **"No test compares the banner text itself"** — one greps the painted banner, and another `cmp`s + `/etc/issue` against the `postinst`'s copy **byte for byte**. The console seam already existed too. +4. **"VM 321/322 were used"** — not confirmed; no VM was created (see Stop). +5. **"the site is published as its CHANGELOG says"** — right: push to `main`, git-sync, ~1–2 min. + Verified live. +6. The publish runbook does exist (`documentation/runbooks/iso-release-gate.md`) and is correctly not + in the ISO README. -Claims that were **right**: no mail goldens existed; the hub read no `language` anywhere outside a -comment; `05-hub-architecture.md` had no customer-mail section; the box has published the field since -v0.247.0. I briefly believed `Report.Language` was never assigned and was wrong — `cmd/` is -gitignored, so `rg` skips `main.go`, where all four assignments live. +## Two defects found before any feature work + +**R-586 — the ISO harness had been RED for two days.** Run unchanged at the base commit it failed two +R-496 checks. The script paints with `> "$CONSOLE_DEV"`; on a console that is a device and truncation +is a no-op, but the harness pointed it at a plain FILE, so each banner erased the previous one. ISO +1.28.0's new bound banner (`c033b3b`, 2026-09-16) paints straight after the pairing banner and wiped +it. That commit did not touch the harness, and **the harness is in no gate and no CI run**, so nobody +saw. Fixed with a FIFO; production code untouched. Its being ungated is still open. + +**The release gate would have stopped the task the operator asked for.** G16 read *"every +Felhom-authored string is Hungarian — PASS = no English sentence"*, encoding the 2026-07-31 scope. +**Ruling 1b of 2026-09-17 supersedes it.** G16 was **rewritten, not waived**: Hungarian FIRST, pinned +by a golden, each secret named once per language. ## What shipped -The hub reads the language the box reports and writes the household's e-mail in it. **Nothing an -operator reads changed.** Hungarian is byte-identical, measured. +Bilingual pairing banner, bound banner and `/etc/issue` (plus `postinst`'s byte-coupled copy); +English halves on both GRUB entries; `website/en/download.html` **live**, linked both ways with +`hreflang`; `letoltes.html` changed by **four lines** and nothing else. -- **`internal/i18n`** — flat bundle, 79 keys, hu authoritative + hu fallback, missing ceiling 0. -- **56 mail goldens per language**, captured from v0.117.0 **before** any string moved. All 56 - Hungarian ones pass unchanged after the rewrite. A `nowFn` seam makes them byte-stable. -- **`customerMessages`/`severityLabels` are derived from the bundle** — one place per sentence, and - all 40+ existing tests and comments that read them still work. -- **Order: last reported → created-with → `hu`.** `reports.language` defaults to **empty**, never - `hu`: "never told us" is not "chose Hungarian". -- **`message_customer`** accepted on `POST /api/v1/event` (additive, optional forever). -- **Per-language bind page**, built the controller's way (substitute markers, then parse). -- Customer form `language` select; `customer.language` in `controller.yaml` (diff: one line). +**The link is not in the nav** — the nav is a shared block `site_gates.py` pins across every page, and +the gate convicted my first attempt, which is how the link found its right home. -## Live evidence, read off the running hub +## Proven, not read -The wire already worked and I measured it before writing anything (read-only copy of `hub.db` + WAL): +Hungarian **goldens** captured before one English line existed; the harness asserts each banner's +first N lines are exactly the golden. English blocks carry no Hungarian letter (Hungarian block = +positive control). Every line ≤ 80 **columns**, counted in characters because the Hungarian lines are +multi-byte. **The whole paint ≤ 25 rows.** All red-proofed — a changed byte, a planted `ó`, an +over-wide line, two extra lines. -``` -demo-felhom 2026-09-18 13:42:56 language='hu' controller 0.255.0 -demo-hp 2026-09-18 13:45:24 language='en' controller 0.255.0 -drill-r50 / peti-felhom / tester-1 language= (0.213.0 / 0.115.0 / 0.245.0) -``` +**The pairing banner is 24 rows on a 25-row console.** One row of margin, which is exactly why the +height is now pinned: two more lines and the **Hungarian** code at row 5 scrolls off the top. -That is the positive and the negative control in one read: boxes ≥ 0.247.0 report the field, older -ones send nothing at all — which is exactly the case the empty default exists for. +Against the built ISO: G1, G2, G5, G6, G7, G9, G16 all pass — including both payload files +**byte-identical to repo HEAD**. -## Two defects found inside this release +## STOP — the image is built, not published -- **R-581 (P2, partly still open).** `CustomerLanguage` picked the newest report by `received_at`, - which has **second** granularity — same-second reports tie and the winner is arbitrary. A household - that had just switched would get the old language back at random. Fixed by ordering on the - autoincrement `id`; caught by a test that failed on the first version. **Still open: - `GetCustomers()` has the same shape** and feeds the whole operator dashboard. -- **R-582 (closed, kept for the lesson).** The English copy-guard stems, ported word-for-word from the - Hungarian, convicted **141 honest sentences**. In Hungarian the stem *is* the claim - (`visszaállíthat` = *can restore*); English splits the modal from the verb, so the claim is a - phrase. Then the decoy suite caught the fix being too narrow — "can **still** be restored" walked - through a pattern written for "can be restored". +`felhom-installer-1.29.0-pve9.2-1.iso`, sha256 `c67ceaa3793b38639d0f24c9c9704270d04e50b74f1ea88b1129fdd1dec6fb02`. -## Gate work +Publishing is public and irreversible and the runbook needs, against the uploaded bytes: a **proof +install on BOTH menu entries**, a reboot (G15), a person choosing the disk (G14) and a round trip +(G11). That is supervised. **The download pages therefore still name 1.28.0** — the published image — +because pointing them at a file that is not there would hand every reader a 404. The new gate stops +anyone updating one page and forgetting the other. -`wire_contract_gate.py`: the R-555 `language` allowlist entry **deleted** — the field is genuinely -decoded now and the gate checks it (202 tags, was 201). `hub_copy_gate.py`: **the sentences moved into -the bundle**, so `CUSTOMER_SURFACES` had to move with them — without that the four declared Go files -would all still exist, the gate would still report success, and it would be scanning nothing. It -learned English, and found one real occurrence (operator-tier, registered with its reason). Three new -decoys including an **innocent control**; the `hub-copy` exemption is removed from -`decoy_coverage_gate.py`. +**No VM created, none destroyed. demo-hp guest 9201 untouched. No floor, no golden.** -## The mails +## Rows -| Group | Count | Golden | English | Live-tested | -|---|---|---|---|---| -| Event types (`mail.event.*`) | 39 | ✅ hu + en | ✅ | pending (§13.1) | -| Severity labels | 4 | ✅ | ✅ | — | -| Customer wrapper (subject, body, 2 lines, sign-off) | 5 | ✅ | ✅ | — | -| Claim arc (claim / reset / reenroll / claimed) | 4×2 | ✅ | ✅ | pending (§13.2) | -| Self-bind mail | 2 | ✅ | ✅ | pending | -| Bind page | 21 | — (render tests) | ✅ | pending | -| Operator mails | 3 | ✅ (unchanged) | n/a — never localised | — | +**R-586** (harness red, ungated) · **R-587** (two root-password files sit in the ISO publish source +directory; both 404 on the bucket against a 200 control, so not leaked — the only thing keeping them +off is an `--include` pattern they miss by naming accident) · **R-588** (release records live in two +different places; it made me wrongly conclude 1.28.0's gate had never been run). ## Green -`go build` / `go vet` / `go test ./...` clean. All 14 felhom.eu gates OK. 15/15 decoys behave. - -**Not yet done:** deploy + live proof (§13), and Part B (controller v0.256.0, the box's own sentence). +`bash -n` + `shellcheck` clean on both scripts · harness **71 checks, all pass** · `site_gates.py` OK · +`repo_gates.py --fast` all 14 OK. diff --git a/documentation/architecture/10-localisation.md b/documentation/architecture/10-localisation.md index 9e4c84f4..d7f82f2d 100644 --- a/documentation/architecture/10-localisation.md +++ b/documentation/architecture/10-localisation.md @@ -477,10 +477,6 @@ reported 60 520 changed lines and measured nothing. A line-index compare is not --- -## 11. Operator decisions - -These are rulings, not proposals. Anything specced against a different assumption is wrong. - ### 10.4 Slice 3 — the hub's e-mails follow the household (hub v0.118.x, controller v0.256.x). SLICE 3 CLOSED. SHIPPED 2026-09-18. Full design: `05-hub-architecture.md` §15. @@ -547,3 +543,37 @@ one mail that did not follow the language — and the one an operator would use 7. **Interface nouns translate** (operator: „translate"). „Indítópult" → Launcher, „Vezérlőpult" → Dashboard, „Biztonsági mentés" → Backup, as the spike built. App names and „Felhom" stay as they are. Every slice follows this. + +### 10.5 Slice 4 — the console banner and the download page (ISO 1.29.0 source, R-559) + +SOURCE SHIPPED 2026-09-18. **The image is built but NOT published** — that is an operator step; see +`documentation/audits/i18n-slice4-2026-09-18/README.md`. + +**The box's screen is BILINGUAL, and that is a decision rather than a stage.** At the moment the +pairing code is shown, nobody has told the box who owns it — there is no language to follow. So the +three console texts carry the Hungarian block byte-for-byte as before, then one blank line, then an +English block, inside the same frame: `print_pairing_banner`, `print_bound_banner` and +`install_felhom_issue` (with the `postinst`'s byte-coupled copy of the issue text). The GRUB entries +gain an English half. **§16 default taken: bilingual for good** — it needs no hub change, no protocol +field, and it has no way to show the wrong language. + +**The Hungarian is a golden, not a grep** (`scripts/iso/test/golden/*.hu.txt`, captured before any +English existed). The harness also pins: no Hungarian letter in the English block (with the Hungarian +block as the positive control), every line ≤ 80 columns, and **the whole paint ≤ 25 rows**. The +pairing banner is **24 rows on a 25-row console** — one row of margin, which is why the height is +pinned: two more lines and the Hungarian pairing code, at row 5, scrolls off the top. + +**The download page has an English twin** at `felhom.eu/en/download`, linked both ways with +`hreflang`. The rest of the marketing site stays Hungarian (ruling 1b). A site gate refuses the two +pages naming different installer files or checksums. + +**The release gate had to be amended.** G16 required every Felhom-authored string to be Hungarian and +would have stopped this publication; ruling 1b supersedes that scope, so G16 was rewritten rather +than waived — Hungarian FIRST, pinned by the golden. + +**Gaps filed:** R-586 (the ISO harness had been red for two days and is in no gate), R-587 +(root-password files in the publish source directory), R-588 (release records live in two places). + +## 11. Operator decisions + +These are rulings, not proposals. Anything specced against a different assumption is wrong. diff --git a/documentation/audits/i18n-slice4-2026-09-18/README.md b/documentation/audits/i18n-slice4-2026-09-18/README.md new file mode 100644 index 00000000..1b99363a --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/README.md @@ -0,0 +1,87 @@ +# Slice 4 — bilingual console, English download page (R-559). 2026-09-18 + +**Source shipped. The ISO is BUILT but NOT PUBLISHED — that step is the operator's (§Stop, below).** + +## The claim in the task that mattered most, and it was wrong + +> *"the GRUB menu has one entry"* — the **release** image has **two** (`grub-release.cfg.tmpl`: +> graphical and text mode). The single-entry template is the appliance image. Both were updated. + +Others: **25** Hungarian printf/echo lines, not 27. **A test DOES compare the banner text** — and a +second one `cmp`s `/etc/issue` against the `postinst`'s copy byte for byte. The console seam already +existed (`FELHOM_CONSOLE_DEV`). The publish runbook is `documentation/runbooks/iso-release-gate.md` +(correctly not in the ISO README). **VM 321/322** were not reachable to confirm — no VM was created +(see Stop). **ISO 1.28.0's gate WAS recorded**, under `documentation/audits/evidence-backup- +promise-2026-09-16/phaseD-iso-gate.txt` rather than a `documentation/tests/iso-release-*` directory; +I briefly concluded it had not been, which is why R-588 is about where records live. + +## Two defects found before any feature work + +**R-586 — the harness had been RED for two days and nobody saw.** Running +`scripts/iso/test/bootstrap-modes.sh` unchanged at the base commit failed two R-496 checks. The +script paints with `> "$CONSOLE_DEV"`: on a console that is a device and truncation is a no-op, but +the harness pointed it at a plain FILE, so each banner erased the one before it. ISO 1.28.0's new +bound banner (commit `c033b3b`, 2026-09-16) paints straight after the pairing banner and wiped it +before the check read it — and that commit did not touch the harness. **The harness is in no gate and +no CI run.** Fixed with a FIFO; production code untouched. + +**The release gate would have stopped this task.** `iso-release-gate.md` **G16** read *"every +Felhom-authored string on the volunteer's path is **Hungarian** — PASS = no English sentence"*. That +encodes the 2026-07-31 scope. **Operator ruling 1b of 2026-09-17 supersedes it** ("the console banner +and the download page ARE in scope"). G16 was **rewritten, not waived**: Hungarian FIRST, pinned by a +golden, each secret named once per language. What it protects is now stronger. + +## What is proven, and how + +| Claim | How | +|---|---| +| The Hungarian is unchanged | **Goldens** captured from the script at `183727db9c44` before one English line existed; the harness asserts each banner's first N lines are exactly the golden. Red-proofed by one changed byte. | +| The English is English | No Hungarian letter in the English block, with the Hungarian block as the **positive control**. Red-proofed by planting an `ó`. | +| It fits the screen | Every line ≤ 80 columns (characters, not bytes), **and** the whole paint ≤ 25 rows. Red-proofed both ways. | +| `/etc/issue` still agrees with `postinst` | The existing `cmp` check, still green — both files were changed identically. | +| The payload in the ISO is the repo's | `cmp` of both files extracted from the built `.deb` against repo HEAD: **IDENTICAL**. | + +**The pairing banner is 24 rows on a 25-row console.** One row of margin. That is the measurement the +plan asked for, and it is why the height check exists: two more English lines make it 26 and the +**Hungarian** pairing code — which sits at row 5 — scrolls off the top. A banner whose code has +scrolled away is furniture. + +## The built image (not published) + +``` +felhom-installer-1.29.0-pve9.2-1.iso +sha256 c67ceaa3793b38639d0f24c9c9704270d04e50b74f1ea88b1129fdd1dec6fb02 +size 1 705 324 544 bytes +menu 2 entries: 'Felhom telepítés / Install Felhom' + 'Felhom telepítés (szöveges mód) / Install Felhom (text mode)' +timeout 15, timeout_style=menu, default=0, banned entries 0 +``` + +Gate criteria run mechanically against the built file: **G1** no `answer.toml` (0 hits) · **G2** no +`.rootpw.txt` emitted · **G5** no credential-shaped literal in the payload (`Bearer` is +`Bearer $token`, a variable) · **G6** two entries, timeout 15, underscore spelling, 0 banned · +**G7** package present at 1.29.0 · **G9** both payload files byte-identical to repo HEAD · **G16** +`jelszavad` 0, `Tulajdonosi jelmondat` 1, `Owner passphrase` 1, harness green. + +## STOP — what is left, and why it is not mine + +**The image is not published.** `iso.felhom.eu` is public and irreversible, and the runbook requires, +against the exact uploaded bytes: **G11** a published checksum and a verified round trip, **G15** the +console after a first boot **and one reboot**, **G14** a person choosing the disk, and a **proof +install on BOTH menu entries** (the `felhom-build-deploy` skill is explicit that reasoning from one +entry to the other was tried in Spike 4 and found insufficient in 1.26.0). + +No VM was created and none destroyed; demo-hp guest 9201 was not touched at all this task. + +**Therefore the download pages still name 1.28.0** — the image that is actually published. Pointing +them at an unpublished file would hand every reader a 404. A new site gate refuses the two pages +naming different files or hashes, so whoever publishes 1.29.0 cannot update one page and forget the +other. + +## The English download page IS live + +``` +https://felhom.eu/en/download 200 +https://felhom.eu/letoltes 200 links to it, hreflang both ways +both name felhom-installer-1.28.0-pve9.2-1.iso, sha a4cd9b6ddcb5… (verified live) +``` diff --git a/documentation/audits/i18n-slice4-2026-09-18/bound-banner-as-painted.txt b/documentation/audits/i18n-slice4-2026-09-18/bound-banner-as-painted.txt new file mode 100644 index 00000000..ffef1407 --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/bound-banner-as-painted.txt @@ -0,0 +1,15 @@ +================================================ + Felhom — a doboz össze van kötve. ✔ + + A beállítás magától folytatódik, ez néhány percig tart. + A vezérlőpult címét az e-mailben kapott levél tartalmazza. + + Ezen a gépen nincs több teendőd. + + Felhom — the box is linked. + + Setup carries on by itself; this takes a few minutes. + Your dashboard address is in the e-mail you received. + + There is nothing left to do on this machine. +================================================ diff --git a/documentation/audits/i18n-slice4-2026-09-18/etc-issue-as-written.txt b/documentation/audits/i18n-slice4-2026-09-18/etc-issue-as-written.txt new file mode 100644 index 00000000..6f2e3096 --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/etc-issue-as-written.txt @@ -0,0 +1,11 @@ + + Felhom otthoni szerver + + Ezen a gépen most nincs dolgod, és bejelentkezni sem kell. + A beállításhoz kövesd a Felhomtól kapott útmutatót. + + Felhom home server + + There is nothing to do on this machine, and no need to log in. + Follow the guide you received from Felhom. + diff --git a/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.manifest.txt b/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.manifest.txt new file mode 100644 index 00000000..dbaf97b5 --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.manifest.txt @@ -0,0 +1,26 @@ +Felhom bare-metal ISO build manifest (R-21 slice A+B+C) +built : 2026-09-18T17:42:21+02:00 +iso-version-tag : v1.29.0 +pve-version : 9.2-1 +source-iso : proxmox-ve_9.2-1.iso +source-iso-sha256 : 4e88fe416df9b527624a175f24c9aa07c714d3332afb1ee3dbf3879573ef2c6c +assistant-version : proxmox-installer-common v9.2.7 +profile : none (a release image bakes no disk selection) +fqdn : (none — interactive install) +mode : release (PUBLIC image — NO answer.toml, NO baked credential, interactive disk selection) +loader : shim (stock MS-signed chain; Secure Boot OK on compliant firmware) +grub-mkimage : unknown +boot-menu : FELHOM release menu — TWO INTERACTIVE entries ('Felhom telepítés' graphical = default, 'Felhom telepítés (szöveges mód)' = Terminal UI), timeout 15s +menu-entries : 2 (graphical default + Terminal UI; timeout 15s) +menu-removed : debug variants, Rescue Boot, memtest86+, UEFI Firmware Settings +automated-entry : NOT PRESENT — no auto-installer-mode.toml, so the stock grub.cfg does + not emit it. Disk selection is INTERACTIVE by construction. +host-install-url : https://felhom.eu/scripts/felhom-host-install.sh (default) +secret-bearing : no (PUBLIC image — carries NO credential of any kind) +root-password : NONE — not baked. The installer prompts the person installing (release gate G2). +answer-file : NONE — no answer.toml, no auto-installer-mode.toml (release gate G1) +felhom-package : felhom-bootstrap_1.29.0_all.deb sha256=af4100a698239de5124e7e48cfe74d66a04b7c8e2d71621adf1aa77ed636ff38 +repo-commit : eb1ae370951f204f6ed27782006246e6a45ea2aa +output : felhom-installer-1.29.0-pve9.2-1.iso +output-sha256 : c67ceaa3793b38639d0f24c9c9704270d04e50b74f1ea88b1129fdd1dec6fb02 +output-size-bytes : 1705324544 diff --git a/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.sha256 b/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.sha256 new file mode 100644 index 00000000..30b018dc --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/felhom-installer-1.29.0-pve9.2-1.iso.sha256 @@ -0,0 +1 @@ +c67ceaa3793b38639d0f24c9c9704270d04e50b74f1ea88b1129fdd1dec6fb02 felhom-installer-1.29.0-pve9.2-1.iso diff --git a/documentation/audits/i18n-slice4-2026-09-18/pairing-banner-as-painted.txt b/documentation/audits/i18n-slice4-2026-09-18/pairing-banner-as-painted.txt new file mode 100644 index 00000000..fd0005df --- /dev/null +++ b/documentation/audits/i18n-slice4-2026-09-18/pairing-banner-as-painted.txt @@ -0,0 +1,21 @@ +================================================ + Felhom — a doboz készen áll, és a párosításra vár. + + Párosító kód: TST-CDE + + Nyisd meg az e-mailben kapott linket, és add meg + ezt a kódot és a Tulajdonosi jelmondatodat + (az 5 szót a Felhom üzemeltetőjétől kaptad). + + Ez a képernyő magától frissül — nincs teendő a + doboznál, és nyugodtan itt hagyhatod bekapcsolva. + + Pairing code: TST-CDE + + Open the link from your e-mail and enter this code + together with your Owner passphrase (the 5 words you + received from your Felhom operator). + + This screen refreshes itself. There is nothing to do + at the box; you can leave it switched on. +================================================ diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 9728a155..22bedb3a 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -735,7 +735,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **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-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=`) 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. **DEPENDENCY added 2026-09-17 (R-553 shipped, v0.251.0):** the behaviour-by-wording sites are fixed, so this slice is unblocked — EXCEPT one producer: `"Sikeres — nincs mentésre jelölt alkalmazás"` (controller/internal/backup/offbox.go) must stay Hungarian until **R-570** closes, because the page's legacy fallback still reads it on boxes that have not run off-site since 0.251.0. Everything else this slice touches is now decided by a kind, not by its words. **RELEASE A SHIPPED 2026-09-18 (controller v0.252.0):** 226 Go literals converted -- flash-as-key (8 writers, 8 readers, legacy prose still shown verbatim), page data and view-model text, the `internal/api` JSON answers, the alert banners (`Alert.MessageKey`), 237 country names at DISPLAY (the cloudflare table is untouched -- only CODES are on the wire, the task claimed otherwise), and the four app-named page titles (R-566 CLOSED). New gate `i18n_go_parity.py` + `i18n_go_base.json` (7 467 base-commit literals, frozen) refuses any key whose Hungarian is not byte-identical; three decoys. Wire goldens freeze `internal/monitor` and `internal/notify` -- the hub MAILS the event message when it has no `customerMessages` entry, so those stay Hungarian until R-558. `HU_FORMAL_CEILING` 16 -> 18 (measured, no word changed). **RELEASE B SHIPPED 2026-09-18 (controller v0.253.0): all 179 error literals carry a key** via `util.MsgError` -- `Error()` is still the Hungarian byte for byte, `errors.Is` answers for the kind AND a wrapped cause, an error ARGUMENT renders recursively, and a foreign error (restic/docker/ssh/stdlib) prints verbatim. 76 display sites go through `errText`, pinned by `TestNoErrErrorInPageOutput`. `memoryVerdict` returns an error; `UpdateRefusal` gained a `Cause`. Plurals are a BUNDLE rule (a key with `.one`/`.other` takes its count first), not a call-site flag. **ZERO Hungarian error literals remain** (ASCII search with both controls, case-insensitive). Two tooling defects found and filed: R-576 (the converter dropped concatenations and the gate could not see it) and the case-sensitive counter. New gap: R-575. **RELEASE C SHIPPED 2026-09-18 (controller v0.254.0) -- SLICE 2 CLOSED.** ~70 saved-note producers write in the BOX language at write time (s16 option 1; a household that switches sees the previous run's note in the old language until the next run). `EndRestoreOp` takes no Hungarian literal from anywhere. The switch became a GLOBE on the dashboard and on the pages outside the dashboard chrome; a visitor with no session gets a display-only `felhom_lang` cookie that `langFor` reads ONLY when there is no session, and a successful CLAIM carries it into the setting (s16). Two parity blocks, measured with a real diff: 3 change shapes across 106 fixtures (footer; the recovery globe posting to the household switch; the login/claim globe posting to /lang), 5 byte-identical. **A DEADLOCK was introduced and caught by the suite hanging (R-578).** Gaps filed: R-572..R-578. | **CLOSED 2026-09-18 - controller v0.252.0 + v0.253.0 + v0.254.0** | | **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. **THE WIRE SURVEY (R-557 release A, 2026-09-18) HANDS THIS ROW ITS INPUT LIST:** (1) `internal/notify/notifier.go` -- 31 event messages. The task assumed the hub composes its own mail from the event kind; it does NOT. `FormatCustomerEmail` uses `customerMessages[eventType]` when it has one and **falls back to the controller's message when it has none**, and appends it as „- Üzenet: %s” whenever the two differ -- several types carry no entry precisely so the controller's dynamic sentence IS the mail (hub `api/handler.go` L2034/L2045/L2066). So an English household's mail is Hungarian until this row ships. (2) `internal/monitor/healthcheck.go` -- 5 storage sentences in report `health.warnings`/`health.issues`, operator-facing. Both are frozen by wire goldens in those packages, so no later slice can translate them by accident; this row is what unfreezes them. **SLICE 3 SHIPPED AND CLOSED 2026-09-18 — hub v0.118.0/v0.118.1 + controller v0.256.0/v0.256.1.** The hub reads the language the box reports and writes the household's e-mail in it; nothing an operator reads moved. 56 mail goldens per language captured from hub v0.117.0 BEFORE any string moved, and all 56 Hungarian ones pass unchanged. Order: last reported -> created-with -> hu. `message_customer` carries the sentences the box composes and the hub cannot translate; 19 producers render both from ONE key, and their Hungarian is byte-identical by the Go parity gate. A Hungarian household sends no second copy, so the fleet's payload is unchanged. The bind page is per-language with `expired` pinned to Hungarian (the language would otherwise be the oracle the text refuses to be). Proven live twice: a real English e-mail read in the inbox and the Hungarian one 74 s later (Part A), and the box's push log showing `[hu-only]` then `[+household(en)]` for the same event 57 s apart (Part B). Gaps filed: R-581 (received_at ties), R-582 (the English guard), R-583 (the test mail), R-584 (credential litter), R-585 (six unconverted producers). R-555 closed with it. | **CLOSED 2026-09-18 - hub v0.118.1 + controller v0.256.1** | -| **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** | +| **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. **SLICE 4 SOURCE SHIPPED 2026-09-18 (ISO 1.29.0 source + the English download page); the IMAGE IS NOT PUBLISHED — that is the operator's step.** The three console texts are bilingual: Hungarian block byte-for-byte as before, then English, inside the same frame; the GRUB entries gain an English half; `postinst`'s byte-coupled copy of the issue text changed identically. The Hungarian is a GOLDEN captured before any English existed, and the harness also pins no-Hungarian-letter-in-English (Hungarian block as the positive control), ≤ 80 columns, and ≤ 25 ROWS — the pairing banner is 24 on a 25-row console, one row of margin. `felhom.eu/en/download` is LIVE, linked both ways, and a site gate refuses the two download pages naming different installer files or checksums. **The release gate's G16 required every Felhom string to be Hungarian and would have stopped this**; ruling 1b (2026-09-17) supersedes that scope, so G16 was REWRITTEN, not waived. Built and gate-checked mechanically: `felhom-installer-1.29.0-pve9.2-1.iso`, sha256 `c67ceaa3…fb02`. Publishing needs a proof install on BOTH menu entries plus a reboot. Gaps filed: R-586, R-587, R-588. | **SOURCE SHIPPED 2026-09-18 — PUBLICATION PENDING (operator: proof install on both menu entries, then publish 1.29.0)** | | **R-560** | **[P3-LOW] Localisation slice 5 — catalog cards, settings and first steps in English.** PLAN 2026-09-17 (10 §7, §10). Inventory §2.5: 835 strings, ~4 984 words across all 53 `.felhom.yml` (use_cases 262, first_steps 233, deploy_fields descriptions 79 / labels 68, prerequisites 60, description 56, tagline 53). Proposed format: an `i18n: {en: {...}}` block inside each `.felhom.yml` (controller's `yaml.Unmarshal` ignores unknown keys, so older controllers are unaffected), field-by-field fallback. Needs a controller change to read it and catalog copy gates with an English rule. Cost 12–16 CC-hours. | **READY - rank P3-LOW; owner: CC (catalog + controller)** | | **R-561** | **[P3-LOW] Localisation slice 6 — the volunteer guide in English, then a stranger's first hour in English; closes R-516.** PLAN 2026-09-17 (10 §10). `runbooks/VOLUNTEER-first-hour.md`: 44 paragraphs, ~1 579 words. Then the 2026-09-14 walk repeated by an English speaker on a fresh box, R-516 closed against `audits/I18N-INVENTORY-2026-09-17.md`. Depends on R-556..R-560. Cost 6–8 CC-hours plus the attended walk. | **READY - rank P3-LOW; owner: CC** | | **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** | @@ -762,6 +762,8 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-584** | **[P2-MED] Credential-bearing probe scripts were left in a live guest's `/tmp` for hours, across three releases.** FOUND 2026-09-18 while cleaning up after slice 3's live proof: `probeB.sh`, `probeB2.sh`, `probeB3.sh`, `probeC.sh` and `probeD.sh` were still in demo-hp guest 9201's `/tmp` from slice 2's releases B, C and D earlier the same session, and all five carried the controller password INLINE (`-d "password=$PW"` with the value substituted). The standing rule in `.claude/rules/ui-hungarian.md` says exactly this: *delete any credential-bearing helper from `/tmp` (host AND guest) when done*. They were not deleted at the end of each phase; they were found by grepping my own litter for secret-shaped strings before removing it, which is a check that happened only because a later phase went looking. All five are shredded. **Why it is a row and not a note: the rule existed, was loaded, and was still not followed three times running — so the rule is not the mechanism.** The password is the shared demo one (Tier-0 boxes, `~/.config/credentials`), unchanged; rotating it is cheap and is the operator's call. **Fix shape:** never inline a credential in a helper — write it to a mode-0600 file and read it with curl's `@file` form, which this run did and which is why this run's own scripts were clean; and make the last act of a phase that pushed a script to a box the `shred -u` of it, the same way R-320 made evidence-copying the last act of a phase. | **READY - rank P2-MED; owner: CC (the mechanism), operator (whether to rotate)** | | **R-585** | **[P3-LOW] Six event producers still send Hungarian only, so an English household can see one Hungarian line in some mails.** FOUND 2026-09-18 by localisation slice 3 Part B (R-558, controller v0.256.0), which converted 19 of the customer-facing producers and left these: `backup_failed`, `db_dump_failed`, `backup_integrity_ok`, `backup_integrity_failed`, `offbox_enlarge_blocked` and `local_api_endpoint_drift`. **Why they were left:** each receives its sentence already FINISHED from another package, so the key and its arguments no longer exist by the time the notifier sees it — converting them means changing their callers, not the notifier. The 15 operator-tier types are deliberately excluded and are NOT part of this row: the operator reads Hungarian. **Why it matters more than it looks: `offbox_enlarge_blocked` has no `customerMessages` entry on the hub**, so its raw sentence IS the household's mail rather than an extra line under a translated headline — for that one type an English household gets a wholly Hungarian mail, not a mostly-English one. **Fix shape:** push the key and its arguments down from each caller (the shape slice 2 release B already used for errors, `util.MsgError`), then add each to the `convertedProducers` table in `internal/notify/message_customer_test.go`, which is the list both language tests walk. | **READY - rank P3-LOW; owner: CC** | | **R-586** | **[P2-MED] The ISO bootstrap harness captured the console to a FILE, so each banner erased the one before it — two checks were RED for two days and nobody saw.** FOUND 2026-09-18 starting slice 4 (R-559): running `scripts/iso/test/bootstrap-modes.sh` unchanged at `183727db9c44` reported `FAIL: R-496: banner painted to the console seam` and `FAIL: R-496: banner names the Tulajdonosi jelmondat`. **Cause:** the script paints each banner with `> "$CONSOLE_DEV"`. On a real console that is a character device and truncation is a no-op, so every paint appears; pointed at a plain file, as the harness did, each paint TRUNCATES. Commit `c033b3b` (ISO 1.28.0, R-535, 2026-09-16) added `print_bound_banner`, which paints immediately after the pairing banner in the same invocation — from that commit the pairing banner was wiped before the check read it. `c033b3b` did not touch the harness. **Why it survived: the harness is in NO gate and NO CI run** — not in `repo_gates.py`, not in `.gitea/`; it runs only when a person runs it, and between 09-16 and 09-18 nobody did. FIXED in the same session: the harness points `FELHOM_CONSOLE_DEV` at a FIFO with a background reader, which restores device semantics (opening a FIFO with `>` truncates nothing) and lets a test see EVERY paint — which slice 4's golden checks then needed anyway. Production code untouched. **What is still open: the harness remains outside every gate.** It needs a container, so it cannot join `repo_gates.py --fast`, which is what both the pre-push hook and CI run — meaning a non-fast entry would still never execute. **Fix shape:** either give CI a container-capable job that runs it, or make the ISO release gate's G16 the place it is required (done for G16 this session — so it now runs at least once per ISO release, which is better than never but later than a push). | **READY - rank P2-MED; owner: CC** | +| **R-587** | **[P3-LOW] Two root-password files sit in the directory the public ISO is published FROM.** FOUND 2026-09-18 running the ISO release gate's credential criteria for slice 4: `/mnt/5_hdd/felhom.eu/felhom-iso/out/` holds `felhom-pve-9.2-1-v1.24.0-nested-probe-generic.iso.rootpw.txt` and `felhom-pve-9.2-1-v1.25.0-nested-vm-generic-mkimage.iso.rootpw.txt` from 2026-07-22/23, mode 0600, left by two appliance-mode builds. **They are NOT on the bucket** — both `https://iso.felhom.eu/` return **404**, against a control (`felhom-installer-1.28.0-pve9.2-1.iso.sha256`) that returns 200, so the check is real and not a dead probe. **The only thing keeping them off a PUBLIC bucket is the `--include "felhom-installer-*"` pattern in the publish command**, and they are named `felhom-pve-*`, so the pattern misses them by an accident of naming rather than by design. A publish typed without the include, or an include widened to `felhom-*`, uploads root passwords to a world-readable bucket. **Fix shape:** shred the two files (they are three months old and their VMs are long gone), and make the release build refuse to run — or the publish step refuse to start — while any `*.rootpw.txt` exists in the out directory. A pattern that protects by coincidence is not a control. | **READY - rank P3-LOW; owner: CC** | +| **R-588** | **[P3-LOW] ISO release records live in two different places, so "was the gate run for this image?" cannot be answered by looking.** FOUND 2026-09-18: the release records for 1.27.0 and 1.27.1 are directories under `documentation/tests/iso-release--/`, and there is none for **1.28.0** — which led me to conclude its gate had not been run. It had: the record is `documentation/audits/evidence-backup-promise-2026-09-16/phaseD-iso-gate.txt`, inside an audit about something else. **The gate's own rule is "a criterion with no recorded observation is a criterion that was not run"**, and that rule is unenforceable while the records have no single home — the question it answers has to be settled by a full-text search for a checksum, which is what it took here. **Fix shape:** one home, `documentation/tests/iso-release--/`, and a line in `iso-release-gate.md`'s Result-recording section naming it; optionally a check that every published ISO version has a record directory. | **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)** | diff --git a/scripts/iso/test/bootstrap-modes.sh b/scripts/iso/test/bootstrap-modes.sh index 77c49f3c..21a77baa 100644 --- a/scripts/iso/test/bootstrap-modes.sh +++ b/scripts/iso/test/bootstrap-modes.sh @@ -279,6 +279,20 @@ done # The pairing code appears once per language — a person reads one block, and must find it there. check "R-559: the pairing code appears in BOTH blocks" \ "[ \"\$(grep -c 'TST-CDE' /work/b.pairing)\" -eq 2 ]" + +# HEIGHT, not just width — the measurement slice 4's plan called for, and the one a width check +# cannot make. A console is 80x25. The pairing banner is now 21 framed lines plus the blank before it +# and the two after: 24 rows, and the console shows the LAST 25. One more line and the paint is 25; +# two more and the top row scrolls away — and the top of this banner is where the HUNGARIAN pairing +# code sits (row 5). The banner exists to be read; a banner whose code has scrolled off is furniture. +# +# 25 is the limit, not 24, because 25 still fits exactly. The margin is one line and this check is +# what makes that fact survive the next person adding a sentence. +banner_rows() { echo $(( $(wc -l < "$1") + 3 )); } # +1 leading blank, +2 trailing +check "R-559: the pairing banner fits a 25-row console (it is $(banner_rows /work/b.pairing))" \ + "[ \"\$(banner_rows /work/b.pairing)\" -le 25 ]" +check "R-559: the bound banner fits a 25-row console (it is $(banner_rows /work/b.bound))" \ + "[ \"\$(banner_rows /work/b.bound)\" -le 25 ]" check "R-559: the bound banner carries no pairing code" "! grep -q 'TST-CDE' /work/b.bound" # /etc/issue: the Hungarian half frozen, the English half added, still no ő/ű and still no admin URL.