G sessions: the runner serves a bundle's put and get sessions and states put-sessions

A value over buscalls.ChunkBytes is put by mesh/objects.put-chunk (written into the module's store as each chunk is
taken, sealed on the last, dropped at its bound or a disorder) and got by mesh/objects.get-chunk after a chunked answer,
for a state's object and a sent object alike; the 8 MiB BundleObjectMost cap is gone with the stateobjects constant.

Builds: design 47 G §518, §345, §457, §490, §620, §622, A-21, A-25
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 22:39:04 +01:00
parent 4bfae38a73
commit 99c36d8ddf
11 changed files with 681 additions and 50 deletions
+18
View File
@@ -3,6 +3,7 @@ package bus_test
import (
"context"
"errors"
"io"
"strings"
"testing"
@@ -70,6 +71,23 @@ func TestABundleReachesItsOwnBoundObjectsAndStreamsOnly(t *testing.T) {
if err != nil || got == nil || string(got.Data) != "what was put" || got.Info.Name != "a" {
t.Fatalf("read back %+v, %v", got, err)
}
// Design 47 G §518: a value over one chunk is streamed into the store and read back out of it, its description kept.
big := strings.Repeat("0123456789", 120<<10)
info, err := conn.ObjectsPutStream(ctx, "m", buscalls.ObjectsNameRequest{Objects: "files", Name: "big"}, "streamed", strings.NewReader(big))
if err != nil || info.Size != uint64(len(big)) || info.Description != "streamed" {
t.Fatalf("streamed put %+v, %v", info, err)
}
opened, rd, found, err := conn.ObjectsOpen(ctx, "m", buscalls.ObjectsNameRequest{Objects: "files", Name: "big"})
if err != nil || !found || opened.Size != uint64(len(big)) {
t.Fatalf("opened %+v %v %v", opened, found, err)
}
if back, _ := io.ReadAll(rd); string(back) != big {
t.Fatalf("read back %d bytes, want %d", len(back), len(big))
}
_ = rd.Close()
if err := conn.ObjectsDelete("m", buscalls.ObjectsNameRequest{Objects: "files", Name: "big"}); err != nil {
t.Fatal(err)
}
if listed, err := conn.ObjectsList("m", buscalls.ObjectsRequest{Objects: "files"}); err != nil || len(listed.Objects) != 1 {
t.Fatalf("listed %+v, %v", listed, err)
}
+24 -15
View File
@@ -17,24 +17,11 @@ import (
// 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 (a bundle is held to what its runtime channel carries by the runtime call, not here), 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)
}
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)
obs, err := c.sentStore(ctx, r)
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)
return nil, err
}
info, err := obs.GetInfo(ctx, r.Object)
if errors.Is(err, jetstream.ErrObjectNotFound) {
@@ -49,3 +36,25 @@ func (c *Conn) SentGet(r buscalls.ObjectsSentGetRequest) (*buscalls.ObjectData,
}
return &buscalls.ObjectData{Info: objectInfoOf(info), Data: data}, nil
}
// sentStore checks the reference r and opens the sender's sent store it names.
func (c *Conn) sentStore(ctx context.Context, r buscalls.ObjectsSentGetRequest) (jetstream.ObjectStore, 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)
}
js, err := c.JetStream()
if err != nil {
return nil, err
}
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)
}
return obs, nil
}
+76
View File
@@ -0,0 +1,76 @@
package bus
import (
"context"
"errors"
"fmt"
"io"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"github.com/nats-io/nats.go/jetstream"
)
// The bus side of a bundle's put and get sessions (design 47 G §518, A-21, A-25): a put written into the module's object
// store as its chunks arrive and a get read out of it as they are asked, through the store's own chunking, so no more than
// one chunk is held in memory. The session itself is the runtime's (internal/runtime/sessions.go).
// ObjectsPutStream stores the object r.Name of the module's object store r.Objects from in, read to its end, with the
// put's description (A-25); it answers once the object is sealed. A read error of in drops the object: nothing is left.
func (c *Conn) ObjectsPutStream(ctx context.Context, module string, r buscalls.ObjectsNameRequest, description string, in io.Reader) (buscalls.ObjectInfo, error) {
if r.Name == "" {
return buscalls.ObjectInfo{}, errors.New("an object is put under a name")
}
obs, _, err := c.objectStore(ctx, module, r.Objects)
if err != nil {
return buscalls.ObjectInfo{}, err
}
info, err := obs.Put(ctx, jetstream.ObjectMeta{Name: r.Name, Description: description}, in)
if err != nil {
_ = obs.Delete(context.WithoutCancel(ctx), r.Name)
return buscalls.ObjectInfo{}, err
}
return objectInfoOf(info), nil
}
// ObjectsOpen is one object of the module's object store as a reader of its bytes beside its info; found is false when there
// is none. The caller closes the reader.
func (c *Conn) ObjectsOpen(ctx context.Context, module string, r buscalls.ObjectsNameRequest) (info buscalls.ObjectInfo, rd io.ReadCloser, found bool, err error) {
obs, _, err := c.objectStore(ctx, module, r.Objects)
if err != nil {
return info, nil, false, err
}
res, err := obs.Get(ctx, r.Name)
if errors.Is(err, jetstream.ErrObjectNotFound) {
return info, nil, false, nil
}
if err != nil {
return info, nil, false, err
}
i, err := res.Info()
if err != nil {
res.Close()
return info, nil, false, err
}
return objectInfoOf(i), res, true, nil
}
// SentOpen is a sent object, read by its reference as SentGet reads it, as a reader of its bytes beside its info.
func (c *Conn) SentOpen(ctx context.Context, r buscalls.ObjectsSentGetRequest) (buscalls.ObjectInfo, io.ReadCloser, error) {
obs, err := c.sentStore(ctx, r)
if err != nil {
return buscalls.ObjectInfo{}, nil, err
}
res, err := obs.Get(ctx, r.Object)
if errors.Is(err, jetstream.ErrObjectNotFound) {
return buscalls.ObjectInfo{}, 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 buscalls.ObjectInfo{}, nil, fmt.Errorf("the sent object %s of %s: %w", r.Object, r.Store, err)
}
i, err := res.Info()
if err != nil {
res.Close()
return buscalls.ObjectInfo{}, nil, err
}
return objectInfoOf(i), res, nil
}