The assigner's state and verb placement are assignments, with its events, tools, views and every caller; the assigner copies the retired state at its start (issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:13:20 +01:00
parent 369ebd092d
commit 0eafb2fe95
164 changed files with 1699 additions and 1498 deletions
@@ -4,20 +4,22 @@ import (
"bytes"
"context"
"encoding/json"
"maps"
"mesh-delivery-view/internal/view"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
@@ -27,11 +29,13 @@ import (
func serve(t *testing.T) string {
t.Helper()
m := view.New()
placed, err := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "x"}}})
placed, err := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "x"}}})
if err != nil {
t.Fatal(err)
}
m.Apply(view.Entry{Bucket: view.PlacementBucket, Key: "placement.a", Value: placed})
for _, k := range slices.Sorted(maps.Keys(placed)) { // a key per module, then the node key
m.Apply(view.Entry{Bucket: view.AssignmentsBucket, Key: k, Value: placed[k]})
}
dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c")
if err != nil {
t.Fatal(err)
@@ -13,9 +13,9 @@ import (
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
@@ -6,23 +6,25 @@ import (
"context"
"encoding/json"
"io"
"maps"
"mesh-delivery-view/internal/view"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
@@ -70,7 +72,7 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
kvs := map[string]jetstream.KeyValue{}
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
if err != nil {
t.Fatal(err)
@@ -90,20 +92,22 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8
}
return b
}
// The seat's placement (issue 774, ADR 0549 §3): x and y on a, nothing on b.
placeValue := func(node string, modules ...string) []byte {
p := meshmoduleassigner.Placement{Node: node}
// The seat's assignments (issue 774, ADR 0549 §3): x and y on a, nothing on b.
place := func(node string, modules ...string) {
p := meshmoduleassigner.Assignments{Node: node}
for _, m := range modules {
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
}
v, err := meshmoduleassignerclient.EncodePlacement(p)
values, err := meshmoduleassignerclient.AssignmentsValues(p)
if err != nil {
t.Fatal(err)
}
return v
for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key
put(view.AssignmentsBucket, k, values[k])
}
}
put(view.PlacementBucket, "placement.a", placeValue("a", "x", "y"))
put(view.PlacementBucket, "placement.b", placeValue("b"))
place("a", "x", "y")
place("b")
put(view.HaveBucket, "a.x", haveValue("b1", nodeengineclient.PhaseChecking, nodeengineclient.HealthWaits))
put(view.HaveBucket, "b.x", haveValue("b1", nodeengineclient.PhaseRunning, nodeengineclient.HealthOK))
@@ -182,7 +186,7 @@ func TestProcessWholeShowsWhatTheWatchmanSays(t *testing.T) { // 1168/AC5
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
kvs := map[string]jetstream.KeyValue{}
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
if err != nil {
t.Fatal(err)
@@ -269,7 +273,7 @@ func TestProcessWholeShowsTheIndexersBucketsOnceTheyExist(t *testing.T) { // 124
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}); err != nil {
t.Fatal(err)
}
@@ -1,4 +1,4 @@
// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, the seat's placement
// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, the seat's assignments
// and builds buckets on its own bus credential, derives one row per module and node in memory, and serves them on a
// local socket (for mesh-delivery-cli) and on a local web page. It starts, stops or retries nothing; the one thing it writes is the operator's answer to an ask that needs no proof of who
// answered, under `<ask key>.view` in its bucket answers (ADR 0528 clause 6).
@@ -21,9 +21,9 @@ import (
"syscall"
"time"
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/views"
@@ -126,7 +126,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}
}
open := buckets{js: js, reader: reader}
for _, bucket := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.ModulesBucket, view.AsksBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} {
for _, bucket := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.ModulesBucket, view.AsksBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} {
go follow(ctx, open, bucket, false, model, logger)
}
// The modules' and the source's history give each fingerprint's first commit and each head's source revision (ADR 0556).
@@ -185,7 +185,7 @@ var (
// owner's read tool (as the view's invokes names it) and views family (its events on the shared events stream); the
// view's tools ask the other six through the owners' read tools on each call.
var ownersFollowed = []struct{ owner, state, tool string }{
{view.AssignerModule, view.PlacementState, view.AssignerModule + ".assigner_read_placement"},
{view.AssignerModule, view.AssignmentsState, view.AssignerModule + ".assigner_read_assignments"},
{view.AssignerModule, "wants", view.AssignerModule + ".assigner_read_wants"},
{view.AssignerModule, "asks", view.AssignerModule + ".assigner_read_asks"},
{view.IndexerModule, "candidates", view.IndexerModule + ".indexer_read_candidates"},