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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 18:50:16 +02:00
parent bc15153e0f
commit 5b6ade033f
57 changed files with 28747 additions and 22 deletions
@@ -181,7 +181,7 @@ likewise silent. Evidence: `audits/DRILL-r361-2026-08-22/evidence/06-part3-decis
| **Indítópult (app launcher) — one-tap grid of the household's openable apps** | controller v0.163.0 | **IMPLEMENTED** | New FIRST sidebar page `/launcher`: colored tiles (deterministic slug color or `.felhom.yml` `brand_color`) + white glyph/monogram, one per openable app (tile ⟺ „Megnyitás" — subdomain presence is the single criterion; controller excluded). Operational → `<a target=_blank>` to the public URL; stopped → greyed + state badge, no link. `/` stays the Vezérlőpult. Endpoint-level + render-test verified; `felhom-controller/REPORT.md` (2026-07-24) | Live operator click-through of a real tile → app pending (browser automation not available on DooPlex). Follow-up: curate `brand_color` for top catalog apps (R-72). Sharing the launcher outside the household is now the capability-URL guest link — see the row below |
| **Indítópult megosztás (vendég link)** — capability URL `/s/<token>` serves a standalone read-only guest launcher (no account, no admin session); optional per-share password; QR | controller v0.165.0 | **IMPLEMENTED** | 160-bit `crypto/rand` token, constant-time match (empty stored = disabled = byte-identical to the mux default 404); guest headers `noindex`/`no-referrer`/`no-store`; optional SEPARATE bcrypt share password + its own per-IP attempt map; signed cookie `HMAC(token`\|`passwordHash)` keyed with `session_secret` (rotate-token OR change-password invalidates all cookies); token redacted in logs (`/s/<redacted>`). Groups A–G (14 tests) + 3 red-proofs; **§13 endpoint-level live validation on 9201 all-pass** (`felhom-controller/REPORT.md` 2026-07-24). **Design ruling: member accounts SUPERSEDED by this capability-URL model; per-member tile visibility parked under the SSO arc (R-15).** | Full operator browser click-through + a validation doc pending → then PROVEN-LIVE. Accepted residuals: link-preview crawlers fetch once (noindex prevents indexing); reverse-proxy/CF access logs hold the path (ops-tier); the modal link carries the request Host (LAN-IP admin ⇒ LAN-IP link) |
| Forgot dashboard password → instant reset code | controller v0.123, hub | **PROVEN-LIVE** | `DRILL-day0-take2-2026-07-12` F-15 (live re-run of the exact failure path: hash applied 1s after request, code accepted first try) | |
| **The dashboard in English** — the household picks its language; Hungarian unchanged | controller v0.247.0 (spike) | **PARTIAL — PROVEN-LIVE for three pages** (launcher, backups overview, app page + the shared layout) | `audits/i18n-2026-09-17/live/` — on demo-hp the Hungarian pages were identical to 0.246.0 apart from version and CSRF token; `POST /settings/language` switched them to English and back; the hub stored the reported `language` (`en`, then `hu`). Unit: `TestI18nParity` (14 states vs fixtures from unconverted templates, red-proofed). Design `architecture/10-localisation.md` | 31 other templates (R-556), Go-side messages (R-557, after R-553), hub e-mails (R-558), console/download page (R-559, scope unconfirmed), catalog copy (R-560), guide + English stranger drill (R-561). The switch is hidden from Hungarian pages until R-556 |
| **The dashboard in English** — the household picks its language; Hungarian unchanged | controller v0.247.0 (spike) → v0.250.0 (slice 1, R-556 CLOSED) | **PARTIAL — PROVEN-LIVE for every dashboard template** (all 36 templates carry their copy in the bundles; the switch is on every dashboard page) | `audits/i18n-slice1-2026-09-17/C/live/` (and `A/`, `B/`, `audits/i18n-2026-09-17/live/`) — on demo-hp 0.250.0 each Hungarian page equals its 0.249.0 fetch once the new switch form is removed (live numbers aside; login, claim, catch-all byte-identical); `POST /settings/language` made every page English and back; the hub stored `hu`, `en`, `hu` (16:45:10Z, 16:46:06Z, 16:46:43Z). Unit: `TestI18nParity` (106 states vs fixtures from unconverted templates), `TestI18nParityCoversEveryMarker`, `TestI18nEnglishPages`, `TestDirectRenderHandlersFollowLanguage`, all red-proofed. Design `architecture/10-localisation.md` | Go-side messages and three app-name page titles (R-557 after R-553, R-566), hub e-mails (R-558), console/download page (R-559), catalog copy (R-560), guide + English stranger drill (R-561); ASCII-only Hungarian invisible to the English page test (R-565). **Hungarian households see the switch only once the fleet floor reaches 0.250.0** |
| Multiple household users / per-person accounts | — | **MISSING** | — | Single dashboard password; acceptable for alpha → R-15 |
| WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer) | agent v0.72, hub v0.35 | **IMPLEMENTED** | `SPIKE-oob-wg-operator-peer-2026-07-05`, `SPIKE-felhom-sshd-2026-07-05` | Mutual-repair desired-state arc not built → R-13. **CHECKED 2026-08-08 (R-260) and this row was NOT claiming something untrue** — it claims the capability is implemented, never that it is monitored, so no correction was owed. What WAS untrue is narrower and sat one layer down: **the hub's own OOB health check could not see whether the operator's key was installed.** `HostOOBRow` mirrored five of the agent's eight OOB fields, so `operator_key_configured` — emitted every heartbeat since agent v0.72.0, i.e. from this row's own vintage — was discarded by `encoding/json` on arrival, and `oobDegraded` returned `ok` for a box with felhom-sshd active, reachable, a valid config, a configured peer and **no operator key at all**. `operator_peer_configured`, which it did read, only says the peer IP is in desired-state — that OOB is MEANT to work, not that entry is possible. Fixed hub v0.99.0; the missing key now degrades and the alert NAMES it; a stanza too old to carry the field is reported distinctly and is never a silent ok. Pinned end-to-end from raw report JSON by `TestHostOOB_MissingOperatorKey_EndToEnd` and `TestHostOOB_NoKeyField_IsNotSilentlyOK_EndToEnd` |
| The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page | agent **v0.119.0**, hub **v0.85.0** | **PROVEN-LIVE** (2026-07-31) | `audits/host-addresses-visible-2026-07-31.md` | Before this the LAN IP was **not reportable at all** — `HostMetrics` carried no address of any kind — and the WG IP existed only in `/offsite`'s peer table keyed by pubkey (peer→host, never host→peer). New wire field `addresses[]`, one row per (interface, address); `IsGlobalUnicast()` is the whole filter, chosen by MEASURING both demo boxes, and it needs no veth/fwbr denylist because that plumbing carries no IP. Rendered live on both 0.119.0 hosts matching their `ip addr` ground truth exactly. **Two honesty properties carry the risk and are both red-proofed:** WireGuard shows the hub ALLOCATION and whether the box CONFIRMS holding it (allocation alone cannot tell a live tunnel from a peer never applied), and an agent below 0.119.0 renders **UNKNOWN, never "no addresses"** — proven live on `drill-r50-0a4f9a` (0.113.0). **Not covered:** a two-LAN-bridge box and a real WG drift, neither of which exists to observe |
+43 -19
View File
@@ -64,11 +64,17 @@ parses it.** Decided by CC in the spike (the starter delegated the choice, §4 1
unchanged.
- **[FACT]** An **undefined** key is left in place, so the set fails to load („function T not
defined") — loud at startup and in every render test (`TestExpandLeavesUndefinedMarker`).
- **[FACT]** Only `executeTemplate` is language-aware (`server.go`). Login, claim, recovery and the
guest share pages render through `s.tmpl` directly and stay Hungarian until their slice.
- **[FACT]** Go-side copy on a converted page: the handler names its title key (`data["TitleKey"]`);
the non-Hungarian sets override the copy-producing funcs `stateLabel`, `timeAgo`, `timeAgoStr`,
`nextRunLabel`, `statusText` from the bundle (`web/i18n_web.go` `localeFuncs`). The Hungarian funcs
- **[FACT]** `executeTemplate` renders dashboard pages in the request's language. Since slice 1
(v0.249.0 recovery, v0.250.0 the rest) the pages outside the dashboard chrome — login, claim,
recovery, both guest share pages, the catch-all — render through `executeTemplateLang`: the language
set and `Lang`, and deliberately NOT the session CSRF fields or the escrow reminder (R-543) that
`executeTemplate` adds. Pinned by `TestDirectRenderHandlersFollowLanguage` (the real routes) and
`TestI18nDirectRenderPagesHaveNoAdminChrome` (reminder genuinely due, absent from the page data of all
six and from the guest page in both languages). No renderer uses `s.tmpl` directly any more.
- **[FACT]** Go-side copy on a converted page: the handler names its title key (`data["TitleKey"]`;
`TestHandlerTitleKeysMatchHungarianTitle` pins each handler's Hungarian literal equal to its hu.json
value); the non-Hungarian sets override the copy-producing funcs `stateLabel`, `timeAgo`, `timeAgoStr`,
`nextRunLabel`, `statusText`, `infraMeta` from the bundle (`web/i18n_web.go` `localeFuncs`). The Hungarian funcs
are not touched; `TestLocaleFuncsHungarianBundleMatchesFuncMap` pins that `hu.json` carries the same
words they return.
@@ -84,14 +90,18 @@ parses it.** Decided by CC in the spike (the starter delegated the choice, §4 1
- **Render blank or raw.** No raw key, no marker, no element empty in English that is not empty in
Hungarian (`TestI18nEnglishPages`).
**[FACT] What stays Hungarian on the English pages, live:** only Go view-model text (the backup-target
banner and offer, the whole-guest tier labels, the update badge „Naprakész") and catalog copy (the
app's tagline, use cases, first steps) — slices 2 and 5. No template copy.
**[FACT] What stays Hungarian on the English pages, live (v0.250.0, all 31 slice-1 templates converted):**
Go view-model text (flash lines, errors, the backup-target banner and offer, the whole-guest tier
labels, the update badge „Naprakész", the catch-all's status sentence), three page titles built in Go
around an app name (logs, deploy, tier-2 settings — R-566), and catalog copy (tagline, use cases, first
steps) — slices 2 and 5. Deliberately left in a template: „Fut…" on `backups_remote`, which the page's
own JS reads (R-563). Evidence: `audits/i18n-slice1-2026-09-17/{A,B,C}/live/`.
**[FACT] Measured limit of the English page test:** it subtracts every string the fixture DATA carries
(catalog copy, handler messages) before looking for Hungarian — so a template word identical to a data
string is masked (the launcher cases pass the nav's „Indítópult" as the page title). The backups and
app cases cover the nav; the limitation is recorded, not fixed.
**[FACT] Measured limits of the English page test.** (1) It subtracts fixture DATA strings before
looking for Hungarian; since slice 1 release A only data strings of ≥ 2 words or ≥ 12 characters are
subtracted, so a one-word template title is no longer masked. (2) **It sees accented Hungarian only.**
An ASCII-only Hungarian word left in a template („mp", „ db", „jelenlegi:", „FIGYELEM:", „, majd a(z)")
passes it on the English page; release C found six such fragments by eye, none by a test (R-565).
---
@@ -126,6 +136,15 @@ yet, which no household has asked for. **Chosen (2)**: it follows rule §1 (noth
and is reversed by deleting one condition in `addLanguageData`. The way back from English is always on
screen.
**[FACT] SUPERSEDED 2026-09-17 by slice 1 release C (controller v0.250.0):** every template is converted,
so the condition is deleted and the switch is on every dashboard page, in both languages. The 89
layout parity fixtures were re-captured and each equals its predecessor plus exactly one switch form
(`audits/i18n-slice1-2026-09-17/C/switch-fixture-diff.txt`); `TestLanguageSwitch_EndToEnd` pins that a
Hungarian household with no `?lang=` sees it. Live on demo-hp: every Hungarian dashboard page equals
its 0.249.0 fetch once the switch form is removed (live numbers aside); login, claim and the catch-all
are unchanged. **A Hungarian household sees it only when the fleet floor reaches 0.250.0** — the
operator's cadence (R-242/R-468); no floor was raised.
---
## 4. Fallback
@@ -153,13 +172,17 @@ bundles. They now judge the page **as rendered in each language** (`controller/s
`controller/scripts/test_gate_decoys.py`.
**[DESIGN] Voice, per language:**
- **hu** — the product speaks in „te". The converted copy carries **6 formal („ön") forms** (R-516).
They are not fixed by a localisation release (§1), so the gate counts them against
`HU_FORMAL_CEILING` = 6 as a ratchet: a new one convicts, fixing one lowers the ceiling.
- **hu** — the product speaks in „te". The converted copy carries formal („ön") forms (R-516). They are
not fixed by a localisation release (§1), so the gate counts them against `HU_FORMAL_CEILING` as a
ratchet: a new one convicts, fixing one lowers the ceiling. The ceiling followed the conversion:
6 (spike) → 10 (release A) → 12 (release B) → **16** (release C). **[FACT] Measured limit:** the stem
list is short — on the release C pages it counts 4 formal forms, while a wider list („írja be",
„adja meg", „válassza", „biztosan eltávolítja", „engedélyezze", …) finds at least 22 keys (R-516).
- **en** — second person, plain: no „please", no „kindly".
**[FACT] Not yet per-language:** the retrieval-promise gate's stems are Hungarian only — an English
retrieval promise is unscanned (in R-556). The hub copy gate and the catalog have no language concept
**[FACT] The retrieval-promise gate scans English too** since slice 1 release A (`EN_PATTERNS`,
`ALLOWLIST_EN`, decoys). Translating exposed that its Hungarian stems miss split verbs („állíthatók
vissza") — R-564. The hub copy gate and the catalog have no language concept
(`scripts/hub_copy_gate.py` stems end in a Hungarian character class; `catalog_gates.py` checks no copy).
---
@@ -231,7 +254,7 @@ session). Each slice ends with the parity gate green for every page it touched.
| slice | row | what | proves | cost |
|---|---|---|---|---|
| 0 (done) | — | mechanism, 3 pages + layout, setting, report field, gates | Hungarian unchanged by measurement; English reachable | spent |
| 1 | R-556 | the other 31 dashboard templates (~1 500 strings, 600 of them JS), parity fixtures per page; switch shown to everyone; English retrieval stems; the extractor's ASCII word list reviewed per page | the whole dashboard in English with Hungarian byte-identical | 12–16 h, three releases |
| 1 (done, v0.248.0–v0.250.0) | R-556 | the other 31 dashboard templates (~1 500 strings, 600 of them JS), parity fixtures per page; switch shown to everyone; English retrieval stems; the extractor's ASCII word list reviewed per page | the whole dashboard in English with Hungarian byte-identical | 12–16 h, three releases |
| 2 | R-557 (after R-553) | Go-side customer strings: 947 shown + 184 errors; flash-in-URL → keys; country names; alert texts | a page's server messages follow the language | 16–20 h |
| 3 | R-558 | hub: per-customer language at creation; `configgen` renders it; `customerMessages` (39), the 5 lifecycle mails and the self-bind page in English; dispatcher reads the reported language | a household's e-mails arrive in its language | 6–8 h, one hub + one controller release |
| 4 | R-559 | console banner (34 lines, console-font limits) and the download page | an English household meets English from the first boot screen (in scope — ruling 1b) | 4–6 h + an ISO train |
@@ -256,7 +279,8 @@ These are rulings, not proposals. Anything specced against a different assumptio
### Decided by CC in the spike — operator may reverse
5. **Mechanism (b2)** — §2.1.
6. **Switch hidden while only three pages are English** — §3.
6. ~~**Switch hidden while only three pages are English** — §3.~~ **Superseded 2026-09-17** by slice 1
release C (v0.250.0): every template converted, switch shown to every household (§3).
### 2026-09-17 (evening) — the two open questions, ruled