§717 (3), the builder, the checker and the indexer follow the assignments by the seat, at the holder the seat-holders view names
buildrun's AssignerHolder asked the seat's verb for the holder every 5 seconds. The builder and the checker now follow mesh-module-assigner's assignments through views.ProcessReader.FollowSeat with the catalog half's holder function (the checker gains the catalog half's reader of §614 for it); the indexer reads the claimant from its own catalog (Indexer.ClaimantNow) and follows its seat-holders view again at each refresh of its build modules. AssignerHolder goes; the manifests consume the seat-holders view (the checker also the catalog half's events and verbs), and the assigner's seat-holders first-view tool answers the checker and the indexer. The beds give each part a claimant and a seat-holders view in place of the seat verb's answer; the indexer's beds hold the claimant from the start, so the merge test reads the newest revision. Builds: design 47 G §717, §614, A-298, A-302 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 `<holder>.assignments` and the bucket
|
||||
// `<holder>_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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>.node","value":{"type":"object"}}]}]}`)
|
||||
`"key-forms":[{"key":"<node>.node","value":{"type":"object"}}]},{"name":"seat-holders","template":"state","view":true,` +
|
||||
`"key-forms":[{"key":"<seat>","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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+4
-4
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 "".
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user