diff --git a/go/delivery/assigner.go b/go/delivery/assigner.go index 6c5d8e2..7987797 100644 --- a/go/delivery/assigner.go +++ b/go/delivery/assigner.go @@ -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 { diff --git a/go/delivery/want_composed_test.go b/go/delivery/want_composed_test.go index 98d3476..72858db 100644 --- a/go/delivery/want_composed_test.go +++ b/go/delivery/want_composed_test.go @@ -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)