A pin names the module as well as the node; a node that answers twice is refused
A provider is a (node, module) pair (design 23), and the pin — the one way a consumer names its provider — named only the node. Two modules on one node can both answer a provision (public-acme and step-ca both offer acme-ca on novox), and then the resolver, given a pin naming that node, took the last provider listed: a coin flip. The same ambiguity beside the consumer was settled by a map walk — random per plan — which is how novox's own route-proxy got its issuer (novox/hq #258). - `pin <node> <provision> <from-node> <module>`: both halves, always. The console gains `pin` and `unpin`. The provider may be on the consumer's own node, since two modules beside it can both answer. - The resolver refuses ambiguity instead of picking, across machines and beside the consumer alike, naming every candidate as node/module and the form of the pin that settles it. A plain capability that grants nothing and serves nothing (three shells beside an editor) is not a choice to put to anybody and stays as it was. - provision_pin gains a nullable module (0050); records made before are completed where the node they name answers once, and left for a person where it answers twice (0051). - The provider of something already satisfied is looked for among what was assigned, not only what the walk has reached — a consumer reached before the provider beside it no longer loses its binding. - The start-time check that every declared verb is runnable samples each verb's required arguments from its schema instead of three guessed keys. Live consequence: a node that has two providers of one bound provision assigned (novox: acme-ca) resolves only once pinned — `pin novox acme-ca novox public-acme`.
This commit is contained in:
@@ -48,11 +48,12 @@ type World struct {
|
||||
Holdings []Held
|
||||
// Offered is what other nodes provide at mesh scope, and everything needed to use it.
|
||||
Offered map[string][]Provider
|
||||
// Pinned is which node this machine was told to get a provision from, by name. Only consulted
|
||||
// when more than one node could answer -- a choice recorded before it was needed should not
|
||||
// start meaning something the day a second provider appears, and one recorded and then made
|
||||
// unnecessary should not quietly stop applying either.
|
||||
Pinned map[string]string
|
||||
// Pinned is which provider this machine was told to get a provision from, by name: a module and
|
||||
// the node it runs on, both (novox/hq #258). Only consulted when more than one could answer -- a
|
||||
// choice recorded before it was needed should not start meaning something the day a second
|
||||
// provider appears, and one recorded and then made unnecessary should not quietly stop applying
|
||||
// either.
|
||||
Pinned map[string]Chosen
|
||||
// Licences is every provision answered by a **record rather than a node**, by provision name.
|
||||
//
|
||||
// novox/hq ADR 0024: a hosted model is on nobody's machine and is reached over the public
|
||||
@@ -260,6 +261,10 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
// still "choose one" after somebody has chosen one. That makes the remedy useless, and it is
|
||||
// how this read when first used.
|
||||
satisfied := map[string]bool{}
|
||||
// Which modules a person assigned here, hostable. The walk marks a module chosen only when it
|
||||
// reaches it, and a consumer may be reached before the provider beside it — so the provider of
|
||||
// something already satisfied is looked for among these as well as among the chosen.
|
||||
assignedHere := map[string]bool{}
|
||||
|
||||
// Everything a person assigned goes in first, except what this machine cannot run. Those are
|
||||
// choices already made, and a requirement one of them answers is not a choice to put back to
|
||||
@@ -284,6 +289,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
for _, o := range m.Offers() {
|
||||
satisfied[o] = true
|
||||
}
|
||||
assignedHere[a] = true
|
||||
}
|
||||
because[a] = "assigned"
|
||||
queue = append(queue, a)
|
||||
@@ -311,6 +317,42 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
// commonest arrangement of all — a service and its database on one node — the weakest
|
||||
// handling, silently.
|
||||
if satisfied[want] && !isModule(catalogue, want) {
|
||||
here := func(name string) bool { return chosen[name] || assignedHere[name] }
|
||||
local := providersHere(catalogue, here, want)
|
||||
// Which of them it matters to choose between. A plain capability — a shell, a display
|
||||
// server — asks nothing of whoever answers it, and three shells beside an editor are
|
||||
// not a choice to put to anybody. One that grants a credential, or serves a fact the
|
||||
// consumer cannot guess, becomes a binding, and a binding is to one provider.
|
||||
matter := local
|
||||
if !brokered[want] {
|
||||
matter = nil
|
||||
for _, name := range local {
|
||||
if _, ok := catalogue[name].Serves[want]; ok {
|
||||
matter = append(matter, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
var by Manifest
|
||||
switch len(matter) {
|
||||
case 0:
|
||||
// Nothing to bind to; satisfied by its presence, as it was.
|
||||
case 1:
|
||||
by = catalogue[matter[0]]
|
||||
default:
|
||||
// Two modules on this machine answer it. Taking whichever a map walk met first
|
||||
// was the rule until novox/hq #258 — random, per plan — and the same stance as
|
||||
// across machines applies: ambiguity is refused, never resolved by picking.
|
||||
c, pinned := world.Pinned[want]
|
||||
if !pinned || c.Node != node.Name || !oneOf(matter, c.Module) {
|
||||
reported[want] = true
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s provides %q %d times, wanted by %s — say which with `pin %s %s %s <module>`: %s",
|
||||
node.Name, want, len(matter), because[want], node.Name, want, node.Name,
|
||||
strings.Join(matter, ", ")))
|
||||
continue
|
||||
}
|
||||
by = catalogue[c.Module]
|
||||
}
|
||||
if brokered[want] {
|
||||
// Answered here, and still a need: the provider is this node.
|
||||
//
|
||||
@@ -326,9 +368,9 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
}
|
||||
needs = append(needs, Needed{
|
||||
Name: want, From: node.Name, At: at,
|
||||
Serves: servedHere(catalogue, chosen, want), For: because[want],
|
||||
SharedOwn: sharedHere(catalogue, chosen, want)})
|
||||
} else if served := servedHere(catalogue, chosen, want); len(served) > 0 {
|
||||
Serves: servedByOne(by, want), For: because[want],
|
||||
SharedOwn: sharedByOne(by, want)})
|
||||
} else if served := servedByOne(by, want); len(served) > 0 {
|
||||
// Answered here with no credential to mint, but the provider serves facts the
|
||||
// consumer cannot guess — a port, a model name — and so still needs a binding.
|
||||
// **The reachability rule does not apply**: both ends are on this same machine, so
|
||||
@@ -358,11 +400,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
if brokered[want] {
|
||||
reported[want] = true
|
||||
where := world.Offered[want]
|
||||
names := make([]string, 0, len(where))
|
||||
for _, p := range where {
|
||||
names = append(names, p.Node)
|
||||
}
|
||||
sort.Strings(names)
|
||||
names := providerNames(where)
|
||||
take := func(p Provider) {
|
||||
if node.At != "" && p.At == "" || node.At == "" && p.At != "" || node.At == "" && p.At == "" {
|
||||
// One of them is not on the private network, so there is no path between
|
||||
@@ -396,17 +434,17 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
"nothing in this mesh provides %q, wanted by %s %s",
|
||||
want, because[want], remedy))
|
||||
case len(where) == 1:
|
||||
if chosenNode, pinned := world.Pinned[want]; pinned && chosenNode != where[0].Node {
|
||||
if c, pinned := world.Pinned[want]; pinned && !c.matches(where[0]) {
|
||||
// One provider, and it is not the one this machine was told to use. Silently
|
||||
// using the other would be the mesh overruling a choice somebody made.
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s was told to get %q from %s, and only %s provides it",
|
||||
node.Name, want, chosenNode, where[0].Node))
|
||||
node.Name, want, c, nameOf(where[0])))
|
||||
break
|
||||
}
|
||||
take(where[0])
|
||||
default:
|
||||
chosenNode, pinned := world.Pinned[want]
|
||||
c, pinned := world.Pinned[want]
|
||||
if !pinned {
|
||||
// **The seat's holder answers, when a seat delivers this** (novox/hq ADR 0110).
|
||||
// Not a guess, which ADR 0009 refuses: the choice was made once, mesh-wide, by
|
||||
@@ -418,27 +456,32 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
break
|
||||
}
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%d nodes provide %q, wanted by %s — say which with `pin %s %s <node>`: %s",
|
||||
"%d providers of %q, wanted by %s — say which with `pin %s %s <node> <module>`: %s",
|
||||
len(where), want, because[want], node.Name, want,
|
||||
strings.Join(names, ", ")))
|
||||
break
|
||||
}
|
||||
var chosen *Provider
|
||||
for i, w := range where {
|
||||
if w.Node == chosenNode {
|
||||
chosen = &where[i]
|
||||
}
|
||||
}
|
||||
if chosen == nil {
|
||||
// Pointed at a machine that does not answer this. Refused rather than
|
||||
matching := c.among(where)
|
||||
switch len(matching) {
|
||||
case 0:
|
||||
// Pointed at a provider that does not answer this. Refused rather than
|
||||
// falling back to another: a fallback would quietly move somebody's data to
|
||||
// a machine they did not choose, which is the whole reason this is asked.
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s was told to get %q from %s, and %s does not provide it — these do: %s",
|
||||
node.Name, want, chosenNode, chosenNode, strings.Join(names, ", ")))
|
||||
break
|
||||
node.Name, want, c, c, strings.Join(names, ", ")))
|
||||
case 1:
|
||||
take(matching[0])
|
||||
default:
|
||||
// A record naming only the node, from before a pin named the module, and that
|
||||
// node answers twice. This once took the last one listed (novox/hq #258): a
|
||||
// coin flip, handed to whoever reads the certificate it chose.
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s was told to get %q from %s, and %s provides it %d times — say which with "+
|
||||
"`pin %s %s %s <module>`: %s",
|
||||
node.Name, want, c, c.Node, len(matching), node.Name, want, c.Node,
|
||||
strings.Join(providerNames(matching), ", ")))
|
||||
}
|
||||
take(*chosen)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -597,31 +640,6 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
// need that is never created is a binding the consumer never gets. It is right about that from the
|
||||
// manifest alone, which is why walking the catalogue mid-resolution is enough here and is not
|
||||
// enough for the values.
|
||||
// sharedHere is the own secret the provider of a provision on this same machine names as its
|
||||
// credential (ADR 0158), or "" when the provider gives each consumer its own.
|
||||
func sharedHere(catalogue map[string]Manifest, chosen map[string]bool, want string) string {
|
||||
for name, m := range catalogue {
|
||||
if !chosen[name] {
|
||||
continue
|
||||
}
|
||||
if own, shared := m.SharedCredentialOf(want); shared {
|
||||
return own
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func servedHere(catalogue map[string]Manifest, chosen map[string]bool, want string) map[string]any {
|
||||
for name, m := range catalogue {
|
||||
if !chosen[name] {
|
||||
continue
|
||||
}
|
||||
if _, ok := m.Serves[want]; ok {
|
||||
return ServedOn(m, want, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isModule reports whether a name is a module in its own right rather than only something
|
||||
// modules provide.
|
||||
|
||||
Reference in New Issue
Block a user