From 7b09de1e5502231fcd7e41a61086b44f5d7ad545 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 1 Nov 2026 01:57:36 +0100 Subject: [PATCH] =?UTF-8?q?Let=20the=20composer=20take=20each=20manifest's?= =?UTF-8?q?=20verdict=20from=20its=20input=20instead=20of=20judging=20(ADR?= =?UTF-8?q?=200578=20=C2=A75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1585, S11 (mesh-sdk half): Case.Verdicts carries the indexer's verdicts; a refused module is held as its stub and named; an unknown field is set aside and named, never refused. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- go/cmd/mesh-compose/main.go | 11 +---- go/cmd/mesh-compose/main_test.go | 49 +++++------------------ go/composer/composer.go | 69 ++++++++++++++++++-------------- go/composer/composer_test.go | 29 ++++++++------ 4 files changed, 67 insertions(+), 91 deletions(-) diff --git a/go/cmd/mesh-compose/main.go b/go/cmd/mesh-compose/main.go index 3168bde..9c38a8f 100644 --- a/go/cmd/mesh-compose/main.go +++ b/go/cmd/mesh-compose/main.go @@ -28,7 +28,6 @@ import ( "sort" "strings" - "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/composer" ) @@ -97,15 +96,7 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { req.Cases[0].Manifests[name] = m } } - // The catalogue's list of predefined keys is the one its manifests' fallbacks are judged against (ADR 0348 §7). - if req.Predefined == nil { - list, err := catalogue.ReadPredefined(*catalogueDir) - if err != nil { - fmt.Fprintln(stderr, "mesh-compose:", err) - return 1 - } - req.Predefined = list - } + // No list of predefined keys is read: the composer judges no manifest (novox/hq ADR 0578 §5). } req.Machines = append(req.Machines, machines...) if *depth != "" { diff --git a/go/cmd/mesh-compose/main_test.go b/go/cmd/mesh-compose/main_test.go index c68ff93..df60e35 100644 --- a/go/cmd/mesh-compose/main_test.go +++ b/go/cmd/mesh-compose/main_test.go @@ -16,6 +16,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/composer" + "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/internal/beside" ) @@ -77,7 +78,9 @@ func encode(t *testing.T, r composer.Request) []byte { // refused. func TestTheCommandWritesOneVerdictPerManifestAndMachine(t *testing.T) { r := request(t, composer.DepthResolve) - r.Cases[0].Manifests["broken"] = composer.Manifest{Raw: json.RawMessage(`{"module":"broken","version":"1","shares":["x"]}`)} + r.Cases[0].Manifests["later"] = composer.Manifest{Raw: json.RawMessage(`{"module":"later","version":"1","shares":["x"]}`)} + r.Cases[0].Manifests["refused"] = composer.Manifest{Raw: json.RawMessage(`{"module":"refused","version":"1"}`)} + r.Cases[0].Verdicts = map[string]delivery.ManifestVerdict{"refused": {State: delivery.VerdictRefused, Problems: []string{"it is wrong"}}} code, v, stderr := runJSON(t, nil, encode(t, r)) if code != 0 { t.Fatalf("exit %d: %s", code, stderr) @@ -86,8 +89,13 @@ func TestTheCommandWritesOneVerdictPerManifestAndMachine(t *testing.T) { t.Fatalf("verdict = %+v", v) } c := v.Cases[0] - if got := c.Manifests["broken"]; got.Accepted || !strings.Contains(got.Refused, `"shares"`) { - t.Errorf("the manifest with an unknown field reads %+v, want refused naming the field", got) + // ADR 0578 §5, §6: the composer judges nothing; it sets an unknown field aside and names it, and holds a manifest + // its verdict refuses, named in the indexer's words. + if got := c.Manifests["later"]; !got.Accepted || !strings.Contains(got.SetAside, `"shares"`) { + t.Errorf("the manifest with an unknown field reads %+v, want accepted naming the field set aside", got) + } + if got := c.Manifests["refused"]; got.Accepted || !strings.Contains(got.Refused, "refused the manifest of refused: it is wrong") { + t.Errorf("the refused manifest reads %+v, want refused in the verdict's words", got) } if got := c.Manifests["nats"]; !got.Accepted { t.Errorf("nats reads %+v, want accepted", got) @@ -310,38 +318,3 @@ func TestTheCommandRunsWithNoNetwork(t *testing.T) { sort.Strings(names) t.Logf("composed with no network: %v", names) } - -// -catalogue judges a fallback to a predefined key against the predefined.json at the checkout's root (ADR 0348 §7): -// xterm (mesh-catalog at 835bf22b) falls back to "font-size", accepted where the root lists it and refused where the -// root's list does not. -func TestACatalogueCheckoutJudgesFallbacksAgainstItsPredefinedKeys(t *testing.T) { - raw, err := os.ReadFile("../../catalogue/testdata/catalogue-835bf22b-xterm.module.json") - if err != nil { - t.Fatal(err) - } - for _, c := range []struct { - list string - want bool - }{ - {`{"font-size": {"type": "number", "description": "the desktop's font size"}}`, true}, - {`{"font-family": {"type": "string", "description": "the desktop's font"}}`, false}, - } { - dir := t.TempDir() - if err := os.MkdirAll(filepath.Join(dir, "modules", "xterm"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "modules", "xterm", "module.json"), raw, 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, catalogue.PredefinedFile), []byte(c.list), 0o600); err != nil { - t.Fatal(err) - } - code, v, stderr := runJSON(t, []string{"-catalogue", dir}, nil) - if code != 0 { - t.Fatalf("exit %d: %s", code, stderr) - } - if got := v.Cases[0].Manifests["xterm"]; got.Accepted != c.want { - t.Errorf("predefined.json %s: xterm %+v, want accepted %v", c.list, got, c.want) - } - } -} diff --git a/go/composer/composer.go b/go/composer/composer.go index 216c8fd..164a096 100644 --- a/go/composer/composer.go +++ b/go/composer/composer.go @@ -1,4 +1,4 @@ -// Package composer is mesh-sdk's composition and validator as one function over a plain request (novox/hq ADR 0482 +// Package composer is mesh-sdk's composition as one function over a plain request (novox/hq ADR 0482 // clause 4, issue 1621): what the command mesh-compose runs, so a program that links any one mesh-sdk can have the // composition of another commit judged by building that commit's command and reading its verdict. // @@ -57,11 +57,9 @@ type Request struct { HoldingsFromClaims bool `json:"holdings_from_claims,omitempty"` // Declarations asks each composed machine's declared paths (DepthCompose only). Declarations bool `json:"declarations,omitempty"` - // Predefined is the list of predefined keys at the catalogue's root (predefined.json, novox/hq ADR 0348 §7) that - // every config value falling back to a key is judged against, when the asker has it (mesh-compose -catalogue - // reads it). Without it a fallback is read as catalogue.ParseManifestForInputs reads it: against the list the mesh - // published when this process holds one, else each key as its value declares it, for the merge gate judges the - // fallback in the catalogue and a manifest that only carries one is not refused here. + // Predefined is the list of predefined keys at the catalogue's root (predefined.json, novox/hq ADR 0348 §7). No + // longer read: the composer judges no manifest (novox/hq ADR 0578 §5), the indexer's judge does, and its verdicts + // come in each case's Verdicts. Kept so an asker that still sends it is read. Predefined catalogue.PredefinedList `json:"predefined,omitempty"` } @@ -70,11 +68,16 @@ type Request struct { // Placements are, by node, the placement the case composes that node with in place of what Input says of it: a change's // own placement files, so a check composes a catalogue pull request's placement (issue 1126, frombus.Placed); a null // placement leaves nothing placed on the node. A case without them composes Input's. +// +// Verdicts are, by module, the indexer's judge's verdict on its manifest in Manifests (novox/hq ADR 0578 §5), put in by +// the assigner or the checker that runs the composer: a refused one is held, composed as its stub so what it offers +// still shapes the rest, and named in the case's verdict. A module without one is accepted (delivery.TakeManifestVerdict). type Case struct { - Name string `json:"name"` - Manifests map[string]Manifest `json:"manifests"` - Kept map[string]map[string]Manifest `json:"kept,omitempty"` - Placements map[string]*delivery.Placement `json:"placements,omitempty"` + Name string `json:"name"` + Manifests map[string]Manifest `json:"manifests"` + Verdicts map[string]delivery.ManifestVerdict `json:"verdicts,omitempty"` + Kept map[string]map[string]Manifest `json:"kept,omitempty"` + Placements map[string]*delivery.Placement `json:"placements,omitempty"` } // Manifest is a module's manifest as its repository holds it (Raw, the bytes), or the digest the delivery indexer keeps @@ -124,10 +127,13 @@ type CaseVerdict struct { Machines map[string]Machine `json:"machines,omitempty"` } -// ManifestRead is one manifest as this mesh-sdk reads it: accepted, or refused with the reader's words. +// ManifestRead is one manifest as this mesh-sdk reads it: accepted, or refused with why: the indexer's verdict +// (Case.Verdicts) or bytes that do not decode. A field the decoder does not know is set aside, named in SetAside, and +// the manifest still accepted (novox/hq ADR 0578 §6). type ManifestRead struct { Accepted bool `json:"accepted"` Refused string `json:"refused,omitempty"` + SetAside string `json:"set_aside,omitempty"` } // Machine is one machine composed: whether it composes (resolves, at DepthResolve), why not, the node-engine @@ -222,31 +228,34 @@ func Run(r Request) (Verdict, error) { return v, nil } -// readManifest reads one manifest as this run reads every one: against the request's list of predefined keys when it -// has one, else as the indexer and the builder read it (Request.Predefined). -func readManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.Manifest, error) { - if predefined != nil { - return catalogue.ParseManifestWith(raw, predefined) - } - return catalogue.ParseManifestForInputs(raw) -} - -func parse(m map[string]Manifest, reads map[string]ManifestRead, predefined catalogue.PredefinedList) (map[string]catalogue.Manifest, error) { +// parse decodes every manifest leniently and takes its verdict (novox/hq ADR 0578 §3, §5): the composer judges none. A +// manifest refused by its verdict is held, composed as its stub without its items (as the bus reader holds one); bytes +// that do not decode are left out. Both are named in reads. +func parse(m map[string]Manifest, reads map[string]ManifestRead, verdicts map[string]delivery.ManifestVerdict) (map[string]catalogue.Manifest, error) { out := map[string]catalogue.Manifest{} for name, man := range m { if len(man.Raw) == 0 { return nil, fmt.Errorf("%w: %s (digest %s)", ErrUnfilled, name, man.Digest) } - parsed, err := readManifest(man.Raw, predefined) - if reads != nil { - if err != nil { - reads[name] = ManifestRead{Refused: err.Error()} - } else { - reads[name] = ManifestRead{Accepted: true} + decoded, err := catalogue.Decode(man.Raw) + read := ManifestRead{Accepted: err == nil, SetAside: decoded.SetAside()} + if err != nil { + read.Refused = fmt.Sprintf("the manifest of %s does not decode: %v", name, err) + } else if v, ok := verdicts[name]; ok { + if refused, _ := delivery.TakeManifestVerdict(&v); refused { + if v.Module == "" { + v.Module = name + } + read = ManifestRead{Refused: v.Why()} + decoded.Items = nil + decoded = compose.Stub(decoded) } } + if reads != nil { + reads[name] = read + } if err == nil { - out[name] = parsed + out[name] = decoded } } return out, nil @@ -254,7 +263,7 @@ func parse(m map[string]Manifest, reads map[string]ManifestRead, predefined cata func runCase(r Request, c Case, depth string) (CaseVerdict, error) { cv := CaseVerdict{Name: c.Name, Manifests: map[string]ManifestRead{}} - shelf, err := parse(c.Manifests, cv.Manifests, r.Predefined) + shelf, err := parse(c.Manifests, cv.Manifests, c.Verdicts) if err != nil { return CaseVerdict{}, err } @@ -268,7 +277,7 @@ func runCase(r Request, c Case, depth string) (CaseVerdict, error) { for i := range in.Nodes { in.Nodes[i].Kept = nil if kept := c.Kept[in.Nodes[i].Name]; len(kept) > 0 { - k, err := parse(kept, nil, r.Predefined) + k, err := parse(kept, nil, nil) if err != nil { return CaseVerdict{}, err } diff --git a/go/composer/composer_test.go b/go/composer/composer_test.go index 6968c2c..9570894 100644 --- a/go/composer/composer_test.go +++ b/go/composer/composer_test.go @@ -120,20 +120,17 @@ func TestARequestItCannotRunIsRefused(t *testing.T) { } } -// A manifest whose config value falls back to a predefined key (novox/hq ADR 0348 §7) is read, not refused for the -// list of predefined keys at the catalogue's root that a run is never handed with its manifests: the merge gate judges -// the fallback in the catalogue. xterm's module.json from mesh-catalog at 835bf22b falls back to "font-size"; a group -// touching xterm or pacman failed its composition on this (group check 18df81380db3). A list in the request is the one -// judged against, so a fallback it does not name is still refused. -func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) { +// The composer judges no manifest (novox/hq ADR 0578 §5): xterm's module.json from mesh-catalog at 835bf22b, whose +// config value falls back to "font-size", is read whatever list of predefined keys a request carries (a group touching +// xterm failed its composition on this, group check 18df81380db3); a module whose verdict in the input refuses it is +// held, composed as its stub, and named in the verdict's words. +func TestTheComposerTakesTheVerdictsOfItsInput(t *testing.T) { raw, err := os.ReadFile("../catalogue/testdata/catalogue-835bf22b-xterm.module.json") if err != nil { t.Fatal(err) } - if !strings.Contains(string(raw), `"predefined"`) { - t.Fatal("the fixture names no predefined key") - } - r := Request{V: Version, Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"xterm": {Raw: raw}}}}} + r := Request{V: Version, Predefined: catalogue.PredefinedList{"something-else": {Type: "string", Description: "x"}}, + Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"xterm": {Raw: raw}}}}} v, err := Run(r) if err != nil { t.Fatal(err) @@ -141,11 +138,17 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) { if got := v.Cases[0].Manifests["xterm"]; !got.Accepted { t.Fatalf("xterm: %+v, want accepted", got) } - r.Predefined = catalogue.PredefinedList{"something-else": {Type: "string", Description: "x"}} + r.Cases[0].Verdicts = map[string]delivery.ManifestVerdict{"xterm": {Module: "xterm", State: delivery.VerdictRefused, + Problems: []string{"falls back to a key not listed"}, Judge: "src2:j"}} + shelf, err := parse(r.Cases[0].Manifests, map[string]ManifestRead{}, r.Cases[0].Verdicts) + if err != nil || !shelf["xterm"].Stubbed { + t.Fatalf("the refused module is not held as its stub: %+v %v", shelf["xterm"], err) + } if v, err = Run(r); err != nil { t.Fatal(err) } - if got := v.Cases[0].Manifests["xterm"]; got.Accepted || !strings.Contains(got.Refused, "not on the list of predefined keys") { - t.Fatalf("xterm judged against a list without its key: %+v, want refused naming the list", got) + if got := v.Cases[0].Manifests["xterm"]; got.Accepted || + !strings.Contains(got.Refused, "the indexer's judge (src2:j) refused the manifest of xterm: falls back to a key not listed") { + t.Fatalf("xterm refused by its verdict: %+v, want refused in the verdict's words", got) } }