GATE19: the gate states its pins at each start, then says pins-stated

Builds: design 47 G §579, §652
This commit is contained in:
2026-11-12 04:55:56 +01:00
parent 476fc0cc30
commit ea87dd80bb
3 changed files with 63 additions and 0 deletions
@@ -98,6 +98,7 @@ func main() {
func run(h *Holder, l *listening) {
<-stdio.Served() // Serve first: the state is reached through it, and an event is heard from as soon as it serves
switchToRecords(h, l)
statePins(h, l)
go h.Work()
for _, pattern := range followed {
pattern := pattern
@@ -130,6 +131,20 @@ func switchToRecords(h *Holder, l *listening) {
}
}
// statePins says every pin the records hold once at each start (design 47 G §579), retrying until it can.
func statePins(h *Holder, l *listening) {
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
n, err := h.Records.StatePins()
if err == nil {
logf("[mesh-delivery] pins stated: %d", n)
return
}
l.set("not yet: " + err.Error())
logf("[mesh-delivery] the pins cannot be stated yet (%v); asking again in %s", err, wait)
time.Sleep(wait)
}
}
// handle takes one event, then asks for a look at every step the event makes possible (Work, React). An event that cannot be read is
// said and acknowledged: read again it would fail again.
func handle(h *Holder, e stdio.Envelope) error {
@@ -255,3 +255,25 @@ func (rs *Records) sayEnded(r *Record) error {
return rs.say(meshdelivery.SubjectRecordEnded, recordEnded{Record: r.Instance, Milestone: r.Milestone,
ForgeMade: r.ForgeMade, Away: r.Ended.Away, At: r.Ended.At}, "record-ended."+r.Instance)
}
// StatePins says `pinned` again for every revision each record it keeps pins, then once `pins-stated` (design 47 G §579): the
// indexer's consumer of the seat's events hears pins said before it existed or older than the stream keeps, and the set form
// absorbs the repeats. Each repeat has an id of its own, since the bus keeps one message under one id inside its duplicate window.
func (rs *Records) StatePins() (int, error) {
kept, err := rs.Kept()
if err != nil {
return 0, err
}
at := rs.Now().UTC()
stated := 0
for _, r := range kept {
for _, p := range sortedPins(r) {
id := "pinned." + r.Instance + "." + p.Revision + ".stated." + strconv.FormatInt(at.UnixNano(), 10)
if err := rs.say(meshdelivery.SubjectPinned, pinsOf(r, []PinnedRevision{p}, ""), id); err != nil {
return stated, err
}
stated++
}
}
return stated, rs.say(meshdelivery.SubjectPinsStated, meshdelivery.PinsStatedEvent{At: at}, "pins-stated."+strconv.FormatInt(at.UnixNano(), 10))
}
@@ -58,3 +58,29 @@ func TestTheSourceToolsReadEveryBranchAndSayItAgainAfterTheLog(t *testing.T) {
t.Fatalf("log %+v events %+v", acts.took, events)
}
}
// Design 47 G §579: at its start the gate says `pinned` again for every revision each record pins, each under an id of its own,
// then `pins-stated` once.
func TestTheGateStatesItsPinsThenSaysPinsStated(t *testing.T) {
at := time.Date(2026, 11, 12, 4, 0, 0, 0, time.UTC)
type said struct{ subject, id string }
var events []said
rs := &Records{Ledger: newLedgerFake(t), Now: func() time.Time { return at },
Say: func(subject string, body any, id string) error {
events = append(events, said{subject, id})
return nil
}}
r, _, err := rs.Open("47/G", map[string]string{"novox/app@1": "c1"}, false)
if err != nil {
t.Fatal(err)
}
if err := rs.Pin(r, HalfCatalog, 3); err != nil {
t.Fatal(err)
}
events = nil
n, err := rs.StatePins()
if err != nil || n != 1 || len(events) != 2 || !strings.HasSuffix(events[0].subject, ".event.pinned") ||
!strings.Contains(events[0].id, ".stated.") || !strings.HasSuffix(events[1].subject, ".event.pins-stated") {
t.Fatalf("%d %v %+v", n, err, events)
}
}