Merge pull request 'Put the indexer's old seatfiles module back for now, so the running indexer can take in mesh-delivery's merges (issue 1966)' (#167) from fix/1966-bootstrap-seatfiles into main

Reviewed-on: #167
This commit was merged in pull request #167.
This commit is contained in:
2026-11-07 19:06:01 +00:00
3 changed files with 188 additions and 0 deletions
@@ -0,0 +1,5 @@
module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/seatfiles
go 1.26.0
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659
@@ -0,0 +1,2 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659 h1:tK7MzxVm/ECNg1Yblhh1ynVEthbZCg2mSPW0wQZC0b0=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
@@ -0,0 +1,181 @@
// Package seatfiles is the indexer's one reading of the seat definitions a revision carries (novox/hq ADR 0633 §1, §6;
// ADR 0635, ADR 0636 §4): a manifest is judged against the seats of the revision it belongs to, never against the list
// the judge was built with. A definition is the contracts module's `seats/<seat>/seat.json` (design 26 *A seat's
// definition*), with the parts it includes from `parts/<part>.json`. This reader takes of it only what a manifest is
// judged by (the seat's scope, what it delivers, its verbs and events) and reads the rest leniently: the format's one
// strict reader (mesh-sdk seatdef) is not in the mesh-sdk the judge is built with, and the indexer of the revision keeps
// the definitions whole.
//
// It is the indexer's own package, a module of its own so that the checker's rules can require it (the lead's ruling on
// milestone 47/B0, ADR 0634 §1: shared code lives with its owner): the indexer's judge and the checker's seat list read
// definitions here and nowhere else. Milestone G's domain SDK reading replaces it.
package seatfiles
import (
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Definition is the part of one seat's definition a manifest is judged by.
type Definition struct {
Format int `json:"format"`
Seat string `json:"seat"`
Scope string `json:"scope"`
Decision string `json:"decision"`
HolderDeclares bool `json:"holder-declares"`
Delivers string `json:"delivers"`
Replicated bool `json:"replicated"`
PulledByKind bool `json:"pulled-by-kind"`
Yields bool `json:"yields"`
HeldByTheEngine bool `json:"held-by-the-engine"`
Includes []string `json:"includes"`
Verbs []DefVerb `json:"verbs"`
Accepts []DefName `json:"accepts"`
Events []DefName `json:"events"`
}
// DefVerb is a verb of a definition.
type DefVerb struct {
Name string `json:"name"`
Description string `json:"description"`
Optional bool `json:"optional"`
Replaces []string `json:"replaces"`
}
// DefName is an accepted message or an event of a definition.
type DefName struct {
Name string `json:"name"`
}
// Part is a part a definition includes: verbs and events written once for the seats that share them.
type Part struct {
Part string `json:"part"`
Verbs []DefVerb `json:"verbs"`
Events []DefName `json:"events"`
}
// ParseDefinition reads one definition; a file that is not one names a seat and a scope.
func ParseDefinition(raw []byte) (Definition, error) {
var d Definition
if err := json.Unmarshal(raw, &d); err != nil {
return Definition{}, fmt.Errorf("a seat definition is not JSON: %w", err)
}
if d.Seat == "" || (d.Scope != catalogue.ScopeMesh && d.Scope != catalogue.ScopeNode) {
return Definition{}, fmt.Errorf("a seat definition names its seat and its scope (mesh or node), not %q and %q", d.Seat, d.Scope)
}
return d, nil
}
// ParsePart reads one part.
func ParsePart(raw []byte) (Part, error) {
var p Part
if err := json.Unmarshal(raw, &p); err != nil {
return Part{}, fmt.Errorf("a part of a seat definition is not JSON: %w", err)
}
return p, nil
}
// Resolve is the definition with the verbs and events of the parts it includes put first, as the indexer keeps it.
func (d Definition) Resolve(parts map[string]Part) (Definition, error) {
out := d
out.Includes, out.Verbs, out.Events = nil, nil, nil
for _, name := range d.Includes {
p, ok := parts[name]
if !ok {
return Definition{}, fmt.Errorf("the seat %s includes the part %q, which is not given", d.Seat, name)
}
out.Verbs = append(out.Verbs, p.Verbs...)
out.Events = append(out.Events, p.Events...)
}
out.Verbs = append(out.Verbs, d.Verbs...)
out.Events = append(out.Events, d.Events...)
return out, nil
}
// Seats are the seats of resolved definitions, in the catalogue's types; a seat its holder declares itself is left out.
// A definition still naming parts it includes is refused: resolve it first.
func Seats(defs []Definition) ([]catalogue.Seat, error) {
var out []catalogue.Seat
for _, d := range defs {
if d.HolderDeclares {
continue
}
if len(d.Includes) > 0 {
return nil, fmt.Errorf("the seat %s names parts it includes: resolve it first", d.Seat)
}
s := catalogue.Seat{Name: d.Seat, Scope: d.Scope, Delivers: d.Delivers, Replicated: d.Replicated,
PulledByKind: d.PulledByKind, Yields: d.Yields, HeldByTheEngine: d.HeldByTheEngine, Decision: d.Decision}
for _, a := range d.Accepts {
s.Accepts = append(s.Accepts, a.Name)
}
for _, e := range d.Events {
s.Emits = append(s.Emits, e.Name)
}
for _, v := range d.Verbs {
s.Serves = append(s.Serves, catalogue.Verb{Name: v.Name, Description: v.Description, Optional: v.Optional, Replaces: v.Replaces})
}
out = append(out, s)
}
return out, nil
}
// SeatsOfFiles are the seats of resolved definitions given as files, as a request carries them.
func SeatsOfFiles(raws [][]byte) ([]catalogue.Seat, error) {
defs := make([]Definition, 0, len(raws))
for _, raw := range raws {
d, err := ParseDefinition(raw)
if err != nil {
return nil, err
}
defs = append(defs, d)
}
return Seats(defs)
}
// ResolveRaw is the definition raw resolved with the parts it includes, each read by part, every field it carries kept as
// written: the verbs and events of its parts put first, then its own, and no includes. It reads raw and every part as
// ParseDefinition and ParsePart do, and answers the definition read with it. It is how a request's `seats` carry a
// definition whole (ADR 0636 §4).
func ResolveRaw(raw []byte, part func(name string) ([]byte, error)) (Definition, json.RawMessage, error) {
d, err := ParseDefinition(raw)
if err != nil {
return Definition{}, nil, err
}
var whole map[string]json.RawMessage
if err := json.Unmarshal(raw, &whole); err != nil {
return Definition{}, nil, err
}
type listed struct{ Verbs, Events []json.RawMessage }
var verbs, events []json.RawMessage
for _, name := range d.Includes {
praw, err := part(name)
if err != nil {
return Definition{}, nil, fmt.Errorf("the seat %s includes the part %q: %w", d.Seat, name, err)
}
var l listed
if _, err := ParsePart(praw); err != nil {
return Definition{}, nil, fmt.Errorf("the part %q of the seat %s: %w", name, d.Seat, err)
}
_ = json.Unmarshal(praw, &l)
verbs, events = append(verbs, l.Verbs...), append(events, l.Events...)
}
var own listed
_ = json.Unmarshal(raw, &own)
verbs, events = append(verbs, own.Verbs...), append(events, own.Events...)
delete(whole, "includes")
for k, list := range map[string][]json.RawMessage{"verbs": verbs, "events": events} {
if len(list) == 0 {
delete(whole, k)
continue
}
b, err := json.Marshal(list)
if err != nil {
return Definition{}, nil, err
}
whole[k] = b
}
out, err := json.Marshal(whole)
return d, out, err
}