B7-3: the pinned event's catalog entries name their branch, the record keeps it
A catalog revision is pinned by its watched branch and its number (revisions are numbered per branch): PinnedRevision gains
branch, Pin and Free take it, the key names it so two branches' revision 9 are two pins, and pinsOf says {branch, revision}.
Also the C2 merge's sourcetools imports follow the contracts' new path.
Builds: design 47 G §579, A-171
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -194,10 +194,12 @@ type NodeAway struct {
|
||||
Since time.Time `json:"since"`
|
||||
}
|
||||
|
||||
// PinnedRevision is a graph revision the record points at: its half (catalog or assignment) and its revision.
|
||||
// PinnedRevision is a graph revision the record points at: its half (catalog or assignment), its revision and, for a catalog
|
||||
// revision, the watched branch it is numbered on.
|
||||
type PinnedRevision struct {
|
||||
Revision string `json:"revision"`
|
||||
Half string `json:"half"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
}
|
||||
|
||||
// RecordEnded is how a record ended: when, why, by whom, and the nodes away it ended without.
|
||||
|
||||
@@ -258,7 +258,7 @@ func TestTheDeliveryRecordIsALedgerInstanceKeyedByMilestoneAndHeads(t *testing.T
|
||||
return nil
|
||||
}}
|
||||
old, _, _ := rs.Open("46/F", heads, false)
|
||||
if err := rs.Pin(old, HalfCatalog, 1); err != nil {
|
||||
if err := rs.Pin(old, HalfCatalog, "main", 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now = at.Add(89 * 24 * time.Hour)
|
||||
@@ -273,7 +273,7 @@ func TestTheDeliveryRecordIsALedgerInstanceKeyedByMilestoneAndHeads(t *testing.T
|
||||
if r, _ := rs.Read(old.Instance); r != nil {
|
||||
t.Fatal("a record past its retention is kept")
|
||||
}
|
||||
if !slices.Equal(freed, []string{"46/F [1] the record is past its retention"}) {
|
||||
if !slices.Equal(freed, []string{"46/F [{main 1}] the record is past its retention"}) {
|
||||
t.Fatalf("freed %v", freed)
|
||||
}
|
||||
list, _ := rs.List()
|
||||
@@ -299,11 +299,15 @@ func TestTheDeliveryRecordIsALedgerInstanceKeyedByMilestoneAndHeads(t *testing.T
|
||||
}}
|
||||
r, _, _ := rs.Open("47/G", heads, false)
|
||||
for range 2 {
|
||||
if err := rs.Pin(r, HalfAssignment, 9); err != nil {
|
||||
if err := rs.Pin(r, HalfAssignment, "", 9); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := rs.Pin(r, HalfCatalog, 9); err != nil {
|
||||
if err := rs.Pin(r, HalfCatalog, "main", 9); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Revisions are numbered per watched branch: the same number on another branch is another pin.
|
||||
if err := rs.Pin(r, HalfCatalog, "feat/x", 9); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pins := 0
|
||||
@@ -313,28 +317,36 @@ func TestTheDeliveryRecordIsALedgerInstanceKeyedByMilestoneAndHeads(t *testing.T
|
||||
}
|
||||
}
|
||||
back, _ := rs.Read(r.Instance)
|
||||
if pins != 2 || len(back.Pinned) != 2 {
|
||||
if pins != 3 || len(back.Pinned) != 3 {
|
||||
t.Fatalf("%d pins written, the record holds %+v", pins, back.Pinned)
|
||||
}
|
||||
if len(got) != 3 || !reflect.DeepEqual(got[0], got[1]) || got[0].subject != meshdelivery.SubjectPinned ||
|
||||
!slices.Equal(got[0].pins.Assignment, []int64{9}) || got[2].pins.Catalog[0] != 9 || got[0].pins.Record != r.Instance {
|
||||
if len(got) != 4 || !reflect.DeepEqual(got[0], got[1]) || got[0].subject != meshdelivery.SubjectPinned ||
|
||||
!slices.Equal(got[0].pins.Assignment, []int64{9}) || !slices.Equal(got[2].pins.Catalog, []meshdelivery.PinsCatalogItem{{Branch: "main", Revision: 9}}) ||
|
||||
!slices.Equal(got[3].pins.Catalog, []meshdelivery.PinsCatalogItem{{Branch: "feat/x", Revision: 9}}) || got[0].pins.Record != r.Instance {
|
||||
t.Fatalf("said %+v", got)
|
||||
}
|
||||
got = nil
|
||||
if err := rs.Free(back, HalfAssignment, 9, "removed by hand"); err != nil {
|
||||
if err := rs.Free(back, HalfAssignment, "", 9, "removed by hand"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rs.Free(back, HalfAssignment, 9, "again"); err != nil {
|
||||
if err := rs.Free(back, HalfAssignment, "", 9, "again"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 1 || got[0].subject != meshdelivery.SubjectFreed || got[0].pins.Why != "removed by hand" ||
|
||||
!slices.Equal(got[0].pins.Assignment, []int64{9}) {
|
||||
t.Fatalf("said %+v", got)
|
||||
}
|
||||
got = nil
|
||||
if err := rs.Free(back, HalfCatalog, "feat/x", 9, "removed"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 1 || !slices.Equal(got[0].pins.Catalog, []meshdelivery.PinsCatalogItem{{Branch: "feat/x", Revision: 9}}) {
|
||||
t.Fatalf("said %+v", got)
|
||||
}
|
||||
if again, _ := rs.Read(r.Instance); len(again.Pinned) != 1 {
|
||||
t.Fatalf("pinned after the free %+v", again.Pinned)
|
||||
}
|
||||
if err := rs.Pin(r, "trunk", 1); err == nil {
|
||||
if err := rs.Pin(r, "trunk", "", 1); err == nil {
|
||||
t.Fatal("a pin of no half is taken")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
// The halves of the revision view a record pins (design 47 *Two graphs, one source of truth*).
|
||||
@@ -39,13 +40,24 @@ func (rs *Records) say(subject string, body any, id string) error {
|
||||
return rs.Say(subject, body, id)
|
||||
}
|
||||
|
||||
// pinKey is a pinned revision's key in the record: its half and its number, so the two halves' numbers never meet.
|
||||
func pinKey(half string, revision int64) string { return half + "-" + strconv.FormatInt(revision, 10) }
|
||||
// pinKey is a pinned revision's key in the record: its half, for a catalog revision its watched branch (revisions are numbered
|
||||
// per branch, A-171) escaped as a name with its slashes escaped too, and its number, so no two revisions' keys meet.
|
||||
func pinKey(half, branch string, revision int64) string {
|
||||
key := half + "-"
|
||||
if half == HalfCatalog {
|
||||
key += strings.ReplaceAll(keys.EscapeName(branch), "/", "=2f") + "-"
|
||||
}
|
||||
return key + strconv.FormatInt(revision, 10)
|
||||
}
|
||||
|
||||
// number is the revision's number on its half's seat.
|
||||
// number is the revision's number on its half's seat: what follows the key's last dash.
|
||||
func (p PinnedRevision) number() (int64, bool) {
|
||||
n, err := strconv.ParseInt(strings.TrimPrefix(p.Revision, p.Half+"-"), 10, 64)
|
||||
return n, err == nil && strings.HasPrefix(p.Revision, p.Half+"-")
|
||||
i := strings.LastIndex(p.Revision, "-")
|
||||
if i < 0 || !strings.HasPrefix(p.Revision, p.Half+"-") {
|
||||
return 0, false
|
||||
}
|
||||
n, err := strconv.ParseInt(p.Revision[i+1:], 10, 64)
|
||||
return n, err == nil
|
||||
}
|
||||
|
||||
// pinsOf is the seat's Pins of a record for these revisions.
|
||||
@@ -58,7 +70,7 @@ func pinsOf(r *Record, pins []PinnedRevision, why string) meshdelivery.Pins {
|
||||
}
|
||||
switch p.Half {
|
||||
case HalfCatalog:
|
||||
out.Catalog = append(out.Catalog, n)
|
||||
out.Catalog = append(out.Catalog, meshdelivery.PinsCatalogItem{Branch: p.Branch, Revision: n})
|
||||
case HalfAssignment:
|
||||
out.Assignment = append(out.Assignment, n)
|
||||
}
|
||||
@@ -74,12 +86,18 @@ func checkHalf(half string) error {
|
||||
}
|
||||
|
||||
// Pin writes that the record points at a graph revision, unless it does already, and says `pinned` (again, when written
|
||||
// before: the id keeps it once).
|
||||
func (rs *Records) Pin(r *Record, half string, revision int64) error {
|
||||
// before: the id keeps it once). A catalog revision names its watched branch, which the record keeps and the event says.
|
||||
func (rs *Records) Pin(r *Record, half, branch string, revision int64) error {
|
||||
if err := checkHalf(half); err != nil {
|
||||
return err
|
||||
}
|
||||
p := PinnedRevision{Revision: pinKey(half, revision), Half: half}
|
||||
if half == HalfCatalog && branch == "" {
|
||||
return fmt.Errorf("a catalog revision is pinned by its watched branch and its number")
|
||||
}
|
||||
if half != HalfCatalog {
|
||||
branch = ""
|
||||
}
|
||||
p := PinnedRevision{Revision: pinKey(half, branch, revision), Half: half, Branch: branch}
|
||||
if _, ok := r.Pinned[p.Revision]; !ok {
|
||||
seq, err := rs.Ledger.Apply(r.Instance, OpPin, p)
|
||||
if err != nil {
|
||||
@@ -95,11 +113,11 @@ func (rs *Records) Pin(r *Record, half string, revision int64) error {
|
||||
|
||||
// Free writes that the record no longer points at a graph revision and says `freed` with why; a revision it does not
|
||||
// point at frees nothing and says nothing.
|
||||
func (rs *Records) Free(r *Record, half string, revision int64, why string) error {
|
||||
func (rs *Records) Free(r *Record, half, branch string, revision int64, why string) error {
|
||||
if err := checkHalf(half); err != nil {
|
||||
return err
|
||||
}
|
||||
p, ok := r.Pinned[pinKey(half, revision)]
|
||||
p, ok := r.Pinned[pinKey(half, branch, revision)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -74,7 +74,7 @@ func TestTheGateStatesItsPinsThenSaysPinsStated(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rs.Pin(r, HalfCatalog, 3); err != nil {
|
||||
if err := rs.Pin(r, HalfCatalog, "main", 3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
events = nil
|
||||
|
||||
@@ -98,6 +98,9 @@
|
||||
"half": {
|
||||
"type": "string"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"ended": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -208,7 +211,8 @@
|
||||
"effect": "set-entry",
|
||||
"fields": [
|
||||
"revision",
|
||||
"half"
|
||||
"half",
|
||||
"branch"
|
||||
],
|
||||
"into": "pinned",
|
||||
"key": "revision"
|
||||
|
||||
Reference in New Issue
Block a user