DLV-M2r1: the checker fails a pull request whose head's seat definition does not parse, naming the seat and where

The lead's ruling on A2i's finding 1 (design 26): the indexer keeps an unparsed definition of a pull
request's head as its entry of the state seat-definitions with `unparsed`, and cannot refuse the manifest.
The checker follows that state as its seventh first view (invokes indexer_read_seat_definitions, consumes
views.seat-definitions; checks start once it is answered too, as M2's six) and fails each pull request
with an entry holding `unparsed`, naming the seat, its module, its directory and the indexer's words; a
state that cannot be read fails it too. Rows: TestTheIndexersJudgeJudgesTheChangesManifests (fails
first without the refusal), the adapter's key match in TestThePublishedVersionsAreTheIndexersPackagesState
(fails first matching every key), the manifest's invokes and consumes; the integration bed serves the
state (without it every check waits and five tests fail).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 14:40:02 +01:00
parent 20269818c9
commit 5700edf955
9 changed files with 168 additions and 6 deletions
@@ -443,7 +443,7 @@ func startBed(t *testing.T) *bed {
}
// The indexer and the assigner as the owners of the states the checker follows: their read tools and views families
// (ADR 0615, DLV-M2).
for _, st := range []string{candidatesState, modulesState, manifestsState, delivery.PackagesState, delivery.IndexerPredefinedState} {
for _, st := range []string{candidatesState, modulesState, manifestsState, delivery.PackagesState, delivery.IndexerPredefinedState, seatDefinitionsState} {
firstviewtest.Serve(t, ctx, js, ctl, delivery.IndexerModule, st, "n1_"+delivery.CheckerModule)
}
firstviewtest.Serve(t, ctx, js, ctl, delivery.AssignerModule, delivery.PlacementState, "n1_"+delivery.CheckerModule)
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -26,6 +27,7 @@ import (
type judgeDeps struct {
asks check.Asks
renames check.Renames
seatDefs check.SeatDefinitions
tools *buildrun.ToolchainSet
trunks *check.Trunks
kept check.KV
@@ -69,6 +71,68 @@ func (k keptPredefined) PredefinedList(ctx context.Context) (json.RawMessage, er
return kept.List, nil
}
// keptSeatDefinitions reads a pull request's entries of the indexer's seat definitions from its first view: each whose
// `unparsed` says why its head's definition does not parse (the entry's shape is the indexer's read tool's answer).
type keptSeatDefinitions struct {
keys func(ctx context.Context) ([]string, error)
get func(ctx context.Context, key string) ([]byte, error)
}
// seatDefinitionsOf reads the state's picture (a key gone between the keys and its get is none).
func seatDefinitionsOf(kv jetstream.KeyValue) keptSeatDefinitions {
return keptSeatDefinitions{keys: func(ctx context.Context) ([]string, error) {
keys, err := kv.Keys(ctx)
if errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil
}
return keys, err
}, get: func(ctx context.Context, key string) ([]byte, error) {
e, err := kv.Get(ctx, key)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, nil
}
if err != nil {
return nil, err
}
return e.Value(), nil
}}
}
func (k keptSeatDefinitions) Unparsed(ctx context.Context, repository string, pull int) ([]check.UnparsedSeat, error) {
suffix := ".pull." + delivery.EscapeName(repository) + "." + strconv.Itoa(pull)
keys, err := k.keys(ctx)
if err != nil {
return nil, err
}
sort.Strings(keys)
var out []check.UnparsedSeat
for _, key := range keys {
if !strings.HasSuffix(key, suffix) {
continue
}
raw, err := k.get(ctx, key)
if err != nil {
return nil, err
}
if raw == nil {
continue
}
var v struct {
Seat string `json:"seat"`
Module string `json:"module"`
Directory string `json:"directory"`
Unparsed string `json:"unparsed"`
}
if err := json.Unmarshal(raw, &v); err != nil {
return nil, fmt.Errorf("%s is no seat definition entry: %w", key, err)
}
if v.Unparsed != "" {
out = append(out, check.UnparsedSeat{Seat: v.Seat, Module: v.Module, Directory: v.Directory, Why: v.Unparsed})
}
}
return out, nil
}
// nodeNames reads every node's name from the node-engine's have, one key `<node>.node` per node with a record (novox/hq
// ADR 0567 §12), at each ask: a node enrolled since is named in the next check.
type nodeNames struct{ kv jetstream.KeyValue }
@@ -156,7 +220,7 @@ func newJudge(d judgeDeps) check.Judge {
nodes = d.nodes
}
ex := &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools,
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, Forge: d.forge, Runs: d.repos, Builds: d.builds, BuildEnds: d.ends, BuildWait: d.buildWait, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Read: d.repos, Dirs: d.repos, Predefined: d.predefined, Tags: d.tags, Renames: d.renames, Logf: d.logf}
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, Forge: d.forge, Runs: d.repos, Builds: d.builds, BuildEnds: d.ends, BuildWait: d.buildWait, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Read: d.repos, Dirs: d.repos, Predefined: d.predefined, Tags: d.tags, Renames: d.renames, SeatDefinitions: d.seatDefs, Logf: d.logf}
if d.nodes != nil && d.builds != nil && d.repos != nil {
ex.Serving = runningServes{have: d.nodes, builds: d.builds, read: d.repos, logf: d.logf}
}
@@ -4,14 +4,18 @@ import (
"context"
"encoding/json"
"fmt"
"maps"
"os"
"reflect"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
"git.novox.be/novox/mesh-sdk/go/delivery"
"mesh-delivery-checker/internal/check"
)
type memManifests map[string][]byte
@@ -142,6 +146,20 @@ func TestThePublishedVersionsAreTheIndexersPackagesState(t *testing.T) {
if got := p.LatestPackages(); len(got) != 1 || got[sdk] == "" {
t.Errorf("after a delete: %v", got)
}
t.Run("a pull request's unparsed seat definitions are its entries of the indexer's seat-definitions", func(t *testing.T) {
state := map[string]string{
"web-api.trunk": `{"seat":"web-api","source":"trunk","digest":"d"}`,
"web-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"web-api","source":"pull","module":"web-contracts","directory":"go","unparsed":"seat.json: bad"}`,
"mail-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"mail-api","source":"pull","digest":"e"}`,
"web-api.pull." + delivery.EscapeName("novox/web") + ".17": `{"seat":"web-api","source":"pull","unparsed":"another pull request's"}`,
}
k := keptSeatDefinitions{keys: func(context.Context) ([]string, error) { return slices.Collect(maps.Keys(state)), nil },
get: func(_ context.Context, key string) ([]byte, error) { return []byte(state[key]), nil }}
got, err := k.Unparsed(context.Background(), "novox/web", 7)
if want := []check.UnparsedSeat{{Seat: "web-api", Module: "web-contracts", Directory: "go", Why: "seat.json: bad"}}; err != nil || !reflect.DeepEqual(got, want) {
t.Errorf("unparsed of novox/web#7 = %+v, %v; want %+v", got, err, want)
}
})
}
// A manifest whose config value falls back to a predefined key (ADR 0348 §7) is read, not logged as unreadable for the
@@ -74,8 +74,11 @@ var (
const (
candidatesState = "candidates"
modulesState = "modules"
manifestsState = "manifests"
// seatDefinitionsState is the indexer's state of the seat definitions (design 26), keyed `<seat>.trunk` and
// `<seat>.pull.<escaped repository>.<number>`.
seatDefinitionsState = "seat-definitions"
modulesState = "modules"
manifestsState = "manifests"
)
// run is the program: it returns when ctx ends or the bus cannot be used.
@@ -144,6 +147,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
packages := reader.Follow(delivery.IndexerModule, delivery.PackagesState)
predefined := reader.Follow(delivery.IndexerModule, delivery.IndexerPredefinedState)
placement := reader.Follow(delivery.AssignerModule, delivery.PlacementState)
// The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails.
seatDefs := reader.Follow(delivery.IndexerModule, seatDefinitionsState)
stepBound, err := durationEnv(getenv, "MESH_CHECKER_STEP_BOUND", check.DefaultStepBound)
if err != nil {
@@ -264,6 +269,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
nodes: openNodeNames(ctx, js, logger.Printf),
predefined: keptPredefined{predefined},
renames: seatRenames{call: caller.Call},
seatDefs: seatDefinitionsOf(seatDefs),
})
ch.Announce = announce
tryer.Judge = ch.Judge
@@ -1041,7 +1041,11 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
// renames is what the change does to web's renamed-from ("bring", "drop") and what the seat mesh-config's verb
// renames answers ("served", "open" on g14, "not served", "unasked" with no seat to ask, "error"); DLV-C5.
renames, seat string
// unparsed is the indexer's words of why the pull request's head's seat definition of web-api does not parse.
unparsed string
}{
{name: "a seat definition that does not parse", unparsed: "seats/web-api/seat.json: unexpected end of JSON input",
fails: "novox/mesh-delivery#7: the seat definition of web-api (module web-contracts, go) does not parse: seats/web-api/seat.json: unexpected end of JSON input"},
{name: "a rename brought, the verb not served", renames: "bring", seat: "not served",
fails: "web (novox/mesh-delivery#7) renames new-key from old-key: refused, the holder of the seat mesh-config does not serve its verb renames"},
{name: "a rename brought, the verb served", renames: "bring", seat: "served"},
@@ -1122,6 +1126,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
b.ex.Renames = fake
}
}
if row.unparsed != "" {
b.ex.SeatDefinitions = fakeSeatDefinitions{"novox/mesh-delivery#7": {{Seat: "web-api", Module: "web-contracts", Directory: "go", Why: row.unparsed}}}
}
var judged []delivery.ComposeJob
answer := b.asks.Answer
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
@@ -1178,3 +1185,10 @@ type fakeRenames struct {
func (f fakeRenames) Renames(context.Context, string) (meshconfig.RenamesAnswer, error) {
return f.answer, f.err
}
// fakeSeatDefinitions are the indexer's unparsed seat definitions by pull request (pullKey).
type fakeSeatDefinitions map[string][]UnparsedSeat
func (f fakeSeatDefinitions) Unparsed(_ context.Context, repository string, pull int) ([]UnparsedSeat, error) {
return f[fmt.Sprintf("%s#%d", repository, pull)], nil
}
@@ -128,6 +128,8 @@ type Examiner struct {
// Manifests are the indexer's manifest digests by module: the catalogue a change's manifests are judged in by the
// indexer's judge (manifests_judge.go, novox/hq ADR 0578 §2); nil judges no manifest.
Manifests TrunkManifests
// SeatDefinitions reads the indexer's seat definitions of a pull request (seatdefs.go, design 26); nil reads none.
SeatDefinitions SeatDefinitions
// Renames asks the seat mesh-config's verb renames for a change that brings or drops a `renamed-from` (renames.go,
// design 32); nil fails such a change, saying it cannot ask.
Renames Renames
@@ -273,7 +275,7 @@ func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) {
// 0578 §2), also after a failure: the verdict names every failure, not only the first (the operator, 2026-11-02).
tests := st.judgeAll(ctx)
st.seriesChanges(ctx) // DLV-C4: named in the verdict's line, whatever the verdict
if v := settled(joinFailures(tests, st.judgeManifests(ctx), st.refuseCycle(ctx), st.judgeRenames(ctx))); v != nil {
if v := settled(joinFailures(tests, st.judgeManifests(ctx), st.refuseCycle(ctx), st.judgeRenames(ctx), st.refuseUnparsed(ctx))); v != nil {
return st.finish(ctx, *v), nil
}
return st.finish(ctx, delivery.Verdict{State: delivery.VerdictPass}), nil
@@ -0,0 +1,55 @@
package check
import (
"context"
"fmt"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// A pull request whose head's seat definition does not parse fails its check, naming the seat and where it lives (design
// 26; the lead's ruling on A2i's finding 1): the indexer keeps such a definition as the pull request's entry of its state
// seat-definitions with `unparsed` set, and cannot refuse the manifest itself, whose digest the trunk may share.
// UnparsedSeat is one seat definition of a pull request's head that does not parse: the seat (the contracts module's name
// when its index did not parse), its module's directory and the indexer's words of why.
type UnparsedSeat struct {
Seat, Module, Directory, Why string
}
// SeatDefinitions reads the indexer's seat definitions of one pull request (its state seat-definitions, through its
// first view, DLV-M2).
type SeatDefinitions interface {
Unparsed(ctx context.Context, repository string, pull int) ([]UnparsedSeat, error)
}
// refuseUnparsed fails the check of each pull request with a seat definition that does not parse; a state that cannot be
// read fails it too, saying so.
func (st *state) refuseUnparsed(ctx context.Context) *delivery.Verdict {
if st.j.SeatDefinitions == nil || st.c.Try != nil {
return nil
}
var out *delivery.Verdict
failed := func(words string) {
f := delivery.FailedFirst{Machine: SeatDefinitionsMachine, Words: words}
if out == nil {
out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f}
return
}
addFailure(out, f)
}
for _, p := range st.c.Pulls {
bad, err := st.j.SeatDefinitions.Unparsed(ctx, p.Repository, p.Number)
if err != nil {
failed(fmt.Sprintf("%s: the indexer's seat definitions of it cannot be read, so none is known to parse: %v", pullKey(p), err))
continue
}
for _, u := range bad {
failed(fmt.Sprintf("%s: the seat definition of %s (module %s, %s) does not parse: %s", pullKey(p), u.Seat, u.Module, u.Directory, u.Why))
}
}
return out
}
// SeatDefinitionsMachine names what refused a seat definition in a verdict's failure.
const SeatDefinitionsMachine = "seat-definitions"
@@ -123,6 +123,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
if want := []string{"gitea.gitea_commits", "gitea.gitea_compare", "gitea.gitea_get_file", "gitea.gitea_list_branches",
"mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_read_predefined",
"mesh-delivery-indexer.indexer_read_seat_definitions",
"seat:mesh-config.renames", "seat:node-engine.compose-candidate"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
t.Errorf("invokes = %v, want the forge's reads and compose-candidate", m.Invokes)
}
@@ -133,7 +134,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
// And a group's check by the package registry seats' published of its tagged versions (design 51 *Tagged versions*).
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported",
"mesh-delivery-assigner.views.placement", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules",
"mesh-delivery-indexer.views.packages", "mesh-delivery-indexer.views.predefined", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
"mesh-delivery-indexer.views.packages", "mesh-delivery-indexer.views.predefined", "mesh-delivery-indexer.views.seat-definitions", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
t.Errorf("consumes = %v, want %v", m.Consumes, want)
}
if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" {
@@ -23,6 +23,7 @@
"mesh-delivery-indexer.views.modules",
"mesh-delivery-indexer.views.packages",
"mesh-delivery-indexer.views.predefined",
"mesh-delivery-indexer.views.seat-definitions",
"npm-package-registry.published"
],
"streams": [
@@ -79,6 +80,7 @@
"mesh-delivery-indexer.indexer_read_modules",
"mesh-delivery-indexer.indexer_read_packages",
"mesh-delivery-indexer.indexer_read_predefined",
"mesh-delivery-indexer.indexer_read_seat_definitions",
"seat:mesh-config.renames",
"seat:node-engine.compose-candidate"
],