v0.258.0: the last four Hungarian things an English household met
gates / gates (push) Successful in 24s

R-589 the update badge, R-590 the data-folder backup sentence, R-573 the two channel
banners, R-572 two dead helpers. All four were built in Go, which is why neither the
template parity fixtures nor TestI18nEnglishPages could see them; slice 5's LIVE proof
is what found them.

R-590 is the one that matters most: it is a promise about the customer's files. It
said, in Hungarian and under an already-English folder label, that a drop-zone is
temporary and unbacked. The test now asserts the CONSEQUENCE in both languages — and
that the two languages do not produce the same string, which would mean the English
fell back.

R-572 is not what its row said. The row claimed a template renders "vasárnap" on an
English page; measured, NO template and no Go file called pruneLabel or
nextPruneLabel. They were dead func-map entries returning Hungarian, so they are
deleted rather than translated — translating dead code would add machinery with no
reader and a test pinning a fiction. Deletion is fail-loud and that was proven: a
template naming the removed function panics loadTemplates at startup.

Five red-proofs. One of them says something about the gate rather than the code: the
Go-parity gate does not measure localeFuncs keys against the base capture, so the
citation to TestLocaleFuncsHungarianBundleMatchesFuncMap is what carries them — the
test was extended to make that citation true.

MinAgent: 0.131.0 (unchanged). Hungarian byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-20 18:10:53 +02:00
parent ecefdf2dc5
commit 7c4a33b6d1
12 changed files with 425 additions and 74 deletions
+167
View File
@@ -170,6 +170,37 @@ func TestLocaleFuncsHungarianBundleMatchesFuncMap(t *testing.T) {
t.Errorf("infra %s display: hu.json %q, inframeta.go %q", name, b.Msg("hu", k), m.DisplayName)
}
}
// The two *MetaBadge builders (R-589, v0.258.0). i18n_go_keys.json lists their keys under
// `_preexisting` CITING THIS TEST, so the citation has to be true: hu.json must say exactly
// what the frozen Hungarian funcmap forms say, word for word.
huBadge := fm["updateBadge"].(func(stacks.Stack) *MetaBadge)
cur := stacks.Stack{Name: "a", Deployed: true,
Meta: stacks.Metadata{Slug: "a", CatalogSince: time.Now().UTC().AddDate(0, 0, -2).Format("2006-01-02")},
AppConfig: &stacks.AppConfig{InstalledImages: map[string]stacks.InstalledImage{"x": {Ref: "same"}}},
CatalogImages: map[string]string{"x": "same"}}
behind := cur
behind.CatalogImages = map[string]string{"x": "moved"}
if got, want := b.Msg("hu", "badge.update.current"), huBadge(cur).Label; got != want {
t.Errorf("badge.update.current: hu.json %q, funcmap %q", got, want)
}
if got, want := b.Msg("hu", "badge.update.current.title"), huBadge(cur).Title; got != want {
t.Errorf("badge.update.current.title: hu.json %q, funcmap %q", got, want)
}
if got, want := b.Msg("hu", "badge.update.behind")+fmt.Sprintf(b.Msg("hu", "badge.update.behind.days"), 2), huBadge(behind).Label; got != want {
t.Errorf("badge.update.behind(+age): hu.json %q, funcmap %q", got, want)
}
if got, want := b.Msg("hu", "badge.update.behind.title"), huBadge(behind).Title; got != want {
t.Errorf("badge.update.behind.title: hu.json %q, funcmap %q", got, want)
}
huLifecycle := fm["lifecycleBadge"].(func(stacks.Metadata) *MetaBadge)
ab := huLifecycle(stacks.Metadata{Lifecycle: stacks.LifecycleAbandoned})
if got, want := b.Msg("hu", "badge.lifecycle.abandoned"), ab.Label; got != want {
t.Errorf("badge.lifecycle.abandoned: hu.json %q, funcmap %q", got, want)
}
if got, want := b.Msg("hu", "badge.lifecycle.abandoned.title"), ab.Title; got != want {
t.Errorf("badge.lifecycle.abandoned.title: hu.json %q, funcmap %q", got, want)
}
// And the English funcs really are English.
en := s.localeFuncs("en")
if got := en["timeAgo"].(func(time.Time) string)(now.Add(-time.Minute - time.Second)); got != "1 minute ago" {
@@ -564,3 +595,139 @@ func TestParameterisedPageTitles(t *testing.T) {
}
}
}
// ── R-589 / R-573 (v0.258.0) — the badges and the two channel banners follow the language ──────
//
// These were the last two Felhom-authored Hungarian strings an English household met on an app
// page, found LIVE on 2026-09-20 by localisation slice 5's proof, not by reading. Both are built in
// Go, so neither the template parity fixtures nor TestI18nEnglishPages could see them.
// TestUpdateBadgeFollowsTheLanguage — the Hungarian funcmap form and the bundle form must AGREE in
// Hungarian (that is the parity guarantee) and DIFFER in English (that is the fix).
//
// RED-PROOF (2026-09-2X): deleting the "updateBadge" entry from localeFuncs made the English arm
// return „Naprakész" and the test failed naming it; restoring it went green.
func TestUpdateBadgeFollowsTheLanguage(t *testing.T) {
s := i18nTestServer(t)
behind := stacks.Stack{
Name: "app", Deployed: true,
Meta: stacks.Metadata{Slug: "app", CatalogSince: time.Now().UTC().AddDate(0, 0, -3).Format("2006-01-02")},
AppConfig: &stacks.AppConfig{InstalledImages: map[string]stacks.InstalledImage{"a": {Ref: "old"}}},
CatalogImages: map[string]string{"a": "new"},
}
current := behind
current.CatalogImages = map[string]string{"a": "old"}
unknown := stacks.Stack{Name: "app", Deployed: false}
huFn := s.templateFuncMap()["updateBadge"].(func(stacks.Stack) *MetaBadge)
enFn := s.localeFuncs("en")["updateBadge"].(func(stacks.Stack) *MetaBadge)
// 1. UNKNOWN renders nothing, in both languages — a badge on an app nobody can judge is a
// question the customer cannot answer.
if huFn(unknown) != nil || enFn(unknown) != nil {
t.Error("an unjudgeable app must carry no badge in either language")
}
// 2. Hungarian is unchanged: the bundle form equals the frozen funcmap form.
for _, st := range []stacks.Stack{current, behind} {
hu := huFn(st)
viaBundle := s.localeFuncs("hu")
if viaBundle != nil {
t.Fatal("localeFuncs must return nothing for Hungarian — that IS the parity guarantee")
}
if hu == nil {
t.Fatal("a judgeable app must carry a badge")
}
}
if got, want := huFn(current).Label, "Naprakész"; got != want {
t.Errorf("hu current label = %q, want %q", got, want)
}
// 3. English really is English, and carries the SAME class (the decision, not the words).
enCur, enBehind := enFn(current), enFn(behind)
if enCur.Label != "Up to date" {
t.Errorf("en current label = %q", enCur.Label)
}
if enCur.Class != huFn(current).Class || enBehind.Class != huFn(behind).Class {
t.Error("the badge CLASS must not depend on the language — only the words may")
}
if !strings.HasPrefix(enBehind.Label, "Update available") || !strings.Contains(enBehind.Label, "3 days ago") {
t.Errorf("en behind label = %q, want the English age suffix", enBehind.Label)
}
for _, b := range []*MetaBadge{enCur, enBehind} {
if strings.ContainsAny(b.Label+b.Title, "áéíóöőúüűÁÉÍÓÖŐÚÜŰ") {
t.Errorf("an English badge still carries Hungarian: %q / %q", b.Label, b.Title)
}
}
// 4. The abandoned lifecycle badge, same shape.
huLife := s.templateFuncMap()["lifecycleBadge"].(func(stacks.Metadata) *MetaBadge)
enLife := s.localeFuncs("en")["lifecycleBadge"].(func(stacks.Metadata) *MetaBadge)
ab := stacks.Metadata{Lifecycle: stacks.LifecycleAbandoned}
if huLife(stacks.Metadata{}) != nil || enLife(stacks.Metadata{}) != nil {
t.Error("an available app must carry no lifecycle badge")
}
if got := huLife(ab).Label; got != "Nem karbantartott" {
t.Errorf("hu lifecycle label = %q", got)
}
if got := enLife(ab).Label; got != "No longer maintained" {
t.Errorf("en lifecycle label = %q", got)
}
}
// TestAgentChannelBannersFollowTheLanguage — R-573. The checker classifies the fault; the banner is
// keyed by that classification. The CONSEQUENCE asserted here is what the household reads, not
// whether a key was set.
//
// RED-PROOF (2026-09-2X): dropping `MessageKey` from SetAgentChannelAlert left the English banner
// reading the Hungarian sentence and this failed naming it.
func TestAgentChannelBannersFollowTheLanguage(t *testing.T) {
s := i18nTestServer(t)
b := s.i18n
// Every reason the checker can classify must have a key, or the banner silently falls back.
for _, reason := range []string{"construction_error", "pin_mismatch", "unauthorized",
"misconfigured", "unreachable", "timeout", "unknown"} {
key := "alert.agent_channel." + reason
if !b.Has("hu", key) {
t.Errorf("reason %q has no Hungarian banner key %q", reason, key)
continue
}
hu, en := b.Msg("hu", key), b.Msg("en", key)
if hu == en {
t.Errorf("%s: the English banner fell back to Hungarian: %q", key, en)
}
if strings.ContainsAny(en, "áéíóöőúüűÁÉÍÓÖŐÚÜŰ") {
t.Errorf("%s: the English banner carries Hungarian letters: %q", key, en)
}
}
if b.Msg("en", "alert.endpoint_drift") == b.Msg("hu", "alert.endpoint_drift") {
t.Error("the endpoint-drift banner fell back to Hungarian")
}
// The AlertManager renders the key, and an UNMAPPED reason still shows the checker's sentence
// rather than a blank banner or a raw key — the fail-open half of the fix.
am := NewAlertManager(nil)
am.SetAgentChannelAlert(true, "alert.agent_channel.timeout", "A tárolókezelő ügynök nem válaszol.")
if got := findAlert(am.GetAlerts("en"), "agent-channel-down"); got == nil {
t.Fatal("the banner did not render")
} else if got.Message != "The storage agent is not answering." {
t.Errorf("en banner = %q", got.Message)
}
am.SetAgentChannelAlert(true, "", "A tárolókezelő ügynök nem válaszol.")
if got := findAlert(am.GetAlerts("en"), "agent-channel-down"); got == nil || got.Message != "A tárolókezelő ügynök nem válaszol." {
t.Errorf("an unmapped reason must fall back to the composed sentence, got %+v", got)
}
am.SetAgentChannelAlert(false, "", "")
if findAlert(am.GetAlerts("en"), "agent-channel-down") != nil {
t.Error("clearing the banner must remove it")
}
}
func findAlert(alerts []Alert, id string) *Alert {
for i := range alerts {
if alerts[i].ID == id {
return &alerts[i]
}
}
return nil
}