From 5bb0662d19e51c277b209ef99e8714914bd9e4a4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 14:23:40 +0100 Subject: [PATCH] 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 --- go/views/status.go | 11 +++++++++-- go/views/views_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/go/views/status.go b/go/views/status.go index 19907d3..d60b89b 100644 --- a/go/views/status.go +++ b/go/views/status.go @@ -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) } diff --git a/go/views/views_test.go b/go/views/views_test.go index d4187a3..7c4b703 100644 --- a/go/views/views_test.go +++ b/go/views/views_test.go @@ -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 {