diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index d643a419..65b4b71e 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -528,7 +528,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { // indexer its runtime of its own answers each first-view tool to the readers its manifest's callers lists name, at // its mark, and refuses every other account, the assigner's own included. firstViewsBeforeAnyNewFormState(t, b, map[string][]string{ - "assignments": {"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer", "n1.mesh-delivery-builder"}, + "assignments": {"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer", "n1.mesh-delivery-builder", "n1.mesh-delivery-checker"}, "seat-holders": {"n1.node-tools"}, "wants": {"n1.mesh-grants"}, }) @@ -1811,7 +1811,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] return msg.Data, nil }, tool)(b.ctx, "") } - readers := []string{"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer", "n1.mesh-delivery-assigner", "n1.mesh-delivery-builder"} + readers := []string{"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer", "n1.mesh-delivery-assigner", "n1.mesh-delivery-builder", "n1.mesh-delivery-checker"} for tool, allowed := range granted { for _, user := range readers { a, err := ask(user, tool) diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index df353e50..159cfe40 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -109,7 +109,7 @@ func TestManifest(t *testing.T) { t.Errorf("runs-as = %q: the bundle runs on the assigner's own account (ADR 0621 §1)", m.RunsAs) } // ADR 0621 §2: each first-view tool answers exactly its readers, never the assigner. - callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder", "seat-holders": "node-tools", "wants": "mesh-grants"} + callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker", "seat-holders": "node-tools", "wants": "mesh-grants"} 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 d54d1be6..51e09caf 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -212,7 +212,7 @@ "mesh-delivery-assigner" ], "env": { - "MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder", + "MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker", "MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools", "MESH_VIEW_CALLERS_WANTS": "mesh-grants" } diff --git a/modules/mesh-delivery-builder/buildrun/assignerholder.go b/modules/mesh-delivery-builder/buildrun/assignerholder.go new file mode 100644 index 00000000..5c56f5b4 --- /dev/null +++ b/modules/mesh-delivery-builder/buildrun/assignerholder.go @@ -0,0 +1,45 @@ +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 index 2fc7e34a..091340ed 100644 --- a/modules/mesh-delivery-builder/buildrun/assignerholder_test.go +++ b/modules/mesh-delivery-builder/buildrun/assignerholder_test.go @@ -1,4 +1,4 @@ -package main +package buildrun import ( "context" @@ -10,7 +10,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/seatlist" ) -// The builder finds the assigner's holder at run time: asked again while the seat list does not answer, and the names of the view +// 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 @@ -23,14 +23,14 @@ func TestTheAssignerHolderIsFoundAtRunTime(t *testing.T) { } 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) {}) + 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" { + 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, + 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/buildrun/names.go b/modules/mesh-delivery-builder/buildrun/names.go index 0896d2ea..2410e40b 100644 --- a/modules/mesh-delivery-builder/buildrun/names.go +++ b/modules/mesh-delivery-builder/buildrun/names.go @@ -1,13 +1,7 @@ package buildrun -// The holders' states the runner reads, named from the builder's own manifest (module.json `reads`; a unit row of the -// builder checks them against it) and from the build seats' states: a bucket is `_`, and no contract -// names a holder (novox/hq ADR 0625 §4, ADR 0634 §1). -const ( - // AssignmentsBucket is the assigner's state assignments, which the toolchain gate reads for the placing of a build module. - AssignerModule = "mesh-delivery-assigner" - AssignmentsBucket = AssignerModule + "_assignments" -) +// The holders' states the runner reads are named from the build seats' and the assigner's holders found at run time: a bucket is +// `_`, and no contract names a holder (novox/hq ADR 0625 §4, ADR 0634 §1). // StepsBucket is the bucket of a build module's state steps. func StepsBucket(module string) string { return module + "_steps" } diff --git a/modules/mesh-delivery-builder/buildrun/standing.go b/modules/mesh-delivery-builder/buildrun/standing.go index 45f49453..379e0725 100644 --- a/modules/mesh-delivery-builder/buildrun/standing.go +++ b/modules/mesh-delivery-builder/buildrun/standing.go @@ -16,7 +16,7 @@ import ( ) // PlacedNodes holds which modules the catalogue places on which node: the seat mesh-module-assigner's state `assignments`, -// held by the assigner (AssignmentsBucket, read by meshmoduleassignerclient.Placed; ADR 0567), or the assignment half +// held by the assigner (the holder's `_assignments`, read by meshmoduleassignerclient.Placed; ADR 0567), or the assignment half // (meshwantsclient.Placed, fed by Revision). The builder counts the nodes it names (Nodes; ADR 0624 §1). It holds no // toolchain: the builder keeps the toolchains it heard in its state toolchains-heard and forgets one when the node-engine // seat says the build module stopped on the node, never on a placement (design 47 G §546). @@ -26,7 +26,7 @@ type PlacedNodes struct { placed placements known bool // the assignments is settled // OnPlaced is told, outside the lock, once the assignments's history is read and after each revision of it - // FollowAssignments hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody. + // FollowAssignmentsIn hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody. OnPlaced func() } @@ -48,13 +48,13 @@ func (g *PlacedNodes) Assignments(key string, value []byte, deleted bool, at tim defer g.mu.Unlock() placed, ok := g.placed.(*meshmoduleassignerclient.Placed) if !ok { - g.logf("%s: %s is not read, the placements take the assignment half (design 47)", AssignmentsBucket, key) + g.logf("the assignments: %s is not read, the placements take the assignment half (design 47)", key) return false } was := placed.Seeded() _, changed, err := placed.Put(key, value, deleted, at) if err != nil { - g.logf("%s: %v (issue 1940)", AssignmentsBucket, err) + g.logf("the assignments: %v (issue 1940)", err) } if !g.known { return false @@ -103,7 +103,7 @@ func (g *PlacedNodes) Settled() { g.known = true } -// assignmentsFollowed is what FollowAssignments feeds: the builder's PlacedNodes, or the ToolchainGate of the checker and the +// assignmentsFollowed is what FollowAssignmentsIn feeds: the builder's PlacedNodes, or the ToolchainGate of the checker and the // indexer. settle says the history is read and answers whether what the part passes on changed; placedTold tells its OnPlaced. type assignmentsFollowed interface { Assignments(key string, value []byte, deleted bool, at time.Time) bool @@ -125,17 +125,11 @@ func (g *ToolchainGate) placedTold() { } } -// FollowAssignments reads the seat mesh-module-assigner's assignments with its history into gate, says it settled, and follows it; changed -// is told whenever what the gate passes on changed. Where the state cannot be read or watched (not there, or this part -// may not read it) it says so and the gate passes every report on: not fatal, since an indexer, builder or checker that -// stops for it stops every delivery (issue 1945). An error of the watch after that ends on the channel. -func FollowAssignments(ctx context.Context, js process.BucketOpener, gate assignmentsFollowed, changed func(context.Context), logf func(string, ...any)) (<-chan error, error) { - return FollowAssignmentsIn(ctx, js, AssignmentsBucket, gate, changed, logf) -} - -// FollowAssignmentsIn is FollowAssignments for the state in bucket, the holder's `_assignments` where the holder is found at -// run time (design 47 G A-119): the builder follows it so; FollowAssignments, with the fixed bucket, is the checker's until its own -// change finds the holder. +// FollowAssignmentsIn reads the seat mesh-module-assigner's assignments, the state `bucket` of its holder (found at run time, design 47 +// G A-119, A-126: AssignmentsOf), with its history into gate, says it settled, and follows it; changed is told whenever what the +// gate passes on changed. Where the state cannot be read or watched (not there, or this part may not read it) it says so and the gate +// passes every report on: not fatal, since an indexer, builder or checker that stops for it stops every delivery (issue 1945). An +// error of the watch after that ends on the channel. func FollowAssignmentsIn(ctx context.Context, js process.BucketOpener, bucket string, gate assignmentsFollowed, changed func(context.Context), logf func(string, ...any)) (<-chan error, error) { errs := make(chan error, 1) kv, err := js.KeyValue(ctx, bucket) diff --git a/modules/mesh-delivery-builder/buildrun/standing_test.go b/modules/mesh-delivery-builder/buildrun/standing_test.go index f2ceee5d..192757c8 100644 --- a/modules/mesh-delivery-builder/buildrun/standing_test.go +++ b/modules/mesh-delivery-builder/buildrun/standing_test.go @@ -86,7 +86,7 @@ func TestPlacedNodes(t *testing.T) { refused := errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_assignments") gate := NewPlacedNodes(logger.Printf) for _, bus := range []refusingBus{{err: refused}, {refuseOpen: true, err: refused}} { - if _, err := FollowAssignments(context.Background(), bus, gate, func(context.Context) {}, logger.Printf); err != nil { + if _, err := FollowAssignmentsIn(context.Background(), bus, "acme-assigner_assignments", gate, func(context.Context) {}, logger.Printf); err != nil { t.Errorf("a refused assignments stopped the start: %v", err) } } diff --git a/modules/mesh-delivery-builder/buildrun/toolchaingate.go b/modules/mesh-delivery-builder/buildrun/toolchaingate.go index 0f6a8ae6..122c2b6c 100644 --- a/modules/mesh-delivery-builder/buildrun/toolchaingate.go +++ b/modules/mesh-delivery-builder/buildrun/toolchaingate.go @@ -21,7 +21,7 @@ import ( // // ToolchainGate stands between the toolchains buckets and what takes their entries in (the indexer's PutToolchain, a // ToolchainSet): an entry is passed on only while the assignments (the seat mesh-module-assigner's state `assignments`, held by -// the assigner, AssignmentsBucket, read by meshmoduleassignerclient.Placed; ADR 0567, ADR 0549 §3) places its build module on +// the assigner, its holder's state assignments, read by meshmoduleassignerclient.Placed; ADR 0567, ADR 0549 §3) places its build module on // its node, and only once it was written since that assignments; it is passed on as deleted otherwise, said once, and // passed on when its node writes it again. A report written before the assignments is one an earlier run of the module // left (issue 1940, 2026-10-25 20:56: mesh-build-node put on ace again brought ace.typescript v26.12.0 of 2026-10-23 @@ -42,7 +42,7 @@ type ToolchainGate struct { raw map[string]map[string][]byte // module -> toolchains key -> value, as the bucket holds it left map[string]string // / -> why it was said left out, so each reason is said once // OnPlaced is told, outside the gate's lock, once the assignments's history is read and after each revision of it - // FollowAssignments hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody. + // FollowAssignmentsIn hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody. OnPlaced func() } @@ -100,13 +100,13 @@ func (g *ToolchainGate) Assignments(key string, value []byte, deleted bool, at t defer g.mu.Unlock() placed, ok := g.placed.(*meshmoduleassignerclient.Placed) if !ok { - g.logf("%s: %s is not read, the gate takes the assignment half (design 47)", AssignmentsBucket, key) + g.logf("the assignments: %s is not read, the gate takes the assignment half (design 47)", key) return false } was := placed.Seeded() node, changed, err := placed.Put(key, value, deleted, at) if err != nil { - g.logf("%s: %v (issue 1940)", AssignmentsBucket, err) + g.logf("the assignments: %v (issue 1940)", err) } if !g.known { return false diff --git a/modules/mesh-delivery-builder/buildrun/toolchaingate_test.go b/modules/mesh-delivery-builder/buildrun/toolchaingate_test.go index 90f6a73a..8f526bce 100644 --- a/modules/mesh-delivery-builder/buildrun/toolchaingate_test.go +++ b/modules/mesh-delivery-builder/buildrun/toolchaingate_test.go @@ -249,7 +249,7 @@ func TestToolchainGate(t *testing.T) { refused := errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_assignments") gate, _, _ := start(t, false) for _, bus := range []refusingBus{{err: refused}, {refuseOpen: true, err: refused}} { - if _, err := FollowAssignments(context.Background(), bus, gate, func(context.Context) {}, logger.Printf); err != nil { + if _, err := FollowAssignmentsIn(context.Background(), bus, "acme-assigner_assignments", gate, func(context.Context) {}, logger.Printf); err != nil { t.Errorf("a refused assignments stopped the start: %v", err) } } 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 f85253dd..0b8c7e46 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go @@ -347,11 +347,11 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // the view is the holder's first-view tool, followed by its views family (A-119, A-131); no assigner module is named. assignmentsErrs := make(chan error, 1) go func() { - holder, ok := assignerHolder(ctx, calls.Call, 5*time.Second, sleepFor, logger.Printf) + holder, ok := buildrun.AssignerHolder(ctx, calls.Call, 5*time.Second, buildrun.SleepFor, logger.Printf) if !ok { return } - names := assignmentsOf(holder) + names := buildrun.AssignmentsOf(holder) assignments, err := reader.Follow(names.Holder, mma.StateAssignments, names.Tool) if err != nil { assignmentsErrs <- err diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go index 60c98982..4127a564 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go @@ -7,6 +7,7 @@ import ( "fmt" "sort" "sync" + "sync/atomic" "time" nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" @@ -23,6 +24,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" + "git.novox.be/novox/mesh-sdk/go/views" ) // The checker's composition of a check on the bus (novox/hq ADR 0593 §7, §9; check.EngineCompositions): each placed @@ -58,6 +60,18 @@ func (e engineAsks) ComposeCandidate(ctx context.Context, node string, ask ne.Co } } +// assignedNodes is the placedNodes' read of the assignments' picture, which is followed once the holder of the seat mesh-module-assigner +// is known: an error says so until then, so a check never passes with no node asked. +func assignedNodes(p *atomic.Pointer[views.Picture]) func(context.Context) (jetstream.KeyValue, error) { + return func(context.Context) (jetstream.KeyValue, error) { + pic := p.Load() + if pic == nil { + return nil, errors.New("the seat that places modules has no holder known yet") + } + return pic, nil + } +} + // placedNodes names every node the seat mesh-module-assigner places now, read from its state when a check composes (no // watch: a check reads it once). A state that cannot be read is an error, so the check says its composition was not // made rather than passing with no node asked (review finding 4). @@ -70,14 +84,14 @@ func (p placedNodes) Placed() ([]string, error) { defer cancel() kv, err := p.kv(ctx) if err != nil { - return nil, fmt.Errorf("the state %s cannot be read: %w", buildrun.AssignmentsBucket, err) + return nil, fmt.Errorf("the assignments cannot be read: %w", err) } lister, err := kv.ListKeys(ctx) if errors.Is(err, jetstream.ErrNoKeysFound) { return nil, nil // assignments that places nothing } if err != nil { - return nil, fmt.Errorf("the state %s cannot be listed: %w", buildrun.AssignmentsBucket, err) + return nil, fmt.Errorf("the assignments cannot be listed: %w", err) } seen := map[string]bool{} for key := range lister.Keys() { 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 a0f3bd8d..102d4024 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 @@ -446,7 +446,7 @@ func (b *bed) said(t *testing.T, words string) { // the seat verb's caller-named and plain subjects); the others never answer. It answers what it was asked. func (b *bed) engine(t *testing.T, nodes ...string) func() []ne.ComposeCandidateInput { t.Helper() - kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buildrun.AssignmentsBucket, History: 1}) + kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: testAssigner + "_assignments", History: 1}) if err != nil { t.Fatal(err) } @@ -615,7 +615,16 @@ 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) } - viewstest.Serve(t, ctx, js, ctl, buildrun.AssignerModule, meshmoduleassigner.StateAssignments, buildrun.AssignerModule+".assigner_read_assignments", "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) + } + } + 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). known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"}) @@ -1274,6 +1283,8 @@ func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 } } +const testAssigner = "acme-assigner" // a holder of the seat mesh-module-assigner no code names + // buildSeatDefinitions are the seat definitions the bed's seat mesh-catalog lists: the build seats, as the mesh's own definitions of // them, whose acting rules say which language each builds. var buildSeatDefinitions = func() map[string]seatdef.Definition { 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 16c37f2f..4cfe5513 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -86,7 +86,6 @@ const ( seatDefinitionsState = "seat-definitions" // The owners' views families, as their manifests' `streams` name them. indexerViews = "indexer-views" - assignerViews = "assigner-views" modulesState = "modules" manifestsState = "manifests" ) @@ -184,7 +183,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro manifests := followOwner(check.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews) packages := followOwner(check.IndexerModule, meshcatalog.StatePackages, "indexer_read_packages", indexerViews) predefined := followOwner(check.IndexerModule, check.IndexerPredefinedState, "indexer_read_predefined", indexerViews) - assignments := followOwner(buildrun.AssignerModule, meshmoduleassigner.StateAssignments, "assigner_read_assignments", assignerViews) + // The assignments is followed once the seat list names the holder of the seat mesh-module-assigner (below). + var assignments atomic.Pointer[views.Picture] // The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails. seatDefs := followOwner(check.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews) if followed != nil { @@ -290,7 +290,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro compositions := check.EngineCompositions{ Requests: check.RequestKeeper{Store: requestsKV}, Asker: engineAsks{call: caller.CallOn}, - Placed: placedNodes{kv: func(context.Context) (jetstream.KeyValue, error) { return assignments, nil }}.Placed, + Placed: placedNodes{kv: assignedNodes(&assignments)}.Placed, Bound: answerBound, Sandbox: seatSandbox{asker: buildrun.Asker{Seats: buildSeats, Publisher: asks, Waiter: waiter, Bound: stepBound, Dropper: buildrun.SeatDrops{Caller: caller}}}, Verdicts: verdicts.all, @@ -343,12 +343,25 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // 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) 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 <-assignments.Follower().Ready(): + case <-pic.Follower().Ready(): } - pe, err := buildrun.FollowAssignments(ctx, reader, gate, ch.NodesChanged, logger.Printf) + pe, err := buildrun.FollowAssignmentsIn(ctx, reader, names.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 b3543658..23517318 100644 --- a/modules/mesh-delivery-checker/manifest_test.go +++ b/modules/mesh-delivery-checker/manifest_test.go @@ -114,7 +114,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 } // The build modules are no names (design 47 G A-82): the steps and toolchains of the build seats are read as the seat's holder's states. if want := []string{"mesh-delivery-builder.builds", "mesh-delivery-watchman.presence", "node-engine.have", - "seat:node-build-go.steps", "seat:node-build-go.toolchains", "seat:node-build-node.steps", "seat:node-build-node.toolchains"}; !reflect.DeepEqual(sorted(m.Reads), want) { + "seat:mesh-module-assigner.assignments", "seat:node-build-go.steps", "seat:node-build-go.toolchains", "seat:node-build-node.steps", "seat:node-build-node.toolchains"}; !reflect.DeepEqual(sorted(m.Reads), want) { t.Errorf("reads = %v, want %v", m.Reads, want) } // DLV-M2 (ADR 0615 §1, ADR 0596): the indexer's candidates, modules, manifests, packages and predefined keys and the @@ -128,7 +128,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 // build module's tool and holds no capacity: it puts check asks on the Go and Node build seats (ADR 0525 §1, §4). // The forge's file read through the seat git, never a module's tool (milestone 47/G W6.14), the build seats' drop, and of // the node seat node-engine its one verb compose-candidate, asked of every placed node (ADR 0593 §7), never its other verbs. - if want := []string{"mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests", + 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"}; !reflect.DeepEqual(sorted(m.Invokes), want) { @@ -138,8 +138,8 @@ 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-assigner.views.assignments", "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"}; !reflect.DeepEqual(sorted(m.Consumes), want) { + 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) { 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 edc99cbe..70277960 100644 --- a/modules/mesh-delivery-checker/module.json +++ b/modules/mesh-delivery-checker/module.json @@ -18,7 +18,6 @@ "mesh-catalog.catalog-revision", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", - "mesh-delivery-assigner.views.assignments", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", @@ -26,6 +25,7 @@ "mesh-delivery-indexer.views.predefined", "mesh-delivery-indexer.views.seat-definitions", "mesh-conditions.findings-asked", + "seat:mesh-module-assigner.views.assignments", "node-build-go.toolchain-reported", "node-build-node.toolchain-reported", "node-engine.running" @@ -53,6 +53,7 @@ "mesh-delivery-builder.builds", "mesh-delivery-watchman.presence", "node-engine.have", + "seat:mesh-module-assigner.assignments", "seat:node-build-go.steps", "seat:node-build-go.toolchains", "seat:node-build-node.steps", @@ -79,7 +80,6 @@ "checker_verdicts" ], "invokes": [ - "mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests", "mesh-delivery-indexer.indexer_read_modules", diff --git a/modules/mesh-delivery-watchman/manifest_test.go b/modules/mesh-delivery-watchman/manifest_test.go index 82f31b4b..6fb90649 100644 --- a/modules/mesh-delivery-watchman/manifest_test.go +++ b/modules/mesh-delivery-watchman/manifest_test.go @@ -58,7 +58,6 @@ type manifest struct { Reads []string `json:"reads"` Consumes []string `json:"consumes"` Invokes []string `json:"invokes"` - Requires []any `json:"requires"` State []state `json:"state"` Streams []struct { Name string `json:"name"`