Every module follows the assigner's new places: the contracts' go/ path, mesh-wants in the delivery contracts, the state and tool assignments (issue 1966)

The legacy key form placement.<node> is gone from the client, so the readers and their fixtures lose it. The check noplacement
now also refuses the assigner's retired names in a manifest.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:29:07 +01:00
parent 2dc9e07e88
commit c9d8950fd0
109 changed files with 682 additions and 681 deletions
@@ -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, PlacementTool}) ||
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}) ||
!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.placement", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed"}) {
!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"}) {
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"`) ||
@@ -17,7 +17,7 @@ import (
func meshForTheGuard() GuardData {
defined := []seatdef.Definition{
{Seat: "mesh-module-assigner", Scope: "mesh", Verbs: []seatdef.Verb{
{Name: "placement", Description: "Read placement: one node's key.", Replaces: []string{"hostnamectl", "uptime"}}}},
{Name: "assignments", Description: "Read assignments: one node's key.", Replaces: []string{"hostnamectl", "uptime"}}}},
{Seat: "node-service-manager", Scope: "node", Verbs: []seatdef.Verb{
{Name: "status", Description: "One unit as the service manager sees it now: its states.", Replaces: []string{"systemctl status", "systemctl is-active"}},
{Name: "restart", Description: "Restart one unit.", Replaces: []string{"systemctl restart"}},
@@ -80,14 +80,14 @@ func TestSSHToAMeshMachineIsRefusedNamingTheTool(t *testing.T) {
"ssh -o StrictHostKeyChecking=no 10.10.0.1 docker ps -a": "anchor/docker.docker_list",
"ssh anchor docker logs --tail 100 nats": "anchor/docker.docker_logs",
"ssh laptop docker logs x": "anchor/docker.docker_logs",
"ssh a uptime": "mesh-module-assigner.placement",
"ssh a uptime": "mesh-module-assigner.assignments",
"cd /tmp && ssh anchor journalctl": "anchor/node-service-manager.journal",
"sudo -u root ssh anchor journalctl": "anchor/node-service-manager.journal",
"timeout 10 ssh anchor journalctl": "anchor/node-service-manager.journal",
"env LANG=C ssh anchor journalctl": "anchor/node-service-manager.journal",
"bash -c 'ssh anchor journalctl -u x'": "anchor/node-service-manager.journal",
`echo "$(ssh anchor journalctl -n 5)" | tail`: "anchor/node-service-manager.journal",
"for n in anchor laptop; do ssh $n uptime; done; ssh anchor uptime": "mesh-module-assigner.placement",
"for n in anchor laptop; do ssh $n uptime; done; ssh anchor uptime": "mesh-module-assigner.assignments",
"ssh -J anchor 203.0.113.9 journalctl": "anchor/node-service-manager.journal",
"ssh via-anchor journalctl": "anchor/node-service-manager.journal",
"ssh nas.lan journalctl": "anchor/node-service-manager.journal",
@@ -6,8 +6,8 @@ import (
"encoding/json"
"errors"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
mesh "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
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"
@@ -44,10 +44,10 @@ func (b aBus) Get(read, key string) ([]byte, bool, error) {
// someSeats are two seats as the seats' contract says them: one held once for the mesh, one on every machine.
func someSeats() []seatdef.Definition {
return []seatdef.Definition{
{Seat: "mesh-module-assigner", Domain: "Placement", Scope: "mesh", Verbs: []seatdef.Verb{
{Name: "placement", Description: "Read placement: one node's key.", Replaces: []string{"hostnamectl", "uptime"}},
{Seat: "mesh-module-assigner", Domain: "Core", Scope: "mesh", Verbs: []seatdef.Verb{
{Name: "assignments", Description: "Read assignments: one node's key.", Replaces: []string{"hostnamectl", "uptime"}},
{Name: "propose", Description: "Propose a change."}}},
{Seat: "node-service-manager", Domain: "Placement", Scope: "node", Verbs: []seatdef.Verb{
{Seat: "node-service-manager", Domain: "Core", Scope: "node", Verbs: []seatdef.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"}}}},
}
@@ -57,18 +57,18 @@ 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{PlacementRead: {}, ModulesRead: {}, ManifestsRead: {}}
for _, p := range []mesh.Placement{
b := aBus{AssignmentsRead: {}, ModulesRead: {}, ManifestsRead: {}}
for _, p := range []mesh.Assignments{
{V: 1, Node: "anchor", Decided: mesh.NodeDecided{PublicDomain: "Anchor.Example"},
Modules: []mesh.PlacedModule{{Module: "docker"}, {Module: "zsh"}}},
{V: 1, Node: "laptop"},
} {
values, err := assignerclient.PlacementValues(p)
values, err := assignerclient.AssignmentsValues(p)
if err != nil {
t.Fatal(err)
}
for k, v := range values {
b[PlacementRead][k] = v
b[AssignmentsRead][k] = v
}
}
for module, manifest := range map[string]string{
@@ -105,7 +105,7 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
for _, r := range m.Replacements {
addresses = append(addresses, r.Address)
}
if got := strings.Join(addresses, ","); got != "mesh-module-assigner.placement,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
if got := strings.Join(addresses, ","); got != "mesh-module-assigner.assignments,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
t.Errorf("replacements: %s", got)
}
t.Run("the again tool answers the operator and the healers only", func(t *testing.T) {
@@ -138,7 +138,7 @@ func TestTheMeshsToolsAreReadFromTheBus(t *testing.T) {
addrs = append(addrs, r.Address)
}
// The foundation's bootstrapped seats are held beside the two the seat answered, in reading order.
if err != nil || !slices.Contains(addrs, "mesh-module-assigner.placement") || !slices.Contains(addrs, "node-service-manager.journal") {
if err != nil || !slices.Contains(addrs, "mesh-module-assigner.assignments") || !slices.Contains(addrs, "node-service-manager.journal") {
t.Fatalf("replacements from the bus: %v, %v", addrs, err)
}
})
@@ -159,7 +159,7 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
for _, want := range []string{
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
"| `<node>/docker.` | `docker_logs` — `docker logs` |",
"| `mesh-module-assigner.` | `placement` — `hostnamectl`, `uptime` |",
"| `mesh-module-assigner.` | `assignments` — `hostnamectl`, `uptime` |",
"Never `ssh` to a mesh machine",
} {
if !strings.Contains(table, want) {
@@ -171,7 +171,7 @@ func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
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-module-assigner.placement": 1}
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-module-assigner.assignments": 1}
ranked := InsteadOf(&m)
if !strings.Contains(ranked, "| `<node>/node-service-manager.` | `journal` — `journalctl` · `status`") ||
strings.Index(ranked, "`mesh-module-assigner.`") > strings.Index(ranked, "`<node>/docker.`") {
@@ -221,8 +221,8 @@ func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
}
}
// Where a module runs is read from the seat mesh-module-assigner's placement (issue 1126 B13), never the controller's answer.
func TestTheMachinesRunningAModuleAreReadFromPlacement(t *testing.T) {
// Where a module runs is read from the seat mesh-module-assigner's assignments (issue 1126 B13), never the controller's answer.
func TestTheMachinesRunningAModuleAreReadFromAssignments(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)
@@ -231,17 +231,17 @@ func TestTheMachinesRunningAModuleAreReadFromPlacement(t *testing.T) {
t.Errorf("a module that runs nowhere: %v (%v)", got, err)
}
if _, err := NodesRunning(aBus{}, "zsh"); err == nil {
t.Error("an unreadable placement was read as no machine")
t.Error("an unreadable assignments was read as no machine")
}
}
// ADR 0615 §1: the tools and the placement come from the owners' first views and their views events, never their state.
// ADR 0615 §1: the tools and the assignments come from the owners' first views and their views events, never their state.
// A reader whose first views are answered from the fixture reads what the state read gave, and a views event changes
// what it reads (CAT-CC3).
func TestTheMeshIsReadFromTheOwnersFirstViews(t *testing.T) {
bus := aMesh(t)
r := newMeshViews()
for _, name := range []string{ModulesRead, ManifestsRead, PlacementRead} {
for _, name := range []string{ModulesRead, ManifestsRead, AssignmentsRead} {
state := strings.TrimPrefix(name, strings.Split(name, ".")[0]+".")
values := map[string]json.RawMessage{}
for k, v := range bus[name] {
@@ -9,5 +9,5 @@ func instructionsText(node, role string) string {
node +
"`\n- **Role:** " +
role +
"\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-module-assigner.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
"\n- The other nodes, their roles and what runs where: ask the seat `mesh-module-assigner` (`mesh-module-assigner.assignments`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
}
+6 -6
View File
@@ -209,7 +209,7 @@ func (s stateOf) Get(key string) (json.RawMessage, bool, error) {
return e.Value, true, nil
}
// nodesRunningMe is the nodes claude-code runs on, from the assigner's placement — for the register tool's question.
// nodesRunningMe is the nodes claude-code runs on, from the assigner's assignments — for the register tool's question.
func nodesRunningMe() ([]string, error) { return NodesRunning(viewStates{meshViews}, "claude-code") }
// The owners' read tools of the states this module follows, which the manifest invokes (novox/hq ADR 0615 §1).
@@ -221,12 +221,12 @@ const (
)
const (
ModulesTool = indexerModule + ".indexer_read_modules"
ManifestsTool = indexerModule + ".indexer_read_manifests"
PlacementTool = assignerModule + ".assigner_read_placement"
ModulesTool = indexerModule + ".indexer_read_modules"
ManifestsTool = indexerModule + ".indexer_read_manifests"
AssignmentsTool = assignerModule + ".assigner_read_assignments"
)
// meshViews is this module's picture of the indexer's modules and manifests and the assigner's placement: each asked
// meshViews is this module's picture of the indexer's modules and manifests and the assigner's assignments: each asked
// once of the owner's read tool, then followed on the owner's views family. It reads none of their state.
var meshViews *views.Reader
@@ -245,7 +245,7 @@ func newMeshViews() *views.Reader {
}}
v.Follow(indexerModule, "modules", ModulesTool)
v.Follow(indexerModule, "manifests", ManifestsTool)
v.Follow(assignerModule, "placement", PlacementTool)
v.Follow(assignerModule, "assignments", AssignmentsTool)
return v
}
+20 -20
View File
@@ -7,7 +7,7 @@ package main
// Read from the bus at start and every few minutes (issue 1126 B13; the controller that answered this before is
// not placed, novox/hq ADR 0590): the seats' verbs from the seats' contract (mesh-contracts), the modules'
// own tools from the manifests the indexer keeps (`mesh-delivery-indexer.modules` and `.manifests`), and the
// machines and where each module runs from the seat mesh-module-assigner's `placement`. Kept in the module's state so
// machines and where each module runs from the seat mesh-module-assigner's `assignments`. Kept in the module's state so
// a render never waits for the bus, and rendered whenever it changes. Nothing here is written by hand: a verb
// that gains a `replaces` is in the next render's table and the guard's next refusal.
@@ -16,8 +16,8 @@ import (
"encoding/json"
"errors"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
mesh "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
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"
@@ -92,9 +92,9 @@ func ReadMeshTools(p Paths) *MeshTools {
// The states read, each declared in the manifest's reads.
const (
PlacementRead = (assignerModule + "." + mesh.StatePlacement)
ModulesRead = indexerModule + "." + catalog.StateModules
ManifestsRead = indexerModule + "." + catalog.StateManifests
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.
@@ -110,17 +110,17 @@ type ModuleTools struct {
Replaces map[string][]string
}
// placements is every node's placement, from the seat mesh-module-assigner's state.
func placements(st States) (map[string]mesh.Placement, error) {
keys, err := st.Keys(PlacementRead)
// assignments is every node's assignments, from the seat mesh-module-assigner's state.
func assignments(st States) (map[string]mesh.Assignments, error) {
keys, err := st.Keys(AssignmentsRead)
if err != nil {
return nil, fmt.Errorf("%s: %w", PlacementRead, err)
return nil, fmt.Errorf("%s: %w", AssignmentsRead, err)
}
read := assignerclient.NewKeys()
for _, k := range keys {
v, found, err := st.Get(PlacementRead, k)
v, found, err := st.Get(AssignmentsRead, k)
if err != nil {
return nil, fmt.Errorf("%s %s: %w", PlacementRead, k, err)
return nil, fmt.Errorf("%s %s: %w", AssignmentsRead, k, err)
}
if found {
_, _ = read.Put(k, v, 0, false) // a key that does not read places nothing
@@ -129,8 +129,8 @@ func placements(st States) (map[string]mesh.Placement, error) {
return read.All(), nil
}
// runsOn is, by module, the nodes placement puts it on, sorted.
func runsOn(placed map[string]mesh.Placement) map[string][]string {
// runsOn is, by module, the nodes assignments puts it on, sorted.
func runsOn(placed map[string]mesh.Assignments) map[string][]string {
out := map[string][]string{}
for node, p := range placed {
for _, m := range p.Modules {
@@ -143,9 +143,9 @@ func runsOn(placed map[string]mesh.Placement) map[string][]string {
return out
}
// NodesRunning is the nodes placement puts module on.
// NodesRunning is the nodes assignments puts module on.
func NodesRunning(st States, module string) ([]string, error) {
placed, err := placements(st)
placed, err := assignments(st)
if err != nil {
return nil, err
}
@@ -261,11 +261,11 @@ func lookup(name string) []string {
return addrs
}
// ReadMeshToolsFrom reads the bus now: the seats' verbs, the placed modules' own tools, and every node placement names
// 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) {
var m MeshTools
placed, err := placements(st)
placed, err := assignments(st)
if err != nil {
m.Replacements = ReplacementsOf(defined, nil)
return m, err
@@ -340,7 +340,7 @@ func guardFiles(p Paths, m *MeshTools) *GuardFiles {
return &GuardFiles{Exe: exe, Data: GuardDataOf(p, m), WaitsDir: WaitsDirPath(p)}
}
// MeshToolsEvery is how often the machines' addresses are resolved again; the tools and the placement are read again
// MeshToolsEvery is how often the machines' addresses are resolved again; the tools and the assignments are read again
// when their followed states change (a views event), not on this timer.
const MeshToolsEvery = 10 * time.Minute
@@ -353,7 +353,7 @@ func refreshMeshTools(p Paths) {
select {
case <-meshViews.Ready():
case <-time.After(MeshToolsFirst):
say("the first views of the tools and the placement were not answered in %s; reading what is held", MeshToolsFirst)
say("the first views of the tools and the assignments were not answered in %s; reading what is held", MeshToolsFirst)
}
startSeatList(seatlist.StdioBus())
for {
@@ -32,11 +32,11 @@
"withKey": {
"managed-mcp.json": "{\n \"mcpServers\": {\n \"anton\": {\n \"type\": \"stdio\",\n \"command\": \"node\",\n \"args\": [\n \"/a/b.js\"\n ],\n \"env\": {}\n },\n \"docs\": {\n \"type\": \"stdio\",\n \"command\": \"docs-mcp\",\n \"args\": [\n \"--x\"\n ]\n },\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n },\n \"search\": {\n \"type\": \"http\",\n \"url\": \"https://s.example/mcp\"\n }\n }\n}\n",
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true,\n \"apiKeyHelper\": \"/state/api-key-helper\"\n}\n",
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** the laptop\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-module-assigner.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** the laptop\n- The other nodes, their roles and what runs where: ask the seat `mesh-module-assigner` (`mesh-module-assigner.assignments`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
},
"plain": {
"managed-mcp.json": "{\n \"mcpServers\": {\n \"mesh\": {\n \"type\": \"http\",\n \"url\": \"http://127.0.0.1:4270/mcp\"\n }\n }\n}\n",
"managed-settings.json": "{\n \"attribution\": {\n \"commit\": \"\",\n \"pr\": \"\"\n },\n \"allowAllClaudeAiMcps\": true\n}\n",
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** not stated — set it in this module's settings for the node\n- The other nodes, their roles and what runs where: ask the placement seat (`mesh-module-assigner.placement`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
"CLAUDE.md": "# This machine is a node of a Novox mesh\n\nWritten by the mesh's `claude-code` module. Edit the module's settings or the catalog, never this file:\nit is rewritten whenever the module renders.\n\n## Who this node is\n\n- **Node:** `workstation`\n- **Role:** not stated — set it in this module's settings for the node\n- The other nodes, their roles and what runs where: ask the seat `mesh-module-assigner` (`mesh-module-assigner.assignments`),\n or `mesh_overview` and `mesh_machine`. Nothing here lists them, because a copy drifts.\n\n## How a session on this mesh works\n\nThe console is the only way to the mesh: the MCP server named `mesh`. It offers five tools, and\neverything else is an address you find and call through them:\n\n- `mesh_search` — words in, matching addresses out. `mesh_describe` — one address's arguments.\n- `mesh_call` — call an address. A seat the mesh holds once is `<seat>.<verb>` (the mesh's own seats\n include `mesh-module-assigner`, `mesh-delivery`, `mesh-config` and `mesh-conditions`);\n a module on a machine is `<node>/<module>.<tool>`.\n- `mesh_overview` and `mesh_machine` — the mesh's seats and machines, and what one machine runs.\n\n- **Symptom first.** For an error, a failing service or anything unexpected, search the record with the\n literal text before forming a hypothesis: the records module's `records_search`, then\n `records_read`.\n- **Ask the mesh before changing it**, and change it through the seats' verbs or the catalog.\n- **A licence** through the `anthropic-licence-manager` seat's verbs. Never edit the agent's credentials\n file by hand, never print or ask for a token.\n\n## Hard rules\n\n- A file the mesh manages is changed through the verb or the catalog that owns it, never on disk. If\n unsure, `<node>/node-engine.composition` for the module says what the mesh writes there.\n- Never write to a store's database by hand; schema changes are numbered migrations.\n- Never push to a main branch: a branch, a pull request, and a human approval for every merge.\n- The mesh creates no symlinks, and nobody else does either.\n- A package is declared in a module, never installed by hand.\n\n## Conventions\n\n- Commit messages are concise, in the imperative, about why.\n- Test before pushing: nodes update unattended.\n- The playbooks in the record say how research, decisions, designs, issues and hand-offs are done.\n"
}
}