package keeper import ( "context" "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" "sort" "strings" "sync" "time" occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/membership" ) // Module is the bus module's own name: the publisher of its events and its conditions. const Module = "nats" // The keeper's own states (old `state` sections, ADR 0595 §10): what it last heard per module, and the persons' // requests its tools hand it. const ( StateAsked = "asked" StateRequests = "requests" // KeyStatus is the keeper's own status in StateAsked, read by its health tool: a key no module can have, since a // module's name holds no underscore. KeyStatus = "_keeper" ) // The seat mesh-broker's events (ADR 0595 §6): on the seat's subjects, and kept on the bus module's own stream (ADR 0620 §2). const ( EventCreated = "created" EventRefused = "refused" EventDrifted = "drifted" EventDriftCleared = "drift-cleared" EventFull = "full" EventFullCleared = "full-cleared" EventOrphaned = "orphaned" EventRecreated = "recreated" EventDropped = "dropped" ) // Event is the body of every bus module event, in the field names of the seat mesh-broker's definition (its message // `said`, mesh-contracts go/seats/meshbroker Said): the entry (module, local name, storage type, template, options, // configuration), its bus name, the settings asked and held by bus object, its fill, the snapshot a recreate or drop // took, and why (refused, drifted). Beside the definition's fields: binding, the names a module receives (a ledger's // history, state, indexes and snapshot, which one bus name cannot carry), and revision, the indexer's revision of the // verdict it acted on; both asked of the definition. // // Written here until this module takes mesh-contracts' generated type; the swap changes no byte of the body. type Event struct { V int `json:"v"` Module string `json:"module"` Local string `json:"local-name"` Storage string `json:"storage-type,omitempty"` Template string `json:"template,omitempty"` Options map[string]json.RawMessage `json:"options,omitempty"` Configuration map[string]json.RawMessage `json:"configuration,omitempty"` BusName string `json:"bus-name"` Binding membership.BusBinding `json:"binding"` Asked map[string]Settings `json:"asked,omitempty"` Held map[string]Settings `json:"held,omitempty"` Fill *EventFill `json:"fill,omitempty"` Snapshot string `json:"snapshot,omitempty"` Why string `json:"why,omitempty"` Revision uint64 `json:"revision"` } // EventFill is an entry's fill as an event says it: its messages, its bytes and its cap. type EventFill struct { Count uint64 `json:"count"` Bytes uint64 `json:"bytes"` Cap int64 `json:"cap"` } // Accepted is a manifest-accepted the keeper heard, or a module of its first view: the module, its manifest's digest, // the indexer's revision of the verdict (0 when the event does not say it yet), and its bus-state entries whole. type Accepted struct { Module string `json:"module"` Digest string `json:"digest"` Revision uint64 `json:"revision"` BusState []catalogue.BusStateEntry `json:"bus-state"` } // FirstView is the indexer's answer of indexer_bus_state: every accepted manifest's bus-state entries, by module. type FirstView struct { Revision uint64 `json:"revision"` Modules []Accepted `json:"modules"` } // Asked is what the keeper holds of one module, the value of its key in StateAsked: what its newest accepted manifest // asks for, and every entry the keeper made for it and the bus holds, by local name. type Asked struct { V int `json:"v"` Module string `json:"module"` Digest string `json:"digest,omitempty"` Revision uint64 `json:"revision"` Entries []catalogue.BusStateEntry `json:"entries"` Made map[string]Made `json:"made,omitempty"` At time.Time `json:"at"` } // Made is one entry as the keeper made it: its plan and what stands of it now. type Made struct { Plan Plan `json:"plan"` // Created says every object of it is on the bus and its `created` was said. Created bool `json:"created"` // Refused is why it could not be made, Waiting why it waits to be made again (a refusal the bus may lift, such as a // grant not composed yet). Refused string `json:"refused,omitempty"` Waiting string `json:"waiting,omitempty"` // Drift is how what the bus holds differs from what was asked, in a way that does not grow. Drift string `json:"drift,omitempty"` // Full is whether its fill is past the raise mark, Fill the last fill read, in percent of its cap. Full bool `json:"full,omitempty"` Fill int64 `json:"fill,omitempty"` // Orphaned says no manifest asks for it any more: it goes only by drop. Orphaned bool `json:"orphaned,omitempty"` } // Status is the keeper's own state, KeyStatus: what its health tool reads. type Status struct { Started time.Time `json:"started"` // Pass is when the last fill pass ended. Pass time.Time `json:"pass"` // FirstView is the indexer's first view: when asked, when answered and at which revision, or why not. FirstView FirstViewStatus `json:"first-view"` // Heard is when the last manifest-accepted was heard, and Unanswered since when one has waited unhandled. Heard time.Time `json:"heard,omitempty"` Unanswered time.Time `json:"unanswered,omitempty"` // Unsaid are the events the bus did not take, kept with their id and the subjects still to say them on, and // UnsaidConditions the conditions the condition keeper did not take, by key: each said again at every fill pass, and // named by the keeper's health until it is (design 25 § 12.2: a failed say is never only logged). Unsaid []Saying `json:"unsaid,omitempty"` UnsaidConditions map[string]oc.Condition `json:"unsaid-conditions,omitempty"` } // Saying is one event of the seat mesh-broker as the keeper says it: its name, its one event id (the same on every // subject and every try, so the bus keeps it once), its body, and Left, the subjects it is still to be said on (nil: // every one). Emit says it on Left, and leaves in Left what the bus did not take. type Saying struct { Event string `json:"event"` ID string `json:"id"` Body Event `json:"body"` Left []string `json:"left,omitempty"` // Said are the subjects this try said it on, in order: the seat's subject first. Said []string `json:"said,omitempty"` Error string `json:"error,omitempty"` At time.Time `json:"at"` } // FirstViewStatus is what the keeper holds of its first view. type FirstViewStatus struct { Owner string `json:"owner"` Verb string `json:"verb"` Asked time.Time `json:"asked,omitempty"` Answered time.Time `json:"answered,omitempty"` Revision uint64 `json:"revision"` Error string `json:"error,omitempty"` Next time.Time `json:"next,omitempty"` } // The first view's owner and verb (design 25 § 12.2). const ( FirstViewOwner = "mesh-delivery-indexer" FirstViewVerb = "indexer_bus_state" ) // Bus is the bus as the keeper's own account reaches it. type Bus interface { // Held reads the object's stream: its settings, whether it is there, and its fill (bytes and messages). Held(ctx context.Context, o Object) (Settings, Fill, bool, error) Create(ctx context.Context, o Object) error // Update brings a held object to o's settings: only a difference that grows (Grows), or a recreate's rebuild. Update(ctx context.Context, o Object) error Delete(ctx context.Context, o Object) error // Snapshot takes a snapshot of the streams and says where it is kept. Snapshot(ctx context.Context, streams []string) (string, error) } // Fill is how much one object holds: its bytes and messages. type Fill struct { Bytes uint64 `json:"bytes"` Messages uint64 `json:"messages"` } // ErrRetry marks a refusal the bus may lift without a change of the manifest (a create the keeper's account is not yet // granted): the entry waits and is made again on the backoff, never said refused. var ErrRetry = errors.New("refused for now") // Store is the keeper's own state. type Store interface { Get(state, key string) ([]byte, uint64, bool, error) Put(state, key string, value []byte) (uint64, error) Delete(state, key string) error Keys(state string) ([]string, error) // Create writes a key only when it has none: ErrExists when it has one (a compare-and-set at revision 0). Create(state, key string, value []byte) (uint64, error) } // ErrExists is a Create of a key that has a value. var ErrExists = errors.New("the key has a value already") // Keeper is the bus module's keeper. type Keeper struct { Bus Bus Store Store // Emit says one of the bus module's events (Saying); Say raises or clears a condition through the condition keeper. Emit func(s *Saying) error Say func(oc.Condition) error // View asks the indexer's indexer_bus_state. View func() (FirstView, error) // Act records a hand act through the seat mesh-hand-acts; Warrant reads the router's record of an ask. Act func(what, why, cause string) error Warrant func(askID string) (*oc.Ask, *oc.Warrant, error) Now func() time.Time Logf func(string, ...any) // Raise and Clear are the fill marks in percent (ADR 0595 §6): settings of the bus module, 75 and 70 by default. Raise, Clear int64 mu sync.Mutex status Status viewed bool // unwritten says the condition that the status cannot be written, with words unsaid, is raised. unwritten bool } func (k *Keeper) now() time.Time { if k.Now == nil { return time.Now().UTC() } return k.Now().UTC() } func (k *Keeper) logf(format string, a ...any) { if k.Logf != nil { k.Logf(format, a...) } } func (k *Keeper) marks() (int64, int64) { raise, clear := k.Raise, k.Clear if raise <= 0 { raise = 75 } if clear <= 0 || clear >= raise { clear = raise - 5 } return raise, clear } // Viewed is whether the first view was answered: before it the keeper creates and orphans nothing. func (k *Keeper) Viewed() bool { k.mu.Lock() defer k.mu.Unlock() return k.viewed } // Status is the keeper's status now. func (k *Keeper) Status() Status { k.mu.Lock() defer k.mu.Unlock() return k.status } func (k *Keeper) setStatus(f func(*Status)) { k.mu.Lock() if k.status.Started.IsZero() { k.status.Started = k.now() } k.status.FirstView.Owner, k.status.FirstView.Verb = FirstViewOwner, FirstViewVerb f(&k.status) s := k.status k.mu.Unlock() if k.Store != nil { raw, _ := json.Marshal(s) _, err := k.Store.Put(StateAsked, KeyStatus, raw) if err != nil { k.logf("the keeper's status cannot be written to its own %s: %v", StateAsked, err) } k.statusWritten(s, err) } } // statusWritten raises, once, the keeper's own condition when its status cannot be written while it holds words the bus // did not take: those are then kept only in memory, and a restart loses them (review round 2, seams note 3 and health // note 4: named, not only a log). The next write that stands clears it. func (k *Keeper) statusWritten(s Status, err error) { if k.Say == nil { return } n := len(s.Unsaid) + len(s.UnsaidConditions) k.mu.Lock() raised := k.unwritten switch { case err != nil && n > 0 && !raised: k.unwritten = true case err == nil && raised: k.unwritten = false default: k.mu.Unlock() return } k.mu.Unlock() c := oc.Condition{Key: ConditionKey(conditionUnwritten, Module, "keeper"), Subject: oc.ConditionSubject{Scope: "module", ID: Module}} if err != nil { needs := "a look at why the keeper cannot write its own state " + StateAsked c.State, c.Severity, c.Headline, c.Needs = occ.StateRaised, occ.SeverityWarning, "The bus module may lose words it could not say", needs c.Summary = fmt.Sprintf("the keeper holds %d word(s) the bus did not take and cannot write its status (%v): they are kept only in memory, and a restart of the keeper loses them", n, err) c.Explanation = occ.NeedsYou + needs + ". " + c.Summary c.Resolved = "cleared by the bus module once its status is written again" } else { c.State, c.Resolved = occ.StateCleared, "the keeper's status is written again, with every word it still has to say" } if e := k.Say(c); e != nil { k.logf("the condition %s (%s) could not be said: %v", c.Key, c.State, e) } } // AskFirstView asks the indexer's first view once and applies it (design 25 § 12.2): every module it answers is // applied as an accepted verdict, a module the keeper holds and the view leaves out is kept (a set-aside manifest is // missing from it, never removed), and then check runs over what is held. It answers the error and when to ask again, // on the backoff from 10 s doubling to 5 min; the keeper's health is unhealthy meanwhile, naming the verb. func (k *Keeper) AskFirstView(wait time.Duration) (time.Duration, error) { at := k.now() k.setStatus(func(s *Status) { s.FirstView.Asked = at }) view, err := k.View() if err != nil { next := wait * 2 if wait <= 0 { next = 10 * time.Second } if next > 5*time.Minute { next = 5 * time.Minute } k.setStatus(func(s *Status) { s.FirstView.Error = fmt.Sprintf("%s.%s did not answer: %v", FirstViewOwner, FirstViewVerb, err) s.FirstView.Next = at.Add(next) }) return next, err } k.mu.Lock() k.viewed = true k.mu.Unlock() k.setStatus(func(s *Status) { s.FirstView.Answered, s.FirstView.Revision, s.FirstView.Error, s.FirstView.Next = k.now(), view.Revision, "", time.Time{} }) for _, a := range view.Modules { k.Heard(a) } k.CheckAll() return 0, nil } // Heard applies one accepted verdict (manifest-accepted, or a module of the first view): applied only when newer than // what the keeper holds for that module by the indexer's revision alone, so a replay never takes an entry back (a // verdict that says no revision is applied when its digest differs). Each entry asked is made where the bus lacks it; // each entry made that is no longer asked is an orphan. An empty verdict ("this module asks nothing now") ends the // module's key once nothing it made is held. func (k *Keeper) Heard(a Accepted) { k.setStatus(func(s *Status) { s.Heard = k.now() }) if !k.Viewed() { k.logf("manifest-accepted of %s heard before the first view: kept on the consumer until it is answered", a.Module) return } held, _, found := k.asked(a.Module) if found && a.Revision != 0 && a.Revision <= held.Revision { k.logf("manifest-accepted of %s at revision %d is not newer than the %d held: not applied", a.Module, a.Revision, held.Revision) return } if found && a.Revision == 0 && a.Digest != "" && a.Digest == held.Digest { return } if !found { held = Asked{V: 1, Module: a.Module} } if held.Made == nil { held.Made = map[string]Made{} } held.Digest, held.Entries, held.At = a.Digest, a.BusState, k.now() if a.Revision > held.Revision { held.Revision = a.Revision } asked := map[string]bool{} for _, e := range a.BusState { asked[e.Name] = true k.makeEntry(&held, e) } for _, local := range sortedKeys(held.Made) { m := held.Made[local] if asked[local] || m.Orphaned { continue } if !m.Created { // Never made: nothing on the bus to orphan. delete(held.Made, local) k.clear(conditionRefused, a.Module, local) continue } m.Orphaned = true held.Made[local] = m k.emit(EventOrphaned, m.Plan, held.Revision, "no accepted manifest of "+a.Module+" asks for it now", nil, nil) k.raise(conditionOrphaned, a.Module, local, occ.SeverityWarning, "Kept data no manifest asks for", "the operator's drop of it, or a manifest asking for it again", fmt.Sprintf("%s of %s is on the bus and no accepted manifest asks for it: it goes only by the bus module's drop, on your warrant (nats_drop), or a manifest asking for it again keeps it", BusName(a.Module, local), a.Module)) } k.write(held) } // makeEntry makes one asked entry where the bus lacks it, compares it where it holds it, and says what it did. func (k *Keeper) makeEntry(held *Asked, e catalogue.BusStateEntry) { module := held.Module plan, err := PlanOf(module, e) m := held.Made[e.Name] if err != nil { if m.Refused != err.Error() { k.emit(EventRefused, Plan{Module: module, Local: e.Name, Storage: e.Storage, Template: e.Template}, held.Revision, err.Error(), nil, nil) k.raise(conditionRefused, module, e.Name, occ.SeverityWarning, "The bus module refused to create kept data", "a manifest that asks for it in a form the bus module makes", fmt.Sprintf("%s of %s was not created: %v. The module gets no binding for it and fails at its start, naming it", e.Name, module, err)) } m.Refused, m.Waiting = err.Error(), "" held.Made[e.Name] = m return } wasOrphan := m.Orphaned m.Plan, m.Orphaned = plan, false if wasOrphan { // Asked again: kept, and its binding said again with created (design 25 § 12.3). m.Created = false k.clear(conditionOrphaned, module, e.Name) } k.ensure(held, &m) held.Made[e.Name] = m } // ensure brings one planned entry onto the bus: each object the bus lacks created, a held one compared. A difference // that only grows is applied; any other is a drift, said and never fixed quietly. func (k *Keeper) ensure(held *Asked, m *Made) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() asked, holds := map[string]Settings{}, map[string]Settings{} var drifts []string for _, o := range m.Plan.Objects { asked[o.Name] = o.Settings s, _, found, err := k.Bus.Held(ctx, o) if err != nil { m.Waiting = fmt.Sprintf("%s cannot be read: %v", o.Stream(), err) return } if !found { if err := k.Bus.Create(ctx, o); err != nil { if errors.Is(err, ErrRetry) { m.Waiting = fmt.Sprintf("%s cannot be created yet: %v", o.Stream(), err) return } m.Refused, m.Waiting = fmt.Sprintf("%s: %v", o.Stream(), err), "" k.emit(EventRefused, m.Plan, held.Revision, m.Refused, asked, nil) k.raise(conditionRefused, held.Module, m.Plan.Local, occ.SeverityWarning, "The bus module refused to create kept data", "a look at why the bus refused it", fmt.Sprintf("%s of %s was not created: %s. The module gets no binding for it and fails at its start, naming it", m.Plan.Local, held.Module, m.Refused)) return } holds[o.Name] = o.Settings continue } holds[o.Name] = s if Differs(o.Settings, s) == "" { continue } if Grows(o.Settings, s) { if err := k.Bus.Update(ctx, o); err != nil { m.Waiting = fmt.Sprintf("%s's larger settings cannot be applied yet: %v", o.Stream(), err) return } holds[o.Name] = o.Settings continue } drifts = append(drifts, o.Stream()+": "+Differs(o.Settings, s)) } wasRefused := m.Refused != "" m.Waiting, m.Refused = "", "" if wasRefused { k.clear(conditionRefused, held.Module, m.Plan.Local) } if !m.Created { // Marked created only once `created` is said whole: the grants part binds on it, so one the bus did not take is // said again on the backoff (Waiting), never marked made and forgotten. if err := k.sayNow(EventCreated, m.Plan, held.Revision, "", asked, holds); err != nil { m.Waiting = fmt.Sprintf("its created could not be said yet: %v", err) return } m.Created = true } drift := strings.Join(drifts, "; ") switch { case drift != "" && drift != m.Drift: m.Drift = drift k.emit(EventDrifted, m.Plan, held.Revision, drift, asked, holds) k.raise(conditionDrifted, held.Module, m.Plan.Local, occ.SeverityWarning, "Kept data differs from what was asked", "a decision: change the manifest back, or the bus module's recreate on your warrant", fmt.Sprintf("%s of %s differs from what its manifest asks, in a way that does not only grow: %s", m.Plan.Local, held.Module, drift)) case drift == "" && m.Drift != "": m.Drift = "" k.emit(EventDriftCleared, m.Plan, held.Revision, "", asked, holds) k.clear(conditionDrifted, held.Module, m.Plan.Local) } } // CheckAll compares every entry the keeper holds with the bus, making what is missing (design 25 § 12.2's check at // start) and making again what waits; it answers what it found, one line per entry. func (k *Keeper) CheckAll() []CheckLine { var out []CheckLine keys, err := k.Store.Keys(StateAsked) if err != nil { return []CheckLine{{Why: "the keeper's own " + StateAsked + " cannot be read: " + err.Error()}} } for _, module := range keys { if module == KeyStatus { continue } held, _, found := k.asked(module) if !found { continue } for _, local := range sortedKeys(held.Made) { m := held.Made[local] if m.Orphaned || m.Plan.Local == "" { out = append(out, lineOf(module, local, m)) continue } for _, e := range held.Entries { if e.Name == local { k.makeEntry(&held, e) } } out = append(out, lineOf(module, local, held.Made[local])) } k.write(held) } return out } // CheckLine is one entry as check answers it. type CheckLine struct { Module string `json:"module,omitempty"` Local string `json:"local,omitempty"` Bus string `json:"bus,omitempty"` Created bool `json:"created"` Refused string `json:"refused,omitempty"` Waiting string `json:"waiting,omitempty"` Drift string `json:"drift,omitempty"` Orphaned bool `json:"orphaned,omitempty"` Full bool `json:"full,omitempty"` Fill int64 `json:"fill-percent,omitempty"` Why string `json:"why,omitempty"` } func lineOf(module, local string, m Made) CheckLine { return CheckLine{Module: module, Local: local, Bus: BusName(module, local), Created: m.Created, Refused: m.Refused, Waiting: m.Waiting, Drift: m.Drift, Orphaned: m.Orphaned, Full: m.Full, Fill: m.Fill} } // Waiting is every entry that waits to be made again, ` : `: what the backoff retries. func (k *Keeper) Waiting() []string { var out []string keys, _ := k.Store.Keys(StateAsked) for _, module := range keys { held, _, found := k.asked(module) if !found { continue } for _, local := range sortedKeys(held.Made) { if w := held.Made[local].Waiting; w != "" { out = append(out, module+" "+local+": "+w) } } } return out } // The kinds of condition the keeper raises, each naming the module and the entry (design 25 § 12.3). const ( conditionFull = "full" conditionDrifted = "drifted" conditionOrphaned = "orphaned" conditionRefused = "refused" // conditionUnwritten is the keeper's own: its status, with words unsaid, cannot be written. conditionUnwritten = "status-unwritten" ) // ConditionKey is the key of one of the keeper's conditions: `nats.._`. func ConditionKey(kind, module, local string) string { return Module + "." + kind + "." + BusName(module, local) } func (k *Keeper) raise(kind, module, local, severity, headline, needs, explanation string) { if k.Say == nil { return } c := oc.Condition{Key: ConditionKey(kind, module, local), Subject: oc.ConditionSubject{Scope: "module", ID: module}, State: occ.StateRaised, Severity: severity, Headline: headline, Needs: needs, Summary: explanation, Explanation: occ.NeedsYou + needs + ". " + explanation, Resolved: "cleared by the bus module once it no longer holds"} k.sayCondition(c) } func (k *Keeper) clear(kind, module, local string) { if k.Say == nil { return } c := oc.Condition{Key: ConditionKey(kind, module, local), Subject: oc.ConditionSubject{Scope: "module", ID: module}, State: occ.StateCleared} k.sayCondition(c) } // sayCondition says c; one the condition keeper did not take is kept in the keeper's status, replacing an older word on // its key, and said again at the next fill pass. func (k *Keeper) sayCondition(c oc.Condition) { err := k.Say(c) k.setStatus(func(s *Status) { if err == nil { delete(s.UnsaidConditions, c.Key) return } if s.UnsaidConditions == nil { s.UnsaidConditions = map[string]oc.Condition{} } s.UnsaidConditions[c.Key] = c }) if err != nil { k.logf("the condition %s (%s) could not be said, said again at the next fill pass: %v", c.Key, c.State, err) } } // SayUnsaid says again every event and condition the bus did not take (the fill pass calls it): an event on the // subjects it is still to be said on, with its one id. func (k *Keeper) SayUnsaid() { s := k.Status() var still []Saying for _, u := range s.Unsaid { u := u if err := k.Emit(&u); err != nil { u.Error = err.Error() still = append(still, u) k.logf("the event %s of %s %s (%s) could not be said again: %v", u.Event, u.Body.Module, u.Body.Local, u.ID, err) } } for _, key := range sortedKeys(s.UnsaidConditions) { k.sayCondition(s.UnsaidConditions[key]) } k.setStatus(func(st *Status) { // Kept: what was not said now, and what was added meanwhile. added := st.Unsaid[min(len(s.Unsaid), len(st.Unsaid)):] st.Unsaid = append(still, added...) }) } // emit says one event; one the bus did not take whole is kept in the keeper's status and said again, on the subjects // left, at the next fill pass. func (k *Keeper) emit(event string, p Plan, revision uint64, why string, asked, held map[string]Settings) { if k.Emit == nil { return } s := k.saying(event, p, revision, why, asked, held) if err := k.Emit(&s); err != nil { s.Error = err.Error() k.setStatus(func(st *Status) { st.Unsaid = append(st.Unsaid, s) }) k.logf("the event %s of %s %s could not be said, said again at the next fill pass: %v", event, p.Module, p.Local, err) } } // sayNow says one event and answers an error when the bus took none of it, so its caller says it again itself. One said // on the seat's subject and not on its history is said: what is left is kept with its one id and said again at the next // fill pass on the subjects left, never said again with a new id (review round 2, health note 3). func (k *Keeper) sayNow(event string, p Plan, revision uint64, why string, asked, held map[string]Settings) error { if k.Emit == nil { return nil } s := k.saying(event, p, revision, why, asked, held) err := k.Emit(&s) if err == nil { return nil } if len(s.Said) > 0 { s.Error = err.Error() k.setStatus(func(st *Status) { st.Unsaid = append(st.Unsaid, s) }) k.logf("the event %s of %s %s was said on %v and not on %v: the rest said again at the next fill pass: %v", event, p.Module, p.Local, s.Said, s.Left, err) return nil } k.logf("the event %s of %s %s could not be said: %v", event, p.Module, p.Local, err) return err } func (k *Keeper) saying(event string, p Plan, revision uint64, why string, asked, held map[string]Settings) Saying { body := Event{V: 1, Module: p.Module, Local: p.Local, Storage: p.Storage, Template: p.Template, Options: p.Entry.Options, Configuration: p.Entry.Configuration, BusName: BusName(p.Module, p.Local), Binding: p.Binding, Asked: asked, Held: held, Why: why, Revision: revision} if event == EventDropped { // A dropped entry's names only: the binding to remove. body.Binding.Types = nil } return Saying{Event: event, ID: EventID(), Body: body, At: k.now()} } // EventID is a new event id: sixteen random bytes in hex. func EventID() string { var b [16]byte _, _ = rand.Read(b[:]) return hex.EncodeToString(b[:]) } func (k *Keeper) asked(module string) (Asked, uint64, bool) { raw, rev, found, err := k.Store.Get(StateAsked, module) if err != nil || !found { return Asked{}, 0, false } var a Asked if json.Unmarshal(raw, &a) != nil { return Asked{}, 0, false } return a, rev, true } // write keeps a module's key, or removes it when its manifest asks for nothing and nothing it made is held: a key goes // only so, never because a module is missing from a first view. func (k *Keeper) write(a Asked) { if len(a.Entries) == 0 && len(a.Made) == 0 { if err := k.Store.Delete(StateAsked, a.Module); err != nil { k.logf("the key of %s cannot be removed from its own %s: %v", a.Module, StateAsked, err) } return } raw, _ := json.Marshal(a) if _, err := k.Store.Put(StateAsked, a.Module, raw); err != nil { k.logf("the key of %s cannot be written to its own %s: %v", a.Module, StateAsked, err) } } // Held is every module the keeper holds, by name: what list, show and orphans answer from. func (k *Keeper) Held() map[string]Asked { out := map[string]Asked{} keys, _ := k.Store.Keys(StateAsked) for _, module := range keys { if module == KeyStatus { continue } if a, _, found := k.asked(module); found { out[module] = a } } return out } func sortedKeys[V any](m map[string]V) []string { out := make([]string, 0, len(m)) for key := range m { out = append(out, key) } sort.Strings(out) return out }