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 272c728d..2652d635 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 @@ -422,6 +422,21 @@ func holderOf(ask func(key string, body any) (json.RawMessage, error)) func(seat } } +// ofHolder reads a build seat's state (`steps`, `pauses`) in the state of the seat's holder, `.`, the holder found +// as seatlist.HolderOf finds it (design 47 G A-128, A-126), as the manifest's `seat:.` reads name it: the grants +// part resolves a read only in that form. A seat whose holder cannot be found reads as an error naming it. +func ofHolder(holder func(seat string) (string, bool), read func(name string) view.StateRead, state string) func(seat string) view.StateRead { + return func(seat string) view.StateRead { + module, ok := holder(seat) + if !ok { + fail := fmt.Errorf("the holder of %s is not found", seat) + return view.StateRead{Keys: func() ([]string, error) { return nil, fail }, + Get: func(string) (json.RawMessage, error) { return nil, fail }} + } + return read(module + "." + state) + } +} + func main() { logf := func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[mesh-delivery-view-tools] "+f+"\n", a...) } // The catalog half per watched branch, followed by the bundle itself through meshcatalogclient's branch follower on @@ -463,8 +478,8 @@ func main() { }, holderOf(stdio.Ask)) u := view.UsageReader{Haves: r.HavesState, Resolved: read(stdio.State("mesh-config.resolved")), Tries: read(stdio.State("mesh-delivery-checker.tries")), 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)) }, + StepsOf: ofHolder(holderOf(stdio.Ask), func(name string) view.StateRead { return read(stdio.State(name)) }, view.StepsState), + PausesOf: ofHolder(holderOf(stdio.Ask), func(name string) view.StateRead { return read(stdio.State(name)) }, 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. 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 1072a07e..939dbd62 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 @@ -7,6 +7,7 @@ import ( "fmt" "mesh-delivery-view/internal/view" "reflect" + "slices" "strings" "sync/atomic" "testing" @@ -650,3 +651,22 @@ func TestABuildSeatsHolderIsTheOneTheSeatListNames(t *testing.T) { t.Error("a seat list that cannot be read names a holder") } } + +// A build seat's steps and pauses are read in the state of the seat's holder, `.steps` and `.pauses`, the form +// the manifest's `seat:.` reads name and the grants part resolves (design 47 G A-82, A-128), never the seat's own name. +func TestBuildSeatStatesAreReadInTheHoldersState(t *testing.T) { + var named []string + read := func(name string) view.StateRead { + named = append(named, name) + return view.StateRead{Keys: func() ([]string, error) { return nil, nil }} + } + holder := func(seat string) (string, bool) { return "acme-" + seat, seat != "node-build-node" } + ofHolder(holder, read, view.StepsState)("node-build-go") + ofHolder(holder, read, "pauses")("node-build-go") + if want := []string{"acme-node-build-go.steps", "acme-node-build-go.pauses"}; !slices.Equal(named, want) { + t.Errorf("read %v, want %v", named, want) + } + if _, err := ofHolder(holder, read, view.StepsState)("node-build-node").Keys(); err == nil { + t.Error("a seat with no holder reads without an error") + } +} diff --git a/modules/mesh-delivery-view/manifest_test.go b/modules/mesh-delivery-view/manifest_test.go index 4805c4bd..4167c0b4 100644 --- a/modules/mesh-delivery-view/manifest_test.go +++ b/modules/mesh-delivery-view/manifest_test.go @@ -153,7 +153,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 if m.Module != "mesh-delivery-view" { t.Errorf("module = %q", m.Module) } - want := []string{"mesh-conditions.conditions", "mesh-config.resolved", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "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 read of the gate's source: each watched branch's is its verb source's (§512, A-173); // 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-watchman.presence", "mesh-delivery-watchman.stuck", "mesh-healers.attempts", "mesh-provisioner.provisions", "mesh-vault.signers", "node-engine.have", "seat:node-build-go.pauses", "seat:node-build-go.steps", "seat:node-build-image.pauses", "seat:node-build-image.steps", "seat:node-build-node.pauses", "seat:node-build-node.steps"} // the build seats' states by seat, never a build module (design 47 G A-82); no read of the gate's source: each watched branch's is its verb source's (§512, A-173); // 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) { diff --git a/modules/mesh-delivery-view/module.json b/modules/mesh-delivery-view/module.json index a5d0865d..e900155f 100644 --- a/modules/mesh-delivery-view/module.json +++ b/modules/mesh-delivery-view/module.json @@ -121,12 +121,12 @@ "seat:node-engine.manifest-reads" ], "reads": [ - "node-build-go.pauses", - "node-build-go.steps", - "node-build-image.pauses", - "node-build-image.steps", - "node-build-node.pauses", - "node-build-node.steps", + "seat:node-build-go.pauses", + "seat:node-build-go.steps", + "seat:node-build-image.pauses", + "seat:node-build-image.steps", + "seat:node-build-node.pauses", + "seat:node-build-node.steps", "mesh-config.resolved", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds",