From 5e95cbed5936d540ef24bbfb69abb575cf36abe5 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 22:48:01 +0100 Subject: [PATCH] A-316: the assigner's candidate gains added, the version and parts the want it would write carries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The state candidates' value (CandidatesByProposalNodeValue) gains `added`, by module name, each an AddedModule with `version` and `parts` in the want's own forms. It is a copy of the two properties of the seat mesh-wants' state wants, because the seat-definition format cannot reuse another repository's seat's forms (includes take only parts/ of the same repository, and a part holds no state value); a fixture holds both to the want's wire. Regenerated with seatgen. The node names the first round-2 pass left in fixtures lines equal to base lines are role names now, the generated tests with them. Builds: design 47 G A-316 (corrected, hq #594), §626 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- go/seats/meshmoduleassigner/contract.go | 10 ++++++++ go/seats/meshmoduleassigner/contract_test.go | 2 +- go/seats/nodeengine/contract_test.go | 2 +- .../go/seats/meshconditions/contract_test.go | 4 ++-- .../mesh-conditions/fixtures/states.json | 24 +++++++++---------- .../meshnodeenrollerclient/enrolment_test.go | 4 ++-- .../mesh-module-assigner/fixtures/states.json | 18 ++++++++++---- seats/mesh-module-assigner/seat.json | 24 +++++++++++++++++++ seats/node-engine/fixtures/events.json | 12 +++++----- seats/node-engine/fixtures/verbs.json | 4 ++-- ts/seats/meshmoduleassigner/index.d.ts | 10 ++++++++ 11 files changed, 83 insertions(+), 31 deletions(-) diff --git a/go/seats/meshmoduleassigner/contract.go b/go/seats/meshmoduleassigner/contract.go index e8a5beb..c260a03 100644 --- a/go/seats/meshmoduleassigner/contract.go +++ b/go/seats/meshmoduleassigner/contract.go @@ -344,6 +344,14 @@ func DecodeSeatHoldersBySeatNodeValue(raw []byte) (SeatHoldersBySeatNodeValue, e return v, nil } +// AddedModule is what the want a candidate would write for a module carries of its build, in the want's own forms (the seat mesh-wants' state wants: `version` and `parts`; copied here because a seat definition cannot reuse another repository's seat's forms, design 47 G A-316). +type AddedModule struct { + // the digest of each of the module's parts, by `:` (design 47 G section 379) + Parts map[string]string `json:"parts,omitempty"` + // the module's released version, for people; none for a build of another watched branch (design 47 G section 379) + Version string `json:"version,omitempty"` +} + // Assignments is one node's assignments assembled. type Assignments struct { Decided NodeDecided `json:"decided"` @@ -687,6 +695,8 @@ type ProposalsByProposalValue = Proposal // CandidatesByProposalNodeValue is one value of the state candidates under the key .. type CandidatesByProposalNodeValue struct { + // by module name, each module the candidate adds to this node without a want there: the `version` and `parts` the want it would write carries (design 47 G A-316); the node-engine builds such a module from this entry + Added map[string]AddedModule `json:"added,omitempty"` // the node's assignments with the proposal laid over it; absent when the proposal takes its key away Assignments *Assignments `json:"assignments,omitempty"` Proposal string `json:"proposal"` diff --git a/go/seats/meshmoduleassigner/contract_test.go b/go/seats/meshmoduleassigner/contract_test.go index e38965e..b083e81 100644 --- a/go/seats/meshmoduleassigner/contract_test.go +++ b/go/seats/meshmoduleassigner/contract_test.go @@ -59,7 +59,7 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, - {name: "a candidate", subject: "", given: "{\"proposal\":\"p-20261105t120000-aabbccdd\",\"v\":1}", wire: "{\"proposal\":\"p-20261105t120000-aabbccdd\",\"v\":1}", + {name: "a candidate, with the modules it adds and what their wants would carry", subject: "", given: "{\"added\":{\"mesh-build-go\":{\"parts\":{\"mesh-build-go:go-binary\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\"version\":\"1.4.0\"}},\"proposal\":\"p-20261105t120000-aabbccdd\",\"v\":1}", wire: "{\"added\":{\"mesh-build-go\":{\"parts\":{\"mesh-build-go:go-binary\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\"version\":\"1.4.0\"}},\"proposal\":\"p-20261105t120000-aabbccdd\",\"v\":1}", decode: func(b []byte) (any, error) { var v CandidatesByProposalNodeValue err := json.Unmarshal(b, &v) diff --git a/go/seats/nodeengine/contract_test.go b/go/seats/nodeengine/contract_test.go index 1872313..4ebf1aa 100644 --- a/go/seats/nodeengine/contract_test.go +++ b/go/seats/nodeengine/contract_test.go @@ -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\":\"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}", + {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}", 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}, diff --git a/health-and-repair/go/seats/meshconditions/contract_test.go b/health-and-repair/go/seats/meshconditions/contract_test.go index a64f2a7..bb12b8a 100644 --- a/health-and-repair/go/seats/meshconditions/contract_test.go +++ b/health-and-repair/go/seats/meshconditions/contract_test.go @@ -25,7 +25,7 @@ func TestFixtures(t *testing.T) { 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.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\"}", + {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 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.\",\"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 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\"}", 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 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 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\"}", 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}, @@ -39,7 +39,7 @@ 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 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\"}", + {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.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.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.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.ace\",\"machine\":\"ace\",\"scope\":\"module\"},\"summary\":\"mesh-build-node on ace uses mesh-build-node.cache as found\"}", decode: func(b []byte) (any, error) { var v FindingsByScopeIDKindValue err := json.Unmarshal(b, &v) diff --git a/health-and-repair/seats/mesh-conditions/fixtures/states.json b/health-and-repair/seats/mesh-conditions/fixtures/states.json index 5f78e8e..6422a5a 100644 --- a/health-and-repair/seats/mesh-conditions/fixtures/states.json +++ b/health-and-repair/seats/mesh-conditions/fixtures/states.json @@ -71,21 +71,21 @@ "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.", - "headline": "mesh-build-node waits for a directory on ace", - "key": "module.mesh-build-node.ace.directory-used-as-found", + "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, "raised": "2026-10-31T08:19:57.076843727Z", - "resolved": "mesh-build-node's directory on ace is the mesh's", + "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.ace", - "machine": "ace", + "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", @@ -104,21 +104,21 @@ "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.", - "headline": "mesh-build-node waits for a directory on ace", - "key": "module.mesh-build-node.ace.directory-used-as-found", + "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, "raised": "2026-10-31T08:19:57.076843727Z", - "resolved": "mesh-build-node's directory on ace is the mesh's", + "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.ace", - "machine": "ace", + "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", diff --git a/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go b/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go index e4063c2..1628885 100644 --- a/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go +++ b/identity-and-access/go/clients/meshnodeenrollerclient/enrolment_test.go @@ -35,7 +35,7 @@ func TestATokenLifeAndTheRedeemProofAreAsTheHolderMakesThem(t *testing.T) { // 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=="} + mine := RedeemAnswer{Node: "laptop", 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)) @@ -43,7 +43,7 @@ func TestATokenLifeAndTheRedeemProofAreAsTheHolderMakesThem(t *testing.T) { if err := d.Decode(&theirs); err != nil { t.Fatal(err) } - if theirs.Node != "g14" || theirs.Signer != "dg==" || theirs.Signature != "cw==" { + if theirs.Node != "laptop" || theirs.Signer != "dg==" || theirs.Signature != "cw==" { t.Errorf("the contract reads %+v", theirs) } back, _ := json.Marshal(theirs) diff --git a/seats/mesh-module-assigner/fixtures/states.json b/seats/mesh-module-assigner/fixtures/states.json index d08098b..6a6fcd3 100644 --- a/seats/mesh-module-assigner/fixtures/states.json +++ b/seats/mesh-module-assigner/fixtures/states.json @@ -63,7 +63,7 @@ "of": "state:assignments/.", "given": { "v": 1, - "node": "ace", + "node": "home-server", "placed": { "module": "plex", "branch": "beta", @@ -106,24 +106,32 @@ "changes": [ { "op": "place", - "node": "ace", + "node": "home-server", "module": "nats", "branch": "beta", "build": "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ], "base": { - "ace": 3 + "home-server": 3 }, "state": "answering" } }, { - "name": "a candidate", + "name": "a candidate, with the modules it adds and what their wants would carry", "of": "state:candidates/.", "given": { "v": 1, - "proposal": "p-20261105t120000-aabbccdd" + "proposal": "p-20261105t120000-aabbccdd", + "added": { + "mesh-build-go": { + "version": "1.4.0", + "parts": { + "mesh-build-go:go-binary": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + } + } } }, { diff --git a/seats/mesh-module-assigner/seat.json b/seats/mesh-module-assigner/seat.json index 09eb93b..9529223 100644 --- a/seats/mesh-module-assigner/seat.json +++ b/seats/mesh-module-assigner/seat.json @@ -350,6 +350,13 @@ "assignments": { "$ref": "#/defs/Assignments", "description": "the node's assignments with the proposal laid over it; absent when the proposal takes its key away" + }, + "added": { + "type": "object", + "additionalProperties": { + "$ref": "#/defs/AddedModule" + }, + "description": "by module name, each module the candidate adds to this node without a want there: the `version` and `parts` the want it would write carries (design 47 G A-316); the node-engine builds such a module from this entry" } }, "required": [ @@ -1061,6 +1068,23 @@ "branch", "nodes" ] + }, + "AddedModule": { + "type": "object", + "description": "what the want a candidate would write for a module carries of its build, in the want's own forms (the seat mesh-wants' state wants: `version` and `parts`; copied here because a seat definition cannot reuse another repository's seat's forms, design 47 G A-316)", + "properties": { + "version": { + "type": "string", + "description": "the module's released version, for people; none for a build of another watched branch (design 47 G section 379)" + }, + "parts": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "the digest of each of the module's parts, by `:` (design 47 G section 379)" + } + } } } } diff --git a/seats/node-engine/fixtures/events.json b/seats/node-engine/fixtures/events.json index 2e85efb..052157f 100644 --- a/seats/node-engine/fixtures/events.json +++ b/seats/node-engine/fixtures/events.json @@ -72,7 +72,7 @@ "subject": "mesh.seat.node-engine.event.composed.home-server", "given": { "v": 1, - "node": "ace", + "node": "home-server", "module": "nats", "fingerprint": "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", @@ -85,7 +85,7 @@ "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, - "node": "ace", + "node": "home-server", "module": "nats", "fingerprint": "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "manifest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", @@ -98,7 +98,7 @@ "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, - "node": "ace", + "node": "home-server", "module": "nats", "fingerprint": "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "manifest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", @@ -112,7 +112,7 @@ "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, - "node": "ace", + "node": "home-server", "module": "nats", "fingerprint": "src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "manifest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", @@ -126,7 +126,7 @@ "subject": "mesh.seat.node-engine.event.running.home-server.nats", "given": { "v": 1, - "node": "ace", + "node": "home-server", "module": "nats", "stopped": true, "at": "2026-11-11T09:00:00Z" @@ -139,7 +139,7 @@ "given": { "owner": "node-engine", "step": "kept-build", - "node": "ace", + "node": "home-server", "on": "the kept build of postgres", "bound": "60m", "since": "2026-11-11T10:00:00Z", diff --git a/seats/node-engine/fixtures/verbs.json b/seats/node-engine/fixtures/verbs.json index 206e63c..94914a3 100644 --- a/seats/node-engine/fixtures/verbs.json +++ b/seats/node-engine/fixtures/verbs.json @@ -129,7 +129,7 @@ "name": "process-builds answers each process's build", "of": "verb:process-builds.answer", "given": { - "node": "ace", + "node": "home-server", "processes": [ { "module": "nats", @@ -152,7 +152,7 @@ "name": "process-builds answers an error", "of": "verb:process-builds.answer", "given": { - "node": "ace", + "node": "home-server", "processes": [], "error": "the service manager did not answer" } diff --git a/ts/seats/meshmoduleassigner/index.d.ts b/ts/seats/meshmoduleassigner/index.d.ts index 44beeef..fb8994b 100644 --- a/ts/seats/meshmoduleassigner/index.d.ts +++ b/ts/seats/meshmoduleassigner/index.d.ts @@ -86,6 +86,14 @@ export declare function seatHoldersBySeatNodeKey(seat: string, node: string): st /** The parts of a key of the form ".", or undefined for a key of another form. */ export declare function parseSeatHoldersBySeatNodeKey(_key: string): { seat: string; node: string } | undefined; +/** what the want a candidate would write for a module carries of its build, in the want's own forms (the seat mesh-wants' state wants: `version` and `parts`; copied here because a seat definition cannot reuse another repository's seat's forms, design 47 G A-316) */ +export interface AddedModule { + /** the digest of each of the module's parts, by `:` (design 47 G section 379) */ + "parts"?: Record; + /** the module's released version, for people; none for a build of another watched branch (design 47 G section 379) */ + "version"?: string; +} + /** one node's assignments assembled */ export interface Assignments { "decided": NodeDecided; @@ -415,6 +423,8 @@ export type ProposalsByProposalValue = Proposal; /** is one value of the state candidates under the key .. */ export interface CandidatesByProposalNodeValue { + /** by module name, each module the candidate adds to this node without a want there: the `version` and `parts` the want it would write carries (design 47 G A-316); the node-engine builds such a module from this entry */ + "added"?: Record; /** the node's assignments with the proposal laid over it; absent when the proposal takes its key away */ "assignments"?: Assignments; "proposal": string;