StatusTools takes the reader's handacts.Recorder: again records the act through seat:mesh-hand-acts.hand-act before it writes the hand ask, and refuses, asking nothing, when the seat does not take it, as the stdio Reader's again does. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
153 lines
6.3 KiB
Go
153 lines
6.3 KiB
Go
package views
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/handacts"
|
|
)
|
|
|
|
// StatusState is a reader's own state of its first views (design 25 § 12.2): at StatusKey each first view's Status, as
|
|
// its process last said it, and at AgainKey a hand ask of its re-read tool, which the process takes and deletes. A
|
|
// process serves no tool (ADR 0436), so its read and re-read tools, in its tools bundle, meet it here.
|
|
const StatusState = "first-views"
|
|
|
|
// StatusKey is where a reader keeps its first view of owner's state.
|
|
func StatusKey(owner, state string) string { return owner + "." + state }
|
|
|
|
// AgainKey is where a re-read tool asks a reader's process to ask owner's first view of state again.
|
|
func AgainKey(owner, state string) string { return "again." + owner + "." + state }
|
|
|
|
// HandAsk is one ask of a re-read tool: who asked (the runtime's proof), why and when.
|
|
type HandAsk struct {
|
|
V int `json:"v"`
|
|
Owner string `json:"owner"`
|
|
State string `json:"state"`
|
|
Why string `json:"why"`
|
|
By string `json:"by"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// KeepStatus is a Follower's Said that writes its status at StatusKey of kv (the reader's StatusState).
|
|
func KeepStatus(ctx context.Context, kv jetstream.KeyValue, logf func(string, ...any)) func(Status) {
|
|
return func(s Status) {
|
|
data, err := json.Marshal(s)
|
|
if err == nil {
|
|
_, err = kv.Put(ctx, StatusKey(s.Owner, s.State), data)
|
|
}
|
|
if err != nil && logf != nil {
|
|
logf("views: the status of the first view %s is not kept: %v", s.Tool, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HearAgain takes each hand ask written at an AgainKey of kv, those standing at the start included, asks that
|
|
// follower's first view again with the ask's words and who asked, and deletes the ask. It returns once the watch is set.
|
|
func HearAgain(ctx context.Context, kv jetstream.KeyValue, followers []*Follower, logf func(string, ...any)) error {
|
|
w, err := kv.Watch(ctx, "again.>")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
defer func() { _ = w.Stop() }()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case e, ok := <-w.Updates():
|
|
if !ok {
|
|
return
|
|
}
|
|
if e == nil || e.Operation() != jetstream.KeyValuePut {
|
|
continue
|
|
}
|
|
var a HandAsk
|
|
if err := json.Unmarshal(e.Value(), &a); err != nil {
|
|
logf("views: the hand ask %s is not read: %v", e.Key(), err)
|
|
_ = kv.Delete(ctx, e.Key())
|
|
continue
|
|
}
|
|
i := slices.IndexFunc(followers, func(f *Follower) bool { return f.Owner == a.Owner && f.State == a.State })
|
|
if i < 0 {
|
|
logf("views: the hand ask %s names no first view this part holds", e.Key())
|
|
_ = kv.Delete(ctx, e.Key())
|
|
continue
|
|
}
|
|
logf("views: %s asks the first view %s again by hand: %s", a.By, followers[i].Tool, a.Why)
|
|
_ = kv.Delete(ctx, e.Key(), jetstream.LastRevision(e.Revision()))
|
|
f := followers[i]
|
|
go func() { _ = f.Again(ctx, a.Why+" ("+a.By+")") }()
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// StatusTools are a reader's read tool of its first views (read) and its re-read tool (again), in its tools bundle,
|
|
// over its StatusState: read lists what the process last said of each; again writes a hand ask the process takes,
|
|
// naming the caller the runtime proved, and is recorded in the status's by-hand count and why. again is a hand act:
|
|
// acts records it through the seat mesh-hand-acts before the ask is written, and one the seat does not take is said and
|
|
// nothing is asked (the zero Recorder records nothing, so again refuses).
|
|
func StatusTools(reader, read, again string, st stdio.KeptState, acts handacts.Recorder, now func() time.Time) []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{Name: read,
|
|
Description: fmt.Sprintf("%s's first views of the indexer's and the assigner's states (novox/hq ADR 0615): per owner and state, "+
|
|
"the tool it asks, the revision its picture held when last answered, when it asked and was answered, how many asks "+
|
|
"(by hand among them) and whether its condition (unanswered past two minutes) is raised.", reader),
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
|
Run: func(map[string]any) (any, error) {
|
|
keys, err := st.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the state %s cannot be listed: %w", StatusState, err)
|
|
}
|
|
out := []Status{}
|
|
for _, k := range keys {
|
|
if strings.HasPrefix(k, "again.") {
|
|
continue
|
|
}
|
|
e, err := st.Get(k)
|
|
if err != nil || e == nil {
|
|
continue
|
|
}
|
|
var s Status
|
|
if json.Unmarshal(e.Value, &s) == nil {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return map[string]any{"first-views": out}, nil
|
|
}},
|
|
{Name: again,
|
|
Description: fmt.Sprintf("Ask %s's first view of an owner's state again now, by hand (a hand act, its caller and why kept in "+
|
|
"the status %s reads): for a picture a person doubts. The part asks once and follows the owner's events after the answer.", reader, read),
|
|
Input: map[string]any{"type": "object", "required": []string{"owner", "state", "why"}, "properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "the owner: mesh-delivery-indexer or mesh-delivery-assigner"},
|
|
"state": map[string]any{"type": "string", "description": "the owner's state, as the read tool names it"},
|
|
"why": map[string]any{"type": "string", "description": "why it is asked again, in plain words"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
owner, _ := args["owner"].(string)
|
|
state, _ := args["state"].(string)
|
|
why, _ := args["why"].(string)
|
|
if owner == "" || state == "" || strings.TrimSpace(why) == "" {
|
|
return nil, errors.New("again names the owner, the state and why")
|
|
}
|
|
a := HandAsk{V: Version, Owner: owner, State: state, Why: strings.TrimSpace(why), By: stdio.CallerOf(args), At: now().UTC()}
|
|
if _, err := acts.Record(handacts.Request{What: fmt.Sprintf("asked %s's first view of %s's %s again by hand", reader, owner, state),
|
|
Why: a.Why, Cause: "first-view-again", Verb: again}); err != nil {
|
|
return nil, fmt.Errorf("the hand act was not recorded through mesh-hand-acts, so nothing was asked again: %w", err)
|
|
}
|
|
if _, err := st.Put(AgainKey(owner, state), a); err != nil {
|
|
return nil, fmt.Errorf("the hand ask is not written: %w", err)
|
|
}
|
|
return map[string]any{"asked": a, "read": read}, nil
|
|
}},
|
|
}
|
|
}
|