The Placement domain goes: the module assigner and the four node seats move to the Core place, the assigner's state and verb placement become assignments, mesh-wants goes to mesh-delivery (issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 21:39:56 +01:00
parent 07ec4b1f31
commit fdcec5d384
45 changed files with 156 additions and 1683 deletions
-1
View File
@@ -39,7 +39,6 @@
"Module",
"Config",
"Core",
"Placement",
"Provisioning",
"Identity and access",
"Delivery",
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
@@ -66,7 +66,7 @@ func TestPlacementClient(t *testing.T) {
Holds: []seat.Holding{{Claim: "node-shell", Scope: "node", Module: "zsh"}},
}
novox := seat.Placement{Node: "novox", Decided: seat.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []seat.PlacedModule{{Module: "postgres", Ports: map[string]int64{"5432": 5432}}},
Holds: []seat.Holding{{Claim: "mesh-store", Scope: "mesh", Module: "postgres"}}}
Holds: []seat.Holding{{Claim: "mesh-example", Scope: "mesh", Module: "postgres"}}}
t.Run("value", func(t *testing.T) {
raw, err := EncodePlacement(g14)
@@ -230,7 +230,7 @@ func TestPlacementClient(t *testing.T) {
t.Errorf("an unchanged placement writes %+v", again)
}
// Keys that do not agree keep what the node had, and say so.
stray, _ := EncodeModule(seat.PlacementByNodeModuleValue{Node: "g14", Placed: seat.PlacedModule{Module: "app"}})
stray, _ := EncodeModule(seat.AssignmentsByNodeModuleValue{Node: "g14", Placed: seat.PlacedModule{Module: "app"}})
if _, err := keys.Put("g14.app", stray, 99, false); err == nil {
t.Error("a module key the node key does not name the value of was taken silently")
}
@@ -0,0 +1,8 @@
module git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
@@ -5,7 +5,7 @@ import (
"fmt"
"strings"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
)
// SeatHoldersKey is a seat's key in the state `seat-holders`: `<seat>` for a seat held once for the mesh (node empty),
@@ -4,7 +4,7 @@ import (
"testing"
"time"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
)
// The digest a node key names is kept as the first writer made it, so a node key written before this client reads.
@@ -18,14 +18,14 @@ func TestDigestIsTheFirstWritersAndHoldersAndStandingRead(t *testing.T) {
keys := map[string][]byte{
"mesh-module-assigner": []byte(`{"v":1,"seat":"mesh-module-assigner","scope":"mesh","holders":[{"module":"mesh-delivery-assigner","node":"novox"}]}`),
"node-files.ace": []byte(`{"v":1,"seat":"node-files","scope":"node","node":"ace","holders":[{"module":"node-files","node":"ace"}]}`),
"node-example.ace": []byte(`{"v":1,"seat":"node-example","scope":"node","node":"ace","holders":[{"module":"node-example","node":"ace"}]}`),
"mesh-vault": []byte(`{"v":2,"seat":"mesh-vault","scope":"mesh","holders":[]}`),
}
for _, c := range []struct {
seat, node string
want int
bad bool
}{{"mesh-module-assigner", "", 1, false}, {"node-files", "ace", 1, false}, {"node-files", "g14", 0, false}, {"mesh-vault", "", 0, true}} {
}{{"mesh-module-assigner", "", 1, false}, {"node-example", "ace", 1, false}, {"node-example", "g14", 0, false}, {"mesh-vault", "", 0, true}} {
got, err := HoldersOf(keys, c.seat, c.node)
if (err != nil) != c.bad || len(got) != c.want {
t.Errorf("HoldersOf(%s, %q) = %v, %v", c.seat, c.node, got, err)
@@ -37,9 +37,9 @@ func TestDigestIsTheFirstWritersAndHoldersAndStandingRead(t *testing.T) {
p := NewPlaced()
t0 := time.Date(2026, 11, 5, 12, 0, 0, 0, time.UTC)
node := func(modules ...string) []byte {
n := seat.PlacementByNodeNodeValue{Node: "ace"}
n := seat.AssignmentsByNodeNodeValue{Node: "ace"}
for _, name := range modules {
n.Modules = append(n.Modules, seat.PlacementByNodeNodeValueModulesItem{Module: name, Digest: "d"})
n.Modules = append(n.Modules, seat.AssignmentsByNodeNodeValueModulesItem{Module: name, Digest: "d"})
}
raw, err := EncodeNode(n)
if err != nil {
@@ -8,7 +8,7 @@ import (
"strings"
"sync"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
)
// **One key per module and node** (research 072 row 37; issue 774). The seat's state `placement` keeps a node's placement
@@ -104,7 +104,7 @@ func checkPlacementModule(module string) error {
}
// EncodeNode is n as JSON at Version.
func EncodeNode(n seat.PlacementByNodeNodeValue) ([]byte, error) {
func EncodeNode(n seat.AssignmentsByNodeNodeValue) ([]byte, error) {
if err := checkPlacementNode(n.Node); err != nil {
return nil, err
}
@@ -114,24 +114,24 @@ func EncodeNode(n seat.PlacementByNodeNodeValue) ([]byte, error) {
// DecodeNode reads a node key's value; it refuses an unknown version, a node or module that is not a name, and a module
// named twice or without a digest.
func DecodeNode(data []byte) (seat.PlacementByNodeNodeValue, error) {
func DecodeNode(data []byte) (seat.AssignmentsByNodeNodeValue, error) {
if err := checkVersion(data); err != nil {
return seat.PlacementByNodeNodeValue{}, err
return seat.AssignmentsByNodeNodeValue{}, err
}
n, err := seat.DecodePlacementByNodeNodeValue(data)
n, err := seat.DecodeAssignmentsByNodeNodeValue(data)
if err != nil {
return n, fmt.Errorf("meshmoduleassignerclient: value does not fit its shape: %w", err)
}
if err := checkPlacementNode(n.Node); err != nil {
return seat.PlacementByNodeNodeValue{}, err
return seat.AssignmentsByNodeNodeValue{}, err
}
seen := map[string]bool{}
for _, r := range n.Modules {
if err := checkPlacementModule(r.Module); err != nil {
return seat.PlacementByNodeNodeValue{}, err
return seat.AssignmentsByNodeNodeValue{}, err
}
if seen[r.Module] || r.Digest == "" {
return seat.PlacementByNodeNodeValue{}, fmt.Errorf("meshmoduleassignerclient: the node key of %s names %s twice or without a digest", n.Node, r.Module)
return seat.AssignmentsByNodeNodeValue{}, fmt.Errorf("meshmoduleassignerclient: the node key of %s names %s twice or without a digest", n.Node, r.Module)
}
seen[r.Module] = true
}
@@ -139,7 +139,7 @@ func DecodeNode(data []byte) (seat.PlacementByNodeNodeValue, error) {
}
// EncodeModule is m as JSON at Version.
func EncodeModule(m seat.PlacementByNodeModuleValue) ([]byte, error) {
func EncodeModule(m seat.AssignmentsByNodeModuleValue) ([]byte, error) {
if err := checkPlacementNode(m.Node); err != nil {
return nil, err
}
@@ -154,19 +154,19 @@ func EncodeModule(m seat.PlacementByNodeModuleValue) ([]byte, error) {
}
// DecodeModule reads a module key's value.
func DecodeModule(data []byte) (seat.PlacementByNodeModuleValue, error) {
func DecodeModule(data []byte) (seat.AssignmentsByNodeModuleValue, error) {
if err := checkVersion(data); err != nil {
return seat.PlacementByNodeModuleValue{}, err
return seat.AssignmentsByNodeModuleValue{}, err
}
m, err := seat.DecodePlacementByNodeModuleValue(data)
m, err := seat.DecodeAssignmentsByNodeModuleValue(data)
if err != nil {
return m, fmt.Errorf("meshmoduleassignerclient: value does not fit its shape: %w", err)
}
if err := checkPlacementNode(m.Node); err != nil {
return seat.PlacementByNodeModuleValue{}, err
return seat.AssignmentsByNodeModuleValue{}, err
}
if err := checkPlacementModule(m.Placed.Module); err != nil {
return seat.PlacementByNodeModuleValue{}, err
return seat.AssignmentsByNodeModuleValue{}, err
}
return m, nil
}
@@ -185,9 +185,9 @@ type Keys struct {
// keysOf is what is held of one node's keys.
type keysOf struct {
node *seat.PlacementByNodeNodeValue
node *seat.AssignmentsByNodeNodeValue
nodeRev uint64
modules map[string]seat.PlacementByNodeModuleValue
modules map[string]seat.AssignmentsByNodeModuleValue
moduleRevs map[string]uint64
legacy *seat.Placement
legacyRev uint64
@@ -245,7 +245,7 @@ func (k *Keys) put(key string, value []byte, revision uint64, deleted bool) (nod
defer k.mu.Unlock()
of := k.nodes[node]
if of == nil {
of = &keysOf{modules: map[string]seat.PlacementByNodeModuleValue{}, moduleRevs: map[string]uint64{}}
of = &keysOf{modules: map[string]seat.AssignmentsByNodeModuleValue{}, moduleRevs: map[string]uint64{}}
k.nodes[node] = of
}
switch kind {
@@ -405,7 +405,7 @@ func (k *Keys) Writes(node string, after *seat.Placement) ([]KeyWrite, error) {
defer k.mu.Unlock()
of := k.nodes[node]
if of == nil {
of = &keysOf{modules: map[string]seat.PlacementByNodeModuleValue{}, moduleRevs: map[string]uint64{}}
of = &keysOf{modules: map[string]seat.AssignmentsByNodeModuleValue{}, moduleRevs: map[string]uint64{}}
}
var out []KeyWrite
deleteModules := func(keep map[string]bool) {
@@ -440,21 +440,21 @@ func (k *Keys) Writes(node string, after *seat.Placement) ([]KeyWrite, error) {
committed[m.Module] = m
}
}
n := seat.PlacementByNodeNodeValue{Node: node, Decided: after.Decided, Pins: after.Pins, Holds: after.Holds, Modules: make([]seat.PlacementByNodeNodeValueModulesItem, 0, len(after.Modules))}
n := seat.AssignmentsByNodeNodeValue{Node: node, Decided: after.Decided, Pins: after.Pins, Holds: after.Holds, Modules: make([]seat.AssignmentsByNodeNodeValueModulesItem, 0, len(after.Modules))}
keep := map[string]bool{}
for _, m := range after.Modules {
if err := checkPlacementModule(m.Module); err != nil {
return nil, err
}
digest := Digest(m)
n.Modules = append(n.Modules, seat.PlacementByNodeNodeValueModulesItem{Module: m.Module, Digest: digest})
n.Modules = append(n.Modules, seat.AssignmentsByNodeNodeValueModulesItem{Module: m.Module, Digest: digest})
keep[m.Module] = true
was, wasPlaced := committed[m.Module]
if held, ok := of.modules[m.Module]; ok && Digest(held.Placed) == digest &&
(held.Previous == nil || wasPlaced && Digest(was) == digest) {
continue // the key holds it, and no previous value a reader could take in its place
}
v := seat.PlacementByNodeModuleValue{Node: node, Placed: m}
v := seat.AssignmentsByNodeModuleValue{Node: node, Placed: m}
if wasPlaced && Digest(was) != digest {
v.Previous = &was
}
@@ -7,7 +7,7 @@ import (
"sync"
"time"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
)
// Placed is where the placement (the seat mesh-placement's state `placement`, held by the assigner; ADR 0567, ADR 0549 §3) places each module, and since when:
@@ -65,7 +65,7 @@ func (p *Placed) Put(key string, value []byte, deleted bool, at time.Time) (node
var derr error
var named string
if kind == NodeKind {
var n seat.PlacementByNodeNodeValue
var n seat.AssignmentsByNodeNodeValue
n, derr = DecodeNode(value)
named = n.Node
for _, r := range n.Modules {
@@ -15,7 +15,7 @@ import (
"strconv"
"strings"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/keys"
)
@@ -15,11 +15,11 @@ import (
const Seat = "mesh-module-assigner"
const (
VerbPlacement = "placement"
VerbProposals = "proposals"
VerbPropose = "propose"
VerbWithdraw = "withdraw"
VerbAgain = "again"
VerbAssignments = "assignments"
VerbProposals = "proposals"
VerbPropose = "propose"
VerbWithdraw = "withdraw"
VerbAgain = "again"
)
const (
@@ -31,7 +31,7 @@ const (
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbPlacement, VerbProposals, VerbPropose, VerbWithdraw, VerbAgain}
return []string{VerbAssignments, VerbProposals, VerbPropose, VerbWithdraw, VerbAgain}
}
// Events is every event published on the seat's subject: a new copy each call.
@@ -58,18 +58,18 @@ const SubjectPlacementJudged = "mesh.seat.mesh-module-assigner.event.placement-j
const SubjectPlacementChanged = "mesh.seat.mesh-module-assigner.event.placement-changed"
const (
StatePlacement = "placement"
StateAssignments = "assignments"
StateProposals = "proposals"
StateCandidates = "candidates"
StatePulses = "pulses"
StateSeatHolders = "seat-holders"
)
// PlacementByNodeNodeKey is the key of one value of the state placement under the form "<node>.node".
func PlacementByNodeNodeKey(node string) string { return node + ".node" }
// AssignmentsByNodeNodeKey is the key of one value of the state assignments under the form "<node>.node".
func AssignmentsByNodeNodeKey(node string) string { return node + ".node" }
// ParsePlacementByNodeNodeKey reads the parts of a key of the form "<node>.node", in the order of the form; the last result is false for a key of another form.
func ParsePlacementByNodeNodeKey(key string) (string, bool) {
// ParseAssignmentsByNodeNodeKey reads the parts of a key of the form "<node>.node", in the order of the form; the last result is false for a key of another form.
func ParseAssignmentsByNodeNodeKey(key string) (string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".node")
if !found0 || v0 == "" {
@@ -82,9 +82,9 @@ func ParsePlacementByNodeNodeKey(key string) (string, bool) {
return v0, true
}
// DecodePlacementByNodeNodeValue reads one value of the state placement under the form "<node>.node", refusing a field PlacementByNodeNodeValue does not know and any text after the value.
func DecodePlacementByNodeNodeValue(raw []byte) (PlacementByNodeNodeValue, error) {
var v PlacementByNodeNodeValue
// DecodeAssignmentsByNodeNodeValue reads one value of the state assignments under the form "<node>.node", refusing a field AssignmentsByNodeNodeValue does not know and any text after the value.
func DecodeAssignmentsByNodeNodeValue(raw []byte) (AssignmentsByNodeNodeValue, error) {
var v AssignmentsByNodeNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
@@ -96,11 +96,11 @@ func DecodePlacementByNodeNodeValue(raw []byte) (PlacementByNodeNodeValue, error
return v, nil
}
// PlacementByNodeModuleKey is the key of one value of the state placement under the form "<node>.<module>".
func PlacementByNodeModuleKey(node, module string) string { return node + "." + module }
// AssignmentsByNodeModuleKey is the key of one value of the state assignments under the form "<node>.<module>".
func AssignmentsByNodeModuleKey(node, module string) string { return node + "." + module }
// ParsePlacementByNodeModuleKey reads the parts of a key of the form "<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParsePlacementByNodeModuleKey(key string) (string, string, bool) {
// ParseAssignmentsByNodeModuleKey reads the parts of a key of the form "<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParseAssignmentsByNodeModuleKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
@@ -118,9 +118,9 @@ func ParsePlacementByNodeModuleKey(key string) (string, string, bool) {
return v0, v2, true
}
// DecodePlacementByNodeModuleValue reads one value of the state placement under the form "<node>.<module>", refusing a field PlacementByNodeModuleValue does not know and any text after the value.
func DecodePlacementByNodeModuleValue(raw []byte) (PlacementByNodeModuleValue, error) {
var v PlacementByNodeModuleValue
// DecodeAssignmentsByNodeModuleValue reads one value of the state assignments under the form "<node>.<module>", refusing a field AssignmentsByNodeModuleValue does not know and any text after the value.
func DecodeAssignmentsByNodeModuleValue(raw []byte) (AssignmentsByNodeModuleValue, error) {
var v AssignmentsByNodeModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
@@ -547,8 +547,8 @@ type Warrant struct {
Warrant string `json:"warrant"`
}
// PlacementInput is what placement takes.
type PlacementInput struct {
// AssignmentsInput is what assignments takes.
type AssignmentsInput struct {
// "true": the changes in order, from the stream and the state's revisions
History string `json:"history,omitempty"`
// one module: the nodes it is placed on
@@ -559,20 +559,20 @@ type PlacementInput struct {
Seats string `json:"seats,omitempty"`
}
// The values of PlacementInput's history.
// The values of AssignmentsInput's history.
const (
PlacementInputHistoryTrue = "true"
PlacementInputHistoryFalse = "false"
AssignmentsInputHistoryTrue = "true"
AssignmentsInputHistoryFalse = "false"
)
// The values of PlacementInput's seats.
// The values of AssignmentsInput's seats.
const (
PlacementInputSeatsTrue = "true"
PlacementInputSeatsFalse = "false"
AssignmentsInputSeatsTrue = "true"
AssignmentsInputSeatsFalse = "false"
)
// PlacementAnswer is what placement answers.
type PlacementAnswer = map[string]any
// AssignmentsAnswer is what assignments answers.
type AssignmentsAnswer = map[string]any
// ProposalsInput is what proposals takes.
type ProposalsInput struct {
@@ -628,26 +628,26 @@ type PlacementJudgedEvent = PlacementEvent
// PlacementChangedEvent is the body of the event placement-changed.
type PlacementChangedEvent = PlacementEvent
// PlacementByNodeNodeValueModulesItem is a type of the contract.
type PlacementByNodeNodeValueModulesItem struct {
// AssignmentsByNodeNodeValueModulesItem is a type of the contract.
type AssignmentsByNodeNodeValueModulesItem struct {
// the digest of the module key's value it stands for
Digest string `json:"digest"`
Module string `json:"module"`
}
// PlacementByNodeNodeValue is one value of the state placement under the key <node>.node.
type PlacementByNodeNodeValue struct {
// AssignmentsByNodeNodeValue is one value of the state assignments under the key <node>.node.
type AssignmentsByNodeNodeValue struct {
Decided NodeDecided `json:"decided"`
Holds []Holding `json:"holds,omitempty"`
// the modules placed, in the order the composition reads them
Modules []PlacementByNodeNodeValueModulesItem `json:"modules"`
Node string `json:"node"`
Pins map[string]Provider `json:"pins,omitempty"`
V int64 `json:"v,omitempty"`
Modules []AssignmentsByNodeNodeValueModulesItem `json:"modules"`
Node string `json:"node"`
Pins map[string]Provider `json:"pins,omitempty"`
V int64 `json:"v,omitempty"`
}
// PlacementByNodeModuleValue is one value of the state placement under the key <node>.<module>.
type PlacementByNodeModuleValue struct {
// AssignmentsByNodeModuleValue is one value of the state assignments under the key <node>.<module>.
type AssignmentsByNodeModuleValue struct {
Node string `json:"node"`
Placed PlacedModule `json:"placed"`
// the value it replaces while a write is under way
@@ -21,19 +21,19 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v PlacementProposedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-module-assigner\\.event\\.placement-proposed$")},
{name: "a node's key", subject: "", given: "{\"decided\":{\"address\":\"10.0.0.2\"},\"modules\":[{\"digest\":\"0123456789abcdef\",\"module\":\"plex\"}],\"node\":\"ace\",\"v\":1}", wire: "{\"decided\":{\"address\":\"10.0.0.2\"},\"modules\":[{\"digest\":\"0123456789abcdef\",\"module\":\"plex\"}],\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v PlacementByNodeNodeValue
var v AssignmentsByNodeNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node that came with a tunnel", subject: "", given: "{\"decided\":{\"tunnel\":{\"interface\":\"wg0\",\"peers\":[{\"address\":\"10.77.0.9/32\",\"public-key\":\"k2\"}],\"port\":51820,\"public-key\":\"k1\"}},\"modules\":[],\"node\":\"ace\",\"v\":1}", wire: "{\"decided\":{\"tunnel\":{\"interface\":\"wg0\",\"peers\":[{\"address\":\"10.77.0.9/32\",\"public-key\":\"k2\"}],\"port\":51820,\"public-key\":\"k1\"}},\"modules\":[],\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v PlacementByNodeNodeValue
var v AssignmentsByNodeNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a module's key", subject: "", given: "{\"node\":\"ace\",\"placed\":{\"bindings\":{\"database\":{\"module\":\"postgres\",\"node\":\"ace\"}},\"module\":\"plex\",\"ports\":{\"32400\":32400}},\"v\":1}", wire: "{\"node\":\"ace\",\"placed\":{\"bindings\":{\"database\":{\"module\":\"postgres\",\"node\":\"ace\"}},\"module\":\"plex\",\"ports\":{\"32400\":32400}},\"v\":1}",
decode: func(b []byte) (any, error) {
var v PlacementByNodeModuleValue
var v AssignmentsByNodeModuleValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
+3
View File
@@ -0,0 +1,3 @@
module git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner
go 1.26.0
+29
View File
@@ -81,6 +81,35 @@
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshmoduleassigner",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshmoduleassigner"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshmoduleassignerclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshmoduleassignerclient"
],
"series": "0.1"
},
{
"name": "ts-meshmoduleassigner",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshmoduleassigner"
],
"series": "0.1",
"generated-from": "."
}
]
},
@@ -1,8 +0,0 @@
module git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
@@ -1,8 +0,0 @@
module git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
@@ -1,326 +0,0 @@
// Package meshwantsclient is the client of the seat mesh-wants: the keys of its state wants, and the check and the codec
// of a want, a node's own want and a hold, which every module that reads or writes wants shares (the assigner that writes
// them, the node-engine, the provisioner and the artifact store that read them). The layouts are the seat's contract; this
// file writes the version and refuses what the earlier codec refused (novox/hq ADR 0528, ADR 0539, ADR 0546, ADR 0634 §4).
package meshwantsclient
import (
"encoding/json"
"fmt"
"regexp"
"strings"
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The three values of the state wants, in the seat's types.
type (
// Want is a node's want for a module: the fingerprint it should run and the digest of the one composition it allows,
// or Removed, or Self when the node composes it itself; Newest and Rule name what holds the node back.
Want = seat.WantsByWantNodeModuleValue
// NodeWant is a node's own want: the digest of the node's own composition it may apply.
NodeWant = seat.WantsByWantNodeNodeValue
// Held is a fingerprint the assigner holds: the node whose health check failed on it, and the check's words.
Held = seat.WantsByHeldModuleFingerprintValue
)
// The rules of ADR 0429 §6 and ADR 0539 clause 6, by number, as a want or a difference names the one that holds a node back.
const (
RuleBuildDone = 1 // the build of F is done
RuleNotHeld = 2 // F is not held
RuleCanary = 3 // the node is the module's canary, or the canary runs F healthy
RuleStep = 4 // at most step other nodes are ahead (step 0: no limit, ADR 0528 §3)
RuleProviders = 5 // every provider the module requires runs healthy on that node, or at its canary
RuleGrant = 6 // the module's credential for F exists (grants.<module>.<fp>)
RuleComposed = 7 // the module's composition for that node at F exists (ADR 0539 §6)
// RuleProvisions: every provision the module requires on that node is provided (novox/hq ADR 0570 §4).
RuleProvisions = 8
)
// ruleRead is the highest rule a reader takes (novox/hq ADR 0614 §2, §4): 9 and 10 are kept for the verb wait and the
// capability hold of a later release.
const ruleRead = 10
// The answers a want carries to a failed backup before an apply (ADR 0539 clause 16).
const (
// BackupAgain is the answer to back the module up again before the apply.
BackupAgain = seat.WantsByWantNodeModuleValueBackupAgain
// BackupSkip is the answer to go on without a backup.
BackupSkip = seat.WantsByWantNodeModuleValueBackupSkip
)
// NodeWord is the last part of a node's own key.
const NodeWord = "node"
const (
wantWord = "want"
heldWord = "held"
srcWord = "src2:"
)
var (
moduleNameOK = regexp.MustCompile(`^[-/_a-zA-Z0-9]+(\.[-/_a-zA-Z0-9]+)*$`)
digestForm = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
)
func moduleKeyPart(module string) (string, error) {
if module == "" {
return "", fmt.Errorf("wants: the module name is empty")
}
if !moduleNameOK.MatchString(module) {
return "", fmt.Errorf("wants: the module name %q holds a wildcard, whitespace, '=', an empty part between dots or a character a key refuses", module)
}
return keys.EscapeName(module), nil
}
func parseModuleKeyPart(part string) (string, error) {
if err := keys.CheckName("module", part); err != nil {
return "", err
}
module, err := keys.UnescapeName(part)
if err != nil {
return "", err
}
if back, err := moduleKeyPart(module); err != nil || back != part {
return "", fmt.Errorf("wants: the module part %q is not a module name as a key writes it", part)
}
return module, nil
}
func checkFingerprint(fp, of string) error {
if part, ok := strings.CutPrefix(fp, srcWord); !ok || !keys.IsHex64(part) {
return fmt.Errorf("wants: %s: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", of, fp, srcWord)
}
return nil
}
func checkDigest(digest, of string) error {
if !digestForm.MatchString(digest) {
return fmt.Errorf("wants: %s %q is not sha256:<64 lowercase hex digits>", of, digest)
}
return nil
}
func checkRule(rule int64, of string) error {
if rule < RuleBuildDone || rule > ruleRead {
return fmt.Errorf("wants: %s names the rule %d, which is not one of 1 to %d", of, rule, ruleRead)
}
return nil
}
// WantKey is the key, in the state wants, of a node's want for a module: `want.<node>.<module>`. A module named `node` is
// refused, since `want.<node>.node` is the node's own key.
func WantKey(node, module string) (string, error) {
if err := keys.CheckName("node", node); err != nil {
return "", err
}
part, err := moduleKeyPart(module)
if err != nil {
return "", err
}
if module == NodeWord {
return "", fmt.Errorf(`wants: the module name "node" is refused: want.%s.node is the node's own key`, node)
}
return seat.WantsByWantNodeModuleKey(node, part), nil
}
// WantNodeKey is the key of a node's own want: `want.<node>.node`.
func WantNodeKey(node string) (string, error) {
if err := keys.CheckName("node", node); err != nil {
return "", err
}
return seat.WantsByWantNodeNodeKey(node), nil
}
// ParseWantNodeKey is the inverse of WantNodeKey; it refuses a module's want key and any key of another form.
func ParseWantNodeKey(key string) (node string, err error) {
node, ok := seat.ParseWantsByWantNodeNodeKey(key)
if !ok || keys.CheckName("node", node) != nil {
return "", fmt.Errorf("wants: %q is not a node's own want key, want.<node>.node", key)
}
return node, nil
}
// ParseWantKey is the inverse of WantKey; it refuses a held key, the node's own want key and any key of another form.
func ParseWantKey(key string) (node, module string, err error) {
refused := fmt.Errorf("wants: %q is not a want key, want.<node>.<module>", key)
node, part, ok := seat.ParseWantsByWantNodeModuleKey(key)
if !ok || part == NodeWord || keys.CheckName("node", node) != nil {
return "", "", refused
}
if module, err = parseModuleKeyPart(part); err != nil {
return "", "", refused
}
return node, module, nil
}
// HeldKey is the key of a module's fingerprint the assigner holds: `held.<module>.<hex>`, the hex being the
// fingerprint's key part.
func HeldKey(module, fingerprint string) (string, error) {
if err := keys.CheckName("module", module); err != nil {
return "", err
}
if err := checkFingerprint(fingerprint, "a held key's fingerprint"); err != nil {
return "", err
}
return seat.WantsByHeldModuleFingerprintKey(module, strings.TrimPrefix(fingerprint, srcWord)), nil
}
// ParseHeldKey is the inverse of HeldKey: the module and the fingerprint (`src2:` and the hex).
func ParseHeldKey(key string) (module, fingerprint string, err error) {
module, part, ok := seat.ParseWantsByHeldModuleFingerprintKey(key)
if !ok || keys.CheckName("module", module) != nil || !keys.IsHex64(part) {
return "", "", fmt.Errorf("wants: %q is not a held key, held.<module>.<fingerprint hex>", key)
}
return module, srcWord + part, nil
}
func checkWant(w Want) error {
if w.Removed {
if w.Fp != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" || w.Release != "" {
return fmt.Errorf("wants: a removed want names no fingerprint, composition, rule, backup answer or put")
}
return nil
}
if w.Hold && !w.Put {
return fmt.Errorf("wants: only a put holds a want")
}
if w.Put && (w.Newest != "" || w.Rule != 0) {
return fmt.Errorf("wants: a put's want is held back by no rule")
}
if err := checkFingerprint(w.Fp, "a want's fingerprint"); err != nil {
return err
}
// A node the catalogue places composes its own modules (ADR 0547 §2): its want is Self and names the build alone. Any
// other want names the one composition it allows (ADR 0539 clause 1).
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("wants: the want of %s says the node composes it, and names a composition", w.Fp)
case !w.Self && w.Composed == "":
return fmt.Errorf("wants: the want of %s names no composition: a want names the digest of the one composition it allows (ADR 0539 clause 1), unless the node composes it itself", w.Fp)
case w.Composed != "":
if err := checkDigest(w.Composed, "a want's composition digest"); err != nil {
return err
}
}
if w.Yes != "" {
if err := checkDigest(w.Yes, "the digest a want's yes allows"); err != nil {
return err
}
} else if w.Release != "" {
return fmt.Errorf("wants: the want of %s names the release %s and no yes it gave", w.Fp, w.Release)
}
switch w.Backup {
case "", BackupAgain, BackupSkip:
default:
return fmt.Errorf("wants: the backup answer %q is not %s or %s", w.Backup, BackupAgain, BackupSkip)
}
if w.Newest == "" && w.Rule == 0 {
return nil
}
if err := checkFingerprint(w.Newest, "a want's newest fingerprint"); err != nil {
return err
}
if w.Newest == w.Fp {
return fmt.Errorf("wants: a want at its newest fingerprint names no rule holding it back")
}
return checkRule(w.Rule, "a want held back")
}
// CheckWant refuses a want the codec refuses.
func CheckWant(w Want) error { return checkWant(w) }
// EncodeWant is w as JSON at the kept values' version; it refuses a fingerprint not of the new form, a removed want naming
// one, and a want that names no composition unless the node composes it itself.
func EncodeWant(w Want) ([]byte, error) {
if err := checkWant(w); err != nil {
return nil, err
}
w.V = keys.Version
return json.Marshal(w)
}
// DecodeWant reads a want; it refuses an unknown version (keys.ErrUnknownVersion), a field the contract does not know and
// what EncodeWant refuses.
func DecodeWant(data []byte) (Want, error) {
if err := keys.CheckVersion(data); err != nil {
return Want{}, err
}
w, err := seat.DecodeWantsByWantNodeModuleValue(data)
if err != nil {
return Want{}, fmt.Errorf("wants: value does not fit its shape: %w", err)
}
if err := checkWant(w); err != nil {
return Want{}, err
}
return w, nil
}
// SameWant says whether two wants are the same value, so the assigner writes only when they differ.
func SameWant(a, b Want) bool { return a == b }
func checkNodeWant(w NodeWant) error {
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("wants: the node's own want says the node composes it, and names a composition")
case w.Self:
return nil
}
return checkDigest(w.Composed, "the node's own want's composition digest")
}
// EncodeNodeWant is w as JSON; it refuses a digest not of the sha256 form, and a want that names no composition unless it
// says the node composes its own (Self, ADR 0547 §2).
func EncodeNodeWant(w NodeWant) ([]byte, error) {
if err := checkNodeWant(w); err != nil {
return nil, err
}
w.V = keys.Version
return json.Marshal(w)
}
// DecodeNodeWant reads a node's own want; it refuses an unknown version, a field a node's own want does not have (a
// fingerprint among them), and what EncodeNodeWant refuses.
func DecodeNodeWant(data []byte) (NodeWant, error) {
if err := keys.CheckVersion(data); err != nil {
return NodeWant{}, err
}
w, err := seat.DecodeWantsByWantNodeNodeValue(data)
if err != nil {
return NodeWant{}, fmt.Errorf("wants: a node's own want does not fit its shape: %w", err)
}
if err := checkNodeWant(w); err != nil {
return NodeWant{}, err
}
return w, nil
}
func checkHeld(h Held) error { return keys.CheckName("node", h.Node) }
// EncodeHeld is h as JSON; it refuses a node name a key refuses.
func EncodeHeld(h Held) ([]byte, error) {
if err := checkHeld(h); err != nil {
return nil, err
}
h.V = keys.Version
return json.Marshal(h)
}
// DecodeHeld reads a hold; it refuses an unknown version and what EncodeHeld refuses.
func DecodeHeld(data []byte) (Held, error) {
if err := keys.CheckVersion(data); err != nil {
return Held{}, err
}
h, err := seat.DecodeWantsByHeldModuleFingerprintValue(data)
if err != nil {
return Held{}, fmt.Errorf("wants: value does not fit its shape: %w", err)
}
if err := checkHeld(h); err != nil {
return Held{}, err
}
return h, nil
}
// SameHeld says whether two holds are the same value.
func SameHeld(a, b Held) bool { return a == b }
@@ -1,162 +0,0 @@
package meshwantsclient
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/keys"
)
func rep(c string) string { return strings.Repeat(c, 64) }
// TestWantsAreWhatTheEarlierCodecWrote holds the values and keys equal to what go/delivery's codec wrote for the same
// values (the golden texts are its output; the field order is the contract's now, so values are compared decoded), and the
// refusals it made.
func TestWantsAreWhatTheEarlierCodecWrote(t *testing.T) {
a, b, c, d := "src2:"+rep("a"), "src2:"+rep("b"), "sha256:"+rep("c"), "sha256:"+rep("d")
sameJSON := func(t *testing.T, got []byte, want string) {
t.Helper()
var g, w any
if err := json.Unmarshal(got, &g); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(want), &w); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(g, w) {
t.Fatalf("wrote %s, the earlier codec wrote %s", got, want)
}
}
t.Run("wants", func(t *testing.T) {
for name, row := range map[string]struct {
w Want
golden string
}{
"composed": {Want{Fp: a, Composed: c, Words: "w"}, `{"v":1,"fp":"` + a + `","composed":"` + c + `","words":"w"}`},
"held back": {Want{Fp: a, Self: true, Newest: b, Rule: RuleGrant, Backup: BackupAgain, Words: "held"}, `{"v":1,"fp":"` + a + `","newest":"` + b + `","rule":6,"backup":"again","words":"held","self":true}`},
"put": {Want{Fp: a, Self: true, Put: true, Hold: true, Yes: d, Release: "r1"}, `{"v":1,"fp":"` + a + `","put":true,"hold":true,"yes":"` + d + `","release":"r1","self":true}`},
"removed": {Want{Removed: true, Words: "gone"}, `{"v":1,"removed":true,"words":"gone"}`},
} {
t.Run(name, func(t *testing.T) {
raw, err := EncodeWant(row.w)
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, row.golden)
back, err := DecodeWant(raw)
if err != nil || !SameWant(back, func() Want { w := row.w; w.V = keys.Version; return w }()) {
t.Fatalf("read back %+v, %v", back, err)
}
})
}
})
t.Run("refused", func(t *testing.T) {
for name, w := range map[string]Want{
"old fingerprint": {Fp: "src1:abc", Composed: c},
"no composition": {Fp: a},
"self and composition": {Fp: a, Self: true, Composed: c},
"hold without put": {Fp: a, Self: true, Hold: true},
"put held back": {Fp: a, Self: true, Put: true, Newest: b, Rule: RuleStep},
"removed names a rule": {Removed: true, Rule: 1},
"release without yes": {Fp: a, Self: true, Release: "r"},
"backup answer unknown": {Fp: a, Self: true, Backup: "later"},
"newest equals fp": {Fp: a, Self: true, Newest: a, Rule: RuleStep},
"rule 11": {Fp: a, Self: true, Newest: b, Rule: 11},
"newest without a rule": {Fp: a, Self: true, Newest: b},
"composition not sha256": {Fp: a, Composed: "sha256:xyz"},
} {
if _, err := EncodeWant(w); err == nil {
t.Errorf("%s: written", name)
}
}
if _, err := EncodeWant(Want{Fp: a, Self: true, Newest: b, Rule: 10}); err != nil {
t.Errorf("rule 10 is read: %v", err)
}
if _, err := DecodeWant([]byte(`{"fp":"` + a + `","self":true}`)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("no version: %v", err)
}
if _, err := DecodeWant([]byte(`{"v":1,"fp":"` + a + `","self":true,"extra":1}`)); err == nil {
t.Error("an unknown field is read")
}
})
t.Run("node want and hold", func(t *testing.T) {
raw, err := EncodeNodeWant(NodeWant{Composed: c, Words: "n"})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"composed":"`+c+`","words":"n"}`)
raw, err = EncodeNodeWant(NodeWant{Self: true})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"composed":"","self":true}`)
if back, err := DecodeNodeWant(raw); err != nil || !back.Self {
t.Fatalf("%+v %v", back, err)
}
for name, w := range map[string]NodeWant{"none": {}, "both": {Self: true, Composed: c}, "bad": {Composed: "x"}} {
if _, err := EncodeNodeWant(w); err == nil {
t.Errorf("%s: written", name)
}
}
if _, err := DecodeNodeWant([]byte(`{"v":1,"composed":"` + c + `","fp":"` + a + `"}`)); err == nil {
t.Error("a fingerprint in a node's own want is read")
}
raw, err = EncodeHeld(Held{Node: "ace", Words: "unhealthy"})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"node":"ace","words":"unhealthy"}`)
if h, err := DecodeHeld(raw); err != nil || !SameHeld(h, Held{V: 1, Node: "ace", Words: "unhealthy"}) {
t.Fatalf("%+v %v", h, err)
}
if _, err := EncodeHeld(Held{Node: "a.b"}); err == nil {
t.Error("a node with a dot is held")
}
})
t.Run("keys", func(t *testing.T) {
for _, row := range []struct{ module, want string }{
{"mesh-delivery-assigner", "want.ace.mesh-delivery-assigner"},
{"novox.be", "want.ace.novox=2ebe"},
} {
k, err := WantKey("ace", row.module)
if err != nil || k != row.want {
t.Fatalf("WantKey %s = %q, %v", row.module, k, err)
}
if n, m, err := ParseWantKey(k); err != nil || n != "ace" || m != row.module {
t.Fatalf("ParseWantKey %q = %q %q %v", k, n, m, err)
}
}
if k, err := HeldKey("mesh-delivery-assigner", a); err != nil || k != "held.mesh-delivery-assigner."+rep("a") {
t.Fatalf("HeldKey = %q %v", k, err)
}
if m, fp, err := ParseHeldKey("held.mesh-delivery-assigner." + rep("a")); err != nil || m != "mesh-delivery-assigner" || fp != a {
t.Fatalf("ParseHeldKey = %q %q %v", m, fp, err)
}
if k, err := WantNodeKey("ace"); err != nil || k != "want.ace.node" {
t.Fatalf("WantNodeKey = %q %v", k, err)
}
if n, err := ParseWantNodeKey("want.ace.node"); err != nil || n != "ace" {
t.Fatalf("ParseWantNodeKey = %q %v", n, err)
}
if _, err := WantKey("ace", "node"); err == nil {
t.Error("a module named node has a want key")
}
if _, err := HeldKey("novox.be", a); err == nil {
t.Error("a module with a dot has a held key")
}
for _, key := range []string{"want.ace.node", "held.m." + rep("a"), "ace.m", "want.ace", "want..m"} {
if _, _, err := ParseWantKey(key); err == nil {
t.Errorf("%q is a want key", key)
}
}
if _, err := ParseWantNodeKey("want.ace.m"); err == nil {
t.Error("a module's want is a node's own")
}
if _, _, err := ParseHeldKey("want.ace.m"); err == nil {
t.Error("a want key is a held key")
}
})
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner
go 1.26.0
-337
View File
@@ -1,337 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshwants is the contract of the seat "mesh-wants", defined whole (seats/mesh-wants/seat.json): The seat that says what each node should run and whether each module's new builds wait for a person (ADR 0565, ADR 0539).
package meshwants
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// Seat is the seat's name.
const Seat = "mesh-wants"
const (
VerbUpgrade = "upgrade"
VerbBus = "bus"
)
const (
EventPolicyChanged = "policy-changed"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbUpgrade, VerbBus}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventPolicyChanged}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
const (
Family = "mesh-wants.events"
FamilyMaxAgeDays = 7
)
// SubjectPolicyChanged is where policy-changed is published.
const SubjectPolicyChanged = "mesh.seat.mesh-wants.event.policy-changed"
const (
StateWants = "wants"
StateDifferences = "differences"
StateCompositions = "compositions"
)
// WantsByWantNodeModuleKey is the key of one value of the state wants under the form "want.<node>.<module>".
func WantsByWantNodeModuleKey(node, module string) string { return "want." + node + "." + module }
// ParseWantsByWantNodeModuleKey reads the parts of a key of the form "want.<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParseWantsByWantNodeModuleKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "want.") {
return "", "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeWantsByWantNodeModuleValue reads one value of the state wants under the form "want.<node>.<module>", refusing a field WantsByWantNodeModuleValue does not know and any text after the value.
func DecodeWantsByWantNodeModuleValue(raw []byte) (WantsByWantNodeModuleValue, error) {
var v WantsByWantNodeModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WantsByWantNodeNodeKey is the key of one value of the state wants under the form "want.<node>.node".
func WantsByWantNodeNodeKey(node string) string { return "want." + node + ".node" }
// ParseWantsByWantNodeNodeKey reads the parts of a key of the form "want.<node>.node", in the order of the form; the last result is false for a key of another form.
func ParseWantsByWantNodeNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "want.") {
return "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".node")
if !found1 || v1 == "" {
return "", false
}
rest = after1
if rest != "" {
return "", false
}
return v1, true
}
// DecodeWantsByWantNodeNodeValue reads one value of the state wants under the form "want.<node>.node", refusing a field WantsByWantNodeNodeValue does not know and any text after the value.
func DecodeWantsByWantNodeNodeValue(raw []byte) (WantsByWantNodeNodeValue, error) {
var v WantsByWantNodeNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WantsByHeldModuleFingerprintKey is the key of one value of the state wants under the form "held.<module>.<fingerprint>".
func WantsByHeldModuleFingerprintKey(module, fingerprint string) string {
return "held." + module + "." + fingerprint
}
// ParseWantsByHeldModuleFingerprintKey reads the parts of a key of the form "held.<module>.<fingerprint>", in the order of the form; the last result is false for a key of another form.
func ParseWantsByHeldModuleFingerprintKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "held.") {
return "", "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeWantsByHeldModuleFingerprintValue reads one value of the state wants under the form "held.<module>.<fingerprint>", refusing a field WantsByHeldModuleFingerprintValue does not know and any text after the value.
func DecodeWantsByHeldModuleFingerprintValue(raw []byte) (WantsByHeldModuleFingerprintValue, error) {
var v WantsByHeldModuleFingerprintValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// DifferencesByNodeModuleKey is the key of one value of the state differences under the form "<node>.<module>".
func DifferencesByNodeModuleKey(node, module string) string { return node + "." + module }
// ParseDifferencesByNodeModuleKey reads the parts of a key of the form "<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParseDifferencesByNodeModuleKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeDifferencesByNodeModuleValue reads one value of the state differences under the form "<node>.<module>", refusing a field DifferencesByNodeModuleValue does not know and any text after the value.
func DecodeDifferencesByNodeModuleValue(raw []byte) (DifferencesByNodeModuleValue, error) {
var v DifferencesByNodeModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// CompositionsByGroupHeadsKey is the key of one value of the state compositions under the form "<group>.<heads>".
func CompositionsByGroupHeadsKey(group, heads string) string { return group + "." + heads }
// ParseCompositionsByGroupHeadsKey reads the parts of a key of the form "<group>.<heads>", in the order of the form; the last result is false for a key of another form.
func ParseCompositionsByGroupHeadsKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeCompositionsByGroupHeadsValue reads one value of the state compositions under the form "<group>.<heads>", refusing a field CompositionsByGroupHeadsValue does not know and any text after the value.
func DecodeCompositionsByGroupHeadsValue(raw []byte) (CompositionsByGroupHeadsValue, error) {
var v CompositionsByGroupHeadsValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// UpgradeInput is what upgrade takes.
type UpgradeInput struct {
// "true": each node's own composition, by its digest, and the assigner's findings
Compare string `json:"compare,omitempty"`
// one module
Module string `json:"module,omitempty"`
// with module: roll, together, record, or default to take a person's choice back
Policy string `json:"policy,omitempty"`
// with policy: why, required and kept
Why string `json:"why,omitempty"`
}
// The values of UpgradeInput's compare.
const (
UpgradeInputCompareTrue = "true"
UpgradeInputCompareFalse = "false"
)
// UpgradeAnswer is what upgrade answers.
type UpgradeAnswer = map[string]any
// BusInput is what bus takes.
type BusInput struct {
}
// BusAnswer is what bus answers.
type BusAnswer = map[string]any
// PolicyChangedEvent is the body of the event policy-changed.
type PolicyChangedEvent = map[string]any
// WantsByWantNodeModuleValue is one value of the state wants under the key want.<node>.<module>.
type WantsByWantNodeModuleValue struct {
// again or skip
Backup string `json:"backup,omitempty"`
Composed string `json:"composed,omitempty"`
Fp string `json:"fp,omitempty"`
Hold bool `json:"hold,omitempty"`
Newest string `json:"newest,omitempty"`
Put bool `json:"put,omitempty"`
Release string `json:"release,omitempty"`
Removed bool `json:"removed,omitempty"`
// the rule that holds the node back, 1 to 10
Rule int64 `json:"rule,omitempty"`
Self bool `json:"self,omitempty"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
Yes string `json:"yes,omitempty"`
}
// The values of WantsByWantNodeModuleValue's backup.
const (
WantsByWantNodeModuleValueBackupAgain = "again"
WantsByWantNodeModuleValueBackupSkip = "skip"
)
// WantsByWantNodeNodeValue is one value of the state wants under the key want.<node>.node.
type WantsByWantNodeNodeValue struct {
Composed string `json:"composed"`
Self bool `json:"self,omitempty"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
}
// WantsByHeldModuleFingerprintValue is one value of the state wants under the key held.<module>.<fingerprint>.
type WantsByHeldModuleFingerprintValue struct {
Node string `json:"node"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
}
// DifferencesByNodeModuleValue is one value of the state differences under the key <node>.<module>.
type DifferencesByNodeModuleValue struct {
Rule int64 `json:"rule,omitempty"`
Sent string `json:"sent,omitempty"`
SentBuild string `json:"sent-build,omitempty"`
Unexplained bool `json:"unexplained,omitempty"`
V int64 `json:"v,omitempty"`
Want string `json:"want,omitempty"`
WantRemoved bool `json:"want-removed,omitempty"`
Words string `json:"words,omitempty"`
}
// CompositionsByGroupHeadsValue is one value of the state compositions under the key <group>.<heads>.
type CompositionsByGroupHeadsValue struct {
Group string `json:"group"`
// 12 lowercase hex digits
Heads string `json:"heads"`
Machine string `json:"machine,omitempty"`
Machines []string `json:"machines,omitempty"`
Sdk string `json:"sdk,omitempty"`
// pass, fail or waits
State string `json:"state"`
// milliseconds
Took int64 `json:"took,omitempty"`
V int64 `json:"v,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Words string `json:"words,omitempty"`
}
@@ -1,76 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshwants
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-wants/fixtures: each decodes into its type, a field the type does not
// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's.
func TestFixtures(t *testing.T) {
cases := []struct {
name, subject, given, wire string
decode func([]byte) (any, error)
subjectIs *regexp.Regexp
}{
{name: "a want", subject: "", given: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}", wire: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByWantNodeModuleValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's own want", subject: "", given: "{\"composed\":\"sha256:aa\",\"self\":true,\"v\":1}", wire: "{\"composed\":\"sha256:aa\",\"self\":true,\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByWantNodeNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a held fingerprint", subject: "", given: "{\"node\":\"ace\",\"v\":1}", wire: "{\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByHeldModuleFingerprintValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a difference", subject: "", given: "{\"unexplained\":true,\"v\":1,\"want\":\"ab12\"}", wire: "{\"unexplained\":true,\"v\":1,\"want\":\"ab12\"}",
decode: func(b []byte) (any, error) {
var v DifferencesByNodeModuleValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a composition", subject: "", given: "{\"group\":\"g1\",\"heads\":\"0123456789ab\",\"state\":\"pass\",\"v\":1}", wire: "{\"group\":\"g1\",\"heads\":\"0123456789ab\",\"state\":\"pass\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v CompositionsByGroupHeadsValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
if err != nil {
t.Errorf("%s: does not decode: %v", c.name, err)
continue
}
back, err := json.Marshal(v)
if err != nil {
t.Errorf("%s: does not encode: %v", c.name, err)
continue
}
var got, want any
if err := json.Unmarshal(back, &got); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(c.wire), &want); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire)
}
if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) {
t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants
go 1.26.0
-58
View File
@@ -1,58 +0,0 @@
{
"module": "mesh-placement-contracts",
"version": "1",
"build": {
"artifacts": [
{
"name": "go-meshmoduleassigner",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshmoduleassigner"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshwants",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshwants"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshmoduleassignerclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshmoduleassignerclient"
],
"series": "0.1"
},
{
"name": "ts-meshmoduleassigner",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshmoduleassigner"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-meshwants",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshwants"
],
"series": "0.1",
"generated-from": "."
}
]
},
"resources": []
}
-11
View File
@@ -1,11 +0,0 @@
{
"format": 1,
"seats": [
"mesh-module-assigner",
"mesh-wants",
"node-service-manager",
"node-package-manager",
"node-container-runtime",
"node-environment"
]
}
@@ -1,47 +0,0 @@
[
{
"name": "a want",
"of": "state:wants/want.<node>.<module>",
"given": {
"v": 1,
"fp": "ab12",
"rule": 3
}
},
{
"name": "a node's own want",
"of": "state:wants/want.<node>.node",
"given": {
"v": 1,
"composed": "sha256:aa",
"self": true
}
},
{
"name": "a held fingerprint",
"of": "state:wants/held.<module>.<fingerprint>",
"given": {
"v": 1,
"node": "ace"
}
},
{
"name": "a difference",
"of": "state:differences/<node>.<module>",
"given": {
"v": 1,
"want": "ab12",
"unexplained": true
}
},
{
"name": "a composition",
"of": "state:compositions/<group>.<heads>",
"given": {
"v": 1,
"group": "g1",
"heads": "0123456789ab",
"state": "pass"
}
}
]
-284
View File
@@ -1,284 +0,0 @@
{
"format": 1,
"seat": "mesh-wants",
"scope": "mesh",
"grain": "whole",
"domain": "Placement",
"decision": "novox/hq ADR 0565, ADR 0634, ADR 0635",
"description": "The seat that says what each node should run and whether each module's new builds wait for a person (ADR 0565, ADR 0539).",
"verbs": [
{
"name": "upgrade",
"description": "Whether each module's new builds wait for a person: its policy (roll, together or record) and where it comes from (a person, the module, the bus, its irreplaceable data, the default); the bus is never rolled. With module, that one; with module, policy and why, a person's choice for it, recorded as a hand act; with policy default, the choice taken back. With compare, each node's own composition and the assigner's findings.",
"input": {
"properties": {
"compare": {
"description": "\"true\": each node's own composition, by its digest, and the assigner's findings",
"enum": [
"true",
"false"
],
"type": "string"
},
"module": {
"description": "one module",
"type": "string"
},
"policy": {
"description": "with module: roll, together, record, or default to take a person's choice back",
"type": "string"
},
"why": {
"description": "with policy: why, required and kept",
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "each module's policy, or the one chosen, or the comparison"
}
},
{
"name": "bus",
"description": "What a bus upgrade would do: the fingerprint of the bus module each node runs against the indexer's newest, and the last bus steps from the hand-act log and the assigner's puts of the bus. Read-only: the step itself is the put on the operator's warrant.",
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what a bus upgrade would do, read only"
}
}
],
"events": [
{
"name": "policy-changed",
"body": {
"type": "object"
}
}
],
"family": {
"name": "mesh-wants.events",
"max-age-days": 7
},
"states": [
{
"name": "wants",
"template": "state",
"key-forms": [
{
"key": "want.<node>.<module>",
"value": {
"type": "object",
"description": "a node's want for a module",
"properties": {
"v": {
"type": "integer"
},
"fp": {
"type": "string"
},
"composed": {
"type": "string"
},
"removed": {
"type": "boolean"
},
"newest": {
"type": "string"
},
"rule": {
"type": "integer",
"description": "the rule that holds the node back, 1 to 10"
},
"backup": {
"type": "string",
"description": "again or skip",
"enum": [
"again",
"skip"
]
},
"words": {
"type": "string"
},
"put": {
"type": "boolean"
},
"hold": {
"type": "boolean"
},
"yes": {
"type": "string"
},
"release": {
"type": "string"
},
"self": {
"type": "boolean"
}
}
},
"description": "a node's want for a module"
},
{
"key": "want.<node>.node",
"value": {
"type": "object",
"description": "a node's own want",
"properties": {
"v": {
"type": "integer"
},
"composed": {
"type": "string"
},
"words": {
"type": "string"
},
"self": {
"type": "boolean"
}
},
"required": [
"composed"
]
},
"description": "a node's own want, naming the digest of its own composition"
},
{
"key": "held.<module>.<fingerprint>",
"value": {
"type": "object",
"description": "a fingerprint the assigner holds",
"properties": {
"v": {
"type": "integer"
},
"node": {
"type": "string"
},
"words": {
"type": "string"
}
},
"required": [
"node"
]
},
"description": "a module's fingerprint the assigner holds"
}
],
"history": 20,
"description": "what each node should run, and the fingerprints held back"
},
{
"name": "differences",
"template": "state",
"key-forms": [
{
"key": "<node>.<module>",
"value": {
"type": "object",
"description": "the difference between a want and what is sent",
"properties": {
"v": {
"type": "integer"
},
"want": {
"type": "string"
},
"want-removed": {
"type": "boolean"
},
"sent": {
"type": "string"
},
"sent-build": {
"type": "string"
},
"rule": {
"type": "integer"
},
"unexplained": {
"type": "boolean"
},
"words": {
"type": "string"
}
}
},
"description": "a module on a node"
}
],
"history": 64,
"description": "differences between a want and what the node-engine is sent"
},
{
"name": "compositions",
"template": "state",
"key-forms": [
{
"key": "<group>.<heads>",
"value": {
"type": "object",
"description": "the composition verdict of a group's check at its heads",
"properties": {
"v": {
"type": "integer"
},
"group": {
"type": "string"
},
"heads": {
"type": "string",
"description": "12 lowercase hex digits"
},
"state": {
"type": "string",
"description": "pass, fail or waits"
},
"words": {
"type": "string"
},
"sdk": {
"type": "string"
},
"machines": {
"type": "array",
"items": {
"type": "string"
}
},
"machine": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
},
"took": {
"type": "integer",
"description": "milliseconds"
}
},
"required": [
"group",
"heads",
"state"
]
},
"description": "a group's check at its heads"
}
],
"history": 5,
"description": "composition verdicts of a group's check; the checker reads them"
}
]
}
-12
View File
@@ -1,12 +0,0 @@
{
"lockfileVersion": 3,
"name": "@novox/mesh-placement-contracts-mesh-module-assigner",
"packages": {
"": {
"name": "@novox/mesh-placement-contracts-mesh-module-assigner",
"version": "0.1.0"
}
},
"requires": true,
"version": "0.1.0"
}
-13
View File
@@ -1,13 +0,0 @@
{
"description": "The contract of the seat mesh-wants, generated from its definition.",
"exports": {
".": "./src/index.ts"
},
"files": [
"src"
],
"name": "@novox/mesh-placement-contracts-mesh-wants",
"type": "module",
"types": "./src/index.ts",
"version": "0.1.0"
}
-211
View File
@@ -1,211 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// The contract of the seat "mesh-wants", defined whole (seats/mesh-wants/seat.json): The seat that says what each node should run and whether each module's new builds wait for a person (ADR 0565, ADR 0539).
export const seat = "mesh-wants";
export const verbs = {
upgrade: "upgrade",
bus: "bus",
} as const;
export const events = {
policyChanged: "policy-changed",
} as const;
export const family = { name: "mesh-wants.events", maxAgeDays: 7 } as const;
export const policyChangedSubject = "mesh.seat.mesh-wants.event.policy-changed";
export const states = {
wants: "wants",
differences: "differences",
compositions: "compositions",
} as const;
/** The key of one value of the state wants under the form "want.<node>.<module>". */
export function wantsByWantNodeModuleKey(node: string, module: string): string {
return "want." + node + "." + module;
}
/** The parts of a key of the form "want.<node>.<module>", or undefined for a key of another form. */
export function parseWantsByWantNodeModuleKey(_key: string): { node: string; module: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("want.")) return undefined;
_rest = _rest.slice(5);
const _at1 = _rest.indexOf(".");
if (_at1 <= 0) return undefined;
const node = _rest.slice(0, _at1);
_rest = _rest.slice(_at1 + 1);
if (_rest === "") return undefined;
const module = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { node, module };
}
/** The key of one value of the state wants under the form "want.<node>.node". */
export function wantsByWantNodeNodeKey(node: string): string {
return "want." + node + ".node";
}
/** The parts of a key of the form "want.<node>.node", or undefined for a key of another form. */
export function parseWantsByWantNodeNodeKey(_key: string): { node: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("want.")) return undefined;
_rest = _rest.slice(5);
const _at1 = _rest.indexOf(".node");
if (_at1 <= 0) return undefined;
const node = _rest.slice(0, _at1);
_rest = _rest.slice(_at1 + 5);
if (_rest !== "") return undefined;
return { node };
}
/** The key of one value of the state wants under the form "held.<module>.<fingerprint>". */
export function wantsByHeldModuleFingerprintKey(module: string, fingerprint: string): string {
return "held." + module + "." + fingerprint;
}
/** The parts of a key of the form "held.<module>.<fingerprint>", or undefined for a key of another form. */
export function parseWantsByHeldModuleFingerprintKey(_key: string): { module: string; fingerprint: string } | undefined {
let _rest = _key;
if (!_rest.startsWith("held.")) return undefined;
_rest = _rest.slice(5);
const _at1 = _rest.indexOf(".");
if (_at1 <= 0) return undefined;
const module = _rest.slice(0, _at1);
_rest = _rest.slice(_at1 + 1);
if (_rest === "") return undefined;
const fingerprint = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { module, fingerprint };
}
/** The key of one value of the state differences under the form "<node>.<module>". */
export function differencesByNodeModuleKey(node: string, module: string): string {
return node + "." + module;
}
/** The parts of a key of the form "<node>.<module>", or undefined for a key of another form. */
export function parseDifferencesByNodeModuleKey(_key: string): { node: string; module: string } | undefined {
let _rest = _key;
const _at0 = _rest.indexOf(".");
if (_at0 <= 0) return undefined;
const node = _rest.slice(0, _at0);
_rest = _rest.slice(_at0 + 1);
if (_rest === "") return undefined;
const module = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { node, module };
}
/** The key of one value of the state compositions under the form "<group>.<heads>". */
export function compositionsByGroupHeadsKey(group: string, heads: string): string {
return group + "." + heads;
}
/** The parts of a key of the form "<group>.<heads>", or undefined for a key of another form. */
export function parseCompositionsByGroupHeadsKey(_key: string): { group: string; heads: string } | undefined {
let _rest = _key;
const _at0 = _rest.indexOf(".");
if (_at0 <= 0) return undefined;
const group = _rest.slice(0, _at0);
_rest = _rest.slice(_at0 + 1);
if (_rest === "") return undefined;
const heads = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { group, heads };
}
/** is what upgrade takes. */
export interface UpgradeInput {
/** "true": each node's own composition, by its digest, and the assigner's findings */
"compare"?: "true" | "false";
/** one module */
"module"?: string;
/** with module: roll, together, record, or default to take a person's choice back */
"policy"?: string;
/** with policy: why, required and kept */
"why"?: string;
}
/** is what upgrade answers. */
export type UpgradeAnswer = Record<string, unknown>;
/** is what bus takes. */
export interface BusInput {
}
/** is what bus answers. */
export type BusAnswer = Record<string, unknown>;
/** is the body of the event policy-changed. */
export type PolicyChangedEvent = Record<string, unknown>;
/** is one value of the state wants under the key want.<node>.<module>. */
export interface WantsByWantNodeModuleValue {
/** again or skip */
"backup"?: "again" | "skip";
"composed"?: string;
"fp"?: string;
"hold"?: boolean;
"newest"?: string;
"put"?: boolean;
"release"?: string;
"removed"?: boolean;
/** the rule that holds the node back, 1 to 10 */
"rule"?: number;
"self"?: boolean;
"v"?: number;
"words"?: string;
"yes"?: string;
}
/** is one value of the state wants under the key want.<node>.node. */
export interface WantsByWantNodeNodeValue {
"composed": string;
"self"?: boolean;
"v"?: number;
"words"?: string;
}
/** is one value of the state wants under the key held.<module>.<fingerprint>. */
export interface WantsByHeldModuleFingerprintValue {
"node": string;
"v"?: number;
"words"?: string;
}
/** is one value of the state differences under the key <node>.<module>. */
export interface DifferencesByNodeModuleValue {
"rule"?: number;
"sent"?: string;
"sent-build"?: string;
"unexplained"?: boolean;
"v"?: number;
"want"?: string;
"want-removed"?: boolean;
"words"?: string;
}
/** is one value of the state compositions under the key <group>.<heads>. */
export interface CompositionsByGroupHeadsValue {
"group": string;
/** 12 lowercase hex digits */
"heads": string;
"machine"?: string;
"machines"?: string[];
"sdk"?: string;
/** pass, fail or waits */
"state": string;
/** milliseconds */
"took"?: number;
"v"?: number;
"warnings"?: string[];
"words"?: string;
}
@@ -1,12 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}
+6 -1
View File
@@ -21,6 +21,11 @@
"node-lock-screen",
"node-clipboard",
"node-bar",
"node-compositor"
"node-compositor",
"mesh-module-assigner",
"node-service-manager",
"node-package-manager",
"node-container-runtime",
"node-environment"
]
}
@@ -1,7 +1,7 @@
[
{
"name": "a node's key",
"of": "state:placement/<node>.node",
"of": "state:assignments/<node>.node",
"given": {
"v": 1,
"node": "ace",
@@ -18,7 +18,7 @@
},
{
"name": "a node that came with a tunnel",
"of": "state:placement/<node>.node",
"of": "state:assignments/<node>.node",
"given": {
"v": 1,
"node": "ace",
@@ -40,7 +40,7 @@
},
{
"name": "a module's key",
"of": "state:placement/<node>.<module>",
"of": "state:assignments/<node>.<module>",
"given": {
"v": 1,
"node": "ace",
@@ -3,13 +3,13 @@
"seat": "mesh-module-assigner",
"scope": "mesh",
"grain": "whole",
"domain": "Placement",
"domain": "Core",
"decision": "novox/hq ADR 0567, ADR 0634, ADR 0635",
"description": "The seat that places modules on nodes (ADR 0567): the state of placement and the proposals that change it. Renamed from mesh-placement (ADR 0634 §9).",
"verbs": [
{
"name": "placement",
"description": "Read placement: one node's key, the nodes one module is placed on, or every key, each with its revision, and how full the state is against its cap. With history, one node's or one module's changes in order: a revision the state keeps whole, an older one as the proposal's changes from its placement-changed event.",
"name": "assignments",
"description": "Read the assignments: one node's key, the nodes one module is placed on, or every key, each with its revision, and how full the state is against its cap. With history, one node's or one module's changes in order: a revision the state keeps whole, an older one as the proposal's changes from its placement-changed event.",
"optional": true,
"input": {
"properties": {
@@ -188,7 +188,7 @@
},
"states": [
{
"name": "placement",
"name": "assignments",
"template": "state",
"key-forms": [
{
@@ -276,7 +276,7 @@
}
],
"history": 64,
"description": "placements, one key per node and one per module placed on it, written only on the operator's Approve of a proposal by compare-and-set at the revision it was laid over"
"description": "assignments, one key per node and one per module placed on it, written only on the operator's Approve of a proposal by compare-and-set at the revision it was laid over"
},
{
"name": "proposals",
@@ -3,6 +3,6 @@
"seat": "node-container-runtime",
"scope": "node",
"grain": "seat-list",
"domain": "Placement",
"domain": "Core",
"decision": "novox/hq ADR 0166, ADR 0207"
}
@@ -3,6 +3,6 @@
"seat": "node-environment",
"scope": "node",
"grain": "seat-list",
"domain": "Placement",
"domain": "Core",
"decision": "novox/hq ADR 0203"
}
@@ -3,6 +3,6 @@
"seat": "node-package-manager",
"scope": "node",
"grain": "seat-list",
"domain": "Placement",
"domain": "Core",
"decision": "novox/hq ADR 0207"
}
@@ -3,7 +3,7 @@
"seat": "node-service-manager",
"scope": "node",
"grain": "seat-list",
"domain": "Placement",
"domain": "Core",
"decision": "novox/hq ADR 0177",
"verbs": [
{
@@ -1,9 +1,9 @@
{
"lockfileVersion": 3,
"name": "@novox/mesh-placement-contracts-mesh-wants",
"name": "@novox/mesh-contracts-mesh-module-assigner",
"packages": {
"": {
"name": "@novox/mesh-placement-contracts-mesh-wants",
"name": "@novox/mesh-contracts-mesh-module-assigner",
"version": "0.1.0"
}
},
@@ -6,7 +6,7 @@
"files": [
"src"
],
"name": "@novox/mesh-placement-contracts-mesh-module-assigner",
"name": "@novox/mesh-contracts-mesh-module-assigner",
"type": "module",
"types": "./src/index.ts",
"version": "0.1.0"
@@ -5,7 +5,7 @@
export const seat = "mesh-module-assigner";
export const verbs = {
placement: "placement",
assignments: "assignments",
proposals: "proposals",
propose: "propose",
withdraw: "withdraw",
@@ -30,20 +30,20 @@ export const placementJudgedSubject = "mesh.seat.mesh-module-assigner.event.plac
export const placementChangedSubject = "mesh.seat.mesh-module-assigner.event.placement-changed";
export const states = {
placement: "placement",
assignments: "assignments",
proposals: "proposals",
candidates: "candidates",
pulses: "pulses",
seatHolders: "seat-holders",
} as const;
/** The key of one value of the state placement under the form "<node>.node". */
export function placementByNodeNodeKey(node: string): string {
/** The key of one value of the state assignments under the form "<node>.node". */
export function assignmentsByNodeNodeKey(node: string): string {
return node + ".node";
}
/** The parts of a key of the form "<node>.node", or undefined for a key of another form. */
export function parsePlacementByNodeNodeKey(_key: string): { node: string } | undefined {
export function parseAssignmentsByNodeNodeKey(_key: string): { node: string } | undefined {
let _rest = _key;
const _at0 = _rest.indexOf(".node");
if (_at0 <= 0) return undefined;
@@ -53,13 +53,13 @@ export function parsePlacementByNodeNodeKey(_key: string): { node: string } | un
return { node };
}
/** The key of one value of the state placement under the form "<node>.<module>". */
export function placementByNodeModuleKey(node: string, module: string): string {
/** The key of one value of the state assignments under the form "<node>.<module>". */
export function assignmentsByNodeModuleKey(node: string, module: string): string {
return node + "." + module;
}
/** The parts of a key of the form "<node>.<module>", or undefined for a key of another form. */
export function parsePlacementByNodeModuleKey(_key: string): { node: string; module: string } | undefined {
export function parseAssignmentsByNodeModuleKey(_key: string): { node: string; module: string } | undefined {
let _rest = _key;
const _at0 = _rest.indexOf(".");
if (_at0 <= 0) return undefined;
@@ -381,8 +381,8 @@ export interface Warrant {
"warrant": string;
}
/** is what placement takes. */
export interface PlacementInput {
/** is what assignments takes. */
export interface AssignmentsInput {
/** "true": the changes in order, from the stream and the state's revisions */
"history"?: "true" | "false";
/** one module: the nodes it is placed on */
@@ -393,8 +393,8 @@ export interface PlacementInput {
"seats"?: "true" | "false";
}
/** is what placement answers. */
export type PlacementAnswer = Record<string, unknown>;
/** is what assignments answers. */
export type AssignmentsAnswer = Record<string, unknown>;
/** is what proposals takes. */
export interface ProposalsInput {
@@ -450,25 +450,25 @@ export type PlacementJudgedEvent = PlacementEvent;
/** is the body of the event placement-changed. */
export type PlacementChangedEvent = PlacementEvent;
export interface PlacementByNodeNodeValueModulesItem {
export interface AssignmentsByNodeNodeValueModulesItem {
/** the digest of the module key's value it stands for */
"digest": string;
"module": string;
}
/** is one value of the state placement under the key <node>.node. */
export interface PlacementByNodeNodeValue {
/** is one value of the state assignments under the key <node>.node. */
export interface AssignmentsByNodeNodeValue {
"decided": NodeDecided;
"holds"?: Holding[];
/** the modules placed, in the order the composition reads them */
"modules": PlacementByNodeNodeValueModulesItem[];
"modules": AssignmentsByNodeNodeValueModulesItem[];
"node": string;
"pins"?: Record<string, Provider>;
"v"?: number;
}
/** is one value of the state placement under the key <node>.<module>. */
export interface PlacementByNodeModuleValue {
/** is one value of the state assignments under the key <node>.<module>. */
export interface AssignmentsByNodeModuleValue {
"node": string;
"placed": PlacedModule;
/** the value it replaces while a write is under way */