Compare commits

..
Author SHA1 Message Date
jschoubben ae96afcaa8 Render a 660 s timeout for the mesh console and the MCP idle bound, so mesh_wait is not cut at a minute (issue 1871)
mesh/delivery ready: it is merged on the Approve
mesh/check pass ae96afcaa8da on 0c63b4c906c6 | unit 521 run, 3903 kept, 13 skipped | integration 534 run, 3931 kept, 21 skipped | over bound: unit (sl…
2026-10-28 03:52:32 +01:00
7 changed files with 55 additions and 2259 deletions
+2 -2
View File
@@ -45,8 +45,8 @@ whenever the node's tool runtime collects the module's tools:
| file | holds |
|---|---|
| `managed-mcp.json` | the tool servers every session loads: the mesh's console as `mesh`, and the servers set in this module's `mcp_servers` setting. **Exclusive**: a server not listed here does not load — not one added with `claude mcp add`, not a project's `.mcp.json`, not a plugin's |
| `managed-settings.json` | the keys set in this module's `managed_settings` setting — at the controller's terminal alone (hq issue 340) — under the mesh's own keys: the repositories' attribution convention, the claude.ai connectors kept beside the managed servers, the key-helper while the node holds an API-key licence, git's credential file while the node holds the forge's read credential (below), and the two that name the `nox-mesh` marketplace and enable its plugin |
| `managed-mcp.json` | the tool servers every session loads: the mesh's console as `mesh` (with a `timeout` of 660000 ms, mesh_wait's longest wait and a minute, so the agent's one-minute default does not cut a wait, issue 1871), and the servers set in this module's `mcp_servers` setting. **Exclusive**: a server not listed here does not load — not one added with `claude mcp add`, not a project's `.mcp.json`, not a plugin's |
| `managed-settings.json` | the keys set in this module's `managed_settings` setting — at the controller's terminal alone (hq issue 340) — under the mesh's own keys: the repositories' attribution convention, the claude.ai connectors kept beside the managed servers, the key-helper while the node holds an API-key licence, git's credential file while the node holds the forge's read credential (below), and the two that name the `nox-mesh` marketplace and enable its plugin, and `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` (660000) in `env` beside the operator's own, the mesh's key winning |
| `CLAUDE.md` | how a session on this mesh works, this node's name and role, the conventions, the **"instead of" table** — then the instruction sections registered for every node and for this one |
| `marketplace/` | the `nox-mesh` plugin (hq ADR 0216): the skills, subagents, commands and output styles registered for every node and for this one, offered in a session as `nox-mesh:<name>` — and the **guard on the agent's shell** (`guard/`), the mesh's own hook and the plugin's only one (hq issue 340). Replaced whole, staged beside and swapped in |
@@ -83,6 +83,20 @@ func TestTheRendererWritesWhatTheTypeScriptOneWrote(t *testing.T) {
for k := range MarketplaceKeys() {
delete(m, k)
}
// The wait bound is new since the TypeScript (issue 1871), and is held below.
if env, ok := m["env"].(map[string]any); ok {
delete(env, "CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT")
if len(env) == 0 {
delete(m, "env")
}
}
}
if m, ok := a.(map[string]any); ok && file == "managed-mcp.json" {
if servers, ok := m["mcpServers"].(map[string]any); ok {
if entry, ok := servers["mesh"].(map[string]any); ok {
delete(entry, "timeout")
}
}
}
_ = json.Unmarshal([]byte(want[file]), &b)
if !reflect.DeepEqual(a, b) {
@@ -104,6 +118,21 @@ func TestASettingCannotReplaceTheMeshsOwnEntryAndABadNameIsLeftOut(t *testing.T)
if mcp.MCPServers["mesh"]["url"] != "http://127.0.0.1:4270/mcp" || mcp.MCPServers["bad name"] != nil {
t.Fatalf("%v", mcp.MCPServers)
}
// The entry bounds a call by mesh_wait's longest wait and the idle bound is laid over the operator's env
// (issue 1871): a session's wait is not cut at the agent's one-minute default.
if mcp.MCPServers["mesh"]["timeout"] != float64(660000) {
t.Errorf("the mesh entry's timeout is %v", mcp.MCPServers["mesh"]["timeout"])
}
for _, held := range []map[string]any{nil, {"CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT": "1", "FOO": "bar"}} {
var m struct {
Env map[string]any `json:"env"`
}
got := Render(Facts{Console: "x"}, Settings{ManagedSettings: map[string]any{"env": held}}, nil, "/h", nil, Config{})
_ = json.Unmarshal([]byte(got["managed-settings.json"]), &m)
if m.Env["CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT"] != "660000" || (held != nil && m.Env["FOO"] != "bar") {
t.Errorf("env %v rendered as %v", held, m.Env)
}
}
if !reflect.DeepEqual(Render(Facts{Console: "x"}, Settings{}, nil, "/h", nil, Config{}), Render(Facts{Console: "x"}, Settings{}, nil, "/h", nil, Config{})) {
t.Fatal("rendering is not deterministic")
}
+24 -1
View File
@@ -22,6 +22,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
)
@@ -98,6 +99,27 @@ func jsonFile(v any) string {
return b.String()
}
// MeshWaitBoundMillis is how long the agent may wait on the mesh's console for one call: mesh_wait's longest
// wait (600 s, mesh-tools node-tools console.WaitLongest) plus a minute's margin. The agent bounds an HTTP
// server's request by the largest of 60 s, the server entry's "timeout" and MCP_TIMEOUT, and a call that
// receives nothing for CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT (5 minutes by default) is aborted
// (code.claude.com/docs/en/mcp.md). Without these every mesh_wait ended "The operation timed out." at a
// minute, whatever its timeout (issue 1871).
const MeshWaitBoundMillis = 660000
// meshWaitEnv lays the idle bound into the environment the settings give every session, the mesh's key winning
// over the operator's, like the forge's keys.
func meshWaitEnv(managed map[string]any) {
env := map[string]any{}
if held, ok := managed["env"].(map[string]any); ok {
for k, v := range held {
env[k] = v
}
}
env["CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT"] = strconv.Itoa(MeshWaitBoundMillis)
managed["env"] = env
}
// Render composes the three files. registered — what was registered through this module and applies
// here — is laid over the servers the operator set in its settings; config is the rest of what was
// registered (ADR 0216): its settings laid over the operator's `managed_settings`, its instruction sections
@@ -119,7 +141,7 @@ func RenderWithMesh(facts Facts, settings Settings, binding *Binding, helperPath
servers[name] = entry
}
}
servers[meshEntry] = map[string]any{"type": "http", "url": facts.Console}
servers[meshEntry] = map[string]any{"type": "http", "url": facts.Console, "timeout": MeshWaitBoundMillis}
// The operator's `managed_settings` (ADR 0213), set at the controller's terminal alone: nothing registered
// through this module's tools is laid over it (novox/hq issue 340).
@@ -143,6 +165,7 @@ func RenderWithMesh(facts Facts, settings Settings, binding *Binding, helperPath
managed[key] = entries
}
forgeEnv(managed, facts.ForgeCredentials)
meshWaitEnv(managed)
if binding != nil && binding.Kind == "api-key" {
managed["apiKeyHelper"] = helperPath
}
-1113
View File
File diff suppressed because it is too large Load Diff
-269
View File
@@ -1,269 +0,0 @@
{
"v": 1,
"node": "g14",
"decided": {
"address": "10.10.0.4",
"account": "jochen",
"tunnel": {
"interface": "wg0",
"unit": "wg-quick@wg0",
"config": "/etc/wireguard/wg0.conf",
"port": 51820,
"mtu": 1380,
"address": "10.10.0.4/24",
"range": "10.10.0.0/24",
"public-key": "mq76YPW0FrExhAvt/ZG1SB2BK6/cPISKgXO53SEMazU="
}
},
"modules": [
{
"module": "adwaita"
},
{
"module": "asus-zephyrus-g14"
},
{
"module": "avahi"
},
{
"module": "blueman"
},
{
"module": "bluetooth"
},
{
"module": "ca-trust"
},
{
"module": "claude-code",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "clipmenu"
},
{
"module": "cups"
},
{
"module": "dbus"
},
{
"module": "dev-tools"
},
{
"module": "disk-load"
},
{
"module": "dmenu"
},
{
"module": "docker"
},
{
"module": "docker-compose"
},
{
"module": "dunst",
"has-layer": true
},
{
"module": "fail2ban"
},
{
"module": "feh"
},
{
"module": "flatpak"
},
{
"module": "fonts"
},
{
"module": "forticlient"
},
{
"module": "gnome-keyring"
},
{
"module": "go"
},
{
"module": "hostname",
"has-layer": true
},
{
"module": "i3"
},
{
"module": "i3status-rust"
},
{
"module": "kernel"
},
{
"module": "lemurs"
},
{
"module": "localization"
},
{
"module": "logrotate"
},
{
"module": "memory-pressure"
},
{
"module": "mesh-build-go",
"has-layer": true
},
{
"module": "mesh-build-node",
"bindings": {
"npm-package-registry": {
"node": "novox",
"module": "gitea"
}
}
},
{
"module": "mesh-cli"
},
{
"module": "mesh-files"
},
{
"module": "mesh-host",
"has-layer": true
},
{
"module": "mesh-mail-notifier"
},
{
"module": "mesh-provisioner-client"
},
{
"module": "mesh-reads"
},
{
"module": "mesh-wireguard"
},
{
"module": "netcheck"
},
{
"module": "networkmanager"
},
{
"module": "nextcloud-client"
},
{
"module": "nftables"
},
{
"module": "nm-applet"
},
{
"module": "node-env"
},
{
"module": "node-tools"
},
{
"module": "nodejs"
},
{
"module": "okioki",
"has-layer": true
},
{
"module": "openrazer"
},
{
"module": "pacman",
"has-layer": true
},
{
"module": "picom"
},
{
"module": "polychromatic"
},
{
"module": "power",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "powerlevel10k"
},
{
"module": "restic"
},
{
"module": "rofi"
},
{
"module": "screen-lock"
},
{
"module": "sensors"
},
{
"module": "slack"
},
{
"module": "snapd"
},
{
"module": "ssh-client"
},
{
"module": "sshd"
},
{
"module": "sudo"
},
{
"module": "systemd"
},
{
"module": "systemd-resolved"
},
{
"module": "time-sync"
},
{
"module": "triggerhappy"
},
{
"module": "xclip"
},
{
"module": "xdg",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "xorg"
},
{
"module": "xterm"
},
{
"module": "zsh"
},
{
"module": "zsh-autosuggestions"
},
{
"module": "zsh-syntax-highlighting"
}
],
"pins": {
"wildcard-resolution": {
"node": "novox",
"module": "dnsmasq"
}
}
}
-584
View File
@@ -1,584 +0,0 @@
{
"v": 1,
"node": "novox",
"decided": {
"endpoint": "142.132.152.141:51820",
"site": "hosting",
"hub": true,
"address": "10.10.0.1",
"public-domain": "novox.be",
"account": "jochens",
"agent-account": "nox",
"tunnel": {
"interface": "wg0",
"unit": "wg-quick@wg0",
"config": "/etc/wireguard/wg0.conf",
"port": 51820,
"address": "10.10.0.1/24",
"range": "10.10.0.0/24",
"public-key": "xW0ETQwyR9rHi2XSfOJrKkairE+Fp70FPNNcirqMixY="
},
"peer-names": {
"XT1l25Z0/YhRNQ8FQM+9XsAp7chx5MI0eLOOg1SUEXA=": "shanks",
"mq76YPW0FrExhAvt/ZG1SB2BK6/cPISKgXO53SEMazU=": "g14",
"zTKYP6Sz1hDwnwxIHgiGXYAeGcWbqti3tcSvKEA0Ew4=": "ace"
}
},
"modules": [
{
"module": "artifact-store-tools",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "avahi"
},
{
"module": "ca-trust"
},
{
"module": "claude-code",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "claude-licence-manager",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"secret": {
"node": "novox",
"module": "mesh-vault"
}
}
},
{
"module": "dbus"
},
{
"module": "de-spiegel",
"ports": {
"35621": 20005
},
"bindings": {
"smtp": {
"node": "novox",
"module": "mailu"
}
}
},
{
"module": "desk-channel",
"has-layer": true
},
{
"module": "dev-tools"
},
{
"module": "disk-load"
},
{
"module": "distribution",
"layer": {
"ports": {
"5000": 5100
}
},
"has-layer": true
},
{
"module": "dnsmasq"
},
{
"module": "docker"
},
{
"module": "fail2ban"
},
{
"module": "gitea",
"ports": {
"3000": 20000
},
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"secret": {
"node": "novox",
"module": "mesh-vault"
}
},
"layer": {
"expose": {
"22": "anywhere"
},
"ports": {
"22": 222
}
},
"has-layer": true
},
{
"module": "go"
},
{
"module": "hostname",
"has-layer": true
},
{
"module": "invoicing",
"ports": {
"80": 20007,
"9000": 20008
},
"bindings": {
"mongodb-database": {
"node": "novox",
"module": "mongodb"
},
"s3-bucket": {
"node": "novox",
"module": "minio"
}
}
},
{
"module": "kernel",
"has-layer": true
},
{
"module": "keycloak",
"ports": {
"8080": 20009
},
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
}
},
"has-layer": true
},
{
"module": "link2pay"
},
{
"module": "localization"
},
{
"module": "logrotate"
},
{
"module": "mailu",
"ports": {
"110": 110,
"143": 143,
"25": 25,
"4243": 20004,
"465": 465,
"587": 587,
"8080": 20010,
"993": 993,
"995": 995
},
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"secret": {
"node": "novox",
"module": "mesh-vault"
}
},
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "mesh-build-go",
"has-layer": true
},
{
"module": "mesh-build-node",
"bindings": {
"npm-package-registry": {
"node": "novox",
"module": "gitea"
}
}
},
{
"module": "mesh-catalog",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
}
}
},
{
"module": "mesh-cli"
},
{
"module": "mesh-config"
},
{
"module": "mesh-controller"
},
{
"module": "mesh-delivery-checker"
},
{
"module": "mesh-delivery-forge-bridge"
},
{
"module": "mesh-delivery-gate",
"has-layer": true
},
{
"module": "mesh-delivery-indexer"
},
{
"module": "mesh-delivery-teller"
},
{
"module": "mesh-delivery-watchman"
},
{
"module": "mesh-files"
},
{
"module": "mesh-grants"
},
{
"module": "mesh-host",
"has-layer": true
},
{
"module": "mesh-issues"
},
{
"module": "mesh-knowledge",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
}
}
},
{
"module": "mesh-mail",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"s3-bucket": {
"node": "novox",
"module": "minio"
}
},
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "mesh-mail-reader",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
}
}
},
{
"module": "mesh-mail-web",
"bindings": {
"oidc-client": {
"node": "novox",
"module": "keycloak"
}
},
"layer": {
"endpoints": {
"web": {
"reach": "internal"
}
}
},
"has-layer": true
},
{
"module": "mesh-provisioner"
},
{
"module": "mesh-provisioner-client"
},
{
"module": "mesh-reads"
},
{
"module": "mesh-vault"
},
{
"module": "mesh-wireguard"
},
{
"module": "messenger",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "minio",
"ports": {
"9000": 20001,
"9001": 20002
}
},
{
"module": "mongodb",
"ports": {
"27017": 20006
}
},
{
"module": "mssql",
"layer": {
"ports": {
"1433": 4848
}
},
"has-layer": true
},
{
"module": "nats"
},
{
"module": "netcheck"
},
{
"module": "nextcloud",
"ports": {
"80": 20003
},
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"s3-bucket": {
"node": "novox",
"module": "minio"
}
}
},
{
"module": "nftables"
},
{
"module": "node-env"
},
{
"module": "node-tools"
},
{
"module": "nodejs"
},
{
"module": "only-office"
},
{
"module": "pacman",
"has-layer": true
},
{
"module": "photos",
"ports": {
"9000": 20011
},
"bindings": {
"mongodb-database": {
"node": "novox",
"module": "mongodb"
},
"s3-bucket": {
"node": "novox",
"module": "minio"
}
}
},
{
"module": "postgres",
"layer": {
"ports": {
"5432": 6852
}
},
"has-layer": true
},
{
"module": "power",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "powerlevel10k"
},
{
"module": "records",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "restic",
"layer": {
"places": {
"repository": "/home/restic"
}
},
"has-layer": true
},
{
"module": "route-proxy"
},
{
"module": "ssh-client"
},
{
"module": "sshd"
},
{
"module": "step-ca"
},
{
"module": "sudo"
},
{
"module": "systemd"
},
{
"module": "systemd-networkd"
},
{
"module": "telegram"
},
{
"module": "time-sync"
},
{
"module": "umami",
"bindings": {
"postgres-database": {
"node": "novox",
"module": "postgres"
},
"secret": {
"node": "novox",
"module": "mesh-vault"
}
},
"layer": {
"expose": {
"3000": "mesh"
},
"ports": {
"3000": 1212
}
},
"has-layer": true
},
{
"module": "website",
"ports": {
"8080": 20012
}
},
{
"module": "zsh",
"has-layer": true
},
{
"module": "zsh-autosuggestions"
},
{
"module": "zsh-syntax-highlighting"
}
],
"holds": [
{
"claim": "git",
"scope": "mesh",
"module": "gitea",
"site": "hosting"
},
{
"claim": "mesh-artifact-store",
"scope": "mesh",
"module": "distribution",
"site": "hosting"
},
{
"claim": "mesh-broker",
"scope": "mesh",
"module": "nats",
"site": "hosting"
},
{
"claim": "mesh-catalog",
"scope": "mesh",
"module": "mesh-catalog",
"site": "hosting"
},
{
"claim": "mesh-config",
"scope": "mesh",
"module": "mesh-config",
"site": "hosting"
},
{
"claim": "mesh-controller",
"scope": "mesh",
"module": "mesh-controller",
"site": "hosting"
},
{
"claim": "mesh-delivery",
"scope": "mesh",
"module": "mesh-delivery-gate",
"site": "hosting"
},
{
"claim": "mesh-dns-resolver",
"scope": "mesh",
"module": "dnsmasq",
"site": "hosting"
},
{
"claim": "mesh-provisioner",
"scope": "mesh",
"module": "mesh-provisioner",
"site": "hosting"
},
{
"claim": "mesh-store",
"scope": "mesh",
"module": "postgres",
"site": "hosting"
},
{
"claim": "mesh-vault",
"scope": "mesh",
"module": "mesh-vault",
"site": "hosting"
},
{
"claim": "npm-package-registry",
"scope": "mesh",
"module": "gitea",
"site": "hosting"
}
]
}
-290
View File
@@ -1,290 +0,0 @@
{
"v": 1,
"node": "shanks",
"decided": {
"site": "home",
"address": "10.10.0.3",
"account": "jochen",
"tunnel": {
"interface": "wg0",
"unit": "wg-quick@wg0",
"config": "/etc/wireguard/wg0.conf",
"port": 51820,
"address": "10.10.0.3/24",
"range": "10.10.0.0/24",
"public-key": "XT1l25Z0/YhRNQ8FQM+9XsAp7chx5MI0eLOOg1SUEXA="
}
},
"modules": [
{
"module": "adwaita"
},
{
"module": "avahi"
},
{
"module": "blueman"
},
{
"module": "bluetooth"
},
{
"module": "brother",
"bindings": {
"s3-bucket": {
"node": "novox",
"module": "minio"
}
},
"has-layer": true
},
{
"module": "buildah"
},
{
"module": "ca-trust"
},
{
"module": "claude-code",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "clipmenu"
},
{
"module": "cups"
},
{
"module": "dbus"
},
{
"module": "dev-tools"
},
{
"module": "disk-load"
},
{
"module": "dmenu"
},
{
"module": "docker"
},
{
"module": "docker-compose"
},
{
"module": "dunst"
},
{
"module": "fail2ban"
},
{
"module": "feh"
},
{
"module": "flatpak"
},
{
"module": "fonts"
},
{
"module": "forticlient"
},
{
"module": "gnome-keyring"
},
{
"module": "go"
},
{
"module": "hostname",
"has-layer": true
},
{
"module": "i3"
},
{
"module": "i3status-rust"
},
{
"module": "kernel",
"has-layer": true
},
{
"module": "lab",
"bindings": {
"npm-package-registry": {
"node": "novox",
"module": "gitea"
}
},
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "lemurs"
},
{
"module": "localization"
},
{
"module": "logrotate"
},
{
"module": "mesh-build-go",
"has-layer": true
},
{
"module": "mesh-build-image"
},
{
"module": "mesh-build-node",
"bindings": {
"npm-package-registry": {
"node": "novox",
"module": "gitea"
}
}
},
{
"module": "mesh-cli"
},
{
"module": "mesh-delivery-builder"
},
{
"module": "mesh-delivery-view"
},
{
"module": "mesh-files"
},
{
"module": "mesh-host",
"has-layer": true
},
{
"module": "mesh-mail-notifier"
},
{
"module": "mesh-provisioner-client"
},
{
"module": "mesh-reads"
},
{
"module": "mesh-wireguard"
},
{
"module": "mounts",
"has-layer": true
},
{
"module": "netcheck"
},
{
"module": "networkmanager"
},
{
"module": "nextcloud-client"
},
{
"module": "nftables"
},
{
"module": "nm-applet"
},
{
"module": "node-env"
},
{
"module": "node-tools"
},
{
"module": "nodejs"
},
{
"module": "openrazer"
},
{
"module": "pacman",
"has-layer": true
},
{
"module": "picom"
},
{
"module": "polychromatic"
},
{
"module": "power",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "powerlevel10k"
},
{
"module": "restic"
},
{
"module": "rofi"
},
{
"module": "screen-lock"
},
{
"module": "slack"
},
{
"module": "snapd"
},
{
"module": "ssh-client"
},
{
"module": "sshd"
},
{
"module": "sudo"
},
{
"module": "systemd"
},
{
"module": "systemd-resolved"
},
{
"module": "time-sync"
},
{
"module": "xclip"
},
{
"module": "xdg",
"has-mesh-wide": true,
"has-layer": true
},
{
"module": "xorg"
},
{
"module": "xterm"
},
{
"module": "zsh"
},
{
"module": "zsh-autosuggestions"
},
{
"module": "zsh-syntax-highlighting"
}
],
"pins": {
"wildcard-resolution": {
"node": "novox",
"module": "dnsmasq"
}
}
}