The bus module's tool hands the ask to the keeper through its own state requests (design 25 § 12.2), as the caller the tool runner proved; the keeper asks indexer_bus_state at once beside its backoff, applies what it answers, records the hand act and writes back what it answered and at which revision. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
218 lines
7.9 KiB
Go
218 lines
7.9 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// Act is what an option of the operator's ask binds for a recreate or a drop: the verb and the entry (asks.ActDigest).
|
|
func (r Request) Act() asks.Act {
|
|
return asks.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 {
|
|
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) {
|
|
continue
|
|
}
|
|
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
|
|
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)
|
|
}
|
|
if ask == nil || w == nil {
|
|
return ErrNoWarrant
|
|
}
|
|
o, err := w.For(Module, *ask)
|
|
if err != nil {
|
|
return fmt.Errorf("no warrant: %w", err)
|
|
}
|
|
if o.Level != asks.Destroy {
|
|
return fmt.Errorf("the operator chose %s, so nothing is removed", o.Label)
|
|
}
|
|
return o.Performs(r.Act())
|
|
}
|
|
|
|
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 {
|
|
k.logf("the hand act %q could not be recorded: %v", what, err)
|
|
}
|
|
}
|