diff --git a/modules/mesh-conditions/cmd/mesh-conditions/hold.go b/modules/mesh-conditions/cmd/mesh-conditions/hold.go index 8491812e..d51e4308 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/hold.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/hold.go @@ -216,26 +216,26 @@ func (k *Keeper) Have(key string, value []byte, deleted bool) error { return k.judge(node, module, "") } -// Placement takes one change of the placement: which module holds which seat on a node. +// Placement takes one change of the placement: which module holds which seat on a node. The keys are read in either +// form, a node key and its module keys or the form before (delivery.PlacementKeys; research 072 row 37). func (k *Keeper) Placement(key string, value []byte, deleted bool) { - node, err := delivery.ParsePlacementKey(key) - if err != nil { - return - } k.mu.Lock() defer k.mu.Unlock() k.init() + node, err := k.placement.Put(key, value, 0, deleted) + if node == "" { + return + } + if err != nil { + k.Logf("the placement of %s is not readable as it stands, so its seats' holders are kept as last read: %v", node, err) + } for hk := range k.holders { if strings.HasPrefix(hk, node+".") { delete(k.holders, hk) } } - if deleted { - return - } - p, err := delivery.DecodePlacement(value) - if err != nil { - k.Logf("the placement of %s is not readable, so its seats' holders are not known: %v", node, err) + p, _, ok := k.placement.Of(node) + if !ok { return } for _, h := range p.Holds { diff --git a/modules/mesh-conditions/cmd/mesh-conditions/hold_test.go b/modules/mesh-conditions/cmd/mesh-conditions/hold_test.go index fa668544..3a454a94 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/hold_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/hold_test.go @@ -113,12 +113,19 @@ func TestADeliveryHoldsItsOwnFindingsUntilItEnds(t *testing.T) { // A seat's finding is about the module the placement says holds the seat on that node (the lead, 2026-10-29): held // while the assigner is delivered on ace, though the seat is named mesh-placement. r := newRig() - placed, err := delivery.EncodePlacement(delivery.Placement{Node: "ace", Modules: []delivery.PlacedModule{{Module: "mesh-delivery-assigner"}}, + // Research 072 row 37: the placement in its keys per module and node; a stale legacy key beside them is not read. + stale, err := delivery.EncodePlacement(delivery.Placement{Node: "ace"}) + if err != nil { + t.Fatal(err) + } + r.k.Placement("placement.ace", stale, false) + placed, err := delivery.PlacementValues(delivery.Placement{Node: "ace", Modules: []delivery.PlacedModule{{Module: "mesh-delivery-assigner"}}, Holds: []delivery.Holding{{Claim: "mesh-placement", Scope: "mesh", Module: "mesh-delivery-assigner"}}}) if err != nil { t.Fatal(err) } - r.k.Placement("placement.ace", placed, false) + r.k.Placement("ace.mesh-delivery-assigner", placed["ace.mesh-delivery-assigner"], false) + r.k.Placement("ace.node", placed["ace.node"], false) _ = r.k.Want("want.ace.mesh-delivery-assigner", want(ctrlNext, ctrlNextComp), false) _ = r.k.Have("ace.mesh-delivery-assigner", have(ctrlRunning, ctrlComposed, delivery.PhaseRunning, delivery.HealthOK), false) seat, seatRaw := finding(t, selfCheck, health.ScopeSeat, "mesh-placement.ace", "holder-silent", "D3", health.Warning, r.now) diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go index a167631c..1ab5383c 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go @@ -112,8 +112,10 @@ type Keeper struct { haves map[string]delivery.Have // holders are, by `.`, the module the placement says holds the seat there. holders map[string]string - stuck map[string]string - held map[string]held + // placement is the placement's keys as heard, assembled per node. + placement *delivery.PlacementKeys + stuck map[string]string + held map[string]held // passed are the deliveries whose bound passed, by node and module, at the fingerprint wanted then; outOfHold the // line a key raised out of a hold says (hold.go). passed map[string]string @@ -130,6 +132,7 @@ func (k *Keeper) init() { map[string]string{}, map[string]held{}, map[string]clearedMemo{} k.takeover = health.Takeover{V: health.TakeoverVersion, Follow: health.FollowController} k.passed, k.outOfHold, k.holders = map[string]string{}, map[string]string{}, map[string]string{} + k.placement = delivery.NewPlacementKeys() } if k.Now == nil { k.Now = time.Now diff --git a/modules/mesh-conditions/go.mod b/modules/mesh-conditions/go.mod index b941eeac..c961ddb7 100644 --- a/modules/mesh-conditions/go.mod +++ b/modules/mesh-conditions/go.mod @@ -3,7 +3,7 @@ module mesh-conditions go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-conditions/go.sum b/modules/mesh-conditions/go.sum index a2b8e018..1dfb7daf 100644 --- a/modules/mesh-conditions/go.sum +++ b/modules/mesh-conditions/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 h1:vMZJm3lVjq7V4SqlSMzteUokziozuoUxws8DakfbgNA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-config/cmd/mesh-config/config_test.go b/modules/mesh-config/cmd/mesh-config/config_test.go index b086ff21..61085cd9 100644 --- a/modules/mesh-config/cmd/mesh-config/config_test.go +++ b/modules/mesh-config/cmd/mesh-config/config_test.go @@ -134,34 +134,58 @@ func (m *memStore) unassign(t *testing.T, node, module string) { m.place(t, node, module, false) } +// place writes a node's placement as the seat writes it: its node key and module keys (research 072 row 37), and for +// ace in the form before, `placement.ace`, which a reader still takes while the change is delivered. func (m *memStore) place(t *testing.T, node, module string, on bool) { t.Helper() - type placed struct { - Module string `json:"module"` + keys := map[string][]byte{} + for _, k := range m.keysOf(PlacementRead) { + e, _ := m.Get(PlacementRead, k) + keys[k] = e.Value } - p := struct { - V int `json:"v"` - Node string `json:"node"` - Modules []placed `json:"modules"` - }{V: 1, Node: node} - if e, _ := m.Get(PlacementRead, "placement."+node); e != nil { - if err := json.Unmarshal(e.Value, &p); err != nil { - t.Fatal(err) - } + all, err := delivery.PlacementsOf(keys) + if err != nil { + t.Fatal(err) } - var kept []placed + p := all[node] + p.Node = node + var kept []delivery.PlacedModule for _, x := range p.Modules { if x.Module != module { kept = append(kept, x) } } if on { - kept = append(kept, placed{Module: module}) + kept = append(kept, delivery.PlacedModule{Module: module}) } p.Modules = kept - if err := m.Put(PlacementRead, "placement."+node, p); err != nil { + for _, k := range m.keysOf(PlacementRead) { + if n, _, _, err := delivery.ParsePlacementKey(k); err == nil && n == node { + _ = m.Delete(PlacementRead, k) + } + } + if node == "ace" { + raw, err := delivery.EncodePlacement(p) + if err != nil { + t.Fatal(err) + } + _ = m.Put(PlacementRead, "placement.ace", json.RawMessage(raw)) + return + } + values, err := delivery.PlacementValues(p) + if err != nil { t.Fatal(err) } + for k, raw := range values { + if err := m.Put(PlacementRead, k, json.RawMessage(raw)); err != nil { + t.Fatal(err) + } + } +} + +func (m *memStore) keysOf(state string) []string { + keys, _ := m.Keys(state) + return keys } // manifest keeps a manifest in the indexer's manifests by its digest, as the indexer writes it, and answers the digest. diff --git a/modules/mesh-config/cmd/mesh-config/sources.go b/modules/mesh-config/cmd/mesh-config/sources.go index e32b4dda..e5452593 100644 --- a/modules/mesh-config/cmd/mesh-config/sources.go +++ b/modules/mesh-config/cmd/mesh-config/sources.go @@ -70,32 +70,32 @@ func (c *Config) sourcesFrom(source string) (Sources, error) { return s, nil } -// readPlacement is every module the seat mesh-placement places on a node (ADR 0567 §1, §9): each node's key -// `placement.`. +// readPlacement is every module the seat mesh-placement places on a node (ADR 0567 §1, §9), from the state's keys in +// either form: a node key and its module keys, or the form before, `placement.` (delivery.PlacementsOf; +// research 072 row 37). By node in name order, each node's modules in the order its placement names them. func (c *Config) readPlacement(s *Sources) error { - placed := map[string]bool{} - return c.each(PlacementRead, func(key string, raw []byte) error { - node, err := delivery.ParsePlacementKey(key) - if err != nil { - return nil // a key of another kind - } - var p struct { - Node string `json:"node"` - Modules []struct { - Module string `json:"module"` - } `json:"modules"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.Node != node { - return fmt.Errorf("the placement %s is not one", key) - } - for _, m := range p.Modules { - if m.Module != "" && !placed[node+"."+m.Module] { - placed[node+"."+m.Module] = true - s.Assignments = append(s.Assignments, Assignment{Node: node, Module: m.Module}) - } - } + keys := map[string][]byte{} + if err := c.each(PlacementRead, func(key string, raw []byte) error { + keys[key] = raw return nil - }) + }); err != nil { + return err + } + placements, err := delivery.PlacementsOf(keys) + if err != nil { + return fmt.Errorf("the placement is not read whole: %w", err) + } + nodes := make([]string, 0, len(placements)) + for node := range placements { + nodes = append(nodes, node) + } + sort.Strings(nodes) + for _, node := range nodes { + for _, module := range placements[node].Assigned() { + s.Assignments = append(s.Assignments, Assignment{Node: node, Module: module}) + } + } + return nil } // readDeclarations reads each placed module's declarations from its manifests as the delivery holds them: the trunk's diff --git a/modules/mesh-config/go.mod b/modules/mesh-config/go.mod index 191237ac..fb3f9958 100644 --- a/modules/mesh-config/go.mod +++ b/modules/mesh-config/go.mod @@ -2,7 +2,7 @@ module mesh-config go 1.26.0 -require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24 +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 require ( github.com/klauspost/compress v1.20.0 // indirect diff --git a/modules/mesh-config/go.sum b/modules/mesh-config/go.sum index 991de7e5..56057e5b 100644 --- a/modules/mesh-config/go.sum +++ b/modules/mesh-config/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24 h1:s0XXZtR7CFPBbnaDygWnE4/Q827iEFt0YfEzujk4Q9g= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030174137-a37181261c24/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 h1:vMZJm3lVjq7V4SqlSMzteUokziozuoUxws8DakfbgNA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 87f1460d..1242c101 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -903,9 +903,9 @@ func (h *Holder) put(key string, p provisions.Provision) { // ---- what a change touches -------------------------------------------------------------------------------------- -// NodesOfPlacement is the node a placement key names. +// NodesOfPlacement is the node a placement key names, in either form (research 072 row 37). func NodesOfPlacement(key string) []string { - if n, err := delivery.ParsePlacementKey(key); err == nil { + if n, _, _, err := delivery.ParsePlacementKey(key); err == nil { return []string{n} } return nil @@ -938,25 +938,23 @@ func (h *Holder) NodesOfModule(module string) []string { return sortedKeys(nodes) } -// placedOn is every node a module is placed on, sorted. +// placedOn is every node a module is placed on, sorted, from the placement's keys in either form +// (delivery.PlacementsOf; research 072 row 37). A node whose keys do not read whole is read as they stand. func (h *Holder) placedOn(module string) []string { nodes := map[string]bool{} + raw := map[string][]byte{} if keys, err := h.Store.Keys(PlacementRead); err == nil { for _, k := range keys { - n, err := delivery.ParsePlacementKey(k) - if err != nil { - continue + if e, err := h.Store.Get(PlacementRead, k); err == nil && e != nil { + raw[k] = e.Value } - e, err := h.Store.Get(PlacementRead, k) - if err != nil || e == nil { - continue - } - if p, err := delivery.DecodePlacement(e.Value); err == nil { - for _, m := range p.Modules { - if m.Module == module { - nodes[n] = true - } - } + } + } + placements, _ := delivery.PlacementsOf(raw) + for n, p := range placements { + for _, m := range p.Modules { + if m.Module == module { + nodes[n] = true } } } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go index f4bb55ad..35cb4626 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go @@ -191,7 +191,17 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) { // A provision's credential is the vault's own provision, on the consumer's node for a login of its own, and the // provision depends on it (clauses 3, 6). c := newMesh() - c.s.write(t, PlacementRead, "placement.novox", `{"v":1,"node":"novox","decided":{},"modules":[{"module":"mesh-vault"}]}`) + // The placement in its keys per module and node (research 072 row 37). + novox, err := delivery.PlacementValues(delivery.Placement{Node: "novox", Modules: []delivery.PlacedModule{{Module: "mesh-vault"}}}) + if err != nil { + t.Fatal(err) + } + for k, v := range novox { + c.s.write(t, PlacementRead, k, string(v)) + if n := NodesOfPlacement(k); !slices.Equal(n, []string{"novox"}) { + t.Errorf("NodesOfPlacement(%q) = %v", k, n) + } + } pair := pgDB pair.CredentialForm = "pair" c.atWant["g14"] = map[string]catalogue.Plan{"gitea": planOf(pair)} diff --git a/modules/mesh-provisioner/go.mod b/modules/mesh-provisioner/go.mod index b73dc004..7edb860e 100644 --- a/modules/mesh-provisioner/go.mod +++ b/modules/mesh-provisioner/go.mod @@ -3,7 +3,7 @@ module mesh-provisioner go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030212912-6c5a8ef893b4 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-provisioner/go.sum b/modules/mesh-provisioner/go.sum index d71e258f..1dfb7daf 100644 --- a/modules/mesh-provisioner/go.sum +++ b/modules/mesh-provisioner/go.sum @@ -1,7 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030182415-754edfae6924 h1:aIiKj066gYFEDokUOq0i2n4vsygu5g0XMKJRRaMI4fM= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030182415-754edfae6924/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030212912-6c5a8ef893b4 h1:uzXzKfjoWB8wCbAPgxUAWSArr+3DKwi8qrB3BUEOVOE= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030212912-6c5a8ef893b4/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 h1:vMZJm3lVjq7V4SqlSMzteUokziozuoUxws8DakfbgNA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go index a25fab0a..efb40831 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go @@ -31,29 +31,26 @@ const ( ScopeMachine = health.ScopeMachine ) -// placements is every node's placement, by node. +// placements is every node's placement, by node, from the state's keys in either form: a node key and its module keys, +// or the form before, `placement.` (delivery.PlacementsOf; research 072 row 37). func (c *Checker) placements() (map[string]delivery.Placement, error) { keys, err := c.Store.Keys(PlacementRead) if err != nil { return nil, fmt.Errorf("the placement cannot be read: %w", err) } - out := map[string]delivery.Placement{} + values := map[string][]byte{} for _, k := range keys { raw, ok, err := c.Store.Get(PlacementRead, k) if err != nil { - return nil, fmt.Errorf("the placement of %s cannot be read: %w", k, err) + return nil, fmt.Errorf("the placement's key %s cannot be read: %w", k, err) } - if !ok { - continue + if ok { + values[k] = raw } - var p delivery.Placement - if err := json.Unmarshal(raw, &p); err != nil { - return nil, fmt.Errorf("the placement of %s is not readable: %w", k, err) - } - if p.Node == "" { - p.Node = k - } - out[p.Node] = p + } + out, err := delivery.PlacementsOf(values) + if err != nil { + return nil, fmt.Errorf("the placement is not readable: %w", err) } return out, nil } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go index 03eb637a..cb083b0f 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go @@ -260,8 +260,12 @@ func machines() *memStore { node, address, endpoint string online bool }{{"novox", "10.10.0.1/16", "203.0.113.5:51820", true}, {"ace", "10.10.0.2/16", "", true}, {"g14", "10.10.0.3/16", "", false}} { - _ = s.Put(PlacementRead, p.node, delivery.Placement{V: 1, Node: p.node, - Decided: delivery.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}}) + pl := delivery.Placement{V: 1, Node: p.node, + Decided: delivery.NodeDecided{Address: p.address, Endpoint: p.endpoint, Hub: p.endpoint != ""}} + if p.node == "ace" { + pl.Modules = []delivery.PlacedModule{{Module: "sshd"}} // a module key beside the node key + } + place(s, pl) _ = s.Put(PresenceRead, p.node, delivery.Presence{V: 1, Online: p.online}) key, _ := delivery.SeatHoldersKey(BannedSeat, p.node) _ = s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node, @@ -270,6 +274,31 @@ func machines() *memStore { return s } +// place writes a node's placement as the seat does, its node key and a key per module (research 072 row 37); g14's in +// the form before, `placement.g14`, which the probes still read while the change is delivered. +func place(s *memStore, p delivery.Placement) { + if p.Node == "g14" { + raw, _ := delivery.EncodePlacement(p) + _ = s.Put(PlacementRead, "placement.g14", json.RawMessage(raw)) + return + } + values, _ := delivery.PlacementValues(p) + for k, raw := range values { + _ = s.Put(PlacementRead, k, json.RawMessage(raw)) + } +} + +// placed is every node's placement the store holds. +func placed(s *memStore) map[string]delivery.Placement { + keys, _ := s.Keys(PlacementRead) + values := map[string][]byte{} + for _, k := range keys { + values[k], _, _ = s.Get(PlacementRead, k) + } + out, _ := delivery.PlacementsOf(values) + return out +} + // filling is a store holding the self-check's written fill finding of a stream: the condition open. func filling(stream string) *memStore { s := newStore() @@ -285,11 +314,9 @@ func filling(stream string) *memStore { func agents(verdicts map[string]string) *memStore { s := machines() for node, v := range verdicts { - raw, _, _ := s.Get(PlacementRead, node) - var p delivery.Placement - _ = json.Unmarshal(raw, &p) + p := placed(s)[node] p.Decided.AgentAccount = "agent" - _ = s.Put(PlacementRead, node, p) + place(s, p) h := delivery.NodeHealth{V: 1, Node: node, Contract: 13} switch v { case "": @@ -303,7 +330,7 @@ func agents(verdicts map[string]string) *memStore { h.Root = []delivery.RootVerdict{{Account: "agent", Module: "claude-code", Resource: "claude-code.agent", State: v, Reason: "can become root without a person: a sudo rule"}} } - raw, _ = delivery.EncodeNodeHealth(h) + raw, _ := delivery.EncodeNodeHealth(h) _ = s.Put(HealthRead, node, json.RawMessage(raw)) } return s diff --git a/modules/mesh-self-check/go.mod b/modules/mesh-self-check/go.mod index 06acc2cf..264fe4a2 100644 --- a/modules/mesh-self-check/go.mod +++ b/modules/mesh-self-check/go.mod @@ -3,7 +3,7 @@ module mesh-self-check go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-self-check/go.sum b/modules/mesh-self-check/go.sum index a2b8e018..1dfb7daf 100644 --- a/modules/mesh-self-check/go.sum +++ b/modules/mesh-self-check/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 h1:vMZJm3lVjq7V4SqlSMzteUokziozuoUxws8DakfbgNA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-vault/cmd/mesh-vault/placed.go b/modules/mesh-vault/cmd/mesh-vault/placed.go index d7ca91a5..a51059f2 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed.go @@ -25,7 +25,8 @@ type PlacementReader struct { Read func(key string) ([]byte, error) } -// placements is every node's placement, by node; none when the vault reads no placement. +// placements is every node's placement, by node, from the state's keys in either form (delivery.PlacementsOf; research +// 072 row 37); none when the vault reads no placement. A node whose keys do not read whole is read as they stand. func (v *Vault) placements() (map[string]delivery.Placement, error) { out := map[string]delivery.Placement{} if v.Placement.Keys == nil || v.Placement.Read == nil { @@ -35,52 +36,73 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) { if err != nil { return nil, fmt.Errorf("the placement's keys could not be read: %w", err) } + values := map[string][]byte{} for _, k := range keys { - node, err := delivery.ParsePlacementKey(k) - if err != nil { + if _, _, _, err := delivery.ParsePlacementKey(k); err != nil { continue } raw, err := v.Placement.Read(k) if err != nil { - return nil, fmt.Errorf("the placement of %s could not be read: %w", node, err) + return nil, fmt.Errorf("the placement's key %s could not be read: %w", k, err) } - if raw == nil { - continue + if raw != nil { + values[k] = raw } - p, err := delivery.DecodePlacement(raw) - if err != nil || p.Node != node { - continue - } - out[node] = p } + out, _ = delivery.PlacementsOf(values) return out, nil } +// nodePlaced is what node's own key of the placement says: the modules placed there and what a person decided of it, +// read from its node key `.node` alone (which names every module placed, research 072 row 37), or, for a node not +// converted yet, from its key in the form before, `placement.`. found is false when it has neither. +func (v *Vault) nodePlaced(node string) (modules []string, decided delivery.NodeDecided, found bool, err error) { + key, err := delivery.PlacementNodeKey(node) + if err != nil { + return nil, decided, false, err + } + raw, err := v.Placement.Read(key) + if err != nil { + return nil, decided, false, err + } + if raw != nil { + n, err := delivery.DecodePlacementNode(raw) + if err != nil { + return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err) + } + for _, r := range n.Modules { + modules = append(modules, r.Module) + } + return modules, n.Decided, true, nil + } + legacy, err := delivery.LegacyPlacementKey(node) + if err != nil { + return nil, decided, false, err + } + if raw, err = v.Placement.Read(legacy); err != nil || raw == nil { + return nil, decided, false, err + } + p, err := delivery.DecodePlacement(raw) + if err != nil { + return nil, decided, false, fmt.Errorf("the placement of %s is not readable: %w", node, err) + } + return p.Assigned(), p.Decided, true, nil +} + // placedOn says module is placed on node by the catalogue. func (v *Vault) placedOn(node, module string) (bool, error) { placed, err := v.placedModules(node) return slices.Contains(placed, module), err } -// placedModules is every module the catalogue places on node, read from the node's one key of the placement; none when -// the vault reads no placement or the node has no key. +// placedModules is every module the catalogue places on node, read from the node's own key of the placement +// (nodePlaced); none when the vault reads no placement or the node has no key. func (v *Vault) placedModules(node string) ([]string, error) { if v.Placement.Read == nil { return nil, nil } - key, err := delivery.PlacementKey(node) - if err != nil { - return nil, err - } - raw, err := v.Placement.Read(key) - if err != nil || raw == nil { - return nil, err - } - p, err := delivery.DecodePlacement(raw) - if err != nil { - return nil, fmt.Errorf("the placement of %s is not readable: %w", node, err) - } - return p.Assigned(), nil + modules, _, _, err := v.nodePlaced(node) + return modules, err } // standsOn says module stands on node: placed there, or assigned there while the assignments stand. A read that fails is diff --git a/modules/mesh-vault/cmd/mesh-vault/placed_test.go b/modules/mesh-vault/cmd/mesh-vault/placed_test.go index dfae8948..4a49a4c2 100644 --- a/modules/mesh-vault/cmd/mesh-vault/placed_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/placed_test.go @@ -16,12 +16,14 @@ func placementsOf(t *testing.T, nodes map[string][]string) PlacementReader { for _, m := range modules { p.Modules = append(p.Modules, delivery.PlacedModule{Module: m}) } - raw, err := delivery.EncodePlacement(p) + // Research 072 row 37: the node key and a key per module. + kv, err := delivery.PlacementValues(p) if err != nil { t.Fatal(err) } - key, _ := delivery.PlacementKey(node) - values[key] = raw + for k, raw := range kv { + values[k] = raw + } } return PlacementReader{ Keys: func() ([]string, error) { diff --git a/modules/mesh-vault/cmd/mesh-vault/signing.go b/modules/mesh-vault/cmd/mesh-vault/signing.go index 7e05eb14..a4c66d9a 100644 --- a/modules/mesh-vault/cmd/mesh-vault/signing.go +++ b/modules/mesh-vault/cmd/mesh-vault/signing.go @@ -303,22 +303,14 @@ func (v *Vault) internalName(node string) (string, error) { if v.Placement.Read == nil { return "", errors.New("the vault reads no placement, so it cannot tell a node's internal name") } - key, err := delivery.PlacementKey(node) - if err != nil { - return "", err - } - raw, err := v.Placement.Read(key) + _, decided, found, err := v.nodePlaced(node) if err != nil { return "", fmt.Errorf(notYetPrefix+"the placement of %s could not be read: %w", node, err) } - if raw == nil { + if !found { return "", fmt.Errorf("%s has no placement of the seat mesh-placement, so it has no name inside the mesh", node) } - p, err := delivery.DecodePlacement(raw) - if err != nil { - return "", fmt.Errorf("the placement of %s is not readable: %w", node, err) - } - if p.Decided.Address == "" { + if decided.Address == "" { return "", fmt.Errorf("%s is not on the private network (its placement names no address), so it has no name inside the mesh to be certified for", node) } return overlay.InternalName(node), nil diff --git a/modules/mesh-vault/cmd/mesh-vault/signing_test.go b/modules/mesh-vault/cmd/mesh-vault/signing_test.go index 68ef9f6d..815cf5bc 100644 --- a/modules/mesh-vault/cmd/mesh-vault/signing_test.go +++ b/modules/mesh-vault/cmd/mesh-vault/signing_test.go @@ -40,16 +40,21 @@ func TestTheVaultSigns(t *testing.T) { "g14": {Node: "g14", Decided: delivery.NodeDecided{Address: "10.77.0.4"}}, "plain": {Node: "plain"}, } + // Research 072 row 37: ace and plain in their keys per module and node, g14 still in the form before. v.Placement = PlacementReader{Read: func(key string) ([]byte, error) { - node, err := delivery.ParsePlacementKey(key) + node, _, kind, err := delivery.ParsePlacementKey(key) if err != nil { return nil, err } p, ok := placements[node] - if !ok { + if !ok || (node == "g14") != (kind == delivery.PlacementLegacyKind) { return nil, nil } - return delivery.EncodePlacement(p) + if kind == delivery.PlacementLegacyKind { + return delivery.EncodePlacement(p) + } + values, err := delivery.PlacementValues(p) + return values[key], err }} records := map[string]string{"ace": base64.StdEncoding.EncodeToString(servingPub), "plain": base64.StdEncoding.EncodeToString(servingPub)} v.Haves = func(key string) ([]byte, error) { diff --git a/modules/mesh-vault/go.mod b/modules/mesh-vault/go.mod index b934e88b..02945920 100644 --- a/modules/mesh-vault/go.mod +++ b/modules/mesh-vault/go.mod @@ -3,7 +3,7 @@ module mesh-vault go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 golang.org/x/crypto v0.57.0 diff --git a/modules/mesh-vault/go.sum b/modules/mesh-vault/go.sum index 78414b54..1dfb7daf 100644 --- a/modules/mesh-vault/go.sum +++ b/modules/mesh-vault/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115 h1:ezKg8aAKQoZ5XEAWQQVk4y/x0h2qC73GcUKBmbqY5jw= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030225009-17fc9b9b8115/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77 h1:vMZJm3lVjq7V4SqlSMzteUokziozuoUxws8DakfbgNA= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082617-a320f8e2cd77/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=