Merge branch 'b6/registry' into feat/registry-milestone

This commit is contained in:
2026-11-05 15:23:24 +01:00
44 changed files with 650 additions and 549 deletions
+4 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"github.com/novox/mesh-host/internal/compositions"
"maps"
@@ -13,7 +14,6 @@ import (
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/compose"
"github.com/novox/mesh-host/internal/compose/resolution"
)
@@ -81,7 +81,7 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st
if len(failed) == 0 {
return refuse(err)
}
failed[delivery.HaveNodeWord] = err.Error()
failed[nodeengineclient.NodeWord] = err.Error()
composed, skipped, stood = nil, nil, nil
}
// The node's own composition is judged only where the node runs one: one that runs none applies it only once a want
@@ -97,7 +97,7 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st
}
}
}
if _, whole := failed[delivery.HaveNodeWord]; whole {
if _, whole := failed[nodeengineclient.NodeWord]; whole {
// Nothing composed to compare: every refusal is said, and nothing else.
for _, m := range sortedKeys(failed) {
a.Refusals = append(a.Refusals, nodeengine.CandidateRefusal{Module: m, Why: failed[m]})
@@ -173,7 +173,7 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st
} // another node's addition: the module here that carries it differs, which the assigner judges
case module == "" || node != s.Node:
// The node's own, or a value this node holds for a module elsewhere (a grant it provides): said as the node's.
refusals[delivery.HaveNodeWord] = append(refusals[delivery.HaveNodeWord], x)
refusals[nodeengineclient.NodeWord] = append(refusals[nodeengineclient.NodeWord], x)
default:
refusals[module] = append(refusals[module], x)
}
+4 -7
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
@@ -42,7 +43,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
p.Modules = append(p.Modules, delivery.PlacedModule{Module: m})
}
b := &bus{placements: map[string]delivery.Placement{"n1": p},
records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd", "container-runtime"}}},
records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd", "container-runtime"}}},
modules: map[string]delivery.Module{"lemurs": {Manifest: delivery.ManifestDigest(lemurs)}},
manifests: map[string][]byte{delivery.ManifestDigest(lemurs): lemurs},
builds: map[string]*delivery.BuilderBuild{}}
@@ -88,11 +89,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
t.Errorf("%s refused as %q, want %q", r.Module, r.Why, want)
}
}
var wireAnswer delivery.CandidateAnswer // the form mesh-sdk's have still holds, until the Have group moves
if wire, err := json.Marshal(a); err != nil || json.Unmarshal(wire, &wireAnswer) != nil {
t.Fatalf("the answer does not read back: %v", err)
}
if raw, err := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"g": wireAnswer}}); err != nil {
if raw, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Candidates: map[string]nodeengine.CandidateAnswer{"g": a}}); err != nil {
t.Errorf("the answer is not one a have holds: %v (%s)", err, raw)
}
if lemursOK := slices.ContainsFunc(a.Added, func(m nodeengine.CandidateAdded) bool { return m.Module == "lemurs" }); !lemursOK {
@@ -594,7 +591,7 @@ func otherNodeNotResolvedIsNamed(t *testing.T) {
b := &bus{placements: map[string]delivery.Placement{
"n1": {Node: "n1", Decided: delivery.NodeDecided{Hub: true, Address: "10.77.0.1", Endpoint: "n1.example:51820"}, Modules: []delivery.PlacedModule{{Module: "cons"}, {Module: overlay.Name}}},
"n2": {Node: "n2", Decided: delivery.NodeDecided{Address: "10.77.0.2"}, Modules: []delivery.PlacedModule{{Module: "prov"}, {Module: overlay.Name}}}},
records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t)}, "n2": {Node: "n2", TunnelKey: publicKey(t)}},
records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t)}, "n2": {Node: "n2", TunnelKey: publicKey(t)}},
modules: map[string]delivery.Module{"prov": {Manifest: delivery.ManifestDigest(prov)}, "cons": {Manifest: delivery.ManifestDigest(cons)}},
manifests: map[string][]byte{delivery.ManifestDigest(prov): prov, delivery.ManifestDigest(cons): cons}}
given := map[string]*delivery.Placement{}
+18 -8
View File
@@ -35,6 +35,7 @@ import (
"encoding/json"
"errors"
"fmt"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
engcomp "github.com/novox/mesh-host/internal/compositions"
"maps"
"net/netip"
@@ -57,8 +58,8 @@ type Reader interface {
// Placements is every node's placement, by node (delivery.PlacementBucket).
Placements(ctx context.Context) (map[string]delivery.Placement, error)
// Records is every node's record, by node: HaveNode.Record of each node's own have key, `<node>.node` of
// delivery.HaveBucket (ADR 0555 §3).
Records(ctx context.Context) (map[string]delivery.NodeRecord, error)
// buckets.Have (ADR 0555 §3).
Records(ctx context.Context) (map[string]nodeengine.NodeRecord, error)
// Modules is every module the indexer holds, by module (delivery.ModulesBucket).
Modules(ctx context.Context) (map[string]delivery.Module, error)
// Manifest is the raw manifest of a digest (delivery.ManifestsBucket).
@@ -252,7 +253,7 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module
type base struct {
in compose.Input
placements map[string]delivery.Placement
records map[string]delivery.NodeRecord
records map[string]nodeengine.NodeRecord
modules map[string]delivery.Module
// ownRefused is each module placed on the composing node whose manifest the indexer refused, with why, in the order
// read: every scope holds it.
@@ -987,15 +988,24 @@ func declaresOwnSecrets(in compose.Input, node string) bool {
return false
}
// attachedOf is a record's attached networks as the composer holds them (the same two fields).
func attachedOf(in []nodeengine.AttachedNetwork) []catalogue.AttachedNetwork {
var out []catalogue.AttachedNetwork
for _, n := range in {
out = append(out, catalogue.AttachedNetwork{Link: n.Link, Network: n.Network})
}
return out
}
// nodeOf is the composition's node from its record (what the machine observes) and its placement (what a person
// decided of it, and where its modules run).
func nodeOf(rec delivery.NodeRecord, p delivery.Placement) compose.Node {
func nodeOf(rec nodeengine.NodeRecord, p delivery.Placement) compose.Node {
n := compose.Node{
Name: rec.Node,
Place: compose.Place{Key: rec.TunnelKey},
SealingKey: rec.SealingKey,
OutwardLinks: rec.OutwardLinks, Networks: rec.Networks, NetworksSaid: rec.NetworksSaid,
ServingKey: rec.ServingKey, Certificate: rec.Certificate, Contract: rec.Contract, Stated: rec.Contract > 0,
OutwardLinks: rec.OutwardLinks, Networks: attachedOf(rec.Networks), NetworksSaid: rec.NetworksSaid,
ServingKey: rec.ServingKey, Certificate: rec.Certificate, Contract: int(rec.Contract), Stated: rec.Contract > 0,
}
placeNode(&n, p)
if len(rec.Capabilities) > 0 {
@@ -1092,7 +1102,7 @@ func tunnelOf(t delivery.RecordTunnel) compose.Tunnel {
// carriedPeers is every peer of the hub's adopted tunnel, by address, each with the node that enrolled under its key
// where one has (a node whose tunnel key is the peer's) and the name a person gave it (inv.CarriedPeers).
func carriedPeers(t compose.Tunnel, names map[string]string, records map[string]delivery.NodeRecord) []compose.CarriedPeer {
func carriedPeers(t compose.Tunnel, names map[string]string, records map[string]nodeengine.NodeRecord) []compose.CarriedPeer {
byKey := map[string]string{}
for node, rec := range records {
if rec.TunnelKey != "" {
@@ -1116,7 +1126,7 @@ func carriedPeers(t compose.Tunnel, names map[string]string, records map[string]
}
// nodesOf is every node named by a placement or a record, by name: the order the controller reads them in.
func nodesOf(placements map[string]delivery.Placement, records map[string]delivery.NodeRecord) []string {
func nodesOf(placements map[string]delivery.Placement, records map[string]nodeengine.NodeRecord) []string {
seen := map[string]bool{}
for n := range placements {
seen[n] = true
+13 -14
View File
@@ -11,6 +11,7 @@ import (
"encoding/json"
"errors"
"fmt"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"github.com/novox/mesh-host/internal/compositions"
"os"
@@ -31,7 +32,7 @@ type bus struct {
vaultAuthority string
handed map[string]bool
placements map[string]delivery.Placement
records map[string]delivery.NodeRecord
records map[string]nodeengine.NodeRecord
modules map[string]delivery.Module
manifests map[string][]byte
values map[string]map[string]any // by node.module
@@ -81,10 +82,12 @@ func (b *bus) Served(_ context.Context, node, module string, keys []string) (map
func (b *bus) Placements(context.Context) (map[string]delivery.Placement, error) {
return b.placements, nil
}
func (b *bus) VaultAuthority(context.Context) string { return b.vaultAuthority }
func (b *bus) Handed(context.Context, string) map[string]bool { return b.handed }
func (b *bus) Records(context.Context) (map[string]delivery.NodeRecord, error) { return b.records, nil }
func (b *bus) Modules(context.Context) (map[string]delivery.Module, error) { return b.modules, nil }
func (b *bus) VaultAuthority(context.Context) string { return b.vaultAuthority }
func (b *bus) Handed(context.Context, string) map[string]bool { return b.handed }
func (b *bus) Records(context.Context) (map[string]nodeengine.NodeRecord, error) {
return b.records, nil
}
func (b *bus) Modules(context.Context) (map[string]delivery.Module, error) { return b.modules, nil }
func (b *bus) Manifest(_ context.Context, digest string) ([]byte, error) {
raw, ok := b.manifests[digest]
if !ok {
@@ -180,7 +183,7 @@ func TestInput(t *testing.T) {
{Module: "media", HasMesh: true, MeshValues: map[string]any{"name": "mesh"}, HasLayer: true,
Layer: map[string]any{"places": map[string]any{"data": "/srv/data"}}, Values: map[string]any{"lines": float64(1000)}}}},
},
records: map[string]delivery.NodeRecord{
records: map[string]nodeengine.NodeRecord{
"anchor": {Node: "anchor", TunnelKey: keys["anchor"], Capabilities: caps, Contract: 2},
"laptop": {Node: "laptop", TunnelKey: keys["laptop"], Capabilities: caps, Authority: "the mesh's certificate"},
},
@@ -331,7 +334,7 @@ func TestInput(t *testing.T) {
p.Modules = append(p.Modules, delivery.PlacedModule{Module: m})
}
return &bus{placements: map[string]delivery.Placement{"n1": p},
records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: keys["anchor"], SealingKey: "sk", Capabilities: []string{"systemd"}}},
records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: keys["anchor"], SealingKey: "sk", Capabilities: []string{"systemd"}}},
modules: map[string]delivery.Module{"keeper": {Manifest: delivery.ManifestDigest(keeper)}, "lemurs": {Manifest: delivery.ManifestDigest(lemurs)},
"objects": {Manifest: delivery.ManifestDigest(objects)}, "files": {Manifest: delivery.ManifestDigest(files)}},
manifests: map[string][]byte{delivery.ManifestDigest(keeper): keeper, delivery.ManifestDigest(lemurs): lemurs,
@@ -490,11 +493,7 @@ func TestInput(t *testing.T) {
if a.Verdict != c.verdict || !c.check(a) {
t.Errorf("%s: %+v", name, a)
}
var wireAnswer delivery.CandidateAnswer // the form mesh-sdk's have still holds, until the Have group moves
if wire, err := json.Marshal(a); err != nil || json.Unmarshal(wire, &wireAnswer) != nil {
t.Fatalf("%s: the answer does not read back: %v", name, err)
}
if raw, err := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"g": wireAnswer}}); err != nil {
if raw, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Candidates: map[string]nodeengine.CandidateAnswer{"g": a}}); err != nil {
t.Errorf("%s: the answer is not one a have holds: %v (%s)", name, err, raw)
}
}
@@ -844,9 +843,9 @@ func statesOf(t *testing.T, ctx context.Context, b *bus) mapStates {
}
records, _ := b.Records(ctx)
for node, r := range records {
k, _ := delivery.HaveNodeKey(node)
k, _ := nodeengineclient.HaveNodeKey(node)
rec := r
raw, err := delivery.EncodeHaveNode(delivery.HaveNode{Record: &rec})
raw, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &rec})
put(HaveRead, k, raw, err)
}
modules, _ := b.Modules(ctx)
@@ -12,6 +12,7 @@ import (
"embed"
"encoding/json"
"errors"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"strings"
"git.novox.be/novox/mesh-sdk/go/delivery"
@@ -25,7 +26,7 @@ var testdata embed.FS
// Bus is a frombus.Reader over values held in memory. A credential asked is answered held when Held is set.
type Bus struct {
Placement map[string]delivery.Placement
Record map[string]delivery.NodeRecord
Record map[string]nodeengine.NodeRecord
Module map[string]delivery.Module
Manifests map[string][]byte
Value map[string]map[string]any // by node.module
@@ -41,9 +42,11 @@ var _ frombus.Reader = (*Bus)(nil)
func (b *Bus) Placements(context.Context) (map[string]delivery.Placement, error) {
return b.Placement, nil
}
func (b *Bus) Records(context.Context) (map[string]delivery.NodeRecord, error) { return b.Record, nil }
func (b *Bus) Modules(context.Context) (map[string]delivery.Module, error) { return b.Module, nil }
func (b *Bus) VaultAuthority(context.Context) string { return "" }
func (b *Bus) Records(context.Context) (map[string]nodeengine.NodeRecord, error) {
return b.Record, nil
}
func (b *Bus) Modules(context.Context) (map[string]delivery.Module, error) { return b.Module, nil }
func (b *Bus) VaultAuthority(context.Context) string { return "" }
func (b *Bus) Manifest(_ context.Context, digest string) ([]byte, error) {
raw, ok := b.Manifests[digest]
@@ -152,7 +155,7 @@ func Provisioned() (*Bus, error) {
Placement: map[string]delivery.Placement{Node: {Node: Node,
Decided: delivery.NodeDecided{Hub: true, Address: "10.77.0.1", Account: "operator", AccountHome: "/home/operator"},
Modules: []delivery.PlacedModule{{Module: "store"}, {Module: "postgres"}, {Module: "mesh-provisioner-client"}, {Module: "restic"}}}},
Record: map[string]delivery.NodeRecord{Node: {Node: Node, TunnelKey: "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=",
Record: map[string]nodeengine.NodeRecord{Node: {Node: Node, TunnelKey: "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=",
SealingKey: "sk", Contract: compose.HandedContract, Capabilities: []string{"systemd", "package-manager"}}},
Module: map[string]delivery.Module{}, Manifests: map[string][]byte{},
Own: []compose.OwnSecretRow{{Node: Node, Module: "mesh-provisioner-client", Name: "provisioner-broker", Origin: "made", SealedTo: "sk", Sealed: `"box"`}},
@@ -3,6 +3,8 @@ package frombustest
import (
"context"
"encoding/json"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
engcomp "github.com/novox/mesh-host/internal/compositions"
"reflect"
"slices"
@@ -313,7 +315,7 @@ func statesOf(t *testing.T, b *Bus) frombus.States {
put(frombus.PlacementRead, "placement."+node, raw, err)
}
for node, rec := range b.Record {
raw, err := delivery.EncodeHaveNode(delivery.HaveNode{Record: &rec})
raw, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &rec})
put(frombus.HaveRead, node+".node", raw, err)
}
for module, m := range b.Module {
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
engcomp "github.com/novox/mesh-host/internal/compositions"
"strings"
"testing"
@@ -18,7 +19,7 @@ import (
func aNode(t *testing.T, manifests ...string) (*bus, map[string]string) {
t.Helper()
p := delivery.Placement{Node: "n1", Decided: delivery.NodeDecided{Hub: true, Address: "10.77.0.1"}}
b := &bus{records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd"}}},
b := &bus{records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd"}}},
modules: map[string]delivery.Module{}, manifests: map[string][]byte{}, builds: map[string]*delivery.BuilderBuild{}}
fps := map[string]string{}
for i, raw := range manifests {
+6 -4
View File
@@ -12,6 +12,8 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"strings"
"time"
@@ -84,11 +86,11 @@ func (r statesReader) Placements(context.Context) (map[string]delivery.Placement
return keys.All(), err
}
func (r statesReader) Records(context.Context) (map[string]delivery.NodeRecord, error) {
out := map[string]delivery.NodeRecord{}
func (r statesReader) Records(context.Context) (map[string]nodeengine.NodeRecord, error) {
out := map[string]nodeengine.NodeRecord{}
err := r.each(HaveRead, func(k string, v []byte) {
if node, _, isNode, err := delivery.ParseHaveKey(k); err == nil && isNode {
if h, err := delivery.DecodeHaveNode(v); err == nil && h.Record != nil {
if node, _, isNode, err := nodeengineclient.ParseHaveKey(k); err == nil && isNode {
if h, err := nodeengineclient.DecodeHaveNode(v); err == nil && h.Record != nil {
out[node] = *h.Record
}
}