Let claude-code read its tools and machines from the bus, not the controller
The controller is not placed (ADR 0590), so its tools, modules, nodes and node verbs answer nothing. claude-code now reads the seats' verbs from the seats' contract, the placed modules' replaces from the indexer's manifests, and the machines and where modules run from mesh-placement's placement; the rendered instructions name the seats that own each job (issue 1126 B13). Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -1,43 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// askingAController answers the controller's verbs as the seat does: `tools` in-process, the rest as the
|
||||
// command's output and its JSON.
|
||||
func askingAController(t *testing.T) Ask {
|
||||
// aBus is the states claude-code reads, as a test holds them: by read, key and value.
|
||||
type aBus map[string]map[string][]byte
|
||||
|
||||
func (b aBus) Keys(read string) ([]string, error) {
|
||||
if b[read] == nil {
|
||||
return nil, errors.New("no such state")
|
||||
}
|
||||
var keys []string
|
||||
for k := range b[read] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (b aBus) Get(read, key string) ([]byte, bool, error) {
|
||||
v, ok := b[read][key]
|
||||
return v, ok, nil
|
||||
}
|
||||
|
||||
// someSeats are two seats as the seats' contract says them: one held once for the mesh, one on every machine.
|
||||
func someSeats() []catalogue.Seat {
|
||||
return []catalogue.Seat{
|
||||
{Name: "mesh-placement", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{
|
||||
{Name: "placement", Description: "Read placement: one node's key.", Replaces: []string{"hostnamectl", "uptime"}},
|
||||
{Name: "propose", Description: "Propose a change."}}},
|
||||
{Name: "node-service-manager", Scope: catalogue.ScopeNode, Serves: []catalogue.Verb{
|
||||
{Name: "status", Description: "One unit as the service manager sees it now: its states, whether it starts at boot.", Replaces: []string{"systemctl status", "systemctl is-active"}},
|
||||
{Name: "journal", Description: "The last lines of one unit's journal.", Replaces: []string{"journalctl"}}}},
|
||||
}
|
||||
}
|
||||
|
||||
// aMesh is two machines, anchor (with a public domain) and laptop, docker and zsh placed on anchor, docker's manifest
|
||||
// saying what its tool replaces, as the seat mesh-placement and the indexer keep them on the bus.
|
||||
func aMesh(t *testing.T) aBus {
|
||||
t.Helper()
|
||||
wrap := func(output string, answer any) json.RawMessage {
|
||||
raw, _ := json.Marshal(map[string]any{"ok": true, "output": output, "answer": answer})
|
||||
return raw
|
||||
}
|
||||
return func(address string, args any) (json.RawMessage, error) {
|
||||
switch address {
|
||||
case "seat:mesh-controller.tools":
|
||||
return json.RawMessage(`{"seats":[
|
||||
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","description":"What one machine reported it can do, what it is assigned, and why.","replaces":["hostnamectl","uptime"]},{"name":"nodes","description":"Every machine."}]},
|
||||
{"seat":"node-service-manager","scope":"node","tools":[
|
||||
{"name":"status","description":"One unit as the service manager sees it now: its states, whether it starts at boot.","replaces":["systemctl status","systemctl is-active"]},
|
||||
{"name":"journal","description":"The last lines of one unit's journal.","replaces":["journalctl"]}]}]}`), nil
|
||||
case "seat:mesh-controller.modules":
|
||||
return wrap("[...]", []any{map[string]any{"module": "docker", "on": []string{"anchor"},
|
||||
"replaces": map[string][]string{"docker_logs": {"docker logs"}}}, map[string]any{"module": "zsh", "on": []string{"anchor"}}}), nil
|
||||
case "seat:mesh-controller.nodes":
|
||||
return wrap("", []any{map[string]any{"name": "anchor"}, map[string]any{"name": "laptop"}}), nil
|
||||
case "seat:mesh-controller.node":
|
||||
node, _ := args.(map[string]any)["node"].(string)
|
||||
if node == "anchor" {
|
||||
return wrap("anchor\n mode converged\n public domain Anchor.Example\n\n what it runs\n", nil), nil
|
||||
}
|
||||
return wrap("laptop\n mode converged\n", nil), nil
|
||||
b := aBus{PlacementRead: {}, ModulesRead: {}, ManifestsRead: {}}
|
||||
for _, p := range []delivery.Placement{
|
||||
{V: 1, Node: "anchor", Decided: delivery.NodeDecided{PublicDomain: "Anchor.Example"},
|
||||
Modules: []delivery.PlacedModule{{Module: "docker"}, {Module: "zsh"}}},
|
||||
{V: 1, Node: "laptop"},
|
||||
} {
|
||||
values, err := delivery.PlacementValues(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for k, v := range values {
|
||||
b[PlacementRead][k] = v
|
||||
}
|
||||
return nil, errors.New("no such verb")
|
||||
}
|
||||
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, _ := delivery.ManifestsKey(digest)
|
||||
raw, err := json.Marshal(delivery.Manifest{V: 1, Module: module, JSON: manifest})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b[ManifestsRead][mkey] = raw
|
||||
key, _ := delivery.ModulesKey(module)
|
||||
b[ModulesRead][key], _ = json.Marshal(delivery.Module{V: 1, None: "nothing is built of it", Manifest: digest, Repository: "novox/mesh-catalog", Commit: "c"})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func resolving(name string) []string {
|
||||
@@ -45,10 +84,11 @@ func resolving(name string) []string {
|
||||
"laptop": {"127.0.0.1", "10.10.0.4"}}[name]
|
||||
}
|
||||
|
||||
// What the controller says of its tools and machines is read whole: the seats' verbs, the modules' own tools,
|
||||
// every machine with its domains and addresses — a loopback address never one of them.
|
||||
func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
|
||||
m, err := AskMeshTools(askingAController(t), resolving)
|
||||
// Issue 1126 B13: what the mesh says of its tools and machines is read from the bus whole, never asked of the
|
||||
// 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)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -56,7 +96,7 @@ func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
|
||||
for _, r := range m.Replacements {
|
||||
addresses = append(addresses, r.Address)
|
||||
}
|
||||
if got := strings.Join(addresses, ","); got != "mesh-controller.node,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
|
||||
if got := strings.Join(addresses, ","); got != "mesh-placement.placement,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
|
||||
t.Errorf("replacements: %s", got)
|
||||
}
|
||||
if len(m.Machines) != 2 || strings.Join(m.Machines[0].Domains, ",") != "anchor.internal,anchor.example" ||
|
||||
@@ -71,12 +111,12 @@ func TestTheMeshsToolsAreAskedOfTheController(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, _ := AskMeshTools(askingAController(t), resolving)
|
||||
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
|
||||
table := InsteadOf(&m)
|
||||
for _, want := range []string{
|
||||
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
|
||||
"| `<node>/docker.` | `docker_logs` — `docker logs` |",
|
||||
"| `mesh-controller.` | `node` — `hostnamectl`, `uptime` |",
|
||||
"| `mesh-placement.` | `placement` — `hostnamectl`, `uptime` |",
|
||||
"Never `ssh` to a mesh machine",
|
||||
} {
|
||||
if !strings.Contains(table, want) {
|
||||
@@ -84,14 +124,14 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if strings.Index(table, "node-service-manager.") > strings.Index(table, "docker.") ||
|
||||
strings.Index(table, "docker.") > strings.Index(table, "`mesh-controller.`") {
|
||||
strings.Index(table, "docker.") > strings.Index(table, "`mesh-placement.`") {
|
||||
t.Errorf("not the machines' seats, then modules, then the mesh's:\n%s", table)
|
||||
}
|
||||
// What the guard here named most comes first: its row, and in its row its verb.
|
||||
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-controller.node": 1}
|
||||
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-placement.placement": 1}
|
||||
ranked := InsteadOf(&m)
|
||||
if !strings.Contains(ranked, "| `<node>/node-service-manager.` | `journal` — `journalctl` · `status`") ||
|
||||
strings.Index(ranked, "`mesh-controller.`") > strings.Index(ranked, "`<node>/docker.`") {
|
||||
strings.Index(ranked, "`mesh-placement.`") > strings.Index(ranked, "`<node>/docker.`") {
|
||||
t.Errorf("not ranked by the guard's record:\n%s", ranked)
|
||||
}
|
||||
m.Refused = nil
|
||||
@@ -121,7 +161,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, _ := AskMeshTools(askingAController(t), resolving)
|
||||
m, _ := ReadMeshToolsFrom(aMesh(t), someSeats(), resolving)
|
||||
if !KeepMeshTools(p, m) {
|
||||
t.Fatal("the first answer was not kept")
|
||||
}
|
||||
@@ -138,18 +178,32 @@ func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Where claude-code runs is read from the controller's `modules` answer as it comes — JSON, from `module list
|
||||
// --json` — and from the printed list too.
|
||||
func TestTheMachinesRunningTheModuleAreReadFromTheControllersAnswer(t *testing.T) {
|
||||
asJSON := json.RawMessage(`{"ok":true,"output":"[...]","answer":[{"module":"zsh","on":["a"]},{"module":"claude-code","on":["a","b"]}]}`)
|
||||
if got := strings.Join(NodesRunning(asJSON, "claude-code"), ","); got != "a,b" {
|
||||
t.Errorf("from JSON: %q", got)
|
||||
// Where a module runs is read from the seat mesh-placement's placement (issue 1126 B13), never the controller's answer.
|
||||
func TestTheMachinesRunningAModuleAreReadFromPlacement(t *testing.T) {
|
||||
b := aMesh(t)
|
||||
if got, err := NodesRunning(b, "zsh"); err != nil || strings.Join(got, ",") != "anchor" {
|
||||
t.Errorf("zsh runs on %v (%v)", got, err)
|
||||
}
|
||||
printed := json.RawMessage(`{"ok":true,"output":"zsh 1 built x on a\nclaude-code 1 built y on a, b\n"}`)
|
||||
if got := strings.Join(NodesRunning(printed, "claude-code"), ","); got != "a,b" {
|
||||
t.Errorf("from the printed list: %q", got)
|
||||
if got, err := NodesRunning(b, "claude-code"); err != nil || got != nil {
|
||||
t.Errorf("a module that runs nowhere: %v (%v)", got, err)
|
||||
}
|
||||
if got := NodesRunning(json.RawMessage(`{"ok":true,"answer":[{"module":"zsh","on":["a"]}]}`), "claude-code"); got != nil {
|
||||
t.Errorf("a module that runs nowhere: %v", got)
|
||||
if _, err := NodesRunning(aBus{}, "zsh"); err == nil {
|
||||
t.Error("an unreadable placement was read as no machine")
|
||||
}
|
||||
}
|
||||
|
||||
// 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), catalogue.Seats(), resolving)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
md := RenderWithMesh(Facts{Node: "w", Console: "x"}, Settings{}, nil, "/h", nil, Config{}, &m)["CLAUDE.md"]
|
||||
if !strings.Contains(md, "## Instead of a shell command") {
|
||||
t.Fatalf("no table rendered:\n%s", md)
|
||||
}
|
||||
if strings.Contains(md, "mesh-controller") {
|
||||
t.Errorf("the instructions name the controller:\n%s", md)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user