Say each account's root verdict and the engine's contract in the node's health report, so the self-check's DA and D-root judge without the controller (issue 1952, ADR 0266)
Every account resource marked root never goes into NodeHealth.Root; the statement's contract into Contract. Relogin, root and found are judged each on its own, so an account may be both. Pins mesh-sdk f3677eb. Test: the report's row in TestAHealthStatementIsSaidOnTheLinkOpenNow carries the agent account's verdict and the contract.
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220254-6eae0765f4d3
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220254-6eae0765f4d3 h1:4qfwx2bv2vosFM6tEbBZUeaQ/pfRauXZuNI3whul30g=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e h1:h8bh7ZyAgPuFC9/lc6iFBD/hduzomLodZ0a1oJZ5X4U=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220254-6eae0765f4d3/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func TestAHealthStatementIsSaidOnTheLinkOpenNow(t *testing.T) {
|
|||||||
t.Run("the node's health report is written on a change only", func(t *testing.T) {
|
t.Run("the node's health report is written on a change only", func(t *testing.T) {
|
||||||
since := time.Date(2026, 10, 29, 20, 0, 0, 0, time.UTC)
|
since := time.Date(2026, 10, 29, 20, 0, 0, 0, time.UTC)
|
||||||
look := func(streak int, writer string) Health {
|
look := func(streak int, writer string) Health {
|
||||||
return Health{At: time.Now(), Network: &NetworkHealth{State: StateUnhealthy, Since: since, Parts: []NetworkPart{
|
return Health{Contract: 7, At: time.Now(), Network: &NetworkHealth{State: StateUnhealthy, Since: since, Parts: []NetworkPart{
|
||||||
{Part: "resolv-conf", State: StateUnhealthy, Writer: writer, Owner: "dnsmasq", Since: since, Streak: streak}}},
|
{Part: "resolv-conf", State: StateUnhealthy, Writer: writer, Owner: "dnsmasq", Since: since, Streak: streak}}},
|
||||||
Units: &UnitsHealth{State: "degraded", Failed: []FailedUnit{{Unit: "cups.service", Scope: "system", Since: since}}},
|
Units: &UnitsHealth{State: "degraded", Failed: []FailedUnit{{Unit: "cups.service", Scope: "system", Since: since}}},
|
||||||
Resources: []ResourceHealth{
|
Resources: []ResourceHealth{
|
||||||
@@ -67,13 +67,16 @@ func TestAHealthStatementIsSaidOnTheLinkOpenNow(t *testing.T) {
|
|||||||
Reason: "relogin needed: the session began before scanner", Since: since},
|
Reason: "relogin needed: the session began before scanner", Since: since},
|
||||||
{Module: "photos", Resource: "photos.library", Kind: KindDirectory, Target: "/srv/photos", State: StateUnhealthy,
|
{Module: "photos", Resource: "photos.library", Kind: KindDirectory, Target: "/srv/photos", State: StateUnhealthy,
|
||||||
Reason: ReasonUsedAsFound + " owned by root", Since: since},
|
Reason: ReasonUsedAsFound + " owned by root", Since: since},
|
||||||
{Module: "letta", Resource: "letta.server", Kind: "container", State: StateHealthy, Since: since}}}
|
{Module: "letta", Resource: "letta.server", Kind: "container", State: StateHealthy, Since: since},
|
||||||
|
{Module: "claude-code", Resource: "claude-code.agent", Kind: KindAccount, Account: "agent", Root: "never",
|
||||||
|
State: StateHealthy, Since: since}}}
|
||||||
}
|
}
|
||||||
peers := []PeerSeen{{Key: "B=", Seen: true}, {Key: "A=", Addresses: []string{"10.10.0.2/32"}}}
|
peers := []PeerSeen{{Key: "B=", Seen: true}, {Key: "A=", Addresses: []string{"10.10.0.2/32"}}}
|
||||||
r := NodeHealthOf("anchor", look(1, "NetworkManager"), peers, Machines{})
|
r := NodeHealthOf("anchor", look(1, "NetworkManager"), peers, Machines{})
|
||||||
if r.Network.Parts[0].Writer != "NetworkManager" || r.Network.Parts[0].Streak != 0 || r.Peers[0].Key != "A=" ||
|
if r.Network.Parts[0].Writer != "NetworkManager" || r.Network.Parts[0].Streak != 0 || r.Peers[0].Key != "A=" ||
|
||||||
len(r.Units.Failed) != 1 || len(r.Relogin) != 1 || r.Relogin[0].Account != "jochen" ||
|
len(r.Units.Failed) != 1 || len(r.Relogin) != 1 || r.Relogin[0].Account != "jochen" ||
|
||||||
len(r.Found) != 1 || r.Found[0].Path != "/srv/photos" {
|
len(r.Found) != 1 || r.Found[0].Path != "/srv/photos" ||
|
||||||
|
len(r.Root) != 1 || r.Root[0].Account != "agent" || r.Root[0].State != StateHealthy || r.Contract != 7 {
|
||||||
t.Fatalf("the report is %+v", r)
|
t.Fatalf("the report is %+v", r)
|
||||||
}
|
}
|
||||||
// The machines an unhealthy part points at, as the controller holds it there; none for the machine's own.
|
// The machines an unhealthy part points at, as the controller holds it there; none for the machine's own.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
)
|
)
|
||||||
@@ -81,7 +82,7 @@ const TowardHub = "hub"
|
|||||||
// streaks, each unhealthy part naming the machines it points at (machines), its machine units, and each account and
|
// streaks, each unhealthy part naming the machines it points at (machines), its machine units, and each account and
|
||||||
// directory of its resources that the six alerts read.
|
// directory of its resources that the six alerts read.
|
||||||
func NodeHealthOf(node string, h Health, peers []PeerSeen, machines Machines) NodeHealth {
|
func NodeHealthOf(node string, h Health, peers []PeerSeen, machines Machines) NodeHealth {
|
||||||
out := NodeHealth{V: NodeHealthVersion, Node: node, Units: h.Units}
|
out := NodeHealth{V: NodeHealthVersion, Node: node, Units: h.Units, Contract: h.Contract}
|
||||||
if len(peers) > 0 {
|
if len(peers) > 0 {
|
||||||
out.Peers = append([]PeerSeen(nil), peers...)
|
out.Peers = append([]PeerSeen(nil), peers...)
|
||||||
sort.Slice(out.Peers, func(i, k int) bool { return out.Peers[i].Key < out.Peers[k].Key })
|
sort.Slice(out.Peers, func(i, k int) bool { return out.Peers[i].Key < out.Peers[k].Key })
|
||||||
@@ -99,11 +100,17 @@ func NodeHealthOf(node string, h Health, peers []PeerSeen, machines Machines) No
|
|||||||
out.Network = &n
|
out.Network = &n
|
||||||
}
|
}
|
||||||
for _, r := range h.Resources {
|
for _, r := range h.Resources {
|
||||||
switch {
|
// Each alert reads its own: an account may need a new login and be declared never to become root at once.
|
||||||
case r.Kind == KindAccount && r.State == StateUnhealthy && strings.HasPrefix(r.Reason, ReasonReloginNeeded):
|
if r.Kind == KindAccount && r.State == StateUnhealthy && strings.HasPrefix(r.Reason, ReasonReloginNeeded) {
|
||||||
out.Relogin = append(out.Relogin, NodeAccount{Account: r.Account, Module: r.Module, Resource: r.Resource,
|
out.Relogin = append(out.Relogin, NodeAccount{Account: r.Account, Module: r.Module, Resource: r.Resource,
|
||||||
Reason: r.Reason, Since: r.Since})
|
Reason: r.Reason, Since: r.Since})
|
||||||
case r.Kind == KindDirectory && strings.HasPrefix(r.Reason, ReasonUsedAsFound):
|
}
|
||||||
|
if r.Kind == KindAccount && r.Root == declaration.RootNever {
|
||||||
|
// The account declared never to become root without a person, judged (ADR 0266): what DA and D-root read.
|
||||||
|
out.Root = append(out.Root, delivery.RootVerdict{Account: r.Account, Module: r.Module, Resource: r.Resource,
|
||||||
|
State: r.State, Reason: r.Reason, Since: r.Since})
|
||||||
|
}
|
||||||
|
if r.Kind == KindDirectory && strings.HasPrefix(r.Reason, ReasonUsedAsFound) {
|
||||||
out.Found = append(out.Found, NodeDirectory{Module: r.Module, Resource: r.Resource, Path: r.Target,
|
out.Found = append(out.Found, NodeDirectory{Module: r.Module, Resource: r.Resource, Path: r.Target,
|
||||||
Reason: r.Reason, Since: r.Since})
|
Reason: r.Reason, Since: r.Since})
|
||||||
}
|
}
|
||||||
@@ -112,6 +119,9 @@ func NodeHealthOf(node string, h Health, peers []PeerSeen, machines Machines) No
|
|||||||
return out.Relogin[i].Account+out.Relogin[i].Module < out.Relogin[k].Account+out.Relogin[k].Module
|
return out.Relogin[i].Account+out.Relogin[i].Module < out.Relogin[k].Account+out.Relogin[k].Module
|
||||||
})
|
})
|
||||||
sort.Slice(out.Found, func(i, k int) bool { return out.Found[i].Path < out.Found[k].Path })
|
sort.Slice(out.Found, func(i, k int) bool { return out.Found[i].Path < out.Found[k].Path })
|
||||||
|
sort.Slice(out.Root, func(i, k int) bool {
|
||||||
|
return out.Root[i].Module+out.Root[i].Resource < out.Root[k].Module+out.Root[k].Resource
|
||||||
|
})
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user