Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/seatsof.go
T
jschoubben 31ff708f88 Judge a successor indexer's manifest with the seat definitions its revision carries, not the built-in list (issue 1966, ADR 0633 §1, §6)
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
2026-11-05 17:52:53 +01:00

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
}