REPORT: v0.251.0 — R-553 and R-563, the five sites, red-proofs, live evidence
gates / gates (push) Successful in 23s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 21:12:29 +02:00
parent 0194342264
commit 736f54b496
+68 -89
View File
@@ -1,110 +1,89 @@
# REPORT — localisation slice 1 (R-556): the whole dashboard in English, Hungarian byte-identical
# REPORT — controller v0.251.0: nothing decides by reading a Hungarian word (R-553 + R-563)
**Date:** 2026-09-17 · **Releases:** v0.248.0 (`ff68b0b`, apps and settings), v0.249.0 (`11b790e`, backups),
**v0.250.0** (`ac149c2` + `be2efe5`, storage, sharing, sign-in, guest, catch-all, debug, the switch for
everyone) · **MinAgent:** 0.131.0 (unchanged) · **Baseline of the slice:** v0.247.0.
**Architecture read before any claim:** `felhom.eu/documentation/architecture/10-localisation.md`
(updated: §2.2, §2.3, §3 decision 6 SUPERSEDED, §5, §10, §11), `02-controller-module-map.md`.
**Evidence:** `felhom.eu/documentation/audits/i18n-slice1-2026-09-17/{A,B,C}/`.
**Date:** 2026-09-17 · **Baseline:** `e236dca48653` (v0.250.0) · **Released:** `0194342` → image
`gitea.dooplex.hu/admin/felhom-controller:0.251.0` · **MinAgent:** 0.131.0 (unchanged).
**Architecture read before any claim:** `felhom.eu/documentation/architecture/10-localisation.md` §1/§9
(§9 rewritten by this session), `07-backup-architecture.md`, `02-controller-module-map.md`.
**Evidence:** `felhom.eu/documentation/audits/r553-2026-09-17/`.
## Claims in the prompt that turned out wrong — named first
1. **Per-page Hungarian line counts** did not reproduce (debug 92 accented lines, not 103; monitoring 48,
not 72; others differ the same way).
2. **Marker counts of the v0.247.0 pages** — measured layout 105, backups 59, app_info 58, launcher 28;
the prompt said 95 / 52 / 48 / 25.
3. **"Templates decide nothing by Hungarian wording"** is false: `backups_remote.html` runs
`indexOf('Fut')` on its own status text. The prompt's sweep checked only `==`, `===` and `case`.
The word stays Hungarian (R-563).
4. **The list of unconverted copy omitted `backups_shared` `restore_banner_js`** — it was still
Hungarian after v0.247.0; converted in B.
5. **"Six direct-render pages"** — confirmed, not wrong: login, claim, recovery, launcher_shared,
launcher_share_password, catchall. No seventh `s.tmpl` render exists now.
1. **„`internal/system/mounts_linux.go` L176/L194/L199 produce site 3's warnings."** They do not.
`h.Warning` from `CheckBackupDestination` never reaches `report.Warnings` — the only producer folded
into the health report is `checkStoragePaths` (healthcheck.go). `system/` needed no change at all.
2. **„Site 1 matches `required field` … all producers are ours."** **Nothing produces `required field`.**
The router matched a phrase no code in this repo writes — a dead arm, removed with the rest.
3. **Site 3's condition matched more than the prompt implies.** Of the nine warnings that exist today,
exactly ONE matches („Az adattároló … nem külön meghajtón van"): the three patterns are lower-case
and every other warning capitalises the word. So the fix had to preserve *one* inline warning, not a
family — `WarnKindStorageNotSeparate` does exactly that.
4. **Line numbers drifted** as the prompt warned (router.go:478 → the block moved into a new
`deployStatusFor`; offbox.go:186/921; alerts.go:208; handlers.go:920/931 → 944).
5. **„`errors.New(refusal)` at the memory site"** — correct, and it is the reason the memory refusal
could not be pinned by a unit test: `memoryVerdict` reads the HOST's real memory. Its kind is pinned
at the source line instead, and the mapping is covered with a translated memory message.
6. **Confirmed as stated:** site 2's classifier has exactly one caller; `offbox_handlers.go:132` is a
false positive (an `[INFO]` log line); site 5 is the one rendered byte that changes.
## Per-page table — all 31 templates
## The five sites
Markers = `{{T}}` occurrences today. States = parity fixture cases rendering the page (captured from the
unconverted template first).
| # | decision | signal added | producer(s) changed | decision changed | test | HEAD-red → fixed-green |
|---|---|---|---|---|---|---|
| 1 | deploy HTTP status | `stacks.ErrRequiredField`, `ErrPathMissing`, `ErrNotEnoughMemory`, `ErrAlreadyDeployed` (`deploy_errors.go`) | `deploy.go` 205/311/326/333/245 | `api/router.go` → new `deployStatusFor` | `TestR553_Deploy_DecisionSurvivesWordingChange`, `TestR553_DeployHandlerUsesTheKind`, `TestR553_DeployProducersCarryKindAndKeepTheirWords` | red: every translated row `= 500, want 400/409`; green after |
| 2 | off-site failure class | `backup.ErrOffsiteQuota` | `offbox.go` 921 | `ClassifyOffsiteFailure` | `TestR553_OffsiteQuota_{Decision,HeadLine}SurvivesWordingChange`, `TestR553_QuotaProducerKeepsItsWords` | red: translated quota `= "unknown"`, head line „ismeretlen okból"; green after |
| 3 | disk-warning placement | `monitor.WarnKind*` + `HealthReport.WarningKinds` / `addWarning` / `WarningKindAt` | `healthcheck.go` `checkStoragePaths` | `web/alerts.go` | `TestR553_StorageWarningsCarryKindsAndKeepTheirWords`, `TestR553_DiskWarningPlacementSurvivesWordingChange`, `TestR553_HubReportWarningsAreUnchangedOnTheWire` | red: translated warning `Inline = false`, and an unrelated warning wrongly moved inline; green after |
| 4 | stale „nothing selected" note | `settings.OffboxTarget.LastWarningKind` + `backup.OffboxWarnNoAppsSelected` | `offbox.go` 1053/1095 (+ both clear sites, both copy sites) | `offboxWarningDisplay` | `TestR553_StaleNote*`, `TestR553_WarningKindIsPersistedAndCopied`, `TestR553_OffboxRunRecordsTheKind` | red: translated note shown verbatim; producer without its kind convicted; green after |
| 5 | remote-backup poll (R-563) | `data-status="{{.Offbox.LastStatus}}"` | `backups_remote.html` status element | the page's own script | `TestR563_PollStartsFromAttribute` (hu + en), `TestR563_AttributeFollowsTheStatus` | red twice: poll back on the word (both languages), word back inline (English page shows Hungarian); green after |
| rel | page | markers | states | | rel | page | markers | states |
|---|---|---|---|---|---|---|---|---|
| A | dashboard | 48 | 4 | | B | backups_restore_wizard | 46 | 5 |
| A | stacks | 37 | 1 | | B | backups_escrow | 58 | 4 |
| A | logs | 9 | 1 | | B | tier2_config | 23 | 4 |
| A | monitoring | 61 | 3 | | B | recovery | 43 | 7 |
| A | app_import | 40 | 2 | | C | storage | 162 | 2 |
| A | app_export | 30 | 1 | | C | storage_network | 70 | 2 |
| A | deploy | 171 | 5 | | C | storage_init | 42 | 1 |
| A | settings_system | 94 | 4 | | C | storage_attach | 27 | 1 |
| A | settings_security | 58 | 2 | | C | sharing | 74 | 6 |
| A | settings_notifications | 34 | 2 | | C | login | 9 | 1 |
| A | app_row | 0 (no copy) | via stacks/dashboard | | C | claim | 21 | 4 |
| A | meta_badge | 0 (no copy) | — | | C | launcher_shared | 4 | 2 |
| A | icons | 0 (no copy) | — | | C | launcher_share_password | 6 | 1 |
| B | backups_apps | 97 | 6 | | C | catchall | 4 | 2 |
| B | backups_remote | 81 | 12 | | C | debug | 189 | 1 |
| B | backups_restore | 78 | 4 | | | **total, all 36 templates** | **1 874** | **106** |
**Message bytes:** every producer's sentence is asserted equal to its pre-change string (sites 1, 2, 4)
or rendered identically (site 5). `util.KindErrorf` / `KindError` (new, `internal/util/errkind.go`,
documented in REUSE.md §1) build the same bytes `fmt.Errorf` did and carry the sentinel for `errors.Is`.
Bundles: hu 1 587 keys, en 1 590 (plural forms), English gap 0.
**External signatures kept, on purpose:** the classifier's restic and ssh arms — `unable to open config
file`, `is there a repository at the following location`, `produced no snapshots`, `connection refused`,
`connection reset`, `no route to host`, `i/o timeout`, `timed out`, `permission denied`, `host key`,
`handshake`, `could not resolve`, `network is unreachable`. We neither write nor translate that output.
## What release C shipped
- 11 templates converted; six ASCII-only Hungarian JS fragments the extractor missed, found by eye.
- `login`, `claim`, both guest share pages and the catch-all through `executeTemplateLang` (household
language, no session CSRF, no escrow reminder).
- Title keys for storage, network storage, both wizards, sharing; the pin test for all 17 title keys.
- The switch on every dashboard page: the hide condition deleted; 89 layout fixtures re-captured, each
proven to equal its predecessor plus exactly one switch form after the version span (89/89).
- A correction: the release C cases had invented page titles; they now carry the handlers' titles and
the 12 affected fixtures were re-captured from `0555091` (unconverted). Diff: `<title>` and the wizard nav.
- `HU_FORMAL_CEILING` 12 → 16 (four more formal forms in unchanged Hungarian).
## Red-proofs (release C; A and B in their folders) — each seen failing, then passing
| test | mutation | failure seen |
|---|---|---|
| `TestI18nParity` | hu `sharing.megosztott_mappak` loses one accent | 6 sharing cases `Hungarian page CHANGED` |
| `TestI18nEnglishPages` | en `storage.eleresi_ut` deleted | `Hungarian template copy on the English page … Elérési út` (2 cases) |
| `TestI18nParityCoversEveryMarker` | a marker in a branch no case renders | `marker never rendered by any parity case: storage_attach.hiba` |
| `TestHandlerTitleKeysMatchHungarianTitle` | hu title reworded; the sharing TitleKey line deleted | `handler title "Hálózati megosztás", but hu.json … = "Hálózati megosztások"`; `page.title.sharing has no handler` |
| `TestDirectRenderHandlersFollowLanguage` | all five call sites back on `s.tmpl.ExecuteTemplate` | five rows: `with en saved: … <html lang="en" false` |
| `TestI18nDirectRenderPagesHaveNoAdminChrome` | `addEscrowBanner` added to `executeTemplateLang` | `put EscrowBanner / CSRFField into the page data` for every page. The page layer alone stays green under this mutation (the guest template has no slot for the bar) — stated in the test |
| `TestLanguageSwitch_EndToEnd` | the v0.247.0 hide condition restored | `a Hungarian household with no ?lang= must see the language switch`; 89 parity cases fail |
**The wire:** `internal/report/builder.go` copies Status/Issues/Warnings only; the kinds are internal.
Pinned by field-set + byte test, and confirmed live (the health block on 0.251.0 carries exactly
`issues, status, warnings`).
## Gates
- `go build ./... && go vet ./... && go test ./...` → rc 0 before each commit.
- `controller_gates.py --fast` → rc 0 (i18n: English missing 0/0, formal 16/16; retrieval-promise OK);
`test_gate_decoys.py` 20/20. Pre-push hook ran on both pushes; no `--no-verify`.
`go build ./... && go vet ./... && go test ./...` → rc 0 before each push. `controller_gates.py --fast`
→ rc 0. No `--no-verify`. One existing test (`TestClassifyOffsiteFailure_EachCauseIsDistinct`) built the
quota error the old way and was adapted to build it as the run does — stated here rather than silently.
## Live validation (endpoint-level; no browser on DooPlex) — demo-hp guest 9201
Deployed by hand; **fleet floor NOT raised, no golden.** Startup: `lang=hu … 1874 markers expanded in
27.35ms`, `lang=en … 28.37ms` (B: 26.5 / 21.3 ms; A: ~24 / ~20 ms).
- **Hungarian 0.249.0 → 0.250.0:** each dashboard page carries one switch form after the version span;
removed, 7 of 8 pages are identical; `/dashboard` differs in live numbers and the order of two
drive rows. `/login`, `/claim`, catch-all: identical, no switch.
- **English:** `POST /settings/language lang=en` → 302; all 11 pages `<html lang="en">`; no accented
Hungarian on storage, sharing, wizards, debug, sign-in. Left: catalog taglines, Go-built text.
- **Back to Hungarian:** identical to the after fetch apart from live numbers.
- **Hub report:** `hu` 16:45:10, `en` 16:46:06, `hu` 16:46:43 (all 0.250.0).
- Guest share pages: not exercised live (would create a share link on the household's box); proven
through the real route in unit tests.
Deployed by hand; **fleet floor unchanged (0.250.0), no golden.**
- **Hungarian pages 0.250.0 → 0.251.0:** `/launcher` and `/monitoring` identical; `/backups/remote`
identical apart from the new attribute and the poll line (+23 bytes); `/dashboard` differs in live
numbers only.
- **Deploy refusal:** `POST /api/stacks/adventurelog/deploy` on an already-deployed app → **409 before
and after with byte-identical message**; the app kept running.
- **Site 5 live:** `id="offbox-status-value" data-status="ok"` and `v.dataset.status === 'running'`.
- **Hub:** `health.warnings` `[]` before and after, health keys unchanged.
- **Not provoked live, and why:** the 400 refusals (a live probe starts an install), the quota failure
(would need a full quota), the stale note (would need clearing the selection), and the disk-warning
placement (this box has no storage warning — 0 banner blocks before and after). All four are carried
by red-proofed tests.
- **A mistake, and what I did:** the FIRST live probe posted an empty value map to `vaultwarden`, which
has no empty required field — so the deploy was accepted (202) and the app installed. I stopped it and
removed it (with the data volume it had just created) within two minutes, verified no container
remained, and rewrote the probe to the 409 form. Nothing else on the box was touched; the standing
apps and `bentopdf` were not involved.
## Teardown
Machine: box on `hu`, 0.250.0; password, session and header files shredded; fetched pages pulled off
and removed; the three fetch/switch scripts (no secrets) stay in `/root`. Host: transfer files removed.
Hub: nothing written; DB copy shredded. Nothing provisioned.
Machine: guest on `hu`, controller 0.251.0; password file and probe scripts shredded/removed; evidence
pulled off before anything was cleaned. Host: transfer files removed. Hub: nothing written; the DB copy
shredded. Nothing provisioned.
## Observations
1. A page decides its progress poll by reading its own Hungarian word „Fut". **FILED: R-563**
2. The retrieval gate's Hungarian stems miss split verbs, exposed by the English translation. **FILED: R-564**
3. Six ASCII-only Hungarian fragments reached the English pages with every test green; the English page test sees accented letters only. **FILED: R-565**
4. Three page titles built in Go around an app name stay Hungarian in the English tab. **FILED: R-566**
5. The two drive wizard pages do not highlight the storage menu group. **FILED: R-567**
6. The dashboard's drive-health rows swapped order between two fetches a minute apart. **FILED: R-568**
7. The formal-form stem list counts 4 of at least 22 formal forms on the release C pages. **FILED: R-516**
8. `r400_debug_routes_test.go` and `debug_route_gate.py` read `debug.html` raw after conversion. **NOT-A-FINDING: they match addresses and identifiers, which never move into a bundle (0 `/api/` strings in either bundle, measured); both stay green and correct.**
9. traefik answers an unrouted host itself, so the catch-all page was fetched from the container address. **NOT-A-FINDING: routing of unknown subdomains is traefik configuration by design; the controller page renders when reached, and the method is stated in the evidence.**
1. Four more API handlers pick their status by matching ENGLISH internal words (`protected`, `not found`, `not deployed`, `still running`, `not orphaned`). **FILED: R-569**
2. The stale-note display keeps a Hungarian-text fallback for boxes that have not run off-site on ≥ 0.251.0, and slice 2 must not translate that producer until it goes. **FILED: R-570**
3. The off-site failure classifier and the alert-placement rule are described in no architecture document. **FILED: R-571**
4. `required field` was matched by the router and produced by nothing — a dead arm. **NOT-A-FINDING: deleted with the rest of the text chain in this release; nothing to track.**
5. The i18n inventory's comparison detector, re-run on the fixed tree, reports one Go compare and one indirect compare. **NOT-A-FINDING: the first is the known `[INFO]` log-line false positive, the second is the deliberate legacy fallback carried by R-570; decoys prove the detector still convicts.**