package delivery import ( "bytes" "encoding/json" "errors" "fmt" "time" ) // **A node's own health report on the bus** (issue 774, the operator's alert table of 2026-10-29): one key per node, // ``, of the node-engine's state `health` (NodeHealthBucket), written by that node's engine alone and only when // what it says changed. The watchman (seat mesh-watchdogs) reads it for six alerts the controller raised from the // passing health message: machine-network (Network), network-rewritten (the resolver file's part, Writer and Owner), // network-unreachable (Peers, read across every node's report), machine-units (Units), relogin-needed (Relogin) and // directory-used-as-found (Found). Moved here from mesh-host internal/link (65fbc1e6) unchanged; the engine builds a // report from its own health statement there (NodeHealthOf). // NodeHealthState and NodeHealthBucket are the node-engine's state health, as a reader names it and as the bus does. const ( NodeHealthState = "health" NodeHealthBucket = "mesh-host_" + NodeHealthState NodeHealthRead = "mesh-host." + NodeHealthState ) // NodeHealthVersion is the version of NodeHealth's layout. const NodeHealthVersion = 1 // NodeHealth is one node's health report, as its engine last found it. type NodeHealth struct { V int `json:"v"` Node string `json:"node"` // Network is the machine's networking, its parts without their streaks; nil where the engine does not judge it yet. Network *NetworkHealth `json:"network,omitempty"` // Peers are the mesh tunnel's peers on the last look that read them, sorted by key. Peers []PeerSeen `json:"peers,omitempty"` // Units is the machine's service managers: each failed unit no module places. Units *UnitsHealth `json:"units,omitempty"` // Relogin are the accounts whose running session needs a new login for a group it was given. Relogin []NodeAccount `json:"relogin,omitempty"` // Found are the directories used as found. Found []NodeDirectory `json:"found,omitempty"` // At is when the engine wrote this, on a change: never a reason to write. At time.Time `json:"at"` } // NetworkHealth is the machine's networking (ADR 0241): the worst of its parts, since when, and each part. type NetworkHealth struct { State string `json:"state"` Since time.Time `json:"since"` Parts []NetworkPart `json:"parts"` } // NetworkPart is one part of a machine's networking, as its engine judged it on its second look. type NetworkPart struct { // Part is resolv-conf, names, tunnel, bus or route. Part string `json:"part"` State string `json:"state"` // Reason is why it is not healthy, in words with no address, path or domain; Said the detail. Reason string `json:"reason,omitempty"` Said string `json:"said,omitempty"` // Writer is the program that rewrote the resolver file, when it can be named; Owner the module whose file it is. Writer string `json:"writer,omitempty"` Owner string `json:"owner,omitempty"` // Toward is what the failure points at: "hub", or each resolver's address that failed. Toward []string `json:"toward,omitempty"` Since time.Time `json:"since"` Streak int `json:"streak,omitempty"` } // UnitsHealth is the machine's service managers (issue 315). type UnitsHealth struct { // State is running, degraded or unknown. State string `json:"state"` // Failed is every unit failed on two looks in a row that no module places. Failed []FailedUnit `json:"failed"` // Unread names a manager that could not be read, with why; Away an account's own manager not running. Unread []string `json:"unread,omitempty"` Away []string `json:"away,omitempty"` } // FailedUnit is one failed unit no module places. type FailedUnit struct { Unit string `json:"unit"` // Scope is system, or user: an account's own manager. Scope string `json:"scope"` Load string `json:"load,omitempty"` Result string `json:"result,omitempty"` Resource string `json:"resource,omitempty"` Since time.Time `json:"since"` } // PeerSeen is one peer of the mesh's tunnel: its public key, its allowed addresses, and whether this machine saw a // handshake with it within five minutes. type PeerSeen struct { Key string `json:"key"` Addresses []string `json:"addresses,omitempty"` Seen bool `json:"seen"` } // NodeAccount is one account marked relogin needed, with the module whose resource it is and why. type NodeAccount struct { Account string `json:"account"` Module string `json:"module,omitempty"` Resource string `json:"resource,omitempty"` Reason string `json:"reason"` Since time.Time `json:"since"` } // NodeDirectory is one directory used as found: the module declaring it, its resource and path, and why. type NodeDirectory struct { Module string `json:"module"` Resource string `json:"resource"` Path string `json:"path"` Reason string `json:"reason"` Since time.Time `json:"since"` } // EncodeNodeHealth is h as JSON at its version. func EncodeNodeHealth(h NodeHealth) ([]byte, error) { h.V = NodeHealthVersion if h.Node == "" { return nil, errors.New("a node's health report names its node") } return json.Marshal(h) } // DecodeNodeHealth reads a report. func DecodeNodeHealth(raw []byte) (NodeHealth, error) { var h NodeHealth if err := json.Unmarshal(raw, &h); err != nil { return NodeHealth{}, fmt.Errorf("a node's health report is not readable: %w", err) } if h.V != NodeHealthVersion { return NodeHealth{}, fmt.Errorf("a node's health report of version %d, and this reads %d", h.V, NodeHealthVersion) } return h, nil } // SameNodeHealth says two reports say the same, whenever each was written. func SameNodeHealth(a, b NodeHealth) bool { a.At, b.At = time.Time{}, time.Time{} ra, errA := json.Marshal(a) rb, errB := json.Marshal(b) return errA == nil && errB == nil && bytes.Equal(ra, rb) }