diff --git a/go/provisions/provisions.go b/go/provisions/provisions.go index c7ee20d..3889dc3 100644 --- a/go/provisions/provisions.go +++ b/go/provisions/provisions.go @@ -264,12 +264,12 @@ func Encode(p Provision) ([]byte, error) { return json.Marshal(p) } -// Decode reads a key's value strictly: a field the layout does not name is refused, so no value, sealed or not, rides in -// a provision (clause 1, ADR 0201 §13). +// Decode reads a key's value. **A field the layout does not name is read past and dropped** (issue 1126 review 1): a +// reader on an older mesh-sdk must not refuse a key a newer writer adds a field to. No value rides in a provision all +// the same (clause 1, ADR 0201 §13): Encode writes the layout alone, so what a reader does not know is never kept. func Decode(data []byte) (Provision, error) { var p Provision dec := json.NewDecoder(bytes.NewReader(data)) - dec.DisallowUnknownFields() if err := dec.Decode(&p); err != nil { return Provision{}, fmt.Errorf("provisions: a provision is not readable: %w", err) } diff --git a/go/provisions/provisions_test.go b/go/provisions/provisions_test.go index a5c49ad..f9e5ab8 100644 --- a/go/provisions/provisions_test.go +++ b/go/provisions/provisions_test.go @@ -56,10 +56,16 @@ func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) { t.Errorf("%s: %s parses as %s %s %s %s, %v", c.name, key, node, consumer, provision, local, err) } } - // A value never rides in a provision: a field the layout does not name is refused. - if _, err := Decode([]byte(`{"v":1,"provision":"p","consumer":{"module":"c","node":"n"},"provider":{"module":"p","node":"n"},` + - `"state":"provided","at":"2026-10-29T22:00:00Z","password":"hunter2"}`)); err == nil { - t.Error("a provision carrying a password was read") + // A field a later layout adds is read past, never kept (issue 1126 review 1: a reader on an older mesh-sdk must not + // refuse a key a newer writer adds a field to). A value still never rides in a provision: Encode writes the layout + // alone, so what a reader does not know is dropped, and encoding it again carries none of it. + later, err := Decode([]byte(`{"v":1,"provision":"p","consumer":{"module":"c","node":"n"},"provider":{"module":"p","node":"n"},` + + `"state":"provided","at":"2026-10-29T22:00:00Z","a-later-field":{"x":1},"password":"hunter2"}`)) + if err != nil || later.Provision != "p" || later.State != Provided { + t.Errorf("a provision with a field of a later layout: %+v, %v", later, err) + } + if again, _ := Encode(later); strings.Contains(string(again), "hunter2") || strings.Contains(string(again), "a-later-field") { + t.Errorf("a field the layout does not name was kept: %s", again) } plan := Plan{Node: "novox", Module: "restic", Plan: catalogue.Plan{}} raw, err := EncodePlan(plan)