ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
118 lines
4.9 KiB
Go
118 lines
4.9 KiB
Go
package resolution
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// keepBound holds every need for a provision that keeps its consumers' data at the provider its
|
|
// consumer was bound to, where the resolution chose another.
|
|
//
|
|
// A need with no record is a binding being made, and is left as resolved: it is recorded when it is
|
|
// first sent. A pin naming the provider the resolution chose is a person moving it, and is left too.
|
|
// Otherwise the recorded provider answers, if it still provides the provision — beside the consumer,
|
|
// or offered from elsewhere — and the move is returned as kept. **One that no longer does is refused,
|
|
// never answered by the provider chosen**: answering it there is exactly the silent move this exists
|
|
// to stop, and the consumer's data is still wherever it was.
|
|
func keepBound(needs []mcat.Needed, catalogue map[string]mcat.Manifest, node mcat.Node, world mcat.World,
|
|
keeps map[string]bool, here func(string) bool) ([]mcat.Needed, []mcat.KeptBinding, []string) {
|
|
var kept []mcat.KeptBinding
|
|
var problems []string
|
|
refused := map[[2]string]bool{}
|
|
out := make([]mcat.Needed, 0, len(needs))
|
|
for _, n := range needs {
|
|
if n.ByRecord || !keeps[n.Name] {
|
|
out = append(out, n)
|
|
continue
|
|
}
|
|
n.KeepsData = true
|
|
bound, recorded := world.Bound[n.For][n.Name]
|
|
chose := mcat.Chosen{Node: n.From, Module: n.Module}
|
|
if !recorded || sameProvider(bound, chose) {
|
|
out = append(out, n)
|
|
continue
|
|
}
|
|
if pin, pinned := world.Pinned[n.Name]; pinned && chosenMatches(pin, mcat.Provider{Node: chose.Node, Module: chose.Module}) {
|
|
out = append(out, n)
|
|
continue
|
|
}
|
|
held, ok, why := boundNeed(n, bound, catalogue, node, world, here)
|
|
if !ok {
|
|
if key := [2]string{n.For, n.Name}; !refused[key] {
|
|
refused[key] = true
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s on %s is bound to %s for %q, which keeps its data, and %s — it would move to %s, "+
|
|
"which holds none of it. Move its data and say so with `pin %s %s %s %s`, or give "+
|
|
"%s back what it provided",
|
|
n.For, node.Name, bound, n.Name, why, chose, node.Name, n.Name, chose.Node, chose.Module,
|
|
bound.Node))
|
|
}
|
|
continue
|
|
}
|
|
out = append(out, held)
|
|
kept = append(kept, mcat.KeptBinding{Machine: node.Name, Consumer: n.For, Provision: n.Name, Bound: bound, Would: chose})
|
|
}
|
|
return out, kept, problems
|
|
}
|
|
|
|
// sameProvider is whether a recorded provider is the one chosen. A record naming no module (none
|
|
// is written without one, but a person's hand might) matches any module on its node.
|
|
func sameProvider(bound, chose mcat.Chosen) bool {
|
|
return bound.Node == chose.Node && (bound.Module == "" || bound.Module == chose.Module)
|
|
}
|
|
|
|
// boundNeed is the need answered by the recorded provider, or why it cannot be.
|
|
func boundNeed(n mcat.Needed, bound mcat.Chosen, catalogue map[string]mcat.Manifest, node mcat.Node, world mcat.World,
|
|
here func(string) bool) (mcat.Needed, bool, string) {
|
|
held := mcat.Needed{Name: n.Name, For: n.For, Local: n.Local, KeepsData: true}
|
|
if bound.Node == node.Name {
|
|
m, known := catalogue[bound.Module]
|
|
if !known || !here(bound.Module) || !providesAt(m, n.Name, mcat.ScopeMesh) {
|
|
return mcat.Needed{}, false, fmt.Sprintf("%s no longer runs on %s", bound.Module, node.Name)
|
|
}
|
|
at := node.At
|
|
if at == "" {
|
|
at = "127.0.0.1"
|
|
}
|
|
held.From, held.At, held.Module = node.Name, at, m.Module
|
|
held.Serves, held.SharedOwn, held.Identity = servedByOne(m, n.Name), sharedByOne(m, n.Name), m.IdentityBoundOf(n.Name)
|
|
held.CredentialForm, held.CredentialTool = m.CredentialFormOf(n.Name), m.CredentialToolOf(n.Name)
|
|
return held, true, ""
|
|
}
|
|
matching := chosenAmong(bound, world.Offered[n.Name])
|
|
if len(matching) != 1 {
|
|
return mcat.Needed{}, false, fmt.Sprintf("%s no longer provides it", bound)
|
|
}
|
|
p := matching[0]
|
|
if node.At == "" || p.At == "" {
|
|
return mcat.Needed{}, false, fmt.Sprintf("%s and %s are not both on the private network", node.Name, p.Node)
|
|
}
|
|
identity := mcat.DefaultIdentityBound
|
|
if pm, known := catalogue[p.Module]; known {
|
|
held.SharedOwn, _ = pm.SharedCredentialOf(n.Name)
|
|
held.CredentialForm, held.CredentialTool = pm.CredentialFormOf(n.Name), pm.CredentialToolOf(n.Name)
|
|
identity = pm.IdentityBoundOf(n.Name)
|
|
}
|
|
held.From, held.At, held.Module, held.Serves, held.Identity = p.Node, p.At, p.Module, p.Serves, identity
|
|
held.LeftOut = p.LeftOut
|
|
return held, true, ""
|
|
}
|
|
|
|
// BindsFrom is the providers this resolution binds a pair credential's need to — the provision, the
|
|
// module that requires it and the credential's local name — answered by a machine rather than by a
|
|
// record. None means this machine states no such binding.
|
|
func BindsFrom(r mcat.Resolution, provision, module, local string) []string {
|
|
var from []string
|
|
for _, n := range r.Needs {
|
|
if n.ByRecord || n.Name != provision || n.For != module || n.Local != local {
|
|
continue
|
|
}
|
|
if !slices.Contains(from, n.From) {
|
|
from = append(from, n.From)
|
|
}
|
|
}
|
|
return from
|
|
}
|