Files
felhom.eu/documentation/audits/i18n-slice2-2026-09-18/redproofs.txt
T
admin 95c10954ae
gates / gates (push) Successful in 23s
docs: localisation slice 2 CLOSED (controller v0.254.0) — R-577, R-578, and a probe rule
10-localisation.md §10.3: the saved notes follow the box language at write time, with the
one-night consequence stated rather than hidden; the globe, and the table of WHO reads which
page and where its globe posts — getting that wrong makes the button do nothing, which it did
on /recovery until the live probe found it. Decision 6 superseded a second time; decision 8
(a claim carries the visitor's language) recorded. Decision 5 of §11's anonymous-surface line:
changing what a VISITOR reads is within what an anonymous request may do; changing anything the
household owns is not, and POST /lang can do only the first.

R-578 — the deadlock, and why it is a row rather than a fixed bug: UpdateOffboxStatus holds the
settings write lock while running its callback, boxLang() wants the read lock, sync.RWMutex is
not reentrant. On a real box an off-site run would have hung FOREVER holding that lock. The
symptom was a test suite going from 8 minutes to a 25-minute timeout. Fixed and guarded, but the
guard covers one package and three helper names; the class needs a gate.

R-577 — a guest share visitor still has no way to pick a language, and the household's setting
is the wrong default for a stranger. Deliberately left, pinned by a test, and the operator's to
decide because it is a promise the share feature makes.

.claude/rules/live-probes.md, unconditional: never send a deploy request for an app that is not
installed, not even expecting a refusal — the endpoint accepts first and validates later. Two
sessions made that mistake in two days, the second WITH a prompt line forbidding it. A prompt is
read once; a rule file is loaded every session.

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

95 lines
6.6 KiB
Plaintext

RED-PROOFS — localisation slice 2 release A (v0.252.0), 2026-09-18
Each line: what was broken, what convicted, restored green after.
1. scripts/i18n_go_parity.py — one byte added to a hu.json value
planted: hu.json "redproof.test" = "Érvénytelen kérés!" (base literal has no "!")
CONVICTS: CHANGED redproof.test / hu.json 'Érvénytelen kérés!' / base 'Érvénytelen kérés'
2. scripts/i18n_go_parity.py — a key citing text nobody wrote
planted: from = "Ez a mondat sosem létezett"
CONVICTS: INVENTED redproof.test cites text that is in no base-commit literal
3. scripts/i18n_go_parity.py — a joined key whose fragments were reworded
planted: from = ["Az alkalmazás (", ") törölve lett."] (neither fragment is a base literal)
CONVICTS: INVENTED ... 'Az alkalmazás ('
4. scripts/i18n_go_parity.py — its own scanner's blind spot, found by the gate IN USE
the first capture filtered by an ASCII-Hungarian WORD LIST and missed 7 real literals
("Naponta", "5 percenkent", "Eletjel (Heartbeat)", "Adatbazis mentes", "Biztonsagi mentes",
"Mentes integritas", "Rendszer allapot"). The gate refused the keys citing them. Fixed by
indexing EVERY literal; the word list is gone. NOT a planted decoy — a live catch.
5. internal/notify wire golden — one byte changed in an event message
planted: "Alkalmazás telepítve: %s" -> "Alkalmazás telepitve: %s"
CONVICTS: TestEventMessageWireTextIsFrozen/one_parameter, got vs want printed
6. internal/monitor wire golden — a wire warning translated
planted: warnFmtStorageUnavailable -> "Storage not reachable: %s"
CONVICTS: TestStorageWarningWireTextIsFrozen/unavailable AND TestStorageWarningFormatsAreFrozen
7. internal/web — a Server with no bundle field renders raw keys
planted: s.bundle() returns s.i18n unchanged (no i18n.Shared fallback)
CONVICTS: TestFlashOnAServerWithNoBundleField — showed "flash.share.enabled"
RELEASE B (v0.253.0), 2026-09-18 — errors carry a key
8. util.msgError.Error() returns ENGLISH instead of Hungarian
CONVICTS: TestMsgErrorKeepsKindAndHuText — "%v printed 'That setting is not valid: …'"
9. Unwrap() returns the kind ALONE (the old KindErrorf behaviour)
CONVICTS: TestMsgErrorUnwrapsTheCauseToo — "the wrapped CAUSE is unreachable"
10. ErrText rewrites a FOREIGN error (restic/docker/stdlib) instead of passing it through
CONVICTS: TestErrTextFallsBackVerbatim, both languages
11. an inner error argument is pre-rendered in Hungarian instead of recursively
CONVICTS: TestErrTextRendersAWrappedMessageErrorToo
12. err.Error() put back at a converted display sink (storage_handlers.go:822)
CONVICTS: TestNoErrErrorInPageOutput, naming the file, the line and the call
13. the English plural collapsed to the singular (alert.deadapp.group.other)
CONVICTS: TestPluralEnglish — "4 installed app is not running"
14. Hungarian GIVEN a plural form it must not have
CONVICTS: TestPluralEnglish — "the Hungarian sentence moved"
TWO LIVE CATCHES IN RELEASE B — neither planted:
A. The bulk converter SILENTLY DROPPED the continuation of a multi-line concatenation
(`fmt.Errorf("a: "+ "b: %s", x)` kept only "a: "), damaging 7 producers. Found by
TestR356_ScenarioC and TestR379_ScenarioA, which assert the SENTENCE a customer reads.
The parity gate did NOT catch it: every surviving fragment was byte-equal to a base literal,
so the gate's question ("is this text real?") was answered yes while the CALL had lost text.
All 7 rebuilt as joined keys; the six wrong keys pruned from both bundles.
B. My own counting script was CASE-SENSITIVE, so it reported "0 error literals left" while five
remained ("occ parancs sikertelen", "hub hiba", "OnlyOffice aldomain nem ismert" x2). The
R-565 shape, in the instrument. Re-measured with re.I; the five are converted.
RELEASE C (v0.254.0), 2026-09-18 — saved notes, and a globe for the language switch
15. langFor drops the `!s.hasSession(r)` guard (a signed-in household inherits a visitor's cookie)
CONVICTS: TestLangForPrecedence/SESSION_→_the_household's_setting,_cookie_NOT_read
— langFor = "en", want "hu"
16. safeBackPath allows a protocol-relative URL (an open redirect off the box)
CONVICTS: TestLangCookieHandler/back_may_only_be_a_same-origin_path
— back="//evil.example/x" → Location "//evil.example/x", want "/"
17. the R-570 producer is translated (the one saved sentence release C may not touch)
CONVICTS: TestR570SentenceStaysHungarian, BOTH arms — the literal is gone AND a bundle key
appeared, each named separately
18. one byte changed inside the FOOTER parity-exception block (lang-globe-menu → -menu2)
CONVICTS: TestI18nParity on all 101 re-captured fixtures
19. one byte changed inside the SHELL parity-exception block (shell-lang → shell-lang2)
CONVICTS: TestI18nParity naming login at line 11
TWO LIVE CATCHES IN RELEASE C — neither planted:
C. The parity HARNESS rendered the three visitor shells through addLanguageData, the DASHBOARD path.
The fixture would have baked a globe posting to /settings/language with a CSRF field — a form the
real page never serves. Caught by reading the diff before re-capturing, and independently by
TestI18nDirectRenderPagesFollowLanguage, which renders through the REAL executeTemplateLang.
The harness now branches on i18nDirectTemplates.
D. The first "is the change only the two declared blocks?" measurement compared LINE BY INDEX, and an
insertion shifts every line below it — so it reported 60 520 changed lines and measured nothing.
Redone as a real (LCS) diff: exactly TWO change shapes across 106 fixtures, and 5 fixtures
byte-identical (the two guest share pages and the catch-all — the three that must not change).
RELEASE C, second round — after the recovery-page finding
E. LIVE CATCH, not planted: /recovery is in the AUTHENTICATED route table, so its reader is the
HOUSEHOLD — but release C's first draft gave it the anonymous globe, which sets a cookie langFor
deliberately ignores once there is a session. The button would have done NOTHING. Found by the
live probe reporting `/recovery globes: 0` (302 to /login) and then reading the route table.
Fixed: executeTemplateLang branches on hasSession — household form with CSRF, or visitor form
without. Two tests and the parity harness now carry the same branch.
F. The per-session CSRF token cannot be a fixture value. Blanked on both sides of every parity
comparison, exactly as relative ages already were; what stays pinned is that the field is THERE
and WHICH form it sits in — which is the half that says whether the globe writes the household's
setting or the visitor's cookie.