diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go index 9baa4b52..73d70683 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go @@ -15,12 +15,15 @@ import ( "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient" + "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-indexer/takein" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate" "git.novox.be/novox/mesh-delivery/sdk/buildstep" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/busretry" + "git.novox.be/novox/mesh-sdk/go/seatdef" + "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -405,6 +408,15 @@ func ownersRead(ask func(key string, body any) (json.RawMessage, error)) view.Re GraphState: indexer("indexer_read_graph")} } +// holderOf is a seat's holder as mesh-sdk's seatlist.HolderOf answers it (design 47 G A-126): false while the seat has none or +// the seat list cannot be read. +func holderOf(ask func(key string, body any) (json.RawMessage, error)) func(seat string) (string, bool) { + return func(seat string) (string, bool) { + module, err := seatlist.HolderOf(ask, seat) + return module, err == nil + } +} + func main() { r := ownersRead(stdio.Ask) r.Builds, r.HavesState = read(stdio.State("mesh-delivery-builder.builds")), read(stdio.State("node-engine.have")) @@ -416,16 +428,28 @@ func main() { r.ProvisionsState = read(stdio.State(view.ProvisionsRead)) r.BuildLog = stdio.Log(builds.BuildLogRead).Since r.Pending = pendingOf + // The build modules are no names (design 47 G A-82): the build seats on the seat list, each held by the module the seat + // list names for it; the seat list and its two tools are wired once by mesh-sdk (§204). + gate := seatlist.NewPart("view", []string{stdio.AnyPerson, "node-tools", "mesh-healers"}) + go func() { + <-stdio.Served() + gate.Follow(seatlist.StdioBus(), nil, func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[mesh-delivery-view-tools] "+f+"\n", a...) }) + }() + seats := buildrun.NewBuildSeats(func() map[string]seatdef.Definition { + if rd := gate.Reader(); rd != nil { + return rd.Definitions() + } + return nil + }, holderOf(stdio.Ask)) u := view.UsageReader{Haves: r.HavesState, Resolved: read(stdio.State("mesh-config.resolved")), Tries: read(stdio.State("mesh-delivery-checker.tries")), - Steps: map[string]view.StateRead{}, Pauses: map[string]view.StateRead{}, Backlog: backlogOf(socket())} - for _, lang := range buildstep.AskLanguages { - module, _ := buildstep.BuildModuleOf(lang) - u.Steps[lang] = read(stdio.State(module + "." + view.StepsState)) - u.Pauses[lang] = read(stdio.State(module + "." + buildstep.PausesState)) - } + Seats: seats, + StepsOf: func(seat string) view.StateRead { return read(stdio.State(seat + "." + view.StepsState)) }, + PausesOf: func(seat string) view.StateRead { return read(stdio.State(seat + "." + buildstep.PausesState)) }, + Backlog: backlogOf(socket())} // view_first_views and view_first_view_again: where the view's process stands with its first views of the indexer's and // the assigner's states (ADR 0615 §6), and a re-read by hand. - tools := append(Tools(r, u), views.StatusTools(viewstate.ViewModule, "view_first_views", "view_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...) + tools := append(Tools(r, u), gate.Tools()...) + tools = append(tools, views.StatusTools(viewstate.ViewModule, "view_first_views", "view_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...) if err := stdio.Serve("", tools); err != nil { fmt.Fprintf(os.Stderr, "[mesh-delivery-view-tools] %v\n", err) os.Exit(1) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go index c4231d61..1072a07e 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go @@ -3,6 +3,7 @@ package main import ( "crypto/ed25519" "encoding/json" + "errors" "fmt" "mesh-delivery-view/internal/view" "reflect" @@ -20,6 +21,7 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/busretry" "git.novox.be/novox/mesh-sdk/go/keys" + "git.novox.be/novox/mesh-sdk/go/seatlist" ) type fake map[string]string @@ -625,3 +627,26 @@ func manifestReadsAsksEveryNode(t *testing.T) { t.Errorf("node_manifest_reads = %+v", rows) } } + +// Design 47 G A-82, A-74: a build seat's holder is the module the seat mesh-module-assigner's verb assignments names for it with +// the seats, and none while the seat has no holder or the list cannot be read. +func TestABuildSeatsHolderIsTheOneTheSeatListNames(t *testing.T) { + var asked []string + ask := func(key string, _ any) (json.RawMessage, error) { + asked = append(asked, key) + return json.RawMessage(`{"seats":[{"seat":"node-build-go","scope":"node","holders":[{"module":"acme-go","node":"n1"}]},{"seat":"node-build-node","scope":"node","holders":[]}]}`), nil + } + holder := holderOf(ask) + if m, ok := holder("node-build-go"); !ok || m != "acme-go" { + t.Errorf("the holder of node-build-go = %q, %v; want acme-go", m, ok) + } + if m, ok := holder("node-build-node"); ok { + t.Errorf("a seat with no holder has %q", m) + } + if len(asked) != 2 || asked[0] != seatlist.HolderKey() { + t.Errorf("asked %v; want the seat mesh-module-assigner's assignments", asked) + } + if _, ok := holderOf(func(string, any) (json.RawMessage, error) { return nil, errors.New("down") })("node-build-go"); ok { + t.Error("a seat list that cannot be read names a holder") + } +} diff --git a/modules/mesh-delivery-view/go.mod b/modules/mesh-delivery-view/go.mod index a97d1bc4..b86a0904 100644 --- a/modules/mesh-delivery-view/go.mod +++ b/modules/mesh-delivery-view/go.mod @@ -16,6 +16,7 @@ require ( git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000 diff --git a/modules/mesh-delivery-view/internal/view/usage.go b/modules/mesh-delivery-view/internal/view/usage.go index 190d6baa..368a2605 100644 --- a/modules/mesh-delivery-view/internal/view/usage.go +++ b/modules/mesh-delivery-view/internal/view/usage.go @@ -21,6 +21,7 @@ import ( "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + buildseats "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-delivery/sdk/buildrun" "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) @@ -50,9 +51,11 @@ const ( // UsageReader reads what build_usage answers from. type UsageReader struct { - // Steps and Pauses are each build module's states by language (go, node, image). - Steps map[string]StateRead - Pauses map[string]StateRead + // Seats are the build seats, each by the acting rule that builds, with the module holding it on the seat list (design 47 G + // A-82): no build module is named here. StepsOf and PausesOf read a build seat's steps and pauses through the seat. + Seats *buildseats.BuildSeats + StepsOf func(seat string) StateRead + PausesOf func(seat string) StateRead // Resolved is mesh-config's resolved values (..); Haves the node-engine's have. Resolved StateRead Haves StateRead @@ -209,6 +212,16 @@ func (u UsageReader) Usage() UsageAnswer { note := func(f string, a ...any) { ans.Notes = append(ans.Notes, fmt.Sprintf(f, a...)) } tries := u.tries(now, &ans, note) + // The build seats once for this answer: each asks the seat list for its holder (design 47 G A-82). + buildSeats := u.Seats.All() + buildSeatOfModule := func(module string) buildseats.BuildModule { + for _, m := range buildSeats { + if m.Module == module && module != "" { + return m + } + } + return buildseats.BuildModule{} + } seats := map[string]map[string]*SeatUsage{} // node -> language -> seat seat := func(node, lang string) *SeatUsage { if seats[node] == nil { @@ -217,9 +230,13 @@ func (u UsageReader) Usage() UsageAnswer { if s := seats[node][lang]; s != nil { return s } - name, _ := buildstep.BuildSeat(lang) - module, _ := buildstep.BuildModuleOf(lang) - s := &SeatUsage{Seat: name, Module: module, Running: RunningUsage{ByKind: map[string]int{}}, LeftRunning: []StepUsage{}, EndedStale: []StepUsage{}, Steps: []StepUsage{}} + var bm buildseats.BuildModule + for _, m := range buildSeats { + if m.AskLanguage() == lang { + bm = m + } + } + s := &SeatUsage{Seat: bm.Seat, Module: bm.Module, Running: RunningUsage{ByKind: map[string]int{}}, LeftRunning: []StepUsage{}, EndedStale: []StepUsage{}, Steps: []StepUsage{}} seats[node][lang] = s return s } @@ -233,10 +250,8 @@ func (u UsageReader) Usage() UsageAnswer { } else { var want []string for _, k := range keys { - for _, lang := range buildstep.AskLanguages { - if m, _ := buildstep.BuildModuleOf(lang); strings.HasSuffix(k, "."+m) { - want = append(want, k) - } + if _, module, _ := strings.Cut(k, "."); buildSeatOfModule(module).Seat != "" { + want = append(want, k) } } raws, err := u.Haves.values(want) @@ -256,8 +271,8 @@ func (u UsageReader) Usage() UsageAnswer { } for k, h := range haves { node, module, _ := strings.Cut(k, ".") - lang := strings.TrimPrefix(module, "mesh-build-") - s := seat(node, lang) + bm := buildSeatOfModule(module) + s := seat(node, bm.AskLanguage()) s.Phase, s.Holds = string(h.Phase), h.Phase == nodeengineclient.PhaseRunning } @@ -310,19 +325,27 @@ func (u UsageReader) Usage() UsageAnswer { } for key := range unreadable { node, module, _ := strings.Cut(key, ".") - seat(node, strings.TrimPrefix(module, "mesh-build-")) + bm := buildSeatOfModule(module) + seat(node, bm.AskLanguage()) } // Every node with a resolved capacity is listed, so "is this node eligible" is answered for it. for key := range resolved { node, module, _ := strings.Cut(key, ".") - seat(node, strings.TrimPrefix(module, "mesh-build-")) + bm := buildSeatOfModule(module) + seat(node, bm.AskLanguage()) + } + if len(buildSeats) == 0 { + note("no build seat is known from the seat definitions and the seat list, so no steps or pauses are read") } // The steps of each build module. steps := map[string]map[string][]usageStep{} // node -> language -> steps - for _, lang := range buildstep.AskLanguages { - module, _ := buildstep.BuildModuleOf(lang) - sr, ok := u.Steps[lang] - if !ok || sr.Keys == nil || sr.Get == nil { + for _, bm := range buildSeats { + lang, module := bm.AskLanguage(), cmpOr(bm.Module, bm.Seat) + var sr StateRead + if u.StepsOf != nil { + sr = u.StepsOf(bm.Seat) + } + if sr.Keys == nil || sr.Get == nil { note("the steps of %s are not readable here", module) continue } @@ -379,10 +402,13 @@ func (u UsageReader) Usage() UsageAnswer { // Pauses: paused. of each module; and capacity., the capacity the module says it judges by. paused := map[string]bool{} // . judged := map[string]buildstep.Capacity{} - for _, lang := range buildstep.AskLanguages { - module, _ := buildstep.BuildModuleOf(lang) - pr, ok := u.Pauses[lang] - if !ok || pr.Get == nil { + for _, bm := range buildSeats { + lang, module := bm.AskLanguage(), cmpOr(bm.Module, bm.Seat) + var pr StateRead + if u.PausesOf != nil { + pr = u.PausesOf(bm.Seat) + } + if pr.Get == nil { note("the pauses of %s are not readable here, so none is said paused", module) continue } @@ -422,7 +448,8 @@ func (u UsageReader) Usage() UsageAnswer { for _, node := range nodes { nu := NodeUsage{Node: node, Seats: []SeatUsage{}, Said: []string{}, CPU: "not on the bus: the disk-load module keeps no state; " + node + "/disk-load.cpu_top reads it (the build modules' share among its units)"} - for _, lang := range buildstep.AskLanguages { + for _, bm := range buildSeats { + lang := bm.AskLanguage() s := seats[node][lang] if s == nil { continue @@ -696,3 +723,11 @@ func sumMedian(d []int64) (sum, median int64) { } return sum, s[(len(s)-1)/2] } + +// cmpOr is the first of two names that is not empty. +func cmpOr(a, b string) string { + if a != "" { + return a + } + return b +} diff --git a/modules/mesh-delivery-view/internal/view/usage_test.go b/modules/mesh-delivery-view/internal/view/usage_test.go index 2762f299..02d87032 100644 --- a/modules/mesh-delivery-view/internal/view/usage_test.go +++ b/modules/mesh-delivery-view/internal/view/usage_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "fmt" + buildseats "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-sdk/go/seatdef" "strconv" "strings" "testing" @@ -82,8 +84,19 @@ func usageFixture() UsageReader { "shanks.mesh-config.other": `{"value":"x"}`, "g14.mesh-build-node.capacity": `{"node":"g14","module":"mesh-build-node","key":"capacity","source":"unreadable"}`, }), - Steps: map[string]StateRead{"go": states(goSteps), "node": states(map[string]string{}), "image": states(map[string]string{})}, - Pauses: map[string]StateRead{"go": states(map[string]string{"paused.ace": `{"v":1,"paused":true,"by":"lead@shanks","at":"2026-10-26T09:00:00Z"}`}), "node": states(nil), "image": states(nil)}, + Seats: buildSeatsOf(), + StepsOf: func(seat string) StateRead { + if seat == "node-build-go" { + return states(goSteps) + } + return states(map[string]string{}) + }, + PausesOf: func(seat string) StateRead { + if seat == "node-build-go" { + return states(map[string]string{"paused.ace": `{"v":1,"paused":true,"by":"lead@shanks","at":"2026-10-26T09:00:00Z"}`}) + } + return states(nil) + }, Tries: states(map[string]string{ "try-" + strconv.FormatInt(at("11:29").UnixNano(), 10): `{"id":"try-1","repository":"novox/mesh-sdk","head":"h","trunk":"t","state":"pass","asks":["` + tryAsk + `"]}`, "try-" + strconv.FormatInt(at("11:58").UnixNano(), 10): `{"id":"try-2","repository":"novox/mesh-delivery","head":"h","trunk":"t","state":"running","started":"2026-10-26T11:58:00Z"}`, @@ -167,7 +180,7 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) { // Nothing readable: each state is named in a note, the backlog's refusal too, and nothing is said held. none := UsageReader{Now: func() time.Time { return usageNow }, Backlog: func() ([]BacklogRow, error) { return nil, errors.New("no socket") }}.Usage() notes := strings.Join(none.Notes, " | ") - for _, want := range []string{"have is not readable", "capacity unknown to this tool: mesh-config's resolved values are not readable", "steps of mesh-build-go are not readable", "tries are not readable", "did not answer: no socket"} { + for _, want := range []string{"have is not readable", "capacity unknown to this tool: mesh-config's resolved values are not readable", "no build seat is known", "tries are not readable", "did not answer: no socket"} { if !strings.Contains(notes, want) { t.Errorf("the notes %q do not say %q", notes, want) } @@ -196,8 +209,8 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) { // Issue 1806's follow-up: the module on shanks says it judges by 1 of mesh-config's 2 (its build CPU bounds it); the // seat says the module's capacity and why. And issue 1890: a running try that names its asks is said with them. u = usageFixture() - u.Pauses["go"] = states(map[string]string{"capacity.shanks": `{"v":1,"effective":1,"configured":2,"quota-from":"the cpu.max of /meshyield.slice","quota-cores":6,"cores-per-step":6,"by-cpu":1,"why":"1, bounded by the CPU","at":"2026-10-26T11:00:00Z"}`, - "capacity.ace": `{"v":1,"effective":2,"configured":3,"why":"stale: mesh-config says 2 now"}`}) + setPauses(&u, "node-build-go", states(map[string]string{"capacity.shanks": `{"v":1,"effective":1,"configured":2,"quota-from":"the cpu.max of /meshyield.slice","quota-cores":6,"cores-per-step":6,"by-cpu":1,"why":"1, bounded by the CPU","at":"2026-10-26T11:00:00Z"}`, + "capacity.ace": `{"v":1,"effective":2,"configured":3,"why":"stale: mesh-config says 2 now"}`})) u.Tries = states(map[string]string{"try-" + strconv.FormatInt(at("11:58").UnixNano(), 10): `{"id":"try-2","repository":"novox/mesh-delivery","head":"h","trunk":"t","state":"running","asks":["` + tryAsk0 + `"],"steps":[{"ask":"` + tryAsk0 + `","node":"shanks","state":"running","phase":"unit: tests of 3 packages"}]}`}) bound := u.Usage() sc := seatOf(t, bound, "shanks", "node-build-go") @@ -208,13 +221,13 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) { t.Errorf("a module's word older than mesh-config's value is not taken: %+v", sa) } // The operator, 2026-10-26: outside its build hours the seat takes nothing new, and says so. - u.Pauses["go"] = states(map[string]string{"capacity.shanks": `{"v":1,"effective":0,"configured":2,"build-hours":"18:00-06:00 CET","outside-hours":true,"why":"0, outside its build hours 18:00-06:00 CET until 18:00 (otherwise 2)","at":"2026-10-26T11:00:00Z"}`}) + setPauses(&u, "node-build-go", states(map[string]string{"capacity.shanks": `{"v":1,"effective":0,"configured":2,"build-hours":"18:00-06:00 CET","outside-hours":true,"why":"0, outside its build hours 18:00-06:00 CET until 18:00 (otherwise 2)","at":"2026-10-26T11:00:00Z"}`})) if so := seatOf(t, u.Usage(), "shanks", "node-build-go"); so.Capacity != 0 || !so.OutsideHours || so.Eligible == nil || *so.Eligible || so.WhyNot != "outside its build hours 18:00-06:00 CET on shanks: it takes nothing new until they open" { t.Errorf("a seat outside its build hours: %+v", so) } // A build-hours value the module cannot read: it builds at any hour, and the seat says the value and why. - u.Pauses["go"] = states(map[string]string{"capacity.shanks": `{"v":1,"effective":2,"configured":2,"build-hours-fault":"the build hours \"shanks=18:00-6\": \"6\" is not a time HH:MM","why":"2; at any hour, because its build hours cannot be read","at":"2026-10-26T11:00:00Z"}`}) + setPauses(&u, "node-build-go", states(map[string]string{"capacity.shanks": `{"v":1,"effective":2,"configured":2,"build-hours-fault":"the build hours \"shanks=18:00-6\": \"6\" is not a time HH:MM","why":"2; at any hour, because its build hours cannot be read","at":"2026-10-26T11:00:00Z"}`})) if sf := seatOf(t, u.Usage(), "shanks", "node-build-go"); sf.BuildHours != "any hour" || !strings.Contains(sf.BuildHoursFault, "shanks=18:00-6") || sf.Eligible == nil || !*sf.Eligible { t.Errorf("a seat whose build hours cannot be read: %+v", sf) } @@ -225,3 +238,25 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) { } var tryAsk0 = stepID(at("11:58"), "a00010") + +// buildSeatsOf is the build seats as the seat definitions and the seat list give them (design 47 G A-82): the seats whose +// acting rule builds, each held by the module the seat list names. +func buildSeatsOf() *buildseats.BuildSeats { + rule := func(on string) seatdef.Definition { + return seatdef.Definition{Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: on}}} + } + defs := map[string]seatdef.Definition{"node-build-go": rule("go"), "node-build-node": rule("typescript"), "node-build-image": rule(seatdef.OnKindImage)} + holders := map[string]string{"node-build-go": "mesh-build-go", "node-build-node": "mesh-build-node", "node-build-image": "mesh-build-image"} + return buildseats.NewBuildSeats(func() map[string]seatdef.Definition { return defs }, func(seat string) (string, bool) { h, ok := holders[seat]; return h, ok }) +} + +// setPauses replaces what one build seat's pauses read. +func setPauses(u *UsageReader, seat string, st StateRead) { + prev := u.PausesOf + u.PausesOf = func(s string) StateRead { + if s == seat { + return st + } + return prev(s) + } +} diff --git a/modules/mesh-delivery-view/manifest_test.go b/modules/mesh-delivery-view/manifest_test.go index bf6b6067..d0e3e918 100644 --- a/modules/mesh-delivery-view/manifest_test.go +++ b/modules/mesh-delivery-view/manifest_test.go @@ -106,7 +106,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 if m.Module != "mesh-delivery-view" { t.Errorf("module = %q", m.Module) } - want := []string{"mesh-build-go.pauses", "mesh-build-go.steps", "mesh-build-image.pauses", "mesh-build-image.steps", "mesh-build-node.pauses", "mesh-build-node.steps", "mesh-conditions.conditions", "mesh-config.resolved", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "mesh-healers.attempts", "mesh-provisioner.provisions", "mesh-vault.signers", "node-engine.have"} // no reads of the indexer's or the assigner's states (ADR 0615 §1, DLV-M4); view_tiers reads the module graph and the provisioner's keys (ADR 0603 §6); view_status reads the keeper's conditions and the healers' attempts (1952/V1); build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9 + want := []string{"mesh-conditions.conditions", "mesh-config.resolved", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "mesh-healers.attempts", "mesh-provisioner.provisions", "mesh-vault.signers", "node-build-go.pauses", "node-build-go.steps", "node-build-image.pauses", "node-build-image.steps", "node-build-node.pauses", "node-build-node.steps", "node-engine.have"} // the build seats' states by seat, never a build module (design 47 G A-82); // no reads of the indexer's or the assigner's states (ADR 0615 §1, DLV-M4); view_tiers reads the module graph and the provisioner's keys (ADR 0603 §6); view_status reads the keeper's conditions and the healers' attempts (1952/V1); build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9 got := slices.Clone(m.Reads) slices.Sort(got) if !reflect.DeepEqual(got, want) { @@ -124,7 +124,9 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 "mesh-delivery-indexer.indexer_read_graph", "mesh-delivery-indexer.indexer_read_heard", "mesh-delivery-indexer.indexer_pending", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_read_predefined", "mesh-delivery-indexer.indexer_read_taken", "seat:mesh-catalog.revision", // ADR 0663: the modules are the catalog half's, read through the seat's verb revision, never the indexer's + "seat:mesh-catalog.seat-definitions", // design 47 G A-82, §204: the build seats are read from the seat list "seat:mesh-hand-acts.hand-act", // the join: its first view asked again by hand is a hand act (views.StatusTools) + "seat:mesh-module-assigner.assignments", // A-82: the holder of each build seat "seat:node-engine.manifest-reads"}; !reflect.DeepEqual(m.Invokes, want) { // ADR 0618 §3: node_manifest_reads asks the node-engine seat's verb of every node; 1585/T24: view_tiers asks indexer_pending, a verb edge (ADR 0614 §2) t.Errorf("invokes = %v, want %v", m.Invokes, want) } @@ -139,7 +141,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 t.Errorf("version = %q, want 0.1", m.Version) } if want := []string{"mesh-conditions.findings-asked", "mesh-delivery-assigner.views.asks", "mesh-delivery-assigner.views.assignments", "mesh-delivery-assigner.views.wants", - "mesh-catalog.catalog-revision", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.taken"}; !reflect.DeepEqual(m.Consumes, want) { + "mesh-catalog.catalog-revision", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.taken"}; !reflect.DeepEqual(m.Consumes, want) { t.Errorf("consumes = %v, want %v", m.Consumes, want) } // The one event the view says: a source revision is delivered (1906/D9, ADR 0539 §17), derived by it from the wants and the haves. @@ -164,7 +166,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 } // The two read tools of the indexer's modules (issue 1562), carried by a bundle the runtime launches; the indexer // itself serves no tool (ADR 0437 §4). Carrying them gives the tool runner read access to every bucket the view reads. - if want := []string{"view_status", "indexer_module", "indexer_modules", "indexer_candidate", "indexer_candidates", "indexer_predefined", "indexer_config_candidates", "indexer_packages", "builder_build", "builder_builds", "builder_history", "assigner_wants", "assigner_want", "assigner_differences", "node_haves", "node_trusts", "build_usage", "view_reach", "indexer_module_history", "indexer_heard", "view_tiers", "view_first_views", "view_first_view_again", "node_manifest_reads"}; !reflect.DeepEqual(m.Tools, want) { + if want := []string{"view_status", "indexer_module", "indexer_modules", "indexer_candidate", "indexer_candidates", "indexer_predefined", "indexer_config_candidates", "indexer_packages", "builder_build", "builder_builds", "builder_history", "assigner_wants", "assigner_want", "assigner_differences", "node_haves", "node_trusts", "build_usage", "view_reach", "indexer_module_history", "indexer_heard", "view_tiers", "view_first_views", "view_first_view_again", "node_manifest_reads", "view_seat_list", "view_seat_list_again"}; !reflect.DeepEqual(m.Tools, want) { t.Errorf("tools = %v, want %v", m.Tools, want) } // D-11: the holders and buckets the view names are its manifest's (no contract names a holder, novox/hq ADR 0634 §1): a diff --git a/modules/mesh-delivery-view/module.json b/modules/mesh-delivery-view/module.json index feb4694c..cb6231ff 100644 --- a/modules/mesh-delivery-view/module.json +++ b/modules/mesh-delivery-view/module.json @@ -27,7 +27,9 @@ "view_tiers", "view_first_views", "view_first_view_again", - "node_manifest_reads" + "node_manifest_reads", + "view_seat_list", + "view_seat_list_again" ], "emits": [ "delivered", @@ -56,6 +58,8 @@ "mesh-delivery-assigner.views.assignments", "mesh-delivery-assigner.views.wants", "mesh-catalog.catalog-revision", + "mesh-catalog.seat-definition-changed", + "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.taken" ], @@ -73,16 +77,18 @@ "mesh-delivery-indexer.indexer_read_predefined", "mesh-delivery-indexer.indexer_read_taken", "seat:mesh-catalog.revision", + "seat:mesh-catalog.seat-definitions", "seat:mesh-hand-acts.hand-act", + "seat:mesh-module-assigner.assignments", "seat:node-engine.manifest-reads" ], "reads": [ - "mesh-build-go.pauses", - "mesh-build-go.steps", - "mesh-build-image.pauses", - "mesh-build-image.steps", - "mesh-build-node.pauses", - "mesh-build-node.steps", + "node-build-go.pauses", + "node-build-go.steps", + "node-build-image.pauses", + "node-build-image.steps", + "node-build-node.pauses", + "node-build-node.steps", "mesh-config.resolved", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds",