R-225/R-227/R-228 Parts 2-4: unknown is not zero, the gateway speaks Hungarian, the set-aside is visible

R-225 — an unread store said '0 pillanatkép / 0 / 50 GB' above a card stating
it held backups under another key. An SFTP listing found snapshot f3d9cd67 and
12 535 KB really there; snapshot_count and repo_size_bytes were simply ABSENT
and the zero value spoke for them. StatsKnown is now NAMED, for the same reason
OffsiteInventory.Empty is: zero is what an unread store and an empty one both
look like, and on the wire 'absent' and '0' are the same bytes. The fill bar
renders only when the fill is known — a 0%-wide bar is a picture of emptiness,
and a picture is a claim. A measured zero still says zero.

R-227 — WHICH LAYER ANSWERS: traefik, and this repo generates its config. But
traefik v3 serves no static files, so a branded proxy page needs a new always-up
container for every 502 on the box — out of proportion, and scoped in the report
rather than built. Shipped instead: the unlock posts via fetch and answers a
gateway failure in Hungarian without leaving the page. Progressive enhancement —
with no JS the plain POST is unchanged and still shows the proxy's error, which
the report says plainly rather than implying otherwise.

R-228 — the set-aside history was recorded in orphaned_renamed_to and read by
nobody: a census found zero references in any template or handler, while 12 535
KB sat at that path. It is surfaced as two facts and stops. It does NOT promise
the history can be reopened, because it cannot be by anyone today (R-199's
inventory is unbuilt) — and the set-aside CONFIRMATION copy was corrected for
the same reason: 'a helyreállítási kód nélkül többé nem lesznek megnyithatók'
implied that WITH the code they could be. The field's own comment called it
'recovery-code-recoverable', which was the same over-promise in the code.

Tests: scenarios F, G, H as render tests per branch of each gate. Red-proofs,
each demonstrated failing then restored: remove the StatsKnown guards (F,
'R-225 RETURNED: an unread store reports a snapshot COUNT of zero'), delete the
set-aside block (H). The F assertion on the fill bar is scoped to the bar's own
container — a bare width:0% search matched unrelated elements and would have
passed for the wrong reason.

28 packages ok, vet clean, all controller gates OK (the emoji gate caught a
warning sign in a template comment).
This commit is contained in:
2026-08-06 08:17:48 +02:00
parent 1e759a16ec
commit c7446f2d6a
7 changed files with 291 additions and 8 deletions
@@ -242,3 +242,38 @@ func TestClassifyRecoveryFailure_MapsFromTheValueNotTheText(t *testing.T) {
t.Fatal("classification followed the TEXT — it must follow the status")
}
}
// ── SCENARIO G (R-227) — A RESTART MID-UNLOCK IS ANSWERED IN HUNGARIAN ──────────────────────────
//
// Measured 2026-08-05 (CAMPAIGN-11 F8): the controller was restarted 0.7 s into an unlock and the
// customer got traefik's raw English `Bad Gateway`. The state was clean; the page was not.
//
// The layer that answers is traefik, whose config this repo generates — but traefik v3 serves no
// static files, so a branded proxy page would need a new always-up container for every 502 on the
// box. What ships is the second sanctioned option: the unlock posts via fetch and answers a gateway
// failure in the page. This asserts the handling is PRESENT and says the right thing; with no JS the
// plain POST is unchanged and still shows the proxy's error, which the report states plainly.
func TestRecoveryClass_G_GatewayErrorIsAnsweredInHungarian(t *testing.T) {
f := newRecoveryFixture(t)
body := getRecoveryPage(t, f.s).Body.String()
// RED-PROOF: delete the fetch handler from recovery.html → this FAILS, and a restart mid-unlock
// shows `Bad Gateway` again.
if !strings.Contains(body, "unlock-gateway-error") {
t.Fatal("R-227 RETURNED: the page carries no handling for a gateway failure")
}
if !strings.Contains(body, "A gép éppen újraindul") {
t.Fatal("the gateway message must say, in Hungarian, that the machine is restarting")
}
if !strings.Contains(body, "resp.status >= 500") {
t.Fatal("a 5xx from the proxy must be caught, not rendered")
}
// It must claim NOTHING about the code — whether it was used is unknown at that point.
if namesTyping(body) && !strings.Contains(body, "Helyreállítási kód (tíz szó)") {
t.Fatal("the gateway path must not blame the code")
}
// Progressive enhancement: the plain form must survive for a JS-less browser.
if !strings.Contains(body, `method="POST" action="/recovery/unlock"`) {
t.Fatal("the plain POST form must remain for browsers without JS")
}
}