Files
mesh-host/internal/link/composedstate.go
T
jschoubben 5906e18028 Node-engine: a composition, its codec, digest and keys come from the node-engine client; the candidate answer and its limits from the seat's contract; the declared packages are the engine's own (H-2, H-3, ADR 0634 §1; issue 1966)
The engine's Composed keeps the catalogue's Plan and the declaration types and goes through nodeengineclient's codec; the fingerprint key part is meshcatalogclient's. The candidate answer is nodeengine.CandidateAnswer; the have still holds it through an interim JSON bridge (link.interimAnswers) until the Have group moves.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 15:10:45 +01:00

104 lines
2.9 KiB
Go

package link
import (
"context"
"errors"
"fmt"
engcomp "github.com/novox/mesh-host/internal/compositions"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-host/internal/selfcompose"
)
// ---- this node's composition state, in the bus's own storage (novox/hq ADR 0593 §6) --------------------------------
// composedStore binds the node's composition state `composed-<node>` over the link open now: bound, never created (the
// grants part creates it, create-only, history engcomp.History).
func composedStore(js nats.JetStreamContext, node string) (nats.KeyValue, error) {
bucket, err := engcomp.BucketFor(node)
if err != nil {
return nil, err
}
kv, err := js.KeyValue(bucket)
if err != nil {
return nil, fmt.Errorf("this node's composition state %s cannot be bound (the grants part creates it; written "+
"again at the next pass that composes): %w", bucket, err)
}
return kv, nil
}
func (l *natsLink) composedStore(node string) (nats.KeyValue, error) {
return composedStore(l.js, node)
}
func (b OverNATS) composedStore(node string) (nats.KeyValue, error) { return composedStore(b.JS, node) }
// EngineComposed is this node's composition state over whichever link the session has open (selfcompose.ComposedStore);
// with none open every read and write is refused, and nothing is kept anywhere else.
type EngineComposed struct {
Session *Session
Node string
}
var _ selfcompose.ComposedStore = EngineComposed{}
func (e EngineComposed) kv() (nats.KeyValue, error) {
e.Session.init()
e.Session.mu.Lock()
bus := e.Session.bus
e.Session.mu.Unlock()
l, ok := bus.(interface {
composedStore(node string) (nats.KeyValue, error)
})
if bus == nil || !ok {
return nil, ErrNoLink
}
return l.composedStore(e.Node)
}
// Entries is every key the state holds with its latest value and revision.
func (e EngineComposed) Entries(ctx context.Context) (map[string]selfcompose.ComposedEntry, error) {
kv, err := e.kv()
if err != nil {
return nil, err
}
keys, err := kv.Keys(nats.Context(ctx))
if errors.Is(err, nats.ErrNoKeysFound) {
return map[string]selfcompose.ComposedEntry{}, nil
}
if err != nil {
return nil, err
}
out := make(map[string]selfcompose.ComposedEntry, len(keys))
for _, key := range keys {
got, err := kv.Get(key)
if errors.Is(err, nats.ErrKeyNotFound) {
continue
}
if err != nil {
return nil, err
}
out[key] = selfcompose.ComposedEntry{Value: got.Value(), Revision: got.Revision()}
}
return out, nil
}
// Put writes a key and answers its revision.
func (e EngineComposed) Put(_ context.Context, key string, value []byte) (uint64, error) {
kv, err := e.kv()
if err != nil {
return 0, err
}
return kv.Put(key, value)
}
// Delete deletes a key (a delete marker, kept with the key's history).
func (e EngineComposed) Delete(_ context.Context, key string) error {
kv, err := e.kv()
if err != nil {
return err
}
return kv.Delete(key)
}