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

This commit is contained in:
2026-11-05 15:12:02 +01:00
58 changed files with 986 additions and 516 deletions
+2 -2
View File
@@ -280,7 +280,7 @@ func ownPackages(t *testing.T) map[string][]string {
}
// 1621/AC2 and AC4: the command imports, through every package of this repository it reaches, only the standard
// library, mesh-sdk, the private network's overlay package (mesh-wireguard's own pure package: names, graph and
// library, mesh-sdk, the contracts modules' pure packages (seats' types, clients' codecs), the private network's overlay package (mesh-wireguard's own pure package: names, graph and
// generator, no bus) and this repository's own packages, and none that reaches a bus, a store, a network service or
// another process.
func TestTheCommandImportsNoBusStoreNetworkOrProcess(t *testing.T) {
@@ -292,7 +292,7 @@ func TestTheCommandImportsNoBusStoreNetworkOrProcess(t *testing.T) {
for path, imports := range own {
for _, imp := range imports {
std := !strings.Contains(strings.SplitN(imp, "/", 2)[0], ".")
if !std && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-sdk/go") && !strings.HasPrefix(imp, "github.com/novox/mesh-host/") {
if !std && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-sdk/go") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-contracts/") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/contracts/") && !strings.HasPrefix(imp, "github.com/novox/mesh-host/") {
t.Errorf("%s imports %s, which is neither the standard library, mesh-sdk nor this repository", path, imp)
}
for _, f := range forbidden {
+2 -2
View File
@@ -2,12 +2,12 @@ package main
import (
"encoding/json"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// authorityPath is where this engine keeps the mesh's own certificate as it last applied it (public): beside the
@@ -17,7 +17,7 @@ func authorityPath(state string) string { return filepath.Join(filepath.Dir(stat
// keepAuthority keeps the mesh's own certificate a composition carries (the resource a module's certificate.authority
// asks for, catalogue.AuthorityID), so the record says the authority this machine holds. A composition carrying none
// changes nothing.
func keepAuthority(state string, c delivery.Composed) error {
func keepAuthority(state string, c engcomp.Composed) error {
for _, raw := range c.Resources {
var r struct {
ID string `json:"id"`
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
"strings"
@@ -29,13 +30,13 @@ func TestTheAppliedAuthorityIsKeptForTheRecord(t *testing.T) {
raw, _ := json.Marshal(map[string]any{"id": id, "type": "file", "path": "/etc/x", "content": content})
return raw
}
if err := keepAuthority(state, delivery.Composed{Resources: []json.RawMessage{res("web.conf", "x")}}); err != nil {
if err := keepAuthority(state, engcomp.Composed{Resources: []json.RawMessage{res("web.conf", "x")}}); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(authorityPath(state)); !os.IsNotExist(err) {
t.Fatalf("a composition with no authority kept one: %v", err)
}
if err := keepAuthority(state, delivery.Composed{Resources: []json.RawMessage{res("ca-trust.certificate-authority", "CA-1")}}); err != nil {
if err := keepAuthority(state, engcomp.Composed{Resources: []json.RawMessage{res("ca-trust.certificate-authority", "CA-1")}}); err != nil {
t.Fatal(err)
}
if got := recordKeys(state, nil)(); got.Authority != "CA-1" {
+3 -4
View File
@@ -2,10 +2,9 @@ package main
import (
"encoding/json"
engcomp "github.com/novox/mesh-host/internal/compositions"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// A composition that names a file the provisioner fills without saying it is filled would have this node-engine write
@@ -13,7 +12,7 @@ import (
// when ace, g14 and shanks wrote claude-code's, node-env's and power's files empty).
func TestAFileItCannotProduceIsNeverWrittenEmpty(t *testing.T) {
unit := json.RawMessage(`{"id":"power.after-boot-unit","type":"file","path":"/etc/systemd/system/mesh-power-after-boot.service","mode":"0644","owner-at-runtime":"owned"}`)
c := delivery.Composed{Node: "ace", Resources: []json.RawMessage{unit}}
c := engcomp.Composed{Node: "ace", Resources: []json.RawMessage{unit}}
d, err := declarationOf(c)
if err != nil {
t.Fatal(err)
@@ -25,7 +24,7 @@ func TestAFileItCannotProduceIsNeverWrittenEmpty(t *testing.T) {
if d, _ = declarationOf(c); refuseEmptyWrites(d) != nil {
t.Fatalf("a filled file was refused: %v", refuseEmptyWrites(d))
}
plain := delivery.Composed{Node: "ace", Resources: []json.RawMessage{json.RawMessage(
plain := engcomp.Composed{Node: "ace", Resources: []json.RawMessage{json.RawMessage(
`{"id":"x.f","type":"file","path":"/etc/x","mode":"0644","content":"x"}`)}}
if d, _ = declarationOf(plain); refuseEmptyWrites(d) != nil {
t.Fatalf("a file with its content was refused: %v", refuseEmptyWrites(d))
+2 -2
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
"strconv"
@@ -11,7 +12,6 @@ import (
"testing"
"git.novox.be/novox/mesh-sdk/go/declaration"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-host/internal/link"
)
@@ -140,7 +140,7 @@ func TestADeclarationWithAFileByDigestAppliesFromTheObject(t *testing.T) {
t.Fatal(err)
}
}
runs := runsAsWritten([]delivery.Composed{{Node: "n", Module: "m", Resources: []json.RawMessage{stale}}}, opts.state)
runs := runsAsWritten([]engcomp.Composed{{Node: "n", Module: "m", Resources: []json.RawMessage{stale}}}, opts.state)
var got struct {
Digest string `json:"digest"`
}
+3 -3
View File
@@ -2,9 +2,9 @@ package main
import (
"context"
engcomp "github.com/novox/mesh-host/internal/compositions"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/apply"
"github.com/novox/mesh-host/internal/liveness"
)
@@ -26,8 +26,8 @@ func heldBy(windows *apply.Windows) func(time.Time) map[string]bool {
// long-running resource the composition declares running not active, read from the service manager and the container
// runtime and from no record. A resource held by an open maintenance window, or waiting for a library, is not counted;
// one that cannot be read is not counted either (unknown is not inactive), and neither is one the manager is restarting.
func inactiveIn(rt liveness.Runtime, held func(time.Time) map[string]bool) func(context.Context, delivery.Composed) bool {
return func(ctx context.Context, c delivery.Composed) bool {
func inactiveIn(rt liveness.Runtime, held func(time.Time) map[string]bool) func(context.Context, engcomp.Composed) bool {
return func(ctx context.Context, c engcomp.Composed) bool {
d, err := declarationOf(c)
if err != nil {
return false
+2 -2
View File
@@ -4,9 +4,9 @@ import (
"context"
"encoding/json"
"errors"
engcomp "github.com/novox/mesh-host/internal/compositions"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/liveness"
)
@@ -77,7 +77,7 @@ func testTheMachineShowsAResourceNotActive(t *testing.T) {
{"unread", fakeRuntime{err: errors.New("the service manager could not be read")}, nil, false},
} {
t.Run(c.name, func(t *testing.T) {
d, err := declarationOf(delivery.Composed{Node: "n1", Module: "dnsmasq", Resources: []json.RawMessage{svc, ctr}})
d, err := declarationOf(engcomp.Composed{Node: "n1", Module: "dnsmasq", Resources: []json.RawMessage{svc, ctr}})
if err != nil {
t.Fatal(err)
}
+21 -20
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
@@ -256,16 +257,16 @@ func awaitBackup(ctx context.Context, events <-chan []byte, node, module string,
// the controller put modules it knew no fingerprint of the new form for into the node's own composition while their
// wants named their own, and the declaration joined of both declared each such file twice, which the filling refuses
// ("2 resources declare X") on every node. The node's own copy is left out, and which were left out is said once.
func declarationOf(cs ...delivery.Composed) (*declaration.Declaration, error) {
func declarationOf(cs ...compositions.Composed) (*declaration.Declaration, error) {
d := &declaration.Declaration{Version: declaration.Version}
var modules []delivery.Composed
var modules []compositions.Composed
for _, c := range cs {
if c.Module != "" {
modules = append(modules, c)
}
}
if len(modules) > 0 {
joined := make([]delivery.Composed, 0, len(cs))
joined := make([]compositions.Composed, 0, len(cs))
for _, c := range cs {
if c.Module == "" {
var dropped []string
@@ -300,7 +301,7 @@ func declarationOf(cs ...delivery.Composed) (*declaration.Declaration, error) {
// the same kind and id. A resource that shares only its target (a unit or container of the same name, another id) is
// not the module's, and stays: dropped, its record would be an orphan the apply removes, stopping the module's unit. It
// answers what it left out, as "<id> (<module>)".
func ownWithout(own delivery.Composed, modules []delivery.Composed) (delivery.Composed, []string) {
func ownWithout(own compositions.Composed, modules []compositions.Composed) (compositions.Composed, []string) {
byID := map[string]string{}
for _, c := range modules {
if c.Module == "" {
@@ -356,14 +357,14 @@ func sayDroppedOnce(dropped []string) {
"declares, and they are left out of it: %s (issue 1945)\n", len(dropped), key)
}
func scopeName(c delivery.Composed) string {
func scopeName(c compositions.Composed) string {
if c.Module == "" {
return agent.NodeModule
}
return c.Module
}
func resourcesOf(cs []delivery.Composed) []declaration.Resource {
func resourcesOf(cs []compositions.Composed) []declaration.Resource {
var out []declaration.Resource
for _, c := range cs {
for _, raw := range c.Resources {
@@ -445,7 +446,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
}
began := time.Now()
node := session.Membership.Node
composedBucket, err := delivery.ComposedBucketFor(node)
composedBucket, err := compositions.BucketFor(node)
if err != nil {
cancel()
say("the node agent cannot name this node's composition bucket: " + err.Error())
@@ -469,7 +470,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
// composes its own (ADR 0555 §3): without it a module that asks for a certificate would be stood in for.
if byModule, _ := kept(); byModule != nil {
for module, resources := range byModule {
if err := keepAuthority(opts.state, delivery.Composed{Module: module, Resources: resources}); err != nil {
if err := keepAuthority(opts.state, compositions.Composed{Module: module, Resources: resources}); err != nil {
say("the mesh's certificate the kept declaration carries could not be kept for this machine's record: " + err.Error())
}
}
@@ -478,7 +479,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
// bus module this node runs declares, as its agent holds what runs (its own composition, or the declaration kept
// before the switch): never read from the node's composition bucket, which the controller wrote. On every node;
// a node that runs no list writes nothing. Looked at again whenever what runs changed (RunsChanged, Settled).
users := userListFollower(opts, session, func() []delivery.Composed {
users := userListFollower(opts, session, func() []compositions.Composed {
if now := current.get(); now != nil {
return now.Everything()
}
@@ -545,7 +546,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
Say: say,
// A failure of the bus not answering for a moment is tried again: 2 s, 5 s, 15 s, 60 s, then every 5 min.
Backoff: link.Backoff,
Apply: func(ctx context.Context, c delivery.Composed, others []delivery.Composed) agent.Result {
Apply: func(ctx context.Context, c compositions.Composed, others []compositions.Composed) agent.Result {
actx, done := applyContext(session.WithLink(ctx))
// While the grants part's list is followed, the controller's list in the bus module's composition is not
// written: the apply keeps the list on the machine (ADR 0547 clause 8, never both).
@@ -571,7 +572,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
sayFacts(ctx, ag)
return res
},
Remove: func(ctx context.Context, module string, others []delivery.Composed) agent.Result {
Remove: func(ctx context.Context, module string, others []compositions.Composed) agent.Result {
actx, done := applyContext(session.WithLink(ctx))
res := removeComposition(actx, opts, module, others, ag.Placed(module), sched, say)
done()
@@ -595,7 +596,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
}
hooks := composerHooks{
seatList: seats,
running: func() []delivery.Composed {
running: func() []compositions.Composed {
if now := own.get(); now != nil {
return runsAsWritten(now.Everything(), opts.state)
}
@@ -639,8 +640,8 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
shadowHooks.refused, shadowHooks.kept, shadowHooks.certify, shadowHooks.seatList = nil, nil, nil, nil
shadowEnv := composerEnv(session, node, say, shadowHooks)
shadow := selfcompose.NewShadow(shadowEnv,
func(ctx context.Context, answers map[string]delivery.CandidateAnswer) {
session.NoteCandidates(ctx, delivery.WithValuesDiffer(answers))
func(ctx context.Context, answers map[string]nodeengine.CandidateAnswer) {
session.NoteCandidates(ctx, compositions.WithValuesDiffer(answers))
})
go shadow.Keep(run, link.Backoff) // this run's alone: a switch begins a run, and its shadow, again
shadowNow.Store(shadow) // what the verb compose-candidate composes a check's candidate with
@@ -712,7 +713,7 @@ func (c *agentNow) get() *agent.Agent {
//
// removed is the modules whose want says removed: in the node's own apply, a resource of any other module goes only when
// its module is among them (issue 774).
func applyComposition(ctx context.Context, opts options, c delivery.Composed, others []delivery.Composed, placed, removed map[string]bool,
func applyComposition(ctx context.Context, opts options, c compositions.Composed, others []compositions.Composed, placed, removed map[string]bool,
sched *apply.Scheduler, say link.Announce) agent.Result {
name := scopeName(c)
if c.Module == "" {
@@ -760,7 +761,7 @@ func applyComposition(ctx context.Context, opts options, c delivery.Composed, ot
}
// removeComposition removes one module's own resources and nothing of another's.
func removeComposition(ctx context.Context, opts options, module string, others []delivery.Composed, placed map[string]bool,
func removeComposition(ctx context.Context, opts options, module string, others []compositions.Composed, placed map[string]bool,
sched *apply.Scheduler, say link.Announce) agent.Result {
scope := apply.Scope{Module: module, Others: resourcesOf(others), Placed: placed}
got := applyModuleHeld(ctx, opts, &declaration.Declaration{Version: declaration.Version}, &scope, sched, say)
@@ -833,7 +834,7 @@ func keptDeclaration(statePath string, signer ed25519.PublicKey) (*declaration.D
// (adoption, identities, groups, libraries) from the node's composition once the agent holds it and from the kept
// declaration until then. What the node judges its health by, and what the filling, the take act and the judgement of
// directories read.
func whatRuns(everything []delivery.Composed, holdsNode bool, kept func() (*declaration.Declaration, error)) (*declaration.Declaration, error) {
func whatRuns(everything []compositions.Composed, holdsNode bool, kept func() (*declaration.Declaration, error)) (*declaration.Declaration, error) {
d, err := declarationOf(everything...)
if err != nil {
return nil, err
@@ -919,8 +920,8 @@ func refuseEmptyWrites(d *declaration.Declaration) error {
// recordedIn says the engine's applied state holds a record of every resource of a composition: what the agent believes
// of a have or the kept declaration only when it does, so a resource removed since is made again (issue 1945). A state
// that cannot be read says yes: the agent then believes what it was told, as before.
func recordedIn(statePath string, say func(string)) func(ctx context.Context, c delivery.Composed) bool {
return func(_ context.Context, c delivery.Composed) bool {
func recordedIn(statePath string, say func(string)) func(ctx context.Context, c compositions.Composed) bool {
return func(_ context.Context, c compositions.Composed) bool {
known, err := store.Load(statePath)
if err != nil {
if say != nil {
@@ -982,7 +983,7 @@ func applyContext(ctx context.Context) (context.Context, context.CancelFunc) {
// composerHooks are what the composer reads of the agent and the machine (selfcompose.Env Running, Unseal, Refused).
type composerHooks struct {
running func() []delivery.Composed
running func() []compositions.Composed
unseal func(string) ([]byte, error)
refused func(why string, held map[string]string)
kept func(kept map[string][]string)
+29 -12
View File
@@ -7,6 +7,8 @@ import (
"crypto/ed25519"
"encoding/json"
"errors"
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
"strings"
@@ -60,7 +62,9 @@ type itBed struct {
mu sync.Mutex
// seatSilent makes the stand-in of the seat mesh-catalog refuse the seat list (it is served from the start otherwise).
seatSilent atomic.Bool
findings nats.KeyValue
// seatAsked counts the node's asks of the stand-in seat, answered or refused: the node reached the bus and asked.
seatAsked atomic.Int32
findings nats.KeyValue
}
// aServer is an embedded bus with JetStream and two users: the test's, unrestricted, and the node's, granted exactly what
@@ -126,12 +130,12 @@ func newItBedAs(t *testing.T, perms nodegrant.Permissions) *itBed {
if b.nodeJS, err = b.nodeConn.JetStream(); err != nil {
t.Fatal(err)
}
composedBucket, _ := delivery.ComposedBucketFor(itNode)
composedBucket, _ := engcomp.BucketFor(itNode)
for _, c := range []struct {
name string
into *nats.KeyValue
history uint8
}{{delivery.AssignerWantsBucket, &b.wants, 8}, {composedBucket, &b.composed, delivery.ComposedHistory},
}{{delivery.AssignerWantsBucket, &b.wants, 8}, {composedBucket, &b.composed, engcomp.History},
{delivery.HaveBucket, &b.haves, delivery.HaveHistory}} {
kv, err := b.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: c.name, History: c.history})
if err != nil {
@@ -253,7 +257,7 @@ func TestNatsANodeThePlacementNamesComposesAndAppliesItsOwn(t *testing.T) {
within(t, 5*time.Second, "the node did not compose and apply its own module once placed", func() bool { return b.file("web") == "greeting=bonjour\n" })
// ADR 0565 §6 (1953): the node's engine writes its own key of the state packages, under the grant the grants part
// composes for it, with each package its compositions declare and the module declaring it; once, while nothing changes.
wantPkgs := `{"node":"` + itNode + `","packages":[{"package":"web-tools","modules":["web"],"absent":false}]}`
wantPkgs := `{"node":"` + itNode + `","packages":[{"absent":false,"modules":["web"],"package":"web-tools"}]}`
within(t, 5*time.Second, "the node did not write its key of the state packages", func() bool {
e, err := st.packages.Get(itNode)
return err == nil && string(e.Value()) == wantPkgs
@@ -351,7 +355,7 @@ func h8back(t *testing.T, b *itBed, placement, resolved, candidates nats.KeyValu
}
within(t, 10*time.Second, "the node did not refuse a candidate taking its placement away", func() bool {
a := node().Candidates["revert-x"]
return a.Proposal == "revert-x" && a.Returns == "" && a.Verdict == delivery.CandidateRefused &&
return a.Proposal == "revert-x" && a.Returns == "" && a.Verdict == nodeengine.CandidateAnswerVerdictRefused &&
strings.Contains(a.Refused, "composes nothing")
})
ran := b.have("web").Composed
@@ -504,17 +508,17 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str
// ADR 0593 §6: the node publishes what it composed in its composition state, under its own grant: web at the build
// its want names, with the digest its have names (the plan left out of it), and the node's own.
first := b.have("web").Composed
webKey, _ := delivery.ComposedKey(itNode, "web", fp)
webKey, _ := engcomp.Key(itNode, "web", fp)
within(t, 5*time.Second, "the node did not publish web's composition in its composition state", func() bool {
e, err := b.composed.Get(webKey)
if err != nil {
return false
}
c, err := delivery.DecodeComposed(e.Value())
c, err := engcomp.Decode(e.Value())
if err != nil {
return false
}
d, err := delivery.ComposedDigestOf(c)
d, err := engcomp.DigestOf(c)
return err == nil && d == first
})
if _, err := resolved.Put(itNode+".web.greeting", []byte(`{"value":"salut","source":"module"}`)); err != nil {
@@ -541,17 +545,18 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str
if err != nil {
t.Fatal(err)
}
var answer delivery.CandidateAnswer
var answer nodeengine.CandidateAnswer
within(t, 10*time.Second, "the node did not answer its candidate", func() bool {
e, err := b.haves.Get(hkey)
if err != nil {
return false
}
n, err := delivery.DecodeHaveNode(e.Value())
answer = n.Candidates["feat-x"]
wire, _ := json.Marshal(n.Candidates["feat-x"])
_ = json.Unmarshal(wire, &answer)
return err == nil && answer.Proposal == "feat-x" && n.Record != nil
})
if answer.Verdict != delivery.CandidateSame || answer.Revision != crev || len(answer.Same) != 1 || answer.Same[0] != "web" {
if answer.Verdict != nodeengine.CandidateAnswerVerdictSame || answer.Revision != int64(crev) || len(answer.Same) != 1 || answer.Same[0] != "web" {
t.Errorf("the answer to a candidate equal to the placement: %+v", answer)
}
if h := b.have("web"); h.Composed != second || b.file("web") != "greeting=salut\n" {
@@ -629,7 +634,7 @@ func h8candidate(t *testing.T, b *itBed, fp, running string) {
func mustComposedKey(t *testing.T, module, fp string) string {
t.Helper()
key, err := delivery.ComposedKey(itNode, module, fp)
key, err := engcomp.Key(itNode, module, fp)
if err != nil {
t.Fatal(err)
}
@@ -904,6 +909,7 @@ func (b *itBed) standInSeat() {
Seat string `json:"seat"`
}
_ = json.Unmarshal(m.Data, &q)
b.seatAsked.Add(1)
var result any
switch {
case b.seatSilent.Load():
@@ -979,6 +985,17 @@ func TestNatsANodeThatCannotReadTheSeatListActsOnNothingNew(t *testing.T) {
defer clock.mu.Unlock()
return len(clock.fire) > 0
})
// A fresh node, nothing applied, reaches the bus and asks the seat while its own composition is held: the read needs
// the bus the node composes from and the grant it has from its start, nothing of what the hold keeps back.
if b.seatAsked.Load() == 0 {
t.Fatal("the fresh node did not ask the seat for its list while its own composition was held")
}
within(t, 5*time.Second, "the fresh node did not say it holds web and its own composition for the unread list", func() bool {
b.mu.Lock()
defer b.mu.Unlock()
said := strings.Join(b.said, "\n")
return strings.Contains(said, selfcompose.WaitsForTheSeatListOwn) && strings.Contains(said, selfcompose.WaitsForTheSeatList)
})
if b.file("web") != "" {
t.Fatalf("the node applied web before it read the seat list: %q", b.file("web"))
}
+3 -4
View File
@@ -3,11 +3,10 @@ package main
import (
"context"
"encoding/json"
engcomp "github.com/novox/mesh-host/internal/compositions"
"path/filepath"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/liveness"
"github.com/novox/mesh-host/internal/store"
)
@@ -21,7 +20,7 @@ func TestACompositionIsRecordedOnlyWhenEveryResourceIs(t *testing.T) {
if err := store.Save(path, s); err != nil {
t.Fatal(err)
}
c := delivery.Composed{Node: "ace", Module: "ca-trust", Resources: []json.RawMessage{
c := engcomp.Composed{Node: "ace", Module: "ca-trust", Resources: []json.RawMessage{
json.RawMessage(`{"id":"ca-trust.trust","type":"file","module":"ca-trust","path":"/etc/ca","mode":"0644","content":"x"}`)}}
recorded := recordedIn(path, func(string) {})
if !recorded(context.Background(), c) {
@@ -87,7 +86,7 @@ func TestAFilledFileDoesNotMakeItsCompositionUnrecorded(t *testing.T) {
if err := store.Save(path, s); err != nil {
t.Fatal(err)
}
c := delivery.Composed{Node: "ace", Module: "ca-trust", Filled: []string{"/etc/ca-filled"}, Resources: []json.RawMessage{
c := engcomp.Composed{Node: "ace", Module: "ca-trust", Filled: []string{"/etc/ca-filled"}, Resources: []json.RawMessage{
json.RawMessage(`{"id":"ca-trust.trust","type":"file","module":"ca-trust","path":"/etc/ca","mode":"0644","content":"x"}`),
json.RawMessage(`{"id":"ca-trust.filled","type":"file","module":"ca-trust","path":"/etc/ca-filled","mode":"0644","owner-at-runtime":"owned"}`)}}
if !recordedIn(path, func(string) {})(context.Background(), c) {
+3 -3
View File
@@ -4,10 +4,10 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"git.novox.be/novox/mesh-sdk/go/declaration"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/store"
)
@@ -20,7 +20,7 @@ import (
// names what is on disk: **only when the node's record says it wrote exactly that content there** (store Applied.Wrote,
// Target), never a file someone else changed. A file that cannot be read, or that the record does not vouch for, is
// left as the composition names it.
func runsAsWritten(running []delivery.Composed, statePath string) []delivery.Composed {
func runsAsWritten(running []engcomp.Composed, statePath string) []engcomp.Composed {
if len(running) == 0 || statePath == "" {
return running
}
@@ -28,7 +28,7 @@ func runsAsWritten(running []delivery.Composed, statePath string) []delivery.Com
if err != nil {
return running
}
out := make([]delivery.Composed, len(running))
out := make([]engcomp.Composed, len(running))
for i, c := range running {
out[i] = c
var changed []json.RawMessage
+1 -1
View File
@@ -110,7 +110,7 @@ func (w findingsOfTheNode) encode(f views.Finding) (string, []byte, error) {
key := conditions.Key(conditions.ScopeMachine, f.ID, f.Kind)
v := condseat.FindingsByKeyValue{V: 1, Key: key, Kind: f.Kind, Observer: f.Observer, Severity: conditions.Warning,
Subject: condseat.Subject{Scope: conditions.ScopeMachine, ID: f.ID, Machine: f.ID}, Summary: f.Summary,
Headline: f.Headline, Explanation: f.Explanation, Source: "seat-list-unread", At: f.At.UTC().Format(time.RFC3339)}
Headline: f.Headline, Explanation: f.Explanation, Source: "seat-list-unread", At: f.At.UTC()}
if err := conditions.CheckFinding(v); err != nil {
return "", nil, err
}
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
engcomp "github.com/novox/mesh-host/internal/compositions"
"os"
"path/filepath"
@@ -25,7 +26,7 @@ import (
// userListFollower is the follower of the bus's user list for one link: the node's composition bucket, the keys the
// agent trusts, the write alone, and the have.
func userListFollower(opts options, session *link.Session, running func() []delivery.Composed,
func userListFollower(opts options, session *link.Session, running func() []engcomp.Composed,
say link.Announce, wrote func()) *userlist.Follower {
return userlist.New(userlist.Env{
Node: session.Membership.Node,
+13 -12
View File
@@ -5,6 +5,7 @@ import (
"crypto/ed25519"
"encoding/json"
"errors"
engcomp "github.com/novox/mesh-host/internal/compositions"
"path/filepath"
"sort"
"strings"
@@ -31,7 +32,7 @@ func aFileOf(module, id string) json.RawMessage {
func TestWhatRunsIsTheAgentsCompositionsAndTheKeptRest(t *testing.T) {
kept := &declaration.Declaration{Identities: []declaration.Identity{{Module: "db", Name: "mesh-db"}},
Libraries: []declaration.Library{{Library: "media"}}}
everything := []delivery.Composed{
everything := []engcomp.Composed{
{Node: "n1", Module: "web", Resources: []json.RawMessage{aFileOf("web", "a")}},
{Node: "n1", Module: "db", Resources: []json.RawMessage{aFileOf("db", "a")}}, // still the kept declaration's
}
@@ -45,7 +46,7 @@ func TestWhatRunsIsTheAgentsCompositionsAndTheKeptRest(t *testing.T) {
}
// Once the agent holds the node's composition, its parts are the node's.
node := delivery.Composed{Node: "n1", Own: delivery.NodeOwn{Identities: []declaration.Identity{{Module: "web", Name: "mesh-web"}}}}
node := engcomp.Composed{Node: "n1", Own: engcomp.NodeOwn{Identities: []declaration.Identity{{Module: "web", Name: "mesh-web"}}}}
d, err = whatRuns(append(everything, node), true, func() (*declaration.Declaration, error) { return nil, errors.New("not read") })
if err != nil {
t.Fatal(err)
@@ -88,7 +89,7 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) {
for _, name := range row.runs {
raws = append(raws, process(name))
}
runs, err := whatRuns([]delivery.Composed{{Node: "n1", Module: "restic", Resources: raws}}, false,
runs, err := whatRuns([]engcomp.Composed{{Node: "n1", Module: "restic", Resources: raws}}, false,
func() (*declaration.Declaration, error) { return nil, errors.New("none kept") })
if err != nil {
t.Fatal(err)
@@ -161,9 +162,9 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) {
// the module's: the declaration joined of both names each file once, so the filling writes it; the node's own resources
// no module declares stay. (Live from 2026-10-25 12:43: "2 resources declare X" on every node.)
func TestTheNodesOwnCopyOfAModulesResourceIsLeftOut(t *testing.T) {
own := delivery.Composed{Node: "n1", Resources: []json.RawMessage{aFileOf("zsh", "rc"), aFileOf("mesh-wireguard", "conf")},
own := engcomp.Composed{Node: "n1", Resources: []json.RawMessage{aFileOf("zsh", "rc"), aFileOf("mesh-wireguard", "conf")},
Filled: []string{"/tmp/r6/zsh/rc"}}
zsh := delivery.Composed{Node: "n1", Module: "zsh", FP: "src2:aa", Resources: []json.RawMessage{aFileOf("zsh", "rc")},
zsh := engcomp.Composed{Node: "n1", Module: "zsh", FP: "src2:aa", Resources: []json.RawMessage{aFileOf("zsh", "rc")},
Filled: []string{"/tmp/r6/zsh/rc"}}
d, err := declarationOf(own, zsh)
if err != nil {
@@ -181,7 +182,7 @@ func TestTheNodesOwnCopyOfAModulesResourceIsLeftOut(t *testing.T) {
}
// The same at the node's own apply: what the others declare is theirs.
trimmed, dropped := ownWithout(own, []delivery.Composed{zsh})
trimmed, dropped := ownWithout(own, []engcomp.Composed{zsh})
if len(trimmed.Resources) != 1 || len(dropped) != 1 || dropped[0] != "zsh.rc (zsh)" {
t.Errorf("the node's own without zsh's: %d resources, dropped %v", len(trimmed.Resources), dropped)
}
@@ -211,9 +212,9 @@ func TestTheKeptDeclarationsFilledPathsGoToTheirModules(t *testing.T) {
strings.Join(filled[agent.NodeModule], ",") != "/etc/motd" || len(filled["zsh"]) != 0 {
t.Fatalf("filled by module: %v", filled)
}
cc := delivery.Composed{Node: "n1", Module: "claude-code", Resources: by["claude-code"], Filled: filled["claude-code"]}
node := delivery.Composed{Node: "n1"}
d, err := whatRuns([]delivery.Composed{cc, node}, true, func() (*declaration.Declaration, error) { return nil, errors.New("not read") })
cc := engcomp.Composed{Node: "n1", Module: "claude-code", Resources: by["claude-code"], Filled: filled["claude-code"]}
node := engcomp.Composed{Node: "n1"}
d, err := whatRuns([]engcomp.Composed{cc, node}, true, func() (*declaration.Declaration, error) { return nil, errors.New("not read") })
if err != nil || !d.Fills("/etc/cc.json") {
t.Fatalf("what runs does not name claude-code's file filled: %v (%v)", d, err)
}
@@ -226,9 +227,9 @@ func TestTheKeptDeclarationsFilledPathsGoToTheirModules(t *testing.T) {
// unit or a container of the same name) is not the module's, and is neither dropped nor left to be removed.
func TestASharedTargetWithAnotherIDIsNotDropped(t *testing.T) {
ownFile, _ := json.Marshal(map[string]any{"id": "host.zshrc", "type": "file", "path": "/tmp/r6/zsh/rc", "content": "x"})
own := delivery.Composed{Node: "n1", Resources: []json.RawMessage{ownFile}}
zsh := delivery.Composed{Node: "n1", Module: "zsh", FP: "src2:aa", Resources: []json.RawMessage{aFileOf("zsh", "rc")}}
trimmed, dropped := ownWithout(own, []delivery.Composed{zsh})
own := engcomp.Composed{Node: "n1", Resources: []json.RawMessage{ownFile}}
zsh := engcomp.Composed{Node: "n1", Module: "zsh", FP: "src2:aa", Resources: []json.RawMessage{aFileOf("zsh", "rc")}}
trimmed, dropped := ownWithout(own, []engcomp.Composed{zsh})
if len(dropped) != 0 || len(trimmed.Resources) != 1 {
t.Errorf("a resource sharing only its target was dropped: %v", dropped)
}