From 32528aa58fb6a850773bea0c538b7a70be55fb3d Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 14:37:33 +0100 Subject: [PATCH] An act owed to the forge with no outcome 15 minutes after it was said is listed by stalled (milestone 47/G, W11.17b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ (Joined from 0fa400d5.) Builds: design 47 G §166, §169 (GATE6) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../cmd/mesh-delivery-gate/owed_test.go | 22 +++++++++ .../cmd/mesh-delivery-gate/owedended.go | 45 +++++++++++++++++++ .../cmd/mesh-delivery-gate/verbs.go | 1 + 3 files changed, 68 insertions(+) diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owed_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owed_test.go index 6c1546f18..58b0b8858 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owed_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owed_test.go @@ -3,6 +3,7 @@ package main import ( "strings" "testing" + "time" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" ) @@ -45,6 +46,16 @@ func TestAnActOwedStaysUntilItsOutcomeIsRecorded(t *testing.T) { } } + // Unanswered, it is stalled only past 15 minutes, naming the oldest act. + if lines := stalledOf(w, id); len(lines) != 0 { + t.Fatalf("stalled before its bound: %+v", lines) + } + w.later(16 * time.Minute) + if lines := stalledOf(w, id); len(lines) != 1 || !strings.Contains(lines[0].Says, "has no outcome from the forge bridge") || + lines[0].Bound != "15m0s" { + t.Fatalf("an act unanswered past 15 minutes is not stalled: %+v", lines) + } + // The owner starts again over the same state: every unanswered act is said again, under the same id. w2 := newWorldOver(t, w.store, w.forge, w.now) w2.owed.silent = true @@ -96,6 +107,17 @@ func TestAnActOwedStaysUntilItsOutcomeIsRecorded(t *testing.T) { } } +// stalledOf are the stalled lines naming a delivery that say an act has no outcome. +func stalledOf(w *world, id string) []StalledLine { + var out []StalledLine + for _, l := range w.h.Stalled() { + if l.ID == id && strings.Contains(l.Says, "owed to the forge") { + out = append(out, l) + } + } + return out +} + // owedHolds is whether the delivery still holds the act unanswered. func owedHolds(w *world, id, act string) bool { for _, e := range w.delivery(id).Owed { diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owedended.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owedended.go index 2f76733ad..46039cc45 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owedended.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/owedended.go @@ -154,3 +154,48 @@ func (rs *Records) Act(r *Record, a RecordAct) error { r.Acts[a.ID], r.Sequence = a, seq return nil } + +// owedBound is how long an act said may wait for its owed.ended before it is stalled: many times a forge call's time, and +// short beside the check's hour (design 47 *What the gate owes the forge*). +const owedBound = 15 * time.Minute + +// owedLines are the stalled lines of the acts said and unanswered past owedBound: one per delivery or group, naming its +// oldest such act and how many more wait. Under the lock. +func (h *Holder) owedLines(now time.Time) []StalledLine { + var out []StalledLine + line := func(id string, state State, number int, owed []Effect) { + var oldest *Effect + more := 0 + for i, e := range owed { + if e.Said == nil || now.Sub(*e.Said) < owedBound { + continue + } + if oldest == nil || e.Said.Before(*oldest.Said) { + if oldest != nil { + more++ + } + oldest = &owed[i] + } else { + more++ + } + } + if oldest == nil { + return + } + says := fmt.Sprintf("the %s owed to the forge, said at %s as %s, has no outcome from the forge bridge", oldest.Kind, + oldest.Said.UTC().Format(time.RFC3339), oldest.ID) + if more > 0 { + says += fmt.Sprintf(", nor have %d more", more) + } + out = append(out, StalledLine{ID: id, Number: number, State: state, For: now.Sub(*oldest.Said).Round(time.Second).String(), + Bound: owedBound.String(), H2: "none: the forge bridge's bridge_owed reads what it performed — the operator's", + Says: says + "; it is said again at the gate's start"}) + } + for id, d := range h.deliveries { + line(id, d.State, d.Number, d.Owed) + } + for id, g := range h.groups { + line(id, "", 0, g.Owed) + } + return out +} diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/verbs.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/verbs.go index 7abc79052..019a320c8 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/verbs.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/verbs.go @@ -230,6 +230,7 @@ func (h *Holder) Stalled() []StalledLine { out = append(out, h.readyLines(now)...) out = append(out, h.turnLines(now)...) // an approved merge held past its bound by another delivery's check (ADR 0548 §4) out = append(out, h.groupLines(now)...) + out = append(out, h.owedLines(now)...) // an act owed the forge with no outcome past its bound (W11.17) sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) return out }