GR16 §565: the grants part reads the restore points' names through kept-points
The part calls seat:mesh-recovery.kept-points as a kept call at its start and again on the seat's ended (outcome done), point-removed and expiry-ended, reads points[].names and asides[].names, keeps them in its state memberships (kept-names.mesh-recovery) and grants the names it kept until the call first answers; grants_first_views says the wait. A name leaves only by an answer. Builds: design 47 G §565, §554, A-230 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -183,6 +183,13 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
return grants.Unwrapped(conn.Call)(grants.CallBrokerList, map[string]any{})
|
||||
}}
|
||||
f.Part.Bindings.Start(ctx, f.Part)
|
||||
// The restore points' names (design 47 G §565): the seat mesh-recovery's kept-points, a kept call asked at each start and on the
|
||||
// seat's ended, point-removed and expiry-ended, until it answers.
|
||||
f.Part.KeptPoints = &grants.KeptPointsView{Changed: f.Wake, Logf: logger.Printf,
|
||||
Ask: func(context.Context) (json.RawMessage, error) {
|
||||
return grants.Unwrapped(conn.Call)(grants.CallKeptPoints, map[string]any{})
|
||||
}}
|
||||
f.Part.KeptPoints.Start(ctx, f.Part)
|
||||
// The seat list: read over the connection and the hub, given to catalogue at every change, and a pass composes again then.
|
||||
gate := newSeatGate()
|
||||
gate.Follow(seatlist.ProcessBus(conn.Call, hub), f.Wake, logger.Printf)
|
||||
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshbroker v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/meshrecovery v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -3031,8 +3031,8 @@ func TestGrants(t *testing.T) {
|
||||
t.Errorf("%s is no seat event of Consumes beside the verdicts and views", subject)
|
||||
}
|
||||
}
|
||||
if n := len(grants.SeatEventSubjects()); n != 6 {
|
||||
t.Errorf("the part hears %d seats' events, want the keeper's two, the vault's one and the broker's three", n)
|
||||
if n := len(grants.SeatEventSubjects()); n != 9 {
|
||||
t.Errorf("the part hears %d seats' events, want the keeper's two, the vault's one, the broker's three and the recovery's three", n)
|
||||
}
|
||||
for _, c := range b.calls {
|
||||
if strings.Contains(c, "mesh-controller") {
|
||||
|
||||
@@ -399,6 +399,8 @@ type FirstViewsState struct {
|
||||
// Bindings is the first view of the bindings, asked of the seat mesh-broker's list, beside the others (design 25
|
||||
// bindings-first-view §6); absent where the part's process holds none.
|
||||
Bindings *BindingsStatus `json:"bindings,omitempty"`
|
||||
// KeptPoints is the kept call of the seat mesh-recovery's kept-points, `waiting` until it first answers after a start (design 47 G §565).
|
||||
KeptPoints *KeptPointsStatus `json:"kept-points,omitempty"`
|
||||
// Streams are the whole reads of streams kept last per subject at the part's start, `waiting` until each ends (design 47 G §556).
|
||||
Streams []StreamRead `json:"streams,omitempty"`
|
||||
// Catalog is the first view of each branch of the catalog half the part follows (design 47 G §614), and CatalogEntries the entries
|
||||
@@ -420,7 +422,7 @@ type CatalogEntry struct {
|
||||
// sayFirstViews keeps the first views' statuses in the part's own state (for grants_first_views) when they change, and
|
||||
// raises or clears FirstViewEmpty for each answered at revision 0. Each pass calls it; nil Views is none.
|
||||
func (p *Part) sayFirstViews(res *Result) {
|
||||
if p.Views == nil && p.Bindings == nil && len(p.StreamReads()) == 0 {
|
||||
if p.Views == nil && p.Bindings == nil && p.KeptPoints == nil && len(p.StreamReads()) == 0 {
|
||||
return
|
||||
}
|
||||
state := FirstViewsState{V: 1, Views: []views.Status{}}
|
||||
@@ -436,6 +438,11 @@ func (p *Part) sayFirstViews(res *Result) {
|
||||
b.Asked, b.Answered = b.Asked.Truncate(time.Second), b.Answered.Truncate(time.Second)
|
||||
state.Bindings = &b
|
||||
}
|
||||
if p.KeptPoints != nil {
|
||||
k := p.KeptPoints.Status()
|
||||
k.Asked, k.Answered = k.Asked.Truncate(time.Second), k.Answered.Truncate(time.Second)
|
||||
state.KeptPoints = &k
|
||||
}
|
||||
state.Streams = p.StreamReads()
|
||||
if p.Views != nil && p.Views.Catalog != nil {
|
||||
state.Catalog, state.CatalogWaiting = p.Views.Catalog.Statuses(), p.Views.Catalog.Waiting()
|
||||
|
||||
@@ -144,6 +144,11 @@ func (f *Follower) open() {
|
||||
f.Wake()
|
||||
return
|
||||
}
|
||||
// The recovery module's points changed: the kept call asks again (keptpoints.go).
|
||||
if f.Part.KeptPoints != nil && IsKeptPointsEvent(event, body) {
|
||||
f.Part.KeptPoints.Heard(event, body)
|
||||
return
|
||||
}
|
||||
// An account the vault minted.
|
||||
if IsAccountIssued(event) {
|
||||
f.Wake()
|
||||
|
||||
@@ -73,7 +73,7 @@ const (
|
||||
)
|
||||
|
||||
// Invokes is every tool the part calls.
|
||||
var Invokes = append([]string{CallUsers, CallUserCan, CallStreams, CallRootFree, CallHandAct, CallSeatDefinitions, CallBrokerList}, append(catalogHalfVerbs(), firstViewTools()...)...)
|
||||
var Invokes = append([]string{CallUsers, CallUserCan, CallStreams, CallRootFree, CallHandAct, CallSeatDefinitions, CallBrokerList, CallKeptPoints}, append(catalogHalfVerbs(), firstViewTools()...)...)
|
||||
|
||||
// CallSeatDefinitions reads the seat list (mesh-sdk seatlist): the verb of the seat mesh-catalog.
|
||||
const CallSeatDefinitions = "seat:mesh-catalog.seat-definitions"
|
||||
@@ -216,7 +216,10 @@ type Inputs struct {
|
||||
// build of it): what the seats it defines, the streams others hear from it and its reads are composed from while its
|
||||
// trunk manifest is refused or waits, so no other principal loses a grant to it (ADR 0601 clause 3).
|
||||
Held map[string]catalogue.Manifest
|
||||
Gaps []Gap
|
||||
// KeptNames are the bus names the restore points and asides of the recovery module name, as last read from `kept-points` (design 47 G
|
||||
// §565, A-230), kept in the part's own state: set by the pass, never read from another part.
|
||||
KeptNames []string
|
||||
Gaps []Gap
|
||||
}
|
||||
|
||||
// HeldHash is one key of the vault's bus-accounts: the hash, and what it is of. The hash is never said by the part, and
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
package grants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshrecovery"
|
||||
)
|
||||
|
||||
// **The recovery module's restore points' names, read through `kept-points`** (design 47 G §565, A-230). The bus names of the
|
||||
// recovery module's account include those a restore point (or an aside) names. The part reads them by its `invokes` of
|
||||
// `seat:mesh-recovery.kept-points`, a kept call (ADR 0668 §1: made again until it answers), at its start and again on every `ended`
|
||||
// with outcome `done`, every `point-removed` and every `expiry-ended` of the seat, which it consumes. Until the call first answers
|
||||
// after a start, the part grants the names it last granted from it, kept in its state memberships (KeptNamesKey): it takes no name
|
||||
// away before that answer, and grants_first_views says the wait.
|
||||
|
||||
// KeptPointsVerb is the seat's verb as the manifest's invokes names it.
|
||||
const (
|
||||
CallKeptPoints = "seat:" + meshrecovery.Seat + "." + meshrecovery.VerbKeptPoints
|
||||
// KeptNamesKey is the key of the part's own state memberships that keeps the names last read from `kept-points`; never a
|
||||
// `<node>.<module>` key, since no machine is named `kept-names`.
|
||||
KeptNamesKey = "kept-names." + meshrecovery.Seat
|
||||
)
|
||||
|
||||
// KeptNames is the value of KeptNamesKey: every bus name a restore point or an aside of the recovery module names.
|
||||
type KeptNames struct {
|
||||
V int `json:"v"`
|
||||
Names []string `json:"names"`
|
||||
At time.Time `json:"at,omitzero"`
|
||||
}
|
||||
|
||||
// keptPointsAnswer is the part of kept-points' answer the part reads: `points[].names` and `asides[].names` (A-230). The contract's
|
||||
// Point gains `names` at the join, as its Aside has it; until then this is the wire's form.
|
||||
type keptPointsAnswer struct {
|
||||
Points []struct {
|
||||
Names []string `json:"names"`
|
||||
} `json:"points"`
|
||||
Asides []struct {
|
||||
Names []string `json:"names"`
|
||||
} `json:"asides"`
|
||||
}
|
||||
|
||||
// KeptPointsStatus is what grants_first_views says of the kept call.
|
||||
type KeptPointsStatus struct {
|
||||
Seat string `json:"seat"`
|
||||
Verb string `json:"verb"`
|
||||
Asked time.Time `json:"asked,omitzero"`
|
||||
Answered time.Time `json:"answered,omitzero"`
|
||||
Asks int `json:"asks"`
|
||||
// Names is how many names the last answer named (or, while waiting, the names kept from the last answer before the start).
|
||||
Names int `json:"names"`
|
||||
// Waiting says the call has not answered since the start: the part grants the names it kept.
|
||||
Waiting bool `json:"waiting"`
|
||||
Why string `json:"why,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KeptPointsView is the kept call of `kept-points`.
|
||||
type KeptPointsView struct {
|
||||
// Ask calls the seat's verb and answers its reply (the process's connection).
|
||||
Ask func(ctx context.Context) (json.RawMessage, error)
|
||||
Changed func()
|
||||
Logf func(string, ...any)
|
||||
Now func() time.Time
|
||||
// Pause waits d between two asks that failed and answers false once ctx ended; nil waits on the context's bound.
|
||||
Pause func(ctx context.Context, d time.Duration) bool
|
||||
// Wait is the first wait between two asks that failed (ten seconds when zero), doubling to MaxWait (five minutes when zero).
|
||||
Wait time.Duration
|
||||
MaxWait time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
part *Part
|
||||
st KeptPointsStatus
|
||||
asking bool
|
||||
again bool
|
||||
}
|
||||
|
||||
func (v *KeptPointsView) now() time.Time {
|
||||
if v.Now != nil {
|
||||
return v.Now().UTC()
|
||||
}
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
func (v *KeptPointsView) logf(format string, args ...any) {
|
||||
if v.Logf != nil {
|
||||
v.Logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Status is the kept call's status now.
|
||||
func (v *KeptPointsView) Status() KeptPointsStatus {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
s := v.st
|
||||
s.Seat, s.Verb = meshrecovery.Seat, meshrecovery.VerbKeptPoints
|
||||
return s
|
||||
}
|
||||
|
||||
// Start asks at the part's start, on a goroutine of its own, and until it answers.
|
||||
func (v *KeptPointsView) Start(ctx context.Context, p *Part) {
|
||||
v.mu.Lock()
|
||||
v.ctx, v.part = ctx, p
|
||||
v.st.Waiting = true
|
||||
v.mu.Unlock()
|
||||
if kept, err := p.keptNames(); err == nil {
|
||||
v.mu.Lock()
|
||||
v.st.Names = len(kept)
|
||||
v.mu.Unlock()
|
||||
}
|
||||
go v.askUntil(ctx, p, "start")
|
||||
}
|
||||
|
||||
// Heard asks again when an event heard is one that changes the points: `ended` with outcome `done`, `point-removed`, `expiry-ended`
|
||||
// of the seat mesh-recovery, named `mesh-recovery.<event>` or by its bus subject.
|
||||
func (v *KeptPointsView) Heard(event string, body []byte) {
|
||||
name, ok := recoveryEvent(event)
|
||||
if !ok || !changesPoints(name, body) {
|
||||
return
|
||||
}
|
||||
v.mu.Lock()
|
||||
ctx, p := v.ctx, v.part
|
||||
v.mu.Unlock()
|
||||
if p == nil {
|
||||
return // not started: the start's ask reads what the event changed
|
||||
}
|
||||
go v.askUntil(ctx, p, "the seat's "+name)
|
||||
}
|
||||
|
||||
func recoveryEvent(event string) (string, bool) {
|
||||
for _, prefix := range []string{meshrecovery.Seat + ".", "mesh.seat." + meshrecovery.Seat + ".event."} {
|
||||
if name, ok := strings.CutPrefix(event, prefix); ok {
|
||||
return name, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func changesPoints(name string, body []byte) bool {
|
||||
switch name {
|
||||
case meshrecovery.EventPointRemoved, meshrecovery.EventExpiryEnded:
|
||||
return true
|
||||
case meshrecovery.EventEnded:
|
||||
var e meshrecovery.EndedEvent
|
||||
return json.Unmarshal(body, &e) != nil || e.Outcome == meshrecovery.EndedEventOutcomeDone
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKeptPointsEvent says an event heard is one the kept call asks again on.
|
||||
func IsKeptPointsEvent(event string, body []byte) bool {
|
||||
name, ok := recoveryEvent(event)
|
||||
return ok && changesPoints(name, body)
|
||||
}
|
||||
|
||||
func (v *KeptPointsView) pause(ctx context.Context, d time.Duration) bool {
|
||||
if v.Pause != nil {
|
||||
return v.Pause(ctx, d)
|
||||
}
|
||||
t, cancel := context.WithTimeout(ctx, d)
|
||||
<-t.Done()
|
||||
cancel()
|
||||
return ctx.Err() == nil
|
||||
}
|
||||
|
||||
// askUntil asks until the seat answers or ctx ends, from ten seconds, doubling, at most every five minutes. An ask made while one
|
||||
// runs is made once more when it ends, so an event heard meanwhile is never lost.
|
||||
func (v *KeptPointsView) askUntil(ctx context.Context, p *Part, why string) {
|
||||
v.mu.Lock()
|
||||
if v.asking {
|
||||
v.again = true
|
||||
v.mu.Unlock()
|
||||
return
|
||||
}
|
||||
v.asking = true
|
||||
v.st.Why = why
|
||||
v.mu.Unlock()
|
||||
wait, most := v.Wait, v.MaxWait
|
||||
if wait == 0 {
|
||||
wait = 10 * time.Second
|
||||
}
|
||||
if most == 0 {
|
||||
most = 5 * time.Minute
|
||||
}
|
||||
for {
|
||||
v.mu.Lock()
|
||||
v.st.Asked = v.now()
|
||||
v.st.Asks++
|
||||
v.mu.Unlock()
|
||||
raw, err := v.Ask(ctx)
|
||||
var n int
|
||||
if err == nil {
|
||||
n, err = p.applyKeptPoints(raw, v.wake)
|
||||
}
|
||||
if err != nil {
|
||||
v.mu.Lock()
|
||||
v.st.Error = err.Error()
|
||||
v.mu.Unlock()
|
||||
v.logf("%s is not answered (%v): asked again in %s; the names last kept stay granted", CallKeptPoints, err, wait)
|
||||
if !v.pause(ctx, wait) {
|
||||
v.mu.Lock()
|
||||
v.asking = false
|
||||
v.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if wait *= 2; wait > most {
|
||||
wait = most
|
||||
}
|
||||
continue
|
||||
}
|
||||
v.mu.Lock()
|
||||
v.st.Answered, v.st.Waiting, v.st.Error, v.st.Names = v.now(), false, "", n
|
||||
again := v.again
|
||||
v.again = false
|
||||
if !again {
|
||||
v.asking = false
|
||||
}
|
||||
v.mu.Unlock()
|
||||
v.wake()
|
||||
if !again {
|
||||
return
|
||||
}
|
||||
wait = v.Wait
|
||||
if wait == 0 {
|
||||
wait = 10 * time.Second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *KeptPointsView) wake() {
|
||||
if v.Changed != nil {
|
||||
v.Changed()
|
||||
}
|
||||
}
|
||||
|
||||
// keptNames are the names last read from kept-points, from the part's own state: none before the first answer ever.
|
||||
func (p *Part) keptNames() ([]string, error) {
|
||||
raw, _, found, err := p.Store.Get(StateMemberships, KeptNamesKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("its own %s %s cannot be read: %w", StateMemberships, KeptNamesKey, err)
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var k KeptNames
|
||||
if err := json.Unmarshal(raw, &k); err != nil {
|
||||
return nil, fmt.Errorf("its own %s %s does not read: %w", StateMemberships, KeptNamesKey, err)
|
||||
}
|
||||
return k.Names, nil
|
||||
}
|
||||
|
||||
// applyKeptPoints replaces the kept names with those of an answer (a name no point names any more is taken away), and calls wake
|
||||
// when they changed. It answers how many names the answer named.
|
||||
func (p *Part) applyKeptPoints(raw json.RawMessage, wake func()) (int, error) {
|
||||
var a keptPointsAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return 0, fmt.Errorf("%s answered what it does not read: %w", CallKeptPoints, err)
|
||||
}
|
||||
var names []string
|
||||
for _, pt := range a.Points {
|
||||
names = append(names, pt.Names...)
|
||||
}
|
||||
for _, as := range a.Asides {
|
||||
names = append(names, as.Names...)
|
||||
}
|
||||
slices.Sort(names)
|
||||
names = slices.Compact(names)
|
||||
was, err := p.keptNames()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if slices.Equal(was, names) {
|
||||
return len(names), nil
|
||||
}
|
||||
body, _ := json.Marshal(KeptNames{V: 1, Names: names, At: p.now()})
|
||||
if _, err := p.Store.Put(StateMemberships, KeptNamesKey, body); err != nil {
|
||||
return 0, fmt.Errorf("its own %s %s cannot be written: %w", StateMemberships, KeptNamesKey, err)
|
||||
}
|
||||
if wake != nil {
|
||||
wake()
|
||||
}
|
||||
return len(names), nil
|
||||
}
|
||||
|
||||
// withKeptNames sets the names the recovery module's account is granted for restore points on in (Inputs.KeptNames), read from
|
||||
// the part's own state in one place for the pass and for its tools.
|
||||
func (p *Part) withKeptNames(in *Inputs) error {
|
||||
names, err := p.keptNames()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in.KeptNames = names
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package grants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Design 47 G §565, A-230: the part reads points[].names and asides[].names of kept-points, keeps them in its state memberships,
|
||||
// grants the names it kept until the call first answers after a start, takes a name away only by an answer, and asks again on
|
||||
// the seat's ended (outcome done), point-removed and expiry-ended alone.
|
||||
func TestKeptPointsNamesAreKeptUntilTheCallAnswers(t *testing.T) {
|
||||
store := memStore{}
|
||||
answer := func(s string) func(context.Context) (json.RawMessage, error) {
|
||||
return func(context.Context) (json.RawMessage, error) { return json.RawMessage(s), nil }
|
||||
}
|
||||
p := &Part{Store: store, Now: func() time.Time { return time.Unix(100, 0) }}
|
||||
v := &KeptPointsView{Ask: answer(`{"points":[{"names":["KV_a_x","KV_b_y"]}],"asides":[{"names":["OBJ_c_z"]}]}`)}
|
||||
p.KeptPoints = v
|
||||
v.askUntil(context.Background(), p, "start")
|
||||
var in Inputs
|
||||
if err := p.withKeptNames(&in); err != nil || !slices.Equal(in.KeptNames, []string{"KV_a_x", "KV_b_y", "OBJ_c_z"}) {
|
||||
t.Fatalf("names after the first answer %v (%v)", in.KeptNames, err)
|
||||
}
|
||||
// A restart: the call fails, and the names last kept stay; the status says it waits.
|
||||
p2 := &Part{Store: store}
|
||||
failing := &KeptPointsView{Ask: func(context.Context) (json.RawMessage, error) { return nil, errors.New("no responders") },
|
||||
Pause: func(context.Context, time.Duration) bool { return false }}
|
||||
p2.KeptPoints = failing
|
||||
failing.Start(context.Background(), p2)
|
||||
failing.askUntil(context.Background(), p2, "start")
|
||||
in = Inputs{}
|
||||
if err := p2.withKeptNames(&in); err != nil || len(in.KeptNames) != 3 {
|
||||
t.Fatalf("names kept before the call answers %v (%v)", in.KeptNames, err)
|
||||
}
|
||||
if s := failing.Status(); !s.Waiting || s.Names != 3 || s.Error == "" {
|
||||
t.Errorf("status %+v, want waiting with 3 names and the error", s)
|
||||
}
|
||||
// An answer takes a name away that no point names any more.
|
||||
failing.Ask = answer(`{"points":[{"names":["KV_a_x"]}],"asides":[]}`)
|
||||
failing.askUntil(context.Background(), p2, "the seat's point-removed")
|
||||
in = Inputs{}
|
||||
_ = p2.withKeptNames(&in)
|
||||
if !slices.Equal(in.KeptNames, []string{"KV_a_x"}) || failing.Status().Waiting {
|
||||
t.Errorf("names after the answer %v, status %+v", in.KeptNames, failing.Status())
|
||||
}
|
||||
// Which events ask again.
|
||||
for event, body := range map[string]string{"mesh-recovery.ended": `{"outcome":"done"}`, "mesh.seat.mesh-recovery.event.point-removed": `{}`,
|
||||
"mesh-recovery.expiry-ended": `{}`} {
|
||||
if !IsKeptPointsEvent(event, []byte(body)) {
|
||||
t.Errorf("%s does not ask again", event)
|
||||
}
|
||||
}
|
||||
for event, body := range map[string]string{"mesh-recovery.ended": `{"outcome":"failed"}`, "mesh-recovery.restored": `{"outcome":"done"}`, "mesh-broker.created": `{}`} {
|
||||
if IsKeptPointsEvent(event, []byte(body)) {
|
||||
t.Errorf("%s asks again", event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,8 @@ type Part struct {
|
||||
fvSaid firstViewsSaid
|
||||
// Bindings is the first view of the bindings, asked of the seat mesh-broker's list (bindingsview.go); nil in the tools.
|
||||
Bindings *BindingsView
|
||||
// KeptPoints is the kept call of the seat mesh-recovery's kept-points (keptpoints.go); nil in the tools.
|
||||
KeptPoints *KeptPointsView
|
||||
// bindMu orders the writes of the kept bindings: an event's and a first view's answer (bindings.go).
|
||||
bindMu sync.Mutex
|
||||
// runningRead is the whole read of the stream running at the start (running.go), guarded by mu.
|
||||
@@ -323,6 +325,9 @@ func (p *Part) Compose() Result {
|
||||
if err := p.withBound(&in); err != nil {
|
||||
res.Errors = append(res.Errors, err.Error())
|
||||
}
|
||||
if err := p.withKeptNames(&in); err != nil {
|
||||
res.Errors = append(res.Errors, err.Error())
|
||||
}
|
||||
for _, g := range in.Refused {
|
||||
res.Refused = append(res.Refused, g.Input+": "+g.Why)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshrecovery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -59,7 +60,8 @@ var seatListEvents = []string{meshcatalog.Seat + "." + meshcatalog.EventSeatDefi
|
||||
|
||||
// seatEvents are the events of seats the part follows its passes by, as `<seat>.<event>`.
|
||||
var seatEvents = []string{EventKeeperRaised, EventKeeperCleared, EventAccountIssued, meshbroker.Seat + "." + BrokerCreated,
|
||||
meshbroker.Seat + "." + BrokerDropped, meshbroker.Seat + "." + BrokerRecreated}
|
||||
meshbroker.Seat + "." + BrokerDropped, meshbroker.Seat + "." + BrokerRecreated,
|
||||
meshrecovery.Seat + "." + meshrecovery.EventEnded, meshrecovery.Seat + "." + meshrecovery.EventPointRemoved, meshrecovery.Seat + "." + meshrecovery.EventExpiryEnded}
|
||||
|
||||
// SeatEventSubjects are the subjects of the seats' events the part follows its passes by besides the verdicts' and the
|
||||
// views': the keeper's condition events, the vault's account-issued and the seat mesh-broker's, each `<seat>.<event>` of
|
||||
|
||||
@@ -119,6 +119,9 @@ func (p *Part) MembershipOf(node, module string) (any, error) {
|
||||
// What the bus module said it created, as the pass reads it, so the composed side carries the grants and bindings the
|
||||
// pass writes (issue 1966).
|
||||
boundErr := p.withBound(&in)
|
||||
if err := p.withKeptNames(&in); boundErr == nil {
|
||||
boundErr = err
|
||||
}
|
||||
recs, left := ListRecords(in)
|
||||
notFree := WithRootFree(&recs, p.Call)
|
||||
m, ok := Memberships(recs)[node+"."+module]
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
"mesh-broker.created",
|
||||
"mesh-broker.dropped",
|
||||
"mesh-broker.recreated",
|
||||
"mesh-recovery.ended",
|
||||
"mesh-recovery.expiry-ended",
|
||||
"mesh-recovery.point-removed",
|
||||
"mesh-delivery-assigner.views.assignments",
|
||||
"mesh-delivery-assigner.views.wants",
|
||||
"mesh-delivery-indexer.views.manifests",
|
||||
@@ -58,7 +61,8 @@
|
||||
"seat:mesh-catalog.seat-definitions",
|
||||
"seat:mesh-delivery.source",
|
||||
"seat:mesh-hand-acts.hand-act",
|
||||
"seat:mesh-broker.list"
|
||||
"seat:mesh-broker.list",
|
||||
"seat:mesh-recovery.kept-points"
|
||||
],
|
||||
"emits": [
|
||||
"asserted",
|
||||
|
||||
Reference in New Issue
Block a user