// Package manifests reads every module's manifest as the catalogue's tree holds it and refuses what no judge could // read: a module.json that does not decode, a module.json giving one field twice, and two manifests that name one module. // // It judges no manifest (novox/hq ADR 0578 ยง2): the checker judges every change's manifests with the indexer's judge, the // one judge, so the field, seat, bus and catalogue rules are said once, there. Until 2026-11 this check parsed each // manifest with mesh-sdk's strict parser and ran the rules between manifests (issue 774, power's bar item of // mesh-catalog ca8d6f33); that judgement moved to the checker, and what stays is the two refusals that are about the // tree, not a manifest's rules. package manifests import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "sort" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // Problems is what is wrong with the tree's manifests under root/modules, one line each: a module.json whose bytes are // no manifest (catalogue.Decode: not JSON, or a field of the wrong shape), one giving a field twice (a decoder keeps the // last and drops the first without a word: a merge of two branches that each added the field writes it so), then a module // named by two manifests. func Problems(root string) ([]string, error) { files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(files) var out []string named := map[string]bool{} for _, f := range files { raw, err := os.ReadFile(f) if err != nil { return nil, err } rel, _ := filepath.Rel(root, f) m, err := catalogue.Decode(raw) if err != nil { out = append(out, fmt.Sprintf("%s: %v", rel, err)) continue } if twice := givenTwice(raw); twice != "" { out = append(out, fmt.Sprintf("%s: the field %s is given twice; a judge reads only the last", rel, twice)) continue } if named[m.Module] { out = append(out, fmt.Sprintf("%s: %s was already given; two manifests name one module", rel, m.Module)) continue } named[m.Module] = true } return out, nil } // givenTwice is the first field an object of raw gives twice, as its path (`invokes`, `build.artifacts[0].env`), or empty. func givenTwice(raw []byte) string { d := json.NewDecoder(bytes.NewReader(raw)) var walk func(path string) string walk = func(path string) string { t, err := d.Token() if err != nil { return "" } switch t { case json.Delim('{'): seen := map[string]bool{} for d.More() { k, err := d.Token() if err != nil { return "" } key, _ := k.(string) at := key if path != "" { at = path + "." + key } if seen[key] { return at } seen[key] = true if twice := walk(at); twice != "" { return twice } } _, _ = d.Token() case json.Delim('['): for i := 0; d.More(); i++ { if twice := walk(fmt.Sprintf("%s[%d]", path, i)); twice != "" { return twice } } _, _ = d.Token() } return "" } return walk("") }