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
+54 -22
View File
@@ -135,7 +135,7 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora
// 5. Docker health
if err := checkDocker(); err != nil {
report.addIssue(fmt.Sprintf("Docker: %v", err), MsgRef{})
addDockerIssue(report, err)
if debug {
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)
}
missingProtected := checkProtectedContainers(protected)
for _, name := range missingProtected {
report.addIssue(fmt.Sprintf("Protected container not running: %s", name), MsgRef{})
}
addProtectedIssues(report, missingProtected)
if debug {
if len(missingProtected) > 0 {
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
storageIssues, storageWarnings, storageKinds := checkStoragePaths(storagePaths)
for _, is := range storageIssues {
report.addIssue(is, MsgRef{})
}
for i, w := range storageWarnings {
report.addWarning(w, storageKinds[i])
}
addStorage(report, storagePaths)
// Determine status
if len(report.Issues) > 0 {
@@ -225,6 +217,38 @@ func (r *HealthReport) FormatMessage() 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 {
cmd := dockerexec.Command("docker", "info", "--format", "{{.ServerVersion}}")
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
// 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.
func checkStoragePaths(paths []settings.StoragePath) (issues, warnings, kinds []string) {
// find it by kind rather than by the words the sentence happens to contain today. issueMsgs and
// 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 {
// Skip decommissioned paths — no longer in active use
if sp.Decommissioned {
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 {
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
}
// Path accessible?
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
}
// Mount point check — warning, not issue (avoids false FAIL on demo/test environments)
if !system.IsMountPoint(sp.Path) {
warnings = append(warnings, fmt.Sprintf(
warnFmtStorageNotSeparate, sp.Path))
kinds = append(kinds, WarnKindStorageNotSeparate)
if !isMountPointFn(sp.Path) {
warn(fmt.Sprintf(warnFmtStorageNotSeparate, sp.Path), WarnKindStorageNotSeparate,
MsgRef{"health.storage_not_separate", []interface{}{sp.Path}})
}
// Disk usage
if di := system.GetDiskUsage(sp.Path); di != nil {
if di := diskUsageFn(sp.Path); di != nil {
if di.UsedPercent >= 95 {
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 {
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}})
}
}
}