hub v0.119.0 — English households get English words for their codes (R-597); R-596/R-598 closed
gates / gates (push) Successful in 24s

The setup code and the owner passphrase now follow the household's language,
one word longer in English so the entropy never drops (setup 3 hu / 4 en,
passphrase 5 hu / 6 en). List and count are chosen together so a caller cannot
pair an English list with a Hungarian count. Hungarian is byte-unchanged.

Three claims in the row were wrong and are recorded as such:
  - the RECOVERY CODE is minted by felhom-agent from the EFF list and has
    always been English; the hub does not own it and no row was added.
  - no claim mail states a word count; the only count wording was the bind
    page's passphrase hint, whose English half is now count-free.
  - the proposed phone-safe filter removes 68% of the list (5270 of 7772
    words) and was measured, then declined, with the reason in source.

Also: guide_quote_gate binds the English volunteer guide's three quoted
messages to the controller's English bundle — nothing did, so the guide would
have gone on quoting Hungarian after the fix. Seven decoys, all convicting,
including the name-for-fact one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-21 07:56:56 +02:00
parent a499327236
commit e02bc03819
18 changed files with 8859 additions and 50 deletions
@@ -356,3 +356,50 @@ token is real. Pinned by `TestBindExpiredIsAlwaysDefaultLanguage`.
56 goldens captured from v0.117.0 before any string moved, in
`hub/internal/notify/testdata/mail_goldens/hu/`, with the English set beside them. The claim is a
diff, not a reading. A golden is never regenerated to make a change pass.
### 15.6 The CODES a household types, per language [hub v0.119.0, R-597]
A mail in English that carries three Hungarian words with accents is not an English mail. The 2026-09-20
drill received exactly that, and could paste the code but not read it to anyone.
**Two secrets this repo mints follow the household's language**, list and word count chosen together
by `configgen.RandomPassphraseFor(lang, use)`:
| Secret | hu | en | who calls it |
|---|---|---|---|
| Setup / reset code | 3 words, 44.6 bits | **4 words, 51.7 bits** | `claim.Engine` via `CustomerLanguage` |
| Owner passphrase | 5 words, 74.3 bits | **6 words, 77.5 bits** | the three `configs.go` sites |
**The rule is per use: English ≥ Hungarian, in bits.** The English list (EFF large, 7772 words after
filtering) carries 12.92 bits/word against the Hungarian list's 14.85, so English takes one more
word. The test computes both sides from the embedded lists rather than comparing a constant with
itself, so shrinking a list or lowering a count fails.
**A third secret is NOT minted here and must not be added.** The customer **recovery code** is minted
by `felhom-agent` (`internal/escrow`) from the same EFF list, ten words, ≈129 bits — it has been
English since it was written. R-597's row listed it here; that was wrong. Writing a row for it in
this table would create a second definition of a secret the hub does not own, which is the drift
`backupTargetAbsentText` already demonstrates across two repos.
**Which language, and when.** The setup code follows `Store.CustomerLanguage` — the same order the
mail carrying it follows (reported → created-with → `hu`), so a code and its e-mail can never
disagree. Two consequences, both deliberate:
- **A household created as `hu` whose box later reports `en`** keeps every code already issued
exactly as it was; the **next** code issued is English. A code is a hash on the box, never
retranslated.
- **At customer creation there is no stored customer yet**, so the Owner passphrase generated on that
form reads the language from **the form field**, not from `CustomerLanguage` — which would answer
Hungarian for every English household created. The store's `createdLanguage` applies the same
default to an absent value, so the two agree.
**The box needs no change for any of this.** It stores and compares a bcrypt hash of whatever was
minted and has no notion of which list the words came from — pinned by the controller's
`TestClaimAcceptsAnEnglishWordCode`. The TTL, the single-use generation and the five-attempt lockout
are untouched and language-blind.
**Count wording.** No claim mail states a word count; they say `Setup code: %s`. The only place a
count appeared was the bind page's passphrase hint, and its English half is now count-free ("The word
phrase you received from your operator during setup") — because "five words" stops being true for an
English household, and was already wrong for one whose passphrase predates this release. The
Hungarian „öt szó" is correct and unchanged.
+54 -3
View File
@@ -749,9 +749,9 @@ everything else held.
| what | why | row |
|---|---|---|
| the **claim page's messages** („Hibás vagy lejárt kód", „A jelszónak legalább 12 karakter…", the lockout) | composed sentences passed into page data, the R-573 shape one screen earlier | **R-596 (P1)** |
| the **setup code itself** (and the recovery code and owner passphrase) | one Hungarian wordlist, not chosen per language | **R-597** |
| the **Backup page's two protection warnings** and its two target names | composed sentences in `backup_handlers.go` / `backup_target_offer.go` | **R-598** |
| ~~the **claim page's messages**~~ | ~~composed sentences passed into page data~~ | **R-596 CLOSED**, controller 0.259.0 |
| ~~the **setup code** and the **owner passphrase**~~ | ~~one Hungarian wordlist~~ | **R-597 CLOSED**, hub 0.119.0 |
| ~~the **Backup page's two protection warnings** and its two target names~~ | ~~composed sentences in Go~~ | **R-598 CLOSED**, controller 0.259.0 |
| the menu word **"Debug"** | it is already English; the complaint was the Hungarian household's | R-516 item 1 |
| the **operator's copy** of every event | operator-tier is Hungarian **by design** (ruling 1) | — |
| the **18 formal „ön" forms** | a localisation release may not change Hungarian bytes (§1); counted, ratcheted | R-516 |
@@ -769,6 +769,57 @@ walk cannot see them.
**R-214 closed as a side effect** — the console's last paint is now the bilingual "the box is linked"
banner. The 2026-09-14 walk recorded it as still reproducing.
### 10.6c Slice 6's residue, closed (controller v0.259.0 + hub v0.119.0, 2026-09-21)
The three rows above are closed. What is worth keeping is not that they closed but **what each one
turned out to be**, because two of the three were not what the row said.
**R-596 — the claim page.** Fourteen live call sites carrying **nine** distinct messages, not the
sixteen literals the row counted; one of the sixteen (`data["Title"]`) was **dead** — `claim.html` is
standalone and `.Title` belongs to `layout.html` — and was deleted rather than translated. The
anonymous, cookie-less page takes its language from `customer.language` in `controller.yaml`
(`langFor` → `settings.GetLanguage` → `configLanguage`); that chain was an unpinned assumption and is
now a test.
**R-598 — the backup warnings.** `degradedMessageFor` now returns a **key**, so the decision stays
language-free and in one place while the words are chosen by whoever knows the reader. The English
is asserted to carry the same NEGATION the Hungarian does — *protects against corrupted files, but
**not** against a disk failure*. An English sentence that promised disk-failure protection would be
worse than leaving it Hungarian.
**R-597 — the codes.** Two of the row's three secrets were mis-attributed:
- **The recovery code was never Hungarian.** `felhom-agent` mints it from the **EFF large wordlist**
and always has — ten English words, ≈129 bits. The hub does not own it, and no row was added for
it: a second definition of that secret is exactly the drift this section exists to prevent.
- **No claim mail states a word count.** The only count wording in the product is the bind page's
passphrase hint, and its English half is now count-free.
- The setup code and the owner passphrase now follow the household's language, one word longer in
English so the entropy **never drops**: setup 3 hu (44.6 bits) → 4 en (51.7); passphrase 5 hu
(74.3) → 6 en (77.5). The floor is computed from the embedded lists at test time, not asserted
against a constant.
**[FACT] The defect class is now four instances deep** — R-573, R-590, R-596, R-598: *a composed
sentence handed to a renderer as page data*. Nothing structural sees it. A template-parity fixture
renders the field faithfully; `TestI18nEnglishPages` reads a template, not a struct; the Go-parity
gate proves the Hungarian is unchanged and says nothing about which language reached the page. **The
only instruments that find it are a live English page and a handler-level render test**, and this
release added the second for both surfaces.
**[FACT] A method finding worth more than the result.** The first live check of the backup page used
the `felhom_lang` cookie and got the **Hungarian** page for `en`. That is correct: `langFor` step 2
says a request carrying a **session** reads the household's saved setting and deliberately ignores
the visitor cookie. The cookie is the right instrument for the anonymous claim page and the **wrong**
one for any signed-in page — where `?lang=` is. A session that had run only the cookie probe would
have concluded R-598 was unfixed and fixed it again. Recorded in
`audits/i18n-closing-2026-09-21/live/backups-page.md`.
**What did NOT get a live walk:** the degraded and absent-drive warnings themselves. Guest 9201 has a
real backup drive, so it is healthy and renders nothing — by design (E-2 Scenario E) — and producing
either state would mean un-assigning a live box's backup target. They are covered by render tests
through the real handler. **The next English walk on a one-drive machine is what actually closes
that**, and it is the same walk R-516 is waiting for in the other language.
## 11. Operator decisions
These are rulings, not proposals. Anything specced against a different assumption is wrong.