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:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -143,3 +144,16 @@ func TestTheRunnerStatesTheCapabilitiesOfItsCallsInItsPing(t *testing.T) {
|
||||
t.Errorf("the runner states %q (%v), want its bus-state and ledger calls", ping.Metadata[announce.Capabilities], err)
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 G §462: the call that reads a sent object is listed under the bus state capability, and the ledger calls keep theirs.
|
||||
func TestTheSentObjectCallBelongsToTheBusStateCapability(t *testing.T) {
|
||||
for c, method := range map[string]string{CapBusStateCalls: buscalls.ObjectsSentGet} {
|
||||
found := false
|
||||
for _, m := range callFamilies[c] {
|
||||
found = found || m == method
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("%s does not list %s", c, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
// callFamilies are the methods each capability stands for, the ones BusState and BusStateWatch answer.
|
||||
var callFamilies = map[string][]string{
|
||||
CapBusStateCalls: {buscalls.ObjectsPut, buscalls.ObjectsGet, buscalls.ObjectsList, buscalls.ObjectsDelete, buscalls.ObjectsInfo,
|
||||
buscalls.ObjectsWatch, buscalls.StreamRead, buscalls.StreamFill, buscalls.WorkQueueSize, buscalls.WorkQueueDead},
|
||||
buscalls.ObjectsWatch, buscalls.ObjectsSentGet, buscalls.StreamRead, buscalls.StreamFill, buscalls.WorkQueueSize, buscalls.WorkQueueDead},
|
||||
CapLedgerCalls: {buscalls.LedgerApply, buscalls.LedgerGet, buscalls.LedgerHistory, buscalls.LedgerWatch, buscalls.LedgerQuery,
|
||||
buscalls.LedgerRebuild, buscalls.LedgerReindex, buscalls.LedgerSnapshot, buscalls.LedgerLag},
|
||||
}
|
||||
@@ -65,6 +65,14 @@ func (b *moduleBus) BusState(method string, params json.RawMessage) (json.RawMes
|
||||
err = conn.ObjectsDelete(b.module, r)
|
||||
answer = buscalls.Done{}
|
||||
}
|
||||
case buscalls.ObjectsSentGet:
|
||||
var r buscalls.ObjectsSentGetRequest
|
||||
if err = decode(method, params, &r); err == nil {
|
||||
var got *buscalls.ObjectData
|
||||
if got, err = conn.SentGet(r); err == nil {
|
||||
answer = got
|
||||
}
|
||||
}
|
||||
case buscalls.ObjectsList:
|
||||
var r buscalls.ObjectsRequest
|
||||
if err = decode(method, params, &r); err == nil {
|
||||
|
||||
Reference in New Issue
Block a user