Let the composer take each manifest's verdict from its input instead of judging (ADR 0578 §5)
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
This commit is contained in:
@@ -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 != "" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-30
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user