stage 3 of 8 for issue 1389

This commit is contained in:
2026-10-20 11:11:54 +00:00
parent df351458aa
commit 655c692a95
14 changed files with 107 additions and 293 deletions
+71 -41
View File
@@ -6,6 +6,8 @@ import (
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/compose"
"git.novox.be/novox/mesh-sdk/go/overlay"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
"github.com/novox/mesh-controller/internal/secrets"
@@ -138,42 +140,73 @@ func TestAnIssuedValueIsADecisionInTheHandActLog(t *testing.T) {
t.Fatal("secret issued is not in the hand-act table")
}
// needOf is the need a consumer has of a provision, as mesh-sdk's resolution gives it (the controller's issuedToHolder and
// providerMoved became the fields Needed.IssuedToHolder and ProviderMoved of compose.Resolve, issue 1389): the shelf, the
// provider on one machine and the consumer on another.
func needOf(t *testing.T, shelf map[string]catalogue.Manifest, provider, consumer, provision string) catalogue.Needed {
t.Helper()
all := map[string]catalogue.Manifest{}
for name, m := range shelf {
all[name] = m
}
all[consumer] = catalogue.Manifest{Module: consumer, Version: "1", Requires: []string{provision}}
// The private network comes with the control plane, and both machines run it.
var network []string
for _, m := range provided {
all[m.Module] = m
if m.Module == overlay.Name {
network = append(network, m.Module)
}
}
in := compose.Input{Catalogue: all, OverlayCIDR: "10.77.0.0/16", InternalSuffix: "internal",
Nodes: []compose.Node{
{Name: "novox", Assigned: append([]string{provider}, network...), Place: compose.Place{Address: "10.77.0.1"}},
{Name: "ace", Assigned: append([]string{consumer}, network...), Place: compose.Place{Address: "10.77.0.2"}}}}
resolved, err := compose.Resolve(in, "ace")
if err != nil {
t.Fatalf("%s does not resolve beside %s: %v", consumer, provider, err)
}
for _, n := range resolved.Plan.Needs {
if n.For == consumer && n.Name == provision {
return n
}
}
t.Fatalf("%s has no need of %s: %+v", consumer, provision, resolved.Plan.Needs)
return catalogue.Needed{}
}
// **A need's credential is the vault's for its holder alone** (issue 757) exactly when its providing module is marked
// moved, so `secret issued` goes to the vault naming each holder, and its software issues the provision's credential.
func TestANeedIsIssuedToItsHolderOnlyWhereTheProviderIsMovedAndItsSoftwareIssuesIt(t *testing.T) {
forge := func(moved bool, issued bool) catalogue.Manifest {
offer := catalogue.Offer{Name: "gitea-read"}
offer := catalogue.Offer{Name: "gitea-read", Scope: catalogue.ScopeMesh}
if issued {
offer.Credential = &catalogue.OfferCredential{IssuedBy: catalogue.IssuedBySoftware, Tool: "gitea_agent_token"}
}
return catalogue.Manifest{Module: "gitea", Moved: moved, Provides: []catalogue.Offer{offer}}
return catalogue.Manifest{Module: "gitea", Version: "1", Moved: moved, Provides: []catalogue.Offer{offer}}
}
shared := func(moved bool) catalogue.Manifest {
return catalogue.Manifest{Module: "gitea", Moved: moved, OwnSecrets: catalogue.OwnSecrets{"key": {Path: "/k"}},
Provides: []catalogue.Offer{{Name: "gitea-read", Credential: &catalogue.OfferCredential{Own: "key"}}}}
return catalogue.Manifest{Module: "gitea", Version: "1", Moved: moved, OwnSecrets: catalogue.OwnSecrets{"key": {Path: "/k"}},
Provides: []catalogue.Offer{{Name: "gitea-read", Scope: catalogue.ScopeMesh, Credential: &catalogue.OfferCredential{Own: "key"}}}}
}
given := func() catalogue.Manifest {
return catalogue.Manifest{Module: "gitea", Moved: true, Provides: []catalogue.Offer{{Name: "gitea-read",
Credential: &catalogue.OfferCredential{IssuedBy: catalogue.IssuedOutside}}}}
return catalogue.Manifest{Module: "gitea", Version: "1", Moved: true, Provides: []catalogue.Offer{{Name: "gitea-read",
Scope: catalogue.ScopeMesh, Credential: &catalogue.OfferCredential{IssuedBy: catalogue.IssuedOutside}}}}
}
need := catalogue.Needed{Name: "gitea-read", From: "novox", Module: "gitea", For: "claude-code"}
for why, c := range map[string]struct {
shelf map[string]catalogue.Manifest
need catalogue.Needed
forge catalogue.Manifest
want bool
}{
"moved and issued by its software": {map[string]catalogue.Manifest{"gitea": forge(true, true)}, need, true},
"not moved": {map[string]catalogue.Manifest{"gitea": forge(false, true)}, need, false},
"a login for each consumer": {map[string]catalogue.Manifest{"gitea": forge(true, false)}, need, true},
"a login for each, not moved": {map[string]catalogue.Manifest{"gitea": forge(false, false)}, need, false},
"a credential every holder shares": {map[string]catalogue.Manifest{"gitea": shared(true)}, need, false},
"a credential a person gives": {map[string]catalogue.Manifest{"gitea": given()}, need, false},
"a provider not on the shelf": {map[string]catalogue.Manifest{}, need, false},
"answered by a record": {map[string]catalogue.Manifest{"gitea": forge(true, true)},
catalogue.Needed{Name: "gitea-read", ByRecord: true, Module: "gitea", For: "claude-code"}, false},
"moved and issued by its software": {forge(true, true), true},
"not moved": {forge(false, true), false},
"a login for each consumer": {forge(true, false), true},
"a login for each, not moved": {forge(false, false), false},
"a credential every holder shares": {shared(true), false},
"a credential a person gives": {given(), false},
} {
if got := issuedToHolder(c.shelf, c.need); got != c.want {
t.Errorf("%s: issued to its holder is %v, want %v", why, got, c.want)
got := needOf(t, map[string]catalogue.Manifest{"gitea": c.forge}, "gitea", "claude-code", "gitea-read")
if got.IssuedToHolder != c.want {
t.Errorf("%s: issued to its holder is %v, want %v", why, got.IssuedToHolder, c.want)
}
}
}
@@ -182,17 +215,14 @@ func TestANeedIsIssuedToItsHolderOnlyWhereTheProviderIsMovedAndItsSoftwareIssues
// V3, so a need of `secret` is issued to its holder and answered by a moved provider, whether or not the module is
// marked moved; another module is not.
func TestTheModuleHoldingTheVaultSeatCountsAsMovedForItsConsumers(t *testing.T) {
vault := catalogue.Manifest{Module: "mesh-vault", Claims: []catalogue.Claim{{Name: "mesh-vault"}},
Provides: []catalogue.Offer{{Name: "secret"}}}
other := catalogue.Manifest{Module: "mesh-vault", Provides: []catalogue.Offer{{Name: "secret"}}}
need := catalogue.Needed{Name: "secret", Local: "admin", From: "novox", Module: "mesh-vault", For: "gitea"}
if !issuedToHolder(map[string]catalogue.Manifest{"mesh-vault": vault}, need) ||
!providerMoved(map[string]catalogue.Manifest{"mesh-vault": vault}, need) {
t.Error("the vault's consumer is not answered by a moved provider")
vault := catalogue.Manifest{Module: "mesh-vault", Version: "1", Claims: []catalogue.Claim{{Name: "mesh-vault", Scope: catalogue.ScopeMesh}},
Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}}
other := catalogue.Manifest{Module: "mesh-vault", Version: "1", Provides: []catalogue.Offer{{Name: "secret", Scope: catalogue.ScopeMesh}}}
if got := needOf(t, map[string]catalogue.Manifest{"mesh-vault": vault}, "mesh-vault", "gitea", "secret"); !got.IssuedToHolder || !got.ProviderMoved {
t.Errorf("the vault's consumer is not answered by a moved provider: %+v", got)
}
if issuedToHolder(map[string]catalogue.Manifest{"mesh-vault": other}, need) ||
providerMoved(map[string]catalogue.Manifest{"mesh-vault": other}, need) {
t.Error("a module not holding the vault seat counts as moved")
if got := needOf(t, map[string]catalogue.Manifest{"mesh-vault": other}, "mesh-vault", "gitea", "secret"); got.IssuedToHolder || got.ProviderMoved {
t.Errorf("a module not holding the vault seat counts as moved: %+v", got)
}
}
@@ -200,21 +230,21 @@ func TestTheModuleHoldingTheVaultSeatCountsAsMovedForItsConsumers(t *testing.T)
// provides entry says `"answered-by": "helper"`, the module need not be marked moved, and no credential is the vault's
// for it. Another provision of the module, or a module without the entry, does not count.
func TestAnOfferAnsweredByTheHelperCountsAsMovedForThatProvisionOnly(t *testing.T) {
proxy := catalogue.Manifest{Module: "route-proxy", Provides: []catalogue.Offer{
{Name: "route", AnsweredBy: catalogue.AnsweredByHelper}, {Name: "other"}}}
plain := catalogue.Manifest{Module: "route-proxy", Provides: []catalogue.Offer{{Name: "route"}}}
proxy := catalogue.Manifest{Module: "route-proxy", Version: "1", Provides: []catalogue.Offer{
{Name: "route", Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}, {Name: "other", Scope: catalogue.ScopeMesh}}}
plain := catalogue.Manifest{Module: "route-proxy", Version: "1", Provides: []catalogue.Offer{{Name: "route", Scope: catalogue.ScopeMesh}}}
shelf := func(m catalogue.Manifest) map[string]catalogue.Manifest {
return map[string]catalogue.Manifest{m.Module: m}
}
route := catalogue.Needed{Name: "route", From: "novox", Module: "route-proxy", For: "gitea"}
other := catalogue.Needed{Name: "other", From: "novox", Module: "route-proxy", For: "gitea"}
if !providerMoved(shelf(proxy), route) {
if got := needOf(t, shelf(proxy), "route-proxy", "gitea", "route"); !got.ProviderMoved {
t.Error("a provision answered by the helper is not answered by a moved provider")
}
if providerMoved(shelf(proxy), other) || providerMoved(shelf(plain), route) {
t.Error("a provision not answered by the helper counts as moved")
}
if issuedToHolder(shelf(proxy), route) {
} else if got.IssuedToHolder {
t.Error("the flag made a credential the vault's")
}
if got := needOf(t, shelf(proxy), "route-proxy", "gitea", "other"); got.ProviderMoved {
t.Error("a provision not answered by the helper counts as moved")
}
if got := needOf(t, shelf(plain), "route-proxy", "gitea", "route"); got.ProviderMoved {
t.Error("a module without the entry counts as moved")
}
}
-18
View File
@@ -1,30 +1,12 @@
package main
import (
"context"
"fmt"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/overlay"
"github.com/novox/mesh-controller/internal/inventory"
)
// localNetworksOf is a machine's attached networks judged (novox/hq ADR 0340): which count as its local
// network, from what it last said of itself and the links it said face outside it. A machine that has
// said none judges to nothing, and a port reached from `local-network` is then admitted from the private
// network only, and said so.
func localNetworksOf(ctx context.Context, inv *inventory.Inventory, node string) ([]catalogue.LocalNetwork, error) {
account, had, err := inv.MachineOf(ctx, node)
if err != nil {
return nil, err
}
outward, err := inv.OutwardLinksOf(ctx, node)
if err != nil {
return nil, err
}
return judgedNetworks(account, had, outward, overlay.Interface), nil
}
// judgedNetworks is the judging alone, from what was read.
func judgedNetworks(account inventory.MachineAccount, had bool, outward []string, tunnel string) []catalogue.LocalNetwork {
if !had || account.Machine == nil {
@@ -8,6 +8,7 @@ import (
"time"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/compose"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
@@ -55,8 +56,12 @@ func TestIssue969TheGateRaisesEachMachineAsItsEngineStatedItself(t *testing.T) {
t.Fatal(err)
}
for name, hands := range want {
got, err := engineTakesHanded(ctx, raised.inventory, name)
if err != nil || got != hands {
in, err := readComposeInput(ctx, raised)
if err != nil {
t.Fatal(err)
}
node, _ := nodeOf(in.Input, name)
if got := node.Stated && node.Contract >= compose.HandedContract; got != hands {
t.Errorf("%s: the gate raises it as taking handed files = %v (%v), the mesh says %v", name, got, err, hands)
}
}
@@ -14,15 +14,15 @@ func TestIssue969TheGateGivesAMachineItsContractSoItTakesHandedFiles(t *testing.
taken := time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC)
stated, ok := standInHealth(snapshot.Machine{Name: "f35", Contract: link.HandedContract}, taken)
if !ok || !takesHanded(stated, ok) {
if !ok || stated.Contract < link.HandedContract {
t.Fatalf("a machine whose engine takes handed files must be given that in the gate: %+v, %v", stated, ok)
}
old, ok := standInHealth(snapshot.Machine{Name: "e12", Contract: link.HandedContract - 1}, taken)
if !ok || takesHanded(old, ok) {
if !ok || old.Contract >= link.HandedContract {
t.Fatalf("a machine whose engine is older must not take handed files: %+v, %v", old, ok)
}
none, ok := standInHealth(snapshot.Machine{Name: "d01"}, taken)
if ok || takesHanded(none, ok) {
if ok {
t.Fatalf("a snapshot that carries no contract states nothing: %+v, %v", none, ok)
}
}
+4 -2
View File
@@ -7,6 +7,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/compose"
"github.com/novox/mesh-controller/internal/conditions"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
@@ -172,11 +173,12 @@ func TestHealthIsSentOnlyToAnEngineThatSaysItReadsIt(t *testing.T) {
inv := open.inventory
reads := func() bool {
t.Helper()
got, err := engineReadsHealth(ctx, inv, "anchor")
in, err := readComposeInput(ctx, open)
if err != nil {
t.Fatal(err)
}
return got
node, _ := nodeOf(in.Input, "anchor")
return node.Stated && node.Contract >= compose.ReadinessContract
}
if reads() {
t.Fatal("an engine that never stated anything is sent health")
@@ -46,15 +46,7 @@ func TestAComposedDeclarationCarriesModulesAndBuildsOnlyForANodeThatReadsThem(t
declare := func(node string) sendable {
t.Helper()
plan, settings, err := planFor(ctx, open, node)
if err != nil {
t.Fatal(err)
}
gens, err := generators(ctx, open)
if err != nil {
t.Fatal(err)
}
d, err := declarationWith(ctx, open, node, plan, settings, gens, Allocating)
d, err := composeSend(ctx, open, node)
if err != nil {
t.Fatal(err)
}
-17
View File
@@ -6,7 +6,6 @@ import (
"testing"
"git.novox.be/novox/mesh-sdk/go/validate"
"github.com/novox/mesh-controller/internal/chain"
)
// The module of each resource and the build of each module (novox/hq issue 1145, design 47 release 1-2). The
@@ -69,19 +68,3 @@ func TestTheNodeEnginesValidatorTakesAModulePerResourceAndABuildPerModule(t *tes
t.Fatalf("1145/AC4: refused: %q\n%s", problems, fmt.Sprint(generic))
}
}
// The build a module carries is the one its assignment names when that is the commit carried, else the one built from
// the commit carried; none when neither is known.
func TestTheBuildOfACarriedCommitIsTheAssignmentsOrTheOneBuiltFromIt(t *testing.T) {
runs := map[string]chain.Ref{"a": {Build: "b-a", Commit: "c1"}, "b": {Build: "b-old", Commit: "c0"}}
looked := map[string]string{"b/c2": "b-new"}
got, err := wireBuilds(map[string]string{"a": "c1", "b": "c2", "c": "c3", "d": ""}, runs,
func(module, commit string) (string, error) { return looked[module+"/"+commit], nil })
if err != nil {
t.Fatal(err)
}
want := map[string]string{"a": "b-a", "b": "b-new"}
if len(got) != len(want) || got["a"] != want["a"] || got["b"] != want["b"] {
t.Fatalf("1145: builds %v, want %v", got, want)
}
}
-129
View File
@@ -316,73 +316,6 @@ func graph(ctx context.Context, open *stores) ([]overlay.Node, overlay.Graph, er
return compose.Graph(in.Input)
}
// whoResolves is the machines whose resolution answers a requirement, and why the others did not.
//
// By what a module **provides**, not by its name. WireGuard is one way to have a private network
// and there could be others, so a machine is on the network because something it runs provides
// one — asking for a particular module by name would be the mistake this whole mechanism exists
// to avoid.
//
// Resolved rather than read from the assignment table, because a module can arrive by being
// required by something else, and a machine that needs the private network to do its job is on it
// for the same reason as one that was handed it directly.
func whoResolves(ctx context.Context, open *stores, requirement string) (
map[string]bool, map[string]string, error) {
inv := open.inventory
nodes, err := inv.Nodes(ctx)
if err != nil {
return nil, nil, err
}
on := map[string]bool{}
// Why a node could not be resolved, kept rather than raised: one broken node must not stop
// the rest being described, and whoever is rendering that node will raise it themselves.
refused := map[string]string{}
for _, n := range nodes {
plan, _, err := planFor(ctx, open, n.Name)
switch {
case unresolvable(err):
refused[n.Name] = err.Error()
continue
case err != nil:
// Not a node that does not resolve — a question that went unanswered. Recording it as a
// refusal would take the machine off the private network, and the generator that reads
// this would then write a roster and a filter without it (novox/hq 04-ISSUES/152).
return nil, nil, fmt.Errorf("whether %s answers %q cannot be read: %w",
n.Name, requirement, err)
}
for _, m := range plan.Modules {
for _, offered := range m.Offers() {
if offered == requirement {
on[n.Name] = true
}
}
}
}
return on, refused, nil
}
// rendering is everything a declaration needs, computed over the whole mesh.
func generators(ctx context.Context, open *stores) (
map[string]catalogue.Generator, error) {
inv := open.inventory
on, refused, err := whoResolves(ctx, open, overlay.Addressing)
if err != nil {
return nil, err
}
net, err := network(ctx, inv, on, refused)
if err != nil {
return nil, err
}
// **One generator now.** Two more used to sit beside it — the names and a resolver's zone
// file — as modules that ran nothing. Both are facts a module asks for in its manifest
// (`facts:` — catalogue.FactsInto), computed from the same machines this sees: the ones on the
// private network, because a name for a machine not on it would resolve to an address nothing
// can reach.
return map[string]catalogue.Generator{
overlay.Name: net,
}, nil
}
func overlayShow(ctx context.Context, open *stores) error {
nodes, computed, err := graph(ctx, open)
if err != nil {
@@ -576,68 +509,6 @@ func onTheNetwork(ctx context.Context, inv *inventory.Inventory,
return out, nil
}
// onThePrivateNetwork is every node's address on the private network, sorted — the same set
// the names and the resolver mean by it (onTheNetwork), so a rule saying "from the mesh" admits
// exactly the machines the mesh names.
//
// A node with no address is left out rather than rendered as an empty source: an empty entry in a
// source set is a syntax error in the rule file, and a rule file that does not load leaves the
// node filtering whatever it was filtering before -- the one outcome worse than a wrong rule,
// because nothing reports it.
func onThePrivateNetwork(ctx context.Context, inv *inventory.Inventory,
shelf map[string]catalogue.Manifest) ([]string, error) {
places, err := onTheNetwork(ctx, inv, shelf)
if err != nil {
return nil, err
}
var out []string
for _, p := range places {
if strings.TrimSpace(p.Address) != "" {
out = append(out, p.Address)
}
}
sort.Strings(out)
return out, nil
}
// namesInTheMesh is every machine's internal name and the address behind it — every machine
// that is on the private network, the same set the resolver means by that.
//
// A machine that is not has no name: writing one that resolves to nothing is worse than not
// writing it, because a connection to an address that does not answer hangs where a name that
// does not resolve fails at once and says so. A machine placed on the overlay but not running
// the module that puts it there is exactly that (novox/hq issue 079).
func namesInTheMesh(ctx context.Context, inv *inventory.Inventory,
shelf map[string]catalogue.Manifest) (map[string]string, error) {
places, err := onTheNetwork(ctx, inv, shelf)
if err != nil {
return nil, err
}
out := map[string]string{}
for _, p := range places {
out[overlay.InternalName(p.Name)] = p.Address
}
// And the carried peers the operator has named (novox/hq issue 112): machines the
// predecessor's resolver answers for and the mesh routes to, known by name on the operator's
// word until they enrol — at which point enrolment verifies the name and the node's own
// entry takes over above. A name the predecessor answers for must keep resolving until the
// machine behind it is a node; without these, taking the resolver silences three machines.
carried, err := inv.CarriedPeers(ctx)
if err != nil {
return nil, err
}
for _, p := range carried {
if p.Named == "" || p.EnrolledAs != "" {
continue
}
if _, taken := out[overlay.InternalName(p.Named)]; taken {
continue // a node of the mesh owns the name; the stale statement loses
}
out[overlay.InternalName(p.Named)] = p.Address
}
return out, nil
}
// artifactStoreOnNetwork is the machine on this network that offers the artifact store, and the
// port THAT MACHINE put it on — the node's setting when it was given one (novox/hq ADR 0100,
// 04-ISSUES/102), the mesh's assignment when it made one, and the manifest's own number only when
+1 -40
View File
@@ -84,37 +84,6 @@ func TestAPlacementAndNothingTogetherIsRefused(t *testing.T) {
}
}
// A machine is named for the others only while it is on the private network — placed AND running
// what puts it there — the same set the resolver means by "on the private network". A machine
// that has an address and no networking is not named: a name resolving to an address that does
// not answer hangs where an unknown name fails at once (novox/hq issue 079).
func TestOnlyAMachineOnThePrivateNetworkIsNamed(t *testing.T) {
open := aMesh(t) // two placed machines, both assigned what puts them on the private network
ctx := t.Context()
shelf, err := open.inventory.Catalogue(ctx)
if err != nil {
t.Fatal(err)
}
names, err := namesInTheMesh(ctx, open.inventory, shelf)
if err != nil {
t.Fatal(err)
}
if names["anchor.internal"] != "10.77.0.1" || names["laptop.internal"] != "10.77.0.2" {
t.Fatalf("two machines on the network are not both named: %v", names)
}
// The laptop keeps its place and its address, and stops running the network.
if err := open.inventory.Unassign(ctx, "laptop", overlay.Name); err != nil {
t.Fatal(err)
}
names, err = namesInTheMesh(ctx, open.inventory, shelf)
if err != nil {
t.Fatal(err)
}
if _, still := names["laptop.internal"]; still || names["anchor.internal"] != "10.77.0.1" {
t.Fatalf("a machine that left the network is still named, or the one that stayed is not: %v", names)
}
}
// novox/hq ADR 0105: the range every address is composed from is the adopted tunnel's, read from
// the tunnel the hub holds — never stored anywhere else.
func TestTheOverlaysRangeIsTheAdoptedTunnels(t *testing.T) {
@@ -315,16 +284,8 @@ func TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves(t *t
func TestTheRosterNamesOnlyTheMachines(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
gens, err := generators(ctx, open)
if err != nil {
t.Fatal(err)
}
for _, node := range []string{"anchor", "laptop"} {
plan, settings, err := planFor(ctx, open, node)
if err != nil {
t.Fatal(err)
}
with, _, err := renderingFor(ctx, open, node, plan, settings, gens, Reading)
with, err := renderingOf(ctx, open, node)
if err != nil {
t.Fatal(err)
}
@@ -5,9 +5,11 @@ package main
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/compose"
)
func TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer(t *testing.T) {
@@ -23,7 +25,7 @@ func TestAnUnassignThatLeavesItsNodeUnresolvedSaysSoOnceInItsAnswer(t *testing.T
said := answered(t, func() error { return assignCommand(ctx, "unassign", []string{"laptop", "notes"}) })
if _, _, err := planFor(ctx, open, "laptop"); err == nil || !unresolvable(err) {
if _, _, err := resolveNode(ctx, open, "laptop"); err == nil || !unresolvable(err) {
t.Fatalf("laptop resolves with two claimants of one seat: %v", err)
}
if n := strings.Count(said, "laptop does not resolve now"); n != 1 {
@@ -45,7 +47,7 @@ func TestAnUnassignThatLeavesItsNodeResolvedSaysNothingOfIt(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if _, _, err := planFor(ctx, open, "laptop"); err != nil {
if _, _, err := resolveNode(ctx, open, "laptop"); err != nil {
t.Fatal(err)
}
if strings.Contains(said, "does not resolve") || strings.Contains(said, "could not be read") {
@@ -65,7 +67,7 @@ func TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal(t *testing.T
if err == nil {
t.Fatalf("two claimants of one node seat resolved:\n%s", said)
}
_, _, planErr := planFor(ctx, open, "laptop")
_, _, planErr := resolveNode(ctx, open, "laptop")
if planErr == nil || planErr.Error() != err.Error() {
t.Errorf("the act's refusal is not the plan's right after:\nact: %v\nplan: %v", err, planErr)
}
@@ -75,15 +77,16 @@ func TestAnAssignThatLeavesItsNodeUnresolvedSaysOnlyThePlansRefusal(t *testing.T
}
// The other readers of the whole mesh still say a machine they leave out, on standard error (novox/hq
// issue 188); an act sends it elsewhere.
// issue 188); an act sends it elsewhere. The line is mesh-sdk's (compose.Declared.Said); where it goes is the context's.
func TestAMachineLeftOutIsSaidWhereTheContextSays(t *testing.T) {
line := "laptop is left out of the rest of the mesh: it does not resolve: why"
var buf bytes.Buffer
sayLeftOut(leftOutSaidBy(context.Background(), &buf), "laptop", errors.New("why"))
if buf.String() != "laptop is left out of the rest of the mesh: it does not resolve: why\n" {
sayFound(leftOutSaidBy(context.Background(), &buf), compose.Declared{Said: []string{line}})
if buf.String() != line+"\n" {
t.Errorf("said %q", buf.String())
}
got := answered(t, func() error {
sayLeftOut(context.Background(), "laptop", errors.New("why"))
sayFound(context.Background(), compose.Declared{Said: []string{line}})
return nil
})
if !strings.Contains(got, "laptop is left out") {
+1 -1
View File
@@ -32,7 +32,7 @@ func TestOneAcceptForAProviderIsPlacedForEveryConsumer(t *testing.T) {
}
placed := func(module string) bool {
t.Helper()
plan, settings, err := planFor(ctx, open, "laptop")
plan, settings, err := resolveNode(ctx, open, "laptop")
if err != nil {
t.Fatalf("laptop was refused: %v", err)
}
+4 -11
View File
@@ -6,6 +6,7 @@ import (
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/compose"
)
// An own secret issued outside the mesh that nobody gave is never made (novox/hq issue 401, ADR 0228). Seen on
@@ -28,7 +29,7 @@ func anAssistant() catalogue.Manifest {
// sentTo composes what a push sends to a machine, on the send path.
func sentTo(t *testing.T, open *stores, node string) []map[string]any {
t.Helper()
plan, settings, err := planFor(t.Context(), open, node)
plan, settings, err := resolveNode(t.Context(), open, node)
if err != nil {
t.Fatal(err)
}
@@ -99,16 +100,8 @@ func TestAQuestionComposesWithoutAnOutsideSecretNobodyGave(t *testing.T) {
t.Fatal(err)
}
pushedBy(t, open, "laptop") // the mesh's own secret is made by the send
plan, settings, err := planFor(ctx, open, "laptop")
if err != nil {
t.Fatal(err)
}
gens, err := generators(ctx, open)
if err != nil {
t.Fatal(err)
}
for _, choosing := range []Choosing{Reading, Foreseeing} {
got, err := declarationWith(ctx, open, "laptop", plan, settings, gens, choosing)
for _, choosing := range []compose.Mode{compose.Reading, compose.Foreseeing} {
got, _, err := composeQuestion(ctx, open, "laptop", choosing, false)
if err != nil {
t.Fatalf("%v: %v", choosing, err)
}
+2 -6
View File
@@ -63,16 +63,12 @@ func TestADeclarationIsTheSameAfterPlacementMoves(t *testing.T) {
}
}
gens, err := generators(ctx, open)
if err != nil {
t.Fatal(err)
}
for _, node := range []string{"anchor", "laptop"} {
plan, settings, err := planFor(ctx, open, node)
plan, _, err := resolveNode(ctx, open, node)
if err != nil {
t.Fatal(err)
}
with, _, err := renderingFor(ctx, open, node, plan, settings, gens, Allocating)
with, err := madeAndRendered(ctx, open, node)
if err != nil {
t.Fatal(err)
}
@@ -49,15 +49,11 @@ func theWordedMesh(t *testing.T) wordedMesh {
func toldOfTheWords(t *testing.T, open *stores, node string) string {
t.Helper()
ctx := t.Context()
gens, err := generators(ctx, open)
plan, _, err := resolveNode(ctx, open, node)
if err != nil {
t.Fatal(err)
}
plan, settings, err := planFor(ctx, open, node)
if err != nil {
t.Fatal(err)
}
with, _, err := renderingFor(ctx, open, node, plan, settings, gens, Allocating)
with, err := madeAndRendered(ctx, open, node)
if err != nil {
t.Fatal(err)
}