Files
mesh-sdk/go/compose/resolve.go
T
jschoubben c459f75730
mesh/delivery merged: source.main moved to novox/mesh-sdk@d6c87ee1a7ae
mesh/delivery-group group fix/1126-provision-credentials merged: every member is merged
mesh/check pass c459f757308a on 036266daff85 | unit 6028 run, 142 kept, 16 skipped | integration 6091 run, 174 kept, 25 skipped | over bound: unit, in…
compose: an offer of form none composes no credential and holds nothing
After the controller, nothing makes a pair row for a new consumer, so a need whose provider offers no credential
(form none) was composed with a stand-in on both ends and the node-engine held the consumer (issue 1126:
mesh-cli on ace, "stands in for the grant of mcp-endpoint"). The vault never mints for form none, so such a need
now composes with no credential: the consumer's need carries none, the provider's grant carries the consumer
and nothing sealed, neither is named foreseen, CredentialsNeeded asks the vault for neither, and no empty grant
file is written.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 15:30:43 +01:00

348 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
}
if credentialless(n) {
// Its provider's offer presents no credential (form none): nothing is sealed, made or stood in for (issue 1126).
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
}