delivery: a composition carries no signature, since the node applies only what it composes itself (ADR 0593 §6)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 19:02:33 +01:00
parent 1c684743f7
commit f4bbcd7e5e
4 changed files with 16 additions and 306 deletions
+9 -16
View File
@@ -57,9 +57,6 @@ type Composed struct {
// (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
}
// composedWire is a composition as it is written.
@@ -74,7 +71,9 @@ type composedWire struct {
Own *NodeOwn `json:"own,omitempty"`
Plan *catalogue.Plan `json:"plan,omitempty"`
Refused string `json:"refused,omitempty"`
Signature *Signature `json:"signature,omitempty"`
// Signature is read and dropped: a composition is unsigned (ADR 0593 §6), and a value the ended controller signed
// still decodes. Nothing writes it.
Signature json.RawMessage `json:"signature,omitempty"`
}
// canonicalJSON is raw with its object keys sorted and its spacing dropped, its numbers kept as written.
@@ -165,11 +164,6 @@ func (c Composed) check() error {
}
}
}
if c.Signature != nil {
if err := c.Signature.check(); err != nil {
return err
}
}
return nil
}
@@ -185,7 +179,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,
Plan: c.Plan, Refused: c.Refused, Signature: c.Signature, Resources: make([]json.RawMessage, len(c.Resources))}
Plan: c.Plan, Refused: c.Refused, Resources: make([]json.RawMessage, len(c.Resources))}
for i, r := range c.Resources {
canon, err := canonicalJSON(r)
if err != nil {
@@ -224,7 +218,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, Plan: w.Plan, Refused: w.Refused, Signature: w.Signature}
Filled: w.Filled, Plan: w.Plan, Refused: w.Refused}
if w.Own != nil {
c.Own = *w.Own
}
@@ -234,15 +228,14 @@ func DecodeComposed(data []byte) (Composed, error) {
return c, nil
}
// ComposedDigestOf is a composition's digest, "sha256:<hex>", of its encoding without its signature and its plan. A want names it
// (ADR 0539 clause 1), the controller writes it beside the composition, and the signature signs it.
// ComposedDigestOf is a composition's digest, "sha256:<hex>", of its encoding without its plan. A want names it (ADR 0539
// clause 1), and the node's engine writes it beside the composition (ADR 0593 §6).
//
// 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
c.Plan = nil
raw, err := EncodeComposed(c)
if err != nil {
@@ -252,8 +245,8 @@ func ComposedDigestOf(c Composed) (string, error) {
return "sha256:" + hex.EncodeToString(sum[:]), nil
}
// SameComposed says whether two compositions are the same value, their signatures included, so the controller writes
// only when they differ.
// SameComposed says whether two compositions are the same value, their plans included, so the engine writes only when
// they differ.
func SameComposed(a, b Composed) bool {
ra, errA := EncodeComposed(a)
rb, errB := EncodeComposed(b)