Review round 1 of the registry milestone (design 25 § 12.2, § 12.3, § 12.7): - the fill pass runs from the start, while the first view is asked; - recreate and drop wake on the operator channel's decided on the keeper's durable consumer, never a minute's tick; - a warrant is spent by compare-and-set (acted.<ask id> in requests) before anything is deleted; a second request on it is refused; a request whose ask ended unanswered is ended, said; the ask is raised before the request is handed; - created is marked only once said; an event or a condition the bus did not take is kept, named by the health and said again at the next fill pass with its one id, on the subjects left (the double publish); - the health judges the first view past its bound and never skips an unread consumer backlog; - an unrecordable hand act is said on the request; nats_requests reads what waits, what ended and each warrant spent, and drops a waiting request by hand; a tool waits for its request's end on one watch of requests; - the whole-module test reads the warrant from the router's record under a process connection. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
295 lines
12 KiB
Go
295 lines
12 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
|
|
)
|
|
|
|
// A person's request (design 25 § 12.3; CAT-N8): the bus module's tools hold no grant of the seat's acting verbs, so
|
|
// each hands its request to the keeper through the keeper's own state requests, stamped with the caller the tool runner
|
|
// proved (never a name the request's body gives), and reads the keeper's answer back from the same key. create and
|
|
// apply act at once; recreate and drop only once the operator's warrant on the ask the tool raised is held, after a
|
|
// snapshot of the entry's streams. Every act is recorded as a hand act.
|
|
|
|
// The verbs a request names.
|
|
const (
|
|
VerbCreate = "create"
|
|
VerbApply = "apply"
|
|
VerbRecreate = "recreate"
|
|
VerbDrop = "drop"
|
|
// VerbFirstViewAgain asks the indexer's first view at once, by hand (design 25 § 12.2), beside the backoff.
|
|
VerbFirstViewAgain = "first-view-again"
|
|
)
|
|
|
|
// Request is one person's request, the value of its key in StateRequests.
|
|
type Request struct {
|
|
ID string `json:"id"`
|
|
Verb string `json:"verb"`
|
|
Module string `json:"module"`
|
|
Local string `json:"local"`
|
|
Why string `json:"why"`
|
|
// Caller is who asked, as the tool runner proved it (stdio.CallerOf), written by the tool.
|
|
Caller string `json:"caller"`
|
|
At time.Time `json:"at"`
|
|
// Ask is the id of the operator's ask the tool raised for a recreate or a drop; the keeper acts only on its warrant.
|
|
Ask string `json:"ask,omitempty"`
|
|
// Done is when the keeper ended it, Outcome what it did, Error why not.
|
|
Done time.Time `json:"done,omitempty"`
|
|
Outcome string `json:"outcome,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Snapshot string `json:"snapshot,omitempty"`
|
|
// Waiting is why it waits, while it waits: the operator's warrant, or the keeper's first view.
|
|
Waiting string `json:"waiting,omitempty"`
|
|
// Unrecorded is why the hand act of what it did could not be recorded, when it could not: said, never only logged.
|
|
Unrecorded string `json:"unrecorded,omitempty"`
|
|
}
|
|
|
|
// AskLasts is how long the operator's ask for a recreate or a drop waits for an answer: shorter than the day the state
|
|
// requests keeps a key, so a request whose ask ended unanswered is ended by the keeper, saying so, before its key ages
|
|
// out, and the warrant's acted key (ActedKey) outlives the ask it spends.
|
|
const AskLasts = 12 * time.Hour
|
|
|
|
// ActedPrefix starts the key in StateRequests that spends one warrant, `acted.<ask id>`, claimed by compare-and-set
|
|
// before a recreate or a drop deletes anything (design 25 § 12.3: a warrant acts once). A request id holds no dot.
|
|
const ActedPrefix = "acted."
|
|
|
|
// ActedKey is the key spending the warrant on ask.
|
|
func ActedKey(ask string) string { return ActedPrefix + ask }
|
|
|
|
// Acted is the value of an acted key: which request spent the warrant, on what.
|
|
type Acted struct {
|
|
Ask string `json:"ask"`
|
|
Request string `json:"request"`
|
|
Verb string `json:"verb"`
|
|
Module string `json:"module"`
|
|
Local string `json:"local"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// Act is what an option of the operator's ask binds for a recreate or a drop: the verb and the entry (occ.ActDigest).
|
|
func (r Request) Act() occ.Act {
|
|
return occ.Act{"verb": r.Verb, "module": r.Module, "local": r.Local}
|
|
}
|
|
|
|
// ErrNoWarrant is a request that waits for the operator's word: kept, acted on once the warrant is held.
|
|
var ErrNoWarrant = errors.New("the operator has not answered yet")
|
|
|
|
// ActOnRequests acts on every request not ended yet, oldest first, and writes each one's end back to its key.
|
|
func (k *Keeper) ActOnRequests() {
|
|
keys, err := k.Store.Keys(StateRequests)
|
|
if err != nil {
|
|
k.logf("the persons' requests cannot be read: %v", err)
|
|
return
|
|
}
|
|
for _, key := range keys {
|
|
if strings.HasPrefix(key, ActedPrefix) {
|
|
continue
|
|
}
|
|
raw, _, found, err := k.Store.Get(StateRequests, key)
|
|
if err != nil || !found {
|
|
continue
|
|
}
|
|
var r Request
|
|
if json.Unmarshal(raw, &r) != nil || !r.Done.IsZero() {
|
|
continue
|
|
}
|
|
outcome, err := k.act(&r)
|
|
if errors.Is(err, ErrNoWarrant) {
|
|
// Said on the request while it waits, once (its write wakes the watch, which then finds the same words).
|
|
if r.Waiting != err.Error() {
|
|
r.Waiting = err.Error()
|
|
data, _ := json.Marshal(r)
|
|
if _, err := k.Store.Put(StateRequests, key, data); err != nil {
|
|
k.logf("why the request %s waits cannot be written: %v", key, err)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
r.Waiting = ""
|
|
r.Done, r.Outcome = k.now(), outcome
|
|
if err != nil {
|
|
r.Error = err.Error()
|
|
}
|
|
data, _ := json.Marshal(r)
|
|
if _, err := k.Store.Put(StateRequests, key, data); err != nil {
|
|
k.logf("the end of the request %s cannot be written: %v", key, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (k *Keeper) act(r *Request) (string, error) {
|
|
if r.Verb == VerbFirstViewAgain {
|
|
_, err := k.AskFirstView(0)
|
|
s := k.Status().FirstView
|
|
k.record(r, fmt.Sprintf("asked %s.%s again by hand: answered %v at revision %d", s.Owner, s.Verb, err == nil, s.Revision))
|
|
if err != nil {
|
|
return "asked, not answered", err
|
|
}
|
|
return fmt.Sprintf("the first view answered at revision %d", s.Revision), nil
|
|
}
|
|
if !k.Viewed() {
|
|
return "", fmt.Errorf("%w: the keeper holds no first view yet, so it acts on nothing", ErrNoWarrant)
|
|
}
|
|
held, _, found := k.asked(r.Module)
|
|
if !found {
|
|
return "nothing done", fmt.Errorf("the bus module holds no entry of %s", r.Module)
|
|
}
|
|
m, made := held.Made[r.Local]
|
|
asked := false
|
|
for _, e := range held.Entries {
|
|
asked = asked || e.Name == r.Local
|
|
}
|
|
switch r.Verb {
|
|
case VerbCreate, VerbApply:
|
|
if !asked {
|
|
return "nothing done", fmt.Errorf("no accepted manifest of %s asks for %s: the bus module creates only what one asks", r.Module, r.Local)
|
|
}
|
|
for _, e := range held.Entries {
|
|
if e.Name == r.Local {
|
|
k.makeEntry(&held, e)
|
|
}
|
|
}
|
|
k.write(held)
|
|
m = held.Made[r.Local]
|
|
k.record(r, fmt.Sprintf("%s of %s's %s: created %v, drift %q, waiting %q", r.Verb, r.Module, r.Local, m.Created, m.Drift, m.Waiting))
|
|
if m.Refused != "" || m.Waiting != "" {
|
|
return "not made", fmt.Errorf("%s%s", m.Refused, m.Waiting)
|
|
}
|
|
return fmt.Sprintf("%s: the bus holds %s as asked%s", r.Verb, BusName(r.Module, r.Local), map[bool]string{true: ", but for a drift a person decides: " + m.Drift}[m.Drift != ""]), nil
|
|
case VerbRecreate, VerbDrop:
|
|
default:
|
|
return "nothing done", fmt.Errorf("%q is no verb a person asks the bus module for: create, apply, recreate or drop", r.Verb)
|
|
}
|
|
if !made || !m.Created {
|
|
return "nothing done", fmt.Errorf("the bus module made no %s of %s", r.Local, r.Module)
|
|
}
|
|
if r.Verb == VerbDrop && !m.Orphaned {
|
|
return "nothing done", fmt.Errorf("%s of %s is asked for by its manifest: drop removes an orphan alone", r.Local, r.Module)
|
|
}
|
|
if r.Verb == VerbRecreate && m.Orphaned {
|
|
return "nothing done", fmt.Errorf("%s of %s is an orphan: drop it, or ask for it in a manifest again", r.Local, r.Module)
|
|
}
|
|
if err := k.warranted(r); err != nil {
|
|
return "nothing done", err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
defer cancel()
|
|
var streams []string
|
|
for _, o := range m.Plan.Objects {
|
|
streams = append(streams, o.Stream())
|
|
}
|
|
path, err := k.Bus.Snapshot(ctx, streams)
|
|
if err != nil {
|
|
return "nothing done", fmt.Errorf("the snapshot before %s failed, so nothing was removed: %w", r.Verb, err)
|
|
}
|
|
r.Snapshot = path
|
|
// The warrant is spent before anything is deleted: of two requests naming one ask, only the one whose claim stands acts.
|
|
if err := k.spend(r); err != nil {
|
|
return "nothing done", err
|
|
}
|
|
for _, o := range m.Plan.Objects {
|
|
if err := k.Bus.Delete(ctx, o); err != nil {
|
|
return "partly done", fmt.Errorf("%s could not be removed after its snapshot at %s: %w", o.Stream(), path, err)
|
|
}
|
|
}
|
|
if r.Verb == VerbDrop {
|
|
delete(held.Made, r.Local)
|
|
k.write(held)
|
|
k.emit(EventDropped, m.Plan, held.Revision, r.Why, nil, nil)
|
|
k.clear(conditionOrphaned, r.Module, r.Local)
|
|
k.record(r, fmt.Sprintf("dropped %s of %s on the operator's warrant (snapshot %s)", r.Local, r.Module, path))
|
|
return fmt.Sprintf("dropped %s, its snapshot at %s", BusName(r.Module, r.Local), path), nil
|
|
}
|
|
for _, o := range m.Plan.Objects {
|
|
if err := k.Bus.Create(ctx, o); err != nil {
|
|
m.Created, m.Refused = false, fmt.Sprintf("%s: %v", o.Stream(), err)
|
|
held.Made[r.Local] = m
|
|
k.write(held)
|
|
return "partly done", fmt.Errorf("%s was removed and could not be made again (its snapshot at %s): %w", o.Stream(), path, err)
|
|
}
|
|
}
|
|
wasDrift := m.Drift != ""
|
|
m.Drift = ""
|
|
held.Made[r.Local] = m
|
|
k.write(held)
|
|
k.emit(EventRecreated, m.Plan, held.Revision, r.Why, nil, nil)
|
|
if wasDrift {
|
|
k.clear(conditionDrifted, r.Module, r.Local)
|
|
}
|
|
k.record(r, fmt.Sprintf("recreated %s of %s on the operator's warrant (snapshot %s)", r.Local, r.Module, path))
|
|
return fmt.Sprintf("recreated %s as asked, its old data's snapshot at %s", BusName(r.Module, r.Local), path), nil
|
|
}
|
|
|
|
// warranted holds a recreate or a drop to the operator's warrant on the ask its tool raised: chosen by the operator,
|
|
// on that ask, binding exactly this act.
|
|
func (k *Keeper) warranted(r *Request) error {
|
|
if r.Ask == "" || k.Warrant == nil {
|
|
return fmt.Errorf("a %s acts only on the operator's warrant, and this request names no ask", r.Verb)
|
|
}
|
|
ask, w, err := k.Warrant(r.Ask)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: the router's record of the ask %s cannot be read now: %v", ErrNoWarrant, r.Ask, err)
|
|
}
|
|
// An ask that ended unanswered ends its request, said: never a request that waits until its key ages out. And a
|
|
// warrant is not acted on once its ask ended, so a request handed after its acted key aged out acts on nothing.
|
|
var expires time.Time
|
|
if !r.At.IsZero() {
|
|
expires = r.At.Add(AskLasts)
|
|
}
|
|
if ask != nil {
|
|
if t, err := occ.Time(ask.Expires); err == nil && !t.IsZero() {
|
|
expires = t
|
|
}
|
|
}
|
|
if !expires.IsZero() && k.now().After(expires) {
|
|
return fmt.Errorf("the operator's ask %s ended at %s with no warrant acted on, so nothing is removed: ask again with a new request",
|
|
r.Ask, expires.UTC().Format(time.RFC3339))
|
|
}
|
|
if ask == nil || w == nil {
|
|
return ErrNoWarrant
|
|
}
|
|
o, err := occ.For(*w, Module, *ask)
|
|
if err != nil {
|
|
return fmt.Errorf("no warrant: %w", err)
|
|
}
|
|
if o.Level != occ.Destroy {
|
|
return fmt.Errorf("the operator chose %s, so nothing is removed", o.Label)
|
|
}
|
|
return occ.Performs(o, r.Act())
|
|
}
|
|
|
|
// spend claims the warrant of r's ask by compare-and-set; a warrant already spent is refused, naming what it did.
|
|
func (k *Keeper) spend(r *Request) error {
|
|
raw, _ := json.Marshal(Acted{Ask: r.Ask, Request: r.ID, Verb: r.Verb, Module: r.Module, Local: r.Local, At: k.now()})
|
|
_, err := k.Store.Create(StateRequests, ActedKey(r.Ask), raw)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !errors.Is(err, ErrExists) {
|
|
return fmt.Errorf("the warrant of the ask %s cannot be spent now, so nothing was removed: %w", r.Ask, err)
|
|
}
|
|
var a Acted
|
|
if got, _, found, _ := k.Store.Get(StateRequests, ActedKey(r.Ask)); found {
|
|
_ = json.Unmarshal(got, &a)
|
|
}
|
|
return fmt.Errorf("the operator's warrant on the ask %s already acted (%s of %s's %s, request %s, at %s): a warrant acts once, so nothing was removed",
|
|
r.Ask, a.Verb, a.Module, a.Local, a.Request, a.At.Format(time.RFC3339))
|
|
}
|
|
|
|
// record records what a request did as a hand act; one that cannot be recorded is said on the request's end.
|
|
func (k *Keeper) record(r *Request, what string) {
|
|
if k.Act == nil {
|
|
return
|
|
}
|
|
if err := k.Act(what+", asked by "+r.Caller, r.Why, "nats_"+strings.ReplaceAll(r.Verb, "-", "_")); err != nil {
|
|
r.Unrecorded = err.Error()
|
|
k.logf("the hand act %q could not be recorded: %v", what, err)
|
|
}
|
|
}
|