health check: the last six producers carry their dashboard sentence (R-79 option A, D3)

Docker unreachable, protected container down, and the four storage-path entries now carry a
bundle key beside their wire text; the wire text is unchanged byte for byte. Hungarian values of
the four storage keys equal the frozen formats (i18n_go_parity). Red-proven: a zero MsgRef on the
unavailable-path warning fails TestR79_RemainingProducersCarryTheirDashboardSentence and
TestR79_HealthBannersFollowTheHousehold (producer-driven, English household sees Hungarian).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-08 14:31:17 +02:00
parent 423b6d3404
commit fcd9f09927
8 changed files with 206 additions and 24 deletions
+6
View File
@@ -1571,6 +1571,12 @@
"health.memory_high": "Memory usage high: %.0f%%", "health.memory_high": "Memory usage high: %.0f%%",
"health.cpu_high": "CPU usage high: %.0f%%", "health.cpu_high": "CPU usage high: %.0f%%",
"health.temperature_high": "Temperature high: %.0f°C (%s)", "health.temperature_high": "Temperature high: %.0f°C (%s)",
"health.docker_unreachable": "Docker is not reachable: %s",
"health.protected_container_down": "A protected system service is not running: %s",
"health.storage_unavailable": "Storage not reachable: %s",
"health.storage_not_separate": "The storage (%s) is not on a separate drive — data is written to the system drive",
"health.storage_usage_high": "Storage usage high: %s (%.0f%%)",
"health.storage_almost_full": "Storage almost full: %s (%.0f%%)",
"hold.copy_holds.db_only": "holds only the settings and the database, not the files", "hold.copy_holds.db_only": "holds only the settings and the database, not the files",
"hold.copy_holds.files": "holds the settings, the database and the files", "hold.copy_holds.files": "holds the settings, the database and the files",
"hold.copy_holds.volumes": "holds the settings, the database and the data volumes", "hold.copy_holds.volumes": "holds the settings, the database and the data volumes",
+6
View File
@@ -1559,6 +1559,12 @@
"health.memory_high": "A memóriahasználat magas: %.0f%%", "health.memory_high": "A memóriahasználat magas: %.0f%%",
"health.cpu_high": "A processzor terhelése magas: %.0f%%", "health.cpu_high": "A processzor terhelése magas: %.0f%%",
"health.temperature_high": "A hőmérséklet magas: %.0f°C (%s)", "health.temperature_high": "A hőmérséklet magas: %.0f°C (%s)",
"health.docker_unreachable": "A Docker nem érhető el: %s",
"health.protected_container_down": "Egy védett rendszerszolgáltatás nem fut: %s",
"health.storage_unavailable": "Adattároló nem elérhető: %s",
"health.storage_not_separate": "Az adattároló (%s) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak",
"health.storage_usage_high": "Adattároló használat magas: %s (%.0f%%)",
"health.storage_almost_full": "Adattároló majdnem megtelt: %s (%.0f%%)",
"hold.copy_holds.db_only": "csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem", "hold.copy_holds.db_only": "csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem",
"hold.copy_holds.files": "a beállításokat, az adatbázist és a fájlokat tartalmazza", "hold.copy_holds.files": "a beállításokat, az adatbázist és a fájlokat tartalmazza",
"hold.copy_holds.volumes": "a beállításokat, az adatbázist és az adatköteteket tartalmazza", "hold.copy_holds.volumes": "a beállításokat, az adatbázist és az adatköteteket tartalmazza",
+54 -22
View File
@@ -135,7 +135,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
// 5. Docker health // 5. Docker health
if err := checkDocker(); err != nil { if err := checkDocker(); err != nil {
report.addIssue(fmt.Sprintf("Docker: %v", err), MsgRef{}) addDockerIssue(report, err)
if debug { if debug {
logger.Printf("[DEBUG] [monitor] Docker daemon: FAIL (%v)", err) logger.Printf("[DEBUG] [monitor] Docker daemon: FAIL (%v)", err)
} }
@@ -153,9 +153,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
logger.Printf("[DEBUG] [monitor] Checking %d protected containers: %v", len(protected), protected) logger.Printf("[DEBUG] [monitor] Checking %d protected containers: %v", len(protected), protected)
} }
missingProtected := checkProtectedContainers(protected) missingProtected := checkProtectedContainers(protected)
for _, name := range missingProtected { addProtectedIssues(report, missingProtected)
report.addIssue(fmt.Sprintf("Protected container not running: %s", name), MsgRef{})
}
if debug { if debug {
if len(missingProtected) > 0 { if len(missingProtected) > 0 {
logger.Printf("[DEBUG] [monitor] Protected containers missing: %v", missingProtected) logger.Printf("[DEBUG] [monitor] Protected containers missing: %v", missingProtected)
@@ -165,13 +163,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
} }
// 7. Storage paths // 7. Storage paths
storageIssues, storageWarnings, storageKinds := checkStoragePaths(storagePaths) addStorage(report, storagePaths)
for _, is := range storageIssues {
report.addIssue(is, MsgRef{})
}
for i, w := range storageWarnings {
report.addWarning(w, storageKinds[i])
}
// Determine status // Determine status
if len(report.Issues) > 0 { if len(report.Issues) > 0 {
@@ -225,6 +217,38 @@ func (r *HealthReport) FormatMessage() string {
return sb.String() return sb.String()
} }
// addDockerIssue / addProtectedIssues / addStorage are RunHealthCheck's Docker, protected-container
// and storage-path halves, split out so a test can drive them without a Docker daemon or a real drive
// (R-79 option A: TestR79_RemainingProducersCarryTheirDashboardSentence). Each entry keeps its wire
// text and carries the dashboard's own sentence beside it (R-516 item 10).
func addDockerIssue(report *HealthReport, err error) {
// The argument is Docker's own error text, which is English in every language (it comes from
// the docker client); the sentence around it follows the household.
report.addIssue(fmt.Sprintf("Docker: %v", err), MsgRef{"health.docker_unreachable", []interface{}{err.Error()}})
}
func addProtectedIssues(report *HealthReport, missing []string) {
for _, name := range missing {
report.addIssue(fmt.Sprintf("Protected container not running: %s", name), MsgRef{"health.protected_container_down", []interface{}{name}})
}
}
func addStorage(report *HealthReport, paths []settings.StoragePath) {
issues, issueMsgs, warnings, kinds, warnMsgs := checkStoragePaths(paths)
for i, is := range issues {
report.addIssue(is, issueMsgs[i])
}
for i, w := range warnings {
report.addWarningMsg(w, kinds[i], warnMsgs[i])
}
}
// isMountPointFn / diskUsageFn are test seams over the two drive probes checkStoragePaths makes.
var (
isMountPointFn = system.IsMountPoint
diskUsageFn = system.GetDiskUsage
)
func checkDocker() error { func checkDocker() error {
cmd := dockerexec.Command("docker", "info", "--format", "{{.ServerVersion}}") cmd := dockerexec.Command("docker", "info", "--format", "{{.ServerVersion}}")
out, err := cmd.Output() out, err := cmd.Output()
@@ -303,39 +327,47 @@ func checkProtectedContainers(protected []string) []string {
// checkStoragePaths returns the storage issues and, beside each warning, its KIND (R-553) — the // checkStoragePaths returns the storage issues and, beside each warning, its KIND (R-553) — the
// dashboard places the "not on a separate drive" warning inline under the storage bars, and it must // dashboard places the "not on a separate drive" warning inline under the storage bars, and it must
// find it by kind rather than by the words the sentence happens to contain today. // find it by kind rather than by the words the sentence happens to contain today. issueMsgs and
func checkStoragePaths(paths []settings.StoragePath) (issues, warnings, kinds []string) { // warnMsgs are parallel to issues and warnings: the dashboard sentence of each (R-79 option A; the
// Hungarian bundle value of each key equals the frozen wire format above, byte for byte).
func checkStoragePaths(paths []settings.StoragePath) (issues []string, issueMsgs []MsgRef, warnings, kinds []string, warnMsgs []MsgRef) {
warn := func(text, kind string, msg MsgRef) {
warnings, kinds, warnMsgs = append(warnings, text), append(kinds, kind), append(warnMsgs, msg)
}
for _, sp := range paths { for _, sp := range paths {
// Skip decommissioned paths — no longer in active use // Skip decommissioned paths — no longer in active use
if sp.Decommissioned { if sp.Decommissioned {
continue continue
} }
// Skip disconnected paths — handled by the storage watchdog // Skip disconnected paths — handled by the storage watchdog. No dashboard sentence: the
// dashboard drops this warning for its own alert.storage.disconnected banner (R-516 item 8).
if sp.Disconnected { if sp.Disconnected {
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageDisconnected, sp.Label, sp.Path)), append(kinds, WarnKindStorageDisconnected) warn(fmt.Sprintf(warnFmtStorageDisconnected, sp.Label, sp.Path), WarnKindStorageDisconnected, MsgRef{})
continue continue
} }
// Path accessible? // Path accessible?
if _, err := os.Stat(sp.Path); err != nil { if _, err := os.Stat(sp.Path); err != nil {
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUnavailable, sp.Path)), append(kinds, WarnKindStorageUnavailable) warn(fmt.Sprintf(warnFmtStorageUnavailable, sp.Path), WarnKindStorageUnavailable,
MsgRef{"health.storage_unavailable", []interface{}{sp.Path}})
continue continue
} }
// Mount point check — warning, not issue (avoids false FAIL on demo/test environments) // Mount point check — warning, not issue (avoids false FAIL on demo/test environments)
if !system.IsMountPoint(sp.Path) { if !isMountPointFn(sp.Path) {
warnings = append(warnings, fmt.Sprintf( warn(fmt.Sprintf(warnFmtStorageNotSeparate, sp.Path), WarnKindStorageNotSeparate,
warnFmtStorageNotSeparate, sp.Path)) MsgRef{"health.storage_not_separate", []interface{}{sp.Path}})
kinds = append(kinds, WarnKindStorageNotSeparate)
} }
// Disk usage // Disk usage
if di := system.GetDiskUsage(sp.Path); di != nil { if di := diskUsageFn(sp.Path); di != nil {
if di.UsedPercent >= 95 { if di.UsedPercent >= 95 {
issues = append(issues, fmt.Sprintf(issueFmtStorageAlmostFull, sp.Path, di.UsedPercent)) issues = append(issues, fmt.Sprintf(issueFmtStorageAlmostFull, sp.Path, di.UsedPercent))
issueMsgs = append(issueMsgs, MsgRef{"health.storage_almost_full", []interface{}{sp.Path, di.UsedPercent}})
} else if di.UsedPercent >= 90 { } else if di.UsedPercent >= 90 {
warnings, kinds = append(warnings, fmt.Sprintf(warnFmtStorageUsageHigh, sp.Path, di.UsedPercent)), append(kinds, WarnKindStorageUsageHigh) warn(fmt.Sprintf(warnFmtStorageUsageHigh, sp.Path, di.UsedPercent), WarnKindStorageUsageHigh,
MsgRef{"health.storage_usage_high", []interface{}{sp.Path, di.UsedPercent}})
} }
} }
} }
@@ -1,9 +1,14 @@
package monitor package monitor
import ( import (
"fmt"
"os"
"path/filepath"
"strings"
"testing" "testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config" "gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/system" "gitea.dooplex.hu/admin/felhom-controller/internal/system"
) )
@@ -62,3 +67,79 @@ func TestR516_ResourceWarningsCarryTheirDashboardSentence(t *testing.T) {
} }
} }
} }
// R-79 option A (operator ruling D3, 2026-10-08) — the six producers R-516 item 10 left verbatim:
// Docker unreachable, a protected container down, and the four storage-path entries. Each now
// carries its dashboard sentence; the wire text is byte for byte what it was (it is ON THE WIRE,
// report health.*; the frozen storage formats are also pinned by TestStorageWarningFormatsAreFrozen).
//
// COMPANION RED-PROOF: give any one of them a zero MsgRef (e.g. the unavailable-path warning back to
// addWarning) — its case fails here, and TestR79_StorageBannerFollowsTheHousehold in internal/web
// shows the Hungarian wire text to an English household again.
func TestR79_RemainingProducersCarryTheirDashboardSentence(t *testing.T) {
type entry struct{ wire, key string }
check := func(name string, texts []string, msgs func(int) MsgRef, want []entry) {
t.Helper()
if len(texts) != len(want) {
t.Fatalf("%s: %d entries %q, want %d — the cases below would be compared against nothing", name, len(texts), texts, len(want))
}
for i := range texts {
if texts[i] != want[i].wire {
t.Errorf("%s: the wire text moved: %q, want %q", name, texts[i], want[i].wire)
}
if k := msgs(i).Key; k != want[i].key {
t.Errorf("%s: %q carries dashboard key %q, want %q", name, texts[i], k, want[i].key)
}
}
}
// Docker and protected containers: driven through the same adders RunHealthCheck calls.
r := &HealthReport{}
addDockerIssue(r, fmt.Errorf("docker not reachable: exit status 1"))
addProtectedIssues(r, []string{"cloudflared", "traefik"})
check("docker+protected", r.Issues, r.IssueMsgAt, []entry{
{"Docker: docker not reachable: exit status 1", "health.docker_unreachable"},
{"Protected container not running: cloudflared", "health.protected_container_down"},
{"Protected container not running: traefik", "health.protected_container_down"},
})
if a := r.IssueMsgAt(1).Args; len(a) != 1 || a[0] != "cloudflared" {
t.Errorf("protected-container sentence args = %v, want [cloudflared]", a)
}
// Storage paths: a missing path, a path that is not a mount point at 92 %, one at 97 %.
oldMount, oldUsage := isMountPointFn, diskUsageFn
defer func() { isMountPointFn, diskUsageFn = oldMount, oldUsage }()
isMountPointFn = func(p string) bool { return !strings.HasSuffix(p, "/notsep") }
diskUsageFn = func(p string) *system.DiskUsageInfo {
switch {
case strings.HasSuffix(p, "/notsep"):
return &system.DiskUsageInfo{UsedPercent: 92}
case strings.HasSuffix(p, "/full"):
return &system.DiskUsageInfo{UsedPercent: 97}
}
return nil
}
dir := t.TempDir()
for _, sub := range []string{"notsep", "full"} {
if err := os.Mkdir(filepath.Join(dir, sub), 0o755); err != nil {
t.Fatal(err)
}
}
missing := filepath.Join(dir, "missing")
r = &HealthReport{}
addStorage(r, []settings.StoragePath{{Path: missing}, {Path: dir + "/notsep"}, {Path: dir + "/full"}})
check("storage warnings", r.Warnings, r.WarningMsgAt, []entry{
{"Adattároló nem elérhető: " + missing, "health.storage_unavailable"},
{"Az adattároló (" + dir + "/notsep) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak", "health.storage_not_separate"},
{"Adattároló használat magas: " + dir + "/notsep (92%)", "health.storage_usage_high"},
})
check("storage issues", r.Issues, r.IssueMsgAt, []entry{
{"Adattároló majdnem megtelt: " + dir + "/full (97%)", "health.storage_almost_full"},
})
if len(r.Warnings) != len(r.WarningKinds) || len(r.Warnings) != len(r.WarningMsgs) || len(r.Issues) != len(r.IssueMsgs) {
t.Errorf("storage: the parallel slices drifted apart")
}
if got := r.WarningKindAt(1); got != WarnKindStorageNotSeparate {
t.Errorf("not-separate warning lost its kind: %q", got)
}
}
@@ -27,7 +27,7 @@ func TestR553_StorageWarningsCarryKindsAndKeepTheirWords(t *testing.T) {
{Path: present}, {Path: present},
{Path: filepath.Join(dir, "kihagyott"), Decommissioned: true}, {Path: filepath.Join(dir, "kihagyott"), Decommissioned: true},
} }
_, warnings, kinds := checkStoragePaths(paths) _, _, warnings, kinds, _ := checkStoragePaths(paths)
if len(warnings) != len(kinds) { if len(warnings) != len(kinds) {
t.Fatalf("a warning without its kind (or the reverse): %d warnings, %d kinds", len(warnings), len(kinds)) t.Fatalf("a warning without its kind (or the reverse): %d warnings, %d kinds", len(warnings), len(kinds))
} }
@@ -53,7 +53,7 @@ func TestStorageWarningWireTextIsFrozen(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, warnings, kinds := checkStoragePaths([]settings.StoragePath{tc.in}) _, _, warnings, kinds, _ := checkStoragePaths([]settings.StoragePath{tc.in})
if len(warnings) != len(tc.want) { if len(warnings) != len(tc.want) {
t.Fatalf("got %d warnings %q, want %d %q", len(warnings), warnings, len(tc.want), tc.want) t.Fatalf("got %d warnings %q, want %d %q", len(warnings), warnings, len(tc.want), tc.want)
} }
@@ -0,0 +1,51 @@
package web
import (
"io"
"log"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-79 option A (operator ruling D3, 2026-10-08) — the storage and protected-container entries of the
// health check reached the banner verbatim: Hungarian for an English household (the frozen storage
// formats), English for a Hungarian one (the container issue). Driven from the PRODUCER
// (monitor.RunHealthCheck), not a hand-built report, so a dropped MsgRef in healthcheck.go is seen here.
// Docker is refused under go test (R-650), so the Docker and protected-container issues appear on
// every run of this test — that is the input, not a side effect on this host.
//
// COMPANION RED-PROOF: give the unavailable-path warning in checkStoragePaths a zero MsgRef — the "en"
// case fails with „Adattároló nem elérhető: …".
func TestR79_HealthBannersFollowTheHousehold(t *testing.T) {
missing := filepath.Join(t.TempDir(), "gone")
cfg := config.Default()
cfg.Hub.Enabled = false
cfg.Stacks.Protected = []string{"traefik"}
hr := monitor.RunHealthCheck(cfg, nil, []settings.StoragePath{{Path: missing}}, settings.SMBSettings{}, nil)
am := NewAlertManager(log.New(io.Discard, "", 0))
am.Refresh(hr, cfg, nil, false, "")
want := map[string][]string{
"hu": {"Adattároló nem elérhető: " + missing, "Egy védett rendszerszolgáltatás nem fut: traefik", "A Docker nem érhető el: "},
"en": {"Storage not reachable: " + missing, "A protected system service is not running: traefik", "Docker is not reachable: "},
}
for lang, ws := range want {
var got []string
for _, a := range am.GetAlerts(lang) {
if strings.HasPrefix(a.ID, "health-") {
got = append(got, a.Message)
}
}
joined := strings.Join(got, "\n")
for _, w := range ws {
if !strings.Contains(joined, w) {
t.Errorf("%s: no banner contains %q; banners:\n%s", lang, w, joined)
}
}
}
}
+6
View File
@@ -178,6 +178,12 @@
"health.memory_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.", "health.memory_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.",
"health.cpu_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.", "health.cpu_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.",
"health.temperature_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.", "health.temperature_high": "BORN AS A KEY (R-516 item 10) -- the dashboard banner's own sentence for a health-check warning/issue whose wire text (report health.*) is English and stays so; a NEW sentence, never a Go literal. Pinned in both languages by TestR516_HealthBannersFollowTheHousehold.",
"health.docker_unreachable": "BORN AS A KEY (R-79 option A, operator ruling D3 2026-10-08) -- the dashboard banner's own sentence for a health-check issue whose wire text (report health.issues) is English and stays so; a NEW sentence, never a Go literal. Pinned by TestR79_RemainingProducersCarryTheirDashboardSentence and TestR79_HealthBannersFollowTheHousehold.",
"health.protected_container_down": "BORN AS A KEY (R-79 option A, operator ruling D3 2026-10-08) -- the dashboard banner's own sentence for a health-check issue whose wire text (report health.issues) is English and stays so; a NEW sentence, never a Go literal. Pinned by TestR79_RemainingProducersCarryTheirDashboardSentence and TestR79_HealthBannersFollowTheHousehold.",
"health.storage_unavailable": "Adattároló nem elérhető: %s",
"health.storage_not_separate": "Az adattároló (%s) nem külön meghajtón van — az adatok a rendszermeghajtóra íródnak",
"health.storage_usage_high": "Adattároló használat magas: %s (%.0f%%)",
"health.storage_almost_full": "Adattároló majdnem megtelt: %s (%.0f%%)",
"alert.deadapp.group": "R-516 item 9 -- REWORDED on purpose: the formal „nézze meg\" became the product's te-form „nézd meg\"; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned in both languages by TestPluralEnglish (i18n_flash_test.go).", "alert.deadapp.group": "R-516 item 9 -- REWORDED on purpose: the formal „nézze meg\" became the product's te-form „nézd meg\"; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned in both languages by TestPluralEnglish (i18n_flash_test.go).",
"event.appstop.failed": "BORN AS A KEY (R-585) -- the interrupted-operation alert used to send the OPERATOR's English sentence (backup.AppStopRecovery.Message) to every household through the customer-enabled backup_failed type; a NEW Hungarian sentence, never a Go literal. Pinned in both languages by TestR585_InterruptedOperationFollowsTheHousehold.", "event.appstop.failed": "BORN AS A KEY (R-585) -- the interrupted-operation alert used to send the OPERATOR's English sentence (backup.AppStopRecovery.Message) to every household through the customer-enabled backup_failed type; a NEW Hungarian sentence, never a Go literal. Pinned in both languages by TestR585_InterruptedOperationFollowsTheHousehold.",
"event.appstop.failed_held": "BORN AS A KEY (R-585) -- the interrupted-operation alert used to send the OPERATOR's English sentence (backup.AppStopRecovery.Message) to every household through the customer-enabled backup_failed type; a NEW Hungarian sentence, never a Go literal. Pinned in both languages by TestR585_InterruptedOperationFollowsTheHousehold.", "event.appstop.failed_held": "BORN AS A KEY (R-585) -- the interrupted-operation alert used to send the OPERATOR's English sentence (backup.AppStopRecovery.Message) to every household through the customer-enabled backup_failed type; a NEW Hungarian sentence, never a Go literal. Pinned in both languages by TestR585_InterruptedOperationFollowsTheHousehold.",