An act owed to the forge with no outcome 15 minutes after it was said is listed by stalled (milestone 47/G, W11.17b)

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
This commit is contained in:
2026-11-11 14:37:33 +01:00
parent 00b95f1f68
commit 32528aa58f
3 changed files with 68 additions and 0 deletions
@@ -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 {
@@ -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
}
@@ -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
}