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
101 lines
4.6 KiB
Go
101 lines
4.6 KiB
Go
package resolution
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"slices"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// ofConsumers is where a provider places its consumers' credentials. Loose inside the braces, so a misspelt name is
|
|
// refused rather than passed over.
|
|
var ofConsumers = regexp.MustCompile(`\$\{consumers:([^}]*)\}`)
|
|
|
|
// consumersPlaceholderProblems is what is wrong with one resource's ${consumers:…}: placed only in a file's content,
|
|
// for a provision the module provides, in a file only the provider reads and the client writes back.
|
|
func consumersPlaceholderProblems(m mcat.Manifest, r map[string]any) []string {
|
|
var problems []string
|
|
for _, field := range sortedKeys(r) {
|
|
v, ok := r[field].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, f := range ofConsumers.FindAllStringSubmatch(v, -1) {
|
|
switch {
|
|
case field != "content" || fmt.Sprint(r["type"]) != "file":
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v names %s in its %s; a provider's consumers "+
|
|
"are placed only in a file's content (novox/hq ADR 0373)", m.Module, r["id"], f[0], field))
|
|
continue
|
|
case !slices.ContainsFunc(m.Provides, func(o mcat.Offer) bool { return o.Name == f[1] }):
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v names %s, and %s does not provide %q: only a "+
|
|
"provider's template holds its consumers' credentials (novox/hq ADR 0373)", m.Module, r["id"], f[0],
|
|
m.Module, f[1]))
|
|
}
|
|
if mode := fmt.Sprint(r["mode"]); mode != "0600" && mode != "0400" {
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v holds its consumers' credentials (%s) with "+
|
|
"mode %q: the provider alone reads it, 0600 or 0400 (novox/hq ADR 0373)", m.Module, r["id"], f[0],
|
|
r["mode"]))
|
|
}
|
|
if owner := ownershipOf(r); owner != mcat.RuntimeOwned {
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v holds its consumers' credentials (%s) and is "+
|
|
"%s at runtime: it is owned, written back whenever it differs (novox/hq ADR 0373)", m.Module,
|
|
r["id"], f[0], owner))
|
|
}
|
|
}
|
|
}
|
|
return problems
|
|
}
|
|
|
|
// consumersHandedOnly says what a file naming ${consumers:…} is where it would be written the old way: the provisioner's
|
|
// client alone fills it, so on a machine that does not hand the module it is **left out** of the declaration, never
|
|
// written as that text or as an empty list the provider would read as no consumers, and never the reason the module is
|
|
// refused: a provider not handed there keeps working as it did, from its grant files (novox/hq ADR 0373, issue 780).
|
|
// A moved module judged from its repository's tree (WithTemplatesFrom: `module check`), on a machine
|
|
// that would hand it, has no templates archive to hand and is judged as it composes; there the placeholder is judged
|
|
// and taken out of the text, since what is composed so is never sent (judged answers whether the file is so judged).
|
|
func consumersHandedOnly(m mcat.Manifest, r map[string]any, with mcat.Rendering) (judged, leftOut bool) {
|
|
content, _ := r["content"].(string)
|
|
switch {
|
|
case ofConsumers.FindString(content) == "":
|
|
return false, false
|
|
case m.Moved && m.Handed == nil && with.Hands && with.Provisioner != "":
|
|
return true, false
|
|
}
|
|
return false, true
|
|
}
|
|
|
|
// consumersLeftOutLine is the plan's one line for a file left out because the module is not handed.
|
|
func consumersLeftOutLine(m mcat.Manifest, r map[string]any) string {
|
|
return fmt.Sprintf("%s's %v names ${consumers:…}, which the provisioner's client alone fills: left out, handed only "+
|
|
"(novox/hq ADR 0373)", m.Module, r["id"])
|
|
}
|
|
|
|
// consumersNotHanded is consumersHandedOnly as a refusal, for a caller that has no way to leave the file out.
|
|
func consumersNotHanded(m mcat.Manifest, r map[string]any, with mcat.Rendering) (judged bool, err error) {
|
|
judged, left := consumersHandedOnly(m, r, with)
|
|
if left {
|
|
return false, fmt.Errorf("%s's resource %v names %s, which the provisioner's client alone fills, and %s is not "+
|
|
"handed to it on this machine: it would be written as that text, or empty, and the provider would read no "+
|
|
"consumers (novox/hq ADR 0373)", m.Module, r["id"], ofConsumers.FindString(fmt.Sprint(r["content"])), m.Module)
|
|
}
|
|
return judged, nil
|
|
}
|
|
|
|
// consumersOfTemplate is what is wrong with a handed file's template naming ${consumers:…}, judged on the file's
|
|
// resource as if its text were inline.
|
|
func consumersOfTemplate(m mcat.Manifest, id, text string) []string {
|
|
for _, r := range m.Resources {
|
|
if fmt.Sprint(r["id"]) != id {
|
|
continue
|
|
}
|
|
withText := map[string]any{}
|
|
for k, v := range r {
|
|
withText[k] = v
|
|
}
|
|
withText["content"] = text
|
|
return consumersPlaceholderProblems(m, withText)
|
|
}
|
|
return nil
|
|
}
|