The image is BUILT but NOT PUBLISHED, and that is deliberate: publishing to
iso.felhom.eu is public and irreversible, and the runbook needs a proof install
on BOTH menu entries plus a reboot against the uploaded bytes. That is
supervised, so I stopped there. felhom-installer-1.29.0-pve9.2-1.iso, sha256
c67ceaa3…fb02, with every mechanically checkable criterion passing (G1, G2, G5,
G6, G7, G9, G16 — including both payload files byte-identical to repo HEAD).
The download pages still name 1.28.0, the image that IS published. Pointing
them at a file that is not there would hand every reader a 404. A new site gate
refuses the two pages naming different installer files or checksums, so
whoever publishes 1.29.0 cannot update one and forget the other.
Measured rather than read: the pairing banner is 24 rows on a 25-row console.
One row of margin — so the height is now pinned, because two more lines push
the HUNGARIAN code at row 5 off the top, and a banner whose code has scrolled
away is furniture.
R-587: two root-password files from July sit in the directory the public ISO is
published from. Both 404 on the bucket (against a 200 control), so nothing
leaked — but the only thing keeping them off is an --include pattern they miss
by an accident of naming. A pattern that protects by coincidence is not a
control.
R-588: release records live in two different places, which made me wrongly
conclude 1.28.0's gate had never been run. It had.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Part B's live proof is a LOG LINE rather than an e-mail, and the evidence page
says why: severityNotifies drops `info`, so every safe converted producer mails
nobody, and every converted producer above `info` describes something bad that
is not true. Triggering one would mean a false record on a real box's timeline
or a state change the fences forbid. The log line was built in v0.256.1 for
exactly this, after finding there was nothing to look at on either side of the
wire.
15:07:28 controller_started [hu-only] — Controller elindult (0.256.1)
15:08:25 controller_started [+household(en)] — Controller elindult (0.256.1)
The Hungarian sentence is identical in both, and the hub stored the Hungarian
in every case including the English-household push.
R-558 CLOSED, R-555 closed with it. R-585 filed: six producers still send
Hungarian only because their sentence arrives already finished from another
package — `offbox_enlarge_blocked` matters most, since it has no hub entry so
its raw sentence IS the household's whole mail.
10-localisation.md gains 10.4; STATUS rewritten for the operator with the two
decisions left (raise the floor to 0.256.1; whether to rotate the demo
password after R-584).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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
R-579 filed and closed the same day: five shell templates loaded style.css
with no cache-buster, so a browser holding the pre-0.254.0 file rendered the
new globe unstyled; and the globe sat outside the card.
- STATUS.md rewritten for the operator: what was seen, why, the third defect
found while fixing it (version disclosure on the guest share page, caught by
TestShareGuest_HeadersTilesNoAdminChrome), and the one decision left —
raise the fleet floor to 0.255.0, with what happens either way.
- 10-localisation.md §3: the shells' asset tag, and why the two guest pages get
an opaque tag rather than the version.
- Audit D: the parity diff (91 of 106 fixtures identical, every dashboard page
among them) and the live endpoint evidence from demo-hp guest 9201.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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
All 179 Hungarian error messages carry a key; zero remain. 10-localisation.md gains §10.2: the
four properties util.MsgError had to have at once and the failure each one prevents, and the
plural rule as a BUNDLE rule rather than a per-call-site flag, with the answerable sentence and
what the other option would have cost.
Two instrument defects recorded rather than tidied away, because both shapes recur:
R-576 — the parity gate has a measured blind spot. The bulk converter dropped the continuation
of multi-line concatenations, damaging 7 producers, and the gate stayed GREEN: every surviving
fragment WAS a byte-equal base-commit literal, so its question ("is this text real?") was
answered yes while the CALL had lost half its sentence. Two behaviour tests caught it. The
general form: a structural gate over the TEXT cannot see a defect in the CALL.
And the script counting what was left was case-sensitive, so it said "0 remain" while five did —
R-565's shape inside the measurement. Every "no Hungarian left" claim in this slice is now made
case-insensitively and with both controls.
R-575 — the soft memory-overcommit warning has no error to carry a key and no language where it
is built, so it renders Hungarian on an English page. Named in the code, not hidden.
Live evidence includes a mistake I made and corrected: a probe of the deploy refusal INSTALLED
vaultwarden on demo-hp (the endpoint accepts before it validates), the same mistake the previous
session recorded. Removed through the product's own path with its data; verified gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
10-localisation.md gains §10.1: the measured numbers (1 120 base literals, not 1 141; 226
converted), the flash-as-key design and why a key must be resolved by the READER, word order
through Go's explicit argument indexes rather than a second placeholder syntax, and the parity
gate that makes "byte-identical" a measurement instead of a reading.
Two claims the plan carried that live source disproved, both about the wire, both recorded:
the country table is NOT on the wire (only codes are), and the hub does NOT always compose its
own customer mail — it falls back to the controller's event message, which is why those 31
sentences stay Hungarian until R-558. That survey is handed to R-558 as its input list.
R-566 CLOSED (four app-named page titles now carry a %s). New rows R-572 (two funcmap helpers
with no English form), R-573 (the two channel-health banners arrive as finished Hungarian),
R-574 (handler_debug.go mixes page copy with payload).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
- audits/r553-2026-09-17/: live before/after on demo-hp (CSRF redacted), the 409 refusal, the hub
health block, red-proofs for all five sites, the site-5 fixture diff, gates.
- 10-localisation.md §9: the five decisions with what each reads now; the rule (a text signature may
remain only where the text is not ours); the one legacy exception and its end date.
- Register: R-553 and R-563 closed to CLOSED-ITEMS; R-569 (four API handlers match English words),
R-570 (the legacy stale-note fallback + the slice-2 fence), R-571 (classifier and alert placement
documented nowhere). R-557 carries the R-570 dependency. 263 -> 264 open.
- STATUS, including the live probe that installed an app and was removed the same minute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Design written after the spike ran (controller v0.247.0 live on demo-hp): mechanism,
flow, fallback, gates per language, catalog model, sliced plan with costs, operator
rulings 1-4 recorded, CC decisions 5-6, open decisions 1b and 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS