Decode a manifest leniently, so a field a reader does not know never leaves its module out (ADR 0578 §3, §6)
Issue 1585, S1: catalogue.Decode sets an unknown field aside and names it (SetAside); only a manifest not read past the field is still left out. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Decode reads a manifest leniently, as every part but the judge reads one (novox/hq ADR 0578 §3): every field the
|
||||
// contract knows as it declares it, and a field it does not know set aside and named (SetAside), never refused. It
|
||||
// judges nothing: no name, seat, bus or catalogue rule is applied, since the indexer judged the manifest once and its
|
||||
// verdict is in the indexer's state (delivery.VerdictsBucket); a reader takes the verdict beside it
|
||||
// (delivery.TakeVerdict). An error is only bytes that are no manifest: not JSON, or a field of the wrong shape.
|
||||
func Decode(raw []byte) (Manifest, error) {
|
||||
var m Manifest
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetAside names what Decode set aside of this manifest: each field it does not know, as the decoder words the first
|
||||
// and with the path of every one it read without, or "" when it knew every field. A reader says it in its own words
|
||||
// (its journal, its finding) and reads the rest; the part that needs the field takes the newer package (ADR 0578 §6).
|
||||
func (m Manifest) SetAside() string { return m.unknown }
|
||||
|
||||
// ReadNoFurther is whether Decode could not read past a field it does not know, so the manifest holds its name and
|
||||
// version alone: a reader holds such a module, as it holds a refused one, and names why (SetAside).
|
||||
func (m Manifest) ReadNoFurther() bool { return strings.Contains(m.unknown, readNoFurther) }
|
||||
|
||||
// readNoFurther marks, in what a manifest set aside, that it was not read past it.
|
||||
const readNoFurther = "(read no further: "
|
||||
@@ -1680,7 +1680,7 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
|
||||
fields = manifestFields{}
|
||||
_ = json.Unmarshal(keys["module"], &fields.Module)
|
||||
_ = json.Unmarshal(keys["version"], &fields.Version)
|
||||
unknown += " (read no further: " + perr.Error() + ")"
|
||||
unknown += " " + readNoFurther + perr.Error() + ")"
|
||||
} else {
|
||||
unknown += " (read without " + strings.Join(pruned, ", ") + ")"
|
||||
}
|
||||
|
||||
@@ -298,10 +298,10 @@ func TestADefaultFillsAContributionAndAServedFactButNotALiteral(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A stored manifest with a key this controller does not know, at the top or inside any block, is read
|
||||
// and its module is left out of every declaration by name; the rest of the catalogue is read; the module
|
||||
// check refuses it. One case per block whose decoder wraps the decoder's words in its own.
|
||||
func TestAStoredManifestWithAnUnknownKeyIsLeftOutAndRegistrationRefusesIt(t *testing.T) {
|
||||
// A stored manifest with a key this reader does not know, at the top or inside any block, is decoded with the key set
|
||||
// aside and named, and its module is composed, never left out (novox/hq ADR 0578 §6, which changes ADR 0262); the judge
|
||||
// refuses it. One case per block whose decoder wraps the decoder's words in its own.
|
||||
func TestAStoredManifestWithAnUnknownKeyIsSetAsideAndTheJudgeRefusesIt(t *testing.T) {
|
||||
for where, raw := range map[string]string{
|
||||
"the top": `{"module": "later", "version": "2", "a-field-from-later": {"x": 1}}`,
|
||||
"a state": `{"module": "later", "version": "2", "state": [{"name": "s", "a-field-from-later": 1}]}`,
|
||||
@@ -311,12 +311,12 @@ func TestAStoredManifestWithAnUnknownKeyIsLeftOutAndRegistrationRefusesIt(t *tes
|
||||
"an own secret": `{"module": "later", "version": "2", "own-secrets": {"s": {"path": "/x", "a-field-from-later": 1}}}`,
|
||||
"a seat's verb": `{"module": "later", "version": "2", "seats": [{"name": "later-seat", "serves": [{"name": "v", "a-field-from-later": 1}]}]}`,
|
||||
} {
|
||||
var m Manifest
|
||||
if err := json.Unmarshal([]byte(raw), &m); err != nil {
|
||||
m, err := Decode([]byte(raw))
|
||||
if err != nil {
|
||||
t.Errorf("%s: the stored manifest was not read: %v", where, err)
|
||||
continue
|
||||
}
|
||||
if m.Module != "later" || m.Version != "2" || !strings.Contains(m.UnknownField(), `"a-field-from-later"`) {
|
||||
if m.Module != "later" || m.Version != "2" || !strings.Contains(m.SetAside(), `"a-field-from-later"`) || m.ReadNoFurther() {
|
||||
t.Errorf("%s: read as %q %q, unknown %q", where, m.Module, m.Version, m.UnknownField())
|
||||
continue
|
||||
}
|
||||
@@ -324,12 +324,8 @@ func TestAStoredManifestWithAnUnknownKeyIsLeftOutAndRegistrationRefusesIt(t *tes
|
||||
t.Errorf("%s: the block's decoder did not say it: %q", where, m.UnknownField())
|
||||
}
|
||||
left := Resolution{Node: "laptop", Modules: []Manifest{m, notifier()}}.LeftOut(nil, false)
|
||||
if why := left["later"]; !strings.Contains(why, "uses a field this controller does not know") ||
|
||||
!strings.Contains(why, "a-field-from-later") {
|
||||
t.Errorf("%s: not left out by name: %v", where, left)
|
||||
}
|
||||
if _, notifierLeft := left["notifier"]; notifierLeft {
|
||||
t.Errorf("%s: another module was left out with it: %v", where, left)
|
||||
if len(left) != 0 {
|
||||
t.Errorf("%s: a module was left out for a field set aside: %v", where, left)
|
||||
}
|
||||
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "a-field-from-later") {
|
||||
t.Errorf("%s: the module check took it: %v", where, err)
|
||||
@@ -373,14 +369,14 @@ func TestALeftOutModuleStillProvidesWhatItProvides(t *testing.T) {
|
||||
t.Errorf("unknown %q does not say it read without %s", m.UnknownField(), want)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(UnknownFieldReason(m), "left out") {
|
||||
t.Fatal(UnknownFieldReason(m))
|
||||
if why := UnknownFieldReason(m); why != "" {
|
||||
t.Fatal(why)
|
||||
}
|
||||
}
|
||||
|
||||
// A left-out module's data is still copied: the backup holder on its machine keeps its lines while every
|
||||
// other thing of it is left out.
|
||||
func TestALeftOutModulesDataIsStillBackedUp(t *testing.T) {
|
||||
// A module with a field set aside is composed whole: its files are declared and its data backed up (novox/hq ADR 0578
|
||||
// §6; before, ADR 0262 left it out and backed up its data alone).
|
||||
func TestAModuleWithAFieldSetAsideIsComposedWhole(t *testing.T) {
|
||||
var later Manifest
|
||||
if err := json.Unmarshal([]byte(`{"module": "later", "version": "2", "a-field-from-later": 1,
|
||||
"resources": [{"id": "d", "type": "directory", "mode": "0700"}, {"id": "rc", "type": "file", "path": "/etc/later.conf", "content": "x\n"}],
|
||||
@@ -397,12 +393,12 @@ func TestALeftOutModulesDataIsStillBackedUp(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, left := composed.LeftOut["later"]; !left {
|
||||
t.Fatalf("not left out: %v", composed.LeftOut)
|
||||
if _, left := composed.LeftOut["later"]; left {
|
||||
t.Fatalf("left out: %v", composed.LeftOut)
|
||||
}
|
||||
got := byID(composed.Resources)
|
||||
if _, declared := got["later.rc"]; declared {
|
||||
t.Fatal("the left-out module's file is still declared")
|
||||
if _, declared := got["later.rc"]; !declared {
|
||||
t.Fatal("the module's file is not declared")
|
||||
}
|
||||
list, _ := got["backups.list"]["content"].(string)
|
||||
if !strings.Contains(list, "# later") || !strings.Contains(list, "path /var/lib/later/d") {
|
||||
|
||||
@@ -50,10 +50,13 @@ func asUnknownField(err error) *UnknownFieldError {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnknownFieldReason is why a module whose stored manifest has a key this controller does not know is
|
||||
// left out of a machine's declaration, or "" when it has none.
|
||||
// UnknownFieldReason is why a module whose stored manifest has a key this reader does not know is left out of a
|
||||
// machine's declaration, or "" when it is not. **A field set aside no longer leaves the module out** (novox/hq ADR 0578
|
||||
// §6, which changes ADR 0262 here): the lenient decoder reads the rest, the reader names the field (SetAside), and the
|
||||
// part that needs the field takes the newer package. Only a manifest not read past the field, which holds its name and
|
||||
// version alone (ReadNoFurther), is still left out, since nothing of it could be composed.
|
||||
func UnknownFieldReason(m Manifest) string {
|
||||
if m.unknown == "" {
|
||||
if !m.ReadNoFurther() {
|
||||
return ""
|
||||
}
|
||||
return m.Module + " uses a field this controller does not know (" + m.unknown + "); it is left out " +
|
||||
|
||||
Reference in New Issue
Block a user