The hand-act recorder records through the seat alone, and the watched reader's reads are proved
The controller's hand-act and warranted are gone (issue 1953 L5b) and the bus refuses them, so the recorder no longer falls back to them: an act the seat does not take is an error saying nothing was recorded. And ADR 0580 clause 1's reader is shown reading a build by key each time and a manifest once per digest. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -5,7 +5,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose/frombus"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose/fromruntime"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -135,6 +137,26 @@ func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) {
|
||||
t.Errorf("%s: data items %v held %v: %v", row.name, items, iheld, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ADR 0580 clause 1: the watched reader reads a state it composes from out of the copy, a build by key each time it is
|
||||
// asked, and a manifest once per digest.
|
||||
byKey := map[string]int{}
|
||||
ws := stdio.WatchedStatesOver(func(state string, handle func(stdio.StateChange) error) error {
|
||||
byKey[state+" watched"]++
|
||||
return handle(stdio.StateChange{Key: "placement." + Node, Op: stdio.OpPut, Value: []byte(`{}`), Current: true})
|
||||
}, func(state, key string) ([]byte, bool, error) { byKey[state]++; return []byte(key), true, nil })
|
||||
w := fromruntime.Watched{WatchedStates: ws}
|
||||
for range 2 {
|
||||
_, _, _ = w.Get(frombus.BuildsRead, "restic."+fp)
|
||||
_, _, _ = w.Get(frombus.ManifestsRead, "sha256-d")
|
||||
if _, found, _ := w.Get(frombus.PlacementRead, "placement."+Node); !found {
|
||||
t.Error("the watched reader does not answer the placement from its copy")
|
||||
}
|
||||
}
|
||||
if byKey[frombus.BuildsRead] != 2 || byKey[frombus.ManifestsRead] != 1 || byKey[frombus.PlacementRead] != 0 ||
|
||||
byKey[frombus.PlacementRead+" watched"] != 1 {
|
||||
t.Errorf("the watched reader read %v, want a build by key each time, a manifest once, the placement watched once", byKey)
|
||||
}
|
||||
}
|
||||
|
||||
func planned[P any](plans map[string]P, module string) bool { _, ok := plans[module]; return ok }
|
||||
|
||||
+11
-28
@@ -1,8 +1,7 @@
|
||||
// Package handacts is the hand-act log's contract (novox/hq ADR 0565 clauses 10 and 11, issue 1953): the layout of an act,
|
||||
// moved unchanged from the controller's (mesh-controller internal/link HandAct) so one reader reads both logs alike; the
|
||||
// names of the seat mesh-hand-acts and of what its holder keeps; and the recorder every module records through, which
|
||||
// asks the seat and, while no holder answers, the controller's verb until the controller hands its lease away, saying
|
||||
// which log took the act.
|
||||
// asks the seat alone: the controller's hand-act and warranted are gone (issue 1953 L5b) and the bus refuses them.
|
||||
package handacts
|
||||
|
||||
import (
|
||||
@@ -95,13 +94,13 @@ type Taken struct {
|
||||
Answer json.RawMessage `json:"answer,omitempty"`
|
||||
}
|
||||
|
||||
// Recorder records acts for a module (ADR 0565 clause 11): through the seat mesh-hand-acts, and while the seat has no
|
||||
// holder that answers, through the controller's verb until the controller hands its lease away (the release's order
|
||||
// gives no seat's holder before its callers; 00-plan.md, "Missing" 1).
|
||||
// Recorder records acts for a module (ADR 0565 clause 11) through the seat mesh-hand-acts alone. An act the seat does
|
||||
// not take is an error that says nothing was recorded, never a quiet success.
|
||||
type Recorder struct {
|
||||
// Ask calls a seat verb by its address (`seat:<seat>.<verb>`): stdio.Ask through the runtime.
|
||||
Ask func(key string, body any) (json.RawMessage, error)
|
||||
// LeaseAway says the controller's lease is handed away: then nothing falls back to it. Nil is never handed away.
|
||||
// LeaseAway is no longer read: nothing falls back to the controller. Kept so a caller that sets it builds; it goes
|
||||
// once no module sets it.
|
||||
LeaseAway func() bool
|
||||
}
|
||||
|
||||
@@ -121,38 +120,22 @@ func (r Recorder) Record(q Request) (Taken, error) {
|
||||
body[k] = v
|
||||
}
|
||||
}
|
||||
controller := map[string]any{"what": q.What, "why": q.Why, "cause": q.Cause}
|
||||
if q.Condition != "" {
|
||||
controller["condition"] = q.Condition
|
||||
}
|
||||
if q.Cause == "" {
|
||||
controller["cause"] = q.Verb
|
||||
}
|
||||
return r.either("hand-act", body, "hand-act", controller)
|
||||
return r.record("hand-act", body)
|
||||
}
|
||||
|
||||
// RecordWarrant records what the asking module did on the operator's warrant for its ask.
|
||||
func (r Recorder) RecordWarrant(asker, ask string) (Taken, error) {
|
||||
body := map[string]any{"asker": asker, "ask": ask}
|
||||
return r.either("warranted", body, "warranted", body)
|
||||
return r.record("warranted", body)
|
||||
}
|
||||
|
||||
func (r Recorder) either(verb string, body any, controllerVerb string, controllerBody any) (Taken, error) {
|
||||
func (r Recorder) record(verb string, body any) (Taken, error) {
|
||||
answer, err := r.Ask("seat:"+Seat+"."+verb, body)
|
||||
if err == nil {
|
||||
return Taken{Log: LogSeat, Answer: answer}, nil
|
||||
}
|
||||
if !NoResponder(err) {
|
||||
return Taken{}, fmt.Errorf("%s.%s refused: %w", Seat, verb, err)
|
||||
if NoResponder(err) {
|
||||
return Taken{}, fmt.Errorf("%s has no holder that answers: nothing was recorded (%w)", Seat, err)
|
||||
}
|
||||
if r.LeaseAway != nil && r.LeaseAway() {
|
||||
return Taken{}, fmt.Errorf("%s has no holder that answers, and the controller handed its lease away: nothing "+
|
||||
"was recorded (%w)", Seat, err)
|
||||
}
|
||||
answer, cerr := r.Ask("seat:mesh-controller."+controllerVerb, controllerBody)
|
||||
if cerr != nil {
|
||||
return Taken{}, fmt.Errorf("%s has no holder that answers (%v), and the controller's %s refused too: %w", Seat, err,
|
||||
controllerVerb, cerr)
|
||||
}
|
||||
return Taken{Log: LogController, Answer: answer}, nil
|
||||
return Taken{}, fmt.Errorf("%s.%s refused, nothing was recorded: %w", Seat, verb, err)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ADR 0565 clauses 10 and 11 (issue 1953 L5-S2, S4): an act reads the controller's entries unchanged, and a recorder
|
||||
// records through the seat, through the controller's verb only while the seat has no holder and the lease is the
|
||||
// controller's, saying which log took it, and refuses to record nowhere.
|
||||
// ADR 0565 clauses 10 and 11 (issue 1953 L5-S2, S4, L5b): an act reads the controller's entries unchanged, and a
|
||||
// recorder records through the seat alone, the controller's verbs being gone, and says loudly when nothing was recorded.
|
||||
func TestAnActIsRecordedInTheLogThatAnswers(t *testing.T) {
|
||||
// The shape of mesh-controller internal/link HandAct, as its bucket keeps an entry (handacts.go at 0fbb5ef3).
|
||||
raw := `{"id":"act-1","at":"2026-10-28T09:00:00Z","by":"person.jochen","verb":"hand-act record","why":"a stuck consumer",` +
|
||||
@@ -39,14 +38,13 @@ func TestAnActIsRecordedInTheLogThatAnswers(t *testing.T) {
|
||||
warrants bool
|
||||
}{
|
||||
{name: "the seat answers", log: LogSeat, asked: "seat:mesh-hand-acts.hand-act"},
|
||||
{name: "no holder yet", seat: noResponders, log: LogController, asked: "seat:mesh-hand-acts.hand-act,seat:mesh-controller.hand-act"},
|
||||
{name: "no holder, the lease handed away", seat: noResponders, away: true, refused: "handed its lease away",
|
||||
{name: "no holder: nothing recorded, the controller never asked", seat: noResponders, refused: "nothing was recorded",
|
||||
asked: "seat:mesh-hand-acts.hand-act"},
|
||||
{name: "the seat refuses", seat: errors.New("why is required"), refused: "refused", asked: "seat:mesh-hand-acts.hand-act"},
|
||||
{name: "neither answers", seat: noResponders, ctrl: noResponders, refused: "refused too",
|
||||
asked: "seat:mesh-hand-acts.hand-act,seat:mesh-controller.hand-act"},
|
||||
{name: "a warrant without a holder", seat: noResponders, warrants: true, log: LogController,
|
||||
asked: "seat:mesh-hand-acts.warranted,seat:mesh-controller.warranted"},
|
||||
{name: "the seat refuses", seat: errors.New("why is required"), refused: "refused, nothing was recorded",
|
||||
asked: "seat:mesh-hand-acts.hand-act"},
|
||||
{name: "a warrant without a holder", seat: noResponders, warrants: true, refused: "nothing was recorded",
|
||||
asked: "seat:mesh-hand-acts.warranted"},
|
||||
{name: "a warrant recorded", warrants: true, log: LogSeat, asked: "seat:mesh-hand-acts.warranted"},
|
||||
} {
|
||||
var asked []string
|
||||
r := Recorder{LeaseAway: func() bool { return c.away }, Ask: func(key string, body any) (json.RawMessage, error) {
|
||||
|
||||
Reference in New Issue
Block a user