diff --git a/CONTEXT.md b/CONTEXT.md index 3794023..d24121e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,10 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-09-20 (v0.257.0 — the app catalog can speak English) +Last updated: 2026-09-20 (v0.258.0 — the last four Hungarian things, and the walk that judged them) + +> **2026-09-20 — v0.258.0 (R-589/R-590/R-573/R-572, slice 6 Part 0).** The four leftovers slice 5's LIVE proof found, all Go-side: the update badge and the lifecycle badge through `localeFuncs` (reusing `compareInstalledToTemplate`/`EffectiveLifecycle`, so the DECISION cannot drift between languages — only the words do); the data-folder card's backup sentence through `s.msgLang`, with the test asserting the CONSEQUENCE in both languages AND that the two differ; the two channel banners keyed by the checker's own CLASSIFICATION with the composed sentence as a FAIL-OPEN fallback. **R-572 was not what its row said** — nothing called `pruneLabel`/`nextPruneLabel`, so the two dead Hungarian-producing func-map entries were DELETED; deletion is fail-loud and that was proven (a template naming a removed func panics `loadTemplates`). Five red-proofs; one of them convicted the GATE rather than the code — `localeFuncs` keys sit under `_preexisting` and are not measured against the base capture, so the citation to `TestLocaleFuncsHungarianBundleMatchesFuncMap` is what carries them, and that test was extended to make the citation true. +> **THE WALK (slice 6 Part B) then judged it**, on a box installed from scratch that day by an English speaker: `felhom.eu/documentation/audits/DRILL-first-hour-en-0258-2026-09-20.md`. Verdict **NOT YET ready for an English-speaking tester** — `internal/web/claim.go` has **16 raw Hungarian literals**, every one a message on the one screen between a household and their box (**R-596, P1**), and `backup_handlers.go` + `backup_target_offer.go` carry the Backup page's two protection warnings (**R-598**). Both are the *composed-sentence-into-page-data* shape — **three instances on file now** (R-573, R-596, R-598), worth naming as a class. Everything else held with ZERO Hungarian: the download page, the bilingual console, all three customer mails, the bind page, the dashboard's first language from `customer.language` alone, both app pages, the catalog, the language switch. **R-214 closed as a side effect.** **Floor NOT raised — the operator's step.** > **2026-09-20 — v0.257.0 (R-560 slice 5 Part A).** The catalog's `.felhom.yml` may now carry an `i18n: {en: …}` sibling block, and the dashboard reads it. `Metadata.For(lang)` merges FIELD BY FIELD (absent or blank English shows Hungarian, so a half-translated app ships); `For("hu")` is the parsed struct with `I18n` cleared and nothing else, deep-compared over all **53 real catalog files** copied into `internal/stacks/testdata/catalog/`; the three prose lists replace WHOLE and every other list is matched by its own key (`env_var`, option `value`, `match_group`, `target`, `path`) — **never by position**; `For` never writes through the manager's shared metadata. Pages reach copy only via `LocalizeStacks`/`LocalizeStackPtr`/`MetaFor`, and `TestNoDirectMetaCopyReadOnPages` AST-parses `internal/web` against a named allow-list so the NEXT page to read catalog copy fails the suite instead of rendering Hungarian to an English household. **Two of the nine red-proofs convicted a hollow TEST, not the code:** a struct copy shares its slices' backing arrays, so the obvious `DeepEqual` mutation check passed a deliberately broken merge; and a ONE-entry fixture cannot tell key matching from position matching. Both rewritten. Proven live on demo-hp (English pages English, seven Hungarian pages byte-identical apart from the CSRF token) and on demo-felhom **0.255.0** (block synced, pages identical, no parse warning in a 93-line log window). Gaps: R-589 (update badge still Hungarian in English), R-590 (the data-folder promise sentence), R-591 (`Stack.Copy()` does not deep-copy the new `Meta.I18n`), R-592 (three defects inside the new catalog gate, each found by its own decoy). **FLEET FLOOR RAISED to 0.257.0** the same session (operator asked), `min_agent` 0.131.0 declared — above the vouched golden 0.246.0, so the declaration carries it (R-472). Hub: `managed floor SERVED for demo-felhom … from declared`; **demo-felhom went 0.255.0 → 0.257.0 by itself in under 12 s**, healthy, `settle-gate: GO — at/above floor 0.257.0`, **and then rendered the English tagline** — the floor delivered the FEATURE, not a version string. Peti's box and tester-1 are DOWN and take it unattended when they return. The catalog was fully translated the same day (50 more apps in three pushes, 1 031 of 1 032 strings): **the English Apps list shows ZERO Hungarian app descriptions across all 53**. Gap added: R-594 (the catalog gate can convict a retrieval promise but cannot REGISTER a true one). diff --git a/REPORT.md b/REPORT.md index 0d35928..defa667 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,149 +1,83 @@ -# REPORT — v0.257.0: the app catalog can speak English (R-560, slice 5 Part A) +# REPORT — v0.258.0: the last four Hungarian things, and the walk that judged them (R-561 slice 6) -2026-09-20 · `main` `60f0a86` · image `gitea.dooplex.hu/admin/felhom-controller:0.257.0` · live on -demo-hp guest 9201. MinAgent 0.131.0, unchanged. +2026-09-20 · `main` at the v0.258.0 commit · image `gitea.dooplex.hu/admin/felhom-controller:0.258.0` +· live on demo-hp guest 9201 and on the drill box built from scratch the same day. MinAgent 0.131.0, +unchanged. Hungarian byte-identical. -## What this release does +Architecture read first and named: `felhom.eu/documentation/architecture/10-localisation.md` §1 (the +Hungarian must not move), §4 (fallback), §5 (voice per language), §10.6b (this slice). -An app's customer-facing text lives in the catalog's `.felhom.yml` and was Hungarian only. This -release lets the same file carry an English twin in an `i18n: {en: …}` sibling block, and makes the -dashboard read it when the household is on English. +## What shipped -Architecture document read first and named here: `felhom.eu/documentation/architecture/10-localisation.md` -— §1 (the Hungarian must not move), §4 (fallback), §5 (the gates), §7 (the catalog's copy model, which -this release turns from a proposal into a measured fact), §11 ruling 7 (app names are not translated). +Four rows, all of them Go-side — which is why neither the template parity fixtures nor +`TestI18nEnglishPages` could see them, and why **slice 5's LIVE proof is what found them**. -## The five properties, and why each is load-bearing +| row | what | how | +|---|---|---| +| **R-589** | the update badge („Naprakész" + its tooltip) and the abandoned-lifecycle badge | bundle-backed forms in `localeFuncs`, reusing `compareInstalledToTemplate` and `EffectiveLifecycle` so **the decision cannot drift between languages — only the words do**; the badge's `Class` is asserted equal across languages for the same stack | +| **R-590** | the data-folder card's backup sentence — **a promise about the customer's files** | `consequenceFor` takes its lookup as a parameter; the test asserts the CONSEQUENCE in both languages, and that the two languages do not produce the same string | +| **R-573** | the agent-channel and endpoint-drift banners | keyed by the checker's own CLASSIFICATION, with the composed sentence kept as a **fail-open** fallback: an unmapped reason renders it verbatim rather than a blank banner or a raw key | +| **R-572** | **not what the row said** | the row claimed a template renders „vasárnap" on an English page. Measured: **no template and no Go file called `pruneLabel` or `nextPruneLabel`.** Dead func-map entries returning Hungarian — **deleted**, not translated | -1. **`For("hu")` is the parsed struct with `I18n` cleared, and nothing else.** Deep-compared against - all 53 real catalog files, copied into `internal/stacks/testdata/catalog/`. `LocalizeStacks` - returns the INPUT SLICE for Hungarian rather than a copy, so nothing on the Hungarian path is - even touched. §1 holds by construction, then measured live. -2. **Fallback is field by field, and blank counts as absent.** A half-translated app is a legal, - shippable state — that is what makes fifty apps pushable a batch at a time. -3. **The three prose lists replace WHOLE; every other list is matched by its own key** — `env_var`, - option `value`, `match_group`, `target`, `path`. Position matching mistranslates silently the - first time a Hungarian field is inserted above another, and the page still looks right. -4. **`For` never writes through.** The metadata is the stack manager's and is shared by concurrent - requests; an in-place merge would put one household's language on another's page. -5. **An older controller ignores the block.** `LoadMetadata` uses non-strict `yaml.Unmarshal` — this - repo constructs no `yaml.Decoder` at all, so there is no `KnownFields` anywhere to turn strict. - Proven live below. +`lifecycleBadge` was not in any row. It is the same builder one function from `updateBadge`, and +leaving a known identical defect there would have been a choice. -## Where a page may read catalog copy +## Why deleting was the right direction for R-572, and how that was proven -**Four templates** carry catalog copy — `dashboard.html` (the app rows' second line), `stacks.html` -(the Apps list), `app_info.html` (the app page, including the data-folder cards) and `deploy.html`. -A fifth, `app_export.html`, reads `.Stack.Meta.Subdomain`, which is configuration. +Translating dead code would have added machinery with no reader and a test pinning a fiction. +Deletion is the **fail-loud** direction: `html/template` refuses to parse a template naming an +undefined function, and `loadTemplates` parses every template once per language at startup. Proven +rather than asserted — a template naming the removed function makes the parity test panic with +`function "pruneLabel" not defined`. `fmtDuration`, named in the same row, produces no Hungarian at +all ("< 1s", "5s", "2m 3s") and was left alone. -**Five handler sites** feed them, and each now goes through `LocalizeStacks` / `LocalizeStackPtr` / -`meta.For(lang)`: the dashboard, the Apps list, the deploy page, the app page — and the logs page, -which today shows only the app's NAME and is routed through the same call anyway, because the next -person to add a line to `logs.html` should not have to notice that this one page was the exception. -Two producers sit in packages that have no request and therefore no language — the integration rows -(`internal/integrations`) and the initial-credentials note (`internal/stacks/initialcreds.go`); both -are re-taken from the localised metadata in the handler, identical bytes for Hungarian. +## Red-proofs — five, each seen to fail and then revert -`TestNoDirectMetaCopyReadOnPages` parses every non-test file in `internal/web` and fails on any -`x.Meta.` read that is not in a named, reasoned allow-list (three entries today, all -`appDetailHandler`'s localised `found`). **What it cannot do, stated in the test:** it reads source, -so it cannot tell a localised receiver from an unlocalised one. It catches the ARRIVAL of a new copy -read, which is when a human has to think — that is the whole claim made for it. - -## Tests, and the red-proofs - -| test | red-proof (each seen to fail, then reverted) | +| what was broken | what convicted | |---|---| -| `TestMetaForHuIsIdentity` (53 real files) | a copy field mutated inside `For`'s hu branch — FAILED naming 53 apps | -| `TestDeployFieldsMatchedByKey` | deploy fields matched by position — FAILED, "Second field" landed on FIRST | -| `TestEmptyEnglishFallsBack` | a blank English string allowed to win — FAILED | -| `TestForDoesNotMutateTheReceiver` | the defensive copy removed — FAILED, showing the written-through strings | -| `TestOverlayMatches…` (optional_config) | groups matched by position — FAILED | -| `TestOverlayMatches…` (integrations) | integrations matched by position — FAILED | -| `TestOverlayMatches…` (data paths) | data paths matched by position — FAILED | -| `TestLocalizeStacksLeavesHungarianSliceAlone` | `I18n` left on the value handed to the page — FAILED | -| `TestNoDirectMetaCopyReadOnPages` | `_ = st.Meta.Description` added to `logsHandler` — FAILED naming file and field | +| the `updateBadge` override removed from `localeFuncs` | English read „Naprakész" | +| `MessageKey` dropped from `SetAgentChannelAlert` | English read the Hungarian sentence | +| the folder sentence hard-coded back to Hungarian | the English arm convicted on three assertions | +| one Hungarian byte changed in a badge key | `TestLocaleFuncsHungarianBundleMatchesFuncMap` named both sides | +| a template naming a deleted helper | startup panic in the parity test | -**Two of the nine convicted a hollow TEST rather than the code, and that is the finding worth -keeping.** +**The fourth says something about the gate, not the code.** `i18n_go_parity.py` did NOT convict that +changed byte, because `localeFuncs` keys sit under `_preexisting` and are deliberately not measured +against the base capture. The citation to the test is what carries them — so the test had to be made +real, and it was extended to compare `hu.json` against the frozen funcmap forms word for word. -- `TestForDoesNotMutateTheReceiver` first did the obvious thing: copy the struct, call `For`, - `DeepEqual` the copy against the original. **A struct copy shares its slices' backing arrays**, so - a write through `out.DeployFields[i].Label` changes BOTH sides and the compare passes. It passed a - deliberately broken merge. It now snapshots the concrete strings before the call. -- The optional-config / integrations / data-path fixture first carried ONE entry per list. With one - entry, position and key agree, and a position-matching implementation passes. It passed. The - fixture now carries two entries per list with only the second translated. +## Proven live, twice -Full suite, vet and build green; `controller_gates.py` green (17 gates). +- **demo-hp (0.258.0):** the English app page shows "Up to date" with an English tooltip while the + Hungarian page still says „Naprakész"; the folder sentence is English on one and unchanged on the + other; the free-space line reads "47.9 GB free" / „47.9 GB szabad". After the fixes, a scan of the + English app pages and the Apps list finds **zero** Felhom-authored Hungarian — the only hit is the + language picker naming itself „Magyar", which is correct. +- **A box installed from scratch that day**, walked by an English speaker: the update badge and the + time helpers were English there too, on hardware that had never run an older controller. -## Live proof — endpoint level, and that is stated because it matters +## What the walk then found, which this release does not fix -No browser exists on DooPlex. Every page was fetched with `curl` from inside guest 9201 at the -controller's container address, with the customer `Host` header and a real logged-in session: the -same handlers and the same templates a browser drives, with only the drawing skipped. +The walk is `felhom.eu/documentation/audits/DRILL-first-hour-en-0258-2026-09-20.md`. Its verdict is +**not yet ready for an English-speaking tester**, and the blocker is in this repo: -- **demo-hp, 0.257.0.** With the catalog pilot synced, `/apps/privatebin`, `/apps/paperless-ngx`, - `/apps/romm`, `/stacks` and two deploy pages render the English catalog text on `?lang=en`. -- **The Hungarian did not move.** The same seven pages in Hungarian, before and after the catalog - push, are byte-identical apart from the per-session CSRF token: equal raw byte counts (43 253 / - 41 532 / 41 547 / 147 121 / 74 326 / 70 290) and equal hashes once the token is normalised. -- **demo-felhom, 0.255.0 — the old-controller check.** With the block synced onto that box - (confirmed positively: the sync named the three apps, and the block is in both the cache and the - stack copy), its pages hash identically before and after, and its log carries **no** parse warning - — in a 93-line window that contains the sync's own lines, so the absence is evidence and not a - dead log. +- **R-596 (P1)** — `internal/web/claim.go` carries **sixteen** raw Hungarian literals, every one a + message the claim page shows. The page's chrome is English; its answers are not. Same shape as + R-573, one screen earlier in the journey, on the one screen between a household and their box. +- **R-598** — `backup_handlers.go` (12) and `backup_target_offer.go` (10): the Backup page's two + protection warnings and its two target names. -Evidence: `felhom.eu/documentation/audits/i18n-slice5-2026-09-20/`. +Both are the *composed-sentence-into-page-data* shape. That shape now has three instances on file +(R-573, R-596, R-598) and is worth naming as a class the next converter looks for. -## What is still Hungarian on an English app page +## Needs the operator -Measured with an accent scan AND an ASCII-folded stem scan, both with a positive and a negative -control. Three things, and the first is correct: - -1. **the language picker's own „Magyar" button** — a picker names each language in its own tongue; -2. **the update badge „Naprakész" and its tooltip** — **R-589**. Slice 1 listed it (§2.3); slice 2 - was the slice that was to take it, and closed without it; -3. **the data-folder card's consequence sentence** — **R-590**. This one makes a promise about the - customer's files, and the label above it is already English, so the line reads half and half. - -The Apps LIST also shows fifty Hungarian descriptions — those are the apps nobody has translated yet. - -## Gaps filed - -**R-589** (the update badge) · **R-590** (the data-folder promise) · **R-591** (`Stack.Copy()` deep-copies -five `Meta` fields and not the new `I18n` map — safe today because nothing mutates an overlay, which is -exactly why it is worth a row) · **R-592** (three defects inside the new catalog gate, each found by its -own decoy). - -## The floor was raised to 0.257.0, and it delivered function rather than a version string - -The operator asked for the floor at the end of the translation work, so it was raised the same -session: `min_controller_version=0.257.0`, `min_agent=0.131.0` — the value this release's CHANGELOG -header declares. The floor is **above the vouched golden (0.246.0)**, so the declaration is what -carries it (R-472); an undeclared floor there is held by design. - -Observables, both positive: - -- Hub: `managed floor SERVED for demo-felhom: floor 0.257.0, agent requirement "0.131.0" from - declared (golden 0.246.0)`. -- Box: **demo-felhom went 0.255.0 → 0.257.0 by itself in under 12 seconds**, healthy, its own log - reading `settle-gate: GO — at/above floor 0.257.0 (we are 0.257.0)`. -- **And then it rendered the English tagline** for an app whose block it had just synced. That is the - observable that matters: the floor delivered the FEATURE, not just a version number. - -**Peti's box and tester-1 are DOWN** and take it unattended when they return — untested on this -version, which is the standing situation for every floor raise. - -## The whole catalog followed the same day - -The operator read the pilot's English and said go, so Part C shipped too: 50 more apps in three -pushes, 1 031 of the catalog's 1 032 strings. **The English Apps list now shows zero Hungarian app -descriptions across all 53 apps** — the only Hungarian left on it is this release's own two gaps -(R-589's „Naprakész" badge, ten occurrences) and the language picker naming itself. +**Raise the fleet floor to 0.258.0.** Nothing breaks if it waits — the four fixes simply do not reach +the other boxes. The drill box already ran 0.258.0, because the golden baked today is 0.258.0 and a +golden above the floor is served normally (`managed floor SERVED … floor 0.257.0 … golden 0.258.0`). ## Teardown -Nothing installed or removed on any box; no app deployed. Guest 9201 on demo-hp was restarted onto the -new image, which is the deploy. The staged password file and the capture scripts were shredded from -both Proxmox hosts and both guests. Both boxes left on Hungarian. +Nothing installed or removed on demo-hp; guest 9201 restarted onto the new image, which is the +deploy. The drill VM and its hub customer are torn down in three layers — see the drill document. +Every staged credential was shredded from both hosts and both guests.