diff --git a/go/compose/composed_test.go b/go/compose/composed_test.go index b6dd92e..b525f5b 100644 --- a/go/compose/composed_test.go +++ b/go/compose/composed_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -70,3 +71,18 @@ func TestCompositionsAreTheDeclarationSplitByModule(t *testing.T) { t.Errorf("a node the mesh does not have was composed") } } + +// 1943/AC1: the upgrade policies and sources a composition's value may name are the catalogue's own words. +func TestTheComposedPoliciesAreTheCatalogues(t *testing.T) { + for _, p := range []string{catalogue.PolicyRoll, catalogue.PolicyTogether, catalogue.PolicyRecord} { + for _, from := range []string{catalogue.FromPerson, catalogue.FromModule, catalogue.FromBus, catalogue.FromData, catalogue.FromDefault} { + v := delivery.ComposedDigest{Digest: "sha256:" + strings.Repeat("a", 64), Judgement: delivery.JudgedNeither, Policy: p, PolicyFrom: from} + if _, err := delivery.EncodeComposedDigest(v); err != nil { + t.Errorf("%s from %s: %v", p, from, err) + } + } + } + if delivery.PolicyRecord != catalogue.PolicyRecord { + t.Errorf("delivery.PolicyRecord = %q, the catalogue's %q", delivery.PolicyRecord, catalogue.PolicyRecord) + } +} diff --git a/go/delivery/composed.go b/go/delivery/composed.go index c5d30c7..a77b6ee 100644 --- a/go/delivery/composed.go +++ b/go/delivery/composed.go @@ -135,12 +135,37 @@ func checkDigest(digest, of string) error { // the Judgement of the move to it from the composition the node runs now, whose digest is Against (empty when the node // runs no composition of the module). Words say why, and are required for a move that cannot be judged. Rewritten when // the node's have changes. +// +// Policy is the module's upgrade policy as the controller holds it (ADR 0236, ADR 0539 clause 14), with PolicyFrom +// where it comes from and PolicyWhy why: the assigner holds a move of a module of policy record from it, the operator's +// own choice (`upgrade` with policy) included. Rewritten when the policy changes. Empty on a composition made by hand +// (mesh-compose), whose want the operator writes. type ComposedDigest struct { - V int `json:"v"` - Digest string `json:"digest"` - Judgement Judgement `json:"judgement"` - Against string `json:"against,omitempty"` - Words string `json:"words,omitempty"` + V int `json:"v"` + Digest string `json:"digest"` + Judgement Judgement `json:"judgement"` + Against string `json:"against,omitempty"` + Words string `json:"words,omitempty"` + Policy string `json:"policy,omitempty"` + PolicyFrom string `json:"policy-from,omitempty"` + PolicyWhy string `json:"policy-why,omitempty"` +} + +// The upgrade policies and their sources a composition's value names: catalogue.PolicyRoll, PolicyTogether and +// PolicyRecord, and catalogue.FromPerson, FromModule, FromBus, FromData and FromDefault, the same words (the compose +// package's test holds them equal; this package does not import the catalogue). +var ( + composedPolicies = []string{"roll", "together", "record"} + composedPolicyFroms = []string{"person", "module", "the bus", "irreplaceable data", "default"} +) + +func oneOf(v string, of []string) bool { + for _, o := range of { + if v == o { + return true + } + } + return false } func (c ComposedDigest) check() error { @@ -166,9 +191,20 @@ func (c ComposedDigest) check() error { return fmt.Errorf("delivery: the judgement %q is not %s, %s, %s or %s", c.Judgement, JudgedRecreates, JudgedMovesData, JudgedNeither, JudgedCannot) } + if c.Policy != "" || c.PolicyFrom != "" { + if !oneOf(c.Policy, composedPolicies) { + return fmt.Errorf("delivery: the upgrade policy %q is not one of %v", c.Policy, composedPolicies) + } + if !oneOf(c.PolicyFrom, composedPolicyFroms) { + return fmt.Errorf("delivery: the upgrade policy's source %q is not one of %v", c.PolicyFrom, composedPolicyFroms) + } + } return nil } +// PolicyRecord is the policy whose moves the assigner holds for a person's yes (ADR 0539 clause 14). +const PolicyRecord = "record" + // EncodeComposedDigest is c as JSON at Version; it refuses an unknown judgement, a digest not of the sha256 form, a // move judged to recreate or move data against nothing, and a move that cannot be judged without its reason. func EncodeComposedDigest(c ComposedDigest) ([]byte, error) { diff --git a/go/delivery/composed_keys_test.go b/go/delivery/composed_keys_test.go index dec6987..bd3630e 100644 --- a/go/delivery/composed_keys_test.go +++ b/go/delivery/composed_keys_test.go @@ -68,8 +68,9 @@ func TestComposedKeys(t *testing.T) { } // 1943/AC1: the controller's value beside a composition, its digest and the judgement of the move, round-trips; an -// unknown judgement, a digest not of the sha256 form, a move judged against nothing, and a cannot-judge without its -// reason are refused, naming why; an unknown version is refused as one. +// unknown judgement, a digest not of the sha256 form, a move judged against nothing, a cannot-judge without its +// reason, and an upgrade policy or source outside the catalogue's words are refused, naming why; an unknown version is +// refused as one. The module's upgrade policy and its source ride beside the judgement, for the assigner's hold. func TestComposedDigestValue(t *testing.T) { digest := "sha256:" + strings.Repeat("d", 64) against := "sha256:" + strings.Repeat("e", 64) @@ -79,6 +80,8 @@ func TestComposedDigestValue(t *testing.T) { {Digest: digest, Judgement: JudgedRecreates, Against: against, Words: "the container's image changes"}, {Digest: digest, Judgement: JudgedMovesData, Against: against}, {Digest: digest, Judgement: JudgedCannot, Words: "the composition the node runs is gone"}, + {Digest: digest, Judgement: JudgedNeither, Policy: "record", PolicyFrom: "person", PolicyWhy: "the operator: its data"}, + {Digest: digest, Judgement: JudgedNeither, Policy: "roll", PolicyFrom: "default"}, } { raw, err := EncodeComposedDigest(v) if err != nil { @@ -102,6 +105,9 @@ func TestComposedDigestValue(t *testing.T) { {ComposedDigest{Digest: digest, Judgement: JudgedMovesData}, "against"}, {ComposedDigest{Digest: digest, Judgement: JudgedCannot}, "why"}, {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Against: "sha256:zz"}, "not sha256"}, + {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Policy: "sometimes", PolicyFrom: "person"}, `policy "sometimes"`}, + {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Policy: "record"}, `source ""`}, + {ComposedDigest{Digest: digest, Judgement: JudgedNeither, PolicyFrom: "person"}, `policy ""`}, } for _, r := range refusals { if _, err := EncodeComposedDigest(r.v); err == nil || !strings.Contains(err.Error(), r.why) {