The take-in's dry run reads the contracts modules' definitions of the revision's merges and puts them over the seats while it judges; cmd/mesh-judge puts the request's seats over its own. A refusal of the named indexer's manifest is said on take-in.pending and in indexer_pending. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
155 lines
4.8 KiB
Go
155 lines
4.8 KiB
Go
package index
|
|
|
|
// The seat definitions a revision carries (novox/hq ADR 0633 §1, ADR 0635 §3, ADR 0636 §4): when a take-in works out which
|
|
// indexer a revision names, the manifests it writes are judged against the seats of that revision, never against the
|
|
// list this program was built with, since the indexer the revision names may serve or invoke what the revision's
|
|
// definitions add (a verb of a seat, an event). They are read from the merges' trees: each contracts module a merge's
|
|
// manifests name (a package artifact with `generated-from`) holds `seats/<seat>/seat.json` and the `parts/` they
|
|
// include, read through the forge's listing and get-file tools. A delivery group's merges are one revision, so a
|
|
// group's members' definitions are read together.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
"mesh-delivery-indexer/judge"
|
|
)
|
|
|
|
// definitionsDir is the directory of a contracts module's definitions that the module.json at dir names, from the first
|
|
// package artifact naming `generated-from`: ok is false for a module that is no contracts module. A manifest that does
|
|
// not decode is none here: the judge says why it is refused.
|
|
func definitionsDir(dir, manifest string) (string, bool) {
|
|
var m struct {
|
|
Build struct {
|
|
Artifacts []struct {
|
|
GeneratedFrom string `json:"generated-from"`
|
|
} `json:"artifacts"`
|
|
} `json:"build"`
|
|
}
|
|
if json.Unmarshal([]byte(manifest), &m) != nil {
|
|
return "", false
|
|
}
|
|
for _, a := range m.Build.Artifacts {
|
|
if a.GeneratedFrom == "" {
|
|
continue
|
|
}
|
|
base := dir
|
|
if base == "." || base == "/" {
|
|
base = ""
|
|
}
|
|
return path.Clean(path.Join(base, a.GeneratedFrom)), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// revisionSeats are the seats of the definitions the ready merges carry, read at their merge commits, the newest merge
|
|
// of a repository governing; none where the indexer has no forge reader. A read the forge does not answer is an error
|
|
// (the take-in is tried again); a definition that does not parse is said and left out, as the indexer that keeps it
|
|
// would say it unparsed.
|
|
func (ix *Indexer) revisionSeats(ctx context.Context, ready []PendingEntry) ([]catalogue.Seat, error) {
|
|
if ix.ReadFile == nil || ix.Files == nil {
|
|
return nil, nil
|
|
}
|
|
var defs []judge.Definition
|
|
for _, e := range ready {
|
|
if e.Covered || e.Forge.Merge == "" {
|
|
continue
|
|
}
|
|
for _, dir := range slices.Sorted(maps.Keys(e.Forge.Manifests)) {
|
|
defsDir, ok := definitionsDir(dir, e.Forge.Manifests[dir])
|
|
if !ok {
|
|
continue
|
|
}
|
|
got, err := ix.readDefinitions(ctx, e.Forge.Repository, e.Forge.Merge, defsDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defs = append(defs, got...)
|
|
}
|
|
}
|
|
seats, err := judge.Seats(defs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return seats, nil
|
|
}
|
|
|
|
// readDefinitions reads the definitions under a contracts module's definitions directory at a commit, resolved with their
|
|
// parts.
|
|
func (ix *Indexer) readDefinitions(ctx context.Context, repository, commit, defsDir string) ([]judge.Definition, error) {
|
|
file := func(p string) ([]byte, error) {
|
|
if defsDir != "." && defsDir != "" {
|
|
p = defsDir + "/" + p
|
|
}
|
|
raw, err := ix.ReadFile(ctx, repository, commit, p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading %s of %s at %.12s: %w", p, repository, commit, err)
|
|
}
|
|
return raw, nil
|
|
}
|
|
dir := path.Join(defsDir, "seats")
|
|
if defsDir == "." || defsDir == "" {
|
|
dir = "seats"
|
|
}
|
|
listed, err := ix.Files(ctx, repository, commit, []string{dir})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing %s of %s at %.12s: %w", dir, repository, commit, err)
|
|
}
|
|
parts := map[string]judge.Part{}
|
|
var out []judge.Definition
|
|
for _, p := range slices.Sorted(maps.Keys(listed[dir])) {
|
|
seat, ok := strings.CutSuffix(p, "/seat.json")
|
|
if !ok || seat == "" || strings.Contains(seat, "/") {
|
|
continue
|
|
}
|
|
raw, err := file("seats/" + seat + "/seat.json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d, err := judge.ParseDefinition(raw)
|
|
if err == nil && d.Seat != seat {
|
|
err = fmt.Errorf("it names the seat %q, not %q", d.Seat, seat)
|
|
}
|
|
for _, name := range d.Includes {
|
|
if err != nil {
|
|
break
|
|
}
|
|
if _, have := parts[name]; have {
|
|
continue
|
|
}
|
|
praw, perr := file("parts/" + name + ".json")
|
|
if perr != nil {
|
|
return nil, perr
|
|
}
|
|
var part judge.Part
|
|
if praw == nil {
|
|
perr = errors.New("the commit holds no such part")
|
|
} else {
|
|
part, perr = judge.ParsePart(praw)
|
|
}
|
|
if perr != nil {
|
|
err = fmt.Errorf("its part %s: %w", name, perr)
|
|
break
|
|
}
|
|
parts[name] = part
|
|
}
|
|
if err == nil {
|
|
d, err = d.Resolve(parts)
|
|
}
|
|
if err != nil {
|
|
ix.logf("take-in: the seat definition seats/%s of %s at %.12s does not parse, so it is left out: %v", seat, repository, commit, err)
|
|
continue
|
|
}
|
|
out = append(out, d)
|
|
}
|
|
return out, nil
|
|
}
|