The node-engine refused the whole pass when two modules claimed a seat held once, or when a consumer's credential was refused. Now the composer leaves that module out of the declaration and composes the rest: the module is returned held (Resolved.Held, resolution.ResolveHolding), reaches the declaration as left out through the filter and the declaration #218 already keep (Rendering.NotPlaced), and Declared.LeftOut names it. The second claimant of a seat is the one held; its claim is not this node's holding. The rest-of-mesh view and the network use the same, so a node with a held module stays on the network. Resolve stays strict for callers that cannot leave a module out. Quick fix: hold only the credential refusal (resolve.go planForNow), about 20 minutes. Thorough fix, chosen: every claim problem is attributed to its module (claimsOf) and both kinds go through the one left-out path of ADR 0163 rule 6, in the declaration, the filter and the network, so no per-kind refusal of a pass remains in the composer. Left for the next step: a declared-and-not-made own secret (NotMadeError) and the Foreseeing-mode refusal still refuse the pass; the frombus candidate path holds those by stub already. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
162 lines
6.1 KiB
Go
162 lines
6.1 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 assignment 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
|
|
}
|
|
|
|
// A module whose claim cannot be held, or whose credential was refused, is left out of the declaration and run as it runs:
|
|
// the machine composes the rest (issue 1966, ADR 0636 §3).
|
|
for module, why := range resolved.Held {
|
|
if r.NotPlaced == nil {
|
|
r.NotPlaced = map[string]string{}
|
|
}
|
|
if _, said := r.NotPlaced[module]; !said {
|
|
r.NotPlaced[module] = why
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|