The indexer's tools bundle runs in a runtime of its own on its own account (runs-as) and serves the first-view tools manifests, modules and verdicts, each answering only the callers its manifest writes (ADR 0621 §1-§2, ADR 0622 §1), and the nine read tools of design 25 § 12.2. The process says each change of its ten read states on the family indexer-views in its old streams section (7 days, 256 MiB), with a mark per state the first view answers its revision from, so a reader that takes the answer misses no event after it (ADR 0615 §4-§5). The shared shape lives in sdk/views: event, answer, mark, the emitter, the tools and the reader's follower. Issue 1966. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
160 lines
6.6 KiB
Go
160 lines
6.6 KiB
Go
package views
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// KeptSource is a state as a tools bundle reads it through its runtime (stdio.KeptState), as a Source.
|
|
type KeptSource struct{ State stdio.KeptState }
|
|
|
|
// Keys is every key with a value.
|
|
func (k KeptSource) Keys() ([]string, error) { return k.State.Keys() }
|
|
|
|
// Get is one key's value and revision.
|
|
func (k KeptSource) Get(key string) (*Entry, bool, error) {
|
|
e, err := k.State.Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, false, err
|
|
}
|
|
return &Entry{Value: e.Value, Revision: e.Revision}, true, nil
|
|
}
|
|
|
|
// ParseCallers is a callers list as a manifest writes it into an env value: comma-separated, each a module (on any
|
|
// machine), <machine>.<module> or person.<name> (stdio.Callers).
|
|
func ParseCallers(list string) stdio.Callers {
|
|
var out stdio.Callers
|
|
for _, c := range strings.Split(list, ",") {
|
|
if c = strings.TrimSpace(c); c != "" {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// CallersEnv is the env value of a first-view tool's callers list, in the owner's tools bundle: MESH_VIEW_CALLERS_<TOOL>,
|
|
// the tool's name in capitals, a hyphen an underscore.
|
|
func CallersEnv(tool string) string {
|
|
return "MESH_VIEW_CALLERS_" + strings.ToUpper(strings.ReplaceAll(tool, "-", "_"))
|
|
}
|
|
|
|
// Refusal is why caller may not ask tool, or nil: the caller's proof, never an argument, against the tool's callers
|
|
// list holding exactly its readers (ADR 0621 §2, ADR 0622 §1); the owner itself is on no list, so it is refused too.
|
|
func Refusal(tool string, callers stdio.Callers, caller string) error {
|
|
if callers.Allows(caller) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s is refused to %q: the tool answers only %s (novox/hq ADR 0621 §2)", tool, caller, strings.Join(callers, ", "))
|
|
}
|
|
|
|
// FirstViewTool is an owner's first-view tool of one of its states (ADR 0621 §1): each call answers one page of what the
|
|
// state holds now, at the revision its mark says, to the callers its list names and nobody else. It reads only the
|
|
// owner's own state, so it answers as soon as the runtime is connected, before the owner has asked anyone anything
|
|
// (ADR 0621 §3).
|
|
func FirstViewTool(owner, name, state, words string, callers stdio.Callers, values, marks Source, now func() time.Time) stdio.Tool {
|
|
return stdio.Tool{
|
|
Name: name,
|
|
Description: fmt.Sprintf("The first view of %s's state %s (novox/hq ADR 0615, ADR 0621): %s Each value whole by key, at the revision "+
|
|
"the owner said its views family up to, so a reader then follows the events on that family and asks again only on a gap in "+
|
|
"revisions. One page at a time: next is the key to ask after for the rest. Answered only to %s; any other caller is refused.",
|
|
owner, state, words, strings.Join(callers, ", ")),
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"after": map[string]any{"type": "string", "description": "the key the page before ended at (its next); none starts at the first"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
if err := Refusal(name, callers, stdio.CallerOf(args)); err != nil {
|
|
return nil, err
|
|
}
|
|
after, _ := args["after"].(string)
|
|
return FirstView(owner, state, values, marks, strings.TrimSpace(after), now())
|
|
},
|
|
}
|
|
}
|
|
|
|
// ReadTool is an owner's ordinary read tool of one of its states (design 25 § 12.2): the keys, one key's value and
|
|
// revision, or a page of values in the first view's form, and the state's fill against its cap as its mark last said
|
|
// it. Granted, as any tool is, to every reader that invokes it, and callable by a session.
|
|
func ReadTool(owner, name, state, words string, values, marks Source, now func() time.Time) stdio.Tool {
|
|
return stdio.Tool{
|
|
Name: name,
|
|
Description: fmt.Sprintf("Read %s's state %s: %s With no argument, its keys and how full it is against its cap (as the "+
|
|
"owner last marked it); with key, that key's value and revision; with values, a page of values whole by key, at the "+
|
|
"revision the owner said its views family up to, continued with after (the page's next).", owner, state, words),
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"key": map[string]any{"type": "string", "description": "one key: its value and revision"},
|
|
"values": map[string]any{"type": "boolean", "description": "a page of values whole by key"},
|
|
"after": map[string]any{"type": "string", "description": "with values: the key the page before ended at"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
key, _ := args["key"].(string)
|
|
after, _ := args["after"].(string)
|
|
all, _ := args["values"].(bool)
|
|
return Read(owner, state, values, marks, strings.TrimSpace(key), all, strings.TrimSpace(after), now())
|
|
},
|
|
}
|
|
}
|
|
|
|
// ReadAnswer is a read tool's answer without values: the state's keys and fill.
|
|
type ReadAnswer struct {
|
|
Owner string `json:"owner"`
|
|
State string `json:"state"`
|
|
Keys []string `json:"keys"`
|
|
Fill *Fill `json:"fill,omitempty"`
|
|
Revision uint64 `json:"revision"`
|
|
Marked time.Time `json:"marked,omitzero"`
|
|
// Words say what the fill means where the owner has not marked the state yet.
|
|
Words string `json:"words,omitempty"`
|
|
}
|
|
|
|
// KeyAnswer is one key of a read tool's answer.
|
|
type KeyAnswer struct {
|
|
Owner string `json:"owner"`
|
|
State string `json:"state"`
|
|
Key string `json:"key"`
|
|
Found bool `json:"found"`
|
|
Value json.RawMessage `json:"value,omitempty"`
|
|
Revision uint64 `json:"revision,omitempty"`
|
|
}
|
|
|
|
// Read is a read tool's answer: one key, a page of values, or the keys and fill.
|
|
func Read(owner, state string, values, marks Source, key string, all bool, after string, now time.Time) (any, error) {
|
|
switch {
|
|
case key != "":
|
|
e, ok, err := values.Get(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s.%s cannot be read: %w", state, key, err)
|
|
}
|
|
a := KeyAnswer{Owner: owner, State: state, Key: key, Found: ok}
|
|
if ok {
|
|
a.Value, a.Revision = e.Value, e.Revision
|
|
}
|
|
return a, nil
|
|
case all:
|
|
return FirstView(owner, state, values, marks, after, now)
|
|
}
|
|
keys, err := values.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the state %s cannot be listed: %w", state, err)
|
|
}
|
|
slices.Sort(keys)
|
|
a := ReadAnswer{Owner: owner, State: state, Keys: keys}
|
|
if marks != nil {
|
|
if e, ok, err := marks.Get(state); err == nil && ok {
|
|
if m, err := DecodeMark(e.Value); err == nil {
|
|
fill := m.Fill
|
|
a.Fill, a.Revision, a.Marked = &fill, m.Revision, m.At
|
|
}
|
|
}
|
|
}
|
|
if a.Fill == nil {
|
|
a.Words = "the owner has not marked this state yet: its fill is said once its views family says a change of it"
|
|
}
|
|
if a.Keys == nil {
|
|
a.Keys = []string{}
|
|
}
|
|
return a, nil
|
|
}
|