Let the operator set the agent's managed settings through claude-code
managed-settings.json carried only the mesh's fixed keys, so permissions and auto-mode rules could only be set by hand per machine, outside the mesh. A managed_settings setting is laid under the mesh's keys, which still win.
This commit is contained in:
@@ -94,6 +94,40 @@ func TestASettingCannotReplaceTheMeshsOwnEntryAndABadNameIsLeftOut(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheOperatorsManagedSettingsAreLaidUnderTheMeshsOwnKeys(t *testing.T) {
|
||||
settings := Settings{ManagedSettings: map[string]any{
|
||||
"autoMode": map[string]any{"allow": []any{"merging an approved pull request"}},
|
||||
"permissions": map[string]any{"deny": []any{"Bash(rm -rf /)"}},
|
||||
"attribution": map[string]any{"commit": "made by a machine"},
|
||||
"allowAllClaudeAiMcps": false,
|
||||
"apiKeyHelper": "/somewhere/else",
|
||||
}}
|
||||
read := func(binding *Binding) map[string]any {
|
||||
var m map[string]any
|
||||
out := Render(Facts{Console: "x"}, settings, binding, "/h", nil)
|
||||
if err := json.Unmarshal([]byte(out["managed-settings.json"]), &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
m := read(nil)
|
||||
if allow := m["autoMode"].(map[string]any)["allow"].([]any); len(allow) != 1 || allow[0] != "merging an approved pull request" {
|
||||
t.Errorf("the operator's auto mode was not carried: %v", m["autoMode"])
|
||||
}
|
||||
if m["permissions"] == nil {
|
||||
t.Errorf("the operator's permissions were not carried: %v", m)
|
||||
}
|
||||
if !reflect.DeepEqual(m["attribution"], map[string]any{"commit": "", "pr": ""}) || m["allowAllClaudeAiMcps"] != true {
|
||||
t.Errorf("a setting replaced the mesh's own keys: %v", m)
|
||||
}
|
||||
if _, ok := m["apiKeyHelper"]; ok {
|
||||
t.Errorf("a setting named a key-helper the licence did not: %v", m)
|
||||
}
|
||||
if helper := read(&Binding{Licence: "api", Kind: "api-key"})["apiKeyHelper"]; helper != "/h" {
|
||||
t.Errorf("an API-key licence's key-helper was replaced: %v", helper)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the credentials file -----------------------------------------------------------------------------
|
||||
|
||||
func i64(v int64) *int64 { return &v }
|
||||
|
||||
@@ -10,9 +10,11 @@ package main
|
||||
// servers the operator declared or registered through this module. Exclusive by
|
||||
// the vendor's rule — a server not listed here does not load (operator's choice,
|
||||
// 2026-10-03).
|
||||
// managed-settings.json the mesh's keys only: the repositories' attribution convention, the claude.ai
|
||||
// connectors kept beside the managed servers, and — for an API-key licence only —
|
||||
// the key-helper. A person's preferences are theirs.
|
||||
// managed-settings.json the keys the operator set in this module's `managed_settings` (the agent's
|
||||
// permissions and auto mode, say), under the mesh's own keys, which always win:
|
||||
// the repositories' attribution convention, the claude.ai connectors kept beside
|
||||
// the managed servers, and — for an API-key licence only — the key-helper. A
|
||||
// person's preferences are theirs, in their own settings.
|
||||
// CLAUDE.md how a session on this mesh works, who this node is, the conventions.
|
||||
|
||||
import (
|
||||
@@ -38,6 +40,8 @@ type Facts struct {
|
||||
type Settings struct {
|
||||
Role string `json:"role"`
|
||||
MCPServers map[string]map[string]any `json:"mcp_servers"`
|
||||
// ManagedSettings are keys of the agent's managed settings the operator sets, for the mesh or a node.
|
||||
ManagedSettings map[string]any `json:"managed_settings"`
|
||||
}
|
||||
|
||||
// Binding is the licence this node holds, as it was last applied.
|
||||
@@ -105,7 +109,14 @@ func Render(facts Facts, settings Settings, binding *Binding, helperPath string,
|
||||
}
|
||||
servers[meshEntry] = map[string]any{"type": "http", "url": facts.Console}
|
||||
|
||||
managed := map[string]any{"attribution": map[string]any{"commit": "", "pr": ""}, "allowAllClaudeAiMcps": true}
|
||||
managed := map[string]any{}
|
||||
for key, value := range settings.ManagedSettings {
|
||||
managed[key] = value
|
||||
}
|
||||
// The mesh's own keys are laid last: a setting never replaces them.
|
||||
managed["attribution"] = map[string]any{"commit": "", "pr": ""}
|
||||
managed["allowAllClaudeAiMcps"] = true
|
||||
delete(managed, "apiKeyHelper")
|
||||
if binding != nil && binding.Kind == "api-key" {
|
||||
managed["apiKeyHelper"] = helperPath
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user