Round 2 T1, T2, T5: the contracts end G with no more tests than at their merge base; fixtures name roles

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
This commit is contained in:
2026-11-12 22:18:04 +01:00
parent 5cb77ce69f
commit 821d611530
15 changed files with 253 additions and 295 deletions
@@ -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)
}
}
+15 -16
View File
@@ -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) {
+2 -2
View File
@@ -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)
+10 -10
View File
@@ -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},
@@ -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)
@@ -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},
@@ -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},
@@ -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",
@@ -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"
}
}
@@ -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
}
}
@@ -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"
}
@@ -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)
}
})
}
@@ -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
// `<module>.<id>` 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)
}
})
}
@@ -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
// `<module>.<id>` 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")
}
}
}
+8 -8
View File
@@ -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",