Leave out a block its holder cannot render, and keep if-capability to known names on offered kinds (hq ADR 0255)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer delivery to the same trunk took over its walk

A piece whose shows the holder's template does not know rendered as nothing and failed
the whole machine's declaration; it is now left out and named, for push, plan and the
merge gate. quote escapes DEL, which TOML refuses bare. An if-capability nothing
detects, or on a kind a holder depends on, would drop a piece silently, so both are
refused.
This commit is contained in:
jochen
2026-10-08 15:20:59 +02:00
parent 9766338368
commit 8984c3437f
10 changed files with 267 additions and 44 deletions
+45 -13
View File
@@ -49,17 +49,27 @@ type PlaceField struct {
// OrderDefault is a piece's order when it gives none: the middle of 0–99.
const OrderDefault = 50
// capabilityName is what an `if-capability` may name: a capability as a node's profile reports it.
// capabilityName is the shape of a capability's name, as a node's profile reports it.
var capabilityName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
// KnownCapabilities is every capability the node-engine detects (mesh-host internal/profile), the
// names an `if-capability` may give (novox/hq ADR 0255). A name nothing detects would leave its
// contribution out on every machine, silently, so it is refused. A detector added there is added here.
var KnownCapabilities = []string{
"battery", "container-runtime", "firewall", "graphical-session", "overlay", "package-manager",
"power-meter", "privileged", "seat", "service-manager", "uplink-dhcpcd", "uplink-networkmanager",
"uplink-systemd-networkd", "virtualisation",
}
// renderFuncs are the functions a holder's template may call. `quote` writes a value as a JSON
// string — a valid basic string in TOML and in most tools' grammars — so a contributed command
// with quotes in it cannot break out of the holder's line.
var renderFuncs = template.FuncMap{
"quote": func(v any) (string, error) {
b, err := json.Marshal(fmt.Sprint(v))
return string(b), err
},
// string with DEL escaped as well — JSON leaves it bare, and TOML refuses it — which makes it a valid
// basic string in TOML and in most tools' grammars, so a contributed command with quotes or control
// characters in it cannot break out of the holder's line.
var renderFuncs = template.FuncMap{"quote": quote}
func quote(v any) (string, error) {
b, err := json.Marshal(fmt.Sprint(v))
return strings.ReplaceAll(string(b), "\x7f", `\u007f`), err
}
// shapedProblems is what is wrong with one contribution of a kind received as data.
@@ -197,7 +207,11 @@ type placedPiece struct {
// shapedContributions is every module's data of one kind to one seat, narrowed by where and by the
// machine's capabilities, ordered, and rendered through the holder's template — each piece under a
// comment line naming its module.
func shapedContributions(modules []Manifest, holder Manifest, s Seat, r Receivable, where map[string]string, caps map[string]bool) (string, error) {
//
// A piece the template renders to nothing — a `shows` the holder does not know yet — or fails on is
// left out and answered as unplaced, naming its module: the other pieces and the rest of the machine's
// declaration go on (novox/hq ADR 0255).
func shapedContributions(modules []Manifest, holder Manifest, s Seat, r Receivable, where map[string]string, caps map[string]bool) (string, []string, error) {
var pieces []placedPiece
for _, m := range inModuleOrder(modules) {
for _, c := range m.allContributions() {
@@ -224,18 +238,24 @@ func shapedContributions(modules []Manifest, holder Manifest, s Seat, r Receivab
}
}
if len(pieces) == 0 {
return "", nil
return "", nil, nil
}
t, err := holderTemplate(holder, s, r.Kind)
if err != nil {
return "", err
return "", nil, err
}
sort.SliceStable(pieces, func(a, b int) bool { return pieces[a].order < pieces[b].order })
var b strings.Builder
var unplaced []string
for _, p := range pieces {
out, err := renderPiece(t, p.module, p.data)
if err == nil && strings.TrimSpace(out) == "" {
err = fmt.Errorf("%s's template renders nothing for it", holder.Module)
}
if err != nil {
return "", err
unplaced = append(unplaced, fmt.Sprintf("%s's %s:%s %s is not placed by %s: %v",
p.module, s.Name, r.Kind, describePiece(p.data), holder.Module, err))
continue
}
fmt.Fprintf(&b, "%s %s\n", r.Comment, p.module)
b.WriteString(out)
@@ -243,7 +263,19 @@ func shapedContributions(modules []Manifest, holder Manifest, s Seat, r Receivab
b.WriteString("\n")
}
}
return b.String(), nil
return b.String(), unplaced, nil
}
// describePiece is a piece in one line, its fields in order, for a person reading why it was left out.
func describePiece(data map[string]any) string {
var parts []string
for _, k := range sortedKeys(data) {
if _, nested := data[k].(map[string]any); nested {
continue
}
parts = append(parts, fmt.Sprintf("%s=%v", k, data[k]))
}
return "(" + strings.Join(parts, " ") + ")"
}
// capable is whether a contribution applies on a machine with these capabilities: always, unless it