From 821d6115305e028bce672352597993e45c75c77a Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 22:18:04 +0100 Subject: [PATCH] Round 2 T1, T2, T5: the contracts end G with no more tests than at their merge base; fixtures name roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test that read every fixture for the operator's account and a node goes (T1); the tests G added fold into existing ones as subtests, so each module counts its merge base (design 47 G §626, A-306); node names in the lines G adds are role names (T5). Builds: design 47 G §626, A-306; review round 2 findings T1, T2, T5 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/clients/meshconfigclient/client_test.go | 31 -- go/clients/nodeengineclient/have_test.go | 31 +- go/seats/meshmoduleassigner/contract_test.go | 4 +- go/seats/nodeengine/contract_test.go | 20 +- .../go/seats/meshconditions/contract_test.go | 14 +- .../go/seats/meshhealers/contract_test.go | 4 +- .../go/seats/meshwatchdogs/contract_test.go | 4 +- .../mesh-conditions/fixtures/events.json | 12 +- .../mesh-conditions/fixtures/states.json | 26 +- .../seats/mesh-healers/fixtures/events.json | 6 +- .../seats/mesh-watchdogs/fixtures/events.json | 8 +- .../meshnodeenrollerclient/enrolment_test.go | 45 ++- .../operatorchannelclient/client_test.go | 275 ++++++++++-------- .../operatorchannelclient/gate_conn_test.go | 52 ---- seats/node-engine/fixtures/events.json | 16 +- 15 files changed, 253 insertions(+), 295 deletions(-) diff --git a/go/clients/meshconfigclient/client_test.go b/go/clients/meshconfigclient/client_test.go index 4e0fc06..ede862f 100644 --- a/go/clients/meshconfigclient/client_test.go +++ b/go/clients/meshconfigclient/client_test.go @@ -1,9 +1,6 @@ package meshconfigclient import ( - "io/fs" - "os" - "path/filepath" "strings" "testing" ) @@ -46,31 +43,3 @@ func TestAHeldValueIsTheOneItsKeyNames(t *testing.T) { } } } - -// Design 47 G: the contracts' fixtures, and the tests generated from them, name no account of the operator and no node -// they run on: a role name stands for either (`operator`, `workstation`). -func TestFixturesNameNoOperatorAccountAndNoWorkstation(t *testing.T) { - root := "../../.." - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil || d.IsDir() { - return err - } - slash := filepath.ToSlash(path) - if !strings.Contains(slash, "/fixtures/") && !strings.HasSuffix(slash, "/contract_test.go") || strings.Contains(slash, "node_modules") { - return nil - } - raw, err := os.ReadFile(path) - if err != nil { - return err - } - for _, word := range []string{"jochen", "shanks"} { - if strings.Contains(string(raw), word) { - t.Errorf("%s names %q: a fixture names the operator and the workstation by role", slash, word) - } - } - return nil - }) - if err != nil { - t.Fatal(err) - } -} diff --git a/go/clients/nodeengineclient/have_test.go b/go/clients/nodeengineclient/have_test.go index 6a408d7..0ccf323 100644 --- a/go/clients/nodeengineclient/have_test.go +++ b/go/clients/nodeengineclient/have_test.go @@ -58,22 +58,21 @@ func TestHaveCodec(t *testing.T) { if _, err := DecodeHave([]byte(`{"v":1,"phase":"running","health":"ok","extra":1}`)); err == nil { t.Fatal("an unknown field is read") } -} - -func TestSameHaveComparesTimesAsInstants(t *testing.T) { - at := time.Date(2026, 11, 5, 12, 0, 0, 0, time.UTC) - zone := time.FixedZone("x", 3600) - mk := func(tm time.Time) seat.HaveByNodeModuleValue { - return seat.HaveByNodeModuleValue{Phase: PhaseFailed, Health: HealthFailed, - PullWait: &seat.PullWait{Image: "i", Holder: "h", Began: tm}, - UserList: &seat.UserListWritten{Source: "s", At: tm}} - } - if !SameHave(mk(at), mk(at.In(zone))) { - t.Fatal("the same instant in another zone differs") - } - if SameHave(mk(at), mk(at.Add(time.Second))) { - t.Fatal("another instant is the same") - } + t.Run("SameHaveComparesTimesAsInstants", func(t *testing.T) { + at := time.Date(2026, 11, 5, 12, 0, 0, 0, time.UTC) + zone := time.FixedZone("x", 3600) + mk := func(tm time.Time) seat.HaveByNodeModuleValue { + return seat.HaveByNodeModuleValue{Phase: PhaseFailed, Health: HealthFailed, + PullWait: &seat.PullWait{Image: "i", Holder: "h", Began: tm}, + UserList: &seat.UserListWritten{Source: "s", At: tm}} + } + if !SameHave(mk(at), mk(at.In(zone))) { + t.Fatal("the same instant in another zone differs") + } + if SameHave(mk(at), mk(at.Add(time.Second))) { + t.Fatal("another instant is the same") + } + }) } func TestHaveNodeCodec(t *testing.T) { diff --git a/go/seats/meshmoduleassigner/contract_test.go b/go/seats/meshmoduleassigner/contract_test.go index b5f4fa0..e38965e 100644 --- a/go/seats/meshmoduleassigner/contract_test.go +++ b/go/seats/meshmoduleassigner/contract_test.go @@ -41,7 +41,7 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a module's key on a watched branch and a build", subject: "", given: "{\"node\":\"ace\",\"placed\":{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"plex\"},\"v\":1}", wire: "{\"node\":\"ace\",\"placed\":{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"plex\"},\"v\":1}", + {name: "a module's key on a watched branch and a build", subject: "", given: "{\"node\":\"home-server\",\"placed\":{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"plex\"},\"v\":1}", wire: "{\"node\":\"home-server\",\"placed\":{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"plex\"},\"v\":1}", decode: func(b []byte) (any, error) { var v AssignmentsByNodeModuleValue err := json.Unmarshal(b, &v) @@ -53,7 +53,7 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a proposal placing a module on a watched branch at an earlier build", subject: "", given: "{\"at\":\"2026-11-10T12:00:00Z\",\"base\":{\"ace\":3},\"by\":\"operator\",\"changes\":[{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"ace\",\"op\":\"place\"}],\"id\":\"p-20261110t120000-aabbccdd\",\"state\":\"answering\",\"why\":\"run nats from beta, back one build\"}", wire: "{\"at\":\"2026-11-10T12:00:00Z\",\"base\":{\"ace\":3},\"by\":\"operator\",\"changes\":[{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"ace\",\"op\":\"place\"}],\"id\":\"p-20261110t120000-aabbccdd\",\"state\":\"answering\",\"why\":\"run nats from beta, back one build\"}", + {name: "a proposal placing a module on a watched branch at an earlier build", subject: "", given: "{\"at\":\"2026-11-10T12:00:00Z\",\"base\":{\"home-server\":3},\"by\":\"operator\",\"changes\":[{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"home-server\",\"op\":\"place\"}],\"id\":\"p-20261110t120000-aabbccdd\",\"state\":\"answering\",\"why\":\"run nats from beta, back one build\"}", wire: "{\"at\":\"2026-11-10T12:00:00Z\",\"base\":{\"home-server\":3},\"by\":\"operator\",\"changes\":[{\"branch\":\"beta\",\"build\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"home-server\",\"op\":\"place\"}],\"id\":\"p-20261110t120000-aabbccdd\",\"state\":\"answering\",\"why\":\"run nats from beta, back one build\"}", decode: func(b []byte) (any, error) { var v ProposalsByProposalValue err := json.Unmarshal(b, &v) diff --git a/go/seats/nodeengine/contract_test.go b/go/seats/nodeengine/contract_test.go index e206c6d..1872313 100644 --- a/go/seats/nodeengine/contract_test.go +++ b/go/seats/nodeengine/contract_test.go @@ -35,19 +35,19 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.manifest-reads-changed\\.[A-Za-z0-9_-]+$")}, - {name: "composed is said", subject: "mesh.seat.node-engine.event.composed.ace", given: "{\"at\":\"2026-11-09T20:00:00Z\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:00:00Z\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", + {name: "composed is said", subject: "mesh.seat.node-engine.event.composed.home-server", given: "{\"at\":\"2026-11-09T20:00:00Z\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:00:00Z\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", decode: func(b []byte) (any, error) { var v ComposedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.composed\\.[^.*> ]+$")}, - {name: "running is said, its node and module in its subject", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", + {name: "running is said, its node and module in its subject", subject: "mesh.seat.node-engine.event.running.home-server.nats", given: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", wire: "{\"at\":\"2026-11-09T20:01:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "running says the build that runs is healthy", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", + {name: "running says the build that runs is healthy", subject: "mesh.seat.node-engine.event.running.home-server.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":true,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "running says the build that runs turned unhealthy, still running", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"ace\",\"v\":1}", + {name: "running says the build that runs turned unhealthy, still running", subject: "mesh.seat.node-engine.event.running.home-server.nats", given: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", wire: "{\"at\":\"2026-11-11T09:05:00Z\",\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"healthy\":false,\"manifest\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"module\":\"nats\",\"node\":\"home-server\",\"v\":1}", decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "running says a module stopped on the node, with no fingerprint", subject: "mesh.seat.node-engine.event.running.ace.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"ace\",\"stopped\":true,\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"ace\",\"stopped\":true,\"v\":1}", + {name: "running says a module stopped on the node, with no fingerprint", subject: "mesh.seat.node-engine.event.running.home-server.nats", given: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"home-server\",\"stopped\":true,\"v\":1}", wire: "{\"at\":\"2026-11-11T09:00:00Z\",\"module\":\"nats\",\"node\":\"home-server\",\"stopped\":true,\"v\":1}", decode: func(b []byte) (any, error) { var v RunningEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.running\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "a kept build's wait is held", subject: "mesh.seat.node-engine.event.wait-held.ace.postgres", given: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"ace\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on ace\"}", wire: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"ace\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on ace\"}", + {name: "a kept build's wait is held", subject: "mesh.seat.node-engine.event.wait-held.home-server.postgres", given: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"home-server\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on home-server\"}", wire: "{\"bound\":\"60m\",\"clock-from\":\"2026-11-11T10:00:00Z\",\"node\":\"home-server\",\"on\":\"the kept build of postgres\",\"owner\":\"node-engine\",\"reason\":\"node away\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"kept-build\",\"words\":\"waiting for the kept build of postgres on home-server\"}", decode: func(b []byte) (any, error) { var v WaitHeldEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.wait-held\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "a hold of the composition ends", subject: "mesh.seat.node-engine.event.wait-ended.ace.postgres", given: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}", wire: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}", + {name: "a hold of the composition ends", subject: "mesh.seat.node-engine.event.wait-ended.home-server.postgres", given: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}", wire: "{\"owner\":\"node-engine\",\"reason\":\"restore point missing\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"compose\"}", decode: func(b []byte) (any, error) { var v WaitEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.wait-ended\\.[^.*> ]+\\.[^.*> ]+$")}, {name: "the finding of a seat list unread, as the engine writes it", subject: "", given: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}", wire: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}", decode: func(b []byte) (any, error) { var v FindingsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, @@ -85,7 +85,7 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v HaveByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "the bus module's have carries the user list written (typed from the engine's values)", subject: "", given: "{\"health\":\"ok\",\"phase\":\"running\",\"user-list\":{\"at\":\"2026-11-05T12:00:00Z\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"follows\":\"grants\",\"handed\":{\"at\":\"2026-11-05T12:00:00Z\",\"box\":{\"ct\":\"c\",\"eph\":\"e\",\"iv\":\"i\",\"tag\":\"t\",\"v\":1},\"from\":\"ace\",\"to\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"users\":3},\"key\":\"-----BEGIN PUBLIC KEY-----\",\"revision\":7,\"source\":\"user-list\",\"users\":3,\"wanted\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\"v\":1}", wire: "{\"health\":\"ok\",\"phase\":\"running\",\"user-list\":{\"at\":\"2026-11-05T12:00:00Z\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"follows\":\"grants\",\"handed\":{\"at\":\"2026-11-05T12:00:00Z\",\"box\":{\"ct\":\"c\",\"eph\":\"e\",\"iv\":\"i\",\"tag\":\"t\",\"v\":1},\"from\":\"ace\",\"to\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"users\":3},\"key\":\"-----BEGIN PUBLIC KEY-----\",\"revision\":7,\"source\":\"user-list\",\"users\":3,\"wanted\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\"v\":1}", decode: func(b []byte) (any, error) { var v HaveByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "a node's own facts with its record and an answer to a candidate (typed from the engine's values)", subject: "", given: "{\"candidates\":{\"p1\":{\"added\":[{\"module\":\"db\",\"unissued\":[\"db.secret\"]}],\"differs\":[{\"bodies\":[{\"id\":\"api.cfg\",\"paths\":[\"/etc/api.conf\"],\"why\":\"content\"}],\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"api\",\"running\":\"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"why\":\"a file differs\"}],\"proposal\":\"p1\",\"refusals\":[{\"module\":\"mail\",\"today\":\"runs\",\"why\":\"a value is missing\"}],\"removed\":[\"old\"],\"revision\":4,\"same\":[\"web\"],\"verdict\":\"differs\"}},\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"composes\":\"own\",\"record\":{\"capabilities\":[\"approval-mark/1\",\"docker\"],\"contract\":3,\"networks\":[{\"link\":\"eth0\",\"network\":\"192.0.2.0/24\"}],\"networks-said\":true,\"node\":\"ace\",\"runner-capabilities\":[\"approval-mark/1\"],\"runner-fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"tunnel-key\":\"k\",\"v\":1},\"v\":1}", wire: "{\"candidates\":{\"p1\":{\"added\":[{\"module\":\"db\",\"unissued\":[\"db.secret\"]}],\"differs\":[{\"bodies\":[{\"id\":\"api.cfg\",\"paths\":[\"/etc/api.conf\"],\"why\":\"content\"}],\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"api\",\"running\":\"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"why\":\"a file differs\"}],\"proposal\":\"p1\",\"refusals\":[{\"module\":\"mail\",\"today\":\"runs\",\"why\":\"a value is missing\"}],\"removed\":[\"old\"],\"revision\":4,\"same\":[\"web\"],\"verdict\":\"differs\"}},\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"composes\":\"own\",\"record\":{\"capabilities\":[\"approval-mark/1\",\"docker\"],\"contract\":3,\"networks\":[{\"link\":\"eth0\",\"network\":\"192.0.2.0/24\"}],\"networks-said\":true,\"node\":\"ace\",\"runner-capabilities\":[\"approval-mark/1\"],\"runner-fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"tunnel-key\":\"k\",\"v\":1},\"v\":1}", + {name: "a node's own facts with its record and an answer to a candidate (typed from the engine's values)", subject: "", given: "{\"candidates\":{\"p1\":{\"added\":[{\"module\":\"db\",\"unissued\":[\"db.secret\"]}],\"differs\":[{\"bodies\":[{\"id\":\"api.cfg\",\"paths\":[\"/etc/api.conf\"],\"why\":\"content\"}],\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"api\",\"running\":\"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"why\":\"a file differs\"}],\"proposal\":\"p1\",\"refusals\":[{\"module\":\"mail\",\"today\":\"runs\",\"why\":\"a value is missing\"}],\"removed\":[\"old\"],\"revision\":4,\"same\":[\"web\"],\"verdict\":\"differs\"}},\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"composes\":\"own\",\"record\":{\"capabilities\":[\"approval-mark/1\",\"docker\"],\"contract\":3,\"networks\":[{\"link\":\"eth0\",\"network\":\"192.0.2.0/24\"}],\"networks-said\":true,\"node\":\"home-server\",\"runner-capabilities\":[\"approval-mark/1\"],\"runner-fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"tunnel-key\":\"k\",\"v\":1},\"v\":1}", wire: "{\"candidates\":{\"p1\":{\"added\":[{\"module\":\"db\",\"unissued\":[\"db.secret\"]}],\"differs\":[{\"bodies\":[{\"id\":\"api.cfg\",\"paths\":[\"/etc/api.conf\"],\"why\":\"content\"}],\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"module\":\"api\",\"running\":\"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"why\":\"a file differs\"}],\"proposal\":\"p1\",\"refusals\":[{\"module\":\"mail\",\"today\":\"runs\",\"why\":\"a value is missing\"}],\"removed\":[\"old\"],\"revision\":4,\"same\":[\"web\"],\"verdict\":\"differs\"}},\"composed\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"composes\":\"own\",\"record\":{\"capabilities\":[\"approval-mark/1\",\"docker\"],\"contract\":3,\"networks\":[{\"link\":\"eth0\",\"network\":\"192.0.2.0/24\"}],\"networks-said\":true,\"node\":\"home-server\",\"runner-capabilities\":[\"approval-mark/1\"],\"runner-fingerprint\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"tunnel-key\":\"k\",\"v\":1},\"v\":1}", decode: func(b []byte) (any, error) { var v HaveByNodeNodeValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a module's data items on a node (typed from the engine's values)", subject: "", given: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"items\":[{\"class\":\"database\",\"covered_by\":\"/var/lib/postgres\",\"item\":\"cluster\",\"measure\":\"dataset\",\"module\":\"postgres\",\"path\":\"/var/lib/postgres\",\"protection\":\"backup\"}],\"module\":\"postgres\",\"node\":\"ace\",\"v\":1}", wire: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"items\":[{\"class\":\"database\",\"covered_by\":\"/var/lib/postgres\",\"item\":\"cluster\",\"measure\":\"dataset\",\"module\":\"postgres\",\"path\":\"/var/lib/postgres\",\"protection\":\"backup\"}],\"module\":\"postgres\",\"node\":\"ace\",\"v\":1}", decode: func(b []byte) (any, error) { var v DataByNodeModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, @@ -111,9 +111,9 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v SeatListAgainAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "process-builds asks one module", subject: "", given: "{\"module\":\"nats\"}", wire: "{\"module\":\"nats\"}", decode: func(b []byte) (any, error) { var v ProcessBuildsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "process-builds answers each process's build", subject: "", given: "{\"node\":\"ace\",\"processes\":[{\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"service\",\"module\":\"nats\",\"process\":\"nats.service\",\"started\":\"2026-11-09T19:00:00Z\"},{\"kind\":\"container\",\"module\":\"route-proxy\",\"process\":\"route-proxy\",\"started\":\"2026-11-01T08:00:00Z\",\"unknown\":\"started before the node-engine handed MESH_FINGERPRINT\"}]}", wire: "{\"node\":\"ace\",\"processes\":[{\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"service\",\"module\":\"nats\",\"process\":\"nats.service\",\"started\":\"2026-11-09T19:00:00Z\"},{\"kind\":\"container\",\"module\":\"route-proxy\",\"process\":\"route-proxy\",\"started\":\"2026-11-01T08:00:00Z\",\"unknown\":\"started before the node-engine handed MESH_FINGERPRINT\"}]}", + {name: "process-builds answers each process's build", subject: "", given: "{\"node\":\"home-server\",\"processes\":[{\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"service\",\"module\":\"nats\",\"process\":\"nats.service\",\"started\":\"2026-11-09T19:00:00Z\"},{\"kind\":\"container\",\"module\":\"route-proxy\",\"process\":\"route-proxy\",\"started\":\"2026-11-01T08:00:00Z\",\"unknown\":\"started before the node-engine handed MESH_FINGERPRINT\"}]}", wire: "{\"node\":\"home-server\",\"processes\":[{\"fingerprint\":\"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"service\",\"module\":\"nats\",\"process\":\"nats.service\",\"started\":\"2026-11-09T19:00:00Z\"},{\"kind\":\"container\",\"module\":\"route-proxy\",\"process\":\"route-proxy\",\"started\":\"2026-11-01T08:00:00Z\",\"unknown\":\"started before the node-engine handed MESH_FINGERPRINT\"}]}", decode: func(b []byte) (any, error) { var v ProcessBuildsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "process-builds answers an error", subject: "", given: "{\"error\":\"the service manager did not answer\",\"node\":\"ace\",\"processes\":[]}", wire: "{\"error\":\"the service manager did not answer\",\"node\":\"ace\",\"processes\":[]}", + {name: "process-builds answers an error", subject: "", given: "{\"error\":\"the service manager did not answer\",\"node\":\"home-server\",\"processes\":[]}", wire: "{\"error\":\"the service manager did not answer\",\"node\":\"home-server\",\"processes\":[]}", decode: func(b []byte) (any, error) { var v ProcessBuildsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "first-view-again names the owner, its state and why", subject: "", given: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}", wire: "{\"owner\":\"mesh-delivery-indexer\",\"state\":\"manifests\",\"why\":\"the picture lags the indexer by hand\"}", decode: func(b []byte) (any, error) { var v FirstViewAgainInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, diff --git a/health-and-repair/go/seats/meshconditions/contract_test.go b/health-and-repair/go/seats/meshconditions/contract_test.go index 7a76c90..a64f2a7 100644 --- a/health-and-repair/go/seats/meshconditions/contract_test.go +++ b/health-and-repair/go/seats/meshconditions/contract_test.go @@ -21,17 +21,17 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v FindingsAskedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-conditions\\.event\\.findings-asked$")}, {name: "the keeper asks every observer", subject: "mesh.seat.mesh-conditions.event.findings-asked", given: "{\"ask\":\"a-20261111t100100\",\"at\":\"2026-11-11T10:01:00Z\",\"by\":\"mesh-conditions\"}", wire: "{\"ask\":\"a-20261111t100100\",\"at\":\"2026-11-11T10:01:00Z\",\"by\":\"mesh-conditions\"}", decode: func(b []byte) (any, error) { var v FindingsAskedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-conditions\\.event\\.findings-asked$")}, - {name: "a condition raised with its owner and what it waits on (design 47 G 685)", subject: "mesh.seat.mesh-conditions.event.condition-raised", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"change\":\"raised\",\"event\":\"condition-raised\",\"key\":\"module.mesh-host.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"owner\":\"forge:operator\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"change\":\"raised\",\"event\":\"condition-raised\",\"key\":\"module.mesh-host.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"owner\":\"forge:operator\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", + {name: "a condition raised with its owner and what it waits on (design 47 G 685)", subject: "mesh.seat.mesh-conditions.event.condition-raised", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"change\":\"raised\",\"event\":\"condition-raised\",\"key\":\"module.mesh-host.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"owner\":\"forge:operator\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"change\":\"raised\",\"event\":\"condition-raised\",\"key\":\"module.mesh-host.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"owner\":\"forge:operator\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", decode: func(b []byte) (any, error) { var v ConditionRaisedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-conditions\\.event\\.condition-raised$")}, - {name: "a condition's owner handed to the operator after two hours (design 47 G 540, A-58)", subject: "mesh.seat.mesh-conditions.event.condition-changed", given: "{\"at\":\"2026-11-11T12:00:00Z\",\"change\":\"owner\",\"event\":\"condition-changed\",\"key\":\"module.mesh-delivery.ace.pipeline-failed\",\"kind\":\"pipeline-failed\",\"owner\":\"operator-channel\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"runner\",\"subject\":{\"id\":\"mesh-delivery.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a head failed its pipeline\",\"waiting-on\":\"operator-channel\",\"was\":\"forge:operator\"}", wire: "{\"at\":\"2026-11-11T12:00:00Z\",\"change\":\"owner\",\"event\":\"condition-changed\",\"key\":\"module.mesh-delivery.ace.pipeline-failed\",\"kind\":\"pipeline-failed\",\"owner\":\"operator-channel\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"runner\",\"subject\":{\"id\":\"mesh-delivery.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a head failed its pipeline\",\"waiting-on\":\"operator-channel\",\"was\":\"forge:operator\"}", + {name: "a condition's owner handed to the operator after two hours (design 47 G 540, A-58)", subject: "mesh.seat.mesh-conditions.event.condition-changed", given: "{\"at\":\"2026-11-11T12:00:00Z\",\"change\":\"owner\",\"event\":\"condition-changed\",\"key\":\"module.mesh-delivery.home-server.pipeline-failed\",\"kind\":\"pipeline-failed\",\"owner\":\"operator-channel\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"runner\",\"subject\":{\"id\":\"mesh-delivery.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a head failed its pipeline\",\"waiting-on\":\"operator-channel\",\"was\":\"forge:operator\"}", wire: "{\"at\":\"2026-11-11T12:00:00Z\",\"change\":\"owner\",\"event\":\"condition-changed\",\"key\":\"module.mesh-delivery.home-server.pipeline-failed\",\"kind\":\"pipeline-failed\",\"owner\":\"operator-channel\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"runner\",\"subject\":{\"id\":\"mesh-delivery.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a head failed its pipeline\",\"waiting-on\":\"operator-channel\",\"was\":\"forge:operator\"}", decode: func(b []byte) (any, error) { var v ConditionChangedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-conditions\\.event\\.condition-changed$")}, {name: "a condition raised from a finding, as the keeper holds it (captured; empty fields left out)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}", decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a condition with an empty evidence trail of headline (captured; empty fields left out)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-30T13:03:46.432309782Z\",\"said\":\"The backup of postgres failed on ace\"}],\"explanation\":\"Needs you: answer whether to back up again or go on without a backup. The backup before applying postgres at 978b5db8cb4e on ace failed: node-backup.now could not be asked: not asked: nothing on this machine answers mesh.seat.node-backup.tool.now.ace.\",\"headline\":\"The backup of postgres failed on ace\",\"key\":\"module.ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f.backup\",\"kind\":\"backup\",\"last-observed\":\"2026-10-30T13:03:46.432309782Z\",\"needs\":\"answer whether to back up again or go on without a backup\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.069818178Z\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"assigner.backup\",\"subject\":{\"id\":\"ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"The backup of postgres failed on ace\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-30T13:03:46.432309782Z\",\"said\":\"The backup of postgres failed on ace\"}],\"explanation\":\"Needs you: answer whether to back up again or go on without a backup. The backup before applying postgres at 978b5db8cb4e on ace failed: node-backup.now could not be asked: not asked: nothing on this machine answers mesh.seat.node-backup.tool.now.ace.\",\"headline\":\"The backup of postgres failed on ace\",\"key\":\"module.ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f.backup\",\"kind\":\"backup\",\"last-observed\":\"2026-10-30T13:03:46.432309782Z\",\"needs\":\"answer whether to back up again or go on without a backup\",\"observations\":1,\"raised\":\"2026-10-31T08:19:57.069818178Z\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"assigner.backup\",\"subject\":{\"id\":\"ace.postgres.978b5db8cb4ef746ce68d82cc616d1439a53266d335e60f368dc44c4cb25610f\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"The backup of postgres failed on ace\"}", decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "a condition carrying its finding's owner, a session, and what it waits on, a seat (design 47 G 685)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator@workstation\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"node-engine\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator@workstation\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"node-engine\"}", + {name: "a condition carrying its finding's owner, a session, and what it waits on, a seat (design 47 G 685)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on home-server, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"key\":\"module.mesh-build-node.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator@workstation\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on home-server is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"node-engine\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on home-server, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"key\":\"module.mesh-build-node.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator@workstation\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on home-server is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"node-engine\"}", decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, - {name: "a condition owned by a commit's author, handed to operator-channel when the keeper hands it over (design 47 G 685, 540)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator-channel\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"forge:operator\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"key\":\"module.mesh-build-node.ace.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator-channel\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on ace is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"forge:operator\"}", + {name: "a condition owned by a commit's author, handed to operator-channel when the keeper hands it over (design 47 G 685, 540)", subject: "", given: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on home-server, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"key\":\"module.mesh-build-node.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator-channel\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on home-server is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"forge:operator\"}", wire: "{\"count\":1,\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on home-server, with another owner or mode than mesh-build-node declares.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"key\":\"module.mesh-build-node.home-server.directory-used-as-found\",\"kind\":\"directory-used-as-found\",\"last-observed\":\"2026-10-26T20:19:41.474823121Z\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observations\":1,\"owner\":\"operator-channel\",\"raised\":\"2026-10-31T08:19:57.076843727Z\",\"resolved\":\"mesh-build-node's directory on home-server is the mesh's\",\"resolver\":\"self\",\"severity\":\"warning\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh\",\"waiting-on\":\"forge:operator\"}", decode: func(b []byte) (any, error) { var v ConditionsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a finding of the bus scope with its zero time (in the finding layout (design 47 G 660))", subject: "", given: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"id\":\"EVENTS.controller\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"bus\",\"severity\":\"warning\",\"since\":\"0001-01-01T00:00:00Z\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\"}", wire: "{\"at\":\"0001-01-01T00:00:00Z\",\"evidence\":[{\"at\":\"0001-01-01T00:00:00Z\",\"said\":\"1 held for EVENTS.controller (counted again every 5m0s while any is held, so one delivered again or dropped ends this within it)\"}],\"id\":\"EVENTS.controller\",\"kind\":\"max-deliveries\",\"needs\":\"deliver them again or drop them, from the mesh MCP server.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"bus\",\"severity\":\"warning\",\"since\":\"0001-01-01T00:00:00Z\",\"source\":\"S9\",\"subject\":{\"id\":\"EVENTS.controller\",\"scope\":\"bus\"},\"summary\":\"the controller's consumer on EVENTS gave up on a message; it is kept in DEAD_LETTERS until delivered again or dropped, with why\"}", decode: func(b []byte) (any, error) { @@ -39,19 +39,19 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a finding of the module scope with its machine (in the finding layout (design 47 G 660))", subject: "", given: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"id\":\"mesh-build-node.ace\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\"}", wire: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on ace uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on ace\",\"id\":\"mesh-build-node.ace\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\"}", + {name: "a finding of the module scope with its machine (in the finding layout (design 47 G 660))", subject: "", given: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"id\":\"mesh-build-node.home-server\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found\"}", wire: "{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"evidence\":[{\"at\":\"2026-10-26T20:19:41.474823121Z\",\"said\":\"mesh-build-node on home-server uses mesh-build-node.cache as found: owned by 10005:10005, mode 700, as found\"}],\"explanation\":\"Needs you: hand the directory over from the control-node, as the operator.\",\"headline\":\"mesh-build-node waits for a directory on home-server\",\"id\":\"mesh-build-node.home-server\",\"kind\":\"directory-used-as-found\",\"needs\":\"hand the directory over from the control-node, as the operator; the details name it and the line to type.\",\"observer\":\"mesh-delivery-watchman\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-10-26T20:19:41.474823121Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-build-node.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on home-server uses mesh-build-node.cache as found\"}", decode: func(b []byte) (any, error) { var v FindingsByScopeIDKindValue err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a finding owned by a session and waiting on a seat (design 47 G 678)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"operator@workstation\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"operator@workstation\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", + {name: "a finding owned by a session and waiting on a seat (design 47 G 678)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.home-server\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"operator@workstation\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.home-server\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"operator@workstation\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"node-engine\"}", decode: func(b []byte) (any, error) { var v FindingsByScopeIDKindValue err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a finding owned by a seat and waiting on a commit's author (design 47 G 678, 540)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:operator\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.ace\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:operator\"}", + {name: "a finding owned by a seat and waiting on a commit's author (design 47 G 678, 540)", subject: "", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.home-server\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:operator\"}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"id\":\"mesh-host.home-server\",\"kind\":\"directory-used-as-found\",\"observer\":\"mesh-delivery-watchman\",\"owner\":\"node-engine\",\"scope\":\"module\",\"severity\":\"warning\",\"since\":\"2026-11-11T10:00:00Z\",\"source\":\"health-report\",\"subject\":{\"id\":\"mesh-host.home-server\",\"machine\":\"home-server\",\"scope\":\"module\"},\"summary\":\"a directory waits for a hand\",\"waiting-on\":\"forge:operator\"}", decode: func(b []byte) (any, error) { var v FindingsByScopeIDKindValue err := json.Unmarshal(b, &v) diff --git a/health-and-repair/go/seats/meshhealers/contract_test.go b/health-and-repair/go/seats/meshhealers/contract_test.go index c0c3e4c..3b58c3c 100644 --- a/health-and-repair/go/seats/meshhealers/contract_test.go +++ b/health-and-repair/go/seats/meshhealers/contract_test.go @@ -17,9 +17,9 @@ func TestFixtures(t *testing.T) { decode func([]byte) (any, error) subjectIs *regexp.Regexp }{ - {name: "an attempt begun, said with its end (design 47 G 665)", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.ace.directory-used-as-found\",\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}", wire: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.ace.directory-used-as-found\",\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}", + {name: "an attempt begun, said with its end (design 47 G 665)", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.home-server.directory-used-as-found\",\"condition\":\"module.mesh-host.home-server.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}", wire: "{\"act\":\"hand-over\",\"begun\":\"2026-11-11T10:00:00Z\",\"budget-key\":\"module.mesh-host.home-server.directory-used-as-found\",\"condition\":\"module.mesh-host.home-server.directory-used-as-found\",\"healer\":\"directory\",\"kind\":\"directory-used-as-found\",\"outcome\":\"acting\",\"until\":\"2026-11-11T12:00:00Z\"}", decode: func(b []byte) (any, error) { var v AttemptChangedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-healers\\.event\\.attempt-changed\\.[^.*> ]+\\.[^.*> ]+$")}, - {name: "an attempt ended by its healer", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"removed\":true}", wire: "{\"condition\":\"module.mesh-host.ace.directory-used-as-found\",\"removed\":true}", + {name: "an attempt ended by its healer", subject: "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", given: "{\"condition\":\"module.mesh-host.home-server.directory-used-as-found\",\"removed\":true}", wire: "{\"condition\":\"module.mesh-host.home-server.directory-used-as-found\",\"removed\":true}", decode: func(b []byte) (any, error) { var v AttemptChangedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-healers\\.event\\.attempt-changed\\.[^.*> ]+\\.[^.*> ]+$")}, {name: "an act a healer would have made, kept from the controller's time (captured)", subject: "", given: "{\"act\":\"assert the module's buckets, logs and streams again through the grants part (grants_assert)\",\"at\":\"2026-11-02T08:15:56.49507336Z\",\"condition\":\"bus.EVENTS.4fs9xzyDY7CD2Cf1wVFYDK_1.consumer-lost\",\"healer\":\"H3\",\"kind\":\"consumer-lost\",\"why\":\"the controller's lease names a controller (or none is written, so one may take it): every act is written as would\"}", wire: "{\"act\":\"assert the module's buckets, logs and streams again through the grants part (grants_assert)\",\"at\":\"2026-11-02T08:15:56.49507336Z\",\"condition\":\"bus.EVENTS.4fs9xzyDY7CD2Cf1wVFYDK_1.consumer-lost\",\"healer\":\"H3\",\"kind\":\"consumer-lost\",\"why\":\"the controller's lease names a controller (or none is written, so one may take it): every act is written as would\"}", decode: func(b []byte) (any, error) { var v WouldByWouldValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, diff --git a/health-and-repair/go/seats/meshwatchdogs/contract_test.go b/health-and-repair/go/seats/meshwatchdogs/contract_test.go index 7b3018b..782d864 100644 --- a/health-and-repair/go/seats/meshwatchdogs/contract_test.go +++ b/health-and-repair/go/seats/meshwatchdogs/contract_test.go @@ -17,9 +17,9 @@ func TestFixtures(t *testing.T) { decode func([]byte) (any, error) subjectIs *regexp.Regexp }{ - {name: "a machine's presence expired", subject: "mesh.seat.mesh-watchdogs.event.machine-away.g14", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"last-heard\":\"2026-11-11T09:58:00Z\",\"machine\":\"g14\",\"v\":1}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"last-heard\":\"2026-11-11T09:58:00Z\",\"machine\":\"g14\",\"v\":1}", + {name: "a machine's presence expired", subject: "mesh.seat.mesh-watchdogs.event.machine-away.laptop", given: "{\"at\":\"2026-11-11T10:00:00Z\",\"last-heard\":\"2026-11-11T09:58:00Z\",\"machine\":\"laptop\",\"v\":1}", wire: "{\"at\":\"2026-11-11T10:00:00Z\",\"last-heard\":\"2026-11-11T09:58:00Z\",\"machine\":\"laptop\",\"v\":1}", decode: func(b []byte) (any, error) { var v MachineAwayEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-watchdogs\\.event\\.machine-away\\.[^.*> ]+$")}, - {name: "a machine heard again", subject: "mesh.seat.mesh-watchdogs.event.machine-back.g14", given: "{\"at\":\"2026-11-11T10:30:00Z\",\"last-heard\":\"2026-11-11T10:30:00Z\",\"machine\":\"g14\",\"v\":1}", wire: "{\"at\":\"2026-11-11T10:30:00Z\",\"last-heard\":\"2026-11-11T10:30:00Z\",\"machine\":\"g14\",\"v\":1}", + {name: "a machine heard again", subject: "mesh.seat.mesh-watchdogs.event.machine-back.laptop", given: "{\"at\":\"2026-11-11T10:30:00Z\",\"last-heard\":\"2026-11-11T10:30:00Z\",\"machine\":\"laptop\",\"v\":1}", wire: "{\"at\":\"2026-11-11T10:30:00Z\",\"last-heard\":\"2026-11-11T10:30:00Z\",\"machine\":\"laptop\",\"v\":1}", decode: func(b []byte) (any, error) { var v MachineBackEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-watchdogs\\.event\\.machine-back\\.[^.*> ]+$")}, {name: "a node's agent online (captured)", subject: "", given: "{\"online\":true,\"v\":1}", wire: "{\"online\":true,\"v\":1}", decode: func(b []byte) (any, error) { var v PresenceByNodeValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, diff --git a/health-and-repair/seats/mesh-conditions/fixtures/events.json b/health-and-repair/seats/mesh-conditions/fixtures/events.json index cc0ad70..3bff1d2 100644 --- a/health-and-repair/seats/mesh-conditions/fixtures/events.json +++ b/health-and-repair/seats/mesh-conditions/fixtures/events.json @@ -25,12 +25,12 @@ "of": "event:condition-raised", "subject": "mesh.seat.mesh-conditions.event.condition-raised", "given": { - "key": "module.mesh-host.ace.directory-used-as-found", + "key": "module.mesh-host.home-server.directory-used-as-found", "kind": "directory-used-as-found", "subject": { "scope": "module", - "id": "mesh-host.ace", - "machine": "ace" + "id": "mesh-host.home-server", + "machine": "home-server" }, "severity": "warning", "summary": "a directory waits for a hand", @@ -48,12 +48,12 @@ "of": "event:condition-changed", "subject": "mesh.seat.mesh-conditions.event.condition-changed", "given": { - "key": "module.mesh-delivery.ace.pipeline-failed", + "key": "module.mesh-delivery.home-server.pipeline-failed", "kind": "pipeline-failed", "subject": { "scope": "module", - "id": "mesh-delivery.ace", - "machine": "ace" + "id": "mesh-delivery.home-server", + "machine": "home-server" }, "severity": "warning", "summary": "a head failed its pipeline", diff --git a/health-and-repair/seats/mesh-conditions/fixtures/states.json b/health-and-repair/seats/mesh-conditions/fixtures/states.json index f7566d9..5f78e8e 100644 --- a/health-and-repair/seats/mesh-conditions/fixtures/states.json +++ b/health-and-repair/seats/mesh-conditions/fixtures/states.json @@ -7,7 +7,7 @@ "evidence": [ { "at": "2026-10-26T20:19:41.474823121Z", - "said": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" + "said": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" } ], "explanation": "Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.", @@ -27,7 +27,7 @@ "machine": "ace", "scope": "module" }, - "summary": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" + "summary": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" } }, { @@ -68,7 +68,7 @@ "evidence": [ { "at": "2026-10-26T20:19:41.474823121Z", - "said": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" + "said": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" } ], "explanation": "Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.", @@ -88,7 +88,7 @@ "machine": "ace", "scope": "module" }, - "summary": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh", + "summary": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh", "owner": "operator@workstation", "waiting-on": "node-engine" } @@ -101,7 +101,7 @@ "evidence": [ { "at": "2026-10-26T20:19:41.474823121Z", - "said": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" + "said": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh" } ], "explanation": "Needs you: hand the directory over from the control-node, as the operator; the details name it and the line to type. A directory of mesh-build-node was already on ace, with another owner or mode than mesh-build-node declares.", @@ -121,7 +121,7 @@ "machine": "ace", "scope": "module" }, - "summary": "mesh-build-node on ace uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `ace/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh", + "summary": "mesh-build-node on home-server uses mesh-build-node.cache as found: used as found: owned by 10005:10005, mode 700, as found; mesh-build-node, mode 0711 was declared and not given it — `home-server/node-engine.hand-over` with the directory's path, then your yes on your channel, with its path, hands it to the mesh", "owner": "operator-channel", "waiting-on": "forge:operator" } @@ -177,7 +177,7 @@ ], "at": "2026-10-26T20:19:41.474823121Z", "scope": "module", - "id": "mesh-build-node.ace", + "id": "mesh-build-node.home-server", "since": "2026-10-26T20:19:41.474823121Z" } }, @@ -188,8 +188,8 @@ "kind": "directory-used-as-found", "subject": { "scope": "module", - "id": "mesh-host.ace", - "machine": "ace" + "id": "mesh-host.home-server", + "machine": "home-server" }, "severity": "warning", "summary": "a directory waits for a hand", @@ -199,7 +199,7 @@ "owner": "operator@workstation", "waiting-on": "node-engine", "scope": "module", - "id": "mesh-host.ace", + "id": "mesh-host.home-server", "since": "2026-11-11T10:00:00Z" } }, @@ -210,8 +210,8 @@ "kind": "directory-used-as-found", "subject": { "scope": "module", - "id": "mesh-host.ace", - "machine": "ace" + "id": "mesh-host.home-server", + "machine": "home-server" }, "severity": "warning", "summary": "a directory waits for a hand", @@ -221,7 +221,7 @@ "owner": "node-engine", "waiting-on": "forge:operator", "scope": "module", - "id": "mesh-host.ace", + "id": "mesh-host.home-server", "since": "2026-11-11T10:00:00Z" } } diff --git a/health-and-repair/seats/mesh-healers/fixtures/events.json b/health-and-repair/seats/mesh-healers/fixtures/events.json index abe6d7f..d9ad3e8 100644 --- a/health-and-repair/seats/mesh-healers/fixtures/events.json +++ b/health-and-repair/seats/mesh-healers/fixtures/events.json @@ -5,9 +5,9 @@ "subject": "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", "given": { "healer": "directory", - "condition": "module.mesh-host.ace.directory-used-as-found", + "condition": "module.mesh-host.home-server.directory-used-as-found", "kind": "directory-used-as-found", - "budget-key": "module.mesh-host.ace.directory-used-as-found", + "budget-key": "module.mesh-host.home-server.directory-used-as-found", "act": "hand-over", "outcome": "acting", "begun": "2026-11-11T10:00:00Z", @@ -19,7 +19,7 @@ "of": "event:attempt-changed", "subject": "mesh.seat.mesh-healers.event.attempt-changed.c-1.a-1", "given": { - "condition": "module.mesh-host.ace.directory-used-as-found", + "condition": "module.mesh-host.home-server.directory-used-as-found", "removed": true } } diff --git a/health-and-repair/seats/mesh-watchdogs/fixtures/events.json b/health-and-repair/seats/mesh-watchdogs/fixtures/events.json index 2c50557..2316bb8 100644 --- a/health-and-repair/seats/mesh-watchdogs/fixtures/events.json +++ b/health-and-repair/seats/mesh-watchdogs/fixtures/events.json @@ -2,10 +2,10 @@ { "name": "a machine's presence expired", "of": "event:machine-away", - "subject": "mesh.seat.mesh-watchdogs.event.machine-away.g14", + "subject": "mesh.seat.mesh-watchdogs.event.machine-away.laptop", "given": { "v": 1, - "machine": "g14", + "machine": "laptop", "at": "2026-11-11T10:00:00Z", "last-heard": "2026-11-11T09:58:00Z" } @@ -13,10 +13,10 @@ { "name": "a machine heard again", "of": "event:machine-back", - "subject": "mesh.seat.mesh-watchdogs.event.machine-back.g14", + "subject": "mesh.seat.mesh-watchdogs.event.machine-back.laptop", "given": { "v": 1, - "machine": "g14", + "machine": "laptop", "at": "2026-11-11T10:30:00Z", "last-heard": "2026-11-11T10:30:00Z" } diff --git a/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go b/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go index fc61d8b..e4063c2 100644 --- a/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go +++ b/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go @@ -32,27 +32,26 @@ func TestATokenLifeAndTheRedeemProofAreAsTheHolderMakesThem(t *testing.T) { if got := (RedeemAnswer{Node: "g14", Signature: "x"}).SignedBytes(); string(got) == "" || string(got) == "x" { t.Errorf("SignedBytes = %s", got) } -} - -// TestTheRedeemAnswerIsTheContractsWire: the client's answer reads into the contract's type with no field dropped, and -// the contract's reads back as the client's wire. -func TestTheRedeemAnswerIsTheContractsWire(t *testing.T) { - mine := RedeemAnswer{Node: "g14", Login: "g14-login", Account: "YQ==", Certificate: "pem", Signer: "dg==", Signature: "cw=="} - wire, _ := json.Marshal(mine) - var theirs seat.RedeemAnswer - d := json.NewDecoder(bytes.NewReader(wire)) - d.DisallowUnknownFields() - if err := d.Decode(&theirs); err != nil { - t.Fatal(err) - } - if theirs.Node != "g14" || theirs.Signer != "dg==" || theirs.Signature != "cw==" { - t.Errorf("the contract reads %+v", theirs) - } - back, _ := json.Marshal(theirs) - var a, b map[string]string - _ = json.Unmarshal(wire, &a) - _ = json.Unmarshal(back, &b) - if len(a) != 6 || !reflect.DeepEqual(a, b) { - t.Errorf("the contract reads %s back as %s", wire, back) - } + // TestTheRedeemAnswerIsTheContractsWire: the client's answer reads into the contract's type with no field dropped, and + // the contract's reads back as the client's wire. + t.Run("TheRedeemAnswerIsTheContractsWire", func(t *testing.T) { + mine := RedeemAnswer{Node: "g14", Login: "g14-login", Account: "YQ==", Certificate: "pem", Signer: "dg==", Signature: "cw=="} + wire, _ := json.Marshal(mine) + var theirs seat.RedeemAnswer + d := json.NewDecoder(bytes.NewReader(wire)) + d.DisallowUnknownFields() + if err := d.Decode(&theirs); err != nil { + t.Fatal(err) + } + if theirs.Node != "g14" || theirs.Signer != "dg==" || theirs.Signature != "cw==" { + t.Errorf("the contract reads %+v", theirs) + } + back, _ := json.Marshal(theirs) + var a, b map[string]string + _ = json.Unmarshal(wire, &a) + _ = json.Unmarshal(back, &b) + if len(a) != 6 || !reflect.DeepEqual(a, b) { + t.Errorf("the contract reads %s back as %s", wire, back) + } + }) } diff --git a/operator-and-conversation/go/clients/operatorchannelclient/client_test.go b/operator-and-conversation/go/clients/operatorchannelclient/client_test.go index 7fd0ce8..92732b9 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/client_test.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/client_test.go @@ -2,6 +2,7 @@ package operatorchannelclient import ( "encoding/json" + "errors" "strings" "testing" "time" @@ -88,6 +89,51 @@ func TestAnAskIsKeptThenPublishedUnderTheModulesOwnName(t *testing.T) { if err := c.Ask(bad); err == nil { t.Error("an ask the contract refuses was published") } + t.Run("AKeyPrefixIsPutBeforeEveryKeyOfTheClientsOwnStateAndNotBeforeWhere", func(t *testing.T) { + for _, tc := range []struct{ prefix, key string }{{"n1.", "n1.ask.d-1"}, {"", "ask.d-1"}} { + c, _ := client() + store := c.State.(*mem) + c.KeyPrefix = tc.prefix + var asked string + c.Record = func(k string) (*stdio.StateEntry, error) { asked = k; return nil, nil } + if err := c.Ask(askedHeld()); err != nil { + t.Fatal(err) + } + if _, ok := store.vals[tc.key]; len(store.vals) != 1 || !ok { + t.Fatalf("prefix %q: keys written %v, want %s", tc.prefix, store.vals, tc.key) + } + // Design 46 7-approval-mark §13: the act and the call's arguments kept beside the ask stay as Take marks it. + e, _ := store.Get(tc.key) + var k Kept + _ = json.Unmarshal(e.Value, &k) + k.Act, k.Arguments = releaseHQ, json.RawMessage(`{"delivery":"hq"}`) + if _, err := store.Update(tc.key, k, e.Revision); err != nil { + t.Fatal(err) + } + if h, err := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { return nil }); h != Acted || err != nil { + t.Fatalf("prefix %q: the warrant was not read from %s: %s %v", tc.prefix, tc.key, h, err) + } + raw := string(store.vals[tc.key]) + if !strings.Contains(raw, `"act":{"delivery":"hq","verb":"release"}`) || !strings.Contains(raw, `"arguments":{"delivery":"hq"}`) || strings.Contains(raw, `"recorded"`) { + t.Errorf("prefix %q: the kept ask after Take is %s", tc.prefix, raw) + } + if _, _, err := c.Where("an-asker.d-1"); err != nil || asked != "an-asker.d-1" { + t.Errorf("prefix %q: Where read %q", tc.prefix, asked) + } + } + }) + // Design 47 G §700, A-91: Where reads the router's record as the contract's typed value, its warrant included. + t.Run("WhereReadsTheRecordsWarrantTyped", func(t *testing.T) { + c, _ := client() + rec := seat.AsksByAskerIDValue{Key: "an-asker.d-1", Asker: "an-asker", State: "chosen", + Warrant: &seat.Warrant{Ask: "d-1", Asker: "an-asker", Outcome: seat.WarrantOutcomeChosen, Option: "ok"}} + raw, _ := json.Marshal(rec) + c.Record = func(string) (*stdio.StateEntry, error) { return &stdio.StateEntry{Value: raw}, nil } + state, w, err := c.Where("d-1") + if err != nil || state != "chosen" || w == nil || w.Option != "ok" { + t.Fatalf("Where = %q, %+v, %v; want chosen and the record's warrant", state, w, err) + } + }) } func TestAWarrantIsActedOnOnceAndOnlyForItsOwnAsk(t *testing.T) { @@ -126,6 +172,109 @@ func TestAWarrantIsActedOnOnceAndOnlyForItsOwnAsk(t *testing.T) { if acted != 1 { t.Errorf("acted %d times", acted) } + // The gate's channel (design 47 G lead answers A-219, A-228): the gate's ask raised to the operator at the level approve, + // the router's record read in the gate's form with its warrant whole, and a warrant claimed once, only for the act its + // option binds. + t.Run("TheGateChannelTranslatesTheGatesForms", func(t *testing.T) { + st := &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} + records := map[string]seat.AsksByAskerIDValue{} + g := &GateChannel{State: st, KeyPrefix: "n1.", Now: func() time.Time { return now }, + Publish: func(module, subject string, body any, id string) error { + if subject != seat.AcceptSubjectAsk(module) { + t.Fatalf("published on %s", subject) + } + return nil + }, + ReadRecord: func(module, id string) (*stdio.StateEntry, error) { + rec, ok := records[module+"."+id] + if !ok { + return nil, nil + } + raw, _ := json.Marshal(rec) + return &stdio.StateEntry{Value: raw}, nil + }} + digest, _ := ActDigest(releaseHQ) + a := approval.Ask{ID: "a1", About: digest, Headline: "Run wipe?", Explanation: "Wipes.", Expires: FormatTime(now.Add(time.Hour)), + OnExpiry: "nothing runs", Options: []approval.Option{{ID: "approve", Label: "Approve", Does: "runs", Binds: digest}}} + if err := g.Ask("shell", a); err != nil { + t.Fatal(err) + } + var k Kept + if e, _ := st.Get("n1.ask.a1"); e == nil || json.Unmarshal(e.Value, &k) != nil || k.Ask.Who != Operator || k.Ask.Options[0].Level != Approve { + t.Fatalf("kept %+v", k) + } + if rec, err := g.Record("shell", "a1"); rec != nil || err != nil { + t.Fatalf("a record not yet there: %+v %v", rec, err) + } + w := seat.Warrant{Ask: "a1", Asker: "shell", Outcome: OutcomeChosen, Option: "approve", Level: Approve, At: FormatTime(now), + AskDigest: Digest(k.Ask), Channel: "telegram", By: &seat.Person{Who: Operator, Kind: "telegram"}} + records["shell.a1"] = seat.AsksByAskerIDValue{Asker: "shell", State: "chosen", Opened: FormatTime(now), Warrant: &w} + rec, err := g.Record("shell", "a1") + if err != nil || rec == nil || rec.State != "chosen" || rec.Warrant == nil || rec.Warrant.Channel != "telegram" || rec.Warrant.Option != "approve" { + t.Fatalf("record %+v %v", rec, err) + } + if h, err := g.Claim("shell", *rec.Warrant, map[string]string{"verb": "other"}, func() { t.Fatal("ran another act") }); h != approval.Claimed || err == nil { + t.Fatalf("another act: %s %v", h, err) + } + delete(st.vals, "n1.ask.a1") + _ = g.Ask("shell", a) + runs := 0 + for i := 0; i < 2; i++ { + h, err := g.Claim("shell", *rec.Warrant, releaseHQ, func() { runs++ }) + if want := []approval.Claim{approval.Claimed, approval.ClaimDone}[i]; h != want || err != nil { + t.Fatalf("claim %d: %s %v", i, h, err) + } + } + if runs != 1 || g.Decided("shell") != seat.SubjectDecided("shell") { + t.Fatalf("runs %d", runs) + } + }) + // The gate's channel over a process connection (design 47 G lead answers A-272, A-273): an ask and its cancel go out as + // accepted kinds of the seat operator-channel with the ask's id as the message id, the router's record is the get of + // `.` in the state asks of the seat's holder, and nothing else is reached: another subject, or a seat with no + // holder, is refused. + t.Run("TheGateChannelOverAConnectionAsksAndReadsOnlyThroughTheSeat", func(t *testing.T) { + type sent struct{ seat, kind, id string } + var out []sent + accept := func(s, kind string, _ any, opts ...stdio.EmitOptions) error { + out = append(out, sent{s, kind, opts[0].MessageID}) + return nil + } + raw, _ := json.Marshal(seat.AsksByAskerIDValue{Asker: "shell", State: "open", Opened: FormatTime(now)}) + vals := map[string]json.RawMessage{"messenger_holder.asks|shell.a1": raw, "other.asks|shell.a1": json.RawMessage(`{"state":"wrong"}`)} + read := func(r string) asksRead { return readOf{state: r, vals: vals} } + holder := func() (string, error) { return "messenger_holder", nil } + st := &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} + g := gateChannelOver(accept, read, st, "n1.", holder) + g.Now = func() time.Time { return now } + + digest, _ := ActDigest(releaseHQ) + a := approval.Ask{ID: "a1", About: digest, Headline: "Run wipe?", Explanation: "Wipes.", Expires: FormatTime(now.Add(time.Hour)), + OnExpiry: "nothing runs", Options: []approval.Option{{ID: "approve", Label: "Approve", Does: "runs", Binds: digest}}} + if err := g.Ask("shell", a); err != nil { + t.Fatal(err) + } + if err := g.Cancel("shell", "a1"); err != nil { + t.Fatal(err) + } + if len(out) != 2 || out[0] != (sent{seat.Seat, seat.AcceptAsk, "a1"}) || out[1] != (sent{seat.Seat, seat.AcceptCancel, "cancel.a1"}) { + t.Fatalf("sent %+v, want the ask and its cancel on the seat's accepts", out) + } + if rec, err := g.Record("shell", "a1"); err != nil || rec == nil || rec.State != "open" { + t.Fatalf("record %+v %v, want the one in the holder's state asks", rec, err) + } + if rec, err := g.Record("shell", "zz"); err != nil || rec != nil { + t.Fatalf("a record not there: %+v %v", rec, err) + } + if err := g.Publish("shell", "mesh.seat.operator-channel.accept.ask.other", nil, "x"); err == nil { + t.Fatal("an accept subject naming another caller was sent") + } + for _, c := range []func() (string, error){func() (string, error) { return "", nil }, func() (string, error) { return "", errors.New("unread") }} { + if _, err := gateChannelOver(accept, read, st, "n1.", c).Record("shell", "a1"); err == nil { + t.Fatal("a record was read with no holder of the seat") + } + } + }) } func TestAnAskEndedWithoutAChoiceIsRecordedAndNothingDone(t *testing.T) { @@ -139,123 +288,17 @@ func TestAnAskEndedWithoutAChoiceIsRecordedAndNothingDone(t *testing.T) { if h, _ := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { t.Fatal("acted after it ended"); return nil }); h != Done { t.Errorf("%s", h) } -} - -func TestAnAskTheRouterRefusedEndsTheRequestWithItsWords(t *testing.T) { - c, _ := client() - _ = c.Ask(askedHeld()) - w := askedWarrant() - w.Outcome, w.Option, w.By, w.Words = OutcomeRefused, "", nil, "its expiry passes the bound of its level" - h, err := c.Take(w, func(seat.Option, seat.Warrant) error { t.Fatal("acted"); return nil }) - if h != Refused || err == nil || !strings.Contains(err.Error(), w.Words) { - t.Fatalf("%s %v, want refused with the router's words", h, err) - } - if h, _ := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { t.Fatal("acted after it ended"); return nil }); h != Done { - t.Errorf("the refused ask was not closed: %s", h) - } -} - -func TestAKeyPrefixIsPutBeforeEveryKeyOfTheClientsOwnStateAndNotBeforeWhere(t *testing.T) { - for _, tc := range []struct{ prefix, key string }{{"n1.", "n1.ask.d-1"}, {"", "ask.d-1"}} { + t.Run("AnAskTheRouterRefusedEndsTheRequestWithItsWords", func(t *testing.T) { c, _ := client() - store := c.State.(*mem) - c.KeyPrefix = tc.prefix - var asked string - c.Record = func(k string) (*stdio.StateEntry, error) { asked = k; return nil, nil } - if err := c.Ask(askedHeld()); err != nil { - t.Fatal(err) + _ = c.Ask(askedHeld()) + w := askedWarrant() + w.Outcome, w.Option, w.By, w.Words = OutcomeRefused, "", nil, "its expiry passes the bound of its level" + h, err := c.Take(w, func(seat.Option, seat.Warrant) error { t.Fatal("acted"); return nil }) + if h != Refused || err == nil || !strings.Contains(err.Error(), w.Words) { + t.Fatalf("%s %v, want refused with the router's words", h, err) } - if _, ok := store.vals[tc.key]; len(store.vals) != 1 || !ok { - t.Fatalf("prefix %q: keys written %v, want %s", tc.prefix, store.vals, tc.key) + if h, _ := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { t.Fatal("acted after it ended"); return nil }); h != Done { + t.Errorf("the refused ask was not closed: %s", h) } - // Design 46 7-approval-mark §13: the act and the call's arguments kept beside the ask stay as Take marks it. - e, _ := store.Get(tc.key) - var k Kept - _ = json.Unmarshal(e.Value, &k) - k.Act, k.Arguments = releaseHQ, json.RawMessage(`{"delivery":"hq"}`) - if _, err := store.Update(tc.key, k, e.Revision); err != nil { - t.Fatal(err) - } - if h, err := c.Take(askedWarrant(), func(seat.Option, seat.Warrant) error { return nil }); h != Acted || err != nil { - t.Fatalf("prefix %q: the warrant was not read from %s: %s %v", tc.prefix, tc.key, h, err) - } - raw := string(store.vals[tc.key]) - if !strings.Contains(raw, `"act":{"delivery":"hq","verb":"release"}`) || !strings.Contains(raw, `"arguments":{"delivery":"hq"}`) || strings.Contains(raw, `"recorded"`) { - t.Errorf("prefix %q: the kept ask after Take is %s", tc.prefix, raw) - } - if _, _, err := c.Where("an-asker.d-1"); err != nil || asked != "an-asker.d-1" { - t.Errorf("prefix %q: Where read %q", tc.prefix, asked) - } - } -} - -// Design 47 G §700, A-91: Where reads the router's record as the contract's typed value, its warrant included. -func TestWhereReadsTheRecordsWarrantTyped(t *testing.T) { - c, _ := client() - rec := seat.AsksByAskerIDValue{Key: "an-asker.d-1", Asker: "an-asker", State: "chosen", - Warrant: &seat.Warrant{Ask: "d-1", Asker: "an-asker", Outcome: seat.WarrantOutcomeChosen, Option: "ok"}} - raw, _ := json.Marshal(rec) - c.Record = func(string) (*stdio.StateEntry, error) { return &stdio.StateEntry{Value: raw}, nil } - state, w, err := c.Where("d-1") - if err != nil || state != "chosen" || w == nil || w.Option != "ok" { - t.Fatalf("Where = %q, %+v, %v; want chosen and the record's warrant", state, w, err) - } -} - -// The gate's channel (design 47 G lead answers A-219, A-228): the gate's ask raised to the operator at the level approve, -// the router's record read in the gate's form with its warrant whole, and a warrant claimed once, only for the act its -// option binds. -func TestTheGateChannelTranslatesTheGatesForms(t *testing.T) { - st := &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} - records := map[string]seat.AsksByAskerIDValue{} - g := &GateChannel{State: st, KeyPrefix: "n1.", Now: func() time.Time { return now }, - Publish: func(module, subject string, body any, id string) error { - if subject != seat.AcceptSubjectAsk(module) { - t.Fatalf("published on %s", subject) - } - return nil - }, - ReadRecord: func(module, id string) (*stdio.StateEntry, error) { - rec, ok := records[module+"."+id] - if !ok { - return nil, nil - } - raw, _ := json.Marshal(rec) - return &stdio.StateEntry{Value: raw}, nil - }} - digest, _ := ActDigest(releaseHQ) - a := approval.Ask{ID: "a1", About: digest, Headline: "Run wipe?", Explanation: "Wipes.", Expires: FormatTime(now.Add(time.Hour)), - OnExpiry: "nothing runs", Options: []approval.Option{{ID: "approve", Label: "Approve", Does: "runs", Binds: digest}}} - if err := g.Ask("shell", a); err != nil { - t.Fatal(err) - } - var k Kept - if e, _ := st.Get("n1.ask.a1"); e == nil || json.Unmarshal(e.Value, &k) != nil || k.Ask.Who != Operator || k.Ask.Options[0].Level != Approve { - t.Fatalf("kept %+v", k) - } - if rec, err := g.Record("shell", "a1"); rec != nil || err != nil { - t.Fatalf("a record not yet there: %+v %v", rec, err) - } - w := seat.Warrant{Ask: "a1", Asker: "shell", Outcome: OutcomeChosen, Option: "approve", Level: Approve, At: FormatTime(now), - AskDigest: Digest(k.Ask), Channel: "telegram", By: &seat.Person{Who: Operator, Kind: "telegram"}} - records["shell.a1"] = seat.AsksByAskerIDValue{Asker: "shell", State: "chosen", Opened: FormatTime(now), Warrant: &w} - rec, err := g.Record("shell", "a1") - if err != nil || rec == nil || rec.State != "chosen" || rec.Warrant == nil || rec.Warrant.Channel != "telegram" || rec.Warrant.Option != "approve" { - t.Fatalf("record %+v %v", rec, err) - } - if h, err := g.Claim("shell", *rec.Warrant, map[string]string{"verb": "other"}, func() { t.Fatal("ran another act") }); h != approval.Claimed || err == nil { - t.Fatalf("another act: %s %v", h, err) - } - delete(st.vals, "n1.ask.a1") - _ = g.Ask("shell", a) - runs := 0 - for i := 0; i < 2; i++ { - h, err := g.Claim("shell", *rec.Warrant, releaseHQ, func() { runs++ }) - if want := []approval.Claim{approval.Claimed, approval.ClaimDone}[i]; h != want || err != nil { - t.Fatalf("claim %d: %s %v", i, h, err) - } - } - if runs != 1 || g.Decided("shell") != seat.SubjectDecided("shell") { - t.Fatalf("runs %d", runs) - } + }) } diff --git a/operator-and-conversation/go/clients/operatorchannelclient/gate_conn_test.go b/operator-and-conversation/go/clients/operatorchannelclient/gate_conn_test.go index fbacc0c..8c119b1 100644 --- a/operator-and-conversation/go/clients/operatorchannelclient/gate_conn_test.go +++ b/operator-and-conversation/go/clients/operatorchannelclient/gate_conn_test.go @@ -2,13 +2,8 @@ package operatorchannelclient import ( "encoding/json" - "errors" - "testing" - "time" - seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-sdk/go/approval" ) type readOf struct { @@ -22,50 +17,3 @@ func (r readOf) Get(key string) (*stdio.StateEntry, error) { } return nil, nil } - -// The gate's channel over a process connection (design 47 G lead answers A-272, A-273): an ask and its cancel go out as -// accepted kinds of the seat operator-channel with the ask's id as the message id, the router's record is the get of -// `.` in the state asks of the seat's holder, and nothing else is reached: another subject, or a seat with no -// holder, is refused. -func TestTheGateChannelOverAConnectionAsksAndReadsOnlyThroughTheSeat(t *testing.T) { - type sent struct{ seat, kind, id string } - var out []sent - accept := func(s, kind string, _ any, opts ...stdio.EmitOptions) error { - out = append(out, sent{s, kind, opts[0].MessageID}) - return nil - } - raw, _ := json.Marshal(seat.AsksByAskerIDValue{Asker: "shell", State: "open", Opened: FormatTime(now)}) - vals := map[string]json.RawMessage{"messenger_holder.asks|shell.a1": raw, "other.asks|shell.a1": json.RawMessage(`{"state":"wrong"}`)} - read := func(r string) asksRead { return readOf{state: r, vals: vals} } - holder := func() (string, error) { return "messenger_holder", nil } - st := &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} - g := gateChannelOver(accept, read, st, "n1.", holder) - g.Now = func() time.Time { return now } - - digest, _ := ActDigest(releaseHQ) - a := approval.Ask{ID: "a1", About: digest, Headline: "Run wipe?", Explanation: "Wipes.", Expires: FormatTime(now.Add(time.Hour)), - OnExpiry: "nothing runs", Options: []approval.Option{{ID: "approve", Label: "Approve", Does: "runs", Binds: digest}}} - if err := g.Ask("shell", a); err != nil { - t.Fatal(err) - } - if err := g.Cancel("shell", "a1"); err != nil { - t.Fatal(err) - } - if len(out) != 2 || out[0] != (sent{seat.Seat, seat.AcceptAsk, "a1"}) || out[1] != (sent{seat.Seat, seat.AcceptCancel, "cancel.a1"}) { - t.Fatalf("sent %+v, want the ask and its cancel on the seat's accepts", out) - } - if rec, err := g.Record("shell", "a1"); err != nil || rec == nil || rec.State != "open" { - t.Fatalf("record %+v %v, want the one in the holder's state asks", rec, err) - } - if rec, err := g.Record("shell", "zz"); err != nil || rec != nil { - t.Fatalf("a record not there: %+v %v", rec, err) - } - if err := g.Publish("shell", "mesh.seat.operator-channel.accept.ask.other", nil, "x"); err == nil { - t.Fatal("an accept subject naming another caller was sent") - } - for _, c := range []func() (string, error){func() (string, error) { return "", nil }, func() (string, error) { return "", errors.New("unread") }} { - if _, err := gateChannelOver(accept, read, st, "n1.", c).Record("shell", "a1"); err == nil { - t.Fatal("a record was read with no holder of the seat") - } - } -} diff --git a/seats/node-engine/fixtures/events.json b/seats/node-engine/fixtures/events.json index bf8142c..2e85efb 100644 --- a/seats/node-engine/fixtures/events.json +++ b/seats/node-engine/fixtures/events.json @@ -69,7 +69,7 @@ { "name": "composed is said", "of": "event:composed", - "subject": "mesh.seat.node-engine.event.composed.ace", + "subject": "mesh.seat.node-engine.event.composed.home-server", "given": { "v": 1, "node": "ace", @@ -82,7 +82,7 @@ { "name": "running is said, its node and module in its subject", "of": "event:running", - "subject": "mesh.seat.node-engine.event.running.ace.nats", + "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, "node": "ace", @@ -95,7 +95,7 @@ { "name": "running says the build that runs is healthy", "of": "event:running", - "subject": "mesh.seat.node-engine.event.running.ace.nats", + "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, "node": "ace", @@ -109,7 +109,7 @@ { "name": "running says the build that runs turned unhealthy, still running", "of": "event:running", - "subject": "mesh.seat.node-engine.event.running.ace.nats", + "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, "node": "ace", @@ -123,7 +123,7 @@ { "name": "running says a module stopped on the node, with no fingerprint", "of": "event:running", - "subject": "mesh.seat.node-engine.event.running.ace.nats", + "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, "node": "ace", @@ -135,7 +135,7 @@ { "name": "a kept build's wait is held", "of": "event:wait-held", - "subject": "mesh.seat.node-engine.event.wait-held.ace.postgres", + "subject": "mesh.seat.node-engine.event.wait-held.home-server.postgres", "given": { "owner": "node-engine", "step": "kept-build", @@ -144,14 +144,14 @@ "bound": "60m", "since": "2026-11-11T10:00:00Z", "clock-from": "2026-11-11T10:00:00Z", - "words": "waiting for the kept build of postgres on ace", + "words": "waiting for the kept build of postgres on home-server", "reason": "node away" } }, { "name": "a hold of the composition ends", "of": "event:wait-ended", - "subject": "mesh.seat.node-engine.event.wait-ended.ace.postgres", + "subject": "mesh.seat.node-engine.event.wait-ended.home-server.postgres", "given": { "owner": "node-engine", "step": "compose",