The bus module's tools hold no grant of the seat's acting verbs (design 25 § 12.3): nats_create, nats_apply, nats_recreate and nats_drop write a request into the keeper's own state requests, stamped with stdio.CallerOf (a name in the arguments is never the caller), and read its end back. recreate and drop raise the operator's ask first, its option binding exactly that act; the keeper acts only on the router's warrant for it (Warrant.For, Option.Performs), after a snapshot of the entry's streams (bussnap, moved from the snapshot command with a streams filter), says recreated or dropped, and records every act as a hand act through mesh-hand-acts. A drop removes an orphan alone. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
206 lines
7.4 KiB
Go
206 lines
7.4 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"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"
|
|
)
|
|
|
|
// 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 !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_"+r.Verb); err != nil {
|
|
k.logf("the hand act %q could not be recorded: %v", what, err)
|
|
}
|
|
}
|