Files
mesh-controller/cmd/mesh-control/plan.go
T
jschoubben c4030947b0 The routing record is 0066, not 0056
0056 is 'the authority is the control plane, not a database'. A citation
pointing at the wrong decision is worse than none: it reads as corroboration.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-11 00:09:56 +02:00

858 lines
31 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"sort"
"strings"
"github.com/novox/mesh-control/internal/catalogue"
"github.com/novox/mesh-control/internal/inventory"
"github.com/novox/mesh-control/internal/licences"
)
// working out what one machine should be.
//
// Split out of main.go, which had reached 2,769 lines because appending was always the
// cheapest next step. That is how novox/hq ADR 0001 records `hal/sdk` reaching 34,636:
// nothing in it was wrong, and no one edit was the one that should have been a new file.
// planFor works out everything a node should run, from what was assigned to it.
func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Resolution, catalogue.SettingsBy, error) {
inv := open.inventory
shelf, err := inv.Catalogue(ctx)
if err != nil {
return catalogue.Resolution{}, nil, err
}
assigned, err := inv.Assigned(ctx, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
capabilities, err := inv.ProfileOf(ctx, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
places, err := inv.Overlays(ctx)
if err != nil {
return catalogue.Resolution{}, nil, err
}
var site string
for _, p := range places {
if p.Name == nodeName {
site = p.Site
}
}
world, err := theRestOfTheMesh(ctx, inv, shelf, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
world.Pinned, err = inv.PinsFor(ctx, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
onNetwork, err := whereEveryoneIs(ctx, inv, shelf)
if err != nil {
return catalogue.Resolution{}, nil, err
}
// What this mesh can answer with a record rather than a machine, and which record each of
// this node's modules was put on. Read across a context boundary by name, which is what
// crossing one is allowed to carry (novox/hq ADR 0008).
world.Licences, world.Using, err = licencesFor(ctx, open, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
// The domain this node composes its routed names under (novox/hq ADR 0066). A route
// contribution carries only a label; the resolver joins <label>.<public-domain> for this node,
// so the fact travels on the node it belongs to rather than being looked up where the name is
// composed.
publicDomain, err := inv.PublicDomainOf(ctx, nodeName)
if err != nil {
return catalogue.Resolution{}, nil, err
}
resolved, err := catalogue.Resolve(shelf, assigned,
catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities,
At: onNetwork[nodeName], PublicDomain: publicDomain}, world)
if err != nil {
return catalogue.Resolution{}, nil, err
}
// The credential for each thing this node takes from elsewhere. Made once and kept, so the
// password a provider is told to create is the one its consumer was given — and sealed to
// this node before it was ever written down, so nothing between here and there can read it.
for i, n := range resolved.Needs {
if n.ByRecord {
// Answered by something the mesh holds, so there is no pair-wise secret between two
// machines. Its key was supplied by a person and sealed to this node then; the mesh
// discarded the plaintext and cannot make another.
sealed, err := keyFor(ctx, open, n.From, nodeName, n.For)
if err != nil {
return catalogue.Resolution{}, nil, err
}
resolved.Needs[i].Sealed = sealed
// If this holder is the licence's manager, hand it the manager node's PUBLIC sealing key
// in its bound facts (novox/hq ADR 0050). It is safe to disclose — a public key — and it
// is what the manager module needs to re-seal a rotated refresh token to this same node,
// having been given no private key of its own. A consumer holder gets none.
pub, err := managerPublicKeyFor(ctx, open, inv, n.From, nodeName, n.For)
if err != nil {
return catalogue.Resolution{}, nil, err
}
if pub != "" {
serves := map[string]any{}
for k, v := range resolved.Needs[i].Serves {
serves[k] = v
}
serves["manager_public_key"] = pub
resolved.Needs[i].Serves = serves
// The manager holder: its empty pre-adoption refresh token is a waiting state, not a
// missing consumer key, so the declaration tolerates it rather than refusing.
resolved.Needs[i].Manager = true
}
continue
}
secret, err := inv.SecretFor(ctx, n.Name, nodeName, n.For, n.From)
if err != nil {
// Said rather than skipped. A machine that resolves cleanly and receives no
// credential is one that will fail to authenticate at some later, less obvious
// moment.
return catalogue.Resolution{}, nil, fmt.Errorf(
"%s on %s needs %s from %s and no credential could be made for it: %w",
n.For, nodeName, n.Name, n.From, err)
}
resolved.Needs[i].Sealed = secret.ForConsumer
}
// Settings for everything that resolved, including modules nobody assigned directly: a
// requirement pulled in by something else is still configurable, and finding out that it is
// not only when you try would be an arbitrary line nobody could predict.
settings := catalogue.SettingsBy{}
var stray []string
for _, m := range resolved.Modules {
layers, err := inv.SettingsFor(ctx, nodeName, m.Module)
if err != nil {
return catalogue.Resolution{}, nil, err
}
if len(layers) == 0 {
continue
}
settings[m.Module] = layers
stray = append(stray, catalogue.UnusedSettings(m, layers)...)
}
if len(stray) > 0 {
// Somebody set something that reaches no file. Said here rather than discovered by the
// machine not behaving differently, which is the slowest way there is.
return catalogue.Resolution{}, nil, fmt.Errorf(
"these settings reach nothing:\n - %s", strings.Join(stray, "\n - "))
}
return resolved, settings, nil
}
// theRestOfTheMesh is what every other node holds and offers.
//
// Two things at once because they come from the same place — resolving the other nodes — and
// because both are facts about what is actually running rather than records that could disagree
// with it. A claim is held by whatever a node runs; a provision is offered by whatever a node
// runs; neither is a table somebody keeps up to date.
//
// **Two passes over the others.** What a node offers the mesh needs that node resolved, and
// resolving it may need what the mesh offers. So the first pass takes brokered requirements on
// trust and answers only *what does each node offer*; the second answers everything with that in
// hand. Nothing is ever declared from the first.
func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory,
shelf map[string]catalogue.Manifest, exclude string) (catalogue.World, error) {
// Every node, not only the placed ones. A machine that was never put on the private network
// still runs modules, still holds claims, and still offers whatever it offers.
nodes, err := inv.Nodes(ctx)
if err != nil {
return catalogue.World{}, err
}
places, err := inv.Overlays(ctx)
if err != nil {
return catalogue.World{}, err
}
siteOf := map[string]string{}
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
assigned []string
}
var others []candidate
for _, n := range nodes {
if n.Name == exclude {
continue
}
theirs, err := inv.Assigned(ctx, n.Name)
if err != nil || len(theirs) == 0 {
continue
}
caps, _ := inv.ProfileOf(ctx, n.Name)
others = append(others, candidate{
catalogue.Node{Name: n.Name, Site: siteOf[n.Name], Capabilities: caps,
At: onNetwork[n.Name]}, theirs})
}
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 {
// Their set does not resolve for some other reason. Not this node's problem to
// report, and nothing of theirs is running, so it offers nothing.
continue
}
for _, m := range got.Modules {
for _, name := range m.OffersAt(catalogue.ScopeMesh) {
// 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.
assigned, err := portsOn(ctx, inv, o.node.Name, m.Module)
if err != nil {
return catalogue.World{}, err
}
serves := catalogue.ServedOn(m, name, assigned)
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.Slice(offered[k], func(i, j int) bool {
return offered[k][i].Node < offered[k][j].Node
})
}
world := catalogue.World{Offered: offered}
for _, o := range others {
got, err := catalogue.Resolve(shelf, o.assigned, o.node, world)
if err != nil {
continue
}
world.Held = append(world.Held, got.Claims...)
}
return world, nil
}
// declarationFor is everything a node would be sent.
//
// One place, because there were three and one of them was written before credentials existed and
// silently produced a declaration missing them — a difference between what `plan` showed and what
// `plan --json` handed to anything reading it.
func declarationFor(ctx context.Context, open *stores, node string,
plan catalogue.Resolution, settings catalogue.SettingsBy) ([]map[string]any, error) {
gens, err := generators(ctx, open)
if err != nil {
return nil, err
}
// **Allocating, because `plan` is the send without the sending.** It is one machine, named by
// a person, who is asking what a push would do — so the port it shows and the secret it seals
// have to be the ones a push would use, and both are kept once chosen. Showing numbers that a
// later push would replace would make the command answer a question nobody asked.
//
// The line is not "a question may not write". It is who is asking and how often: this is a
// person, about one machine, on purpose. What may not write is the comparison the mesh runs
// over every machine to answer whether each is up to date — see Choosing.
return declarationWith(ctx, open, node, plan, settings, gens, Allocating)
}
// declarationWith is the same, for a caller that has already worked out the generators once and
// is about to use them for every node.
// Choosing says whether this composition may allocate what has not been allocated yet.
//
// **The comparison the mesh runs over every machine must not change what it is comparing.**
// Composing a declaration assigns each module a machine port and seals its secrets, and `status`
// composes one for every node to answer *is this machine running what I would send it* — so that
// question allocated, minted, wrote, and contended with the very machine it was asking about. A
// status polled every two seconds while a node applies is then two writers on the same rows,
// which is how it came to hang rather than answer.
//
// `plan` sits on the other side of this and allocates, because it is a person asking what a push
// would do to one named machine. The distinction is not question versus command; it is a person
// asking once about one machine versus the mesh asking continuously about all of them.
//
// So the mesh chooses a port when it commits to sending one, and every other caller reads what
// was chosen. A module with nothing assigned yet has never been sent, which is exactly what a
// machine "waiting" means — the read needs no number to be right about that.
type Choosing bool
const (
// Allocating is the send path: what is not assigned yet is assigned now and kept.
Allocating Choosing = true
// Reading is every question: what is assigned is used, and nothing is created.
Reading Choosing = false
)
func declarationWith(ctx context.Context, open *stores, node string,
plan catalogue.Resolution, settings catalogue.SettingsBy,
gens map[string]catalogue.Generator, choosing Choosing) ([]map[string]any, error) {
inv := open.inventory
grants, err := grantsFor(ctx, open, node)
if err != nil {
return nil, err
}
// Where this machine puts what each module needs reachable (novox/hq ADR 0038).
//
// **Assigned here rather than written by a module**, because a module is written once and
// assigned anywhere: any number it picks is a guess about a machine it has never seen. Made
// before the declaration is composed, because the container's mapping, the rule set and what a
// consumer is told are all derived from it.
// What this machine was already given, for a composition that may not allocate.
already := map[string]map[int]int{}
if choosing == Reading {
held, err := inv.PortsFor(ctx, node)
if err != nil {
return nil, err
}
for _, a := range held {
if already[a.Module] == nil {
already[a.Module] = map[int]int{}
}
already[a.Module][a.Wanted] = a.Machine
}
}
ports := map[string]map[int]int{}
for _, m := range plan.Modules {
for _, l := range m.Listens {
// **Only a port the module actually publishes is the mesh's to move.** A container's
// mapping is the thing that translates; without one the software binds what it binds,
// and an assignment would not move the service — it would open the wrong number in the
// rule set and leave the real one shut. Recorded either way, because this map means
// *where this module's port is on this machine* and every reader of it needs that
// answer whether or not the mesh was the one who chose it.
where, mayAssign := m.MachineSide(l.Port)
switch {
case mayAssign && choosing == Allocating:
at, err := inv.PortFor(ctx, node, m.Module, l.Port, l.Fixed)
if err != nil {
return nil, fmt.Errorf(
"%s needs %d reachable on %s and it could not be assigned: %w",
m.Module, l.Port, node, err)
}
where = at.Machine
case mayAssign:
// Whatever was chosen last time, and nothing if there was no last time.
if at, known := already[m.Module][l.Port]; known {
where = at
}
}
if ports[m.Module] == nil {
ports[m.Module] = map[int]int{}
}
ports[m.Module][l.Port] = where
}
}
// And each module's own secrets — a superuser password, an administrator, an account. Made
// per node, so a module running on three machines has three.
needed := map[string]map[string]string{}
for _, m := range plan.Modules {
for name := range m.OwnSecrets {
// Minted on the send path and only read on every other. Making one is an insert, and
// a question that writes is a question that can block against the machine it is about.
var sealed string
var err error
if choosing == Allocating {
sealed, err = inv.SecretForModule(ctx, node, m.Module, name)
} else {
var held bool
sealed, held, err = inv.ModuleSecretIfIssued(ctx, node, m.Module, name)
if err == nil && !held {
// Never issued, so this machine cannot be running it. Left out rather than
// invented: an empty string here would compose a declaration that differs
// from what would be sent, and the comparison this feeds would then be
// answering about a declaration nothing will ever push.
continue
}
}
if err != nil {
return nil, err
}
if needed[m.Module] == nil {
needed[m.Module] = map[string]string{}
}
needed[m.Module][name] = sealed
}
}
// And a certificate for this machine's name inside the mesh, when anything on it asks. Issued
// rather than stored: the node's key does not change, so signing again produces an equally
// valid certificate and there is nothing to keep in step.
var certificate, authority string
for _, m := range plan.Modules {
if m.Certificate == nil {
continue
}
issued, meshCA, err := certificateFor(ctx, open, node)
if err != nil {
return nil, err
}
certificate, authority = issued, meshCA
break
}
// And who else is on the private network, which is what a rule saying "from the mesh"
// resolves to. Every node's address, including this one's: a machine reaching itself by its
// own overlay address rather than by loopback is ordinary, and leaving it out would filter
// the node's own traffic to itself with no rule naming why.
private, err := onThePrivateNetwork(ctx, inv)
if err != nil {
return nil, err
}
// And every machine's name, so a container can reach one. The same set that writes the
// machine's own hosts file — one reading, so a container and its machine cannot disagree
// about where another machine is.
names, err := namesInTheMesh(ctx, inv)
if err != nil {
return nil, err
}
// And every routed name → the node that serves it (novox/hq ADR 0066). Alongside the
// `<node>.internal` names above, so a container — or an internal ACME validator — resolves a
// routed name to the proxy that serves it, mesh-wide. The mesh publishes the names it was told
// to serve and knows nothing about what they mean.
routes, err := routeNamesInTheMesh(ctx, open)
if err != nil {
return nil, err
}
for name, at := range routes {
names[name] = at
}
return plan.Declaration(catalogue.Rendering{
Settings: settings, Generators: gens, Grants: grants, Needed: needed, Ports: ports,
Certificate: certificate, Authority: authority, Mesh: private, Names: names})
}
// routeNamesInTheMesh is every routed name and the address of the node that serves it (novox/hq
// ADR 0066).
//
// **Mesh-wide, so any container resolves any routed name to its proxy** — including an internal
// ACME validator, which cannot complete a challenge for a name it cannot reach. A routed name is
// composed on the consumer's node (from its label and that node's public domain) and served by the
// node answering the consumer's route requirement; this gathers both.
//
// It reads route names off resolutions rather than a table because there is no table: a route is a
// contribution, computed from what each node runs. Name-agnostic — a contribution counts as a
// routed name only because it carried a label the mesh composed, never because the mesh knows what
// "route" means. A node that does not resolve is skipped, so one machine's broken set does not cost
// the rest their names.
func routeNamesInTheMesh(ctx context.Context, open *stores) (map[string]string, error) {
inv := open.inventory
places, err := inv.Overlays(ctx)
if err != nil {
return nil, err
}
address := map[string]string{}
for _, p := range places {
if strings.TrimSpace(p.Address) != "" {
address[p.Name] = p.Address
}
}
nodes, err := inv.Nodes(ctx)
if err != nil {
return nil, err
}
out := map[string]string{}
for _, n := range nodes {
plan, settings, err := planFor(ctx, open, n.Name)
if err != nil {
continue
}
for _, m := range plan.Modules {
for to := range m.Contributes {
values, asks, err := plan.ContributionsFrom(to, m.Module, settings)
if err != nil {
return nil, err
}
if !asks {
continue
}
// A routed name, and only that: a contribution the mesh composed a name for from a
// label it was given. A grant that happens to carry a `name` of its own — a database
// name — carries no label and is left alone.
if _, labelled := values["label"]; !labelled {
continue
}
name, _ := values["name"].(string)
if name == "" {
continue
}
// The node that serves it: whoever answers this consumer's route requirement, or
// this same node when the proxy is beside the consumer.
serving := n.Name
for _, need := range plan.Needs {
if need.Name == to && need.For == m.Module {
serving = need.From
break
}
}
if at := address[serving]; at != "" {
out[strings.ToLower(name)] = at
}
}
}
}
return out, nil
}
// certificateFor is what the mesh certifies about one machine's internal name.
//
// It reaches across two contexts and reads neither one's store from the other: `inventory` knows
// the machine and whether it is on the private network, `identity` holds the authority and the
// key that machine reported. The process holding both grants asks each for its part
// (novox/hq ADR 0008).
func certificateFor(ctx context.Context, open *stores, node string) (string, string, error) {
inv := open.inventory
ident, err := open.Identity(ctx)
if err != nil {
return "", "", err
}
record, err := inv.NodeByName(ctx, node)
if err != nil {
return "", "", err
}
serving, err := ident.ServingKeyOf(ctx, record.ID)
if err != nil {
return "", "", err
}
if serving == "" {
// The machine joined before it had one, or never reported it. Said plainly, because the
// remedy is on the machine and no amount of pushing from here will produce one.
return "", "", fmt.Errorf(
"%s wants a certificate and has never told the mesh what key it serves with; it "+
"joins again to report one", node)
}
// The name it is certified for. Only a machine on the private network has one — a certificate
// for a name nothing resolves is a certificate nothing can check.
//
// With the catalogue, not without it. Being on the private network is a conclusion about what
// a node resolves to, so a nil shelf resolves nothing and every machine looks like it is on no
// network — which refused every certificate the mesh was asked for, and said the machine was
// not on a network it plainly was.
shelf, err := inv.Catalogue(ctx)
if err != nil {
return "", "", err
}
where, err := whereEveryoneIs(ctx, inv, shelf)
if err != nil {
return "", "", err
}
name := where[node]
if name == "" {
return "", "", fmt.Errorf(
"%s wants a certificate and is not on the private network, so it has no name inside "+
"the mesh to be certified for", node)
}
issued, err := ident.Certify(ctx, node, name, serving)
if err != nil {
return "", "", err
}
authority, err := ident.EstablishAuthority(ctx)
if err != nil {
return "", "", err
}
return issued, authority.Certificate, nil
}
// grantsFor is every credential this node must create, because something elsewhere uses it.
//
// The mirror of what a consumer is given, and the half that makes the credential real: a password
// nothing was told to create is a password that authenticates nowhere. Sealed to this node, so
// the mesh hands over something it cannot itself use.
func grantsFor(ctx context.Context, open *stores, node string) ([]catalogue.Grant, error) {
inv := open.inventory
issued, err := inv.SecretsFrom(ctx, node)
if err != nil {
return nil, err
}
// Where each consumer is, so a provider that must reach back to one does not have to know how
// the mesh names machines.
shelf, err := inv.Catalogue(ctx)
if err != nil {
return nil, err
}
onNetwork, err := whereEveryoneIs(ctx, inv, shelf)
if err != nil {
return nil, err
}
// What each consumer actually asked for, taken from that machine's own resolution rather than
// from a record beside it. A provider told to create a password and not what to create it for
// can do nothing with it, and the name a consumer wants is the consumer's to say.
out := make([]catalogue.Grant, 0, len(issued))
for _, s := range issued {
plan, settings, err := planFor(ctx, open, s.Consumer)
if err != nil {
// Their set does not resolve. Skipped rather than fatal: this node is not the place
// to report another machine's problem, and a grant for something that is not going to
// run would have the provider create a user nothing uses.
continue
}
values, asks, err := plan.ContributionsFrom(s.Name, s.ConsumerModule, settings)
if err != nil {
return nil, err
}
// A port in there is the consumer's software port until this. The consumer is on another
// machine, so the assignment that moved it is that machine's — fetched here rather than
// looked for in this one's, which is the whole reason the co-located fix could not reach
// this case (novox/hq 04-ISSUES/038, the cross-node half).
published, err := portsOn(ctx, inv, s.Consumer, s.ConsumerModule)
if err != nil {
return nil, err
}
values = catalogue.AtPublishedPort(values, s.ConsumerModule, published)
from := s.ConsumerModule
if !asks {
// That module no longer wants this. Left empty, which is what the declaration reads
// as "nobody asks for it any more" — and is how a login is withdrawn rather than kept
// working for ever after its consumer went away.
from = ""
}
// The consumer's identity slug, from its own manifest, carried on the grant so the provider
// derives the same login the consumer does (novox/hq ADR 0049). Refused here if it still would
// not fit the tightest backend — the mesh chose the name, so the mesh refuses it, with the
// remedy a short slug rather than a login a provider silently shortened.
slug := ""
for _, mm := range plan.Modules {
if mm.Module == s.ConsumerModule {
slug = mm.Slug
break
}
}
if from != "" {
if err := catalogue.CheckIdentity(s.Consumer, catalogue.IdentitySource(slug, s.ConsumerModule)); err != nil {
return nil, err
}
}
out = append(out, catalogue.Grant{
Provision: s.Name, Consumer: s.Consumer, At: onNetwork[s.Consumer],
From: from, Values: values, Slug: slug, Sealed: s.ForProvider})
}
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
// the file before it is sent is the difference between believing a merge worked and knowing.
show := set.Bool("files", false, "print the files this node would be given")
// The declaration exactly as the node would receive it. For handing to something else --
// checking it against the host's own parser, most usefully, which is the only way to know
// that what the control plane emits is what the host accepts.
asJSON := set.Bool("json", false, "print the declaration this node would be sent")
positionals, err := parseAround(set, args)
if err != nil {
return err
}
if len(positionals) != 1 {
return errors.New("plan <node> [--files] [--json]")
}
args = positionals
open, err := openStores(ctx)
if err != nil {
return err
}
defer open.Close()
plan, settings, err := planFor(ctx, open, args[0])
if err != nil {
return err
}
if len(plan.Modules) == 0 {
fmt.Printf("%s is assigned nothing\n", args[0])
return nil
}
if *asJSON {
resources, err := declarationFor(ctx, open, args[0], plan, settings)
if err != nil {
return err
}
body, err := json.MarshalIndent(
map[string]any{"declaration": 1, "resources": resources}, "", " ")
if err != nil {
return err
}
fmt.Println(string(body))
return nil
}
fmt.Printf("%s would run:\n", args[0])
for _, m := range plan.Modules {
fmt.Printf(" %-20s %s\n", m.Module, plan.Because[m.Module])
}
// What was assigned here and cannot run here. Said with the rest rather than as a refusal: it is
// one module on the wrong machine, the others still run, and the remedy is to move this one.
for _, u := range plan.Unhostable {
for _, c := range u.Missing {
fmt.Printf(" %-20s not applied — %s\n", u.Module, catalogue.WrongMachine(u.Module, c, args[0]))
}
}
for _, c := range plan.Claims {
fmt.Printf(" holds %s, one per %s\n", c.Claim, c.Scope)
}
// What this machine depends on that is not on it. Worth saying out loud: it is the only part
// of a node's set that stops working when a *different* machine goes away, and nothing else
// in this output would have told anybody that.
for _, n := range plan.Needs {
fmt.Printf(" needs %s from %s, for %s\n", n.Name, n.From, n.For)
}
resources, err := declarationFor(ctx, open, args[0], plan, settings)
if err != nil {
return err
}
for module, layers := range settings {
for _, layer := range layers {
fmt.Printf(" %-20s settings from %s\n", module, layer.From)
}
}
fmt.Printf("\n%d resource(s)\n", len(resources))
if *show {
for _, r := range resources {
content, ok := r["content"].(string)
if !ok {
continue
}
fmt.Printf("\n--- %v %v ---\n%s", r["id"], r["path"], content)
}
}
return nil
}
// licencesFor is what this node can be answered with by record, and what it was put on.
//
// A mesh with no licences at all is the ordinary case and must not be an error: every existing
// mesh is one, and a control plane that refused to plan because nobody had bought an API key
// would be unusable for the thing it already does.
func licencesFor(ctx context.Context, open *stores, node string) (
map[string][]catalogue.Record, map[string]map[string]catalogue.Record, error) {
held, err := open.Licences(ctx)
if err != nil {
return nil, nil, err
}
all, err := held.All(ctx)
if err != nil {
return nil, nil, err
}
if len(all) == 0 {
return nil, nil, nil
}
offered := map[string][]catalogue.Record{}
byName := map[string]catalogue.Record{}
for _, one := range all {
record := catalogue.Record{Name: one.Name, Serves: one.Serves}
offered[licences.Provision] = append(offered[licences.Provision], record)
byName[one.Name] = record
}
using := map[string]map[string]catalogue.Record{}
for _, one := range all {
holders, err := held.HoldersOf(ctx, one.Name)
if err != nil {
return nil, nil, err
}
for _, h := range holders {
if h.Node != node {
continue
}
if using[h.Module] == nil {
using[h.Module] = map[string]catalogue.Record{}
}
using[h.Module][licences.Provision] = byName[one.Name]
}
}
return offered, using, nil
}
// keyFor is the licence key sealed to one machine, for one module.
//
// **Empty is not an error here.** The mesh discarded the plaintext when it was supplied, so a
// holder recorded afterwards genuinely has no key — and the declaration refuses that by name,
// where the module and the path are both in view, rather than here.
func keyFor(ctx context.Context, open *stores, licence, node, module string) (string, error) {
held, err := open.Licences(ctx)
if err != nil {
return "", err
}
return held.KeyFor(ctx, licence, node, module)
}
// managerPublicKeyFor is the manager node's public sealing key, but only when (node, module) is the
// licence's manager holder — empty otherwise.
//
// It is delivered to the manager module in its bound facts so it can re-seal a rotated refresh token
// to this node (novox/hq ADR 0050). Public, so it travels in the clear like any other bound fact; and
// scoped to the manager holder alone, so a consumer never receives it and nothing invites a consumer
// to seal anything.
func managerPublicKeyFor(
ctx context.Context, open *stores, inv *inventory.Inventory, licence, node, module string,
) (string, error) {
held, err := open.Licences(ctx)
if err != nil {
return "", err
}
managerNode, managerModule, err := held.ManagerOf(ctx, licence)
if err != nil {
return "", err
}
if managerNode == "" || node != managerNode || module != managerModule {
return "", nil
}
return inv.SealingKeyOf(ctx, node)
}
// portsOn is one module's assignments on one machine, by the port the software uses.
func portsOn(
ctx context.Context, inv *inventory.Inventory, node, module string,
) (map[int]int, error) {
all, err := inv.PortsFor(ctx, node)
if err != nil {
return nil, err
}
out := map[int]int{}
for _, a := range all {
if a.Module == module {
out[a.Wanted] = a.Machine
}
}
return out, nil
}