diff --git a/go/delivery/assigner.go b/go/delivery/assigner.go index 1c9cdf0..d477387 100644 --- a/go/delivery/assigner.go +++ b/go/delivery/assigner.go @@ -196,15 +196,37 @@ type Want struct { // 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"` + // Warrant is the mark of a want that may replace the bus server (novox/hq ADR 0594 §6): WarrantPut on a want a put + // writes on the operator's warrant after the streams' snapshot, WarrantOperator on one the operator's credential + // writes by hand at the terminal. The node agent reads this mark and nothing else to decide whether the bus server + // may be replaced (§2); a want without it never replaces the server. Empty on every other want. + Warrant string `json:"warrant,omitempty"` } +// The warrants a want may carry (Want.Warrant, novox/hq ADR 0594 §6). +const ( + // WarrantPut marks a want the assigner's put wrote on the operator's warrant, after the streams' snapshot. + WarrantPut = "put" + // WarrantOperator marks a want the operator's credential wrote by hand at the terminal. + WarrantOperator = "operator" +) + func (w Want) check() error { if w.Removed { - 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") + if w.FP != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" || w.Warrant != "" { + return fmt.Errorf("delivery: a removed want names no fingerprint, composition, rule, backup answer, put or warrant") } return nil } + switch w.Warrant { + case "", WarrantOperator: + case WarrantPut: + if !w.Put { + return fmt.Errorf("delivery: only a put's want carries the put's warrant") + } + default: + return fmt.Errorf("delivery: the warrant %q is not %s or %s", w.Warrant, WarrantPut, WarrantOperator) + } if w.Hold && !w.Put { return fmt.Errorf("delivery: only a put holds a want") } diff --git a/go/delivery/assigner_test.go b/go/delivery/assigner_test.go index b26279f..bbaec4e 100644 --- a/go/delivery/assigner_test.go +++ b/go/delivery/assigner_test.go @@ -79,6 +79,9 @@ func TestAssignerValuesRoundTrip(t *testing.T) { {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleCanary, Words: "the canary ace does not run it healthy yet"}, {Removed: true, Words: "the module left g14"}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Hold: true, Words: "put by the operator: the bus's user list"}, + // 774/S1: a want that may replace the bus server says its warrant (ADR 0594 §6). + {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Warrant: WarrantPut}, + {FP: fp2of('a'), Self: true, Warrant: WarrantOperator}, } for _, w := range wants { data, err := EncodeWant(w) @@ -151,6 +154,10 @@ func TestAssignerValuesRefused(t *testing.T) { {FP: fp2of('a'), Composed: hashOf('1'), Hold: true}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Newest: fp2of('b'), Rule: 3}, {Removed: true, Put: true}, + // 774/S1: a removed want carries no warrant, a warrant is put or operator, and put only on a put's want. + {Removed: true, Warrant: WarrantOperator}, + {FP: fp2of('a'), Self: true, Warrant: "yes"}, + {FP: fp2of('a'), Self: true, Warrant: WarrantPut}, } { if _, err := EncodeWant(w); err == nil { t.Errorf("EncodeWant(%+v) accepted", w)