Node-engine: a reader of the seat list that could not start holds the composer, where none gated nothing (ADR 0636 §1; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -593,10 +593,12 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
// composer hands nothing new meanwhile, and the node says it has waited five minutes (novox/hq ADR 0636 §1).
|
||||
seats, err := startSeatList(run, session, node, say)
|
||||
if err != nil {
|
||||
say(err.Error())
|
||||
// No reader is no gate that lets everything through: the node acts on nothing new, as one that waits does.
|
||||
say(err.Error() + "; the node composes nothing new until it is started again and reads the list (novox/hq ADR 0636 §1)")
|
||||
}
|
||||
hooks := composerHooks{
|
||||
seatList: seats,
|
||||
seatList: seats,
|
||||
seatListFailed: err != nil,
|
||||
running: func() []compositions.Composed {
|
||||
if now := own.get(); now != nil {
|
||||
return runsAsWritten(now.Everything(), opts.state)
|
||||
@@ -639,6 +641,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
// its own key, `<node>.node`, under candidates.
|
||||
shadowHooks := hooks
|
||||
shadowHooks.refused, shadowHooks.kept, shadowHooks.certify, shadowHooks.seatList = nil, nil, nil, nil
|
||||
shadowHooks.seatListFailed = false
|
||||
shadowEnv := composerEnv(session, node, say, shadowHooks)
|
||||
shadow := selfcompose.NewShadow(shadowEnv,
|
||||
func(ctx context.Context, answers map[string]nodeengine.CandidateAnswer) {
|
||||
@@ -994,6 +997,8 @@ type composerHooks struct {
|
||||
certify func(ctx context.Context) error
|
||||
// seatList is this run's reader of the seat list: the composer hands nothing new until it was read (ADR 0636 §1).
|
||||
seatList *seatListState
|
||||
// seatListFailed says the reader could not start: the composer then holds like a reader that waits (selfcompose.Unread).
|
||||
seatListFailed bool
|
||||
}
|
||||
|
||||
// selfComposer is the node's own composer: it composes what the seat's placement places on the node, and nothing while
|
||||
@@ -1047,6 +1052,8 @@ func composerEnv(session *link.Session, node string, say func(string), hooks com
|
||||
VaultAuthority: hooks.vaultAuthority, Certify: hooks.certify}
|
||||
if hooks.seatList != nil {
|
||||
env.SeatList, env.SeatListChanged = hooks.seatList.reader, hooks.seatList.changed
|
||||
} else if hooks.seatListFailed {
|
||||
env.SeatList = selfcompose.Unread{}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-host/internal/selfcompose"
|
||||
)
|
||||
|
||||
// ADR 0636 §1: when the node's reader of the seat list could not start, the composer is gated by a reader that never
|
||||
// allows, where a missing reader would let the node act on everything; the shadow, which applies nothing, stays ungated.
|
||||
func TestAReaderThatCouldNotStartHoldsTheComposer(t *testing.T) {
|
||||
failed := composerEnv(nil, "n1", func(string) {}, composerHooks{seatListFailed: true})
|
||||
if failed.SeatList == nil || failed.SeatList.MayAct() || failed.SeatList.MayAct("node-engine") {
|
||||
t.Fatalf("the composer of a node whose reader did not start is gated by %v, want a gate that allows nothing", failed.SeatList)
|
||||
}
|
||||
if _, ok := failed.SeatList.(selfcompose.Unread); !ok {
|
||||
t.Errorf("gate is %T", failed.SeatList)
|
||||
}
|
||||
if got := composerEnv(nil, "n1", func(string) {}, composerHooks{}).SeatList; got != nil {
|
||||
t.Errorf("a composer with no reader asked for gates by %v, want none (the shadow)", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user