From ca46b8938f90640ceafe6557e52c84a381987f52 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 9 Nov 2026 22:48:34 +0100 Subject: [PATCH] S1: seat settle gains a delayed nak and its capability, for a step given again (issue 1748) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- go/seat.go | 18 ++++++++++++++++++ go/seat_pull_test.go | 14 ++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/go/seat.go b/go/seat.go index 8a9b71f..9bf4866 100644 --- a/go/seat.go +++ b/go/seat.go @@ -86,8 +86,15 @@ const ( SettleNak = "nak" SettleTerm = "term" SettleProgress = "progress" + // SettleNakAfter hands the message back to be delivered again after a delay (SeatSettleAfter): the bus's own + // delayed redelivery, so no part keeps a timer for it. + SettleNakAfter = "nak-after" ) +// CapSeatSettleDelay is the capability a tool runner states when it applies SettleNakAfter; a module that settles with +// it asks it in its manifest's capabilities, so its want waits on a node whose runner does not state it. +const CapSeatSettleDelay = "seat-settle-delay" + // SeatPull pulls at most one message from a held seat's worker the module's membership lists, waiting at most wait // (none when 0, at most 25 s): nil when none came. Unlike SeatTake nothing is fetched ahead: a build module pulls an // ask only when it has a free slot (novox/hq ADR 0525 ยง4; issue 1887 A11). A message never settled is delivered again @@ -124,6 +131,17 @@ func SeatSettle(id, fate string) error { return err } +// SeatSettleAfter hands the message pulled under id back to be delivered again after delay (SettleNakAfter). The +// runtime refuses a delay of 0 or below; a runtime that does not state CapSeatSettleDelay refuses the fate. +func SeatSettleAfter(id string, delay time.Duration) error { + c, err := live() + if err != nil { + return err + } + _, err = c.ask("mesh/seat.settle", map[string]any{"id": id, "fate": SettleNakAfter, "delay_ms": delay.Milliseconds()}, seatTimeout) + return err +} + // SeatRecord reads one key of a record a seat keeps for this module, under its own name, or nil. func SeatRecord(bucket, key string) (*StateEntry, error) { c, err := live() diff --git a/go/seat_pull_test.go b/go/seat_pull_test.go index eb1d914..e4ac16a 100644 --- a/go/seat_pull_test.go +++ b/go/seat_pull_test.go @@ -23,7 +23,10 @@ func TestABundlePullsAndSettlesThroughTheRuntime(t *testing.T) { return nil, err } got <- p - return map[string]any{}, SeatSettle(p.ID, SettleAck) + if err := SeatSettle(p.ID, SettleAck); err != nil { + return nil, err + } + return map[string]any{}, SeatSettleAfter("p2", time.Minute) }}}, inR, outW) }() lines := bufio.NewScanner(outR) @@ -59,8 +62,15 @@ func TestABundlePullsAndSettlesThroughTheRuntime(t *testing.T) { } asked = next() params, _ = asked["params"].(map[string]any) - if asked["method"] != "mesh/seat.settle" || params["id"] != "p1" || params["fate"] != "ack" { + if asked["method"] != "mesh/seat.settle" || params["id"] != "p1" || params["fate"] != "ack" || params["delay_ms"] != nil { t.Fatalf("settled %v", asked) } send(map[string]any{"jsonrpc": "2.0", "id": asked["id"], "result": map[string]any{}}) + // S1 (issue 1748): a delayed nak names its fate and its delay in milliseconds. + asked = next() + params, _ = asked["params"].(map[string]any) + if asked["method"] != "mesh/seat.settle" || params["id"] != "p2" || params["fate"] != "nak-after" || params["delay_ms"] != 60000.0 { + t.Fatalf("settled after a delay %v", asked) + } + send(map[string]any{"jsonrpc": "2.0", "id": asked["id"], "result": map[string]any{}}) }