W16.5: claude-code reads which modules exist and their manifests from the catalog half through meshcatalogclient's branch follower (milestone 47/G)

Builds: design 47 G §196, §594, §614; lead answers A-5, A-15

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 23:25:52 +01:00
parent 0098960b5a
commit 7278e509f4
7 changed files with 233 additions and 68 deletions
@@ -0,0 +1,60 @@
package main
// claude-code's reading of which modules exist and of each placed module's manifest (design 47 G §196, §594, §614): the
// catalog half, taken through meshcatalogclient's follower of the watched branches on the part's one views reader. A
// module's manifest is the one its catalog graph entry carries whole (§443), never the indexer's state `modules` or
// `manifests`.
import (
"fmt"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
)
// CatalogHalf is what this module reads of the catalog half.
type CatalogHalf interface {
// Entry is a module's entry at the trunk; an error while a watched branch has no first view yet, never "no module".
Entry(module string) (catalog.CatalogEntry, bool, error)
// Ready is nil once every watched branch has its first view (none-yet counting as one), else says what it waits for.
Ready() error
}
// branchesHalf is the CatalogHalf over the follower of the watched branches.
type branchesHalf struct{ b *meshcatalogclient.Branches }
func (h branchesHalf) Ready() error {
followed := h.b.Followed()
if len(followed) == 0 {
return fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)")
}
var waiting []string
for _, branch := range followed {
if c, ok := h.b.Catalog(branch); ok {
select {
case <-c.Ready():
default:
waiting = append(waiting, branch)
}
}
}
if len(waiting) > 0 {
return fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", "))
}
return nil
}
func (h branchesHalf) Entry(module string) (catalog.CatalogEntry, bool, error) {
if err := h.Ready(); err != nil {
return catalog.CatalogEntry{}, false, err
}
for _, branch := range h.b.Followed() { // the branch followed first names a module more than one hold
if c, ok := h.b.Catalog(branch); ok {
if e, ok := c.Entry(module); ok {
return e, true, nil
}
}
}
return catalog.CatalogEntry{}, false, nil
}
@@ -0,0 +1,90 @@
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/views"
)
// wiringOf is a part's reader in miniature: it keeps the followers adopted, so a test starts them as the reader's Start does.
type wiringOf struct {
mu sync.Mutex
fs []*views.Follower
}
func (w *wiringOf) Adopt(f *views.Follower, _, _ string, _ func(context.Context, []byte) error) error {
w.mu.Lock()
defer w.mu.Unlock()
w.fs = append(w.fs, f)
return nil
}
func (w *wiringOf) Drop(string, string) error { return nil }
// Design 47 G §196, §594: which modules exist, and the manifest of each, are the catalog half's; a branch with no first
// view yet is an error naming it, never no module, and an entry's manifest is the one it carries whole.
func TestCatalogHalfIsTheWatchedBranchesNewestRevision(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
answered := false
var mu sync.Mutex
call := func(key string, _ any) (json.RawMessage, error) {
mu.Lock()
defer mu.Unlock()
switch key {
case meshcatalogclient.RegistrationsVerb:
return json.Marshal(catalog.RegistrationsAnswer{Registrations: []catalog.Registration{{Module: "m", Version: 1, Branches: []string{"main"}}}})
case meshcatalogclient.SourceVerb:
return json.Marshal(delivery.SourceAnswer{})
case meshcatalogclient.RevisionVerb:
if !answered {
return nil, fmt.Errorf("the holder is away")
}
return json.Marshal(catalog.RevisionAnswer{Revision: &catalog.CatalogRevision{Revision: 1, Kind: meshcatalogclient.KindTrunk},
Entries: []catalog.CatalogEntry{{Module: "pg", Manifest: map[string]any{"module": "pg"}}}})
}
return nil, fmt.Errorf("asked %s", key)
}
w := &wiringOf{}
b, err := meshcatalogclient.NewBranches(w, call)
if err != nil {
t.Fatal(err)
}
h := branchesHalf{b}
if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no watched branch") {
t.Fatalf("before the registrations: %v, want an error naming the missing branch", err)
}
go func() { _ = w.fs[0].Start(ctx) }() // the registrations
for len(b.Followed()) == 0 {
select {
case <-ctx.Done():
t.Fatal("the registrations' branch is never followed")
case <-time.After(10 * time.Millisecond):
}
}
if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no first view of main") {
t.Fatalf("with no first view: %v, want an error naming the branch, never no module", err)
}
mu.Lock()
answered = true
mu.Unlock()
c, _ := b.Catalog("main")
if err := c.Start(ctx); err != nil {
t.Fatal(err)
}
got, ok, err := h.Entry("pg")
if err != nil || !ok || got.Manifest["module"] != "pg" || h.Ready() != nil {
t.Fatalf("with the first view: %v, %v, %v", got, ok, err)
}
if _, ok, err := h.Entry("absent"); ok || err != nil {
t.Fatalf("a module the half does not list: %v, %v", ok, err)
}
}
@@ -277,9 +277,9 @@ func TestTheManifestListsEveryToolServed(t *testing.T) {
_ = json.Unmarshal(raw, &m)
// The hand-act seat's warranted and hand-act, the seat mesh-catalog's seat list (ADR 0635 §1), and the owners' read tools of the three followed states; no verb of
// the controller, and no read of the indexer's or the assigner's state (ADR 0615 §1): their views are consumed.
if !slices.Equal(m.Invokes, []string{"seat:mesh-hand-acts.warranted", "seat:mesh-hand-acts.hand-act", "seat:mesh-catalog.seat-definitions", ModulesTool, ManifestsTool, AssignmentsTool}) ||
if !slices.Equal(m.Invokes, []string{"seat:mesh-hand-acts.warranted", "seat:mesh-hand-acts.hand-act", "seat:mesh-catalog.seat-definitions", RevisionVerb, RegistrationsVerb, SourceVerb, AssignmentsTool}) ||
!slices.Equal(m.Reads, []string{"claude-licence-manager.bindings"}) ||
!slices.Equal(m.Consumes, []string{"mesh-delivery-indexer.views.modules", "mesh-delivery-indexer.views.manifests", "mesh-delivery-assigner.views.assignments", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-conditions.findings-asked"}) {
!slices.Equal(m.Consumes, []string{"mesh-catalog.catalog-revision", "mesh-catalog.registration-changed", "mesh-delivery.source-moved", "mesh-delivery-assigner.views.assignments", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-conditions.findings-asked"}) {
t.Errorf("invokes = %v, reads = %v, consumes = %v", m.Invokes, m.Reads, m.Consumes)
}
if src, _ := os.ReadFile("proposals.go"); !strings.Contains(string(src), `"seat:mesh-hand-acts.warranted"`) ||
@@ -2,13 +2,10 @@ package main
import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"os"
"reflect"
@@ -57,7 +54,7 @@ func someSeats() []seatdef.Definition {
// saying what its tool replaces, as the seat mesh-module-assigner and the indexer keep them on the bus.
func aMesh(t *testing.T) aBus {
t.Helper()
b := aBus{AssignmentsRead: {}, ModulesRead: {}, ManifestsRead: {}}
b := aBus{AssignmentsRead: {}}
for _, p := range []mesh.Assignments{
{V: 1, Node: "anchor", Decided: mesh.NodeDecided{PublicDomain: "Anchor.Example"},
Modules: []mesh.PlacedModule{{Module: "docker"}, {Module: "zsh"}}},
@@ -71,21 +68,33 @@ func aMesh(t *testing.T) aBus {
b[AssignmentsRead][k] = v
}
}
return b
}
// fakeCatalog is the catalog half as the tests give it: docker and zsh, docker's manifest saying what its tool replaces.
type fakeCatalog struct {
entries map[string]catalog.CatalogEntry
notYet error
}
func (f fakeCatalog) Entry(module string) (catalog.CatalogEntry, bool, error) {
e, ok := f.entries[module]
return e, ok, f.notYet
}
func (f fakeCatalog) Ready() error { return f.notYet }
func aCatalog() fakeCatalog {
f := fakeCatalog{entries: map[string]catalog.CatalogEntry{}}
for module, manifest := range map[string]string{
"docker": `{"module":"docker","replaces":{"docker_logs":["docker logs"]}}`,
"zsh": `{"module":"zsh"}`,
} {
digest := fmt.Sprintf("%x", sha256.Sum256([]byte(manifest)))
mkey, _ := catalogclient.ManifestsKey(digest)
raw, err := json.Marshal(catalog.ManifestsByDigestValue{V: 1, Module: module, Manifest: manifest})
if err != nil {
t.Fatal(err)
}
b[ManifestsRead][mkey] = raw
key, _ := catalogclient.ModulesKey(module)
b[ModulesRead][key], _ = json.Marshal(catalog.ModulesByModuleValue{V: 1, None: "nothing is built of it", Manifest: digest, Repository: "novox/mesh-catalog", Commit: "c"})
var whole map[string]any
_ = json.Unmarshal([]byte(manifest), &whole)
f.entries[module] = catalog.CatalogEntry{Module: module, Manifest: whole}
}
return b
return f
}
func resolving(name string) []string {
@@ -97,7 +106,7 @@ func resolving(name string) []string {
// controller: the seats' verbs, the placed modules' own tools, every machine with its domains and addresses — a
// loopback address never one of them.
func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
m, err := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
m, err := ReadMeshToolsFrom(aMesh(t), aCatalog(), someSeats(), resolving)
if err != nil {
t.Fatal(err)
}
@@ -134,7 +143,7 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
case <-time.After(10 * time.Second):
t.Fatalf("the seat list was not read: %+v", seatList.Status())
}
got, err := ReadMeshToolsFrom(aMesh(t), definedSeats(), resolving)
got, err := ReadMeshToolsFrom(aMesh(t), aCatalog(), definedSeats(), resolving)
var addrs []string
for _, r := range got.Replacements {
addrs = append(addrs, r.Address)
@@ -156,7 +165,7 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
// **The table is generated, not written** (novox/hq ADR 0245): what the records say a verb replaces is a row,
// the machines' seats first, and nothing is rendered where the records say nothing.
func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
m, _ := ReadMeshToolsFrom(aMesh(t), aCatalog(), someSeats(), resolving)
table := InsteadOf(&m)
for _, want := range []string{
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
@@ -206,7 +215,7 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
// Asked again with the same answer, nothing is written and nothing renders.
func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
p := Paths{State: t.TempDir()}
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
m, _ := ReadMeshToolsFrom(aMesh(t), aCatalog(), someSeats(), resolving)
if !KeepMeshTools(p, m) {
t.Fatal("the first answer was not kept")
}
@@ -243,7 +252,7 @@ func TestTheMachinesRunningAModuleAreReadFromAssignments(t *testing.T) {
func TestTheMeshIsReadFromTheOwnersFirstViews(t *testing.T) {
bus := aMesh(t)
r := newMeshViews()
for _, name := range []string{ModulesRead, ManifestsRead, AssignmentsRead} {
for _, name := range []string{AssignmentsRead} {
state := strings.TrimPrefix(name, strings.Split(name, ".")[0]+".")
values := map[string]json.RawMessage{}
for k, v := range bus[name] {
@@ -257,16 +266,20 @@ func TestTheMeshIsReadFromTheOwnersFirstViews(t *testing.T) {
t.Fatal(err)
}
}
want, err := ReadMeshToolsFrom(bus, someSeats(), resolving)
want, err := ReadMeshToolsFrom(bus, aCatalog(), someSeats(), resolving)
if err != nil {
t.Fatal(err)
}
got, err := ReadMeshToolsFrom(viewStates{r}, someSeats(), resolving)
got, err := ReadMeshToolsFrom(viewStates{r}, aCatalog(), someSeats(), resolving)
if err != nil || !reflect.DeepEqual(got, want) || len(got.Replacements) == 0 {
t.Fatalf("from the views: %+v, %v\nfrom the state: %+v", got, err, want)
}
// A reader not yet answered says so, and reads nothing.
if _, err := ReadMeshToolsFrom(viewStates{newMeshViews()}, someSeats(), resolving); err == nil || !strings.Contains(err.Error(), "no first view") {
// A catalog half with no first view says so, and reads no module as absent.
if _, err := ReadMeshToolsFrom(bus, fakeCatalog{notYet: errors.New("the catalog half has no first view of main yet")}, someSeats(), resolving); err == nil || !strings.Contains(err.Error(), "no first view") {
t.Fatalf("an unanswered catalog half: %v", err)
}
if _, err := ReadMeshToolsFrom(viewStates{newMeshViews()}, aCatalog(), someSeats(), resolving); err == nil || !strings.Contains(err.Error(), "no first view") {
t.Fatalf("an unanswered first view: %v", err)
}
}
@@ -274,7 +287,7 @@ func TestTheMeshIsReadFromTheOwnersFirstViews(t *testing.T) {
// Issue 1126 B13 AC1 (ADR 0590): over the seats the mesh defines, the rendered instructions, their table among them,
// name no address of the controller, which is not placed: each job is named by the seat that owns it now.
func TestTheRenderedInstructionsNameNoControllerAddress(t *testing.T) {
m, err := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
m, err := ReadMeshToolsFrom(aMesh(t), aCatalog(), someSeats(), resolving)
if err != nil {
t.Fatal(err)
}
+26 -6
View File
@@ -25,6 +25,7 @@ import (
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
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"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"git.novox.be/novox/mesh-sdk/go/views"
@@ -217,12 +218,19 @@ func nodesRunningMe() ([]string, error) { return NodesRunning(viewStates{meshVie
// holds them equal).
const (
assignerModule = "mesh-delivery-assigner"
indexerModule = "mesh-delivery-indexer"
)
// The seat verbs the follower of the watched branches asks (design 47 G §614), as the manifest's `invokes` names them.
const (
RevisionVerb = "seat:mesh-catalog.revision"
RegistrationsVerb = "seat:mesh-catalog.registrations"
SourceVerb = "seat:mesh-delivery.source"
)
// meshCatalog is this module's reading of which modules exist: the catalog half, set where the reader is built.
var meshCatalog CatalogHalf
const (
ModulesTool = indexerModule + ".indexer_read_modules"
ManifestsTool = indexerModule + ".indexer_read_manifests"
AssignmentsTool = assignerModule + ".assigner_read_assignments"
)
@@ -253,8 +261,6 @@ func newMeshViews() *views.Reader {
default:
}
}}
v.Follow(indexerModule, "modules", ModulesTool)
v.Follow(indexerModule, "manifests", ManifestsTool)
v.Follow(assignerModule, "assignments", AssignmentsTool)
return v
}
@@ -335,7 +341,7 @@ func tools(p Paths, servers ServerState, view *ServerView, config ConfigState, c
Description: "Write Claude Code's managed directory now, from the mesh's facts, this module's settings and the servers registered here — after reading the bus again for what each tool replaces and which machines the mesh has (the instructions' table and the shell's guard).",
Run: func(map[string]any) (any, error) {
answer := map[string]any{}
if m, err := ReadMeshToolsFrom(viewStates{meshViews}, definedSeats(), lookup); err != nil {
if m, err := ReadMeshToolsFrom(viewStates{meshViews}, meshCatalog, definedSeats(), lookup); err != nil {
answer["asked"] = "the bus could not be read in full (" + err.Error() + "); what was kept is rendered"
} else {
KeepMeshTools(p, m)
@@ -469,6 +475,20 @@ func main() {
}
p, launched := PathsFrom(os.Getenv)
meshViews = newMeshViews()
branches, err := catalogclient.FollowBranches(meshViews, func(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) })
if err != nil {
say("%v", err)
os.Exit(1)
}
branches.Logf = say
// A change in a branch's half is told as any followed state's: the instructions' table is read again.
branches.Changed = func(string, string, json.RawMessage, bool, time.Time) {
select {
case meshChanged <- struct{}{}:
default:
}
}
meshCatalog = branchesHalf{branches}
if launched {
fsys = FilesFor(p)
}
+13 -33
View File
@@ -18,8 +18,6 @@ import (
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"net"
"os"
"path/filepath"
@@ -93,8 +91,6 @@ func ReadMeshTools(p Paths) *MeshTools {
// The states read, each declared in the manifest's reads.
const (
AssignmentsRead = (assignerModule + "." + mesh.StateAssignments)
ModulesRead = indexerModule + "." + catalog.StateModules
ManifestsRead = indexerModule + "." + catalog.StateManifests
)
// States reads the module states the manifest declares in its reads: a state's keys, and one key's value.
@@ -152,44 +148,28 @@ func NodesRunning(st States, module string) ([]string, error) {
return runsOn(placed)[module], nil
}
// modulesTools is every placed module's own tools that replace a command, by module, from the manifest the indexer
// keeps of it. A module whose manifest cannot be read is left out and said in the error.
func modulesTools(st States, on map[string][]string) ([]ModuleTools, error) {
// modulesTools is every placed module's own tools that replace a command, by module, from the manifest its catalog
// graph entry carries. A module whose manifest cannot be read is left out and said in the error.
func modulesTools(cat CatalogHalf, on map[string][]string) ([]ModuleTools, error) {
var out []ModuleTools
var problems []error
if err := cat.Ready(); err != nil {
return nil, err
}
modules := make([]string, 0, len(on))
for m := range on {
modules = append(modules, m)
}
sort.Strings(modules)
for _, module := range modules {
key, err := catalogclient.ModulesKey(module)
if err != nil {
continue
}
raw, found, err := st.Get(ModulesRead, key)
entry, found, err := cat.Entry(module)
if err != nil || !found {
if err != nil {
problems = append(problems, fmt.Errorf("%s %s: %w", ModulesRead, module, err))
problems = append(problems, fmt.Errorf("the catalog half's entry of %s: %w", module, err))
}
continue
}
m, err := catalogclient.DecodeModule(raw)
if err != nil || m.Manifest == "" {
continue
}
mkey, err := catalogclient.ManifestsKey(m.Manifest)
if err != nil {
continue
}
raw, found, err = st.Get(ManifestsRead, mkey)
if err != nil || !found {
if err != nil {
problems = append(problems, fmt.Errorf("%s of %s: %w", ManifestsRead, module, err))
}
continue
}
kept, err := catalogclient.DecodeManifest(raw)
kept, err := json.Marshal(entry.Manifest)
if err != nil {
problems = append(problems, fmt.Errorf("the manifest of %s: %w", module, err))
continue
@@ -197,7 +177,7 @@ func modulesTools(st States, on map[string][]string) ([]ModuleTools, error) {
var man struct {
Replaces map[string][]string `json:"replaces"`
}
if json.Unmarshal([]byte(kept.Manifest), &man) != nil || len(man.Replaces) == 0 {
if json.Unmarshal(kept, &man) != nil || len(man.Replaces) == 0 {
continue
}
out = append(out, ModuleTools{Module: module, On: on[module], Replaces: man.Replaces})
@@ -263,14 +243,14 @@ func lookup(name string) []string {
// ReadMeshToolsFrom reads the bus now: the seats' verbs, the placed modules' own tools, and every node assignments names
// with its domains and addresses. A part that cannot be read is left out and said in the error; what was read is kept.
func ReadMeshToolsFrom(st States, defined []seatdef.Definition, resolve func(string) []string) (MeshTools, error) {
func ReadMeshToolsFrom(st States, cat CatalogHalf, defined []seatdef.Definition, resolve func(string) []string) (MeshTools, error) {
var m MeshTools
placed, err := assignments(st)
if err != nil {
m.Replacements = ReplacementsOf(defined, nil)
return m, err
}
modules, merr := modulesTools(st, runsOn(placed))
modules, merr := modulesTools(cat, runsOn(placed))
m.Replacements = ReplacementsOf(defined, modules)
for node, p := range placed {
machine := Machine{Name: node, Domains: []string{node + InternalSuffix}}
@@ -357,7 +337,7 @@ func refreshMeshTools(p Paths) {
}
startSeatList(seatlist.StdioBus())
for {
m, err := ReadMeshToolsFrom(viewStates{meshViews}, definedSeats(), lookup)
m, err := ReadMeshToolsFrom(viewStates{meshViews}, meshCatalog, definedSeats(), lookup)
if err != nil {
say("reading what each tool replaces and which machines the mesh has: %v", err)
}
+6 -4
View File
@@ -40,13 +40,15 @@
"seat:mesh-hand-acts.warranted",
"seat:mesh-hand-acts.hand-act",
"seat:mesh-catalog.seat-definitions",
"mesh-delivery-indexer.indexer_read_modules",
"mesh-delivery-indexer.indexer_read_manifests",
"seat:mesh-catalog.revision",
"seat:mesh-catalog.registrations",
"seat:mesh-delivery.source",
"mesh-delivery-assigner.assigner_read_assignments"
],
"consumes": [
"mesh-delivery-indexer.views.modules",
"mesh-delivery-indexer.views.manifests",
"mesh-catalog.catalog-revision",
"mesh-catalog.registration-changed",
"mesh-delivery.source-moved",
"mesh-delivery-assigner.views.assignments",
"mesh-catalog.seat-definition-changed",
"mesh-catalog.seat-definition-removed",