The console reads the assigner's state assignments and imports the contracts' new paths (issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -17,8 +17,8 @@ package console
|
||||
// session — so a tool that arrived a minute ago is found without the client reconnecting.
|
||||
|
||||
import (
|
||||
assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
assigner "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
|
||||
@@ -55,7 +55,7 @@ const (
|
||||
// searchCap is how many matches a search answers before it says how many more there were.
|
||||
const searchCap = 25
|
||||
|
||||
const grammar = "Addresses: `<seat>.<verb>` for a seat held once for the mesh (e.g. `mesh-module-assigner.placement`); " +
|
||||
const grammar = "Addresses: `<seat>.<verb>` for a seat held once for the mesh (e.g. `mesh-module-assigner.assignments`); " +
|
||||
"`<node>/<seat>.<verb>` for a seat every machine holds (e.g. `ace/node-packet-filter.rules`); " +
|
||||
"`<node>/<module>.<tool>` for a module on one machine (e.g. `novox/postgres.postgres_list_databases`); " +
|
||||
"and `<module>.<tool>` also for a module whose instances are interchangeable."
|
||||
@@ -96,7 +96,7 @@ func discovery() []map[string]any {
|
||||
"for when an address is said to be missing or on another machine."},
|
||||
{"name": verbFirstViews, "inputSchema": obj(map[string]any{}),
|
||||
"description": "The tool runner's first views of the four states it reads of the indexer and the assigner (the " +
|
||||
"assigner's placement and seat-holders, the indexer's manifests and modules): for each, the owner, the tool " +
|
||||
"assigner's assignments and seat-holders, the indexer's manifests and modules): for each, the owner, the tool " +
|
||||
"asked, the revision held and when it was made, how many keys, when it was asked and answered, how often " +
|
||||
"it was asked and how often by hand, whether its condition is raised, and why it was last asked. Read-only."},
|
||||
{"name": verbFirstAgain, "inputSchema": obj(map[string]any{
|
||||
@@ -145,7 +145,7 @@ type index struct {
|
||||
NotAnswering []string
|
||||
Listing *Listing // the flat catalogue the call path resolves subjects with
|
||||
Discovery announce.Discovery
|
||||
// Recorded is where placement places each module that declares tools.
|
||||
// Recorded is where the assigner places each module that declares tools.
|
||||
Recorded map[string][]string
|
||||
// Unheard is every runtime whose answer this index lacks: it said it was there and did not say
|
||||
// what it serves in time, or it answered before and not now. An address it might answer is never
|
||||
@@ -245,7 +245,7 @@ func findTool(tools []Tool, name string) *Tool {
|
||||
return nil
|
||||
}
|
||||
|
||||
// recordedModule is a module as the bus holds it: where the placement seat places it, and whether its manifest declares
|
||||
// recordedModule is a module as the bus holds it: where the assigner seat places it, and whether its manifest declares
|
||||
// tools — what should announce itself, and where (novox/hq ADR 0571).
|
||||
type recordedModule struct {
|
||||
Module string `json:"module"`
|
||||
@@ -271,9 +271,9 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ
|
||||
var recordsWhy []string
|
||||
wg.Add(2)
|
||||
// Which modules there are and where they are placed, and which machines the mesh has, from the bus (ADR 0571
|
||||
// clauses 4 and 5): the indexer's modules and manifests and the placement seat's placements, never any other record.
|
||||
// clauses 4 and 5): the indexer's modules and manifests and the assigner seat's assignments, never any other record.
|
||||
go func() { defer wg.Done(); recorded, known, recordsWhy = recordsOn(src) }()
|
||||
// Who holds which seat on which node, from placement (ADR 0571 clause 4): no other call.
|
||||
// Who holds which seat on which node, from the assigner (ADR 0571 clause 4): no other call.
|
||||
go func() { defer wg.Done(); placed, placedErr = seatHoldersOn(src) }()
|
||||
d, err := announce.Gather(conn, held)
|
||||
wg.Wait()
|
||||
@@ -340,7 +340,7 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ
|
||||
}
|
||||
l.NotAnswering = append(l.NotAnswering, recordsWhy...)
|
||||
if x.PlacedWhy != "" {
|
||||
l.NotAnswering = append(l.NotAnswering, firstview.SeatHoldersRead+" (who holds which seat by placement could not be read: "+
|
||||
l.NotAnswering = append(l.NotAnswering, firstview.SeatHoldersRead+" (who holds which seat by assignment could not be read: "+
|
||||
x.PlacedWhy+")")
|
||||
}
|
||||
for _, u := range x.Unheard {
|
||||
@@ -360,7 +360,7 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ
|
||||
}
|
||||
|
||||
// Source is where the console reads the four states of the indexer and the assigner it indexes the mesh by: the assigner's
|
||||
// placement and seat-holders, the indexer's modules and manifests, by `<owner>.<state>` (firstview.PlacementRead and the
|
||||
// assignments and seat-holders, the indexer's modules and manifests, by `<owner>.<state>` (firstview.AssignmentsRead and the
|
||||
// rest). The tool runner's is its first views (firstview.Views), never the owners' state (novox/hq ADR 0615 §1).
|
||||
type Source interface {
|
||||
// State is every current value of a state by key; an error says why it cannot be told yet.
|
||||
@@ -390,21 +390,21 @@ type manifestFacts struct {
|
||||
|
||||
var manifestsKept sync.Map // digest → manifestFacts
|
||||
|
||||
// recordsOn is every module the indexer holds, with where the placement seat places it and whether it declares tools,
|
||||
// and every machine placement names (moduleNames, assigner.MachineNames). What cannot be read is said, one line
|
||||
// recordsOn is every module the indexer holds, with where the assigner seat places it and whether it declares tools,
|
||||
// and every machine the assignments name (moduleNames, assigner.MachineNames). What cannot be read is said, one line
|
||||
// per state, for the listing's not answering.
|
||||
func recordsOn(src Source) (recorded []recordedModule, machines []string, why []string) {
|
||||
placements, err := src.State(firstview.PlacementRead)
|
||||
held, err := src.State(firstview.AssignmentsRead)
|
||||
if err != nil {
|
||||
why = append(why, firstview.PlacementRead+" (where modules are placed could not be read, so what is missing cannot "+
|
||||
why = append(why, firstview.AssignmentsRead+" (where modules are placed could not be read, so what is missing cannot "+
|
||||
"be said: "+err.Error()+")")
|
||||
}
|
||||
machines = assigner.MachineNames(placements)
|
||||
machines = assigner.MachineNames(held)
|
||||
on := map[string][]string{}
|
||||
// The placement's keys in either form, a node key and its module keys or the form before (research 072 row 37).
|
||||
assembled, err := assigner.PlacementsOf(placements)
|
||||
// The assignments' keys in either form, a node key and its module keys or the form before (research 072 row 37).
|
||||
assembled, err := assigner.AssignmentsOf(held)
|
||||
if err != nil {
|
||||
why = append(why, firstview.PlacementRead+" (not every key is a placement: "+err.Error()+")")
|
||||
why = append(why, firstview.AssignmentsRead+" (not every key is an assignment: "+err.Error()+")")
|
||||
}
|
||||
for _, node := range machines {
|
||||
for _, m := range assembled[node].Modules {
|
||||
@@ -473,7 +473,7 @@ func manifestOf(src Source, digest string) (manifestFacts, error) {
|
||||
return facts, nil
|
||||
}
|
||||
|
||||
// placedHolders is who holds seat (on node, for a seat held per machine) by placement, or none.
|
||||
// placedHolders is who holds seat (on node, for a seat held per machine) by assignment, or none.
|
||||
func (x *index) placedHolders(seat, node string) []meshmoduleassigner.SeatHoldersHoldersItem {
|
||||
if len(x.Placed) == 0 {
|
||||
return nil
|
||||
@@ -725,7 +725,7 @@ func resolve(x *index, address string) (target, error) {
|
||||
for _, h := range hs {
|
||||
held = append(held, h.Module+" on "+h.Node)
|
||||
}
|
||||
return target{}, fmt.Errorf("the seat %s is held by %s by placement, but no holder of it announced itself on the bus. "+
|
||||
return target{}, fmt.Errorf("the seat %s is held by %s by assignment, but no holder of it announced itself on the bus. "+
|
||||
"Ask again in a moment; mesh_runtimes shows which runtimes answered", prefix, strings.Join(held, ", "))
|
||||
}
|
||||
if len(x.Unheard) > 0 {
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestTheMeshsToolsAreFoundByAddress(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(stopNodes)
|
||||
// What the mesh holds, as the indexer and placement write it (ADR 0571 clauses 4, 5): never the controller's records.
|
||||
// What the mesh holds, as the indexer and the assigner write it (ADR 0571 clauses 4, 5): never the controller's records.
|
||||
tools := map[string][]string{"alpha": {"one", "two"}, "beta": {"three"}, "gamma": {"given"}, "delta": nil,
|
||||
"epsilon": {"seven"}, "mesh-module-assigner": {"nodes"}}
|
||||
mesh.Placed(t, tools, map[string][]string{"desk": {"alpha", "beta", "delta"}, "bench": {"epsilon", "mesh-module-assigner"}})
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestTheConsoleListsAndCallsOverHTTP(t *testing.T) {
|
||||
}
|
||||
defer stop()
|
||||
|
||||
// What the mesh holds, as the indexer and placement write it (ADR 0571): ghost declares tools on desk and nothing
|
||||
// What the mesh holds, as the indexer and the assigner write it (ADR 0571): ghost declares tools on desk and nothing
|
||||
// announces it.
|
||||
mesh.Placed(t, map[string][]string{"alpha": {"one", "two"}, "beta": {"three"}, "ghost": {"boo"}},
|
||||
map[string][]string{"desk": {"alpha", "beta", "ghost"}})
|
||||
|
||||
@@ -17,7 +17,7 @@ type viewer struct {
|
||||
}
|
||||
|
||||
func (v *viewer) Status() []views.Status {
|
||||
return []views.Status{{Owner: "mesh-delivery-assigner", Tool: "placement", State: "placement", Revision: 4, Asks: 1}}
|
||||
return []views.Status{{Owner: "mesh-delivery-assigner", Tool: "assignments", State: "assignments", Revision: 4, Asks: 1}}
|
||||
}
|
||||
|
||||
func (v *viewer) Again(_ context.Context, why string) []error {
|
||||
@@ -35,7 +35,7 @@ func TestTheConsoleShowsAndAsksAgainTheRunnersFirstViews(t *testing.T) {
|
||||
v := &viewer{}
|
||||
var recorded []map[string]any
|
||||
s := &Surface{src: v, who: "desk.node-tools", Record: func(a map[string]any) error { recorded = append(recorded, a); return nil }}
|
||||
if got := text(s.discover(context.Background(), "mesh_first_views", nil)); !strings.Contains(got, "mesh-delivery-assigner") || !strings.Contains(got, "placement") {
|
||||
if got := text(s.discover(context.Background(), "mesh_first_views", nil)); !strings.Contains(got, "mesh-delivery-assigner") || !strings.Contains(got, "assignments") {
|
||||
t.Errorf("mesh_first_views shows %s", got)
|
||||
}
|
||||
got := s.discover(context.Background(), "mesh_first_view_again", map[string]any{"why": "doubted"})
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestTheManifestNamesTheFirstFamiliesOneByOne(t *testing.T) {
|
||||
}
|
||||
// A wait is no consumer (ADR 0367 §2): what it consumes is the four views families of its first views (ADR 0615 §5, issue
|
||||
// 1966 TLS-5) and the two events of the seat that serves the seat list (ADR 0636 §4), and nothing else.
|
||||
wantConsumes := []string{"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.seat-holders",
|
||||
wantConsumes := []string{"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-assigner.views.assignments", "mesh-delivery-assigner.views.seat-holders",
|
||||
"mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules"}
|
||||
if !reflect.DeepEqual(m.Consumes, wantConsumes) {
|
||||
t.Errorf("the tool runner consumes %v, want %v", m.Consumes, wantConsumes)
|
||||
|
||||
@@ -6,7 +6,7 @@ package console
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -70,7 +70,7 @@ type Surface struct {
|
||||
// [{module, node}]}], holders_why}`.
|
||||
const MethodSeats = "mesh/seats"
|
||||
|
||||
// seatsAnswer is MethodSeats' answer: mesh-sdk's seat definitions, with each seat's holders by placement.
|
||||
// seatsAnswer is MethodSeats' answer: mesh-sdk's seat definitions, with each seat's holders by assignment.
|
||||
func (s *Surface) seatsAnswer() map[string]any {
|
||||
placed, err := seatHoldersOn(s.src)
|
||||
x := &index{Placed: placed}
|
||||
@@ -88,7 +88,7 @@ func (s *Surface) seatsAnswer() map[string]any {
|
||||
}
|
||||
out := map[string]any{"seats": seats}
|
||||
if err != nil {
|
||||
out["holders_why"] = "who holds which seat by placement could not be read: " + err.Error()
|
||||
out["holders_why"] = "who holds which seat by assignment could not be read: " + err.Error()
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (s *Surface) HandleContext(ctx context.Context, r Request) *Reply {
|
||||
return answer(r.ID, map[string]any{})
|
||||
case MethodSeats:
|
||||
// The seats' contract and who holds each, for a client on this machine that has no seat definitions of its own
|
||||
// (the TypeScript `mesh`): read in-process from mesh-sdk and from placement, never asked of anything else
|
||||
// (the TypeScript `mesh`): read in-process from mesh-sdk and from the assigner, never asked of anything else
|
||||
// (novox/hq ADR 0571 clauses 4 and 5).
|
||||
return answer(r.ID, s.seatsAnswer())
|
||||
case "tools/list":
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
seatplacement "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
||||
assigner "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
seatassigner "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"github.com/nats-io/nats.go/micro"
|
||||
|
||||
"github.com/novox/mesh-tools/node-tools/internal/announce"
|
||||
@@ -58,18 +58,18 @@ func TestARuntimeThatAnswersLateIsFoundAndOneThatNeverDoesIsNamed(t *testing.T)
|
||||
IndexKept, announce.Patience = time.Minute, 2500*time.Millisecond
|
||||
t.Cleanup(func() { IndexKept, announce.Patience = keptWas, patienceWas })
|
||||
mesh := mt.New(t)
|
||||
// What the mesh holds, as the indexer and placement write it (ADR 0571): never the controller's records.
|
||||
// What the mesh holds, as the indexer and the assigner write it (ADR 0571): never the controller's records.
|
||||
mesh.Placed(t, map[string][]string{"slack": {"slack_check"}, "nap": {"doze"}, "nextcloud-client": {"nextcloud_check"}},
|
||||
map[string][]string{"laptop": {"slack", "nextcloud-client"}, "sleeper": {"nap", "nextcloud-client"},
|
||||
"desktop": {"nextcloud-client"}, "bench": nil})
|
||||
// Who holds which seat, by placement (ADR 0571 clause 4), as the seat mesh-module-assigner's holder composes it.
|
||||
// Who holds which seat, by assignment (ADR 0571 clause 4), as the seat mesh-module-assigner's holder composes it.
|
||||
placedKV := mesh.Bucket(t, meshtest.SeatHoldersBucket)
|
||||
holdersKey, err := assigner.SeatHoldersKey("mesh-wants", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := json.Marshal(seatplacement.SeatHolders{V: assigner.Version, Seat: "mesh-wants", Scope: "mesh",
|
||||
Holders: []seatplacement.SeatHoldersHoldersItem{{Module: "mesh-delivery-assigner", Node: "bench"}}})
|
||||
raw, _ := json.Marshal(seatassigner.SeatHolders{V: assigner.Version, Seat: "mesh-wants", Scope: "mesh",
|
||||
Holders: []seatassigner.SeatHoldersHoldersItem{{Module: "mesh-delivery-assigner", Node: "bench"}}})
|
||||
if _, err := placedKV.Put(holdersKey, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -158,8 +158,8 @@ func TestARuntimeThatAnswersLateIsFoundAndOneThatNeverDoesIsNamed(t *testing.T)
|
||||
|
||||
// The silent one is named, never called missing.
|
||||
for address, want := range map[string]string{
|
||||
// A seat whose holder announced nothing is named with its holder by placement, never called missing.
|
||||
"mesh-wants.read": "the seat mesh-wants is held by mesh-delivery-assigner on bench by placement",
|
||||
// A seat whose holder announced nothing is named with its holder by assignment, never called missing.
|
||||
"mesh-wants.read": "the seat mesh-wants is held by mesh-delivery-assigner on bench by assignment",
|
||||
"sleeper/nap.doze": "nap is assigned to sleeper in the mesh's records, but nothing that answered discovery serves it: discovery did not hear in full from node-tools on sleeper (it answered PING",
|
||||
"sleeper/nextcloud-client.nextcloud_check": "nextcloud-client on sleeper did not answer discovery: discovery did not hear in full from node-tools on sleeper",
|
||||
} {
|
||||
|
||||
Reference in New Issue
Block a user