Checker: the manifests of a change are judged against the seat list its reader holds (the request's Seats; a list not read fails the check), the module graph follows it, the manifest invokes seat-definitions and consumes its two events; composer from the delivery sdk (D-29; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -15,8 +15,8 @@ import (
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
|
||||
@@ -395,6 +395,13 @@ func startBed(t *testing.T) *bed {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The seat mesh-catalog's holder answers the seat list with no trunk seat named: the checker's reader has then heard the seat
|
||||
// and holds the bootstrapped definitions, which the judge is handed (ADR 0636 §1).
|
||||
for _, subject := range []string{"mesh.seat.mesh-catalog.call.seat-definitions", "mesh.seat.mesh-catalog.call.seat-definitions.>"} {
|
||||
if _, err := ctl.Subscribe(subject, func(m *nats.Msg) { _ = m.Respond([]byte(`{"result":{"revision":1,"seats":[]},"node":"ctl"}`)) }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
b := &bed{ctx: ctx, js: js, buckets: map[string]jetstream.KeyValue{}, tools: map[string]jetstream.KeyValue{}, srvURL: srv.ClientURL()}
|
||||
|
||||
@@ -28,6 +28,8 @@ type judgeDeps struct {
|
||||
asks check.Asks
|
||||
renames check.Renames
|
||||
seatDefs check.SeatDefinitions
|
||||
// seats are the seat definitions the manifests are judged against (seatlist.go).
|
||||
seats check.SeatList
|
||||
tools *buildrun.ToolchainSet
|
||||
trunks *check.Trunks
|
||||
kept check.KV
|
||||
@@ -220,7 +222,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, KnownPackages: 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}
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, KnownPackages: 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, SeatList: d.seats, 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}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
"mesh-delivery-checker/internal/check"
|
||||
@@ -146,6 +147,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// published, started once both are registered.
|
||||
hub := process.NewEventHub(logger.Printf)
|
||||
reader := &views.ProcessReader{Module: delivery.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub}
|
||||
seats := seatList{reader: followSeatList(seatlist.ProcessBus(caller.Call, hub), logger.Printf)}
|
||||
findings := process.AwaitBucket(ctx, js, conn, findingsBucket, "a first view unanswered past its bound is said in the log alone", logger.Printf)
|
||||
reader.Findings = func() views.FindingWriter { return sdkfindings.Writer(ctx, findings.KV) }
|
||||
// Each by the owner's read tool and views family as the owner's manifest names them.
|
||||
@@ -263,12 +265,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// not there yet a group's check asks and waits in vain until its bound, said in its verdict.
|
||||
tags := tagAsks{js: js, node: cred.Node, heard: &check.PublishedHeard{}, logf: logger.Printf, now: time.Now}
|
||||
ch.Says = tags // a pull request's verdict event, on the same stream asks (design 26 *Out of step*)
|
||||
if err := hub.Handle("mesh.seat.*.event.>", func(m jetstream.Msg) {
|
||||
if !tags.heardPublished(m) {
|
||||
_ = m.Ack() // nothing else the checker consumes rides the shared stream
|
||||
for subject := range publishedSeats {
|
||||
if err := hub.Handle(subject, func(m jetstream.Msg) {
|
||||
if !tags.heardPublished(m) {
|
||||
_ = m.Ack() // nothing else the checker consumes rides the shared stream
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
ch.Judge = newJudge(judgeDeps{
|
||||
asks: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}, tools: tools, trunks: trunks,
|
||||
@@ -279,6 +283,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
predefined: keptPredefined{predefined},
|
||||
renames: seatRenames{call: caller.Call},
|
||||
seatDefs: seatDefinitionsOf(seatDefs),
|
||||
seats: seats,
|
||||
})
|
||||
ch.Announce = announce
|
||||
tryer.Judge = ch.Judge
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
)
|
||||
|
||||
// The checker reads the seat list as every part does (novox/hq ADR 0635 §1, ADR 0636): through the seat mesh-catalog's verb
|
||||
// seat-definitions, its two events heard as handlers of the module's event hub, from the bootstrapped definitions until the seat
|
||||
// has answered. What the reader holds is given to catalogue at every change, which the module graph is worked out with, and to the
|
||||
// indexer's judge as a request's Seats, which the manifests of a change are judged against.
|
||||
|
||||
// followSeatList starts the checker's reader of the seat list over the bus; the hub is started by the caller once every handler is registered.
|
||||
func followSeatList(bus seatlist.Bus, logf func(string, ...any)) *seatlist.Reader {
|
||||
reader, err := seatlist.Start(seatlist.Config{Bus: bus, Bootstrap: seatlist.Bootstrapped(),
|
||||
OnChange: func(seats []catalogue.Seat) { catalogue.UseSeats(seats) }})
|
||||
if err != nil {
|
||||
logf("the checker cannot follow its seat list, so no manifest is judged until it starts again: %v", err)
|
||||
}
|
||||
if reader != nil {
|
||||
catalogue.UseSeats(reader.Seats())
|
||||
}
|
||||
return reader
|
||||
}
|
||||
|
||||
// seatList is the reader as the check's SeatList: the held definitions raw, and an error while the seat has not answered.
|
||||
type seatList struct{ reader *seatlist.Reader }
|
||||
|
||||
func (s seatList) Definitions() ([]json.RawMessage, error) {
|
||||
if s.reader == nil {
|
||||
return nil, fmt.Errorf("the checker follows no seat list")
|
||||
}
|
||||
if st := s.reader.Status(); !st.Heard {
|
||||
return nil, fmt.Errorf("%s", st.Health())
|
||||
}
|
||||
defs := s.reader.Definitions()
|
||||
var out []json.RawMessage
|
||||
for _, seat := range slices.Sorted(maps.Keys(defs)) {
|
||||
raw, err := json.Marshal(defs[seat])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the definition of %s cannot be encoded: %w", seat, err)
|
||||
}
|
||||
out = append(out, raw)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -205,10 +205,14 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s
|
||||
}
|
||||
}
|
||||
}
|
||||
verdicts := map[string]delivery.ManifestVerdict{}
|
||||
verdicts := map[string]json.RawMessage{}
|
||||
if e.Verdicts != nil {
|
||||
for name, v := range e.Verdicts() {
|
||||
verdicts[name] = v
|
||||
raw, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return a, fmt.Errorf("the verdict of the manifest of %s cannot be encoded: %w", name, err)
|
||||
}
|
||||
verdicts[name] = raw
|
||||
}
|
||||
}
|
||||
for _, p := range c.Pulls {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -1054,7 +1054,13 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
// trunk's, in a second ask (ADR 0633 clause 6, 1585/T22, T23)
|
||||
indexer string
|
||||
ownJudge bool
|
||||
// seats is the seat list the checker holds: "read" gives the judge its two definitions as the request's Seats, "unread"
|
||||
// is a reader the seat has not answered, and the manifests are then judged by nothing (ADR 0636 §1).
|
||||
seats string
|
||||
}{
|
||||
{name: "the seat list read is the request's Seats", seats: "read"},
|
||||
{name: "the seat list not read judges nothing", seats: "unread",
|
||||
fails: "the change's manifests cannot be judged: the seat list is not read (the seat list is held from the bootstrap"},
|
||||
{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",
|
||||
@@ -1162,6 +1168,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
b.ex.Read = fixedManifests{strings.Repeat("a", 64): {Module: "mail"}, strings.Repeat("b", 64): {Module: "web"},
|
||||
web: {Module: "web", Claims: []catalogue.Claim{{Name: CatalogSeat}}}}
|
||||
}
|
||||
if row.seats != "" {
|
||||
b.ex.SeatList = fakeSeatList{read: row.seats == "read"}
|
||||
}
|
||||
var judged []delivery.ComposeJob
|
||||
answer := b.asks.Answer
|
||||
b.asks.Answer = func(a delivery.StepAsk) delivery.Step {
|
||||
@@ -1207,6 +1216,12 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
}) {
|
||||
t.Errorf("%s: the case says %v, want the new indexer's manifest said accepted by the trunk's manifest validator", row.name, cs.notes)
|
||||
}
|
||||
if row.seats == "unread" {
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) || len(judged) != 0 {
|
||||
t.Errorf("%s: %+v, judged %d, want a fail saying %q before any judge", row.name, v.Failed, len(judged), row.fails)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if row.ownJudge && (len(judged) != 2 || judged[0].Fetch["commit"] != head1 || judged[1].Fetch["commit"] != trunk) {
|
||||
t.Fatalf("%s: judged %+v, want the head's judge and then the trunk's", row.name, judged)
|
||||
}
|
||||
@@ -1224,6 +1239,12 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
req.Cases[0].Manifests["mail"].Digest != strings.Repeat("a", 64) {
|
||||
t.Fatalf("%s: the request %+v: %v", row.name, req, err)
|
||||
}
|
||||
if row.seats == "read" && (len(req.Seats) != 2 || string(req.Seats[0]) != `{"seat":"a"}` || string(req.Seats[1]) != `{"seat":"b"}`) {
|
||||
t.Errorf("%s: the request's seats %s, want the two definitions the reader holds", row.name, req.Seats)
|
||||
}
|
||||
if row.seats == "" && len(req.Seats) != 0 {
|
||||
t.Errorf("%s: the request's seats %s, want none from a checker that follows no list", row.name, req.Seats)
|
||||
}
|
||||
if !reflect.DeepEqual(req.Packages, row.known) || len(req.Input) != 0 && string(req.Input) != "null" {
|
||||
t.Errorf("%s: the judge's known packages %v and input %s, want the known packages %v and no input", row.name,
|
||||
req.Packages, req.Input, row.known)
|
||||
@@ -1242,6 +1263,16 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// fakeSeatList is the seat list reader as a test gives it: two definitions once read, else the reader's words.
|
||||
type fakeSeatList struct{ read bool }
|
||||
|
||||
func (f fakeSeatList) Definitions() ([]json.RawMessage, error) {
|
||||
if !f.read {
|
||||
return nil, errors.New("the seat list is held from the bootstrap (2 seats) and nothing new is acted on")
|
||||
}
|
||||
return []json.RawMessage{json.RawMessage(`{"seat":"a"}`), json.RawMessage(`{"seat":"b"}`)}, nil
|
||||
}
|
||||
|
||||
// knownNames are the packages state's names as a test gives them, each with the <module>/<artifact> it is built from.
|
||||
type knownNames map[string]string
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
|
||||
@@ -137,6 +137,9 @@ type Examiner struct {
|
||||
Manifests TrunkManifests
|
||||
// SeatDefinitions reads the indexer's seat definitions of a pull request (seatdefs.go, design 26); nil reads none.
|
||||
SeatDefinitions SeatDefinitions
|
||||
// SeatList gives the seat definitions the change's manifests are judged against, as the seat list reader holds them
|
||||
// (manifests_judge.go, novox/hq ADR 0636 §1); nil gives the judge none.
|
||||
SeatList SeatList
|
||||
// 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
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/packagename"
|
||||
@@ -63,6 +63,13 @@ type TrunkManifests interface {
|
||||
ManifestDigests() map[string]string
|
||||
}
|
||||
|
||||
// SeatList gives the seat definitions the manifests are judged against, whole, as raw definitions (a request's Seats): the
|
||||
// list the seat list reader holds. It is an error when the list has not been read from the seat yet, since a manifest judged
|
||||
// against the bootstrapped seats alone would be refused for a seat it names that the mesh defines (novox/hq ADR 0636 §1).
|
||||
type SeatList interface {
|
||||
Definitions() ([]json.RawMessage, error)
|
||||
}
|
||||
|
||||
// PredefinedList is the catalogue's list of predefined keys the indexer keeps (its state predefined, key list), the one a
|
||||
// config value falling back to a key is judged against (ADR 0348 §7); nil when it keeps none.
|
||||
type PredefinedList interface {
|
||||
@@ -124,6 +131,13 @@ func (st *state) judgeManifests(ctx context.Context) *delivery.Verdict {
|
||||
st.c.note("the manifests are judged against the predefined keys of " + from)
|
||||
}
|
||||
req.Packages = st.knownPackages(ctx)
|
||||
if st.j.SeatList != nil {
|
||||
seats, err := st.j.SeatList.Definitions()
|
||||
if err != nil {
|
||||
return fail(goModule, "the change's manifests cannot be judged: the seat list is not read ("+err.Error()+")")
|
||||
}
|
||||
req.Seats = seats
|
||||
}
|
||||
cv, trunkCase, v := st.runJudge(ctx, req, touched, commit, at)
|
||||
if v != nil {
|
||||
return v
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
"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:mesh-hand-acts.hand-act", "seat:node-engine.compose-candidate"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
||||
"seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "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)
|
||||
}
|
||||
if want := []string{"node-build-go", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
||||
@@ -133,7 +133,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
||||
// The checker is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it starts.
|
||||
// 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-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "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", "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)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
],
|
||||
"consumes": [
|
||||
"go-package-registry.published",
|
||||
"mesh-catalog.seat-definition-changed",
|
||||
"mesh-catalog.seat-definition-removed",
|
||||
"mesh-build-go.toolchain.reported",
|
||||
"mesh-build-node.toolchain.reported",
|
||||
"mesh-delivery-assigner.views.placement",
|
||||
@@ -81,6 +83,7 @@
|
||||
"mesh-delivery-indexer.indexer_read_packages",
|
||||
"mesh-delivery-indexer.indexer_read_predefined",
|
||||
"mesh-delivery-indexer.indexer_read_seat_definitions",
|
||||
"seat:mesh-catalog.seat-definitions",
|
||||
"seat:mesh-config.renames",
|
||||
"seat:mesh-hand-acts.hand-act",
|
||||
"seat:node-engine.compose-candidate"
|
||||
|
||||
Reference in New Issue
Block a user