Read past a provision field a later layout adds

Decode refused any field it did not know, so every reader on an older
mesh-sdk refused a key the moment a writer added one (plan, this release).
It now reads past it and drops it; Encode still writes the layout alone, so
no value rides in a provision (issue 1126 review 1).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 12:18:21 +01:00
parent c94f30d398
commit f569924037
2 changed files with 13 additions and 7 deletions
+3 -3
View File
@@ -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)
}
+10 -4
View File
@@ -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)