From fce9189579244f90919b6642be7779e68a2c35d2 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 15:26:49 +0100 Subject: [PATCH 1/4] First views: a finding's times are times, as the conditions client types them (issue 1966) --- node-tools/internal/firstview/firstview.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-tools/internal/firstview/firstview.go b/node-tools/internal/firstview/firstview.go index e672aa3..967fc47 100644 --- a/node-tools/internal/firstview/firstview.go +++ b/node-tools/internal/firstview/firstview.go @@ -207,8 +207,8 @@ func conditionOf(f views.Finding) (string, []byte, error) { key := conditions.Key(scopeModule, f.ID, f.Kind) v := condseat.FindingsByKeyValue{V: 1, Key: key, Kind: f.Kind, Observer: f.Observer, Severity: conditions.Warning, Subject: condseat.Subject{Scope: scopeModule, ID: f.ID}, Summary: f.Summary, Headline: f.Headline, - Explanation: f.Explanation, Source: f.Observer + "." + f.Kind, At: f.At.UTC().Format(time.RFC3339), - Evidence: []condseat.Evidence{{At: f.At.UTC().Format(time.RFC3339), Said: f.Summary}}} + Explanation: f.Explanation, Source: f.Observer + "." + f.Kind, At: f.At.UTC(), + Evidence: []condseat.Evidence{{At: f.At.UTC(), Said: f.Summary}}} if err := conditions.CheckFinding(v); err != nil { return "", nil, err } From 387f54734635d4a03fa86a0c70d205f7e8a8a6e2 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 15:31:22 +0100 Subject: [PATCH 2/4] =?UTF-8?q?Tool=20runner:=20the=20console,=20its=20tes?= =?UTF-8?q?t=20bed=20and=20the=20provider=20read=20placement,=20the=20cata?= =?UTF-8?q?logue's=20keys=20and=20the=20provisions=20through=20the=20seats?= =?UTF-8?q?'=20clients,=20not=20go/delivery,=20go/provider=20or=20go/provi?= =?UTF-8?q?sions=20(T-1,=20ADR=200634=20=C2=A74;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- node-tools/go.mod | 6 ++ node-tools/internal/console/address.go | 62 ++++++++++++------- node-tools/internal/console/mcp.go | 4 +- node-tools/internal/console/runtimes_test.go | 19 +++--- node-tools/internal/firstview/firstview.go | 12 +++- .../internal/firstview/firstview_test.go | 16 +++++ node-tools/internal/meshtest/meshtest.go | 39 +++++++----- node-tools/internal/provide/provide.go | 4 +- node-tools/internal/provide/provide_test.go | 4 +- 9 files changed, 114 insertions(+), 52 deletions(-) diff --git a/node-tools/go.mod b/node-tools/go.mod index 823bb94..ace34fa 100644 --- a/node-tools/go.mod +++ b/node-tools/go.mod @@ -3,6 +3,12 @@ module github.com/novox/mesh-tools/node-tools go 1.26.0 require ( + git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-20261105000000-000000000000 + git.novox.be/novox/mesh-contracts/provisioning/go/seats/meshprovisioner v0.0.0-20261105000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-20261105000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-20261105000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-20261105000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-20261105000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-20261105000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-20261105000000-000000000000 github.com/nats-io/nats.go v1.54.0 diff --git a/node-tools/internal/console/address.go b/node-tools/internal/console/address.go index 64c21ec..a5d06a4 100644 --- a/node-tools/internal/console/address.go +++ b/node-tools/internal/console/address.go @@ -17,8 +17,10 @@ package console // session — so a tool that arrived a minute ago is found without the client reconnecting. import ( + assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "context" "encoding/json" @@ -30,6 +32,7 @@ import ( "github.com/novox/mesh-tools/node-tools/internal/announce" "github.com/novox/mesh-tools/node-tools/internal/bus" + "github.com/novox/mesh-tools/node-tools/internal/firstview" ) // IndexKept is how long what the mesh answered is kept before it is asked again: long enough that @@ -149,7 +152,7 @@ type index struct { // called missing while it is here (2026-10-05). Unheard []unheard // Placed is the state seat-holders as the seat mesh-placement's holder wrote it (novox/hq ADR 0571 clause 4), by its - // keys `` and `.`, read with delivery.HoldersOf; PlacedWhy says why it could not be read. + // keys `` and `.`, read with assigner.HoldersOf; PlacedWhy says why it could not be read. Placed map[string][]byte PlacedWhy string // Replaces is what each verb and tool replaces (novox/hq ADR 0245): @@ -337,7 +340,7 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ } l.NotAnswering = append(l.NotAnswering, recordsWhy...) if x.PlacedWhy != "" { - l.NotAnswering = append(l.NotAnswering, delivery.SeatHoldersRead+" (who holds which seat by placement could not be read: "+ + l.NotAnswering = append(l.NotAnswering, assigner.SeatHoldersRead+" (who holds which seat by placement could not be read: "+ x.PlacedWhy+")") } for _, u := range x.Unheard { @@ -357,7 +360,7 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ } // Source is where the console reads the four states of the indexer and the assigner it indexes the mesh by: the assigner's -// placement and seat-holders, the indexer's modules and manifests, by `.` (delivery.PlacementRead and the +// placement and seat-holders, the indexer's modules and manifests, by `.` (assigner.PlacementRead and the // rest). The tool runner's is its first views (firstview.Views), never the owners' state (novox/hq ADR 0615 §1). type Source interface { // State is every current value of a state by key; an error says why it cannot be told yet. @@ -366,16 +369,16 @@ type Source interface { Entry(name, key string) ([]byte, error) } -// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as delivery.SeatHoldersRead. +// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as assigner.SeatHoldersRead. func seatHoldersOn(src Source) (map[string][]byte, error) { - return src.State(delivery.SeatHoldersRead) + return src.State(assigner.SeatHoldersRead) } // The indexer's states the console reads (novox/hq ADR 0571 clauses 4 and 5): every module, and each manifest by its // digest, by the names a reader's manifest declares. var ( - modulesRead = delivery.IndexerModule + ".modules" - manifestsRead = delivery.IndexerModule + ".manifests" + modulesRead = firstview.ModulesRead + manifestsRead = firstview.ManifestsRead ) // manifestFacts is what the console takes of a manifest: the tools it declares and what each replaces. A manifest's @@ -388,20 +391,20 @@ type manifestFacts struct { var manifestsKept sync.Map // digest → manifestFacts // recordsOn is every module the indexer holds, with where the placement seat places it and whether it declares tools, -// and every machine placement names (delivery.ModuleNames, delivery.MachineNames). What cannot be read is said, one line +// and every machine placement names (moduleNames, assigner.MachineNames). What cannot be read is said, one line // per state, for the listing's not answering. func recordsOn(src Source) (recorded []recordedModule, machines []string, why []string) { - placements, err := src.State(delivery.PlacementRead) + placements, err := src.State(assigner.PlacementRead) if err != nil { - why = append(why, delivery.PlacementRead+" (where modules are placed could not be read, so what is missing cannot "+ + why = append(why, assigner.PlacementRead+" (where modules are placed could not be read, so what is missing cannot "+ "be said: "+err.Error()+")") } - machines = delivery.MachineNames(placements) + machines = assigner.MachineNames(placements) on := map[string][]string{} // The placement's keys in either form, a node key and its module keys or the form before (research 072 row 37). - assembled, err := delivery.PlacementsOf(placements) + assembled, err := assigner.PlacementsOf(placements) if err != nil { - why = append(why, delivery.PlacementRead+" (not every key is a placement: "+err.Error()+")") + why = append(why, assigner.PlacementRead+" (not every key is a placement: "+err.Error()+")") } for _, node := range machines { for _, m := range assembled[node].Modules { @@ -412,13 +415,13 @@ func recordsOn(src Source) (recorded []recordedModule, machines []string, why [] if err != nil { return nil, machines, append(why, modulesRead+" (which modules there are could not be read: "+err.Error()+")") } - names, err := delivery.ModuleNames(modules) + names, err := moduleNames(modules) if err != nil { return nil, machines, append(why, modulesRead+" ("+err.Error()+")") } for _, name := range names { r := recordedModule{Module: name, On: on[name]} - if m, err := delivery.DecodeModule(modules[name]); err == nil && m.Manifest != "" { + if m, err := meshcatalogclient.DecodeModule(modules[name]); err == nil && m.Manifest != "" { facts, err := manifestOf(src, m.Manifest) if err != nil { why = append(why, name+" (its manifest could not be read, so whether it answers tools is not known: "+err.Error()+")") @@ -431,6 +434,21 @@ func recordsOn(src Source) (recorded []recordedModule, machines []string, why [] return recorded, machines, why } +// moduleNames is every module the indexer holds, sorted, from its state `modules` as a reader holds its keys (key to +// value; a deleted key absent). A key that is not a module's is refused, naming it. +func moduleNames(keys map[string][]byte) ([]string, error) { + out := make([]string, 0, len(keys)) + for k := range keys { + module, err := meshcatalogclient.ParseModulesKey(k) + if err != nil { + return nil, err + } + out = append(out, module) + } + sort.Strings(out) + return out, nil +} + // manifestOf is what the console takes of the manifest of a digest, read from the indexer's manifests once. func manifestOf(src Source, digest string) (manifestFacts, error) { if kept, ok := manifestsKept.Load(digest); ok { @@ -443,12 +461,12 @@ func manifestOf(src Source, digest string) (manifestFacts, error) { if entry == nil { return manifestFacts{}, fmt.Errorf("the indexer holds no manifest %s", digest) } - m, err := delivery.DecodeManifest(entry) + m, err := meshcatalogclient.DecodeManifest(entry) if err != nil { return manifestFacts{}, err } var facts manifestFacts - if err := json.Unmarshal([]byte(m.JSON), &facts); err != nil { + if err := json.Unmarshal([]byte(m.Manifest), &facts); err != nil { return manifestFacts{}, err } manifestsKept.Store(digest, facts) @@ -456,19 +474,19 @@ func manifestOf(src Source, digest string) (manifestFacts, error) { } // placedHolders is who holds seat (on node, for a seat held per machine) by placement, or none. -func (x *index) placedHolders(seat, node string) []delivery.SeatHolder { +func (x *index) placedHolders(seat, node string) []meshmoduleassigner.SeatHoldersHoldersItem { if len(x.Placed) == 0 { return nil } - hs, err := delivery.HoldersOf(x.Placed, seat, node) + hs, err := assigner.HoldersOf(x.Placed, seat, node) if err != nil || len(hs) > 0 || node != "" { return hs } // A seat held per machine, asked without one: every node's key of it. - var all []delivery.SeatHolder + var all []meshmoduleassigner.SeatHoldersHoldersItem for key := range x.Placed { if s, n, ok := strings.Cut(key, "."); ok && s == seat { - if more, err := delivery.HoldersOf(x.Placed, seat, n); err == nil { + if more, err := assigner.HoldersOf(x.Placed, seat, n); err == nil { all = append(all, more...) } } diff --git a/node-tools/internal/console/mcp.go b/node-tools/internal/console/mcp.go index ee93c89..2abf4a5 100644 --- a/node-tools/internal/console/mcp.go +++ b/node-tools/internal/console/mcp.go @@ -6,8 +6,8 @@ package console import ( "context" "encoding/json" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "os" "strings" "sync" @@ -78,7 +78,7 @@ func (s *Surface) seatsAnswer() map[string]any { for _, st := range catalogue.Seats() { holders := x.placedHolders(st.Name, "") if holders == nil { - holders = []delivery.SeatHolder{} + holders = []meshmoduleassigner.SeatHoldersHoldersItem{} } tools := st.Serves if tools == nil { diff --git a/node-tools/internal/console/runtimes_test.go b/node-tools/internal/console/runtimes_test.go index 94af57d..2c7df29 100644 --- a/node-tools/internal/console/runtimes_test.go +++ b/node-tools/internal/console/runtimes_test.go @@ -8,7 +8,8 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + seatplacement "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "github.com/nats-io/nats.go/micro" "github.com/novox/mesh-tools/node-tools/internal/announce" @@ -61,13 +62,15 @@ func TestARuntimeThatAnswersLateIsFoundAndOneThatNeverDoesIsNamed(t *testing.T) map[string][]string{"laptop": {"slack", "nextcloud-client"}, "sleeper": {"nap", "nextcloud-client"}, "desktop": {"nextcloud-client"}, "bench": nil}) // Who holds which seat, by placement (ADR 0571 clause 4), as the seat mesh-placement's holder composes it. - placedKV := mesh.Bucket(t, delivery.SeatHoldersBucket) - for key, sh := range delivery.SeatHoldersOf(map[string]delivery.Placement{"bench": {Node: "bench", - Holds: []delivery.Holding{{Claim: "mesh-wants", Scope: "mesh", Module: "mesh-delivery-assigner"}}}}) { - raw, _ := json.Marshal(sh) - if _, err := placedKV.Put(key, raw); err != nil { - t.Fatal(err) - } + placedKV := mesh.Bucket(t, assigner.SeatHoldersBucket) + holdersKey, err := assigner.SeatHoldersKey("mesh-wants", "") + if err != nil { + t.Fatal(err) + } + raw, _ := json.Marshal(seatplacement.SeatHolders{V: assigner.Version, Seat: "mesh-wants", Scope: "mesh", + Holders: []seatplacement.SeatHoldersHoldersItem{{Module: "mesh-delivery-assigner", Node: "bench"}}}) + if _, err := placedKV.Put(holdersKey, raw); err != nil { + t.Fatal(err) } consoleMembership := mt.MembershipOf("node-tools", "laptop-console", false, nil) consoleMembership.State = mt.ConsoleReads() diff --git a/node-tools/internal/firstview/firstview.go b/node-tools/internal/firstview/firstview.go index 967fc47..efc776f 100644 --- a/node-tools/internal/firstview/firstview.go +++ b/node-tools/internal/firstview/firstview.go @@ -13,6 +13,7 @@ import ( "fmt" conditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" + assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" "sort" "sync" "time" @@ -25,7 +26,14 @@ import ( // The owners and their first-view tools (the tools' addresses are `.`). const ( Indexer = "mesh-delivery-indexer" - Assigner = "mesh-delivery-assigner" + Assigner = assigner.Module +) + +// The names the console reads the indexer's two states by (`.`), as a manifest's `reads` names them; the +// assigner's two are its client's (assigner.PlacementRead, assigner.SeatHoldersRead). +const ( + ModulesRead = Indexer + ".modules" + ManifestsRead = Indexer + ".manifests" ) // Spec is one first view the runner holds. @@ -39,7 +47,7 @@ var Specs = []Spec{ {Indexer, "modules", "modules"}, } -// Name is the name a state is read by, `.`: delivery.PlacementRead, delivery.SeatHoldersRead and the indexer's. +// Name is the name a state is read by, `.`: assigner.PlacementRead, assigner.SeatHoldersRead and the indexer's. func (s Spec) Name() string { return s.Owner + "." + s.State } // Views is the four followers. diff --git a/node-tools/internal/firstview/firstview_test.go b/node-tools/internal/firstview/firstview_test.go index ae2d5a0..ed25d20 100644 --- a/node-tools/internal/firstview/firstview_test.go +++ b/node-tools/internal/firstview/firstview_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-sdk/go/views" "github.com/novox/mesh-tools/node-tools/internal/bus" @@ -166,3 +167,18 @@ func TestTheRunnerHoldsTheFourStatesFromTheOwnersFirstViewsAndTheirEvents(t *tes t.Errorf("the gap was not said in the log: %v", logs) } } + +// The four first views are read by the names the seats' clients give the states, and the console reads the indexer's +// two by the names here. +func TestTheFirstViewsAreNamedAsTheClientsNameTheirStates(t *testing.T) { + want := map[string]bool{assigner.PlacementRead: true, assigner.SeatHoldersRead: true, ModulesRead: true, ManifestsRead: true} + for _, s := range Specs { + if !want[s.Name()] { + t.Errorf("the first view %s is read by no name a client gives", s.Name()) + } + delete(want, s.Name()) + } + if len(want) > 0 { + t.Errorf("no first view reads %v", want) + } +} diff --git a/node-tools/internal/meshtest/meshtest.go b/node-tools/internal/meshtest/meshtest.go index 9ca7cbf..704f5d1 100644 --- a/node-tools/internal/meshtest/meshtest.go +++ b/node-tools/internal/meshtest/meshtest.go @@ -13,8 +13,11 @@ package meshtest import ( "context" "encoding/json" + assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + seatplacement "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + meshcatalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-sdk/go/buscompose" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/membership" "os" "path/filepath" @@ -150,14 +153,22 @@ func sdkMembership(t *testing.T, mem bus.Membership) membership.Membership { return out } +// The indexer's two states the console reads, and their buckets (the test bed's own names: the indexer's client names +// no holder). +const ( + indexer = "mesh-delivery-indexer" + ModulesBucket = indexer + "_modules" + ManifestsBucket = indexer + "_manifests" +) + // ConsoleReads are the states a console's runtime reads to say what the mesh holds (novox/hq ADR 0571 clauses 4, 5): // placement, the indexer's modules and manifests, and the seat holders. func ConsoleReads() []bus.StateIssued { return []bus.StateIssued{ - {Name: delivery.PlacementRead, Bucket: delivery.PlacementBucket}, - {Name: delivery.IndexerModule + ".modules", Bucket: delivery.ModulesBucket}, - {Name: delivery.IndexerModule + ".manifests", Bucket: delivery.ManifestsBucket}, - {Name: delivery.SeatHoldersRead, Bucket: delivery.SeatHoldersBucket}, + {Name: assigner.PlacementRead, Bucket: assigner.PlacementBucket}, + {Name: indexer + ".modules", Bucket: ModulesBucket}, + {Name: indexer + ".manifests", Bucket: ManifestsBucket}, + {Name: assigner.SeatHoldersRead, Bucket: assigner.SeatHoldersBucket}, } } @@ -173,20 +184,20 @@ func (m *Mesh) Placed(t *testing.T, tools map[string][]string, on map[string][]s } return b } - modules, manifests, placement := kv(delivery.ModulesBucket), kv(delivery.ManifestsBucket), kv(delivery.PlacementBucket) - kv(delivery.SeatHoldersBucket) + modules, manifests, placement := kv(ModulesBucket), kv(ManifestsBucket), kv(assigner.PlacementBucket) + kv(assigner.SeatHoldersBucket) for module, declared := range tools { manifest, _ := json.Marshal(map[string]any{"module": module, "tools": declared}) - digest := delivery.ManifestDigest(manifest) - raw, err := delivery.EncodeManifest(delivery.Manifest{Module: module, JSON: string(manifest)}) + digest := meshcatalogclient.ManifestDigest(manifest) + raw, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: module, Manifest: string(manifest), V: 1}) if err != nil { t.Fatal(err) } if _, err := manifests.Put(digest, raw); err != nil { t.Fatal(err) } - raw, err = delivery.EncodeModule(delivery.Module{None: "not built here", Manifest: digest, Repository: "novox/" + module, - Commit: strings.Repeat("a", 40)}) + raw, err = meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{None: "not built here", Manifest: digest, Repository: "novox/" + module, + Commit: strings.Repeat("a", 40), V: 1}) if err != nil { t.Fatal(err) } @@ -195,12 +206,12 @@ func (m *Mesh) Placed(t *testing.T, tools map[string][]string, on map[string][]s } } for node, placed := range on { - p := delivery.Placement{V: delivery.Version, Node: node} + p := seatplacement.Placement{V: assigner.Version, Node: node} for _, module := range placed { - p.Modules = append(p.Modules, delivery.PlacedModule{Module: module}) + p.Modules = append(p.Modules, seatplacement.PlacedModule{Module: module}) } // As the seat writes it: its node key and a key per module (research 072 row 37). - values, err := delivery.PlacementValues(p) + values, err := assigner.PlacementValues(p) if err != nil { t.Fatal(err) } diff --git a/node-tools/internal/provide/provide.go b/node-tools/internal/provide/provide.go index c7a400c..dec22be 100644 --- a/node-tools/internal/provide/provide.go +++ b/node-tools/internal/provide/provide.go @@ -15,8 +15,8 @@ import ( "fmt" "time" - "git.novox.be/novox/mesh-sdk/go/provider" - "git.novox.be/novox/mesh-sdk/go/provisions" + provisions "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" + "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient/provider" "github.com/novox/mesh-tools/node-tools/internal/bus" ) diff --git a/node-tools/internal/provide/provide_test.go b/node-tools/internal/provide/provide_test.go index 47957c9..988119d 100644 --- a/node-tools/internal/provide/provide_test.go +++ b/node-tools/internal/provide/provide_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" + provisions "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" + "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient/provider" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/provider" - "git.novox.be/novox/mesh-sdk/go/provisions" "github.com/novox/mesh-tools/node-tools/internal/bus" mt "github.com/novox/mesh-tools/node-tools/internal/meshtest" From 6965119bce9ebdab101cda59f87e3bfd66b830f7 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 15:36:18 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Tool=20runner:=20the=20grants=20part's=20me?= =?UTF-8?q?mbership=20bucket=20and=20key=20are=20membership's,=20which=20t?= =?UTF-8?q?he=20SDK=20keeps=20(ADR=200635=20=C2=A74;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- node-tools/internal/bus/bus.go | 9 ++++----- node-tools/internal/meshtest/meshtest.go | 15 +++++++-------- .../internal/runtime/ledger_credentials_test.go | 6 +++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/node-tools/internal/bus/bus.go b/node-tools/internal/bus/bus.go index 5480691..ec1648b 100644 --- a/node-tools/internal/bus/bus.go +++ b/node-tools/internal/bus/bus.go @@ -18,7 +18,6 @@ import ( "encoding/json" "errors" "fmt" - "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/membership" "log" "regexp" @@ -355,7 +354,7 @@ func (c *Conn) Credential() Credential { return c.cred } // Follow reads a module's membership on this machine once and follows it live, so its tools are // served where the mesh issued them (ADR 0175). It is read from the grants part's state memberships, its key -// `.` (novox/hq ADR 0571 clause 7 and 10, mesh-sdk buscompose.GrantsMembershipsBucket), and nowhere else: +// `.` (novox/hq ADR 0571 clause 7 and 10, mesh-sdk membership.GrantsBucket), and nowhere else: // the old assignments stream is not read and its subject is not heard. func (c *Conn) Follow(module string) { c.mu.Lock() @@ -434,10 +433,10 @@ func (c *Conn) watchGrants(module string, say bool) bool { c.Logf(format, a...) } } - key := buscompose.MembershipKey(c.node, module) + key := membership.GrantsKey(c.node, module) // First the key's direct get, which the account is granted alone: a bus that refuses it, or a state not there yet, // answers at once, where binding the bucket would wait out its bound on a refusal. - bucket := buscompose.GrantsMembershipsBucket + bucket := membership.GrantsBucket probe, err := c.nc.Request("$JS.API.DIRECT.GET.KV_"+bucket+".$KV."+bucket+"."+key, nil, GrantsPatience) if err != nil || probe.Header.Get("Status") == "503" { why := "it did not answer" @@ -457,7 +456,7 @@ func (c *Conn) watchGrants(module string, say bool) bool { kv, err := js.KeyValue(ctx, bucket) if err != nil { logf("[mesh-tools] the grants part's memberships (%s) cannot be read (%v); %s has no membership "+ - "until it can be", buscompose.GrantsMembershipsBucket, err, module) + "until it can be", membership.GrantsBucket, err, module) return false } w, err := kv.Watch(context.Background(), key) diff --git a/node-tools/internal/meshtest/meshtest.go b/node-tools/internal/meshtest/meshtest.go index 704f5d1..5966866 100644 --- a/node-tools/internal/meshtest/meshtest.go +++ b/node-tools/internal/meshtest/meshtest.go @@ -17,7 +17,6 @@ import ( seatplacement "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" meshcatalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" - "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/membership" "os" "path/filepath" @@ -91,8 +90,8 @@ func New(t *testing.T) *Mesh { js, _ := nc.JetStream() _ = js.DeleteStream("EVENTS") // The grants part's state memberships, where a runtime reads what is issued it (ADR 0571 clause 10). - _ = js.DeleteKeyValue(buscompose.GrantsMembershipsBucket) - if _, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: buscompose.GrantsMembershipsBucket, History: 1}); err != nil { + _ = js.DeleteKeyValue(membership.GrantsBucket) + if _, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: membership.GrantsBucket, History: 1}); err != nil { t.Fatal(err) } if _, err := js.AddStream(&nats.StreamConfig{Name: "EVENTS", Subjects: []string{"mesh.mod.*.event.>"}}); err != nil { @@ -103,7 +102,7 @@ func New(t *testing.T) *Mesh { } // Issue writes a membership where the grants part keeps it, its state memberships, key `.` (novox/hq ADR -// 0571 clause 7 and 10; mesh-sdk buscompose.GrantsMembershipsBucket), the one place a runtime reads it. It writes with +// 0571 clause 7 and 10; mesh-sdk membership.GrantsBucket), the one place a runtime reads it. It writes with // mesh-sdk's encoder, the grants part's own, never json of this package's type: a value built here by hand agreed with // the reader's mistake and hid that the part wrote another shape (issue 774). func (m *Mesh) Issue(t *testing.T, mem bus.Membership) { @@ -123,13 +122,13 @@ func (m *Mesh) IssueInGrantsWrapped(t *testing.T, mem bus.Membership, published func (m *Mesh) putGrants(t *testing.T, mem bus.Membership, body []byte) { t.Helper() - kv, err := m.js.KeyValue(buscompose.GrantsMembershipsBucket) + kv, err := m.js.KeyValue(membership.GrantsBucket) if err != nil { - if kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: buscompose.GrantsMembershipsBucket, History: 1}); err != nil { + if kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: membership.GrantsBucket, History: 1}); err != nil { t.Fatal(err) } } - if _, err := kv.Put(buscompose.MembershipKey(mem.Node, mem.Module), body); err != nil { + if _, err := kv.Put(membership.GrantsKey(mem.Node, mem.Module), body); err != nil { t.Fatal(err) } } @@ -137,7 +136,7 @@ func (m *Mesh) putGrants(t *testing.T, mem bus.Membership, body []byte) { // DropGrants deletes the grants part's state memberships, as a bus that has not made it yet holds none; Issue makes it again. func (m *Mesh) DropGrants(t *testing.T) { t.Helper() - if err := m.js.DeleteKeyValue(buscompose.GrantsMembershipsBucket); err != nil { + if err := m.js.DeleteKeyValue(membership.GrantsBucket); err != nil { t.Fatal(err) } } diff --git a/node-tools/internal/runtime/ledger_credentials_test.go b/node-tools/internal/runtime/ledger_credentials_test.go index c96afd5..0377d5c 100644 --- a/node-tools/internal/runtime/ledger_credentials_test.go +++ b/node-tools/internal/runtime/ledger_credentials_test.go @@ -63,7 +63,7 @@ func TestALedgerIsWrittenOnlyOnItsModulesLedgerConnection(t *testing.T) { t.Fatal(err) } } - for _, b := range []string{"m_records", buscompose.GrantsMembershipsBucket} { + for _, b := range []string{"m_records", membership.GrantsBucket} { if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b}); err != nil { t.Fatal(err) } @@ -76,8 +76,8 @@ func TestALedgerIsWrittenOnlyOnItsModulesLedgerConnection(t *testing.T) { raw, _ := json.Marshal(mem) var sdk membership.Membership _ = json.Unmarshal(raw, &sdk) - grants, _ := js.KeyValue(ctx, buscompose.GrantsMembershipsBucket) - if _, err := grants.Put(ctx, buscompose.MembershipKey("anchor", "m"), membership.EncodeGrantsMembership(sdk)); err != nil { + grants, _ := js.KeyValue(ctx, membership.GrantsBucket) + if _, err := grants.Put(ctx, membership.GrantsKey("anchor", "m"), membership.EncodeGrantsMembership(sdk)); err != nil { t.Fatal(err) } From c55b1bcf38b6ec631a26edb83875bd0e9b2a2341 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 15:37:17 +0100 Subject: [PATCH 4/4] First views: the integration test reads the views families of the manifest's consumes, not the seat list's events (T-3; issue 1966) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- node-tools/internal/firstview/firstview_integration_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node-tools/internal/firstview/firstview_integration_test.go b/node-tools/internal/firstview/firstview_integration_test.go index 035f4cc..a7972dc 100644 --- a/node-tools/internal/firstview/firstview_integration_test.go +++ b/node-tools/internal/firstview/firstview_integration_test.go @@ -44,7 +44,10 @@ func TestTheRunnersConsumerCarriesTheOwnersViewsFamiliesWhenTheOwnersAreDownThen filters := map[string][]string{} families := 0 for _, c := range manifest.Consumes { - emitter, event, _ := strings.Cut(c, ".views.") + emitter, event, isView := strings.Cut(c, ".views.") + if !isView { // the seat mesh-catalog's events: the seat list's, on EVENTS (cmd/node-tools/seatlist.go) + continue + } stream := streamOf[emitter] if stream == "" { t.Fatalf("the manifest consumes %s, whose views stream the test does not know", c)