Files
mesh-catalog/modules/claude-code/cmd/claude-code/config_test.go
T
jochen 3677af6079
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh, merge-check-typescript.sh): main's version judged it; the change's judges the pull r…
mesh/delivery superseded: a newer head of the same pull request
claude-code: write the home scope only into an agent account's own home
On the nodes where the agent runs as the operator, the home scope wrote the
operator's own ~/.claude, whose rules and skills load in every session, and
any node's tool could be addressed from another (hq issue 340). Take it
only for the serving node, where its home is an agent account's own; never
place a bus item in the operator's home. Ignore the built binary.
2026-10-09 09:41:52 +02:00

457 lines
18 KiB
Go

package main
// The agent's configuration registered at three scopes (novox/hq ADR 0216): each test is one row of the
// record's "How it is checked".
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// memConfig is the `config` state as a map, shared by the nodes of a test the way the bus shares it.
type memConfig map[string]json.RawMessage
func (m memConfig) Put(key string, value any) error {
raw, err := json.Marshal(value)
m[key] = raw
return err
}
func (m memConfig) Delete(key string) error { delete(m, key); return nil }
func (m memConfig) Keys() ([]string, error) {
out := []string{}
for k := range m {
out = append(out, k)
}
return out, nil
}
func (m memConfig) Get(key string) (json.RawMessage, bool, error) {
raw, ok := m[key]
return raw, ok, nil
}
// deliver hands every key of the state to a node's view, as its watch would.
func deliver(m memConfig, v *ConfigView) {
for key, raw := range m {
var it Item
_ = json.Unmarshal(raw, &it)
v.Take(key, "put", &it)
}
}
// registeredBefore puts an item on the bus as a registration made before mesh- and node-scope text moved to the
// controller's terminal (novox/hq issue 340), for each node given (none at the mesh scope), as every node's watch
// would hand it over to this one, and renders.
func registeredBefore(t *testing.T, p Paths, w map[string]string, state memConfig, view *ConfigView, it Item, nodes ...string) {
t.Helper()
if it.Scope == ScopeMesh {
nodes = []string{""}
}
for _, n := range nodes {
item := it
if err := state.Put(it.Key(n), item); err != nil {
t.Fatal(err)
}
view.Take(it.Key(n), "put", &item)
}
if _, err := RenderNow(p, writer(w)); err != nil {
t.Fatal(err)
}
}
func one(name, content string) map[string]string { return map[string]string{name + ".md": content} }
func pluginOf(t *testing.T, w map[string]string) map[string]PluginFile {
t.Helper()
var files map[string]PluginFile
if err := json.Unmarshal([]byte(w[MarketplaceDir+"/"]), &files); err != nil {
t.Fatalf("no marketplace written: %v", err)
}
return files
}
func TestEachKindLandsInItsOnePlace(t *testing.T) {
p, w := node(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
register := func(it Item) {
t.Helper()
if problem := it.Problem(); problem != "" {
t.Fatalf("%s %s: %s", it.Kind, it.Name, problem)
}
registeredBefore(t, p, w, state, view, it)
}
register(Item{Kind: KindSkill, Name: "review", Scope: ScopeMesh,
Files: map[string]string{"SKILL.md": "---\nname: review\ndescription: d\n---\nbody", "scripts/run.sh": "#!/bin/sh\necho hi\n"}})
register(Item{Kind: KindAgent, Name: "reviewer", Scope: ScopeMesh, Files: one("reviewer", "---\nname: reviewer\n---\nx")})
register(Item{Kind: KindCommand, Name: "ship", Scope: ScopeMesh, Files: one("ship", "ship it")})
register(Item{Kind: KindOutputStyle, Name: "terse", Scope: ScopeMesh, Files: one("terse", "---\nname: terse\n---\nshort")})
register(Item{Kind: KindInstructions, Name: "conventions", Scope: ScopeMesh, Files: one("conventions", "Commit in the imperative.")})
plugin := pluginOf(t, w)
root := "plugins/" + Plugin + "/"
for _, want := range []string{".claude-plugin/marketplace.json", root + ".claude-plugin/plugin.json",
root + "skills/review/SKILL.md", root + "skills/review/scripts/run.sh", root + "agents/reviewer.md",
root + "commands/ship.md", root + "output-styles/terse.md"} {
if _, ok := plugin[want]; !ok {
t.Errorf("the plugin lacks %s", want)
}
}
if !plugin[root+"skills/review/scripts/run.sh"].Executable {
t.Error("a script is not executable")
}
if !strings.Contains(w["CLAUDE.md"], "### conventions\n\nCommit in the imperative.") {
t.Errorf("the instruction section is not in the managed instruction file:\n%s", w["CLAUDE.md"])
}
var managed map[string]any
_ = json.Unmarshal([]byte(w["managed-settings.json"]), &managed)
if managed["enabledPlugins"].(map[string]any)[Plugin+"@"+Plugin] != true {
t.Errorf("managed settings: %v", managed)
}
if _, inPlugin := plugin[root+"settings.json"]; inPlugin {
t.Error("settings were put in the plugin, which drops them")
}
}
func TestAMeshItemReachesEveryNodeANodeItemOneAndAHomeItemOneHome(t *testing.T) {
a, wa := confinedNode(t, "laptop")
b, wb := node(t, "server")
state := memConfig{}
va, vb := NewConfigView(a), NewConfigView(b)
for _, r := range []struct {
it Item
nodes []string
}{
{Item{Kind: KindCommand, Name: "everywhere", Scope: ScopeMesh, Files: one("everywhere", "x")}, nil},
{Item{Kind: KindCommand, Name: "server-only", Scope: ScopeNode, Files: one("server-only", "x")}, []string{"server"}},
{Item{Kind: KindCommand, Name: "at-home", Scope: ScopeHome, Files: one("at-home", "x")}, []string{"laptop"}},
} {
if r.it.Scope == ScopeHome {
if _, err := Register(a, r.it, r.nodes, false, state, va, writer(wa)); err != nil {
t.Fatal(err)
}
continue
}
registeredBefore(t, a, wa, state, va, r.it, r.nodes...)
}
deliver(state, vb)
if _, err := RenderNow(b, writer(wb)); err != nil {
t.Fatal(err)
}
root := "plugins/" + Plugin + "/commands/"
pa, pb := pluginOf(t, wa), pluginOf(t, wb)
if _, ok := pa[root+"everywhere.md"]; !ok {
t.Error("the mesh item is missing on the laptop")
}
if _, ok := pb[root+"everywhere.md"]; !ok {
t.Error("the mesh item is missing on the server")
}
if _, ok := pa[root+"server-only.md"]; ok {
t.Error("the server's item reached the laptop")
}
if _, ok := pb[root+"server-only.md"]; !ok {
t.Error("the server's item is missing on the server")
}
if _, err := os.Stat(filepath.Join(a.Home, ".claude", "commands", "at-home.md")); err != nil {
t.Error("the home item was not placed in the laptop's home")
}
if _, err := os.Stat(filepath.Join(b.Home, ".claude", "commands", "at-home.md")); err == nil {
t.Error("the laptop's home item reached the server's home")
}
if _, ok := pa[root+"at-home.md"]; ok {
t.Error("a home item went into the plugin")
}
}
func TestTheMeshsOwnKeysCannotBeSetOrReplaced(t *testing.T) {
for _, key := range []string{"extraKnownMarketplaces", "enabledPlugins", "attribution", "apiKeyHelper"} {
it := Item{Kind: KindSettings, Name: SettingsName, Scope: ScopeMesh, Settings: map[string]any{key: true}}
if it.Problem() == "" {
t.Errorf("a registration could set %s", key)
}
}
out := Render(Facts{Console: "x"}, Settings{ManagedSettings: map[string]any{"enabledPlugins": map[string]any{Plugin + "@" + Plugin: false}}},
nil, "/h", nil, Config{})
var managed map[string]any
_ = json.Unmarshal([]byte(out["managed-settings.json"]), &managed)
if managed["enabledPlugins"].(map[string]any)[Plugin+"@"+Plugin] != true {
t.Error("the operator's setting turned the mesh's plugin off")
}
}
func TestTheHomeScopeOwnsOnlyWhatItPlaced(t *testing.T) {
p, w := confinedNode(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
mine := filepath.Join(p.Home, ".claude", "agents", "mine.md")
_ = os.MkdirAll(filepath.Dir(mine), 0o755)
writeFile(t, mine, "the person's own")
answer, _ := Register(p, Item{Kind: KindAgent, Name: "mine", Scope: ScopeHome, Files: one("mine", "the mesh's")}, nil, false, state, view, writer(w))
if answer["registered"] != false {
t.Fatalf("a name the person uses was taken: %v", answer)
}
if raw, _ := os.ReadFile(mine); string(raw) != "the person's own" {
t.Fatal("the person's file was overwritten")
}
placed := filepath.Join(p.Home, ".claude", "agents", "placed.md")
if _, err := Register(p, Item{Kind: KindAgent, Name: "placed", Scope: ScopeHome, Files: one("placed", "v1")}, nil, false, state, view, writer(w)); err != nil {
t.Fatal(err)
}
if raw, _ := os.ReadFile(placed); string(raw) != "v1" {
t.Fatal("the home item was not placed")
}
if _, err := Register(p, Item{Kind: KindAgent, Name: "placed", Scope: ScopeHome}, nil, true, state, view, writer(w)); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(placed); err == nil {
t.Fatal("unregistering did not remove what the mesh placed")
}
if _, err := os.Stat(mine); err != nil {
t.Fatal("unregistering removed the person's file")
}
// Changed by hand after it was placed: left alone when unregistered.
if _, err := Register(p, Item{Kind: KindAgent, Name: "edited", Scope: ScopeHome, Files: one("edited", "v1")}, nil, false, state, view, writer(w)); err != nil {
t.Fatal(err)
}
edited := filepath.Join(p.Home, ".claude", "agents", "edited.md")
writeFile(t, edited, "changed by hand")
if _, err := Register(p, Item{Kind: KindAgent, Name: "edited", Scope: ScopeHome}, nil, true, state, view, writer(w)); err != nil {
t.Fatal(err)
}
if raw, _ := os.ReadFile(edited); string(raw) != "changed by hand" {
t.Fatal("a placed file changed by hand was removed")
}
}
func TestAnItemAboveTheLimitOrMisshapenIsRefused(t *testing.T) {
big := Item{Kind: KindSkill, Name: "big", Scope: ScopeMesh, Files: map[string]string{"SKILL.md": strings.Repeat("x", MaxItemBytes+1)}}
cases := map[string]Item{
"too big": big,
"a bad name": {Kind: KindAgent, Name: "Bad Name", Scope: ScopeMesh, Files: one("Bad Name", "x")},
"a skill without one": {Kind: KindSkill, Name: "s", Scope: ScopeMesh, Files: map[string]string{"other.md": "x"}},
"a path outside": {Kind: KindSkill, Name: "s", Scope: ScopeMesh, Files: map[string]string{"SKILL.md": "x", "../escape": "x"}},
"a hook at home": {Kind: KindHook, Name: "h", Scope: ScopeHome, Event: "Stop", Command: "true"},
"settings at home": {Kind: KindSettings, Name: SettingsName, Scope: ScopeHome, Settings: map[string]any{"model": "x"}},
"a hook anywhere": {Kind: KindHook, Name: "h", Scope: ScopeMesh, Event: "Stop", Command: "true"},
}
for label, it := range cases {
if it.Problem() == "" {
t.Errorf("%s was accepted", label)
}
}
}
func TestAHomeItemIsImportedAndTheStaleOnesAreNamed(t *testing.T) {
p, _ := node(t, "laptop")
dir := filepath.Join(p.Home, ".claude")
_ = os.MkdirAll(filepath.Join(dir, "skills", "old", "scripts"), 0o755)
writeFile(t, filepath.Join(dir, "skills", "old", "SKILL.md"), "---\nname: old\n---\nuse mcp__gone__do_it")
writeFile(t, filepath.Join(dir, "skills", "old", "scripts", "a.sh"), "#!/bin/sh\n")
it, err := ImportFromHome(p, KindSkill, "old")
if err != nil || len(it.Files) != 2 || it.Files["scripts/a.sh"] == "" {
t.Fatalf("%+v %v", it, err)
}
items := HomeItems(p, Config{Mesh: []Item{{Kind: KindSkill, Name: "old"}}}, Servers{})
if len(items) != 1 || len(items[0].Notes) != 2 {
t.Fatalf("%+v", items)
}
}
// The manifest lists exactly the tools the bundle serves: a tool missing from it is never announced.
func TestTheManifestListsEveryToolServed(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Tools []string `json:"tools"`
}
_ = json.Unmarshal(raw, &m)
listed := map[string]bool{}
for _, n := range m.Tools {
listed[n] = true
}
p, _ := node(t, "laptop")
served := tools(p, nil, NewServerView(p), memConfig{}, NewConfigView(p))
for _, tool := range served {
if !listed[tool.Name] {
t.Errorf("%s is served and not in the manifest", tool.Name)
}
delete(listed, tool.Name)
}
for n := range listed {
t.Errorf("%s is in the manifest and not served", n)
}
}
// The tree writer's comparison: a directory holding exactly the files given, executable bits included.
func TestATreeIsTheSameOnlyWhenEveryFileAndModeIs(t *testing.T) {
dir := t.TempDir()
files := map[string]PluginFile{"a.md": {Content: "a"}, "s/run.sh": {Content: "#!/bin/sh\n", Executable: true}}
_ = os.MkdirAll(filepath.Join(dir, "s"), 0o755)
writeFile(t, filepath.Join(dir, "a.md"), "a")
writeFile(t, filepath.Join(dir, "s", "run.sh"), "#!/bin/sh\n")
if sameTree(dir, files) {
t.Fatal("a script without its executable bit counted as the same")
}
_ = os.Chmod(filepath.Join(dir, "s", "run.sh"), 0o755)
if !sameTree(dir, files) {
t.Fatal("the same tree counted as different")
}
writeFile(t, filepath.Join(dir, "extra.md"), "x")
if sameTree(dir, files) {
t.Fatal("an extra file counted as the same")
}
}
// A registration removed while a node was away is dropped when it is back: the watch hands over only what is
// there, so the view is pruned to the keys the state still holds.
func TestWhatWasRemovedWhileANodeWasAwayIsDropped(t *testing.T) {
p, w := node(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
for _, name := range []string{"kept", "gone"} {
registeredBefore(t, p, w, state, view, Item{Kind: KindCommand, Name: name, Scope: ScopeMesh, Files: one(name, "x")})
}
delete(state, "mesh.command.gone") // removed from another node while this one was off
back := NewConfigView(p) // the node starts again from what it last wrote
if len(back.Items()) != 2 {
t.Fatalf("the view did not start from what was last written: %v", back.Items())
}
keys, _ := state.Keys()
if !back.Prune(keys) || len(back.Items()) != 1 {
t.Fatalf("after pruning: %v", back.Items())
}
}
// The review's findings, each held by a test.
func TestAPathOrAKeyThatIsNotWhatItSaysIsRefused(t *testing.T) {
for label, files := range map[string]map[string]string{
"a dot": {"SKILL.md": "x", ".": "x"},
"an empty segment": {"SKILL.md": "x", "a//b": "x"},
"a parent segment": {"SKILL.md": "x", "a/../b": "x"},
"a file and directory": {"SKILL.md": "x", "a": "x", "a/b": "x"},
} {
if (Item{Kind: KindSkill, Name: "s", Scope: ScopeMesh, Files: files}).Problem() == "" {
t.Errorf("%s was accepted", label)
}
}
p, _ := node(t, "laptop")
v := NewConfigView(p)
home := Item{Kind: KindCommand, Name: "x", Scope: ScopeHome, Files: one("x", "y")}
if v.Take("mesh.command.x", "put", &home); len(v.Items()) != 0 {
t.Fatal("a mesh key holding a home item was taken")
}
for _, key := range []string{"mesh.command.x", "mesh.agent.x"} {
mesh := Item{Kind: KindCommand, Name: "x", Scope: ScopeMesh, Files: one("x", "y")}
v.Take(key, "put", &mesh)
}
if len(v.Items()) != 1 {
t.Fatalf("an item under a key of another kind was taken: %v", v.Items())
}
}
func TestAllIsEveryNodeAndANameThatCannotBeAKeyIsRefused(t *testing.T) {
nodes, err := ExpandNodes([]string{"all"}, func() ([]string, error) { return []string{"ace", "g14"}, nil })
if err != nil || strings.Join(nodes, ",") != "ace,g14" {
t.Fatalf("%v %v", nodes, err)
}
p, w := node(t, "laptop")
answer, _ := Register(p, Item{Kind: KindCommand, Name: "c", Scope: ScopeHome, Files: one("c", "x")}, []string{"a.b"}, false, memConfig{}, NewConfigView(p), writer(w))
if answer["registered"] != false {
t.Fatalf("a node name with a dot was used in a key: %v", answer)
}
if (Item{Kind: KindSettings, Name: SettingsName, Scope: ScopeMesh, Settings: map[string]any{"deniedMcpServers": []any{}}}).Problem() == "" {
t.Fatal("a registration could deny the mesh's console")
}
}
func TestTheOperatorsOwnPluginsAndMarketplacesAreKept(t *testing.T) {
out := Render(Facts{Console: "x"}, Settings{ManagedSettings: map[string]any{
"enabledPlugins": map[string]any{"theirs@market": true},
"extraKnownMarketplaces": map[string]any{"market": map[string]any{"source": map[string]any{"source": "github", "repo": "o/r"}}},
}}, nil, "/h", nil, Config{})
var managed struct {
Enabled map[string]any `json:"enabledPlugins"`
Known map[string]any `json:"extraKnownMarketplaces"`
}
_ = json.Unmarshal([]byte(out["managed-settings.json"]), &managed)
if managed.Enabled["theirs@market"] != true || managed.Enabled[Plugin+"@"+Plugin] != true || managed.Known["market"] == nil || managed.Known[Plugin] == nil {
t.Fatalf("%+v", managed)
}
}
func TestTheHomeLeavesThePersonsChoicesAlone(t *testing.T) {
p, _ := node(t, "laptop")
dir := filepath.Join(p.Home, ".claude")
// An identical file the person made is not taken over, so unregistering never removes it.
_ = os.MkdirAll(filepath.Join(dir, "agents"), 0o755)
writeFile(t, filepath.Join(dir, "agents", "same.md"), "x")
if _, left := PlaceHome(p, map[string]string{"agents/same.md": "x"}); len(left) != 1 {
t.Fatalf("an identical file of the person's was taken over: %v", left)
}
PlaceHome(p, map[string]string{})
if _, err := os.Stat(filepath.Join(dir, "agents", "same.md")); err != nil {
t.Fatal("the person's file was removed")
}
// A placed file the person deleted stays deleted while it is registered.
PlaceHome(p, map[string]string{"commands/c.md": "x"})
_ = os.Remove(filepath.Join(dir, "commands", "c.md"))
PlaceHome(p, map[string]string{"commands/c.md": "x"})
if _, err := os.Stat(filepath.Join(dir, "commands", "c.md")); err == nil {
t.Fatal("a file the person deleted was placed again")
}
// The kind's own directory stays when the mesh's last item in it goes.
PlaceHome(p, map[string]string{"skills/s/SKILL.md": "x"})
PlaceHome(p, map[string]string{})
if _, err := os.Stat(filepath.Join(dir, "skills", "s")); err == nil {
t.Fatal("the item's own directory was left")
}
if _, err := os.Stat(filepath.Join(dir, "skills")); err != nil {
t.Fatal("the kind's directory was removed")
}
// Never through a symbolic link.
elsewhere := t.TempDir()
if err := os.Symlink(elsewhere, filepath.Join(dir, "output-styles")); err != nil {
t.Skip("no symbolic links here")
}
PlaceHome(p, map[string]string{"output-styles/o.md": "x"})
if _, err := os.Stat(filepath.Join(elsewhere, "o.md")); err == nil {
t.Fatal("a file was written through a symbolic link")
}
}
func TestOneFileThatCannotBeWrittenDoesNotStopTheOthers(t *testing.T) {
p, _ := node(t, "laptop")
w := map[string]string{}
failing := func(name, content string) (string, error) {
if name == MarketplaceDir+"/" {
return "", os.ErrPermission
}
w[name] = content
return name + ": written", nil
}
if _, err := RenderNow(p, failing); err == nil {
t.Fatal("the failure was not reported")
}
if w["managed-settings.json"] == "" || w["managed-mcp.json"] == "" || w["CLAUDE.md"] == "" {
t.Fatalf("the other files were not written: %v", w)
}
}
// The answer says what this node wrote even when its own watch took the change first.
func TestTheAnswerSaysWhatWasWrittenWhenTheWatchWasFirst(t *testing.T) {
p, w := confinedNode(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
it := Item{Kind: KindCommand, Name: "c", Scope: ScopeHome, Files: one("c", "x")}
first := it
view.Take(it.Key("laptop"), "put", &first) // the watch, first
answer, err := Register(p, it, nil, false, state, view, writer(w))
if err != nil || answer["rendered here"] == nil || answer["here"] != nil {
t.Fatalf("%v %v", answer, err)
}
}