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
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""guide_quote_gate -- the English guide may only quote what the screen actually says (R-596/R-598).
Run from anywhere: python3 scripts/guide_quote_gate.py
Exit 0 clean * 1 convicted * 2 inconclusive (a file it needs is missing).
WHY THIS EXISTS. `documentation/runbooks/VOLUNTEER-first-hour.en.md` is what a volunteer tester
follows instead of the Hungarian guide. Three of its lines QUOTE messages the dashboard prints:
the claim page's wrong-code and lockout answers, and the Backup page's system-disk warning. Those
three sentences live in the CONTROLLER's English bundle, in a different repo, and nothing bound
them together -- so the guide would have gone on quoting Hungarian for as long as nobody re-walked
it. That is exactly how it read for a day: the 2026-09-20 drill's guide quoted the Hungarian
because the Hungarian was what shipped, and when v0.259.0 changed the screen the guide became wrong
in the other direction.
WHAT IT CHECKS. Each entry below names a bundle KEY and asserts its English value appears verbatim
in the guide. A reworded key, or a reworded guide, convicts and prints both sides.
WHAT IT DOES NOT CHECK, deliberately: that the guide is complete, or that any OTHER sentence in it
is accurate. It binds the three quotes that are quotes. Everything else in that document is prose a
person has to re-walk, and pretending otherwise would be the label without the fact.
SCOPE IS A FACT. The controller clone may not sit beside this one (CI checks out one repo). An
absent sibling is INCONCLUSIVE (exit 2), never a silent pass -- the failure this project keeps
closing is a check that reports green because it could not look.
THE DECOY (R-421). scripts/test_guide_quote_gate.py builds a guide that MENTIONS every key by name
in prose -- the label without the fact -- and asserts this gate still convicts, because it compares
the VALUE, not the key.
"""
from __future__ import annotations
import io
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(HERE)
WORKSPACE = os.path.dirname(REPO)
GUIDE = os.path.join(REPO, "documentation", "runbooks", "VOLUNTEER-first-hour.en.md")
EN_BUNDLE = os.path.join(
WORKSPACE, "felhom-controller", "controller", "internal", "i18n", "locales", "en.json"
)
# key -> why the guide quotes it. The reason is part of the record: a future session deciding to
# drop a quote should have to argue with the reason, not just with the list.
QUOTED = {
"claim.msg.bad_code":
"section 13 -- the answer to a mistyped setup code. This is the sentence the 2026-09-20 "
"English drill stopped on; a tester who reads a different one cannot tell a typo from a "
"dead code, which is the whole point of the section.",
"claim.msg.too_many":
"section 13 -- the lockout answer after five wrong codes. The guide promises 15 minutes; "
"if the message ever says something else, the guide is telling a tester to wait wrongly.",
"claim.msg.password_too_short":
"section 13 -- the minimum-password answer. Quoted with its number filled in, so the guide "
"and claimMinPassword cannot disagree silently.",
"backup.target.degraded":
"section 9 -- the warning that says the backup does NOT survive a disk failure. It is a "
"promise about whether the tester's files are safe.",
}
# Keys whose English carries a printf verb: the guide quotes the rendered form. value -> rendered.
RENDER = {
"claim.msg.password_too_short": lambda v: v % 12,
}
def main() -> int:
if not os.path.exists(GUIDE):
print("guide-quote: INCONCLUSIVE -- no %s" % os.path.relpath(GUIDE, REPO), file=sys.stderr)
return 2
if not os.path.exists(EN_BUNDLE):
print(
"guide-quote: INCONCLUSIVE -- the felhom-controller clone is not beside this one "
"(looked for %s). Not a pass: this gate cannot see what the screen says." % EN_BUNDLE,
file=sys.stderr,
)
return 2
guide = io.open(GUIDE, encoding="utf-8").read()
bundle = json.load(io.open(EN_BUNDLE, encoding="utf-8"))
problems = []
for key, why in sorted(QUOTED.items()):
if key not in bundle:
problems.append("MISSING KEY %s\n en.json does not know it.\n %s" % (key, why))
continue
want = bundle[key]
if key in RENDER:
want = RENDER[key](want)
if want not in guide:
problems.append(
"NOT QUOTED %s\n"
" the screen says: %r\n"
" the guide does not contain that sentence.\n"
" %s" % (key, want, why)
)
print("guide-quote: %d quoted messages checked against the controller's English bundle" % len(QUOTED))
if problems:
print("\nguide-quote gate CONVICTS (%d):" % len(problems))
for p in problems:
print(" " + p)
print(
"\nThe English guide and the English dashboard disagree. Fix whichever is wrong --\n"
"but a tester follows the guide, so a guide that quotes a sentence nobody sees is the\n"
"more expensive of the two."
)
return 1
print("guide-quote gate OK: every quoted message is what the screen says.")
return 0
if __name__ == "__main__":
sys.exit(main())