T-8: the console reads an answer sent as a reference for the person asking (5bcf68d, taken)

Builds: design 47 G §69 (the console reads whole), §62, §407

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 14:41:05 +01:00
parent 78e5de538b
commit 60c432838b
3 changed files with 92 additions and 1 deletions
+5 -1
View File
@@ -1170,7 +1170,11 @@ func (s *Surface) discover(ctx context.Context, name string, args map[string]any
}
return failure(why)
}
content := []map[string]any{{"type": "text", "text": pretty(got.Result)}}
result, unread := resolveSent(s.conn, got.Result)
content := []map[string]any{{"type": "text", "text": pretty(result)}}
if unread != "" {
content = append(content, map[string]any{"type": "text", "text": unread})
}
if got.Node != "" {
content = append(content, map[string]any{"type": "text", "text": "answered by " + got.Node})
}
+31
View File
@@ -0,0 +1,31 @@
package console
import (
"encoding/json"
"fmt"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/stateobjects"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
// resolveSent is the answer a person reads (ADR 0666 §16): an answer a module sent as a reference to an object of its
// sent store is read from it when the bus grants this runner's account the read (the standing grant the manifests give
// the callers of the verb), else shown as its reference with its size and why it was not read.
func resolveSent(conn *bus.Conn, result json.RawMessage) (json.RawMessage, string) {
ref, ok := stateobjects.ReadRef(result)
if !ok {
return result, ""
}
whole, err := stateobjects.Receive(result, func(r stateobjects.Ref) ([]byte, error) {
got, err := conn.SentGet(buscalls.ObjectsSentGetRequest{Store: r.Store, Object: r.Object, Size: r.Size, Digest: r.Digest})
if err != nil {
return nil, err
}
return got.Data, nil
})
if err != nil {
return result, fmt.Sprintf("the answer is %d bytes, held as the object %s of %s, and was not read: %v", ref.Size, ref.Object, ref.Store, err)
}
return whole, ""
}
+56
View File
@@ -0,0 +1,56 @@
package console
import (
"context"
"crypto/sha256"
"encoding/hex"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/stateobjects"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
// ADR 0666 §16: an answer a module sent as a reference is read from its sent store for the person asking; one the bus does
// not let the runner read is shown as its reference with its size and why it was not read.
func TestAnAnswerSentAsAReferenceIsReadForThePersonAsking(t *testing.T) {
nc, err := nats.Connect(meshtest.URL(t))
if err != nil {
t.Fatal(err)
}
defer nc.Close()
js, _ := jetstream.New(nc)
obs, err := js.CreateObjectStore(context.Background(), jetstream.ObjectStoreConfig{Bucket: "m_sent-objects"})
if err != nil {
t.Fatal(err)
}
conn, err := bus.Connect(bus.Credential{URL: meshtest.URL(t), Module: "console", Node: "anchor"})
if err != nil {
t.Fatal(err)
}
defer conn.Close()
body := []byte(`{"lines":"` + strings.Repeat("x", 3<<20) + `"}`)
sum := sha256.Sum256(body)
ref := stateobjects.Ref{Store: "m_sent-objects", Object: "tool.lines." + hex.EncodeToString(sum[:]), Size: int64(len(body)),
Digest: hex.EncodeToString(sum[:]), Form: stateobjects.FormVersion}
if _, err := obs.PutBytes(context.Background(), ref.Object, body); err != nil {
t.Fatal(err)
}
if got, unread := resolveSent(conn, stateobjects.Wrap(ref)); unread != "" || string(got) != string(body) {
t.Fatalf("read %d bytes of %d, %q", len(got), len(body), unread)
}
missing := ref
missing.Store = "gone_sent-objects"
wrapped := stateobjects.Wrap(missing)
got, unread := resolveSent(conn, wrapped)
if string(got) != string(wrapped) || !strings.Contains(unread, "3145") || !strings.Contains(unread, "gone_sent-objects") {
t.Fatalf("an unread answer showed %q and %q", got, unread)
}
if got, unread := resolveSent(conn, []byte(`{"a":1}`)); unread != "" || string(got) != `{"a":1}` {
t.Fatalf("a plain answer showed %q, %q", got, unread)
}
}