Files
mesh-delivery/sdk/views/tools.go
T
jschoubben 5d7fff91ce DLV-O1, DLV-O3, DLV-O4: the indexer answers its first views and says its views family
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
2026-11-04 12:22:26 +01:00

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
}