A removal of an approved replacement names the registered text's fingerprint; the cases ADR 0642 cites are tests

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 23:16:23 +01:00
parent 6fb27549c9
commit b3fc5b4580
2 changed files with 78 additions and 3 deletions
@@ -208,12 +208,13 @@ func (p Proposal) glanceAsk() oc.Ask {
}
var d strings.Builder
if p.Action == ActionRemove {
removes := "The approved text it removes"
removes, print := "The approved text it removes", p.Fingerprint()
if p.Over != "" {
removes = "The registered text it removes"
// The registered text, never the replacement's: a removal of a replacement removes both.
removes, print = "The registered text it removes", fingerprintOf(p.Over)
}
fmt.Fprintf(&d, "Proposed through the claude-code module on %s at %s UTC. %s has the fingerprint %s.",
p.From, p.At.UTC().Format("15:04 on 2 Jan"), removes, p.Fingerprint())
p.From, p.At.UTC().Format("15:04 on 2 Jan"), removes, print)
} else {
if whole {
d.WriteString("The text:\n" + shown + "\n")
@@ -179,3 +179,77 @@ func TestANodeSectionRegisteredForThisNodeIsReplacedOnlyThere(t *testing.T) {
t.Errorf("the node's section was not replaced in place:\n%s", got)
}
}
// The cases ADR 0642 cites, by name.
func TestRegisteredSectionEdgeCases(t *testing.T) {
const name = "what-this-mesh-is"
t.Run("TestANameRegisteredWithTwoTextsIsRefusedUntilOneIsUnregistered", func(t *testing.T) {
p, _ := node(t, "g14")
register(t, p, "registered")
raw, _ := json.Marshal(map[string]any{"instructions": map[string]string{name: registeredNew}})
writeFile(t, p.Settings, string(raw))
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
in := ProposeInput{Scope: ScopeMesh, Name: name, Text: "A third text.\n"}
if _, err := Propose(p, in, store, r, t0); err == nil || !strings.Contains(err.Error(), "different texts") {
t.Fatalf("a name registered with two texts was proposed over: %v", err)
}
if _, err := Propose(p, ProposeInput{Scope: ScopeMesh, Name: name, Remove: true}, store, r, t0); err == nil {
t.Error("a name registered with two texts was removed")
}
writeFile(t, p.config(), "{}") // one of the two is unregistered
if _, err := Propose(p, in, store, r, t0); err != nil {
t.Errorf("refused after one was unregistered: %v", err)
}
})
t.Run("TestRemovingAReplacementLeavesNeitherText", func(t *testing.T) {
p, _ := node(t, "g14")
register(t, p, "registered")
store, r := &memProposals{m: map[string]json.RawMessage{}}, newRouter()
approve := func(in ProposeInput, at time.Time) string {
in.Scope, in.Name = ScopeMesh, name
out, err := Propose(p, in, store, r, at)
if err != nil {
t.Fatal(err)
}
id := out["proposed"].(string)
r.close(r.chose(id, OptionApprove, at.Add(time.Minute)))
if _, _, err := Reconcile(p, store, r, nil, at.Add(time.Minute)); err != nil {
t.Fatal(err)
}
return id
}
approve(ProposeInput{Text: registeredNew}, t0)
rid := approve(ProposeInput{Remove: true}, t0.Add(time.Hour))
if got := renderedInstructions(t, p); strings.Contains(got, "The old words.") || strings.Contains(got, "The new words.") {
t.Errorf("a text is left:\n%s", got)
}
// The ask names the registered text's fingerprint, not the replacement's.
d := r.published[rid].Details
if !strings.Contains(d, "The registered text it removes has the fingerprint "+fingerprintOf(textDigest(registeredOld))) ||
strings.Contains(d, fingerprintOf(textDigest(registeredNew))) {
t.Errorf("the removal names the wrong fingerprint: %q", d)
}
})
t.Run("TestAChangedRegisteredTextOutlivesItsApproval", func(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: name, Text: registeredNew}, 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)
}
// The registered text changes after the approval: the approval was bound to the old text, so it is not
// taken for the new one, which renders as registered.
raw, _ := json.Marshal(map[string]Item{"mesh.instructions." + name: {Kind: KindInstructions, Name: name,
Scope: ScopeMesh, Files: one(name, "A later registered text.\n"), By: "g14"}})
writeFile(t, p.config(), string(raw))
if got := renderedInstructions(t, p); !strings.Contains(got, "A later registered text.") {
t.Errorf("the changed registered text was swallowed by the old approval:\n%s", got)
}
})
}