The node-engine serves its seat's verbs from the bootstrapped definition until it holds the seat list (issue 1966, recovery) #213

Merged
mesh-tools merged 1 commits from fix/1966-engine-seat-verbs-bootstrap into main 2026-11-08 17:34:52 +00:00
2 changed files with 43 additions and 1 deletions
+18 -1
View File
@@ -13,6 +13,7 @@ import (
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/micro"
)
@@ -99,6 +100,22 @@ func AnswerEngineCall(handle EngineFunc, verb, node, subject string, body []byte
return say()
}
// ServedEngineSeat is the engine's seat as it serves it: the seat list's definition once the engine holds it, and the
// bootstrapped definition before (novox/hq ADR 0636 §2: every reader holds the bootstrapped seats from its start). The
// link serves the seat when it opens, before the node agent starts the reader of the list; read from the list alone, the
// engine served no verb, so nobody answered manifest-reads and the assigner kept an engine's old statement (issue 1966).
func ServedEngineSeat() catalogue.Seat {
if s, ok := catalogue.SeatNamed(EngineSeat); ok && len(s.Serves) > 0 {
return s
}
for _, s := range catalogue.SeatsFromDefinitions(seatlist.Bootstrapped()) {
if s.Name == EngineSeat {
return s
}
}
return catalogue.Seat{Name: EngineSeat}
}
// EngineSeats is a link that serves this node's seat. Optional, as Asked is.
type EngineSeats interface {
ServeEngineSeat(EngineFunc)
@@ -118,7 +135,7 @@ func (l *natsLink) ServeEngineSeat(handle EngineFunc) {
return
}
apart := make(chan struct{}, apartAtOnce)
for _, v := range catalogue.EngineVerbs(EngineSeat) {
for _, v := range ServedEngineSeat().Serves {
verb := v.Name
answer := EngineAnswer(handle, verb, l.node, apart)
meta := EngineEndpointMetadata(v, l.node)
+25
View File
@@ -1,6 +1,9 @@
package link
import (
"slices"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatlist/seatlisttest"
)
@@ -8,3 +11,25 @@ import (
// The seats the tests judge against are given to catalogue as the node-engine gives the ones it read from the bus
// (novox/hq ADR 0635 §1); catalogue compiles none.
func init() { catalogue.UseSeats(catalogue.SeatsFromDefinitions(seatlisttest.Definitions())) }
// Before the engine holds a seat list it serves its seat's verbs from the bootstrapped definition (issue 1966: served from
// the empty list, it answered no verb and the assigner kept an old statement).
func TestServedEngineSeatBeforeTheList(t *testing.T) {
held := catalogue.Seats()
defer catalogue.UseSeats(held)
for _, tc := range []struct {
name string
seats []catalogue.Seat
}{{"no list held", nil}, {"list held", held}} {
catalogue.UseSeats(tc.seats)
var names []string
for _, v := range ServedEngineSeat().Serves {
names = append(names, v.Name)
}
for _, want := range []string{ActManifestReads, ActComposition, ActHandOver} {
if !slices.Contains(names, want) {
t.Errorf("%s: the engine serves %v, without %s", tc.name, names, want)
}
}
}
}