Files
felhom.eu/REPORT.md
T
admin 9167cf53af
gates / gates (push) Successful in 23s
hub v0.118.0: the household's e-mails follow the household's language (R-558 Part A)
The hub has written every customer e-mail in Hungarian whatever the box was set
to. The box has published its language since controller v0.247.0; nothing read
it. Now it does.

Nothing an operator reads changes. The Hungarian mails are byte-identical, and
that is a diff rather than a reading: 56 goldens per language captured from
v0.117.0 BEFORE any string moved, and all 56 Hungarian ones pass unchanged after
every sentence was routed through the new bundle.

- internal/i18n: flat bundle, 79 keys, hu authoritative + hu fallback, ceiling 0.
- customerMessages/severityLabels are DERIVED from the bundle, so a sentence is
  written in one place and all 40+ tests that read those maps still work.
- Language order: last reported -> created-with -> hu. reports.language defaults
  to EMPTY, never hu: "never told us" is not "chose Hungarian".
- message_customer on POST /api/v1/event, additive and optional forever, for the
  sentences the box composes and the hub cannot translate.
- The bind page is per-language, and its `expired` state stays Hungarian: it is
  the state an unknown token lands in, so rendering a real English customer's
  token in English would make the LANGUAGE answer what the TEXT refuses to.

Two defects found inside the release:
- R-581: the newest report was picked by received_at, which has SECOND
  granularity, so same-second reports tied and the winner was arbitrary. Ordered
  by the autoincrement id now. GetCustomers() still has the shape - row open.
- R-582: the English copy-guard stems, ported word for word from Hungarian,
  convicted 141 honest sentences. The English claim is a phrase with a modal.

R-555 closed: the language allowlist entry is out of wire_contract_gate.py.
hub_copy_gate.py follows the sentences into the bundle - without that it would
have scanned four files that no longer hold any customer text and reported
success. Three new decoys incl. an innocent control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-18 16:20:11 +02:00

100 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — localisation slice 3 Part A: the hub's e-mails follow the household's language
**hub v0.118.0** · felhom.eu base `20aafc3dec20` · 2026-09-18 · R-558 (Part A), R-555 closed
## 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 <t> ADD COLUMN <c> <type> NOT NULL DEFAULT <v>")`, 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).
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.
## 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.
- **`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).
## Live evidence, read off the running hub
The wire already worked and I measured it before writing anything (read-only copy of `hub.db` + WAL):
```
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=<ABSENT> (0.213.0 / 0.115.0 / 0.245.0)
```
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.
## Two defects found inside this release
- **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".
## Gate work
`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`.
## The mails
| 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 | — |
## 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).