Files
mesh-sdk/go/compose/resolve.go
T
jschoubben 5d26c658f5 Compose a node's plans once per read, resolving each machine once (issue 1954)
A node's plans took 2.6-13.6 s of CPU on the provisioner: one composition
resolved every machine of the mesh some two hundred times (who is on the
private network, and the rest of the mesh, worked out again on every
look), three compositions did so again each, and the holder composed
twice. A composer now works each out once over its unchanging Input;
Composing gives Resolve, Render and Generators one composer;
PlansHeldTwice composes the wanted and the newest builds over one read of
the bus, says where the time went by phase, and the states reader keeps a
build's take-in by its content. WatchedStates takes a process's own
writes into the copy at once, so a holder reads its own states from it.

Measured on a captured novox mesh: 11.7 s to 0.49 s per composition of an
unchanged node, plans and provisions identical on all four nodes.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 16:18:44 +01:00

344 lines
14 KiB
Go

package compose
import (
"fmt"
"slices"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// LicenceProvision is what a module requires in order to be given a licence (the controller's licences.Provision).
const LicenceProvision = "model-access"
// Resolved is what one node should run, from what was assigned to it, with the settings of everything that resolved.
type Resolved struct {
Plan catalogue.Resolution
Settings catalogue.SettingsBy
// Foreseen are the needs composed with a stand-in for a credential the next send makes (or holds with no value given
// here), as "<module> on <node> needs <provision> from <provider>". Nothing was made.
Foreseen []string
// NotGiven are the words of each credential only a person gives that nobody has: the consumer still resolves without it
// (novox/hq ADR 0337 clause 4).
NotGiven []string
}
// Resolve works out everything a node should run, from what was assigned to it (the controller's planFor, plan.go): the
// resolution over the rest of the mesh, the credential of each thing the node takes from elsewhere, and the settings of
// every module that resolved. It makes and writes nothing: a credential not held is composed with a stand-in and named
// foreseen, one only a person gives and nobody has is left out and said, one the send would refuse is refused with the
// controller's words. A failure to compose the node's own modules is an error that satisfies IsUnresolvable, told apart
// from an unreadable Input (novox/hq 04-ISSUES/152).
func Resolve(in Input, node string) (Resolved, error) {
return newComposer(in).planFor(node)
}
// IsUnresolvable reports whether err is a node's own set failing to compose, rather than the mesh being unable to answer.
func IsUnresolvable(err error) bool { return unresolvable(err) }
// planFor is the controller's planFor, worked out once per node in one composition (issue 1954): each caller has its
// own copy of what a caller may set (a need's fields, as Resolution.Compose sets its Serves), the rest only read.
func (c *composer) planFor(nodeName string) (Resolved, error) {
p, ok := c.plans[nodeName]
if !ok {
p.resolved, p.err = c.planForNow(nodeName)
if c.plans == nil {
c.plans = map[string]planOf{}
}
c.plans[nodeName] = p
}
if p.err != nil {
return Resolved{}, p.err
}
return p.resolved.copied(), nil
}
// planOf is planFor's answer for one node, kept.
type planOf struct {
resolved Resolved
err error
}
// copied is r with its own needs, words and settings lists: what a caller may change of it.
func (r Resolved) copied() Resolved {
out := r
out.Plan.Needs = slices.Clone(r.Plan.Needs)
out.Foreseen = slices.Clone(r.Foreseen)
out.NotGiven = slices.Clone(r.NotGiven)
if r.Settings != nil {
out.Settings = make(catalogue.SettingsBy, len(r.Settings))
for m, layers := range r.Settings {
out.Settings[m] = slices.Clone(layers)
}
}
return out
}
// planForNow is planFor worked out.
func (c *composer) planForNow(nodeName string) (Resolved, error) {
resolved, err := c.resolution(nodeName)
if err != nil {
return Resolved{}, err
}
node, err := c.in.node(nodeName)
if err != nil {
return Resolved{}, err
}
shelf := c.shelfFor(node)
var out Resolved
// The credential for each thing this node takes from elsewhere.
for i, n := range resolved.Needs {
// Issued for this holder alone and kept by the vault (issue 757).
resolved.Needs[i].IssuedToHolder = issuedToHolder(shelf, n)
resolved.Needs[i].ProviderMoved = providerMoved(shelf, n)
resolved.Needs[i].AnsweredByHelper = answeredByHelper(shelf, n)
if n.ByRecord {
// Answered by something the mesh holds, so there is no pair-wise secret between two machines.
licence, _ := c.licence(n.From)
if licence.Keys == nil {
// The keys are not given: composed with a stand-in and named, not failed for a value only a send reads.
resolved.Needs[i].Sealed = catalogue.ForeseenSealed
out.Foreseen = append(out.Foreseen, fmt.Sprintf("%s on %s needs %s from %s", n.For, nodeName, n.Name, n.From))
} else {
// Given, and none for this holder: the mesh holds none.
resolved.Needs[i].Sealed = licence.Keys[Holder{Node: nodeName, Module: n.For}]
}
// 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).
if pub := c.managerPublicKeyFor(licence, nodeName, n.For); 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.
resolved.Needs[i].Manager = true
}
continue
}
cred, held := c.credential(n, nodeName)
_, outside := issuedOutsideBy(shelf, n)
switch {
case cred.State == CredentialRefused:
// 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 Resolved{}, fmt.Errorf("%s on %s needs %s from %s and no credential could be made for it: %s",
n.For, nodeName, n.Name, n.From, cred.Why)
case cred.State == CredentialNotGiven, outside && !(held && cred.State == CredentialHeld):
// Not given, and the rest of the consumer runs (ADR 0337 clause 4): no credential file, so the module can tell
// it was not given; nothing the mesh made stands in for it.
why := cred.Why
if why == "" {
why = "nobody gave it"
}
out.NotGiven = append(out.NotGiven, fmt.Sprintf("%s on %s holds no %s from %s: %s", n.For, nodeName, n.Name, n.From, why))
continue
case cred.State == CredentialHeld && cred.Sealed != "":
resolved.Needs[i].Sealed = cred.Sealed
default:
// Held with no value here, or none held: the next send makes it (or reads it), and a stand-in is composed.
resolved.Needs[i].Sealed = catalogue.ForeseenSealed
out.Foreseen = append(out.Foreseen, fmt.Sprintf("%s on %s needs %s from %s", n.For, nodeName, n.Name, n.From))
}
}
// Settings for everything that resolved, including modules nobody assigned directly: a requirement pulled in by
// something else is still configurable.
settings := catalogue.SettingsBy{}
for _, m := range resolved.Modules {
layers := node.Settings[m.Module]
if len(layers) == 0 {
continue
}
settings[m.Module] = append([]catalogue.Layer(nil), layers...)
}
out.Plan, out.Settings = resolved, settings
return out, nil
}
// credential is how the pair credential of one need stands in the Input, and whether the Input names it at all.
func (c *composer) credential(n catalogue.Needed, consumer string) (Credential, bool) {
for _, cr := range c.in.Credentials {
if cr.Provision == n.Name && cr.Consumer == consumer && cr.ConsumerModule == n.For && cr.Provider == n.From && cr.Local == n.Local {
return cr, true
}
}
return Credential{State: CredentialWouldMake}, false
}
// licence is the licence of that name.
func (c *composer) licence(name string) (Licence, bool) {
for _, l := range c.in.Licences {
if l.Name == name {
return l, true
}
}
return Licence{}, false
}
// managerPublicKeyFor is the manager node's public sealing key, but only when (node, module) is the licence's manager holder
// and empty otherwise (plan.go managerPublicKeyFor): 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), and to no consumer.
func (c *composer) managerPublicKeyFor(l Licence, node, module string) string {
if l.Manager == (Holder{}) || node != l.Manager.Node || module != l.Manager.Module {
return ""
}
n, err := c.in.node(node)
if err != nil {
return ""
}
return n.SealingKey
}
// issuedOutsideBy is whether a need's provision is offered by its provider with a credential issued outside the mesh
// (novox/hq ADR 0337), and that provider module: the one answering it when known, else any that offers it so.
func issuedOutsideBy(shelf map[string]catalogue.Manifest, n catalogue.Needed) (string, bool) {
if n.Module != "" {
if m, known := shelf[n.Module]; known {
return n.Module, m.IssuedOutsideOf(n.Name)
}
}
return catalogue.OffersIssuedOutside(shelf, n.Name)
}
// issuedToHolder is whether the vault keeps a need's credential for its holder alone (issues 749, 757 and 779).
func issuedToHolder(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
if n.ByRecord || n.Module == "" {
return false
}
provider, known := shelf[n.Module]
if !known || !movedProvider(provider) {
return false
}
if _, issued := provider.IssuedCredentialOf(n.Name); issued {
return true
}
return provider.CredentialFormOf(n.Name) == catalogue.CredentialFormPair
}
// providerMoved is whether a need is answered by a module marked moved (issue 788), or by one whose offer of the provision
// says the provider helper answers it (issue 792).
func providerMoved(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
if n.ByRecord || n.Module == "" {
return false
}
provider, known := shelf[n.Module]
return known && (movedProvider(provider) || provider.AnsweredByHelperFor(n.Name))
}
// answeredByHelper is whether a need's provider offers it answered by the provider helper (issue 792): keyed by the seat
// mesh-provisioner for every consumer, handed or not (catalogue.Keyed, issue 774).
func answeredByHelper(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
if n.ByRecord || n.Module == "" {
return false
}
provider, known := shelf[n.Module]
return known && provider.AnsweredByHelperFor(n.Name)
}
// movedProvider is whether a provider's credentials are the vault's: the module is marked moved, or it holds the
// mesh-vault seat (issue 793).
func movedProvider(m catalogue.Manifest) bool {
return m.Moved || slices.ContainsFunc(m.Claims, func(c catalogue.Claim) bool { return c.Name == "mesh-vault" })
}
// resolution works out what one node should run, from what was assigned to it: the controller's planFor up to and
// including the resolution (plan.go planFor), and no further. A failure to compose the node's own modules is wrapped as
// notResolvable; every other failure is returned as it is.
func (c *composer) resolution(nodeName string) (catalogue.Resolution, error) {
node, err := c.in.node(nodeName)
if err != nil {
return catalogue.Resolution{}, err
}
shelf := c.shelfFor(node)
world, _, left, err := c.theRestOfTheMesh(nodeName)
if err != nil {
return catalogue.Resolution{}, err
}
world.Pinned = node.Pins
// Where each of its consumers of a provision that keeps data was last sent (novox/hq ADR 0232).
world.Bound = node.Bound
onNetwork, err := c.whereEveryoneIs()
if err != nil {
return catalogue.Resolution{}, 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.
world.Licences, world.Using = c.licencesFor(nodeName)
resolved, err := catalogue.Resolve(shelf, node.Assigned,
catalogue.Node{Name: nodeName, Site: node.Place.Site, Capabilities: node.Capabilities,
At: onNetwork[nodeName], PublicDomain: node.PublicDomain,
Account: node.Account, AccountHome: node.AccountHome,
AgentAccount: node.AgentAccount, AgentAccountHome: node.AgentAccountHome}, world)
if err != nil {
// What another node's set would have offered is missing when that set does not resolve here: said with the
// refusal, so "nothing provides" is read beside why (issue 1126, probe #532).
if len(left) > 0 {
var why []string
for _, l := range left {
why = append(why, l.String())
}
err = fmt.Errorf("%w\n\nand %d node(s) offer nothing here, as they could not be resolved: %s", err, len(left), strings.Join(why, "; "))
}
if len(c.unhostable) > 0 {
err = fmt.Errorf("%w\n\nand these modules offer nothing here, as their node cannot run them in this composition: %s",
err, strings.Join(c.unhostable, "; "))
}
// The node's own set does not compose: the only failure a mesh-wide gatherer may pass over.
return catalogue.Resolution{}, notResolvable{err}
}
return resolved, nil
}
// shelfFor is the catalogue as one node composes its own set: the Input's, with each recorded or core module the node keeps at the
// build it runs put in place of the current one (plan.go keepRecorded). A copy: the Input's catalogue is untouched. Only the node's own planFor and
// resolution use it; the rest of the mesh and who is on the network are read with the current manifests, as the controller reads them
// before it keeps a recorded module.
func (c *composer) shelfFor(node Node) map[string]catalogue.Manifest {
if len(node.Kept) == 0 {
return c.in.Catalogue
}
shelf := make(map[string]catalogue.Manifest, len(c.in.Catalogue)+len(node.Kept))
for name, m := range c.in.Catalogue {
shelf[name] = m
}
for name, m := range node.Kept {
shelf[name] = m
}
return shelf
}
// 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 not an error.
func (c *composer) licencesFor(node string) (map[string][]catalogue.Record, map[string]map[string]catalogue.Record) {
all := c.in.Licences
if len(all) == 0 {
return 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[LicenceProvision] = append(offered[LicenceProvision], record)
byName[one.Name] = record
}
using := map[string]map[string]catalogue.Record{}
for _, one := range all {
for _, h := range one.Holders {
if h.Node != node {
continue
}
if using[h.Module] == nil {
using[h.Module] = map[string]catalogue.Record{}
}
using[h.Module][LicenceProvision] = byName[one.Name]
}
}
return offered, using
}