ADR 0593 §1 and §7: compose, its bus reader and runtime reader, takein, validate, the overlay generator, the composer's run and mesh-compose, and the judgement of a move leave mesh-sdk for the node-engine's internal packages, so no other module composes and a change to them rebuilds only the node-engine. The shapes others read (the composer's request and verdict, the overlay's names, compositions and answers) stay in mesh-sdk. The node's grant is now a golden file of what the grants part composes, since the composer of the bus's user list leaves mesh-sdk too. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
package compose
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// Rendered is everything a node's declaration is composed with (plan.go renderingFor), over an Input: the place half (Places)
|
|
// and the credential half. Nothing was allocated, made, kept or written; what a send would do is returned for the caller to do.
|
|
type Rendered struct {
|
|
Rendering catalogue.Rendering
|
|
// ForeseenPorts are the ports the mesh may assign that the Input holds none for ("<module>:<port>").
|
|
ForeseenPorts []string
|
|
// Foreseen are the stand-ins the composition used for what the Input does not hold: an own secret the next send makes
|
|
// ("<module>/<name>"), a grant's sealed copy, the certificate and the bus's user list.
|
|
Foreseen []string
|
|
// CoreBoxesWanted are the core credentials whose box the send must ask the vault for and keep.
|
|
CoreBoxesWanted []CoreBoxWanted
|
|
// Withheld are the consumers the grants leave out because their identity overflows the provision's bound (novox/hq ADR 0225),
|
|
// and Unbound those whose credential from this node is on record and whose own resolution binds them elsewhere (issue
|
|
// 274). The controller says them; a check refuses a change that adds one (ADR 0446 clause 1).
|
|
Withheld []catalogue.Overflow
|
|
Unbound []catalogue.Unbound
|
|
// Said are the lines the controller says while composing (the bus users left out for want of a credential).
|
|
Said []string
|
|
}
|
|
|
|
// Render composes the rendering of a node: the grants it must create, where it puts its ports, its own secrets, the vault's boxes,
|
|
// its certificate, the names of the mesh, the bus's user list and its membership. The refusals are the controller's, in its words;
|
|
// where more than one fault stands, the first reported may differ from the controller's, which interleaves the halves.
|
|
func Render(in Input, node string, mode Mode) (Rendered, error) {
|
|
return newComposer(in).render(node, mode)
|
|
}
|
|
|
|
// Composing is Resolve, Render and Generators of one node over one composition, which resolves each node of the mesh
|
|
// once for all three (issue 1954: three compositions resolved the mesh again each, 11.7 s for one node's plans). Each
|
|
// answers as it does alone; the first refusal ends it, in that order, with what follows it empty.
|
|
func Composing(in Input, node string, mode Mode) (Resolved, Rendered, map[string]catalogue.Generator, error) {
|
|
c := newComposer(in)
|
|
resolved, err := c.planFor(node)
|
|
if err != nil {
|
|
return Resolved{}, Rendered{}, nil, err
|
|
}
|
|
rendered, err := c.render(node, mode)
|
|
if err != nil {
|
|
return resolved, Rendered{}, nil, err
|
|
}
|
|
gens, err := c.generators()
|
|
if err != nil {
|
|
return resolved, rendered, nil, err
|
|
}
|
|
return resolved, rendered, gens, nil
|
|
}
|
|
|
|
func (c *composer) render(nodeName string, mode Mode) (Rendered, error) {
|
|
resolved, err := c.planFor(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
node, err := c.in.node(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
plan := resolved.Plan
|
|
var out Rendered
|
|
foreseen := func(s string) { out.Foreseen = append(out.Foreseen, s) }
|
|
|
|
grants, withheld, unbound, grantsForeseen, err := c.grantsFor(nodeName)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
placed, err := c.places(nodeName, resolved)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r := placed.Rendering
|
|
out.ForeseenPorts = placed.ForeseenPorts
|
|
// A plan carries its contributions' machine ports, refused where the placement assigned none (issue 774).
|
|
r.Planned = mode == Planning
|
|
r.Grants, r.Withheld, r.Unbound = grants, withheld, unbound
|
|
out.Withheld, out.Unbound = withheld, unbound
|
|
for _, g := range grantsForeseen {
|
|
foreseen(g)
|
|
}
|
|
|
|
// And each module's own secrets: a module handed to the provisioner has its secrets from the vault through the client.
|
|
handing := catalogue.Rendering{Hands: r.Hands, Provisioner: r.Provisioner, Assigned: r.Assigned}
|
|
needed, foreseenOwn, err := c.ownSecretsFor(node, plan, handing, mode)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r.Needed = needed
|
|
if mode == Foreseeing || mode == Planning || len(foreseenOwn) > 0 {
|
|
r.Foreseen = foreseenOwn
|
|
}
|
|
var names []string
|
|
for module, secrets := range foreseenOwn {
|
|
for name := range secrets {
|
|
names = append(names, module+"/"+name)
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for _, n := range names {
|
|
foreseen(n)
|
|
}
|
|
|
|
// The core's own credentials as the vault sealed them, for an engine that opens the vault's box.
|
|
if node.Stated && node.Contract >= VaultBoxContract {
|
|
sealed, notPlaced, wanted := c.coreBoxesFor(node, plan.Modules, handing)
|
|
r.VaultSealed, r.NotPlaced = sealed, notPlaced
|
|
out.CoreBoxesWanted = wanted
|
|
}
|
|
|
|
// And a certificate for this machine's name inside the mesh, when anything on it asks.
|
|
for _, m := range plan.Modules {
|
|
if m.Certificate == nil {
|
|
continue
|
|
}
|
|
certificate, authority, stood, err := c.certificateFor(node)
|
|
if err != nil {
|
|
return Rendered{}, err
|
|
}
|
|
r.Certificate, r.Authority = certificate, authority
|
|
if stood {
|
|
foreseen("the certificate and the authority of " + nodeName)
|
|
}
|
|
break
|
|
}
|
|
|
|
// The bus's user list, for the machine that runs the bus: names and hashes the Input carries, never passwords.
|
|
if holdsTheBus(plan.Modules) {
|
|
r.BusUsers = c.in.BusUsers
|
|
if r.BusUsers == "" {
|
|
r.BusUsers = ForeseenBusUsers
|
|
foreseen(BusUsersStandIn)
|
|
}
|
|
if len(c.in.BusMissing) > 0 {
|
|
out.Said = append(out.Said, fmt.Sprintf("the bus's user list leaves out %d user(s) the mesh has minted no credential "+
|
|
"for: %s. Each is a user that cannot connect until one is issued", len(c.in.BusMissing), strings.Join(c.in.BusMissing, ", ")))
|
|
}
|
|
}
|
|
r.BusMembership = c.in.BusMemberships[nodeName]
|
|
out.Rendering = r
|
|
// And what the composition found on the way, which the controller writes to its log: a machine not counted as on the network.
|
|
out.Said = c.saidBeside(out.Said)
|
|
return out, nil
|
|
}
|