From 655c692a95fd24590afd19f0f41c3c17d4d77072 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 11:11:54 +0000 Subject: [PATCH] stage 3 of 8 for issue 1389 --- cmd/mesh-controller/issued_test.go | 112 +++++++++------ cmd/mesh-controller/local_network.go | 18 --- .../merge_gate_handing_integration_test.go | 9 +- .../merge_gate_handing_test.go | 6 +- cmd/mesh-controller/module_health_test.go | 6 +- .../modules_wire_store_test.go | 10 +- cmd/mesh-controller/modules_wire_test.go | 17 --- cmd/mesh-controller/network.go | 129 ------------------ cmd/mesh-controller/network_test.go | 41 +----- .../one_resolution_integration_test.go | 19 +-- cmd/mesh-controller/outside_accept_test.go | 2 +- cmd/mesh-controller/outside_not_given_test.go | 15 +- cmd/mesh-controller/placement_moved_test.go | 8 +- .../placement_words_moved_test.go | 8 +- 14 files changed, 107 insertions(+), 293 deletions(-) diff --git a/cmd/mesh-controller/issued_test.go b/cmd/mesh-controller/issued_test.go index 7752244e..baa1b90a 100644 --- a/cmd/mesh-controller/issued_test.go +++ b/cmd/mesh-controller/issued_test.go @@ -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") + } } diff --git a/cmd/mesh-controller/local_network.go b/cmd/mesh-controller/local_network.go index 833e3af3..8cd4ec06 100644 --- a/cmd/mesh-controller/local_network.go +++ b/cmd/mesh-controller/local_network.go @@ -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 { diff --git a/cmd/mesh-controller/merge_gate_handing_integration_test.go b/cmd/mesh-controller/merge_gate_handing_integration_test.go index 05710b27..58259195 100644 --- a/cmd/mesh-controller/merge_gate_handing_integration_test.go +++ b/cmd/mesh-controller/merge_gate_handing_integration_test.go @@ -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) } } diff --git a/cmd/mesh-controller/merge_gate_handing_test.go b/cmd/mesh-controller/merge_gate_handing_test.go index 318c73ff..73c5a9da 100644 --- a/cmd/mesh-controller/merge_gate_handing_test.go +++ b/cmd/mesh-controller/merge_gate_handing_test.go @@ -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) } } diff --git a/cmd/mesh-controller/module_health_test.go b/cmd/mesh-controller/module_health_test.go index f3706247..c28989d4 100644 --- a/cmd/mesh-controller/module_health_test.go +++ b/cmd/mesh-controller/module_health_test.go @@ -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") diff --git a/cmd/mesh-controller/modules_wire_store_test.go b/cmd/mesh-controller/modules_wire_store_test.go index a708ddcd..ea3fcd6f 100644 --- a/cmd/mesh-controller/modules_wire_store_test.go +++ b/cmd/mesh-controller/modules_wire_store_test.go @@ -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) } diff --git a/cmd/mesh-controller/modules_wire_test.go b/cmd/mesh-controller/modules_wire_test.go index 50b4c961..7dd25721 100644 --- a/cmd/mesh-controller/modules_wire_test.go +++ b/cmd/mesh-controller/modules_wire_test.go @@ -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) - } -} diff --git a/cmd/mesh-controller/network.go b/cmd/mesh-controller/network.go index 2137473e..a05ee537 100644 --- a/cmd/mesh-controller/network.go +++ b/cmd/mesh-controller/network.go @@ -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 diff --git a/cmd/mesh-controller/network_test.go b/cmd/mesh-controller/network_test.go index 9ff9056d..92ae3d31 100644 --- a/cmd/mesh-controller/network_test.go +++ b/cmd/mesh-controller/network_test.go @@ -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) } diff --git a/cmd/mesh-controller/one_resolution_integration_test.go b/cmd/mesh-controller/one_resolution_integration_test.go index 989f3ca2..f8972224 100644 --- a/cmd/mesh-controller/one_resolution_integration_test.go +++ b/cmd/mesh-controller/one_resolution_integration_test.go @@ -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") { diff --git a/cmd/mesh-controller/outside_accept_test.go b/cmd/mesh-controller/outside_accept_test.go index e3aa7cd9..f34f8d58 100644 --- a/cmd/mesh-controller/outside_accept_test.go +++ b/cmd/mesh-controller/outside_accept_test.go @@ -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) } diff --git a/cmd/mesh-controller/outside_not_given_test.go b/cmd/mesh-controller/outside_not_given_test.go index e1becf05..c38ae56e 100644 --- a/cmd/mesh-controller/outside_not_given_test.go +++ b/cmd/mesh-controller/outside_not_given_test.go @@ -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) } diff --git a/cmd/mesh-controller/placement_moved_test.go b/cmd/mesh-controller/placement_moved_test.go index b873070c..661f0691 100644 --- a/cmd/mesh-controller/placement_moved_test.go +++ b/cmd/mesh-controller/placement_moved_test.go @@ -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) } diff --git a/cmd/mesh-controller/placement_words_moved_test.go b/cmd/mesh-controller/placement_words_moved_test.go index f6562433..3a8dbadc 100644 --- a/cmd/mesh-controller/placement_words_moved_test.go +++ b/cmd/mesh-controller/placement_words_moved_test.go @@ -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) }