J3 (A2g ruling): a reader's again tool records its hand act through mesh-hand-acts before it asks

StatusTools takes the reader's handacts.Recorder: again records the act through seat:mesh-hand-acts.hand-act before
it writes the hand ask, and refuses, asking nothing, when the seat does not take it, as the stdio Reader's again does.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 14:23:40 +01:00
parent 315d7e9b8c
commit 5bb0662d19
2 changed files with 36 additions and 2 deletions
+9 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/nats-io/nats.go/jetstream"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/handacts"
)
// StatusState is a reader's own state of its first views (design 25 § 12.2): at StatusKey each first view's Status, as
@@ -92,8 +93,10 @@ func HearAgain(ctx context.Context, kv jetstream.KeyValue, followers []*Follower
// StatusTools are a reader's read tool of its first views (read) and its re-read tool (again), in its tools bundle,
// over its StatusState: read lists what the process last said of each; again writes a hand ask the process takes,
// naming the caller the runtime proved, and is recorded in the status's by-hand count and why.
func StatusTools(reader, read, again string, st stdio.KeptState, now func() time.Time) []stdio.Tool {
// naming the caller the runtime proved, and is recorded in the status's by-hand count and why. again is a hand act:
// acts records it through the seat mesh-hand-acts before the ask is written, and one the seat does not take is said and
// nothing is asked (the zero Recorder records nothing, so again refuses).
func StatusTools(reader, read, again string, st stdio.KeptState, acts handacts.Recorder, now func() time.Time) []stdio.Tool {
return []stdio.Tool{
{Name: read,
Description: fmt.Sprintf("%s's first views of the indexer's and the assigner's states (novox/hq ADR 0615): per owner and state, "+
@@ -136,6 +139,10 @@ func StatusTools(reader, read, again string, st stdio.KeptState, now func() time
return nil, errors.New("again names the owner, the state and why")
}
a := HandAsk{V: Version, Owner: owner, State: state, Why: strings.TrimSpace(why), By: stdio.CallerOf(args), At: now().UTC()}
if _, err := acts.Record(handacts.Request{What: fmt.Sprintf("asked %s's first view of %s's %s again by hand", reader, owner, state),
Why: a.Why, Cause: "first-view-again", Verb: again}); err != nil {
return nil, fmt.Errorf("the hand act was not recorded through mesh-hand-acts, so nothing was asked again: %w", err)
}
if _, err := st.Put(AgainKey(owner, state), a); err != nil {
return nil, fmt.Errorf("the hand ask is not written: %w", err)
}
+27
View File
@@ -314,6 +314,33 @@ func TestViews(t *testing.T) {
}
_ = w.Stop()
})
t.Run("a reader's again tool records its hand act through mesh-hand-acts before it writes the ask, and asks nothing when the seat does not take it", func(t *testing.T) {
var recorded []handacts.Request
take := errors.New("no responders")
acts := handacts.Recorder{Ask: func(key string, body any) (json.RawMessage, error) {
if key != "seat:mesh-hand-acts.hand-act" {
return nil, fmt.Errorf("asked %s", key)
}
raw, _ := json.Marshal(body)
var q handacts.Request
_ = json.Unmarshal(raw, &q)
recorded = append(recorded, q)
if take != nil {
return nil, take
}
return json.RawMessage(`{"log":"mesh-hand-acts"}`), nil
}}
again := StatusTools("reader", "reader_first_views", "reader_first_view_again", stdio.State(StatusState), acts, now)[1]
args := map[string]any{"owner": "o", "state": "modules", "why": "doubted"}
if _, err := again.Run(args); err == nil || !strings.Contains(err.Error(), "nothing was asked again") || len(recorded) != 1 {
t.Errorf("a hand act the seat did not take: %v, recorded %v", err, recorded)
}
take = nil // taken: the ask is written next (here refused, for no runtime serves this test)
if _, err := again.Run(args); err == nil || !strings.Contains(err.Error(), "hand ask is not written") || len(recorded) != 2 ||
recorded[1].Why != "doubted" || recorded[1].Verb != "reader_first_view_again" || !strings.Contains(recorded[1].What, "o's modules") {
t.Errorf("a hand act taken: %v, recorded %+v", err, recorded)
}
})
t.Run("an ask through a tool reads the answer bare or in the tool runner's envelope, and its error", func(t *testing.T) {
bare := `{"v":1,"owner":"o","state":"s","revision":3,"values":{"k":"1"}}`
for name, c := range map[string]struct {