A tool reads one module's bindings again from the seat events the shared stream still holds (issue 1966)
A binding the bus module said created that the part did not keep had no recovery: distribution's holds, windows, mirrors and events are missing on the bus node's memberships. grants_bindings_again, a hand act, asks the process to read the seat mesh-broker's created, recreated and dropped of one module again from EVENTS by sequence and keep each through HearBroker in order, so a dropped entry stays dropped. grants_bindings shows what it kept, and says when the stream no longer holds a created: then the seat's list answering bindings is the path (the record extending ADR 0595 §7). The part alone is granted the stream's message get, no consumer. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"mesh-grants/internal/buscompose"
|
||||
"mesh-grants/internal/grants"
|
||||
)
|
||||
|
||||
// readAgainAtMost bounds one re-read of the seat mesh-broker's events (grants_bindings_again): a stream holding more of
|
||||
// them than this is read up to here, and the answer says the rest was not read.
|
||||
const readAgainAtMost = 20000
|
||||
|
||||
// brokerHistory reads the seat mesh-broker's events again from the shared events stream, by sequence and subject
|
||||
// (STREAM.MSG.GET with next_by_subj, the grant buscompose.SeatEventsReadAgain): a get per event, no consumer, oldest first.
|
||||
type brokerHistory struct {
|
||||
ctx context.Context
|
||||
nc *nats.Conn
|
||||
}
|
||||
|
||||
// msgGetAnswer is the bus's answer to a STREAM.MSG.GET.
|
||||
type msgGetAnswer struct {
|
||||
Message *struct {
|
||||
Subject string `json:"subject"`
|
||||
Seq uint64 `json:"seq"`
|
||||
Data []byte `json:"data"`
|
||||
Time time.Time `json:"time"`
|
||||
} `json:"message"`
|
||||
Error *struct {
|
||||
Code int `json:"code"`
|
||||
ErrCode int `json:"err_code"`
|
||||
Description string `json:"description"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// noMessage is the bus's error code for a get past the last message of the subject.
|
||||
const noMessage = 10037
|
||||
|
||||
func (h brokerHistory) Said() ([]grants.SaidEvent, error) {
|
||||
subject := "mesh.seat." + meshbroker.Seat + ".event.*"
|
||||
var out []grants.SaidEvent
|
||||
for seq := uint64(1); ; {
|
||||
if len(out) >= readAgainAtMost {
|
||||
return out, fmt.Errorf("the stream holds more than %d of the seat's events: read up to sequence %d, the rest not", readAgainAtMost, seq)
|
||||
}
|
||||
body, _ := json.Marshal(map[string]any{"seq": seq, "next_by_subj": subject})
|
||||
ctx, cancel := context.WithTimeout(h.ctx, requestWithin)
|
||||
reply, err := h.nc.RequestWithContext(ctx, buscompose.SeatEventsReadAgain, body)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("%s at sequence %d: %w", buscompose.SeatEventsReadAgain, seq, err)
|
||||
}
|
||||
var a msgGetAnswer
|
||||
if err := json.Unmarshal(reply.Data, &a); err != nil {
|
||||
return out, fmt.Errorf("%s answered what does not read: %w", buscompose.SeatEventsReadAgain, err)
|
||||
}
|
||||
if a.Error != nil {
|
||||
if a.Error.ErrCode == noMessage {
|
||||
return out, nil
|
||||
}
|
||||
return out, fmt.Errorf("%s at sequence %d: %s", buscompose.SeatEventsReadAgain, seq, a.Error.Description)
|
||||
}
|
||||
if a.Message == nil {
|
||||
return out, nil
|
||||
}
|
||||
out = append(out, grants.SaidEvent{Subject: a.Message.Subject, Data: a.Message.Data, At: a.Message.Time})
|
||||
seq = a.Message.Seq + 1
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,24 @@ func TestTheGrantsPartWhole(t *testing.T) {
|
||||
})
|
||||
sayBroker("dropped", membership.BusBinding{Local: "kept"})
|
||||
until("mod-c's binding gone after dropped", func() bool { return len(bindingOf()) == 0 })
|
||||
// Issue 1966, grants_bindings_again: asked as the tool asks, the process reads the seat's events again from EVENTS by
|
||||
// sequence (brokerHistory) and keeps mod-c's created and dropped in their order, so the dropped entry stays dropped.
|
||||
ask, _ := json.Marshal(grants.Ask{By: "jochen@shanks", At: time.Now(), Bindings: "mod-c"})
|
||||
if _, err := kvs[grants.Module+"."+grants.StateUserList].Put(ctx, grants.KeyAsked, ask); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
until("mod-c's bindings read again from EVENTS", func() bool {
|
||||
e, err := kvs[grants.Module+"."+grants.StateUserList].Get(ctx, grants.KeyBindingsAgain)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var again grants.BindingsAgain
|
||||
return json.Unmarshal(e.Value(), &again) == nil && again.Module == "mod-c" && again.Read == 2 && again.Error == "" &&
|
||||
slices.Equal(again.Dropped, []string{"kept"}) && len(again.Kept) == 0
|
||||
})
|
||||
if b := bindingOf(); len(b) != 0 {
|
||||
t.Errorf("a dropped binding came back from the re-read: %+v", b)
|
||||
}
|
||||
for _, bucket := range []string{"mesh-controller_lease", grants.Module + "_lease", grants.Module + "_takeover"} {
|
||||
if _, err := js.KeyValue(ctx, bucket); !errors.Is(err, jetstream.ErrBucketNotFound) {
|
||||
t.Errorf("the bucket %s: %v, want none", bucket, err)
|
||||
|
||||
@@ -135,6 +135,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
Objects: sealedObjects{ctx: ctx, js: js}, Purging: ownPurging{ctx: ctx, js: js, module: account.Module}, Logf: logger.Printf,
|
||||
Bus: grants.BusAddress{URL: account.URL, Fingerprint: account.Fingerprint}}
|
||||
f.Watch = fv
|
||||
f.Part.History = brokerHistory{ctx: ctx, nc: nc}
|
||||
// The seat list: read over the connection and the hub, given to catalogue at every change, and a pass composes again then.
|
||||
gate := &seatGate{}
|
||||
gate.follow(seatlist.ProcessBus(conn.Call, hub), f.Wake, logger.Printf)
|
||||
|
||||
@@ -256,13 +256,26 @@ func toolsOf(p *grants.Part) []stdio.Tool {
|
||||
"every membership of the module the part wrote, and the kept bindings a membership lacks. One module, or every one. Read-only.",
|
||||
Input: schema(map[string]string{"module": "one module, e.g. distribution; none for every one"}),
|
||||
Run: func(a map[string]any) (any, error) { return p.BindingsOf(grants.Answer(a, "module")) }},
|
||||
{Name: grants.ToolBindingsAgain, Description: "Read one module's bindings again from the seat mesh-broker's created, " +
|
||||
"recreated and dropped that the shared events stream still holds, kept as if heard now (issue 1966), by a person's act " +
|
||||
"with why, recorded as a hand act: the step when grants_bindings lacks an entry the bus module made. Asks the part's " +
|
||||
"process; grants_bindings shows what it kept, what was already kept, and when the stream no longer holds the created.",
|
||||
Input: schema(map[string]string{"module": "the module, e.g. distribution", "why": "why, kept in the hand act"}, "module", "why"),
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
return p.AskBindingsAgain(stdio.CallerOf(a), grants.Answer(a, "module"), grants.Answer(a, "why"), recordHandActFor("bindings-again"))
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// recordHandAct records a person's act through the seat mesh-hand-acts.
|
||||
func recordHandAct(what, why string) error {
|
||||
_, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: "first-view-again"})
|
||||
return err
|
||||
// recordHandAct records a person's act of grants_first_view_again through the seat mesh-hand-acts.
|
||||
func recordHandAct(what, why string) error { return recordHandActFor("first-view-again")(what, why) }
|
||||
|
||||
// recordHandActFor records a person's act through the seat mesh-hand-acts, with its cause.
|
||||
func recordHandActFor(cause string) func(what, why string) error {
|
||||
return func(what, why string) error {
|
||||
_, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: cause})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// serveTools serves the tools to the runtime until it stops.
|
||||
|
||||
@@ -3189,6 +3189,56 @@ func TestGrants(t *testing.T) {
|
||||
if got := bindingsOn("a.mod-x"); len(got) != 0 {
|
||||
t.Errorf("after dropped: %+v", got)
|
||||
}
|
||||
// Issue 1966, grants_bindings_again (the lead's ruling, option b): the process reads the seat's events the shared
|
||||
// stream still holds again, oldest first, and keeps one module's created, recreated and dropped as if heard now: a
|
||||
// binding never kept comes back, one dropped since stays dropped, another module's is left; the hand act is recorded
|
||||
// before the ask, and an ask without why or a module is refused.
|
||||
event := func(name, module, local, bucket string) grants.SaidEvent {
|
||||
body, _ := json.Marshal(grants.BrokerEvent{V: 1, Module: module, LocalName: local, StorageType: "key-value", Template: "state",
|
||||
Binding: membership.BusBinding{Local: local, Storage: "key-value", Template: "state", Bucket: bucket}})
|
||||
return grants.SaidEvent{Subject: "mesh.seat.mesh-broker.event." + name, Data: body, At: time.Date(2026, 11, 3, 0, 0, 0, 0, time.UTC)}
|
||||
}
|
||||
p.Create = b
|
||||
p.History = saidHistory{event("created", "mod-x", "windows", "mod-x_windows"), event("refused", "mod-x", "mirrors", ""),
|
||||
event("created", "mod-y", "other", "mod-y_other"), event("created", "mod-x", "gone", "mod-x_gone"),
|
||||
event("dropped", "mod-x", "gone", "")}
|
||||
var acts []string
|
||||
record := func(what, why string) error { acts = append(acts, what+": "+why); return nil }
|
||||
for _, bad := range [][2]string{{"mod-x", ""}, {"", "x"}, {"a.mod-x", "x"}} {
|
||||
if _, err := p.AskBindingsAgain("jochen@shanks", bad[0], bad[1], record); err == nil || len(acts) != 0 {
|
||||
t.Errorf("grants_bindings_again %q %q was asked: %v %v", bad[0], bad[1], err, acts)
|
||||
}
|
||||
}
|
||||
readAgain := func(module string) string {
|
||||
if _, err := p.AskBindingsAgain("jochen@shanks", module, "distribution's lost bindings", record); err != nil {
|
||||
t.Fatalf("grants_bindings_again %s: %v", module, err)
|
||||
}
|
||||
p.Compose()
|
||||
got, err := p.BindingsOf(module)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := json.Marshal(got)
|
||||
return string(raw)
|
||||
}
|
||||
if got := readAgain("mod-x"); !strings.Contains(got, `"kept":["windows"],"already":[],"dropped":["gone"]`) || !strings.Contains(got, `"read":3`) {
|
||||
t.Errorf("the first re-read: %s", got)
|
||||
}
|
||||
if got := bindingsOn("a.mod-x"); len(got) != 1 || got[0].Bucket != "mod-x_windows" {
|
||||
t.Errorf("after the re-read, mod-x's membership carries %+v", got)
|
||||
}
|
||||
if raw, _, found, _ := b.ownGet(grants.StateMemberships, grants.BoundKey("mod-y")); found {
|
||||
t.Errorf("another module's binding was kept by mod-x's re-read: %s", raw)
|
||||
}
|
||||
if got := readAgain("mod-x"); !strings.Contains(got, `"kept":[],"already":["windows"]`) {
|
||||
t.Errorf("the second re-read: %s", got)
|
||||
}
|
||||
if got := readAgain("mod-z"); !strings.Contains(got, "holds no created or recreated of mod-z") || !strings.Contains(got, "seat mesh-broker's list") {
|
||||
t.Errorf("a module whose created the stream no longer holds: %s", got)
|
||||
}
|
||||
if len(acts) != 3 || !strings.HasPrefix(acts[0], "grants_bindings_again mod-x: ") {
|
||||
t.Errorf("hand acts %v", acts)
|
||||
}
|
||||
})
|
||||
|
||||
// Issue 1966 (F2), the conformance row: an entry asked in `requires` is granted where its old section is. For a
|
||||
@@ -4625,3 +4675,8 @@ func oldStateReaders(path string, src []byte) ([]string, error) {
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// saidHistory is the seat mesh-broker's events a shared events stream holds, oldest first (grants.BrokerHistory).
|
||||
type saidHistory []grants.SaidEvent
|
||||
|
||||
func (h saidHistory) Said() ([]grants.SaidEvent, error) { return h, nil }
|
||||
|
||||
@@ -600,13 +600,18 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
|
||||
t.Errorf("a one-filter consumer's grants = %v: its update is the no-filter create form", one)
|
||||
}
|
||||
for _, u := range users {
|
||||
if u.Module != GrantsModule || u.Kind != KindModule {
|
||||
continue
|
||||
}
|
||||
perms, err := PermissionsFor(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Issue 1966: the grants part alone reads the shared events stream by sequence, to read the seat mesh-broker's
|
||||
// events again (grants_bindings_again).
|
||||
if may := MayPublish(perms, "$JS.API.STREAM.MSG.GET.EVENTS"); may != (u.Module == GrantsModule && u.Kind == KindModule) {
|
||||
t.Errorf("%s %s may read EVENTS by sequence: %v", u.Kind, u.Module, may)
|
||||
}
|
||||
if u.Module != GrantsModule || u.Kind != KindModule {
|
||||
continue
|
||||
}
|
||||
if !MayPublish(perms, "$JS.API.CONSUMER.CREATE.EVENTS.a_m") || MayPublish(perms, "$JS.API.CONSUMER.CREATE.EVENTS.a_quiet") ||
|
||||
MayPublish(perms, "$JS.API.CONSUMER.DELETE.EVENTS.a_m") || MayPublish(perms, "$JS.API.CONSUMER.CREATE.EVENTS.a_m.mesh.mod.x.event.happened") {
|
||||
t.Errorf("the grants part creates consumers as %v", u.CreatesConsumers)
|
||||
|
||||
@@ -859,6 +859,10 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
||||
pub = append(pub, CreateConsumerGrants(p.CreatesConsumers)...)
|
||||
// And its own object bucket, where it writes the user list sealed to the bus node (ADR 0547 clause 7).
|
||||
pub = append(pub, GrantsUserListWrites()...)
|
||||
// And the read of one message of the shared events stream by its sequence and subject, where the part reads the
|
||||
// seat mesh-broker's created, recreated and dropped again for grants_bindings_again (issue 1966): a get, never a
|
||||
// consumer, so no other account's consumer is touched.
|
||||
pub = append(pub, SeatEventsReadAgain)
|
||||
}
|
||||
// The view alone reads the build seats' workers for the backlog of asks (issue 1887 A8a).
|
||||
if p.Module == ViewModule {
|
||||
@@ -1546,6 +1550,10 @@ func CreateConsumerGrants(consumers []ModuleConsumer) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// SeatEventsReadAgain is the grants part's read of the shared events stream by sequence (STREAM.MSG.GET with
|
||||
// next_by_subj): a binding the bus module said that the part did not keep is read again from it (issue 1966).
|
||||
const SeatEventsReadAgain = "$JS.API.STREAM.MSG.GET." + EventsStream
|
||||
|
||||
// GrantsModule is the one module whose principal publishes the modules' memberships (novox/hq ADR 0547 §5): the grants
|
||||
// part, which composes them from the manifests and assignments. It defines no stream and is granted no other subject here.
|
||||
const GrantsModule = "mesh-grants"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||
"mesh-grants/internal/buscompose"
|
||||
@@ -245,11 +246,174 @@ func (p *Part) BindingsOf(module string) (any, error) {
|
||||
}
|
||||
out = append(out, k)
|
||||
}
|
||||
if module != "" && len(out) == 0 {
|
||||
return map[string]any{"modules": out, "says": "the part keeps no binding of " + module + ": the bus module said no created for it that the part heard"}, nil
|
||||
again, err := p.lastBindingsAgain(module)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"modules": out,
|
||||
"says": "each binding the part kept from the bus module's created and recreated, beside the memberships it wrote; lacking names a kept binding a membership does not carry yet"}, nil
|
||||
answer := map[string]any{"modules": out,
|
||||
"says": "each binding the part kept from the bus module's created and recreated, beside the memberships it wrote; lacking names a kept binding a membership does not carry yet"}
|
||||
if module != "" && len(out) == 0 {
|
||||
answer["says"] = "the part keeps no binding of " + module + ": the bus module said no created for it that the part heard"
|
||||
}
|
||||
if again != nil {
|
||||
answer["read-again"] = again
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
// **A binding the part did not keep is read again from the shared events stream** (issue 1966; the lead's ruling, option
|
||||
// b): grants_bindings_again asks the part's process, by a person's act recorded as a hand act, to read the seat
|
||||
// mesh-broker's events the stream still holds, oldest first, and to keep each of one module's created, recreated and
|
||||
// dropped through HearBroker, as if heard now, so a binding dropped since is not brought back. The stream keeps its events
|
||||
// for a bounded age: a created older than that is not there, and the answer says so, naming the path then (the seat's
|
||||
// list answering each entry's binding, a record extending ADR 0595 §7).
|
||||
|
||||
// KeyBindingsAgain is, in StateUserList, the last re-read of one module's bindings: BindingsAgain.
|
||||
const KeyBindingsAgain = "bindings-again"
|
||||
|
||||
// BrokerHistory reads the seat mesh-broker's events again from the shared events stream.
|
||||
type BrokerHistory interface {
|
||||
// Said is every event of the seat the stream still holds, oldest first.
|
||||
Said() ([]SaidEvent, error)
|
||||
}
|
||||
|
||||
// SaidEvent is one event of the seat mesh-broker as the shared events stream holds it.
|
||||
type SaidEvent struct {
|
||||
Subject string `json:"subject"`
|
||||
Data []byte `json:"data"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// BindingsAgain is what the last re-read of one module's bindings found: the seat's events read, the local names whose
|
||||
// binding it kept (Kept), those it found already kept as said (Already), those a dropped took away (Dropped), the oldest
|
||||
// event the stream still holds, and in plain words what it means.
|
||||
type BindingsAgain struct {
|
||||
V int `json:"v"`
|
||||
Module string `json:"module"`
|
||||
By string `json:"by"`
|
||||
At time.Time `json:"at"`
|
||||
Read int `json:"read"`
|
||||
Kept []string `json:"kept"`
|
||||
Already []string `json:"already"`
|
||||
Dropped []string `json:"dropped"`
|
||||
Oldest time.Time `json:"oldest,omitzero"`
|
||||
Says string `json:"says"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// bindingsAgain reads module's bindings again from the seat's events the stream holds, keeps each, and writes what it
|
||||
// found to its own state; an error is said in the pass's errors and in the kept result.
|
||||
func (p *Part) bindingsAgain(res *Result, module, by string) {
|
||||
out := BindingsAgain{V: 1, Module: module, By: by, At: p.now(), Kept: []string{}, Already: []string{}, Dropped: []string{}}
|
||||
events, err := p.History.Said()
|
||||
if err != nil {
|
||||
out.Error = "the shared events stream could not be read: " + err.Error()
|
||||
}
|
||||
heardCreate := false
|
||||
for _, e := range events {
|
||||
if out.Oldest.IsZero() || (!e.At.IsZero() && e.At.Before(out.Oldest)) {
|
||||
out.Oldest = e.At
|
||||
}
|
||||
name, ok := brokerEvent(e.Subject, p.Brokers())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var said BrokerEvent
|
||||
if json.Unmarshal(e.Data, &said) != nil || said.Module != module {
|
||||
continue
|
||||
}
|
||||
out.Read++
|
||||
local := firstOf(said.LocalName, said.Local, said.Binding.Local)
|
||||
changed, err := p.HearBroker(e.Subject, e.Data, p.Brokers())
|
||||
if err != nil {
|
||||
out.Error = fmt.Sprintf("the %s of %s could not be kept: %v", name, local, err)
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case name == BrokerDropped && changed:
|
||||
out.Dropped = appendOnce(out.Dropped, local)
|
||||
out.Kept = slices.DeleteFunc(out.Kept, func(l string) bool { return l == local })
|
||||
case name != BrokerDropped:
|
||||
heardCreate = true
|
||||
out.Dropped = slices.DeleteFunc(out.Dropped, func(l string) bool { return l == local })
|
||||
if changed {
|
||||
out.Kept = appendOnce(out.Kept, local)
|
||||
out.Already = slices.DeleteFunc(out.Already, func(l string) bool { return l == local })
|
||||
} else if !slices.Contains(out.Kept, local) {
|
||||
out.Already = appendOnce(out.Already, local)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case out.Error != "":
|
||||
out.Says = "the re-read of " + module + " stopped: " + out.Error
|
||||
res.Errors = append(res.Errors, out.Says)
|
||||
case !heardCreate:
|
||||
out.Says = fmt.Sprintf("the shared events stream holds no created or recreated of %s (its oldest event: %s): a created older "+
|
||||
"than the stream keeps is not there, and its binding comes back only by the seat mesh-broker's list answering each "+
|
||||
"entry's binding (a record extending ADR 0595 §7), not by this tool", module, out.Oldest.Format(time.RFC3339))
|
||||
default:
|
||||
out.Says = fmt.Sprintf("read %d of the seat mesh-broker's events of %s again: kept %v, already kept %v, dropped %v; the pass "+
|
||||
"writes them into its memberships", out.Read, module, out.Kept, out.Already, out.Dropped)
|
||||
}
|
||||
if p.Logf != nil {
|
||||
p.Logf("the bindings of %s read again by hand, for %s: %s", module, by, out.Says)
|
||||
}
|
||||
raw, _ := json.Marshal(out)
|
||||
p.changed(res, StateUserList, KeyBindingsAgain, raw)
|
||||
}
|
||||
|
||||
func appendOnce(list []string, s string) []string {
|
||||
if slices.Contains(list, s) {
|
||||
return list
|
||||
}
|
||||
return append(list, s)
|
||||
}
|
||||
|
||||
// AskBindingsAgain answers the tool grants_bindings_again: it asks the part's process to read one module's bindings
|
||||
// again from the seat mesh-broker's events the shared events stream holds, and records the hand act through record (the
|
||||
// seat mesh-hand-acts) first; a module that is no module's name, or an ask without why, is refused before anything is
|
||||
// asked. grants_bindings shows what the process found.
|
||||
func (p *Part) AskBindingsAgain(caller, module, why string, record func(what, why string) error) (any, error) {
|
||||
if module == "" || strings.ContainsAny(module, ".*> ") {
|
||||
return nil, fmt.Errorf("grants_bindings_again needs one module, by its name: %q is none", module)
|
||||
}
|
||||
if strings.TrimSpace(why) == "" {
|
||||
return nil, errors.New("grants_bindings_again needs why: it is recorded as a hand act")
|
||||
}
|
||||
if record != nil {
|
||||
if err := record(ToolBindingsAgain+" "+module, why); err != nil {
|
||||
return nil, fmt.Errorf("the hand act could not be recorded, so nothing was asked: %w", err)
|
||||
}
|
||||
}
|
||||
ask, _ := json.Marshal(Ask{By: caller, At: p.now(), Bindings: module})
|
||||
rev, err := p.Store.Put(StateUserList, KeyAsked, ask)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the ask could not be written: %w", err)
|
||||
}
|
||||
return map[string]any{"asked": rev, "module": module,
|
||||
"says": "the part's process reads the seat mesh-broker's created, recreated and dropped of " + module + " again from the shared " +
|
||||
"events stream at this ask, and keeps each as if heard now; grants_bindings shows what it kept, what was already kept, " +
|
||||
"and whether the stream still holds its created"}, nil
|
||||
}
|
||||
|
||||
// lastBindingsAgain is the last re-read kept, when it was of module (or any, for an empty module).
|
||||
func (p *Part) lastBindingsAgain(module string) (*BindingsAgain, error) {
|
||||
raw, _, found, err := p.Store.Get(StateUserList, KeyBindingsAgain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("its own %s %s cannot be read: %w", StateUserList, KeyBindingsAgain, err)
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var b BindingsAgain
|
||||
if err := json.Unmarshal(raw, &b); err != nil {
|
||||
return nil, fmt.Errorf("its own %s %s does not read: %w", StateUserList, KeyBindingsAgain, err)
|
||||
}
|
||||
if module != "" && b.Module != module {
|
||||
return nil, nil
|
||||
}
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
func sameBinding(a, b membership.BusBinding) bool {
|
||||
|
||||
@@ -174,6 +174,9 @@ type Part struct {
|
||||
// Say says a condition to the operator: the process's; nil in the tools.
|
||||
Say func(oc.Condition) error
|
||||
Now func() time.Time
|
||||
// History reads the seat mesh-broker's events again from the shared events stream (bindings.go): the process's
|
||||
// connection; nil in the tools, which ask the process.
|
||||
History BrokerHistory
|
||||
// SeatList is the seat list reader, which gates a module whose seats the list has not named (inputs.go, SeatGate); nil
|
||||
// gates none.
|
||||
SeatList SeatGate
|
||||
@@ -258,6 +261,9 @@ type Ask struct {
|
||||
Consumer string `json:"consumer,omitempty"`
|
||||
// FirstView is, from grants_first_view_again, one first view to ask once more by hand (its state), or AllFirstViews.
|
||||
FirstView string `json:"first-view,omitempty"`
|
||||
// Bindings is, from grants_bindings_again, one module whose bindings the part reads again from the seat mesh-broker's
|
||||
// events the shared events stream still holds (bindings.go, issue 1966).
|
||||
Bindings string `json:"bindings,omitempty"`
|
||||
}
|
||||
|
||||
// AllFirstViews asks every first view once more.
|
||||
@@ -298,6 +304,14 @@ func (p *Part) Compose() Result {
|
||||
p.Views.SayUnsaid()
|
||||
in := p.inputs()
|
||||
p.keepBrokers(in)
|
||||
var ask Ask
|
||||
if p.Create != nil || p.Consumers != nil {
|
||||
ask = p.asked()
|
||||
}
|
||||
// A module's bindings read again by hand are kept before the pass reads the kept bindings, so it writes them now.
|
||||
if ask.Bindings != "" && p.History != nil {
|
||||
p.bindingsAgain(&res, ask.Bindings, ask.By)
|
||||
}
|
||||
if err := p.withBound(&in); err != nil {
|
||||
res.Errors = append(res.Errors, err.Error())
|
||||
}
|
||||
@@ -493,10 +507,6 @@ func (p *Part) Compose() Result {
|
||||
}
|
||||
p.clearFull()
|
||||
held, heldErr := HeldStreams(p.Call)
|
||||
var ask Ask
|
||||
if p.Create != nil || p.Consumers != nil {
|
||||
ask = p.asked()
|
||||
}
|
||||
forced := ask.Assert
|
||||
for _, module := range sortedKeys(in.Manifests) {
|
||||
if !assigned(in, module) {
|
||||
|
||||
@@ -33,11 +33,13 @@ const (
|
||||
ToolSeatListAgain = "grants_seat_list_again"
|
||||
// ToolBindings reads the bindings the part keeps from the bus module's events (bindings.go, issue 1966).
|
||||
ToolBindings = "grants_bindings"
|
||||
// ToolBindingsAgain reads one module's bindings again from the seat mesh-broker's events the shared events stream holds.
|
||||
ToolBindingsAgain = "grants_bindings_again"
|
||||
)
|
||||
|
||||
// Tools is every tool, in the manifest's order.
|
||||
var Tools = []string{ToolGrants, ToolMembership, ToolUserList, ToolComposeNow, ToolAssert, ToolAccounts, ToolConsumerMatch,
|
||||
ToolAcceptHashes, ToolFirstViews, ToolFirstViewAgain, ToolSeatList, ToolSeatListAgain, ToolBindings}
|
||||
ToolAcceptHashes, ToolFirstViews, ToolFirstViewAgain, ToolSeatList, ToolSeatListAgain, ToolBindings, ToolBindingsAgain}
|
||||
|
||||
// WritesGrants is what grants says of the part's own grants: written once the list the bus loaded carries a build's
|
||||
// accounts, and the one key the assigner reads (ADR 0571 clause 9, which changes ADR 0547 clause 5).
|
||||
|
||||
@@ -62,7 +62,8 @@
|
||||
"grants_first_view_again",
|
||||
"grants_seat_list",
|
||||
"grants_seat_list_again",
|
||||
"grants_bindings"
|
||||
"grants_bindings",
|
||||
"grants_bindings_again"
|
||||
],
|
||||
"state": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user