v0.252.0 — the sentences the program builds follow the language (R-557 slice 2 release A)
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
Slice 1 translated the dashboard's markup. The sentences the program BUILDS were still Hungarian literals in Go, so an English household clicked an English button and was answered in Hungarian. 226 of them move into the bundle here. A flash was the hard part: it travels inside the redirect URL and is rendered by a DIFFERENT request, so it now carries a bundle key plus its parameters. A link minted by an older controller carries prose and is shown verbatim — never a raw key, never dropped. Also converted: page data and view-model text, the internal/api JSON answers, the alert banners (Alert.MessageKey, rendered on the way out of GetAlerts), 237 country names at display, and the four page titles built around an app name (R-566 closed). Hungarian is byte-identical, and that is measured rather than read: scripts/i18n_go_parity.py freezes every Go literal at the base commit (7 467) and refuses a key whose Hungarian is not that text, byte for byte. Three decoys, each seen to convict. Its own first version filtered the capture through an ASCII-Hungarian word list and missed seven real literals — the R-565 class. The filter is gone. Nothing on the wire moved, and wire goldens now hold it there: the report's health warnings and every notify event message stay Hungarian, because the hub MAILS the controller's sentence when it has no entry of its own. Slice 3 (R-558) owns those. MinAgent: 0.131.0 (unchanged). No hub release needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -38,6 +38,19 @@ const (
|
||||
WarnKindStorageUsageHigh = "storage-usage-high" // a data drive is filling up
|
||||
)
|
||||
|
||||
// Storage warning formats. Named (v0.252.0, R-557) so the one property localisation slice 2 must not
|
||||
// break is testable: these sentences are ON THE WIRE (report `health.warnings`), so they stay
|
||||
// Hungarian in every language and their bytes may not move. The dashboard renders its OWN sentence
|
||||
// for the household's language from the kind above plus the parameters below — never from these.
|
||||
// Pinned by TestStorageWarningFormatsAreFrozen.
|
||||
const (
|
||||
warnFmtStorageDisconnected = "Meghajtó leválasztva: %s (%s)"
|
||||
warnFmtStorageUnavailable = "Adattároló nem elérhető: %s"
|
||||
warnFmtStorageNotSeparate = "Az adattároló (%s) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak"
|
||||
warnFmtStorageUsageHigh = "Adattároló használat magas: %s (%.0f%%)"
|
||||
issueFmtStorageAlmostFull = "Adattároló majdnem megtelt: %s (%.0f%%)"
|
||||
)
|
||||
|
||||
// addWarning appends a warning together with its kind, so the two slices cannot drift apart. Every
|
||||
// warning goes through here; `WarningKindAt` reads them back.
|
||||
func (r *HealthReport) addWarning(text, kind string) {
|
||||
@@ -354,29 +367,29 @@ func checkStoragePaths(paths []settings.StoragePath) (issues, warnings, kinds []
|
||||
|
||||
// Skip disconnected paths — handled by the storage watchdog
|
||||
if sp.Disconnected {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf("Meghajtó leválasztva: %s (%s)", sp.Label, sp.Path)), append(kinds, WarnKindStorageDisconnected)
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageDisconnected, sp.Label, sp.Path)), append(kinds, WarnKindStorageDisconnected)
|
||||
continue
|
||||
}
|
||||
|
||||
// Path accessible?
|
||||
if _, err := os.Stat(sp.Path); err != nil {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf("Adattároló nem elérhető: %s", sp.Path)), append(kinds, WarnKindStorageUnavailable)
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUnavailable, sp.Path)), append(kinds, WarnKindStorageUnavailable)
|
||||
continue
|
||||
}
|
||||
|
||||
// Mount point check — warning, not issue (avoids false FAIL on demo/test environments)
|
||||
if !system.IsMountPoint(sp.Path) {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"Az adattároló (%s) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak", sp.Path))
|
||||
warnFmtStorageNotSeparate, sp.Path))
|
||||
kinds = append(kinds, WarnKindStorageNotSeparate)
|
||||
}
|
||||
|
||||
// Disk usage
|
||||
if di := system.GetDiskUsage(sp.Path); di != nil {
|
||||
if di.UsedPercent >= 95 {
|
||||
issues = append(issues, fmt.Sprintf("Adattároló majdnem megtelt: %s (%.0f%%)", sp.Path, di.UsedPercent))
|
||||
issues = append(issues, fmt.Sprintf(issueFmtStorageAlmostFull, sp.Path, di.UsedPercent))
|
||||
} else if di.UsedPercent >= 90 {
|
||||
warnings, kinds = append(warnings, fmt.Sprintf("Adattároló használat magas: %s (%.0f%%)", sp.Path, di.UsedPercent)), append(kinds, WarnKindStorageUsageHigh)
|
||||
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUsageHigh, sp.Path, di.UsedPercent)), append(kinds, WarnKindStorageUsageHigh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
)
|
||||
|
||||
// TestStorageWarningWireTextIsFrozen — localisation slice 2 (R-557), scenario S6.
|
||||
//
|
||||
// `HealthReport.Warnings` is ON THE WIRE: report/builder.go copies it into `health.warnings`, the hub
|
||||
// stores it and the operator reads it there. The rule for slice 2 is that nothing on the wire moves —
|
||||
// so these five sentences may NOT become bundle keys the way a page's copy does, however English the
|
||||
// household's dashboard is.
|
||||
//
|
||||
// The golden below was captured from this file at 736f54b49610, the base commit of v0.252.0, BEFORE
|
||||
// any conversion. It is the measurement, not a restatement of the code: a rewording, a re-punctuation
|
||||
// or a translation of any producer in checkStoragePaths fails here and prints both strings.
|
||||
//
|
||||
// What the DASHBOARD shows for these warnings follows the language by a different route — the kinds
|
||||
// beside them (WarnKind*, internal, never on the wire) plus the parameters, so the page can render its
|
||||
// own sentence without the wire text changing. See internal/web/alerts.go.
|
||||
func TestStorageWarningWireTextIsFrozen(t *testing.T) {
|
||||
// A path that cannot exist, so os.Stat fails deterministically on any machine.
|
||||
const absent = "/felhom-test-does-not-exist/adat"
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
in settings.StoragePath
|
||||
want []string
|
||||
kind []string
|
||||
}{
|
||||
{
|
||||
name: "disconnected",
|
||||
in: settings.StoragePath{Path: "/mnt/hdd_1", Label: "Kulso HDD", Disconnected: true},
|
||||
want: []string{"Meghajtó leválasztva: Kulso HDD (/mnt/hdd_1)"},
|
||||
kind: []string{WarnKindStorageDisconnected},
|
||||
},
|
||||
{
|
||||
name: "unavailable",
|
||||
in: settings.StoragePath{Path: absent, Label: "Adat"},
|
||||
want: []string{"Adattároló nem elérhető: " + absent},
|
||||
kind: []string{WarnKindStorageUnavailable},
|
||||
},
|
||||
{
|
||||
name: "decommissioned is silent",
|
||||
in: settings.StoragePath{Path: absent, Decommissioned: true},
|
||||
want: nil,
|
||||
kind: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, warnings, kinds := checkStoragePaths([]settings.StoragePath{tc.in})
|
||||
if len(warnings) != len(tc.want) {
|
||||
t.Fatalf("got %d warnings %q, want %d %q", len(warnings), warnings, len(tc.want), tc.want)
|
||||
}
|
||||
for i := range tc.want {
|
||||
if warnings[i] != tc.want[i] {
|
||||
t.Errorf("wire text moved:\n got %q\n want %q", warnings[i], tc.want[i])
|
||||
}
|
||||
if kinds[i] != tc.kind[i] {
|
||||
t.Errorf("kind moved: got %q, want %q", kinds[i], tc.kind[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorageWarningFormatsAreFrozen pins the three producers the table above cannot reach on every
|
||||
// machine — they need a real mount point and a real 91 %-full disk, and faking either would test the
|
||||
// fake. It compares the CONSTANTS the producers use, so a translation fails here whatever the machine.
|
||||
//
|
||||
// The rendered form is compared too, not only the format string: a change from `%s` to `%v`, or a
|
||||
// verb reordered with an explicit index, leaves the format literal looking similar and the sentence
|
||||
// different.
|
||||
func TestStorageWarningFormatsAreFrozen(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, got, want string
|
||||
}{
|
||||
{"disconnected", fmt.Sprintf(warnFmtStorageDisconnected, "Kulso HDD", "/mnt/hdd_1"),
|
||||
"Meghajtó leválasztva: Kulso HDD (/mnt/hdd_1)"},
|
||||
{"unavailable", fmt.Sprintf(warnFmtStorageUnavailable, "/mnt/adat"),
|
||||
"Adattároló nem elérhető: /mnt/adat"},
|
||||
{"not separate", fmt.Sprintf(warnFmtStorageNotSeparate, "/mnt/adat"),
|
||||
"Az adattároló (/mnt/adat) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak"},
|
||||
{"usage high", fmt.Sprintf(warnFmtStorageUsageHigh, "/mnt/adat", 91.4),
|
||||
"Adattároló használat magas: /mnt/adat (91%)"},
|
||||
{"almost full", fmt.Sprintf(issueFmtStorageAlmostFull, "/mnt/adat", 96.0),
|
||||
"Adattároló majdnem megtelt: /mnt/adat (96%)"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if tc.got != tc.want {
|
||||
t.Errorf("%s: wire text moved (slice 2 may not translate a sentence the hub reads)\n"+
|
||||
" got %q\n want %q", tc.name, tc.got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user