package compose import ( "fmt" "slices" "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 " on needs from ". 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. func (c *composer) planFor(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) 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)) } // 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, _, _, 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 { // 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, and so every other node's // resolution, is untouched. 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 }