A want the node composes itself says so (Self) and names the build alone; every other want names its composition as before, so no caller of the old form changes (issue 1126, ADR 0547 §2)
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: ace: novox/mesh-catalog#505: checks/placement/go.mod names mesh-sdk f1d7c7377dc4, which is neither on mesh-sdk's trunk nor the grou…

This commit is contained in:
2026-10-26 19:13:56 +01:00
parent dfe3ba9475
commit 4d5f430224
2 changed files with 41 additions and 21 deletions
+31 -14
View File
@@ -190,11 +190,14 @@ type Want struct {
// of policy record that the node agent judged to recreate a container or move its data is applied only when its
// want names the composition so allowed. Empty when none was asked.
Yes string `json:"yes,omitempty"`
// Self says the node composes this module itself (ADR 0547 §2): the want names the build alone, and no composition.
// A want that is not Self names the one composition it allows (ADR 0539 clause 1), as before.
Self bool `json:"self,omitempty"`
}
func (w Want) check() error {
if w.Removed {
if w.FP != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold {
if w.FP != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" {
return fmt.Errorf("delivery: a removed want names no fingerprint, composition, rule, backup answer or put")
}
return nil
@@ -208,9 +211,14 @@ func (w Want) check() error {
if err := checkFingerprint(w.FP, "a want's fingerprint"); err != nil {
return err
}
// From ADR 0547's second milestone the node composes its own modules (§2), and a want names the build alone: no
// composition digest. A want written before names one, which is read as it was written.
if w.Composed != "" {
// From ADR 0547's second milestone a node the catalogue places composes its own modules (§2): its want is Self and
// names the build alone. Any other want names the one composition it allows (ADR 0539 clause 1).
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("delivery: the want of %s says the node composes it, and names a composition", w.FP)
case !w.Self && w.Composed == "":
return fmt.Errorf("delivery: the want of %s names no composition: a want names the digest of the one composition it allows (ADR 0539 clause 1), unless the node composes it itself", w.FP)
case w.Composed != "":
if err := checkDigest(w.Composed, "a want's composition digest"); err != nil {
return err
}
@@ -320,15 +328,15 @@ type NodeWant struct {
V int `json:"v"`
Composed string `json:"composed"`
Words string `json:"words,omitempty"`
// Self says the node composes its own composition (ADR 0547 §2) and the want names none.
Self bool `json:"self,omitempty"`
}
// EncodeNodeWant is w as JSON at Version; it refuses a digest not of the sha256 form. From ADR 0547's second milestone
// the node composes its own composition (§2) and its own want names none: an empty Composed is that want.
// EncodeNodeWant is w as JSON at Version; it refuses a digest not of the sha256 form, and a want that names no
// composition unless it says the node composes its own (Self, ADR 0547 §2).
func EncodeNodeWant(w NodeWant) ([]byte, error) {
if w.Composed != "" {
if err := checkDigest(w.Composed, "the node's own want's composition digest"); err != nil {
return nil, err
}
if err := w.check(); err != nil {
return nil, err
}
w.V = Version
return json.Marshal(w)
@@ -346,14 +354,23 @@ func DecodeNodeWant(data []byte) (NodeWant, error) {
if err := d.Decode(&w); err != nil {
return NodeWant{}, fmt.Errorf("delivery: a node's own want does not fit its shape: %w", err)
}
if w.Composed != "" {
if err := checkDigest(w.Composed, "the node's own want's composition digest"); err != nil {
return NodeWant{}, err
}
if err := w.check(); err != nil {
return NodeWant{}, err
}
return w, nil
}
// check refuses a node's own want that names no composition and does not say the node composes its own, or does both.
func (w NodeWant) check() error {
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("delivery: the node's own want says the node composes it, and names a composition")
case w.Self:
return nil
}
return checkDigest(w.Composed, "the node's own want's composition digest")
}
// EncodeHeld is h as JSON at Version; it refuses a node name a key refuses.
func EncodeHeld(h Held) ([]byte, error) {
if err := h.check(); err != nil {
+10 -7
View File
@@ -19,8 +19,8 @@ func TestAWantNamesItsComposition(t *testing.T) {
{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleComposed, Words: "no composition of b for ace yet"},
{Removed: true},
// ADR 0547 §2, §3 (issue 1126): from the second milestone a want names the build alone, and the yes a person gave.
{FP: fp2of('a')},
{FP: fp2of('a'), Yes: hashOf('3')},
{FP: fp2of('a'), Self: true},
{FP: fp2of('a'), Self: true, Yes: hashOf('3')},
} {
raw, err := EncodeWant(w)
if err != nil {
@@ -36,7 +36,10 @@ func TestAWantNamesItsComposition(t *testing.T) {
w Want
why string
}{
{Want{FP: fp2of('a'), Yes: "sha256:12"}, "a want's yes allows"},
{Want{FP: fp2of('a')}, "names no composition"},
{Want{FP: fp2of('a'), Self: true, Composed: hashOf('1')}, "names a composition"},
{Want{FP: fp2of('a'), Self: true, Yes: "sha256:12"}, "a want's yes allows"},
{Want{Removed: true, Self: true}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: "sha256:12"}, "not sha256"},
{Want{Removed: true, Composed: hashOf('1')}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: hashOf('1'), Backup: "later"}, `backup answer "later"`},
@@ -48,8 +51,8 @@ func TestAWantNamesItsComposition(t *testing.T) {
t.Errorf("EncodeWant(%+v) = %v, want a refusal saying %q", r.w, err, r.why)
}
}
if w, err := DecodeWant([]byte(`{"v":1,"fp":"` + fp2of('a') + `"}`)); err != nil || w.Composed != "" {
t.Errorf("a want naming the build alone (ADR 0547 §2) is not read: %+v, %v", w, err)
if _, err := DecodeWant([]byte(`{"v":1,"fp":"` + fp2of('a') + `"}`)); err == nil || !strings.Contains(err.Error(), "names no composition") {
t.Errorf("a want of release 5, naming no composition and not the node's own, was read: %v", err)
}
if RuleComposed != 7 {
t.Errorf("RuleComposed = %d, want the seventh rule", RuleComposed)
@@ -90,13 +93,13 @@ func TestTheNodesOwnWant(t *testing.T) {
if err != nil || back != w {
t.Errorf("node want came back %+v, %v", back, err)
}
for _, bad := range []NodeWant{{Composed: "sha256:1"}} {
for _, bad := range []NodeWant{{}, {Composed: "sha256:1"}, {Self: true, Composed: hashOf('2')}} {
if _, err := EncodeNodeWant(bad); err == nil {
t.Errorf("EncodeNodeWant(%+v) accepted", bad)
}
}
// ADR 0547 §2 (issue 1126): from the second milestone the node composes its own composition, and its want names none.
if raw, err := EncodeNodeWant(NodeWant{Words: "the node composes its own"}); err != nil {
if raw, err := EncodeNodeWant(NodeWant{Self: true, Words: "the node composes its own"}); err != nil {
t.Errorf("a node want naming no composition: %v", err)
} else if back, err := DecodeNodeWant(raw); err != nil || back.Composed != "" {
t.Errorf("a node want naming no composition came back %+v, %v", back, err)