process: read a state its owner keeps one bucket per node as one state, so the provisioner, the assigner and the view read every node's compositions (ADR 0593 §6)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 18:23:27 +01:00
parent b56841596e
commit 8dc610c628
3 changed files with 296 additions and 7 deletions
+18 -7
View File
@@ -28,6 +28,8 @@ const stateTimeout = 30 * time.Second
type State struct {
Module string
Name string
// bucketName is the bucket when it is not <module>_<name>: one node's bucket of a per-node state (NodeFamily).
bucketName string
// kv binds this state's underlying key-value store: a live connection's own (Conn.State), or, in a
// unit test, a fake that never reaches a bus.
kv func(ctx context.Context) (jetstream.KeyValue, error)
@@ -36,14 +38,19 @@ type State struct {
}
// bucket is this state's name on the bus.
func (s State) bucket() string { return s.Module + "_" + s.Name }
func (s State) bucket() string {
if s.bucketName != "" {
return s.bucketName
}
return s.Module + "_" + s.Name
}
// State is the named state this connection's module keeps, bound lazily to its bucket on each call so a
// bucket the account is not granted fails at the call that needed it, naming the bucket. The bind itself
// (js.KeyValue) is what asks $JS.API.STREAM.INFO.KV_<bucket>, so it is what withRefusal watches: a bucket
// not granted is refused there, promptly, as nats.ErrPermissionViolation.
func (c *Conn) State(name string) State {
return c.bound(c.account.Module, name, func() error { return c.refusedUnannounced(name) })
return c.bound(c.account.Module, name, "", func() error { return c.refusedUnannounced(name) })
}
// Read is another module's state this connection's module reads, named as its manifest's `reads` names it
@@ -52,7 +59,7 @@ func (c *Conn) State(name string) State {
// the seat mesh-provisioner's plans). A name that is not `<module>.<state>` fails at the call that needed it.
func (c *Conn) Read(read string) State {
module, name, ok := strings.Cut(read, ".")
return c.bound(module, name, func() error {
return c.bound(module, name, "", func() error {
if !ok || module == "" || name == "" || strings.Contains(name, ".") {
return fmt.Errorf("%q is not a state another module keeps, <module>.<state>", read)
}
@@ -60,10 +67,14 @@ func (c *Conn) Read(read string) State {
})
}
// bound is module's state name on this connection, checked by refused before every bind.
func (c *Conn) bound(module, name string, refused func() error) State {
bucket := module + "_" + name
return State{Module: module, Name: name, closed: func() bool { return c.nc.IsClosed() }, kv: func(ctx context.Context) (jetstream.KeyValue, error) {
// bound is module's state name on this connection, in its bucket <module>_<name> or the bucket given, checked by
// refused before every bind.
func (c *Conn) bound(module, name, bucket string, refused func() error) State {
given := bucket
if bucket == "" {
bucket = module + "_" + name
}
return State{Module: module, Name: name, bucketName: given, closed: func() bool { return c.nc.IsClosed() }, kv: func(ctx context.Context) (jetstream.KeyValue, error) {
if err := refused(); err != nil {
return nil, err
}