T-6: the tool runner reads a sent object by its reference, under the bus state capability

mesh/objects.sent-get reads a sender's sent store only; refused: a state's objects, a reference that is none, an object over a bundle's channel.

Builds: design 47 G §347, §59, §462

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 12:06:40 +01:00
parent a5b8ae1a72
commit 78e5de538b
4 changed files with 108 additions and 1 deletions
+55
View File
@@ -0,0 +1,55 @@
package bus
import (
"context"
"errors"
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/stateobjects"
"github.com/nats-io/nats.go/jetstream"
)
// SentGet reads one object of a sender's store of sent objects by the reference that carried it (ADR 0666 §16): the
// store is the sender's `<module>_sent-objects`, and no other — a state's objects are reached through their owner's
// bindings alone. It reads as this connection's account, which the bus lets read a sender's sent store only where the
// grants part granted it, from the manifests' callers and consumers; a refusal says the store.
func (c *Conn) SentGet(r buscalls.ObjectsSentGetRequest) (*buscalls.ObjectData, error) {
ref := stateobjects.Ref{Store: r.Store, Object: r.Object, Size: r.Size, Digest: r.Digest, Form: stateobjects.FormVersion}
if _, ok := stateobjects.ReadRef(stateobjects.Wrap(ref)); !ok {
return nil, errors.New("a sent object is named by the reference's store, object, size and sha256, as the reply or event carried them")
}
if !strings.HasSuffix(r.Store, catalogue.BusNameSeparator+catalogue.SentObjectsLocal) {
return nil, fmt.Errorf("%s is no sent store (a module's is `<module>%s%s`): only a sent object is read by its reference",
r.Store, catalogue.BusNameSeparator, catalogue.SentObjectsLocal)
}
if r.Size > stateobjects.BundleObjectMost {
return nil, fmt.Errorf("the sent object %s is %d bytes and a bundle's runtime channel carries at most %d: it is not read "+
"through a bundle", r.Object, r.Size, stateobjects.BundleObjectMost)
}
js, err := c.jetStream()
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), StateTimeout)
defer cancel()
obs, err := js.ObjectStore(ctx, r.Store)
if err != nil {
return nil, fmt.Errorf("the sent store %s cannot be read as %s: it is not on the bus, or the bus grants this account no "+
"read of it (the manifests' callers and consumers are granted): %w", r.Store, c.cred.Module, err)
}
info, err := obs.GetInfo(ctx, r.Object)
if errors.Is(err, jetstream.ErrObjectNotFound) {
return nil, fmt.Errorf("the sent object %s is not in %s: it is kept 7 days, then expires", r.Object, r.Store)
}
if err != nil {
return nil, fmt.Errorf("the sent object %s of %s: %w", r.Object, r.Store, err)
}
data, err := obs.GetBytes(ctx, r.Object)
if err != nil {
return nil, fmt.Errorf("the sent object %s of %s: %w", r.Object, r.Store, err)
}
return &buscalls.ObjectData{Info: objectInfoOf(info), Data: data}, nil
}
+30
View File
@@ -0,0 +1,30 @@
package bus
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/stateobjects"
)
// Design 47 G §347, §59, §462: a sent object is read by the reference that carried it, from a sender's sent store only; a
// state's objects, a reference that is none and an object too large for a bundle's channel are refused before the bus is asked.
func TestSentGetRefusesWhatIsNoSentObjectsReference(t *testing.T) {
digest := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
big := int64(stateobjects.BundleObjectMost) + 1
for _, c := range []struct {
name string
req buscalls.ObjectsSentGetRequest
want string
}{
{"no digest", buscalls.ObjectsSentGetRequest{Store: "m_sent-objects", Object: "o", Size: 3}, "named by the reference"},
{"a state's objects", buscalls.ObjectsSentGetRequest{Store: "m_asks-objects", Object: "o", Size: 3, Digest: digest}, "no sent store"},
{"over the channel", buscalls.ObjectsSentGetRequest{Store: "m_sent-objects", Object: "o", Size: big, Digest: digest}, "not read through a bundle"},
} {
_, err := (&Conn{}).SentGet(c.req)
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: got %v, want an error containing %q", c.name, err, c.want)
}
}
}