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:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user