B7-1: recovery-merged names the branch the merge moved and the module of the record's catalog half

Builds: design 47 G §81, §522, A-161, A-172
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:46:13 +01:00
parent b52dd7ca44
commit 9ee1cbe3f5
2 changed files with 60 additions and 1 deletions
@@ -715,6 +715,33 @@ func TestTheRecordsToolsReadThemAndEndOneByHand(t *testing.T) {
t.Fatal("a recovery with no why was taken")
}
})
t.Run("recovery-merged names the branch the merge moved and the module its record's catalog half names (design 47 G A-161, A-172)", func(t *testing.T) {
var body meshdelivery.RecoveryMergedEvent
rs := &Records{Ledger: newLedgerFake(t), Now: func() time.Time { return at },
Say: func(subject string, b any, id string) error {
if subject == meshdelivery.SubjectRecoveryMerged {
body = b.(meshdelivery.RecoveryMergedEvent)
}
return nil
}}
commit := strings.Repeat("2", 40)
r, _, err := rs.Open(recordMilestone("feat/x", 4), map[string]string{"novox/app@222222222222": commit}, false)
if err != nil {
t.Fatal(err)
}
if err := rs.AddEntry(r, RecordEntry{Module: "mesh-y", Fingerprint: "src2:f2"}); err != nil {
t.Fatal(err)
}
if err := rs.AddEntry(r, RecordEntry{Module: "mesh-x", Fingerprint: "src2:f1", Node: "ace"}); err != nil {
t.Fatal(err)
}
if _, err := rs.Recovery("novox/app", commit, "why", "jochen"); err != nil {
t.Fatal(err)
}
if body.Branch != "feat/x" || body.Module != "mesh-y" {
t.Fatalf("said %+v", body)
}
})
t.Run("a removal asks the runtime's ledger removal of the instance, and one said twice changes nothing", func(t *testing.T) {
was := ledgerCall
t.Cleanup(func() { ledgerCall = was })
@@ -3,9 +3,11 @@ package main
import (
"errors"
"fmt"
"sort"
"strings"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// Recovery names a merge a recovery (design 47 G §81, A-23): the delivery record that holds the commit is marked through its
@@ -36,7 +38,8 @@ func (rs *Records) Recovery(repository, commit, why, by string) (meshdelivery.Re
r.Recovery, r.Sequence = &want, seq
now := rs.now()
if err := rs.say(meshdelivery.SubjectRecoveryMerged, meshdelivery.RecoveryMergedEvent{At: &now, By: by, Commit: head,
Repository: repository, V: 1, Why: why}, "recovery-merged."+r.Instance+"."+head); err != nil {
Repository: repository, V: 1, Why: why,
Branch: branchOf(r), Module: moduleOf(r)}, "recovery-merged."+r.Instance+"."+head); err != nil {
return meshdelivery.RecoveryAnswer{}, fmt.Errorf("the record %s is marked but recovery-merged is not said: %w", r.Instance, err)
}
return meshdelivery.RecoveryAnswer{By: by, Commit: head, Recovery: true, Repository: repository}, nil
@@ -44,3 +47,32 @@ func (rs *Records) Recovery(repository, commit, why, by string) (meshdelivery.Re
}
return meshdelivery.RecoveryAnswer{}, fmt.Errorf("no delivery record holds %s at %s: the merge is taken in first", repository, commit)
}
// branchOf is the watched branch a record's merge moved: the record of a taken source revision is keyed `<branch>.<source>`
// (recordMilestone); a milestone of another form names no branch.
func branchOf(r *Record) string {
part, _, ok := strings.Cut(r.Milestone, ".")
if !ok {
return ""
}
branch, err := keys.UnescapeName(part)
if err != nil {
return ""
}
return branch
}
// moduleOf is the module the merge changed, from the entries of the record's catalog half (those that name no node), the
// modules in order and comma-separated when the merge changed several.
func moduleOf(r *Record) string {
seen := map[string]bool{}
var modules []string
for _, e := range r.Entries {
if e.Node == "" && e.Module != "" && !seen[e.Module] {
seen[e.Module] = true
modules = append(modules, e.Module)
}
}
sort.Strings(modules)
return strings.Join(modules, ", ")
}