From fc5048619076524ef5756aea2d36bd8f54a9541a Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:51:56 +0100 Subject: [PATCH] views: an ask through a tool reads the tool runner's {result, error} envelope A5's request: the bus module's keeper asks the indexer's first view through the tool runner, which answers in that envelope; its error is the ask's. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- go/views/follow.go | 13 ++++++++++++- go/views/views_test.go | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/go/views/follow.go b/go/views/follow.go index 83c7fdc..5c005f7 100644 --- a/go/views/follow.go +++ b/go/views/follow.go @@ -352,7 +352,8 @@ func (f *Follower) Gap(ctx context.Context, why string) error { return f.askUntil(ctx, "a gap: "+why, false) } -// AskVia is a Follower's Ask through a process's call of the owner's tool (process.Conn.Call), decoding its answer. +// AskVia is a Follower's Ask through a call of the owner's tool (process.Conn.Call, or stdio.Ask through the tool +// runner), decoding its answer: bare, or in the tool runner's envelope {result, error}, whose error is the ask's. func AskVia(call func(key string, body any) (json.RawMessage, error), tool string) func(context.Context, string) (Answer, error) { return func(_ context.Context, after string) (Answer, error) { body := map[string]any{} @@ -363,6 +364,16 @@ func AskVia(call func(key string, body any) (json.RawMessage, error), tool strin if err != nil { return Answer{}, err } + var wrapped struct { + Result json.RawMessage `json:"result"` + Error string `json:"error"` + } + if json.Unmarshal(raw, &wrapped) == nil && (len(wrapped.Result) > 0 || wrapped.Error != "") { + if wrapped.Error != "" { + return Answer{}, fmt.Errorf("%s answered: %s", tool, wrapped.Error) + } + raw = wrapped.Result + } return DecodeAnswer(raw) } } diff --git a/go/views/views_test.go b/go/views/views_test.go index ebeef12..3625195 100644 --- a/go/views/views_test.go +++ b/go/views/views_test.go @@ -260,6 +260,24 @@ func TestViews(t *testing.T) { t.Errorf("asked again by hand: %v, %v", again, err) } }) + 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 { + answer string + rev uint64 + err string + }{ + "bare": {bare, 3, ""}, + "in an envelope": {`{"result":` + bare + `}`, 3, ""}, + "an error": {`{"error":"not yet"}`, 0, "not yet"}, + } { + ask := AskVia(func(string, any) (json.RawMessage, error) { return json.RawMessage(c.answer), nil }, "o.first_view") + a, err := ask(context.Background(), "") + if (c.err == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.err)) || a.Revision != c.rev { + t.Errorf("%s: %+v, %v", name, a, err) + } + } + }) t.Run("an event names its state on its subject, and decodes as it was encoded", func(t *testing.T) { if s := StateOfEvent("mesh.stream.mesh-delivery-indexer.event.views.config-candidates"); s != "config-candidates" { t.Errorf("state of subject = %q", s)