novox since 15:36 (nats's built manifest) and ace since 15:37 (baserow's config.json) had no plans at all, so the vault refused every secret their clients asked. The holder now composes with frombus.PlansHeld: a module whose build or plan refuses is held, its why kept in the new state refused and read by provisioner_plans, its last plan and its provisions left as they were, and every other module on the node composed and written. Pins mesh-sdk 59311407.
125 lines
4.5 KiB
Go
125 lines
4.5 KiB
Go
package main
|
|
|
|
// The holder's tools (novox/hq ADR 0570 Consequences, ADR 0564 §12's rule): a read of every key of `provisions` and
|
|
// `plans`, one key whole, each state's fill, composing one node now, and reading the operator's answers now.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/provisions"
|
|
)
|
|
|
|
// The holder's tools.
|
|
const (
|
|
ProvisionsTool = "provisioner_provisions"
|
|
PlansTool = "provisioner_plans"
|
|
ComposeTool = "provisioner_compose"
|
|
DecideTool = "provisioner_decide"
|
|
)
|
|
|
|
func str(d string) map[string]any { return map[string]any{"type": "string", "description": d} }
|
|
|
|
func holderTools(h *Holder) []stdio.Tool {
|
|
arg := func(a map[string]any, k string) string { s, _ := a[k].(string); return strings.TrimSpace(s) }
|
|
return []stdio.Tool{
|
|
{Name: ProvisionsTool,
|
|
Description: "Every provision the seat keeps (novox/hq ADR 0570): its key <node>.<consumer>.<provision>[.<local>], " +
|
|
"provider, consumer and state (asked, provided, waits, failed, retiring, with why), narrowed by node and " +
|
|
"state, with how many keys and bytes the state holds; or one key whole.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{"key": str("one key, whole"),
|
|
"node": str("a consumer's node"), "state": str("asked, provided, waits, failed or retiring")}},
|
|
Run: func(a map[string]any) (any, error) {
|
|
return h.read(ProvisionsState, arg(a, "key"), arg(a, "node"), arg(a, "state"))
|
|
}},
|
|
{Name: PlansTool,
|
|
Description: "Every module's provisioning plan the seat composed, <node>.<module>, narrowed by node, with how many " +
|
|
"keys and bytes the state holds, and each placed module held with no plan composed and why (refused: its " +
|
|
"manifest or its plan refuses, the node's other modules composed); or one plan whole.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{"key": str("one plan, <node>.<module>"),
|
|
"node": str("a node")}},
|
|
Run: func(a map[string]any) (any, error) {
|
|
out, err := h.read(PlansState, arg(a, "key"), arg(a, "node"), "")
|
|
if err != nil || arg(a, "key") != "" {
|
|
return out, err
|
|
}
|
|
refused, err := h.read(RefusedState, "", arg(a, "node"), "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.(map[string]any)[RefusedState] = refused.(map[string]any)[RefusedState]
|
|
return out, nil
|
|
}},
|
|
{Name: ComposeTool,
|
|
Description: "Compose one node's plans and provisions now from the bus, as a change event would, and say what " +
|
|
"was written, removed, changed and asked.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{"node": str("the node")},
|
|
"required": []string{"node"}},
|
|
Run: func(a map[string]any) (any, error) {
|
|
if arg(a, "node") == "" {
|
|
return nil, fmt.Errorf("compose names the node")
|
|
}
|
|
return h.ComposeNode(context.Background(), arg(a, "node"))
|
|
}},
|
|
{Name: DecideTool,
|
|
Description: "Read the operator's answers to every retiring provision's ask now, writing each warrant into its " +
|
|
"key, as the router's word on an ask would.",
|
|
Run: func(map[string]any) (any, error) { return map[string]any{"said": h.Decide()}, nil }},
|
|
}
|
|
}
|
|
|
|
// read is a state's keys narrowed, or one whole, with the state's fill.
|
|
func (h *Holder) read(state, key, node, want string) (any, error) {
|
|
if key != "" {
|
|
e, err := h.Store.Get(state, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if e == nil {
|
|
return nil, fmt.Errorf("%s holds no %s", state, key)
|
|
}
|
|
return json.RawMessage(e.Value), nil
|
|
}
|
|
keys, err := h.Store.Keys(state)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows := []map[string]any{}
|
|
bytes := 0
|
|
for _, k := range keys {
|
|
e, err := h.Store.Get(state, k)
|
|
if err != nil || e == nil {
|
|
continue
|
|
}
|
|
bytes += len(e.Value)
|
|
if node != "" && !strings.HasPrefix(k, node+".") {
|
|
continue
|
|
}
|
|
row := map[string]any{"key": k}
|
|
if state == ProvisionsState {
|
|
p, err := provisions.Decode(e.Value)
|
|
if err != nil {
|
|
row["unreadable"] = err.Error()
|
|
} else {
|
|
if want != "" && p.State != want {
|
|
continue
|
|
}
|
|
row["state"], row["provider"], row["why"] = p.State, p.Provider.Module+" on "+p.Provider.Node, p.Why
|
|
}
|
|
}
|
|
if state == RefusedState {
|
|
var r Refusal
|
|
if err := json.Unmarshal(e.Value, &r); err != nil {
|
|
row["unreadable"] = err.Error()
|
|
} else {
|
|
row["module"], row["why"], row["since"] = r.Module, r.Why, r.Since
|
|
}
|
|
}
|
|
rows = append(rows, row)
|
|
}
|
|
return map[string]any{state: rows, "count": len(rows), "fill": map[string]int{"keys": len(keys), "bytes": bytes}}, nil
|
|
}
|