A decoder keeps the last of two equal keys without a word, so a merge of two branches that each add a field to a manifest loses one side silently; the indexer's judge reads the same bytes. Rows: a top-level field and a field inside an artifact, each named by its path. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
// 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("")
|
|
}
|