Files
mesh-catalog/modules/mesh-mail-notifier/cmd/mesh-mail-notifier/health.go
T
jochen b771794f1c issue 800: keep notify's health as unit; the gate refuses a one-process module judged only by its own tool
ADR 0227 rule 8 / ADR 0240 rule 2 refuse a module with no health check of
another kind beside a tool check. mesh-mail-notifier has one process
(notify); wiring mail_notifier_health as its manifest health, as the review
note's wording suggested (mirroring mesh-mail's watch/mail_health), trips
that rule because mesh-mail also has keep (kind unit) beside watch.
mail_notifier_health stays built, tested and callable; notify's manifest
health stays kind unit until the lead decides how a one-process module
gets this automatically.
2026-10-15 18:33:22 +02:00

63 lines
2.9 KiB
Go

package main
// mesh-mail-notifier's own health (review round 1 of issue 800, which added this tool beside mail-notifier's
// seat verb status, as mesh-mail's own health tool is mail_health beside its seat tools). A notice that fails
// to send stays pending and is retried, at most once a minute (Flush, notifier.go); the account's
// failing-since stays in the Store, so this tool — its own process, asked separately by the node-engine or by
// mesh_call — sees the same account the live notifier does, with no state of its own to lose between asks.
//
// Unhealthy once an account's notice has failed to show for more than UnhealthyAfter; healthy otherwise, even
// while a notice is mid-retry.
//
// STOP (review round 1 of issue 800, not decided by its note): this tool is served and tested, but module.json
// does not name it as the notify process's own health check — mesh-mail-notifier has one process (notify), and
// the catalogue's gate refuses a module judged only by its own tool, with no check of another kind beside it
// (ADR 0227 rule 8, ADR 0240 rule 2; internal/catalogue/health.go ~297 in mesh-controller). mesh-mail avoids
// this because it has two processes — keep (kind unit) beside watch (kind tool, mail_health); mesh-mail-notifier
// has only notify, so wiring this tool the same way trips the gate. notify's health stays kind unit (liveness,
// as it was before this round) until the lead decides how a one-process module gets an automatic, node-engine
// enforced health of this kind: a second process judged some other way, or a change to the gate's rule.
import (
"fmt"
"sort"
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/health"
)
// HealthTool is the module's own health tool the node-engine asks (beside mail-notifier.status, the seat's own verb).
const HealthTool = "mail_notifier_health"
// UnhealthyAfter is how long a notice may fail to send before that is unhealthy, not merely retrying.
const UnhealthyAfter = 10 * time.Minute
// Judge is mesh-mail-notifier's health, from the failures the Store keeps.
func Judge(failures map[string]FailureStatus, now time.Time) health.Answer {
if len(failures) == 0 {
return health.Healthy("every notice shown; none pending a retry")
}
accounts := make([]string, 0, len(failures))
for account := range failures {
accounts = append(accounts, account)
}
sort.Strings(accounts)
var problems, retrying []string
for _, account := range accounts {
f := failures[account]
age := now.Sub(f.Since).Round(time.Second)
if now.Sub(f.Since) > UnhealthyAfter {
problems = append(problems, fmt.Sprintf("%s: its notice has failed to show for %s (%d attempt(s), last: %s)",
account, age, f.Count, f.LastError))
} else {
retrying = append(retrying, fmt.Sprintf("%s has been retrying for %s", account, age))
}
}
if len(problems) > 0 {
return health.Unhealthy(strings.Join(problems, "; "))
}
return health.Healthy(strings.Join(retrying, "; "))
}