diff --git a/cmd/mesh-controller/network.go b/cmd/mesh-controller/network.go index b48c6136..283e8335 100644 --- a/cmd/mesh-controller/network.go +++ b/cmd/mesh-controller/network.go @@ -684,7 +684,7 @@ func artifactStoreOnNetwork(ctx context.Context, inv *inventory.Inventory, if !offers { continue } - serves, err := servedOnNode(ctx, inv, machine, m, catalogue.ArtifactStoreProvision) + serves, _, err := servedOnNode(ctx, inv, machine, m, catalogue.ArtifactStoreProvision) if err != nil { return "", "", false, fmt.Errorf("reading where %s puts the artifact store: %w", machine, err) } diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index 52e98383..1ae7dd79 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -291,10 +291,9 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, caps, _ := inv.ProfileOf(ctx, n.Name) // Its public domain too (novox/hq ADR 0346): a provider elsewhere serves its own composed names, // and composes them as its own plan does, under its own domain. - domain, err := inv.PublicDomainOf(ctx, n.Name) - if err != nil { - return catalogue.World{}, err - } + // Read as its profile is: a machine whose domain cannot be read (removed since the list was read) + // composes no public name, which leaves the key out and says so, and fails no other machine's plan. + domain, _ := inv.PublicDomainOf(ctx, n.Name) others = append(others, candidate{ catalogue.Node{Name: n.Name, Site: siteOf[n.Name], Capabilities: caps, At: onNetwork[n.Name], PublicDomain: domain}, theirs}) @@ -316,16 +315,12 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory, // What that module says a consumer needs to know, with that node's settings on // it: a port somebody moved on the provider is a port its consumers must be told // about, and the two coming from different places is how they come to disagree. - serves, err := servedOnNode(ctx, inv, o.node.Name, m, name) + serves, layers, err := servedOnNode(ctx, inv, o.node.Name, m, name) if err != nil { return catalogue.World{}, err } // And its own composed names, as that node composes them (novox/hq ADR 0346): a name it // does not compose leaves the key out, said under each consumer's need. - layers, err := inv.SettingsFor(ctx, o.node.Name, m.Module) - if err != nil { - return catalogue.World{}, err - } serves, leftOut, err := got.ServedOwnNames(m, name, serves, catalogue.WithDefaults(m, layers)) if err != nil { return catalogue.World{}, err @@ -1592,19 +1587,20 @@ func managerPublicKeyFor( // artifact store's trust and the references composed through it (04-ISSUES/102). Unreadable // given ports are that node's refusal to report, not this reader's. func servedOnNode(ctx context.Context, inv *inventory.Inventory, node string, - m catalogue.Manifest, provision string) (map[string]any, error) { + m catalogue.Manifest, provision string) (map[string]any, []catalogue.Layer, error) { ports, layers, err := portsGivenOn(ctx, inv, node, m) if err != nil { - return nil, err + return nil, nil, err } serves := catalogue.ServedOn(m, provision, ports) if len(serves) > 0 { serves, err = catalogue.Settle(serves, catalogue.WithDefaults(m, layers)) if err != nil { - return nil, err + return nil, nil, err } } - return serves, nil + // The layers too, read once, for what is filled after (novox/hq ADR 0346). + return serves, layers, nil } // portsGivenOn is where a node puts a module's ports — assigned, then given over them — and the diff --git a/internal/catalogue/consumer_into_serves.go b/internal/catalogue/consumer_into_serves.go index 74e5fbfd..e5d47916 100644 --- a/internal/catalogue/consumer_into_serves.go +++ b/internal/catalogue/consumer_into_serves.go @@ -135,6 +135,9 @@ func CheckServes(m Manifest) []string { for _, key := range sortedAnyKeys(m.Serves[provision]) { text, ok := m.Serves[provision][key].(string) if !ok { + // A provider's own name is filled only in a value of its own (novox/hq ADR 0346): one + // nested in a list or an object would be served as the placeholder, unsaid. + problems = append(problems, nestedOwnNames(m, provision, key, m.Serves[provision][key])...) continue } // A probe identity, because what is checked is the shape of the statement and not diff --git a/internal/catalogue/served_own_names.go b/internal/catalogue/served_own_names.go index f374e911..ca231c83 100644 --- a/internal/catalogue/served_own_names.go +++ b/internal/catalogue/served_own_names.go @@ -171,3 +171,32 @@ func quoted(values []string) []string { } 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, ", "))} +} diff --git a/internal/catalogue/served_own_names_test.go b/internal/catalogue/served_own_names_test.go index 1c3bc9ed..db00dcc3 100644 --- a/internal/catalogue/served_own_names_test.go +++ b/internal/catalogue/served_own_names_test.go @@ -143,6 +143,11 @@ func TestAServedNameIsCheckedWhenTheDefinitionIsRead(t *testing.T) { t.Errorf("%s: want a refusal saying %q, got %v", bad, want, err) } } + nested := strings.Replace(aRegistryForge, `"public-root":"https://${bound:route:name-web}/api/packages/novox/npm/"`, + `"roots":["https://${bound:route:name-web}/x"]`, 1) + if _, err := ParseManifest([]byte(nested)); err == nil || !strings.Contains(err.Error(), "inside a list or an object") { + t.Errorf("a nested own name is refused, not served as the placeholder: %v", err) + } if _, err := ParseManifest([]byte(aRegistryForge)); err != nil { t.Fatalf("its own name is accepted: %v", err) } diff --git a/testdata/store-tests b/testdata/store-tests index 8f592653..0640e2b3 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -58,3 +58,4 @@ ./cmd/mesh-controller TestASettingsWalkStopsBeforeADataMoveAndPassesOverAMachineAway ./cmd/mesh-controller TestACrossingSendSupersedesASettingsGate ./cmd/mesh-controller TestASettingsWalkFailsOnARefusalThatCannotClear +./cmd/mesh-controller TestAProviderElsewhereServesItsOwnComposedName