No client names a holder: each reader keeps its own constant, held to its manifest (ADR 0625 section 4, issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 18:37:28 +01:00
parent a00c780352
commit c3754e48c8
5 changed files with 56 additions and 35 deletions
+7 -7
View File
@@ -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, assigner.SeatHoldersRead+" (who holds which seat by placement could not be read: "+
l.NotAnswering = append(l.NotAnswering, firstview.SeatHoldersRead+" (who holds which seat by placement 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>` (assigner.PlacementRead and the
// placement and seat-holders, the indexer's modules and manifests, by `<owner>.<state>` (firstview.PlacementRead 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.
@@ -369,9 +369,9 @@ type Source interface {
Entry(name, key string) ([]byte, error)
}
// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as assigner.SeatHoldersRead.
// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as firstview.SeatHoldersRead.
func seatHoldersOn(src Source) (map[string][]byte, error) {
return src.State(assigner.SeatHoldersRead)
return src.State(firstview.SeatHoldersRead)
}
// The indexer's states the console reads (novox/hq ADR 0571 clauses 4 and 5): every module, and each manifest by its
@@ -394,9 +394,9 @@ var manifestsKept sync.Map // digest → manifestFacts
// and every machine placement names (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(assigner.PlacementRead)
placements, err := src.State(firstview.PlacementRead)
if err != nil {
why = append(why, assigner.PlacementRead+" (where modules are placed could not be read, so what is missing cannot "+
why = append(why, firstview.PlacementRead+" (where modules are placed could not be read, so what is missing cannot "+
"be said: "+err.Error()+")")
}
machines = assigner.MachineNames(placements)
@@ -404,7 +404,7 @@ func recordsOn(src Source) (recorded []recordedModule, machines []string, why []
// 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)
if err != nil {
why = append(why, assigner.PlacementRead+" (not every key is a placement: "+err.Error()+")")
why = append(why, firstview.PlacementRead+" (not every key is a placement: "+err.Error()+")")
}
for _, node := range machines {
for _, m := range assembled[node].Modules {
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/novox/mesh-tools/node-tools/internal/announce"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/meshtest"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
@@ -62,7 +63,7 @@ func TestARuntimeThatAnswersLateIsFoundAndOneThatNeverDoesIsNamed(t *testing.T)
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.
placedKV := mesh.Bucket(t, assigner.SeatHoldersBucket)
placedKV := mesh.Bucket(t, meshtest.SeatHoldersBucket)
holdersKey, err := assigner.SeatHoldersKey("mesh-wants", "")
if err != nil {
t.Fatal(err)
+11 -9
View File
@@ -11,13 +11,13 @@ import (
"context"
"encoding/json"
"fmt"
conditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"sort"
"sync"
"time"
conditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
"git.novox.be/novox/mesh-sdk/go/views"
"github.com/novox/mesh-tools/node-tools/internal/bus"
@@ -26,14 +26,16 @@ import (
// The owners and their first-view tools (the tools' addresses are `<owner>.<tool>`).
const (
Indexer = "mesh-delivery-indexer"
Assigner = assigner.Module
Assigner = "mesh-delivery-assigner"
)
// The names the console reads the indexer's two states by (`<owner>.<state>`), as a manifest's `reads` names them; the
// assigner's two are its client's (assigner.PlacementRead, assigner.SeatHoldersRead).
// The names the console reads the four states by (`<owner>.<state>`), as the manifest's `consumes` names their views
// families (no seat's client names a holder, novox/hq ADR 0625 §4; TestTheFirstViewsAreTheManifestsViews).
const (
ModulesRead = Indexer + ".modules"
ManifestsRead = Indexer + ".manifests"
PlacementRead = Assigner + ".placement"
SeatHoldersRead = Assigner + ".seat-holders"
ModulesRead = Indexer + ".modules"
ManifestsRead = Indexer + ".manifests"
)
// Spec is one first view the runner holds.
@@ -47,7 +49,7 @@ var Specs = []Spec{
{Indexer, "modules", "modules"},
}
// Name is the name a state is read by, `<owner>.<state>`: assigner.PlacementRead, assigner.SeatHoldersRead and the indexer's.
// Name is the name a state is read by, `<owner>.<state>`: PlacementRead, SeatHoldersRead and the indexer's.
func (s Spec) Name() string { return s.Owner + "." + s.State }
// Views is the four followers.
@@ -4,12 +4,13 @@ import (
"context"
"encoding/json"
"errors"
"os"
"slices"
"strings"
"sync"
"testing"
"time"
assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
"git.novox.be/novox/mesh-sdk/go/views"
"github.com/novox/mesh-tools/node-tools/internal/bus"
@@ -168,15 +169,27 @@ func TestTheRunnerHoldsTheFourStatesFromTheOwnersFirstViewsAndTheirEvents(t *tes
}
}
// The four first views are read by the names the seats' clients give the states, and the console reads the indexer's
// two by the names here.
func TestTheFirstViewsAreNamedAsTheClientsNameTheirStates(t *testing.T) {
want := map[string]bool{assigner.PlacementRead: true, assigner.SeatHoldersRead: true, ModulesRead: true, ManifestsRead: true}
// The four first views are the manifest's `consumes` families, and the console reads each by the name here.
func TestTheFirstViewsAreTheManifestsViews(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Consumes []string `json:"consumes"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
want := map[string]bool{PlacementRead: true, SeatHoldersRead: true, ModulesRead: true, ManifestsRead: true}
for _, s := range Specs {
if !want[s.Name()] {
t.Errorf("the first view %s is read by no name a client gives", s.Name())
t.Errorf("the first view %s is read by no name here", s.Name())
}
delete(want, s.Name())
if view := s.Owner + ".views." + s.Tool; !slices.Contains(m.Consumes, view) {
t.Errorf("the first view %s has no family in the manifest's consumes %v", view, m.Consumes)
}
}
if len(want) > 0 {
t.Errorf("no first view reads %v", want)
+17 -12
View File
@@ -13,11 +13,6 @@ package meshtest
import (
"context"
"encoding/json"
assigner "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
seatplacement "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
meshcatalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/membership"
"os"
"path/filepath"
"runtime"
@@ -26,6 +21,12 @@ 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"
meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
meshcatalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/membership"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -155,19 +156,23 @@ func sdkMembership(t *testing.T, mem bus.Membership) membership.Membership {
// The indexer's two states the console reads, and their buckets (the test bed's own names: the indexer's client names
// no holder).
const (
indexer = "mesh-delivery-indexer"
ModulesBucket = indexer + "_modules"
ManifestsBucket = indexer + "_manifests"
indexer = "mesh-delivery-indexer"
assignerHolder = "mesh-delivery-assigner"
// PlacementBucket and SeatHoldersBucket are the assigner's states placement and seat-holders.
PlacementBucket = assignerHolder + "_placement"
SeatHoldersBucket = assignerHolder + "_seat-holders"
ModulesBucket = indexer + "_modules"
ManifestsBucket = indexer + "_manifests"
)
// ConsoleReads are the states a console's runtime reads to say what the mesh holds (novox/hq ADR 0571 clauses 4, 5):
// placement, the indexer's modules and manifests, and the seat holders.
func ConsoleReads() []bus.StateIssued {
return []bus.StateIssued{
{Name: assigner.PlacementRead, Bucket: assigner.PlacementBucket},
{Name: assignerHolder + ".placement", Bucket: PlacementBucket},
{Name: indexer + ".modules", Bucket: ModulesBucket},
{Name: indexer + ".manifests", Bucket: ManifestsBucket},
{Name: assigner.SeatHoldersRead, Bucket: assigner.SeatHoldersBucket},
{Name: assignerHolder + ".seat-holders", Bucket: SeatHoldersBucket},
}
}
@@ -183,8 +188,8 @@ func (m *Mesh) Placed(t *testing.T, tools map[string][]string, on map[string][]s
}
return b
}
modules, manifests, placement := kv(ModulesBucket), kv(ManifestsBucket), kv(assigner.PlacementBucket)
kv(assigner.SeatHoldersBucket)
modules, manifests, placement := kv(ModulesBucket), kv(ManifestsBucket), kv(PlacementBucket)
kv(SeatHoldersBucket)
for module, declared := range tools {
manifest, _ := json.Marshal(map[string]any{"module": module, "tools": declared})
digest := meshcatalogclient.ManifestDigest(manifest)