mesh/merge-gate head 7a0c219f961e merged into trunk 332fbaa9b175 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 7a0c219f961e merged into trunk 332fbaa9b175 — pass: its merge-check.sh passed
mesh/delivery delivered
203 lines
7.3 KiB
Go
203 lines
7.3 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A provider may tell its consumers its own composed names (novox/hq ADR 0346, issue 624).
|
|
//
|
|
// **The name is already the mesh's.** A module contributes a label to a requirement — a route — and
|
|
// the mesh joins it with the providing machine's domains; the module reads the result in its own files
|
|
// as `${bound:<requirement>:name}` (novox/hq 04-ISSUES/122). Its consumers could not be told it: a
|
|
// served value named only the consumer (ADR 0202) and settings. The forge's package registry writes
|
|
// every tarball at its public name, and a consumer that must trust that name had no source for it but a
|
|
// second copy kept by hand. So a served value may say `${bound:<requirement>:<name>}` for a requirement
|
|
// the provider contributes to, and the mesh fills it from the same composition the provider's own files
|
|
// and its proxy receive.
|
|
//
|
|
// **A name the providing machine does not compose leaves the key out** (ADR 0346 §3): a machine with
|
|
// no public domain has no public name to give, and an empty value or a refusal would be worse than a
|
|
// consumer told nothing — a refused provider machine takes every provision it offers down with it. The
|
|
// plan says which key was left out, and why, on the provider's machine and under each consumer's need.
|
|
|
|
// ownNameFacts are what a served value may name of the provider's own composition: the names, as its
|
|
// own files read them (withOwnNames).
|
|
var ownNameFacts = []string{"name", "internal-name"}
|
|
|
|
// requirementsContributedTo is every requirement m contributes to, sorted.
|
|
func requirementsContributedTo(m Manifest) []string {
|
|
seen := map[string]bool{}
|
|
for to := range m.Contributes {
|
|
seen[to] = true
|
|
}
|
|
for to := range m.ContributesMany {
|
|
seen[to] = true
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for to := range seen {
|
|
out = append(out, to)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// ownNameKeys is every name a served value of m may say for one requirement it contributes to:
|
|
// `name` and `internal-name`, and `name-<local>` and `internal-name-<local>` for each local name it
|
|
// contributes under.
|
|
func ownNameKeys(m Manifest, to string) []string {
|
|
keys := append([]string{}, ownNameFacts...)
|
|
for _, local := range sortedKeys(m.ContributesMany[to]) {
|
|
for _, fact := range ownNameFacts {
|
|
keys = append(keys, fact+"-"+local)
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// ServedOwnNames fills the provider's own composed names into what it serves one provision's
|
|
// consumers, from this resolution's composition of the provider's machine (ADR 0346).
|
|
//
|
|
// A value naming a name this machine does not compose is left out, with its key, and said. The map is
|
|
// copied only when something changes, as ServedTo does: the caller's may be the manifest's.
|
|
func (r Resolution) ServedOwnNames(m Manifest, provision string, serves map[string]any, layers []Layer) (
|
|
map[string]any, []string, error) {
|
|
var asking []string
|
|
for _, key := range sortedAnyKeys(serves) {
|
|
if text, ok := serves[key].(string); ok && bound.MatchString(text) {
|
|
asking = append(asking, key)
|
|
}
|
|
}
|
|
if len(asking) == 0 {
|
|
return serves, nil, nil
|
|
}
|
|
own := map[string]map[string]string{}
|
|
for _, to := range requirementsContributedTo(m) {
|
|
names, err := r.ownNames(m, to, layers)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
values := map[string]string{}
|
|
withOwnNames(values, names)
|
|
own[to] = values
|
|
}
|
|
out := make(map[string]any, len(serves))
|
|
for k, v := range serves {
|
|
out[k] = v
|
|
}
|
|
var said []string
|
|
for _, key := range asking {
|
|
text := serves[key].(string)
|
|
var missing string
|
|
filled := bound.ReplaceAllStringFunc(text, func(token string) string {
|
|
parts := bound.FindStringSubmatch(token)
|
|
if value := own[parts[1]][parts[2]]; value != "" && parts[3] == "" {
|
|
return value
|
|
}
|
|
if missing == "" {
|
|
missing = token
|
|
}
|
|
return token
|
|
})
|
|
if missing != "" {
|
|
delete(out, key)
|
|
said = append(said, fmt.Sprintf(
|
|
"%s serves %s without %q: it names %s, and %s composes no such name for %s (it has no "+
|
|
"public domain, or the endpoint is not reached publicly) (novox/hq ADR 0346)",
|
|
m.Module, provision, key, missing, orHere(r.Node), m.Module))
|
|
continue
|
|
}
|
|
out[key] = filled
|
|
}
|
|
return out, said, nil
|
|
}
|
|
|
|
func orHere(node string) string {
|
|
if node == "" {
|
|
return "this machine"
|
|
}
|
|
return node
|
|
}
|
|
|
|
// ServedLeftOut is what the providers in this node's own set leave out of what they serve, because
|
|
// this machine composes no name a served value asks for (ADR 0346 §3): said on the provider's machine,
|
|
// where the remedy is.
|
|
func (r Resolution) ServedLeftOut(settings SettingsBy) []string {
|
|
var said []string
|
|
for _, m := range r.Modules {
|
|
for _, provision := range sortedServes(m.Serves) {
|
|
_, lines, err := r.ServedOwnNames(m, provision, m.Serves[provision], WithDefaults(m, settings[m.Module]))
|
|
if err != nil {
|
|
said = append(said, fmt.Sprintf("%s serves %s, and its own names could not be composed: %v",
|
|
m.Module, provision, err))
|
|
continue
|
|
}
|
|
said = append(said, lines...)
|
|
}
|
|
}
|
|
return said
|
|
}
|
|
|
|
// checkServedOwnNames refuses a served `${bound:…}` that names a requirement the module does not
|
|
// contribute to, a fact other than its own names, or a local name it does not contribute under; and
|
|
// one with a default, since a served value is left out rather than given one (ADR 0346 §3, §4).
|
|
func checkServedOwnNames(m Manifest, provision, key, text string) []string {
|
|
var problems []string
|
|
contributes := requirementsContributedTo(m)
|
|
for _, parts := range bound.FindAllStringSubmatch(text, -1) {
|
|
to, fact, fallback := parts[1], parts[2], parts[3]
|
|
says := fmt.Sprintf("%s serves %s, and the value it serves as %q says %s", m.Module, provision, key, parts[0])
|
|
switch {
|
|
case fallback != "":
|
|
problems = append(problems, says+": a served name takes no default; one the machine does not "+
|
|
"compose leaves the key out (novox/hq ADR 0346)")
|
|
case !oneOf(contributes, to):
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s, and %s contributes to %s: a served value names only the provider's own names "+
|
|
"(novox/hq ADR 0346)", says, m.Module, orNothing(quoted(contributes))))
|
|
case !oneOf(ownNameKeys(m, to), fact):
|
|
problems = append(problems, fmt.Sprintf("%s, and what %s is called through %s is %s (novox/hq ADR 0346)",
|
|
says, m.Module, to, strings.Join(ownNameKeys(m, to), ", ")))
|
|
}
|
|
}
|
|
return problems
|
|
}
|
|
|
|
func quoted(values []string) []string {
|
|
out := make([]string, len(values))
|
|
for i, v := range values {
|
|
out[i] = fmt.Sprintf("%q", v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// nestedOwnNames refuses a `${bound:…}` inside a served list or object: only a string value of its own is
|
|
// filled (ServedOwnNames), so a nested one would reach the consumer as the placeholder (ADR 0346 §5).
|
|
func nestedOwnNames(m Manifest, provision, key string, value any) []string {
|
|
var found []string
|
|
var walk func(v any)
|
|
walk = func(v any) {
|
|
switch x := v.(type) {
|
|
case string:
|
|
found = append(found, bound.FindAllString(x, -1)...)
|
|
case []any:
|
|
for _, e := range x {
|
|
walk(e)
|
|
}
|
|
case map[string]any:
|
|
for _, k := range sortedAnyKeys(x) {
|
|
walk(x[k])
|
|
}
|
|
}
|
|
}
|
|
walk(value)
|
|
if len(found) == 0 {
|
|
return nil
|
|
}
|
|
return []string{fmt.Sprintf(
|
|
"%s serves %s, and the value it serves as %q holds %s inside a list or an object: a provider's own "+
|
|
"name is filled only in a string value of its own (novox/hq ADR 0346)",
|
|
m.Module, provision, key, strings.Join(found, ", "))}
|
|
}
|