delivery, catalogue: the node-engine's compose-candidate verb, compositions carrying their plan or refusal, the kept import form and a build's imports on its key, so the engine alone composes and the indexer switches form without a rebuild (ADR 0593 §4, §6, §7)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 18:19:24 +01:00
parent 04d951d9da
commit 346dd43228
7 changed files with 373 additions and 4 deletions
+25 -2
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/declaration"
)
@@ -46,6 +47,15 @@ type Composed struct {
Resources []json.RawMessage
Filled []string
Own NodeOwn
// Plan is the module's whole provisioning plan as the node-engine hands it to the provisioner (templates, files,
// own secrets by name, items, collections, provisions; never a value), composed node-wide (novox/hq ADR 0593 §6,
// §8): the provisioner makes every plan from it. Nil when the module hands the provisioner nothing; only on a
// module's key. It is part of the encoding, so the digest moves when the plan does.
Plan *catalogue.Plan
// Refused is why the module cannot be composed at this fingerprint on this node, the rest of the node composed
// (ADR 0593 §6): a refused composition holds no resource, filled file, data or plan, and has no digest
// (ComposedDigestOf refuses it); only on a module's key. The node keeps running what it ran of the module.
Refused string
// Signature is the controller's or, made by hand, the operator's (ADR 0539 clause 3, SignComposed); it signs the
// digest and is never part of it.
Signature *Signature
@@ -61,6 +71,8 @@ type composedWire struct {
Resources []json.RawMessage `json:"resources"`
Filled []string `json:"filled_files,omitempty"`
Own *NodeOwn `json:"own,omitempty"`
Plan *catalogue.Plan `json:"plan,omitempty"`
Refused string `json:"refused,omitempty"`
Signature *Signature `json:"signature,omitempty"`
}
@@ -99,6 +111,12 @@ func (c Composed) check() error {
return fmt.Errorf("delivery: the composition of %s carries the node's own parts, which only <node>.node carries", c.Module)
}
}
if c.Module == "" && (c.Plan != nil || c.Refused != "") {
return fmt.Errorf("delivery: the node's own composition carries no plan and no refusal: only a module's does")
}
if c.Refused != "" && (len(c.Resources) > 0 || len(c.Filled) > 0 || c.Data || c.Plan != nil) {
return fmt.Errorf("delivery: the composition of %s on %s is refused and holds resources, filled files, data or a plan", c.Module, c.Node)
}
seen := map[string]bool{}
files := map[string][]*declaration.File{}
for _, raw := range c.Resources {
@@ -166,7 +184,7 @@ func (c Composed) wire() (composedWire, error) {
return composedWire{}, err
}
w := composedWire{V: Version, Node: c.Node, Module: c.Module, FP: c.FP, Data: c.Data, Filled: c.Filled,
Signature: c.Signature, Resources: make([]json.RawMessage, len(c.Resources))}
Plan: c.Plan, Refused: c.Refused, Signature: c.Signature, Resources: make([]json.RawMessage, len(c.Resources))}
for i, r := range c.Resources {
canon, err := canonicalJSON(r)
if err != nil {
@@ -205,7 +223,7 @@ func DecodeComposed(data []byte) (Composed, error) {
return Composed{}, fmt.Errorf("delivery: a composition does not fit its shape: %w", err)
}
c := Composed{V: w.V, Node: w.Node, Module: w.Module, FP: w.FP, Data: w.Data, Resources: w.Resources,
Filled: w.Filled, Signature: w.Signature}
Filled: w.Filled, Plan: w.Plan, Refused: w.Refused, Signature: w.Signature}
if w.Own != nil {
c.Own = *w.Own
}
@@ -217,7 +235,12 @@ func DecodeComposed(data []byte) (Composed, error) {
// ComposedDigestOf is a composition's digest, "sha256:<hex>", of its encoding without its signature. A want names it
// (ADR 0539 clause 1), the controller writes it beside the composition, and the signature signs it.
//
// A refused composition has no digest: it is refused, naming the module (ADR 0593 §6).
func ComposedDigestOf(c Composed) (string, error) {
if c.Refused != "" {
return "", fmt.Errorf("delivery: the composition of %s on %s is refused (%s) and has no digest", c.Module, c.Node, c.Refused)
}
c.Signature = nil
raw, err := EncodeComposed(c)
if err != nil {