diff --git a/go/delivery/control_records_test.go b/go/delivery/control_records_test.go index 2c64037..51aed3c 100644 --- a/go/delivery/control_records_test.go +++ b/go/delivery/control_records_test.go @@ -32,6 +32,10 @@ func TestTheControlRecordsReadTheirWritersValues(t *testing.T) { `"relogin":[{"account":"jochen","module":"brother","resource":"brother.scanner","reason":"relogin needed: the session began before scanner","since":"2026-10-29T20:00:00Z"}],` + `"found":[{"module":"photos","resource":"photos.library","path":"/srv/photos","reason":"used as found: owned by root","since":"2026-10-29T20:00:00Z"}],` + `"at":"2026-10-29T20:00:00Z"}`, &NodeHealth{}}, + // The engine's verdict on an account declared never to become root (ADR 0266), and its contract. + {"a node's health report, the agent account judged", `{"v":1,"node":"anchor","root":[{"account":"agent",` + + `"module":"claude-code","resource":"claude-code.agent","state":"healthy","since":"2026-10-29T20:00:00Z"}],` + + `"contract":13,"at":"2026-10-29T20:00:00Z"}`, &NodeHealth{}}, // A part pointing at another machine names it (issue 774): the hub's tunnel, held at the hub's machine. {"a node's health report, a part pointing at the hub", `{"v":1,"node":"anchor","network":{"state":"unhealthy",` + `"since":"2026-10-29T20:00:00Z","parts":[{"part":"tunnel","state":"unhealthy","reason":"the tunnel to the hub has not handshaken for over five minutes",` + diff --git a/go/delivery/nodehealth.go b/go/delivery/nodehealth.go index b9928a3..62c541a 100644 --- a/go/delivery/nodehealth.go +++ b/go/delivery/nodehealth.go @@ -40,6 +40,13 @@ type NodeHealth struct { Relogin []NodeAccount `json:"relogin,omitempty"` // Found are the directories used as found. Found []NodeDirectory `json:"found,omitempty"` + // Root are the engine's verdicts on each account a module declares never to become root without a person (novox/hq + // ADR 0266), sorted: the agent account's among them where a module declares it. What the self-check's DA and D-root + // judge an agent's confinement by, on the machine's own word. + Root []RootVerdict `json:"root,omitempty"` + // Contract is the engine's statement contract (mesh-host link's NodeKeyContract): a reader tells an engine older + // than a judging it reads (RootContract for Root) from one that judged nothing wrong. + Contract int `json:"contract,omitempty"` // At is when the engine wrote this, on a change: never a reason to write. At time.Time `json:"at"` } @@ -113,6 +120,21 @@ type NodeAccount struct { Since time.Time `json:"since"` } +// RootContract is the statement contract from which an engine judges an account's root (mesh-host link.RootContract). +const RootContract = 3 + +// RootVerdict is the engine's verdict on one account declared never to become root without a person: healthy (no way +// found), unhealthy (its reason starts "can become root without a person"), or unknown while its setuid search has not +// completed, with the module and resource declaring it. +type RootVerdict struct { + Account string `json:"account"` + Module string `json:"module"` + Resource string `json:"resource"` + State string `json:"state"` + Reason string `json:"reason,omitempty"` + 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"`