Files
mesh-catalog/modules/mesh-vault/cmd/mesh-vault/placed.go
T
jschoubben c9d8950fd0 Every module follows the assigner's new places: the contracts' go/ path, mesh-wants in the delivery contracts, the state and tool assignments (issue 1966)
The legacy key form placement.<node> is gone from the client, so the readers and their fixtures lose it. The check noplacement
now also refuses the assigner's retired names in a manifest.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 22:29:07 +01:00

150 lines
5.2 KiB
Go

package main
// Where modules stand, from the seat mesh-module-assigner's state assignments (novox/hq ADR 0549 §3, §5; ADR 0561;
// issue 774), held by the assigner (ADR 0567). The controller's mirror of bindings (Vault.Assignments) goes with its lease,
// so the vault reads which module stands on which node from the seat's assignments first, and the mirror beside it while
// both stand: a module either names stands. The provisioner's plan, which only a binding's value carries, is read from
// the mirror still: its home after the lease is a subtask of its own (issue 774).
import (
"errors"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
placing "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"slices"
"sort"
"strings"
)
// AssignerReader reads the seat's assignments: every key, and one key's value (nil when there is none).
type AssignerReader struct {
Keys func() ([]string, error)
Read func(key string) ([]byte, error)
}
// assignments is every node's assignments, by node, from the state's keys (assignerclient.AssignmentsOf; research
// 072 row 37); none when the vault reads no assignments. A node whose keys do not read whole is read as they stand.
func (v *Vault) assignments() (map[string]placing.Assignments, error) {
out := map[string]placing.Assignments{}
if v.Assigner.Keys == nil || v.Assigner.Read == nil {
return out, nil
}
keys, err := v.Assigner.Keys()
if err != nil {
return nil, fmt.Errorf("the mirror's keys could not be read: %w", err)
}
values := map[string][]byte{}
for _, k := range keys {
if _, _, _, err := assignerclient.ParseKey(k); err != nil {
continue
}
raw, err := v.Assigner.Read(k)
if err != nil {
return nil, fmt.Errorf("the assignments' key %s could not be read: %w", k, err)
}
if raw != nil {
values[k] = raw
}
}
out, _ = assignerclient.AssignmentsOf(values)
return out, nil
}
// nodePlaced is what node's own key of the assignments says: the modules placed there and what a person decided of it,
// read from its node key `<node>.node` alone (which names every module placed, research 072 row 37). found is false when
// it has none.
func (v *Vault) nodePlaced(node string) (modules []string, decided placing.NodeDecided, found bool, err error) {
key, err := assignerclient.NodeKey(node)
if err != nil {
return nil, decided, false, err
}
raw, err := v.Assigner.Read(key)
if err != nil {
return nil, decided, false, err
}
if raw != nil {
n, err := assignerclient.DecodeNode(raw)
if err != nil {
return nil, decided, false, fmt.Errorf("the assignments of %s are not readable: %w", node, err)
}
for _, r := range n.Modules {
modules = append(modules, r.Module)
}
return modules, n.Decided, true, nil
}
return nil, decided, false, 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 own key of the assignments
// (nodePlaced); none when the vault reads no assignments or the node has no key.
func (v *Vault) placedModules(node string) ([]string, error) {
if v.Assigner.Read == nil {
return nil, nil
}
modules, _, _, err := v.nodePlaced(node)
return modules, err
}
// standsOn says module stands on node: placed there, or bound there while the mirror stands. A read that fails is
// an error, never "not there".
func (v *Vault) standsOn(node, module string) (bool, error) {
placed, err := v.placedOn(node, module)
if err != nil || placed {
return placed, err
}
d, ok := v.Askers.(Declared)
if !ok || d.Read == nil {
if v.Assigner.Read == nil {
return false, errors.New("the vault reads neither the seat's assignments nor the mirror")
}
return false, nil
}
raw, err := standing(d.Read)(node + "." + module)
return raw != nil, err
}
// busNode is the node running the bus's module: from the seat's assignments, and from the mirror while no assignment names
// one. The vault adopts from one node alone.
func (v *Vault) busNode() (string, error) {
placed, err := v.assignments()
if err != nil {
return "", err
}
var nodes []string
for node, p := range placed {
if slices.Contains(assignerclient.Assigned(p), BusModule) {
nodes = append(nodes, node)
}
}
from := "placed"
if len(nodes) == 0 && v.Assignments.Keys != nil {
from = "assigned"
keys, err := v.Assignments.Keys()
if err != nil {
return "", fmt.Errorf("the mirror's keys could not be read: %w", err)
}
for _, k := range keys {
if node, module, ok := strings.Cut(k, "."); ok && module == BusModule {
nodes = append(nodes, node)
}
}
} else if len(nodes) == 0 && len(placed) == 0 {
return "", errors.New("the vault reads neither the seat's assignments nor the mirror, so it cannot tell which node holds the bus")
}
sort.Strings(nodes)
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return "", fmt.Errorf("no node is %s the bus module %s", from, BusModule)
}
return "", fmt.Errorf("%d nodes are %s the bus module %s (%s): the vault adopts from one alone", len(nodes), from,
BusModule, strings.Join(nodes, ", "))
}