diff --git a/cmd/mesh-control/main.go b/cmd/mesh-control/main.go index 6979387..abc8f49 100644 --- a/cmd/mesh-control/main.go +++ b/cmd/mesh-control/main.go @@ -994,8 +994,13 @@ func planFor(ctx context.Context, inv *inventory.Inventory, nodeName string) (ca return catalogue.Resolution{}, nil, err } + onNetwork, err := whereEveryoneIs(ctx, inv, shelf) + if err != nil { + return catalogue.Resolution{}, nil, err + } resolved, err := catalogue.Resolve(shelf, assigned, - catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities}, world) + catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities, + At: onNetwork[nodeName]}, world) if err != nil { return catalogue.Resolution{}, nil, err } @@ -1053,6 +1058,13 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, for _, p := range places { siteOf[p.Name] = p.Site } + // Which machines are actually on the private network, and what they are called there. Not + // "has an address" — that was true of every placed machine and told you nothing about whether + // anything could reach it. It is what resolved the module. + onNetwork, err := whereEveryoneIs(ctx, inv, shelf) + if err != nil { + return catalogue.World{}, err + } type candidate struct { node catalogue.Node @@ -1069,10 +1081,11 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, } caps, _ := inv.ProfileOf(ctx, n.Name) others = append(others, candidate{ - catalogue.Node{Name: n.Name, Site: siteOf[n.Name], Capabilities: caps}, theirs}) + catalogue.Node{Name: n.Name, Site: siteOf[n.Name], Capabilities: caps, + At: onNetwork[n.Name]}, theirs}) } - offered := map[string][]string{} + offered := map[string][]catalogue.Provider{} for _, o := range others { got, err := catalogue.Resolve(shelf, o.assigned, o.node, catalogue.World{Unchecked: true}) if err != nil { @@ -1082,12 +1095,29 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, } for _, m := range got.Modules { for _, name := range m.OffersAt(catalogue.ScopeMesh) { - offered[name] = append(offered[name], o.node.Name) + // What that module says a consumer needs to know, with that node's settings on + // it: a port somebody moved on the provider is a port its consumers must be told + // about, and the two coming from different places is how they come to disagree. + serves := m.Serves[name] + if len(serves) > 0 { + layers, err := inv.SettingsFor(ctx, o.node.Name, m.Module) + if err != nil { + return catalogue.World{}, err + } + serves, err = catalogue.Settle(serves, layers) + if err != nil { + return catalogue.World{}, err + } + } + offered[name] = append(offered[name], catalogue.Provider{ + Node: o.node.Name, At: o.node.At, Serves: serves}) } } } for k := range offered { - sort.Strings(offered[k]) + sort.Slice(offered[k], func(i, j int) bool { + return offered[k][i].Node < offered[k][j].Node + }) } world := catalogue.World{Offered: offered} @@ -1101,6 +1131,54 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, return world, nil } +// whereEveryoneIs is each machine's name on the private network, for the ones on it. +// +// **Resolved without consulting the rest of the mesh**, and that is not an optimisation. Every +// other path here answers a question about one node by resolving the others; this one is called +// *from* that path, so doing the same would not terminate — which it did not, for two minutes, +// until it was run. +// +// An unchecked resolution is exactly right for the question anyway. Whether a machine is on the +// private network depends on what it was assigned and what that requires, both of which are local +// facts. What it takes *from* other machines does not change the answer. +// +// The distinction that matters is kept: a machine absent from the network module's own view is +// absent here, so "has an address" is not mistaken for "is reachable" — which it was, before the +// network became something a machine is given. +func whereEveryoneIs(ctx context.Context, inv *inventory.Inventory, + shelf map[string]catalogue.Manifest) (map[string]string, error) { + + places, err := inv.Overlays(ctx) + if err != nil { + return nil, err + } + out := map[string]string{} + for _, p := range places { + if p.Address == "" { + continue + } + assigned, err := inv.Assigned(ctx, p.Name) + if err != nil || len(assigned) == 0 { + continue + } + caps, _ := inv.ProfileOf(ctx, p.Name) + got, err := catalogue.Resolve(shelf, assigned, + catalogue.Node{Name: p.Name, Site: p.Site, Capabilities: caps}, + catalogue.World{Unchecked: true}) + if err != nil { + continue + } + for _, m := range got.Modules { + for _, offered := range m.Offers() { + if offered == overlay.Requirement { + out[p.Name] = overlay.InternalName(p.Name) + } + } + } + } + return out, nil +} + func planCommand(ctx context.Context, args []string) error { set := flag.NewFlagSet("plan", flag.ContinueOnError) // Because "one resource" does not tell you whether the settings landed. Being able to read diff --git a/internal/catalogue/brokered_test.go b/internal/catalogue/brokered_test.go index 096096a..c404992 100644 --- a/internal/catalogue/brokered_test.go +++ b/internal/catalogue/brokered_test.go @@ -1,6 +1,7 @@ package catalogue import ( + "encoding/json" "strings" "testing" ) @@ -12,6 +13,23 @@ import ( // `requires`, so both were answered the same way, and the second answer was to install PostgreSQL // on every machine that runs a web application. +// onNetwork is a set of providers, all of them reachable. Written as a helper because a machine +// that cannot reach the one answering its requirement is its own case, tested separately. +// reachable is this machine, on the private network. +func reachable() Node { + n := workstation() + n.At = "workstation.internal" + return n +} + +func onNetwork(nodes ...string) map[string][]Provider { + out := make([]Provider, 0, len(nodes)) + for _, n := range nodes { + out = append(out, Provider{Node: n, At: n + ".internal"}) + } + return map[string][]Provider{"database": out} +} + func brokeredShelf() map[string]Manifest { return shelf( Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("database")}, @@ -21,8 +39,8 @@ func brokeredShelf() map[string]Manifest { func TestADatabaseIsNotInstalledOnEveryMachineThatUsesOne(t *testing.T) { // The fault this whole distinction exists for. - got, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), - World{Offered: map[string][]string{"database": {"anchor"}}}) + got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), + World{Offered: onNetwork("anchor")}) if err != nil { t.Fatal(err) } @@ -34,8 +52,8 @@ func TestADatabaseIsNotInstalledOnEveryMachineThatUsesOne(t *testing.T) { func TestWhatAMachineTakesFromElsewhereIsRecorded(t *testing.T) { // It is the only part of a node's set that stops working when a *different* machine goes // away, and it is where a credential will have to be handed back. - got, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), - World{Offered: map[string][]string{"database": {"anchor"}}}) + got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), + World{Offered: onNetwork("anchor")}) if err != nil { t.Fatal(err) } @@ -65,8 +83,8 @@ func TestNothingInTheMeshProvidingItIsRefusedWithSomewhereToPutIt(t *testing.T) func TestTwoNodesProvidingItIsRefusedRatherThanPicked(t *testing.T) { // Same rule as everywhere else. Picking one would be a guess about which database a person // meant, and the wrong guess is somebody's data in the wrong place. - _, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), - World{Offered: map[string][]string{"database": {"anchor", "archive"}}}) + _, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), + World{Offered: onNetwork("anchor", "archive")}) if err == nil { t.Fatal("one of two databases was picked silently") } @@ -78,9 +96,9 @@ func TestTwoNodesProvidingItIsRefusedRatherThanPicked(t *testing.T) { } func TestSayingWhichOneSettlesIt(t *testing.T) { - got, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), + got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), World{ - Offered: map[string][]string{"database": {"anchor", "archive"}}, + Offered: onNetwork("anchor", "archive"), Pinned: map[string]string{"database": "archive"}, }) if err != nil { @@ -94,9 +112,9 @@ func TestSayingWhichOneSettlesIt(t *testing.T) { func TestBeingPointedAtAMachineThatDoesNotProvideItIsRefused(t *testing.T) { // Rather than falling back to one that does. A fallback would quietly move somebody's data to // a machine they did not choose, which is the whole reason the question is asked. - _, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), + _, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), World{ - Offered: map[string][]string{"database": {"anchor", "archive"}}, + Offered: onNetwork("anchor", "archive"), Pinned: map[string]string{"database": "somewhere-else"}, }) if err == nil { @@ -110,9 +128,9 @@ func TestBeingPointedAtAMachineThatDoesNotProvideItIsRefused(t *testing.T) { func TestOneProviderDoesNotOverruleAChoice(t *testing.T) { // A single answer is normally taken silently. Not when somebody said they wanted a different // one -- that is the mesh overruling a person, which it does nowhere else. - _, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), + _, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(), World{ - Offered: map[string][]string{"database": {"anchor"}}, + Offered: onNetwork("anchor"), Pinned: map[string]string{"database": "archive"}, }) if err == nil { @@ -177,3 +195,134 @@ func TestASiteScopedProvisionIsRefused(t *testing.T) { t.Fatalf("unhelpful refusal: %v", err) } } + +// Being told, which is the difference between knowing and being able to. + +func boundShelf() map[string]Manifest { + return shelf( + Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("database"), + Serves: map[string]map[string]any{"database": {"port": 5432, "driver": "postgres"}}}, + Manifest{Module: "meshboard", Version: "1", Requires: []string{"database"}, + Binds: map[string]string{"database": "/etc/meshboard/database.json"}}, + ) +} + +func binding(t *testing.T, out []map[string]any) map[string]any { + t.Helper() + for _, r := range out { + if r["path"] != "/etc/meshboard/database.json" { + continue + } + var parsed map[string]any + if err := json.Unmarshal([]byte(r["content"].(string)), &parsed); err != nil { + t.Fatalf("what the app is told is not readable: %v", err) + } + return parsed + } + t.Fatalf("the app was told nothing: %v", out) + return nil +} + +func TestAnAppIsToldWhereItsDatabaseIs(t *testing.T) { + // Knowing it needs the anchor's database is useless to the program that needs it unless the + // program is told. This is the whole point of the field. + got, err := Resolve(boundShelf(), []string{"meshboard"}, reachable(), + World{Offered: map[string][]Provider{"database": {{Node: "anchor", At: "anchor.internal", + Serves: map[string]any{"port": 5432, "driver": "postgres"}}}}}) + if err != nil { + t.Fatal(err) + } + told := binding(t, mustDeclare(t, got)) + if told["from"] != "anchor" || told["at"] != "anchor.internal" { + t.Fatalf("it was not told where: %v", told) + } + serves, _ := told["serves"].(map[string]any) + if serves["port"] != float64(5432) || serves["driver"] != "postgres" { + t.Fatalf("it was not told how: %v", serves) + } +} + +func TestItSaysItCarriesNoCredential(t *testing.T) { + // A missing field looks like a bug; a stated absence looks like a boundary. Somebody wiring + // this up must not spend an afternoon looking for the password field. + got, _ := Resolve(boundShelf(), []string{"meshboard"}, reachable(), + World{Offered: map[string][]Provider{"database": {{Node: "anchor", At: "anchor.internal"}}}}) + told := binding(t, mustDeclare(t, got)) + note, _ := told["generated"].(string) + if !strings.Contains(note, "no credential") { + t.Fatalf("the file does not say what it does not carry: %v", note) + } + for key := range told { + if strings.Contains(key, "password") || strings.Contains(key, "secret") { + t.Fatalf("something that looks like a credential appeared: %q", key) + } + } +} + +func TestTwoMachinesThatCannotReachEachOtherAreRefused(t *testing.T) { + // An app on one machine and a database on another that share no private network is a mesh + // that reports itself configured and does not work. Said here rather than discovered as a + // connection timing out. + _, err := Resolve(boundShelf(), []string{"meshboard"}, workstation(), // not on the network + World{Offered: map[string][]Provider{"database": {{Node: "anchor", At: "anchor.internal"}}}}) + if err == nil { + t.Fatal("an app was pointed at a database it has no path to") + } + if !strings.Contains(err.Error(), "private network") { + t.Fatalf("the refusal does not say what is wrong: %v", err) + } + if !strings.Contains(err.Error(), meshNetwork) { + t.Fatalf("the refusal does not say what to assign: %v", err) + } +} + +func TestTheProviderBeingOffTheNetworkIsAlsoRefused(t *testing.T) { + // Both directions, because the failure is identical from either end and the remedy differs. + _, err := Resolve(boundShelf(), []string{"meshboard"}, reachable(), + World{Offered: map[string][]Provider{"database": {{Node: "anchor"}}}}) + if err == nil { + t.Fatal("an app was pointed at a database that is not on the private network") + } + if !strings.Contains(err.Error(), "anchor") { + t.Fatalf("the refusal does not name the unreachable end: %v", err) + } +} + +func TestBindingSomethingAnsweredHereWritesNothing(t *testing.T) { + // A file saying "it is on this node" is a fact nobody needs and one more thing to keep true. + got, err := Resolve(shelf( + Manifest{Module: "zsh", Version: "1", Provides: Offers("shell")}, + Manifest{Module: "tools", Version: "1", Requires: []string{"shell"}, + Binds: map[string]string{"shell": "/etc/tools/shell.json"}}, + ), []string{"tools"}, reachable(), World{}) + if err != nil { + t.Fatal(err) + } + for _, r := range mustDeclare(t, got) { + if r["path"] == "/etc/tools/shell.json" { + t.Fatalf("a binding was written for something on this machine: %v", r) + } + } +} + +func TestBindingWhatYouDoNotRequireIsRefused(t *testing.T) { + _, err := ParseManifest([]byte(`{"module":"app","version":"1", + "binds":{"database":"/etc/app/db.json"}}`)) + if err == nil { + t.Fatal("a module was told about something it never asked for") + } + if !strings.Contains(err.Error(), "does not require") { + t.Fatalf("unhelpful refusal: %v", err) + } +} + +func TestServingWhatYouDoNotProvideIsRefused(t *testing.T) { + _, err := ParseManifest([]byte(`{"module":"app","version":"1", + "serves":{"database":{"port":5432}}}`)) + if err == nil { + t.Fatal("a module served something it does not provide") + } + if !strings.Contains(err.Error(), "does not provide") { + t.Fatalf("unhelpful refusal: %v", err) + } +} diff --git a/internal/catalogue/manifest.go b/internal/catalogue/manifest.go index d42b650..b7b6e91 100644 --- a/internal/catalogue/manifest.go +++ b/internal/catalogue/manifest.go @@ -162,8 +162,27 @@ type Manifest struct { // delivers the facts, and the module turns them into whatever it runs. That boundary is why // swapping the proxy does not touch a single module that publishes through it. Receives map[string]string `json:"receives,omitempty"` + + // Serves is what a consumer needs to know in order to use something this module provides — a + // port, a path, a realm. The module's half of the answer; the mesh adds the other half, which + // is *which machine* and *where it is on the private network*. + // + // It does not carry a credential and cannot: a manifest is the same on every mesh, and a + // secret is the one thing that must not be. + Serves map[string]map[string]any `json:"serves,omitempty"` + + // Binds is where this module wants to be told about something it requires, per requirement. + // + // Because "this machine needs a database from the anchor" is useless to the program that + // needs it unless the program is told. A file, like everything else — the host writes files + // and knows nothing about provisions, which is what keeps this from needing anything new + // down there. + Binds map[string]string `json:"binds,omitempty"` } +// BoundID is the resource identity of the file a module is told about a provision in. +func BoundID(requirement string) string { return "bound-" + requirement } + // Wants is everything that must be provided on the same node: what this module requires, and what // it contributes to. func (m Manifest) Wants() []string { @@ -263,6 +282,36 @@ func ParseManifest(raw []byte) (Manifest, error) { "%s contributes nothing to %q; if it only needs one, require it", m.Module, to)) } } + for to := range m.Serves { + var offered bool + for _, o := range m.Offers() { + if o == to { + offered = true + } + } + if !offered { + problems = append(problems, fmt.Sprintf( + "%s serves %q to whoever requires it, and does not provide it", m.Module, to)) + } + } + for to, where := range m.Binds { + if !strings.HasPrefix(where, "/") { + problems = append(problems, fmt.Sprintf( + "%s binds %q at %q, which is not an absolute path", m.Module, to, where)) + } + var wanted bool + for _, w := range m.Wants() { + if w == to { + wanted = true + } + } + if !wanted { + // Being told about something you never asked for would write a file describing a + // machine this one has no business talking to. + problems = append(problems, fmt.Sprintf( + "%s binds %q and does not require it", m.Module, to)) + } + } for to, where := range m.Receives { if !name.MatchString(to) { problems = append(problems, fmt.Sprintf("%q is not a usable name to receive", to)) diff --git a/internal/catalogue/resolve.go b/internal/catalogue/resolve.go index b8ff77f..ae7a38c 100644 --- a/internal/catalogue/resolve.go +++ b/internal/catalogue/resolve.go @@ -22,6 +22,9 @@ type Node struct { // — a capability that was looked for and not found is the same as one nobody looked for, as // far as deciding what may run here goes. Capabilities map[string]bool + // At is this machine's own name on the private network, empty if it is not on one. Needed to + // tell whether it can reach the node answering its requirements at all. + At string } // World is what the rest of the mesh already has. @@ -31,8 +34,8 @@ type Node struct { type World struct { // Held is the claims already taken, for the scopes wider than one node. Held []Held - // Offered is what other nodes provide at mesh scope: the name, and which nodes provide it. - Offered map[string][]string + // Offered is what other nodes provide at mesh scope, and everything needed to use it. + Offered map[string][]Provider // Pinned is which node this machine was told to get a provision from, by name. Only consulted // when more than one node could answer -- a choice recorded before it was needed should not // start meaning something the day a second provider appears, and one recorded and then made @@ -48,6 +51,17 @@ type World struct { Unchecked bool } +// Provider is one node answering a mesh-scoped requirement. +type Provider struct { + // Node is the machine. + Node string + // At is its name on the private network, or empty if it is not on one. The mesh's half of + // the answer: a module can say it serves on port 5432, and only the mesh knows where. + At string + // Serves is what the providing module said a consumer needs to know, settled. + Serves map[string]any +} + // Held is a claim somebody already has, used for the scopes wider than one node. type Held struct { Claim string @@ -80,6 +94,10 @@ type Needed struct { Name string // From is the node providing it. From string + // At is where that node is on the private network. + At string + // Serves is what the providing module said a consumer needs to know. + Serves map[string]any // For is the module that wanted it. For string } @@ -189,6 +207,25 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world if brokered[want] { reported[want] = true where := world.Offered[want] + names := make([]string, 0, len(where)) + for _, p := range where { + names = append(names, p.Node) + } + sort.Strings(names) + take := func(p Provider) { + if node.At != "" && p.At == "" || node.At == "" && p.At != "" || node.At == "" && p.At == "" { + // One of them is not on the private network, so there is no path between + // them. Said here rather than discovered as a connection timing out on a + // machine that the mesh reported as configured. + problems = append(problems, fmt.Sprintf( + "%s needs %q from %s, and they are not both on the private network — "+ + "assign %s to whichever is missing it", + node.Name, want, p.Node, meshNetwork)) + return + } + needs = append(needs, Needed{Name: want, From: p.Node, At: p.At, + Serves: p.Serves, For: because[want]}) + } switch { case world.Unchecked: // First pass. Whether anything answers this is exactly the question this pass @@ -204,42 +241,40 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world "nothing in this mesh provides %q, wanted by %s %s", want, because[want], remedy)) case len(where) == 1: - if chosenNode, pinned := world.Pinned[want]; pinned && chosenNode != where[0] { + if chosenNode, pinned := world.Pinned[want]; pinned && chosenNode != where[0].Node { // One provider, and it is not the one this machine was told to use. Silently // using the other would be the mesh overruling a choice somebody made. problems = append(problems, fmt.Sprintf( "%s was told to get %q from %s, and only %s provides it", - node.Name, want, chosenNode, where[0])) + node.Name, want, chosenNode, where[0].Node)) break } - needs = append(needs, Needed{Name: want, From: where[0], For: because[want]}) + take(where[0]) default: - sorted := append([]string{}, where...) - sort.Strings(sorted) chosenNode, pinned := world.Pinned[want] if !pinned { problems = append(problems, fmt.Sprintf( "%d nodes provide %q, wanted by %s — say which with `pin %s %s `: %s", len(where), want, because[want], node.Name, want, - strings.Join(sorted, ", "))) + strings.Join(names, ", "))) break } - var offers bool - for _, w := range where { - if w == chosenNode { - offers = true + var chosen *Provider + for i, w := range where { + if w.Node == chosenNode { + chosen = &where[i] } } - if !offers { + if chosen == nil { // Pointed at a machine that does not answer this. Refused rather than // falling back to another: a fallback would quietly move somebody's data to // a machine they did not choose, which is the whole reason this is asked. problems = append(problems, fmt.Sprintf( "%s was told to get %q from %s, and %s does not provide it — these do: %s", - node.Name, want, chosenNode, chosenNode, strings.Join(sorted, ", "))) + node.Name, want, chosenNode, chosenNode, strings.Join(names, ", "))) break } - needs = append(needs, Needed{Name: want, From: chosenNode, For: because[want]}) + take(*chosen) } continue } @@ -438,6 +473,25 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) { var out []map[string]any for _, m := range r.Modules { resources := m.Resources + for _, to := range sortedKeys(m.Binds) { + var found *Needed + for i, n := range r.Needs { + if n.Name == to { + found = &r.Needs[i] + } + } + if found == nil { + // Bound to something answered on this machine rather than from the mesh. Nothing + // to write: the answer is here, and a file saying "it is on this node" would be + // a fact nobody needs and one more thing to keep true. + continue + } + file, err := boundFile(*found, m.Binds[to]) + if err != nil { + return nil, err + } + resources = append(append([]map[string]any{}, resources...), file) + } for _, to := range sortedKeys(m.Receives) { file, err := receivedFile(to, m.Receives[to], given[to]) if err != nil { @@ -579,3 +633,34 @@ func FromAnywhere(names ...string) []Offer { } return out } + +// meshNetwork is what to assign to a machine that needs to reach another one. +// +// A string here rather than an import, because the private network is a module the control plane +// ships and this package must not depend on the thing it resolves. The name being wrong would +// show up as a refusal naming a module nobody can assign, which a test checks. +const meshNetwork = "networking" + +// boundFile is what a module is told about something it requires from another machine. +// +// Where it is and what the providing module said about using it. **No credential**, and the file +// says so rather than leaving a reader to wonder whether one was meant to be there — a missing +// field looks like a bug, and a stated absence looks like a boundary. +func boundFile(n Needed, path string) (map[string]any, error) { + body, err := json.MarshalIndent(map[string]any{ + "binding": 1, + "provision": n.Name, + "from": n.From, + "at": n.At, + "serves": n.Serves, + "generated": "by the mesh — do not edit; replaced whenever this changes. " + + "It carries no credential: the mesh has no way to issue one yet", + }, "", " ") + if err != nil { + return nil, err + } + return map[string]any{ + "id": BoundID(n.Name), "type": "file", "path": path, "mode": "0644", + "content": string(body) + "\n", + }, nil +} diff --git a/internal/catalogue/settings.go b/internal/catalogue/settings.go index 3ae9bb7..ef1fc8d 100644 --- a/internal/catalogue/settings.go +++ b/internal/catalogue/settings.go @@ -85,6 +85,12 @@ func ApplySettings(resource map[string]any, layers []Layer) (map[string]any, err return out, nil } +// Settle lays settings over a module's own values. Exported for what a provider serves, which is +// settled where the mesh is walked rather than where a node is declared. +func Settle(base map[string]any, layers []Layer) (map[string]any, error) { + return settle(base, layers, nil, "what is served") +} + // settle lays the layers over a module's own values, in order. // // Shared by a file's content and a module's contributions, because they are the same act: the