Files
mesh-sdk/go/views/tools.go
T
jschoubben 315d7e9b8c J3 (A2g, A2h): a process's pictures of its owners' states are go/views', and a first view says when each key was written
ProcessReader and Picture (A2g's mesh-delivery sdk/firstview at c6c6687) and the test owner (viewstest) move into
go/views, so the builder, checker, teller, view and watchman read the indexer's and the assigner's states through the
one shared follower. The first view's answer carries each key's written time as the bus keeps it, and
Follower.Changed hands it on, so a reader's want time is the owner's write, not its own start (A2h's request).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 14:22:20 +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, Written: e.At}, 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
}