Join 9: claude-code adopts its licence binding's first view through its views reader (A-134)
The manager says binding.changed.<consumer>: the module's views reader hears it by the adoption and asks the first view (FirstBinding) once its subscription is taken, instead of a subscription of the exact subject of its own. Builds: design 47 G A-134 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
var now = time.Now().UnixMilli()
|
||||
@@ -595,6 +598,20 @@ func TestTheFirstViewOfTheBindingIsApplied(t *testing.T) {
|
||||
if _, err := FirstBinding(p, ask, writer(w)); err != nil || len(asked) != 6 || asked[5] != VaultGet {
|
||||
t.Fatalf("a start asked %v", asked)
|
||||
}
|
||||
// A-134: the first view is the follower the module's views reader adopts on binding.changed.<consumer>; its answer
|
||||
// applies the binding as FirstBinding does.
|
||||
r := &views.Reader{Module: Module}
|
||||
f := bindingsFollower(p, ask, writer(w), func(done string, err error) {
|
||||
if err != nil || !strings.Contains(done, `"generation":9`) {
|
||||
t.Errorf("the adopted first view applied %q, %v", done, err)
|
||||
}
|
||||
})
|
||||
if err := r.Adopt(f, Manager, BindingChangedEvent, func(context.Context, []byte) error { return nil }); err != nil || !r.Follows(Manager+".bindings") {
|
||||
t.Fatalf("the bindings follower is not adopted: %v", err)
|
||||
}
|
||||
if err := f.Start(context.Background()); err != nil || len(asked) != 9 {
|
||||
t.Fatalf("the adopted first view: %v, asked %v", err, asked)
|
||||
}
|
||||
raw, _ := os.ReadFile("../../module.json")
|
||||
var m struct {
|
||||
Consumes, Invokes []string
|
||||
|
||||
@@ -668,22 +668,15 @@ func run(p Paths, view *ServerView, configView *ConfigView) {
|
||||
}
|
||||
go report()
|
||||
}
|
||||
go persist("hearing this node's licence binding", func() error {
|
||||
err := stdio.Subscribe(BindingChanged, func(e stdio.Envelope) error {
|
||||
applied(OnBindingChanged(p, e.Body, ask, writeManaged))
|
||||
// Adopted by this module's views reader (design 47 G A-134): the reader hears binding.changed.<consumer> and asks the
|
||||
// first view once its subscription is taken, and raises and clears that view's findings through the module's writer.
|
||||
if err := meshViews.Adopt(bindingsFollower(p, ask, writeManaged, applied), Manager, BindingChangedEvent,
|
||||
func(_ context.Context, body []byte) error {
|
||||
applied(OnBindingChanged(p, body, ask, writeManaged))
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
go persist("asking this node's licence binding", func() error {
|
||||
done, err := FirstBinding(p, ask, writeManaged)
|
||||
if err == nil {
|
||||
applied(done, nil)
|
||||
}
|
||||
return err
|
||||
}, func(int) {})
|
||||
}
|
||||
return err
|
||||
}, func(n int) { say("hearing this node's licence binding%s", refusals(n)) })
|
||||
}); err != nil {
|
||||
say("this node's licence binding is not followed: %v", err)
|
||||
}
|
||||
// The token of the licence this node holds, asked again when the vault says its access secret changed (§19).
|
||||
go persist("hearing the vault's changes of this node's licence token", func() error {
|
||||
return stdio.Subscribe(SecretChanged, func(e stdio.Envelope) error {
|
||||
|
||||
@@ -18,6 +18,7 @@ package main
|
||||
// every node, `<node>.<server>` for one — which every node watches.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -30,6 +31,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// Seat is the licence manager's role, and Manager the module whose events this one consumes.
|
||||
@@ -481,7 +484,25 @@ func fetched(p Paths, b *BindingState, ask Ask, write WriteManaged) (string, err
|
||||
|
||||
// BindingChanged is the manager's event of a binding written or deleted (design 39 bus-and-switch §21), which this
|
||||
// module consumes (§22).
|
||||
const BindingChanged = Manager + ".binding.changed"
|
||||
const BindingChanged = Manager + "." + BindingChangedEvent
|
||||
|
||||
// BindingChangedEvent is that event's name as the manager says it, with the consumer as its further part
|
||||
// (`binding.changed.<consumer>`, design 47 G A-134).
|
||||
const BindingChangedEvent = "binding.changed"
|
||||
|
||||
// bindingsFollower is this node's first view of its licence binding, adopted by the module's views reader (A-134): its
|
||||
// ask is FirstBinding, whose outcome goes to applied; the picture keeps no binding, which the node's own files hold.
|
||||
func bindingsFollower(p Paths, ask Ask, write WriteManaged, applied func(string, error)) *views.Follower {
|
||||
return &views.Follower{Owner: Manager, Tool: SeatVerb("bindings"), State: "bindings",
|
||||
Ask: func(context.Context, string) (views.Answer, error) {
|
||||
done, err := FirstBinding(p, ask, write)
|
||||
if err != nil {
|
||||
return views.Answer{}, err
|
||||
}
|
||||
applied(done, nil)
|
||||
return views.Answer{Owner: Manager, State: "bindings", Values: map[string]json.RawMessage{}, At: time.Now()}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
// bindingOf is a consumer's binding as the manager says it, in binding.changed and in the verb bindings' answer: an
|
||||
// empty licence is a release (B22: decoded from a copy of the writer's form, testdata/binding-changed.json).
|
||||
|
||||
Reference in New Issue
Block a user