diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index 027ef5c5..01bec1c4 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -115,7 +115,7 @@ func TestManifest(t *testing.T) { // Design 47 G §717: the teller and the status view take their first views of the seat mesh-wants' asks and wants, and of // the assignments, from the tools named for the states. callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker,mesh-delivery-view", - "seat-holders": "node-tools,mesh-delivery-builder,mesh-delivery-view,mesh-delivery-teller", "wants": "mesh-grants,mesh-delivery-view", "asks": "mesh-delivery-teller,mesh-delivery-view"} + "seat-holders": "node-tools,mesh-delivery-builder,mesh-delivery-view,mesh-delivery-teller,mesh-delivery-checker,mesh-delivery-indexer", "wants": "mesh-grants,mesh-delivery-view", "asks": "mesh-delivery-teller,mesh-delivery-view"} env := m.Build.Artifacts[0].Env for tool, list := range callers { if env[views.CallersEnv(tool)] != list || views.ParseCallers(env[views.CallersEnv(tool)]).Allows("n1."+m.Module) { diff --git a/modules/mesh-delivery-assigner/module.json b/modules/mesh-delivery-assigner/module.json index 42b8c50b..99c7955c 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -249,7 +249,7 @@ "env": { "MESH_VIEW_CALLERS_ASKS": "mesh-delivery-teller,mesh-delivery-view", "MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker,mesh-delivery-view", - "MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools,mesh-delivery-builder,mesh-delivery-view,mesh-delivery-teller", + "MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools,mesh-delivery-builder,mesh-delivery-view,mesh-delivery-teller,mesh-delivery-checker,mesh-delivery-indexer", "MESH_VIEW_CALLERS_WANTS": "mesh-grants,mesh-delivery-view" } } diff --git a/modules/mesh-delivery-builder/buildrun/assignerholder.go b/modules/mesh-delivery-builder/buildrun/assignerholder.go deleted file mode 100644 index 5c56f5b4..00000000 --- a/modules/mesh-delivery-builder/buildrun/assignerholder.go +++ /dev/null @@ -1,45 +0,0 @@ -package buildrun - -import ( - "context" - "time" - - mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-sdk/go/seatlist" -) - -// **No part names the assigner** (design 47 G A-119, A-126, A-131): the module holding the seat mesh-module-assigner is asked of -// the seat that places modules through mesh-sdk's seatlist.HolderOf, and the names of the view of its state assignments are made -// from it, so a new holder is followed with no change of code. The builder and the checker follow the assignments so. - -// AssignerHolder is the module holding the seat mesh-module-assigner now, asked until it answers or ctx ends. wait is the pause -// between two asks that failed; sleep says false when ctx ended during it. -func AssignerHolder(ctx context.Context, call seatlist.HolderCaller, wait time.Duration, sleep func(context.Context, time.Duration) bool, logf func(string, ...any)) (string, bool) { - for { - holder, err := seatlist.HolderOf(call, mma.Seat) - if err == nil { - return holder, true - } - logf("who holds the seat %s is not known yet, so no node is counted by the assignments: %v", mma.Seat, err) - if !sleep(ctx, wait) { - return "", false - } - } -} - -// AssignmentsNames are the names made from the holder: the view's first-view tool `.assignments` and the bucket -// `_assignments` of the state. -type AssignmentsNames struct{ Holder, Tool, Bucket string } - -// AssignmentsOf is the names of the holder's state assignments. -func AssignmentsOf(holder string) AssignmentsNames { - return AssignmentsNames{Holder: holder, Tool: holder + "." + mma.StateAssignments, Bucket: holder + "_" + mma.StateAssignments} -} - -// SleepFor waits d, a context's bound, and says whether ctx is still alive. -func SleepFor(ctx context.Context, d time.Duration) bool { - w, cancel := context.WithTimeout(ctx, d) - defer cancel() - <-w.Done() - return ctx.Err() == nil -} diff --git a/modules/mesh-delivery-builder/buildrun/assignerholder_test.go b/modules/mesh-delivery-builder/buildrun/assignerholder_test.go deleted file mode 100644 index 091340ed..00000000 --- a/modules/mesh-delivery-builder/buildrun/assignerholder_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package buildrun - -import ( - "context" - "encoding/json" - "errors" - "testing" - "time" - - "git.novox.be/novox/mesh-sdk/go/seatlist" -) - -// The builder and the checker find the assigner's holder at run time: asked again while the seat list does not answer, and the names of the view -// are made from the holder (design 47 G A-119), not fixed in code. -func TestTheAssignerHolderIsFoundAtRunTime(t *testing.T) { - asks, slept := 0, 0 - call := func(key string, _ any) (json.RawMessage, error) { - if key != seatlist.HolderKey() { - t.Errorf("asked %q, want %q", key, seatlist.HolderKey()) - } - if asks++; asks == 1 { - return nil, errors.New("down") - } - return json.RawMessage(`{"seats":[{"seat":"mesh-module-assigner","holders":[{"module":"acme-assigner"}]}]}`), nil - } - holder, ok := AssignerHolder(context.Background(), call, time.Second, func(context.Context, time.Duration) bool { slept++; return true }, func(string, ...any) {}) - if !ok || holder != "acme-assigner" || slept != 1 { - t.Fatalf("holder = %q, %v after %d pauses, want acme-assigner after one", holder, ok, slept) - } - if n := AssignmentsOf(holder); n.Tool != "acme-assigner.assignments" || n.Bucket != "acme-assigner_assignments" { - t.Errorf("names = %+v", n) - } - if _, ok := AssignerHolder(context.Background(), func(string, any) (json.RawMessage, error) { return nil, errors.New("down") }, time.Second, - func(context.Context, time.Duration) bool { return false }, func(string, ...any) {}); ok { - t.Error("a holder was found though ctx ended") - } -} diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go index ca9ba149..1bb200f4 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go @@ -283,15 +283,14 @@ func startBedWith(t *testing.T, closed bool) *bed { "git": "mesh-delivery-forge-bridge", "mesh-catalog": "mesh-delivery-indexer", // the floor's sources are the holders of git and mesh-catalog "mesh-builds": builds.BuilderModule}) // the builder's own seat, whose verb reserved it answers once its definition is read (A-115, A-278) readers := []string{"n1_" + builds.BuilderModule, "n2_" + builds.BuilderModule} - // The seat mesh-module-assigner's holder is read from the seat's verb assignments, as the builder finds it at run time (A-119). - assignerSub, err := ctl.Subscribe("mesh.seat.mesh-module-assigner.call.assignments.>", func(m *nats.Msg) { - reply, _ := json.Marshal(map[string]any{"result": map[string]any{"seats": []map[string]any{{"seat": "mesh-module-assigner", "holders": []map[string]string{{"module": testAssigner}}}}}, "node": "ctl"}) - _ = m.Respond(reply) - }) - if err != nil { + // Design 47 G §717, A-298: the catalog half names testAssigner the claimant of mesh-module-assigner, and its seat-holders view + // names it the seat's holder, so the builder follows the assignments at it; nothing answers the seat's verb assignments. + b.catalog.put(t, meshcatalog.CatalogEntry{Module: testAssigner, Repository: "novox/mesh-catalog", Commit: "c0", Fp: "src2:" + strings.Repeat("a", 64), + ManifestDigest: "sha256:" + strings.Repeat("b", 64), Manifest: map[string]any{"module": testAssigner, "claims": []any{map[string]any{"name": "mesh-module-assigner"}}}}) + seatHolders := viewstest.Serve(t, ctx, js, ctl, testAssigner, "seat-holders", testAssigner+".seat-holders", readers...) + if _, err := seatHolders.Put(ctx, "mesh-module-assigner", []byte(`{"seat":"mesh-module-assigner","holders":[{"module":"`+testAssigner+`","node":"n1"}]}`)); err != nil { t.Fatal(err) } - t.Cleanup(func() { _ = assignerSub.Unsubscribe() }) viewstest.Serve(t, ctx, js, ctl, testAssigner, "assignments", testAssigner+".assignments", readers...) b.builds = mk(builds.BuilderBuildsBucket, builds.BuilderBuildsHistory) b.instances = mk(instancesBucket, 1) @@ -990,6 +989,12 @@ func TestTheBuilderAsksTheBuildSeatsAndCallsNoNode(t *testing.T) { if n := calls.Load(); n != 0 { t.Errorf("%d tool calls reached a build module", n) } + // Design 47 G §717, A-298: the assignments are followed at the holder the seat-holders view names, no seat verb asked for it. + for deadline := time.Now().Add(10 * time.Second); !strings.Contains(b.out.String(), "holds "+testAssigner+"'s assignments, the holder of the seat mesh-module-assigner"); time.Sleep(10 * time.Millisecond) { + if time.Now().After(deadline) { + t.Fatalf("the builder did not follow the assignments at the seat's holder:\n%s", b.out.String()) + } + } // 1887/AC1: one ask per build module the build needs (netcheck: a Go and a TypeScript bundle), each on its seat's // delivery stream. got := asked() diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go index ed90d144..07e6ad1e 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go @@ -119,7 +119,9 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // The build modules are no names (design 47 G A-82): the holders of the build seats, the seat list's definitions and the // catalog half's claims, found at each use. seatGate := newSeatGate() - seatGate.Holders(branches.SeatHolders(ctx)) + // The part's one holder function (A-298): the seat list's callers and the views found by their seat (§717) read it. + holders := branches.SeatHolders(ctx) + seatGate.Holders(holders) seats := buildrun.NewBuildSeats(seatGate.definitions, versions.HolderOf(branches)) bound := defaultStepBound if v := strings.TrimSpace(getenv("MESH_BUILDER_STEP_BOUND")); v != "" { @@ -344,26 +346,21 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // asked when the bound ends), is call-unanswered in the builder's findings (issue 1966, ADR 0668 §3, ADR 0596 §2). reads.callsOf(found, pubBoundsKV.KV, heardBy(calls.CallKept), logger.Printf) b.Forms = reads.built - // The assignments names the nodes the builder counts, once the seat list names the seat's holder and its first view is answered: - // the view is the holder's first-view tool, followed by its views family (A-119, A-131); no assigner module is named. + // The assignments names the nodes the builder counts: the seat mesh-module-assigner's view, followed at the holder the + // seat-holders view names and as it moves (design 47 G §717), its first view the holder's tool named for the state; no + // assigner module is named and nothing asks for the holder on a timer. assignmentsErrs := make(chan error, 1) + assignments, err := reader.FollowSeat(mma.Seat, mma.StateAssignments, holders) + if err != nil { + return err + } go func() { - holder, ok := buildrun.AssignerHolder(ctx, calls.Call, 5*time.Second, buildrun.SleepFor, logger.Printf) - if !ok { - return - } - names := buildrun.AssignmentsOf(holder) - assignments, err := reader.Follow(names.Holder, mma.StateAssignments, names.Tool) - if err != nil { - assignmentsErrs <- err - return - } select { case <-ctx.Done(): return case <-assignments.Follower().Ready(): } - pe, err := buildrun.FollowAssignmentsIn(ctx, reader, names.Bucket, gate, b.NodesChanged, logger.Printf) + pe, err := buildrun.FollowAssignmentsIn(ctx, reader, assignments.Bucket(), gate, b.NodesChanged, logger.Printf) if err != nil { assignmentsErrs <- err return diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index 63cbc73b..20ee2d97 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -547,7 +547,11 @@ func startBed(t *testing.T) *bed { for repo, commit := range trunkView.Load().(map[string]string) { heads = append(heads, meshcatalog.Head{Repository: repo, Commit: commit}) } - body, _ := json.Marshal(meshcatalog.RevisionAnswer{Revision: &meshcatalog.CatalogRevision{Kind: "trunk", Revision: 1, Trunk: heads}}) + // Design 47 G §717, A-298: the trunk's entry of testAssigner claims mesh-module-assigner, so the catalog half names it + // the claimant whose seat-holders view the checker follows. + body, _ := json.Marshal(meshcatalog.RevisionAnswer{Revision: &meshcatalog.CatalogRevision{Kind: "trunk", Branch: "main", Revision: 1, Trunk: heads}, + Entries: []meshcatalog.CatalogEntry{{Module: testAssigner, ManifestDigest: "sha256:" + strings.Repeat("a", 64), + Manifest: map[string]any{"module": testAssigner, "claims": []any{map[string]any{"name": meshmoduleassigner.Seat}}}}}}) reply, _ := json.Marshal(map[string]any{"result": json.RawMessage(body), "node": "ctl"}) _ = m.Respond(reply) }); err != nil { @@ -615,15 +619,21 @@ func startBed(t *testing.T) *bed { for _, st := range []string{candidatesState, modulesState, manifestsState, meshcatalog.StatePackages, check.IndexerPredefinedState, seatDefinitionsState} { viewstest.Serve(t, ctx, js, ctl, check.IndexerModule, st, check.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+check.CheckerModule) } - // The seat mesh-module-assigner's holder is read from the seat's verb assignments, as the checker finds it at run time (A-119, A-126). - for _, subject := range []string{"mesh.seat.mesh-module-assigner.call.assignments", "mesh.seat.mesh-module-assigner.call.assignments.>"} { - if _, err := ctl.Subscribe(subject, func(m *nats.Msg) { - reply, _ := json.Marshal(map[string]any{"result": map[string]any{"seats": []map[string]any{{"seat": "mesh-module-assigner", "holders": []map[string]string{{"module": testAssigner}}}}}, "node": "ctl"}) - _ = m.Respond(reply) - }); err != nil { - t.Fatal(err) + // The catalog half's registrations and the gate's source branch (design 47 G §614), and the claimant's seat-holders view naming + // testAssigner the holder of mesh-module-assigner (§717): nothing answers the seat's verb assignments. + for subject, result := range map[string]any{"mesh.seat.mesh-catalog.call.registrations": meshcatalog.RegistrationsAnswer{Registrations: []meshcatalog.Registration{}}, + "mesh.seat.mesh-delivery.call.source": map[string]any{"branches": []string{"main"}}} { + reply, _ := json.Marshal(map[string]any{"result": result, "node": "ctl"}) + for _, s := range []string{subject, subject + ".>"} { + if _, err := ctl.Subscribe(s, func(m *nats.Msg) { _ = m.Respond(reply) }); err != nil { + t.Fatal(err) + } } } + seatHolders := viewstest.Serve(t, ctx, js, ctl, testAssigner, meshmoduleassigner.StateSeatHolders, testAssigner+"."+meshmoduleassigner.StateSeatHolders, "n1_"+check.CheckerModule) + if _, err := seatHolders.Put(ctx, meshmoduleassigner.Seat, []byte(`{"seat":"`+meshmoduleassigner.Seat+`","holders":[{"module":"`+testAssigner+`","node":"n1"}]}`)); err != nil { + t.Fatal(err) + } viewstest.Serve(t, ctx, js, ctl, testAssigner, meshmoduleassigner.StateAssignments, testAssigner+"."+meshmoduleassigner.StateAssignments, "n1_"+check.CheckerModule) // 1929/AC1: the indexer's value of the repository's module, with no TypeScript manifest, so the Go build module alone // judges the repository (an unknown repository is judged by both, issue 1887 A12). @@ -1281,6 +1291,9 @@ func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 !said.Keeps("asks") || said.Readable("asks") || !said.Keeps("first-views") || said.Readable("first-views") || !said.Keeps("findings") || !said.Keeps("inputs") || said.Readable("inputs") || !said.Keeps("compositions") || said.Readable("compositions") || !said.Keeps("could-not-run") || said.Readable("could-not-run") || len(said.States) != 12 { t.Fatalf("the checker said %+v, want its twelve states, asks, hearing, first-views, inputs, compositions and could-not-run its own (DLV-M2; W6.9), with kept, owed, previews, tries and verdicts readable, tries by the delivery view (1929/AC4; 1887 A5b; build_usage)", said) } + // Design 47 G §717, A-298: the assignments are followed at the holder the seat-holders view of the catalog half's claimant + // names, no seat verb asked for it. + b.said(t, "holds "+testAssigner+"'s assignments, the holder of the seat mesh-module-assigner") } const testAssigner = "acme-assigner" // a holder of the seat mesh-module-assigner no code names diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go index a9f662c1..ffb2a79d 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -30,6 +30,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-checker/checkermsg" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/announce" @@ -343,26 +344,26 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro watch.put = gate.Toolchain // Until the assignments's first view is answered the gate passes every report on, as before the assignments is settled. assignmentsErrs := make(chan error, 1) + // The assignments is the seat mesh-module-assigner's view, followed at the holder the seat-holders view names and as it + // moves (design 47 G §717): the holder function is the catalog half's claimant's seat-holders view (A-298), so no assigner + // module is named and nothing asks for the holder on a timer. + branches, err := meshcatalogclient.FollowBranchesProcess(reader, caller.Call) + if err != nil { + return err + } + branches.Logf = logger.Printf + pic, err := reader.FollowSeat(meshmoduleassigner.Seat, meshmoduleassigner.StateAssignments, branches.SeatHolders(ctx)) + if err != nil { + return err + } + assignments.Store(pic) go func() { - // The module holding the seat mesh-module-assigner is found at run time (design 47 G A-119, A-126, A-131): the view is its - // first-view tool, followed by its views family; no assigner module is named. - holder, ok := buildrun.AssignerHolder(ctx, caller.Call, 5*time.Second, buildrun.SleepFor, logger.Printf) - if !ok { - return - } - names := buildrun.AssignmentsOf(holder) - pic, err := reader.Follow(names.Holder, meshmoduleassigner.StateAssignments, names.Tool) - if err != nil { - assignmentsErrs <- err - return - } - assignments.Store(pic) select { case <-ctx.Done(): return case <-pic.Follower().Ready(): } - pe, err := buildrun.FollowAssignmentsIn(ctx, reader, names.Bucket, gate, ch.NodesChanged, logger.Printf) + pe, err := buildrun.FollowAssignmentsIn(ctx, reader, pic.Bucket(), gate, ch.NodesChanged, logger.Printf) if err != nil { assignmentsErrs <- err return diff --git a/modules/mesh-delivery-checker/manifest_test.go b/modules/mesh-delivery-checker/manifest_test.go index e883554b..3e255b1c 100644 --- a/modules/mesh-delivery-checker/manifest_test.go +++ b/modules/mesh-delivery-checker/manifest_test.go @@ -132,7 +132,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 if want := []string{"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:git.file", "seat:mesh-catalog.revision", "seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:node-build-go.drop", "seat:node-build-node.drop", "seat:node-engine.compose-candidate", + "seat:git.file", "seat:mesh-catalog.registrations", "seat:mesh-catalog.revision", "seat:mesh-catalog.seat-definitions", "seat:mesh-delivery.source", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:node-build-go.drop", "seat:node-build-node.drop", "seat:node-engine.compose-candidate", // The image build seat's drop too: a check of a module built by it is asked of it, and dropped of it (buildrun.SeatDrops). "seat:node-build-image.drop", // A-290, A-291: a step's answer its key keeps as an object, read through each build seat's verb output. @@ -146,8 +146,9 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 t.Errorf("uses = %v, want the Go and Node build seats", m.Uses) } // The checker is woken by a build seat's report of a toolchain, as the builder is: the bucket may not exist when it starts. - if want := []string{"mesh-catalog.catalog-revision", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-conditions.findings-asked", "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", "node-build-go.toolchain-reported", "node-build-node.toolchain-reported", "node-engine.running", "seat:mesh-module-assigner.views.assignments"}; !reflect.DeepEqual(sorted(m.Consumes), want) { + // Design 47 G §717, §614: the catalog half, whose claimant's seat-holders view names the holder of the assignments. + if want := []string{"mesh-catalog.catalog-revision", "mesh-catalog.registration-changed", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-conditions.findings-asked", "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", "mesh-delivery.source-moved", "node-build-go.toolchain-reported", "node-build-node.toolchain-reported", "node-engine.running", "seat:mesh-module-assigner.views.assignments", "seat:mesh-module-assigner.views.seat-holders"}; !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" { diff --git a/modules/mesh-delivery-checker/module.json b/modules/mesh-delivery-checker/module.json index adfd7767..b2e3e7e9 100644 --- a/modules/mesh-delivery-checker/module.json +++ b/modules/mesh-delivery-checker/module.json @@ -16,6 +16,9 @@ ], "consumes": [ "mesh-catalog.catalog-revision", + "mesh-catalog.registration-changed", + "mesh-delivery.source-moved", + "seat:mesh-module-assigner.views.seat-holders", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.candidates", @@ -89,6 +92,8 @@ "mesh-delivery-indexer.indexer_read_seat_definitions", "seat:git.file", "seat:mesh-catalog.revision", + "seat:mesh-catalog.registrations", + "seat:mesh-delivery.source", "seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/buildmodules.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/buildmodules.go index f26ab1d2..493b7891 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/buildmodules.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/buildmodules.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "maps" "slices" "sync" @@ -9,7 +10,10 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" + mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-sdk/go/seatdef" + "git.novox.be/novox/mesh-sdk/go/seatlist" + "git.novox.be/novox/mesh-sdk/go/views" "mesh-delivery-indexer/internal/index" ) @@ -29,10 +33,15 @@ type buildModulesFollow struct { hear func(stream, module string) // nil until the indexer's consumers are bound (hearWith) heard map[string]bool // streams heard watched map[string]bool // build modules whose toolchains watch was asked + // seatHolders is told at each refresh, once the reader is made (A-302). + seatHolders atomic.Pointer[seatHoldersFollow] } // refresh finds the build modules again and acts on what moved. func (f *buildModulesFollow) refresh() { + if h := f.seatHolders.Load(); h != nil { + h.refresh() + } by, err := f.ix.BuildModulesNow(f.ctx, f.defs()) if err != nil { f.logf("the build modules are not found again, the ones found before stand: %v", err) @@ -106,3 +115,56 @@ func refreshingBuildModules(say func(context.Context, meshcatalog.CatalogRevisio return err } } + +// seatHoldersFollow is the indexer's follow of the seat-holders view of the module claiming mesh-module-assigner, its holder +// function (design 47 G A-302, A-298): the claimant is read from the indexer's own catalog (it holds the catalog seat), and at +// each refresh (the start, a change of the seat list, a catalog revision) a claimant other than the one followed has its view +// followed and the old one's dropped. Nothing is asked on a timer. +type seatHoldersFollow struct { + ctx context.Context + ix interface { + ClaimantNow(context.Context, string) (string, bool) + } + reader *views.ProcessReader + logf func(string, ...any) + + mu sync.Mutex + owner string + values func() map[string]json.RawMessage +} + +// refresh follows the claimant's seat-holders view when the claimant is another than the one followed. +func (h *seatHoldersFollow) refresh() { + claimant, ok := h.ix.ClaimantNow(h.ctx, mma.Seat) + h.mu.Lock() + defer h.mu.Unlock() + if !ok || claimant == h.owner { + return + } + if h.owner != "" { + if err := h.reader.Drop(h.owner, mma.StateSeatHolders); err != nil { + h.logf("the seat-holders view of %s is not dropped: %v", h.owner, err) + } + h.owner, h.values = "", nil + } + p, err := h.reader.Follow(claimant, mma.StateSeatHolders, claimant+"."+mma.StateSeatHolders) + if err != nil { + h.logf("the seat-holders view of %s is not followed: %v", claimant, err) + return + } + h.owner, h.values = claimant, p.Follower().Values + h.logf("the seat-holders view of %s, the claimant of %s in the indexer's catalog, is followed", claimant, mma.Seat) +} + +// holders is the holder function: the holders of a seat as the followed seat-holders view says them. +func (h *seatHoldersFollow) holders() func(seat string) []string { + return seatlist.HoldersIn(func() (map[string]json.RawMessage, error) { + h.mu.Lock() + values := h.values + h.mu.Unlock() + if values == nil { + return nil, nil + } + return values(), nil + }) +} diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/integration_test.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/integration_test.go index 52e3d407..6092105f 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/integration_test.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/integration_test.go @@ -49,6 +49,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/graph" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/views" + "git.novox.be/novox/mesh-sdk/go/views/viewstest" "mesh-delivery-indexer/internal/index" "mesh-delivery-indexer/judge" @@ -805,6 +806,7 @@ func startIndexerRun(t *testing.T, put func(context.Context, buildLog), before . b(ctx, js) } + seedClaimantAlone(t, ctx, js) serveAssignerAssignments(t, ctx, js, ctl) broker := filepath.Join(t.TempDir(), "broker") @@ -905,6 +907,7 @@ func seedBuildModules(t *testing.T, ctx context.Context, js jetstream.JetStream, Manifest: digest, None2: sdkstep.ToolchainNoReport} entries[b.module] = index.CatalogEntryOf(b.module, held, catalogue.Manifest{}, "", graph.Node{None: held.None2}) } + seedClaimant(t, ctx, js, manifests, entries) maps.Copy(entries, others) rev, objects, err := index.CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: 1, Kind: "trunk"}, entries) if err != nil { @@ -915,6 +918,57 @@ func seedBuildModules(t *testing.T, ctx context.Context, js jetstream.JetStream, } } +// seedClaimant keeps the module claiming mesh-module-assigner in entries, its manifest by digest and the seat's definition (design +// 47 G A-302): the indexer reads the claimant from its own catalog and follows its seat-holders view, which serveAssignerAssignments +// serves. +func seedClaimant(t *testing.T, ctx context.Context, js jetstream.JetStream, manifests jetstream.KeyValue, entries map[string]meshcatalog.CatalogEntry) { + t.Helper() + if _, err := js.KeyValue(ctx, index.Bucket(index.Module, index.SeatDefinitionsState)); errors.Is(err, jetstream.ErrBucketNotFound) { + if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.Bucket(index.Module, index.SeatDefinitionsState), History: 1}); err != nil { + t.Fatal(err) + } + } + seedAssignerSeat(t, ctx, js) + manifest := `{"module":"` + assignerHolderModule + `","version":"1","claims":[{"name":"` + meshmoduleassigner.Seat + `","scope":"mesh"}]}` + digest := meshcatalogclient.ManifestDigest([]byte(manifest)) + mkey, err := meshcatalogclient.ManifestsKey(digest) + if err != nil { + t.Fatal(err) + } + mdata, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: assignerHolderModule, Manifest: manifest}) + if err != nil { + t.Fatal(err) + } + if _, err := manifests.Put(ctx, mkey, mdata); err != nil { + t.Fatal(err) + } + held := meshcatalog.ModulesByModuleValue{Repository: "novox/mesh-build-modules", Commit: "4444444444444444444444444444444444444444", Path: "modules/" + assignerHolderModule, + Manifest: digest, None2: sdkstep.ToolchainNoReport} + entries[assignerHolderModule] = index.CatalogEntryOf(assignerHolderModule, held, catalogue.Manifest{}, "", graph.Node{None: held.None2}) +} + +// seedClaimantAlone keeps a first trunk revision holding the claimant alone, where no test kept one (seedBuildModules). +func seedClaimantAlone(t *testing.T, ctx context.Context, js jetstream.JetStream) { + t.Helper() + objects := newBusObjects(js, testAccount, index.CatalogRevisionsObjects) + if list, err := index.ListRevisions(ctx, objects, false); err != nil || len(list.Revisions) > 0 { + return + } + manifests, err := js.KeyValue(ctx, index.Bucket(index.Module, index.ManifestsState)) + if err != nil { + t.Fatal(err) + } + entries := map[string]meshcatalog.CatalogEntry{} + seedClaimant(t, ctx, js, manifests, entries) + rev, kept, err := index.CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: 1, Kind: "trunk"}, entries) + if err != nil { + t.Fatal(err) + } + if err := index.KeepRevision(ctx, objects, rev, kept); err != nil { + t.Fatal(err) + } +} + // seedAssignerSeat keeps the trunk definition of the seat mesh-module-assigner, which offers its state assignments as a view (§616, // §699): the indexer consumes it in the seat's form, and its own manifest is judged against the seat list it holds. func seedAssignerSeat(t *testing.T, ctx context.Context, js jetstream.JetStream) { @@ -925,7 +979,8 @@ func seedAssignerSeat(t *testing.T, ctx context.Context, js jetstream.JetStream) } assignerDef := json.RawMessage(`{"format":1,"seat":"mesh-module-assigner","scope":"mesh","grain":"whole","domain":"Delivery","decision":"novox/hq ADR 0567",` + `"verbs":[{"name":"assignments","input":{"type":"object"},"answer":{"type":"object"}}],"states":[{"name":"assignments","template":"state","view":true,` + - `"key-forms":[{"key":".node","value":{"type":"object"}}]}]}`) + `"key-forms":[{"key":".node","value":{"type":"object"}}]},{"name":"seat-holders","template":"state","view":true,` + + `"key-forms":[{"key":"","value":{"type":"object"}}]}]}`) assignerSum := sha256.Sum256(assignerDef) assignerEntry, _ := json.Marshal(index.SeatDefinitionEntry{Seat: "mesh-module-assigner", Source: index.SourceTrunk, Repository: "novox/mesh-contracts", Module: "mesh-contracts", Directory: ".", Commit: "4444444444444444444444444444444444444444", Digest: hex.EncodeToString(assignerSum[:]), Definition: assignerDef}) @@ -998,17 +1053,20 @@ func TestIndexerWritesModulesAtAMerge(t *testing.T) { // 1239/AC9; I1, I3: the m // as objects, said as catalog-revision on the seat's subject, every entry it lists stored once by digest. objects := newBusObjects(js, testAccount, index.CatalogRevisionsObjects) list, err := index.ListRevisions(ctx, objects, false) - if err != nil || len(list.Revisions) != 1 || list.Revisions[0].Name != "trunk.1" || !list.Revisions[0].Current || - list.Entries != list.Revisions[0].Entries || len(list.Unlisted) != 0 || list.Fill.Bytes == 0 { - t.Fatalf("revisions kept = %+v (%v): want trunk.1 alone, current, each entry it lists stored once", list, err) + // The bed's catalog holds the claimant of mesh-module-assigner from the start (seedClaimantAlone, A-302): the merge's revision + // is the newest, current, lists the merge's module beside the claimant, and every entry it lists is stored. + if err != nil || len(list.Revisions) == 0 || !list.Revisions[len(list.Revisions)-1].Current || list.Revisions[len(list.Revisions)-1].Entries != 2 || + list.Entries < list.Revisions[len(list.Revisions)-1].Entries || len(list.Unlisted) != 0 || list.Fill.Bytes == 0 { + t.Fatalf("revisions kept = %+v (%v): want the merge's revision newest, current, its two entries stored", list, err) } + newest := list.Revisions[len(list.Revisions)-1].Revision events, err := js.Stream(ctx, "EVENTS") if err != nil { t.Fatal(err) } if msg, err := events.GetLastMsgForSubject(ctx, "mesh.seat.mesh-catalog.event."+meshcatalog.EventCatalogRevision); err != nil || - !strings.Contains(string(msg.Data), `"revision":1`) { - t.Errorf("catalog-revision said: %v (%v): want revision 1", msg, err) + !strings.Contains(string(msg.Data), fmt.Sprintf(`"revision":%d`, newest)) { + t.Errorf("catalog-revision said: %v (%v): want revision %d", msg, err, newest) } // W4.12 through the process: a recompute by hand moves nothing and answers equal; a candidate's half dropped by hand // leaves the trunk's revision and its entries. @@ -1038,15 +1096,15 @@ func TestIndexerWritesModulesAtAMerge(t *testing.T) { // 1239/AC9; I1, I3: the m return index.TakenIn{} } key, value, _ := index.NewRecomputeAsk("a check", "ctl", time.Now()) - if r := answer(key, value); !r.Stored || !strings.Contains(r.Answer, "equal to the kept revision 1") { - t.Errorf("indexer_recompute answered %+v: want equal to the kept revision 1", r) + if r := answer(key, value); !r.Stored || !strings.Contains(r.Answer, fmt.Sprintf("equal to the kept revision %d", newest)) { + t.Errorf("indexer_recompute answered %+v: want equal to the kept revision %d", r, newest) } key, value, _ = index.NewDropCandidateGraphAsk("g1", "h1", "its gate event never came", "ctl", time.Now()) if r := answer(key, value); !r.Stored || !slices.Equal(r.Dropped, []string{"entry.dx"}) { t.Errorf("indexer_drop_candidate_graph answered %+v: want candidate.g1.h1 dropped with entry.dx", r) } - if after, err := index.ListRevisions(ctx, objects, false); err != nil || len(after.Revisions) != 1 || after.Entries != list.Entries { - t.Errorf("revisions after the drop = %+v (%v): want trunk.1 and its entries alone", after, err) + if after, err := index.ListRevisions(ctx, objects, false); err != nil || len(after.Revisions) != len(list.Revisions) || after.Entries != list.Entries { + t.Errorf("revisions after the drop = %+v (%v): want the trunk's revisions and their entries alone", after, err) } } @@ -1580,17 +1638,11 @@ func serveAssignerAssignments(t *testing.T, ctx context.Context, js jetstream.Je t.Fatal(err) } late := assignerLate - // The seat's verb assignments with seats true answers who holds each seat (mesh-sdk's seatlist.HolderOf, A-126). - holder := func(m *nats.Msg) { - raw, _ := json.Marshal(map[string]any{"result": map[string]any{"seats": []map[string]any{{"seat": meshmoduleassigner.Seat, "holders": []map[string]string{{"module": assignerHolderModule}}}}}, "node": "n0"}) - _ = m.Respond(raw) - } - for _, subject := range []string{"mesh.seat." + meshmoduleassigner.Seat + ".call.assignments.>", "mesh.seat." + meshmoduleassigner.Seat + ".tool.assignments"} { - hs, err := nc.Subscribe(subject, holder) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { _ = hs.Unsubscribe() }) + // Design 47 G §717, A-302: the claimant's seat-holders view names it the holder of mesh-module-assigner; nothing answers the + // seat's verb assignments. + seatHolders := viewstest.Serve(t, ctx, js, nc, assignerHolderModule, meshmoduleassigner.StateSeatHolders, assignerHolderModule+"."+meshmoduleassigner.StateSeatHolders) + if _, err := seatHolders.Put(ctx, meshmoduleassigner.Seat, []byte(`{"seat":"`+meshmoduleassigner.Seat+`","holders":[{"module":"`+assignerHolderModule+`","node":"n1"}]}`)); err != nil { + t.Fatal(err) } sub, err := nc.Subscribe("mesh.mod."+assignerHolderModule+".call.assignments.>", func(m *nats.Msg) { if late != nil { // its runtime comes up after the indexer started (DLV-F5r) diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/main.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/main.go index c47bfec2..8a2bc3f0 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/main.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/main.go @@ -560,7 +560,12 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro hub := process.NewEventHub(logger.Printf) reader := &views.ProcessReader{Module: index.Module, Call: calls.Call, Logf: logger.Printf, Hub: hub, Findings: func() views.WireWriter { return writer }} - followAssignments(ctx, reader, calls.Call, process.AwaitBucket(ctx, js, conn, index.Bucket(cred.Module, views.StatusState), "indexer_first_views reads nothing and indexer_first_view_again is not heard", logger.Printf), cred, js, gate, func(ctx context.Context) { + // The holder function (A-302): the seat-holders view of the claimant of mesh-module-assigner in the indexer's own catalog, + // followed again at each refresh of the build modules (a seat list change, a catalog revision). + seatHolders := &seatHoldersFollow{ctx: ctx, ix: ix, reader: reader, logf: logger.Printf} + seatHolders.refresh() + buildModules.seatHolders.Store(seatHolders) + followAssignments(ctx, reader, seatHolders.holders(), process.AwaitBucket(ctx, js, conn, index.Bucket(cred.Module, views.StatusState), "indexer_first_views reads nothing and indexer_first_view_again is not heard", logger.Printf), cred, js, gate, func(ctx context.Context) { if err := ix.Recompute(ctx); err != nil { logger.Printf("writing the modules again after the assignments of a build module changed: %v", err) } diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/packages_integration_test.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/packages_integration_test.go index 51ed5d5c..a8cab348 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/packages_integration_test.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/packages_integration_test.go @@ -223,8 +223,8 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T) // No index names the seats: the indexer lists the module's `seats/` directory (ADR 0635 §6, D-I5). seatFiles[contractsMerge] = map[string]string{"seats/git/seat.json": git} seatFiles[badHead] = map[string]string{"seats/git/seat.json": `{"format":1,"seat":"git"}`} - // The indexer starts with the two build seats the fixture keeps (seedBuildModules) and no other definition: the verb - // seat-definitions answers them at once (ADR 0636 §4, §8). + // The indexer starts with the two build seats and the seat mesh-module-assigner the fixture keeps (seedBuildModules, its claimant + // for A-302) and no other definition: the verb seat-definitions answers them at once (ADR 0636 §4, §8). if got, err := index.SeatDefinitionsOf(jsSource{ctx, seatDefs}, nil, ""); err != nil || func() string { list, _ := got.(index.SeatDefinitionList) var names []string @@ -232,8 +232,8 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T) names = append(names, d.Seat) } return strings.Join(names, ",") - }() != "node-build-go,node-build-node" { - t.Errorf("seat-definitions of an indexer that kept the build seats alone = %+v, %v", got, err) + }() != "mesh-module-assigner,node-build-go,node-build-node" { + t.Errorf("seat-definitions of an indexer that kept the build seats and the assigner's alone = %+v, %v", got, err) } contracts := `{"module":"mesh-contracts","version":"1.0","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.1","generated-from":"."}]}}` merged("novox/mesh-contracts", 80, contractsMerge, forgemsg.Forge{Root: "dddddddddddddddddddddddddddddddddddddddd", diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/views.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/views.go index eafed25b..a606c090 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/views.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer/views.go @@ -16,7 +16,6 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/process" - "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/heard" mcseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" @@ -105,28 +104,24 @@ func clearHeld(ctx context.Context, w index.FindingWriter, scope, id, kind, why const graphState = graph.State // followAssignments reads the seat mesh-module-assigner's assignments through its holder's first view and then its views family, -// never its bucket (ADR 0615 §1, DLV-F1r), into gate: the holder is the module the seat list names for the seat, asked through -// mesh-sdk's seatlist.HolderOf (design 47 G A-119, A-126, A-131), so no module is named and a new holder is followed with no change -// of code. The module's views reader follows it (design 47 G §532, A-26), which asks the first view, hears the views family and -// raises and clears the finding of a first view unanswered through the indexer's finding writer. Each key is taken as it changes, -// settled once answered, with changed told whenever what the gate passes on changed. It waits for the holder and the first answer -// at most views.Bound, so a normal start, the two owners coming up together, computes the fingerprints once; past it the indexer -// goes on, the gate passing every report, and the holder is asked again until it answers. -func followAssignments(ctx context.Context, reader *views.ProcessReader, call seatlist.HolderCaller, status *process.AwaitedBucket, cred process.Account, js jetstream.JetStream, +// never its bucket (ADR 0615 §1, DLV-F1r), into gate: the holder is the one the seat-holders view names, found and followed as it +// moves through views.ProcessReader.FollowSeat with holders, the indexer's holder function (design 47 G §717, A-302), so no module +// is named and nothing asks for the holder on a timer. The module's views reader follows it (design 47 G §532, A-26), which asks +// the first view, hears the views family and raises and clears the finding of a first view unanswered through the indexer's +// finding writer. Each key is taken as it changes, settled once answered, with changed told whenever what the gate passes on +// changed. It waits for the first answer at most views.Bound, so a normal start, the two owners coming up together, computes the +// fingerprints once; past it the indexer goes on, the gate passing every report, until the holder's first view arrives. +func followAssignments(ctx context.Context, reader *views.ProcessReader, holders func(seat string) []string, status *process.AwaitedBucket, cred process.Account, js jetstream.JetStream, gate *buildrun.ToolchainGate, changed func(context.Context), logf func(string, ...any)) { var settled atomic.Bool // the first view answered: the gate's nodes are the counted ones from now on answered := make(chan struct{}) + pic, err := reader.FollowSeat(mma.Seat, mma.StateAssignments, holders) + if err != nil { + logf("the assignments of the seat %s is not followed: %v", mma.Seat, err) + reader.Start(ctx, js, cred, status) + return + } go func() { - holder, ok := buildrun.AssignerHolder(ctx, call, 5*time.Second, buildrun.SleepFor, logf) - if !ok { - return - } - names := buildrun.AssignmentsOf(holder) - pic, err := reader.Follow(names.Holder, mma.StateAssignments, names.Tool) - if err != nil { - logf("the assignments of the seat %s is not followed: %v", mma.Seat, err) - return - } f := pic.Follower() keep := f.Changed // the picture's own, kept f.Changed = func(key string, value json.RawMessage, deleted bool, at time.Time) { diff --git a/modules/mesh-delivery-indexer/internal/index/buildmodules.go b/modules/mesh-delivery-indexer/internal/index/buildmodules.go index 189baa57..fe151613 100644 --- a/modules/mesh-delivery-indexer/internal/index/buildmodules.go +++ b/modules/mesh-delivery-indexer/internal/index/buildmodules.go @@ -102,6 +102,41 @@ func (ix *Indexer) BuildModules() map[string]BuildModule { // BuildModulesNow finds the build modules from the seat definitions defs and the manifests of the modules the indexer // holds (BuildModulesOf), says each problem in the log, uses them (UseBuildModules) and answers them. func (ix *Indexer) BuildModulesNow(ctx context.Context, defs map[string]seatdef.Definition) (map[string]BuildModule, error) { + manifests, err := ix.heldManifests(ctx) + if err != nil { + return nil, err + } + by, problems := BuildModulesOf(defs, manifests) + for _, p := range problems { + ix.logf("%s", p) + } + ix.UseBuildModules(by) + return by, nil +} + +// ClaimantNow is the module whose manifest, among the modules the indexer holds, claims seat: the one claimant, read from the +// indexer's own catalog, the catalog seat's holder needing no catalog half (design 47 G A-302, A-298). None, or more than +// one, is none. +func (ix *Indexer) ClaimantNow(ctx context.Context, seat string) (string, bool) { + manifests, err := ix.heldManifests(ctx) + if err != nil { + ix.logf("who claims %s is not read from the modules held: %v", seat, err) + return "", false + } + var claimants []string + for _, m := range manifests { + if slices.ContainsFunc(m.Claims, func(c catalogue.Claim) bool { return c.Name == seat }) { + claimants = append(claimants, m.Module) + } + } + if len(claimants) != 1 { + return "", false + } + return claimants[0], true +} + +// heldManifests are the manifests of the modules the indexer holds, one that cannot be read said in the log. +func (ix *Indexer) heldManifests(ctx context.Context) ([]catalogue.Manifest, error) { held, err := ix.heldModules(ctx) if err != nil { return nil, err @@ -118,12 +153,7 @@ func (ix *Indexer) BuildModulesNow(ctx context.Context, defs map[string]seatdef. } manifests = append(manifests, m) } - by, problems := BuildModulesOf(defs, manifests) - for _, p := range problems { - ix.logf("%s", p) - } - ix.UseBuildModules(by) - return by, nil + return manifests, nil } // languageBuiltBy is the language module is the build module of, or "". diff --git a/modules/mesh-delivery-indexer/manifest_test.go b/modules/mesh-delivery-indexer/manifest_test.go index 9498fb53..5eaa9f36 100644 --- a/modules/mesh-delivery-indexer/manifest_test.go +++ b/modules/mesh-delivery-indexer/manifest_test.go @@ -169,7 +169,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build // internal/broker/state.go:62-93). // The registry seats' published fill each (§413: no consume of version.numbered) package's series (DLV-I5, design 51 *The versions*). if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-builds.package-published", "mesh-builds.toolchain-agreed", "mesh-conditions.findings-asked", "mesh-delivery-checker.verdict", "mesh-delivery-forge-bridge.forge.**", - "mesh-delivery.freed", "mesh-delivery.heads-replaced", "mesh-delivery.pinned", "mesh-delivery.pins-stated", "mesh-delivery.source-moved", "mesh-pipeline-runner.pipeline-ended", "mesh-pipeline-runner.run-started", "mesh-wants.version-delivered", "mesh-wants.wait-ended", "mesh-wants.wanted", "node-build-go.step-ended", "node-engine.running", "npm-package-registry.published", "operator-channel.decided", "seat:mesh-module-assigner.views.assignments"}; !reflect.DeepEqual(sorted(m.Consumes), want) { + "mesh-delivery.freed", "mesh-delivery.heads-replaced", "mesh-delivery.pinned", "mesh-delivery.pins-stated", "mesh-delivery.source-moved", "mesh-pipeline-runner.pipeline-ended", "mesh-pipeline-runner.run-started", "mesh-wants.version-delivered", "mesh-wants.wait-ended", "mesh-wants.wanted", "node-build-go.step-ended", "node-engine.running", "npm-package-registry.published", "operator-channel.decided", "seat:mesh-module-assigner.views.assignments", "seat:mesh-module-assigner.views.seat-holders"}; !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" { diff --git a/modules/mesh-delivery-indexer/module.json b/modules/mesh-delivery-indexer/module.json index d467259d..f6dedfc9 100644 --- a/modules/mesh-delivery-indexer/module.json +++ b/modules/mesh-delivery-indexer/module.json @@ -84,6 +84,7 @@ "mesh-build-node.toolchain.reported", "mesh-conditions.findings-asked", "seat:mesh-module-assigner.views.assignments", + "seat:mesh-module-assigner.views.seat-holders", "mesh-builds.package-published", "mesh-builds.toolchain-agreed", "mesh-wants.version-delivered",