Files
mesh-catalog/modules/claude-code/cmd/claude-code/replace_registered_test.go
T
jschoubben 657cb2b6c8 A section registered before the controller's terminal took over, or set there, is replaced or removed by a proposal the operator approves
It could not be changed at all: propose refused a name set there and never replaced one. The ask now says plainly that
it replaces or removes the registered section and binds its text's digest; on Approve every node renders the new text
in the place of the old (a removal leaves it out); a replacement keeps the section's own heading form (issue 340 note 14).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 22:14:55 +01:00

182 lines
8.1 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
"time"
)
// A section registered for the mesh before the controller's terminal took that over, or set at the terminal, is
// replaced or removed by a proposal the operator approves (novox/hq ADR 0274, issue 340 note 14).
const registeredOld = "# What this mesh is\n\n## Its purpose\n\nThe old words.\n"
const registeredNew = "# What this mesh is\n\n## Its purpose\n\nThe new words.\n"
// register places the old text the way the node keeps it: in the registered items (mesh scope) or in the settings.
func register(t *testing.T, p Paths, where string) {
t.Helper()
switch where {
case "registered":
raw, _ := json.Marshal(map[string]Item{"mesh.instructions.what-this-mesh-is": {Kind: KindInstructions,
Name: "what-this-mesh-is", Scope: ScopeMesh, Files: one("what-this-mesh-is", registeredOld), By: "g14"}})
writeFile(t, p.config(), string(raw))
case "terminal":
raw, _ := json.Marshal(map[string]any{"instructions": map[string]string{"what-this-mesh-is": registeredOld}})
writeFile(t, p.Settings, string(raw))
}
}
func TestARegisteredSectionIsReplacedOrRemovedOnTheOperatorsApproval(t *testing.T) {
for _, where := range []string{"registered", "terminal"} {
t.Run(where, func(t *testing.T) {
p, _ := node(t, "g14")
other, _ := node(t, "ace")
register(t, p, where)
register(t, other, where)
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
propose := func(in ProposeInput, at time.Time) string {
t.Helper()
in.Scope, in.Name = ScopeMesh, "what-this-mesh-is"
out, err := Propose(p, in, store, r, at)
if err != nil {
t.Fatal(err)
}
return out["proposed"].(string)
}
// The same text is no change; a new section with such headings is still refused.
if _, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "what-this-mesh-is", Text: registeredOld}, store, r, t0); err == nil {
t.Error("the registered text was proposed over itself")
}
if _, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "fresh", Text: registeredNew}, store, r, t0); err == nil {
t.Error("a new section may hold the mesh's own headings")
}
id := propose(ProposeInput{Text: registeredNew}, t0)
a := r.published[id]
if a.Headline != "Replace Claude instructions 'what-this-mesh-is'?" ||
!strings.Contains(a.Explanation, "in place of the instructions 'what-this-mesh-is' registered before") {
t.Errorf("the ask does not say it replaces the registered section: %q / %q", a.Headline, a.Explanation)
}
if !strings.Contains(a.Details, "It replaces the registered instructions of this name, whose text has the fingerprint "+
fingerprintOf(textDigest(registeredOld))) || !strings.Contains(a.Details, "The new words.") {
t.Errorf("the ask does not show the text and what it replaces: %q", a.Details)
}
if got := renderedInstructions(t, p); !strings.Contains(got, "The old words.") || strings.Contains(got, "The new words.") {
t.Fatal("a proposal changed what renders")
}
// Declined, nothing changes.
r.close(r.chose(id, OptionDecline, t0.Add(time.Hour)))
if changed, _, _ := Reconcile(p, store, r, nil, t0.Add(time.Hour)); changed {
t.Error("a decline changed what renders")
}
// Approved, the registered section is replaced in place, on every node that proves the warrant.
id = propose(ProposeInput{Text: registeredNew}, t0.Add(2*time.Hour))
r.close(r.chose(id, OptionApprove, t0.Add(3*time.Hour)))
for _, n := range []Paths{p, other} {
if changed, _, err := Reconcile(n, store, r, nil, t0.Add(3*time.Hour)); err != nil || !changed {
t.Fatalf("%s: the approval changed nothing (%v)", n.Node, err)
}
got := renderedInstructions(t, n)
if strings.Contains(got, "The old words.") || strings.Count(got, "The new words.") != 1 ||
strings.Count(got, "### what-this-mesh-is") != 1 {
t.Fatalf("%s: the section was not replaced once, in place:\n%s", n.Node, got)
}
if strings.Contains(got, "## Instructions the operator approved") {
t.Errorf("%s: a replacement is listed apart from the section it replaces", n.Node)
}
}
// claude_code_registrations and claude_code_proposals show it.
reg, err := Registrations(p, &bus{kept: map[string]map[string]any{}}, memConfig{})
if err != nil {
t.Fatal(err)
}
raw, _ := json.Marshal(reg)
if !strings.Contains(string(raw), "registered text replaced by an approved proposal") {
t.Errorf("claude_code_registrations does not show the replacement: %s", raw)
}
list, _ := ListProposals(p, store, r, "", t0.Add(4*time.Hour))
raw, _ = json.Marshal(list)
if !strings.Contains(string(raw), "replaces the registered section whose text") {
t.Errorf("claude_code_proposals does not show the replacement: %s", raw)
}
// A removal of the registered section is asked the same way and leaves nothing.
rid := propose(ProposeInput{Remove: true}, t0.Add(5*time.Hour))
if got := r.published[rid]; got.Headline != "Remove Claude instructions 'what-this-mesh-is'?" {
t.Errorf("removal ask: %q", got.Headline)
}
r.close(r.chose(rid, OptionApprove, t0.Add(6*time.Hour)))
if changed, _, _ := Reconcile(p, store, r, nil, t0.Add(6*time.Hour)); !changed {
t.Fatal("the approved removal changed nothing")
}
if got := renderedInstructions(t, p); strings.Contains(got, "what-this-mesh-is") {
t.Errorf("the removal left the section:\n%s", got)
}
})
}
}
func TestARegisteredSectionIsRemovedByAProposalToo(t *testing.T) {
p, _ := node(t, "g14")
register(t, p, "registered")
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
out, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "what-this-mesh-is", Remove: true}, store, r, t0)
if err != nil {
t.Fatal(err)
}
id := out["proposed"].(string)
if !strings.Contains(r.published[id].Explanation, "registered") || !strings.Contains(r.published[id].Details, "registered text it removes") {
t.Errorf("the ask does not say it removes a registered section: %+v", r.published[id])
}
r.close(r.chose(id, OptionApprove, t0.Add(time.Hour)))
if _, _, err := Reconcile(p, store, r, nil, t0.Add(time.Hour)); err != nil {
t.Fatal(err)
}
if got := renderedInstructions(t, p); strings.Contains(got, "The old words") {
t.Errorf("the registered section still renders:\n%s", got)
}
// Nothing registered and nothing approved: still refused.
if _, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: "nothing-here", Remove: true}, store, r, t0); err == nil {
t.Error("a section nobody holds was removed")
}
}
func TestAnApprovalIsBoundToTheRegisteredTextItReplaces(t *testing.T) {
prop := Proposal{ID: "instr-0123456789abcdef", Action: ActionReplace, Scope: ScopeMesh, Name: "x", Text: "t\n", Digest: textDigest("t\n"),
From: "g14", At: t0}
plain := prop.Act(OptionApprove)
if _, ok := plain["over"]; ok {
t.Error("a proposal over nothing binds an over: its older warrants would no longer hold")
}
prop.Over = textDigest("old\n")
if prop.Act(OptionApprove)["over"] != prop.Over {
t.Error("the act does not bind the registered text it replaces")
}
prop.Over = "not a digest"
if ProposalProblem(prop) == "" {
t.Error("a proposal over a text with no digest is one this module makes")
}
}
func TestANodeSectionRegisteredForThisNodeIsReplacedOnlyThere(t *testing.T) {
p, _ := node(t, "g14")
raw, _ := json.Marshal(map[string]Item{"node.g14.instructions.local": {Kind: KindInstructions, Name: "local", Scope: ScopeNode,
Files: one("local", "## Local\n\nold\n"), By: "g14"}})
writeFile(t, p.config(), string(raw))
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
out, err := Propose(p, ProposeInput{Scope: ScopeNode, Nodes: []string{"g14"}, Name: "local", Text: "## Local\n\nnew\n"}, store, r, t0)
if err != nil {
t.Fatal(err)
}
id := out["proposed"].(string)
r.close(r.chose(id, OptionApprove, t0.Add(time.Hour)))
if _, _, err := Reconcile(p, store, r, nil, t0.Add(time.Hour)); err != nil {
t.Fatal(err)
}
got := renderedInstructions(t, p)
if strings.Contains(got, "\nold\n") || !strings.Contains(got, "\nnew\n") || strings.Count(got, "### local") != 1 {
t.Errorf("the node's section was not replaced in place:\n%s", got)
}
}