diff --git a/modules/mesh-self-check/cmd/mesh-self-check/main.go b/modules/mesh-self-check/cmd/mesh-self-check/main.go index 27d588ffe..6ddc930a6 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/main.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/main.go @@ -9,12 +9,13 @@ import ( "context" "encoding/json" "fmt" + assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "os" "time" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" "git.novox.be/novox/mesh-sdk/go/views" @@ -76,10 +77,10 @@ func NewChecker() *Checker { // self-check's own (first-view-unanswered), written into its state findings. func newViews() *views.Reader { r := &views.Reader{Module: Module, Logf: logf, Findings: meshconditionsclient.FindingWriter{State: stdio.State("findings")}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}} - r.Follow(delivery.AssignerModule, delivery.PlacementState, PlacementTool) - r.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool) - r.Follow(delivery.IndexerModule, "modules", ModulesTool) - r.Follow(delivery.IndexerModule, "manifests", ManifestsTool) + r.Follow(assignerclient.Module, placing.StatePlacement, PlacementTool) + r.Follow(assignerclient.Module, placing.StateSeatHolders, SeatHoldersTool) + r.Follow(indexerModule, "modules", ModulesTool) + r.Follow(indexerModule, "manifests", ManifestsTool) return r } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go index a73b0edda..406d0d051 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_data.go @@ -22,15 +22,14 @@ import ( "git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings" "git.novox.be/novox/mesh-catalog/modules/restic/backupgood" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The states D13 reads (declared under `reads`). const ( ReadingsRead = diskreadings.ReadingsRead GoodRead = backupgood.GoodRead - ModulesRead = delivery.IndexerModule + ".modules" - ManifestsRead = delivery.IndexerModule + ".manifests" + ModulesRead = indexerModule + ".modules" + ManifestsRead = indexerModule + ".manifests" ) // The kinds D13 raises. diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go index 878e053d2..ed2eeb2f7 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go @@ -10,6 +10,9 @@ import ( "context" "encoding/json" "fmt" + watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "net" "regexp" "slices" @@ -18,22 +21,21 @@ import ( "sync" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The states the machine probes read (declared under `reads`), and the verb D8 asks each node. const ( - PlacementRead = delivery.AssignerModule + "." + "placement" - SeatHoldersRead = delivery.SeatHoldersRead - PresenceRead = delivery.WatchmanModule + ".presence" + PlacementRead = assignerclient.Module + "." + "placement" + SeatHoldersRead = assignerclient.SeatHoldersRead + PresenceRead = watchmanModule + ".presence" BannedSeat = "node-intrusion-prevention" BannedVerb = "seat:" + BannedSeat + ".banned" ScopeMachine = meshconditionsclient.ScopeMachine ) // placements is every node's placement, by node, from the state's keys in either form: a node key and its module keys, -// or the form before, `placement.` (delivery.PlacementsOf; research 072 row 37). -func (c *Checker) placements() (map[string]delivery.Placement, error) { +// or the form before, `placement.` (assignerclient.PlacementsOf; research 072 row 37). +func (c *Checker) placements() (map[string]placing.Placement, error) { keys, err := c.Store.Keys(PlacementRead) if err != nil { return nil, fmt.Errorf("the placement cannot be read: %w", err) @@ -48,7 +50,7 @@ func (c *Checker) placements() (map[string]delivery.Placement, error) { values[k] = raw } } - out, err := delivery.PlacementsOf(values) + out, err := assignerclient.PlacementsOf(values) if err != nil { return nil, fmt.Errorf("the placement is not readable: %w", err) } @@ -63,15 +65,15 @@ func (c *Checker) heard() (map[string]bool, error) { } out := map[string]bool{} for _, k := range keys { - node, _, _, err := delivery.ParsePresenceKey(k) - if err != nil || node == "" { + node, ok := watchdogs.ParsePresenceByNodeKey(k) + if !ok || node == "" { continue } raw, ok, err := c.Store.Get(PresenceRead, k) if err != nil { return nil, fmt.Errorf("the presence of %s cannot be read: %w", node, err) } - var p delivery.Presence + var p watchdogs.PresenceByNodeValue if ok && json.Unmarshal(raw, &p) == nil && p.Online { out[node] = true } @@ -94,7 +96,7 @@ func (c *Checker) holdersOf(seat string) ([]string, error) { if err != nil { return nil, fmt.Errorf("the holders of %s cannot be read: %w", k, err) } - var sh delivery.SeatHolders + var sh placing.SeatHolders if !ok || json.Unmarshal(raw, &sh) != nil || sh.Seat != seat || sh.Node == "" || len(sh.Holders) == 0 { continue } @@ -214,3 +216,10 @@ func probeBans(ctx context.Context, c *Checker) ([]Observation, error) { } return sorted(out), nil } + +// The holders whose states the probes read, one constant each (a reader names its holder once; ADR 0634 ยง4). +const ( + watchmanModule = "mesh-delivery-watchman" + indexerModule = "mesh-delivery-indexer" + nodeEngineModule = "mesh-host" +) diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_root.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_root.go index c52c72ff8..ee20deaa9 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_root.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_root.go @@ -9,18 +9,19 @@ package main import ( "context" "fmt" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "sort" "strings" "sync" "time" - - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The engine's words and bounds the judgement reads (mesh-host rootsearch). const ( // HealthRead is the node-engines' health reports. - HealthRead = delivery.NodeHealthRead + HealthRead = (nodeEngineModule + "." + nodeseat.StateHealth) // rootPending starts a verdict's reason while the engine's setuid search runs and no complete one judges. rootPending = "not judged yet (search running)" // rootQuiet is how long nothing is raised while an agent account waits for that search, counted from when this @@ -32,17 +33,17 @@ const ( ) // report is a node's health report, and whether it has one. -func (c *Checker) report(node string) (delivery.NodeHealth, bool, error) { +func (c *Checker) report(node string) (nodeseat.HealthByNodeValue, bool, error) { raw, ok, err := c.Store.Get(HealthRead, node) if err != nil { - return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err) + return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s cannot be read: %w", node, err) } if !ok { - return delivery.NodeHealth{}, false, nil + return nodeseat.HealthByNodeValue{}, false, nil } - h, err := delivery.DecodeNodeHealth(raw) + h, err := engineclient.DecodeHealth(raw) if err != nil { - return delivery.NodeHealth{}, false, fmt.Errorf("the health report of %s: %w", node, err) + return nodeseat.HealthByNodeValue{}, false, fmt.Errorf("the health report of %s: %w", node, err) } return h, true, nil } @@ -83,7 +84,7 @@ const ( // judgedConfined is the controller's judgement over one machine's report: confined only when the machine is heard // from, its engine judges root, and every verdict on the agent account is healthy. why is said either way. -func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool, string) { +func judgedConfined(agent string, h nodeseat.HealthByNodeValue, had, heard bool) (bool, string) { switch { case !had: return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine has stated "+ @@ -91,10 +92,10 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool, case !heard: return false, fmt.Sprintf("the agent account %s is not judged: the machine is not heard from, and a verdict "+ "from before is not a verdict on now", agent) - case h.Contract < delivery.RootContract: + case h.Contract < engineclient.RootContract: return false, fmt.Sprintf("the agent account %s is not judged: the machine's node-engine is older than "+ "the judging of an account's root (its statement's contract is %d, the judging is %d)", - agent, h.Contract, delivery.RootContract) + agent, h.Contract, engineclient.RootContract) } verdicts := verdictsOn(agent, h) if len(verdicts) == 0 { @@ -118,8 +119,8 @@ func judgedConfined(agent string, h delivery.NodeHealth, had, heard bool) (bool, } // verdictsOn is every verdict on the account, sorted by module and resource. -func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict { - var out []delivery.RootVerdict +func verdictsOn(agent string, h nodeseat.HealthByNodeValue) []nodeseat.RootAccountVerdict { + var out []nodeseat.RootAccountVerdict for _, v := range h.Root { if v.Account == agent { out = append(out, v) @@ -131,8 +132,8 @@ func verdictsOn(agent string, h delivery.NodeHealth) []delivery.RootVerdict { // rootVerdictKind reads a report for the agent account: pending when every verdict on it is healthy or waits for the // setuid search, at least one of them that; complete when every verdict is healthy or one found a way to root. -func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int { - if !had || !heard || h.Contract < delivery.RootContract { +func rootVerdictKind(agent string, h nodeseat.HealthByNodeValue, had, heard bool) int { + if !had || !heard || h.Contract < engineclient.RootContract { return verdictOther } pending, any := false, false @@ -159,7 +160,7 @@ func rootVerdictKind(agent string, h delivery.NodeHealth, had, heard bool) int { // agentQuiet says the one thing keeping a machine's agent account unjudged is the engine's setuid search, first seen // waiting less than rootQuiet ago. -func (c *Checker) agentQuiet(node, agent string, h delivery.NodeHealth, had, heard bool) bool { +func (c *Checker) agentQuiet(node, agent string, h nodeseat.HealthByNodeValue, had, heard bool) bool { switch rootVerdictKind(agent, h, had, heard) { case verdictComplete: c.pending.judged(node) @@ -186,7 +187,7 @@ type confinement struct { } // confined judges node's agent account from its placement, its report and its presence. -func (c *Checker) confined(node string, place delivery.Placement, heard bool) (confinement, error) { +func (c *Checker) confined(node string, place placing.Placement, heard bool) (confinement, error) { agent := place.Decided.AgentAccount if agent == "" { return confinement{Why: fmt.Sprintf("%s names no agent account: agents run as the operator account (%s)", diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go index 2165a844a..5e569af83 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go @@ -4,6 +4,13 @@ import ( "context" "encoding/json" "errors" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "slices" "strings" "testing" @@ -13,7 +20,6 @@ import ( "git.novox.be/novox/mesh-catalog/modules/restic/backupgood" "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // bus is the bus module's answers as a probe asks them: every mesh stream and consumer as defined, unless changed. @@ -257,42 +263,42 @@ func machines() *memStore { node, address, endpoint string online bool }{{"novox", "10.10.0.1/16", "203.0.113.5:51820", true}, {"ace", "10.10.0.2/16", "", true}, {"g14", "10.10.0.3/16", "", false}} { - pl := delivery.Placement{V: 1, Node: p.node, - Decided: delivery.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}} + pl := placing.Placement{V: 1, Node: p.node, + Decided: placing.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}} if p.node == "ace" { - pl.Modules = []delivery.PlacedModule{{Module: "sshd"}} // a module key beside the node key + pl.Modules = []placing.PlacedModule{{Module: "sshd"}} // a module key beside the node key } place(s, pl) - _ = s.Put(PresenceRead, p.node, delivery.Presence{V: 1, Online: p.online}) - key, _ := delivery.SeatHoldersKey(BannedSeat, p.node) - _ = s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node, - Holders: []delivery.SeatHolder{{Module: "fail2ban", Node: p.node}}}) + _ = s.Put(PresenceRead, p.node, watchdogs.PresenceByNodeValue{V: 1, Online: p.online}) + key, _ := assignerclient.SeatHoldersKey(BannedSeat, p.node) + _ = s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node, + Holders: []placing.SeatHoldersHoldersItem{{Module: "fail2ban", Node: p.node}}}) } return s } // place writes a node's placement as the seat does, its node key and a key per module (research 072 row 37); g14's in // the form before, `placement.g14`, which the probes still read while the change is delivered. -func place(s *memStore, p delivery.Placement) { +func place(s *memStore, p placing.Placement) { if p.Node == "g14" { - raw, _ := delivery.EncodePlacement(p) + raw, _ := assignerclient.EncodePlacement(p) _ = s.Put(PlacementRead, "placement.g14", json.RawMessage(raw)) return } - values, _ := delivery.PlacementValues(p) + values, _ := assignerclient.PlacementValues(p) for k, raw := range values { _ = s.Put(PlacementRead, k, json.RawMessage(raw)) } } // placed is every node's placement the store holds. -func placed(s *memStore) map[string]delivery.Placement { +func placed(s *memStore) map[string]placing.Placement { keys, _ := s.Keys(PlacementRead) values := map[string][]byte{} for _, k := range keys { values[k], _, _ = s.Get(PlacementRead, k) } - out, _ := delivery.PlacementsOf(values) + out, _ := assignerclient.PlacementsOf(values) return out } @@ -314,20 +320,20 @@ func agents(verdicts map[string]string) *memStore { p := placed(s)[node] p.Decided.AgentAccount = "agent" place(s, p) - h := delivery.NodeHealth{V: 1, Node: node, Contract: 13} + h := nodeseat.HealthByNodeValue{V: 1, Node: node, Contract: 13} switch v { case "": case "old": h.Contract = 2 - h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}} + h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateHealthy}} case "pending": - h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", + h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: stateUnknown, Reason: rootPending + ": 3 of 9 trees"}} default: - h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v, + h.Root = []nodeseat.RootAccountVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v, Reason: "can become root without a person: a sudo rule"}} } - raw, _ := delivery.EncodeNodeHealth(h) + raw, _ := engineclient.EncodeHealth(h) _ = s.Put(HealthRead, node, json.RawMessage(raw)) } return s @@ -429,10 +435,10 @@ func (r *dataRig) firstSeen(node, module, item string, ago time.Duration) *dataR } func (r *dataRig) holder(node string) { - key, _ := delivery.SeatHoldersKey("node-backup", node) - _ = r.s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node, - Holders: []delivery.SeatHolder{{Module: "restic", Node: node}}}) - _ = r.s.Put(PresenceRead, node, delivery.Presence{V: 1, Online: true}) + key, _ := assignerclient.SeatHoldersKey("node-backup", node) + _ = r.s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node, + Holders: []placing.SeatHoldersHoldersItem{{Module: "restic", Node: node}}}) + _ = r.s.Put(PresenceRead, node, watchdogs.PresenceByNodeValue{V: 1, Online: true}) } // dataMesh builds the mesh: pg declares data (backed up within 48 h, written all the time within 24 h) and dumps; plex @@ -450,9 +456,9 @@ func dataMesh(change func(*dataRig)) *memStore { man.Data.Own = append(man.Data.Own, it) } raw, _ := json.Marshal(man) - digest := delivery.ManifestDigest(raw) - _ = r.s.Put(ManifestsRead, digest, delivery.Manifest{V: 1, Module: module, JSON: string(raw)}) - _ = r.s.Put(ModulesRead, module, delivery.Module{V: 1, Manifest: digest}) + digest := catalogclient.ManifestDigest(raw) + _ = r.s.Put(ManifestsRead, digest, catalog.ManifestsByDigestValue{V: 1, Module: module, Manifest: string(raw)}) + _ = r.s.Put(ModulesRead, module, catalog.ModulesByModuleValue{V: 1, Manifest: digest}) } declare("pg", map[string]catalogue.DataItem{ "data": {Class: catalogue.ClassValuable, Within: "48h", Active: "24h"}, diff --git a/modules/mesh-self-check/go.mod b/modules/mesh-self-check/go.mod index 999679c50..c1d1d7419 100644 --- a/modules/mesh-self-check/go.mod +++ b/modules/mesh-self-check/go.mod @@ -3,6 +3,13 @@ module mesh-self-check go 1.26.0 require ( + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f