127 lines
7.6 KiB
Go
127 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
|
|
"mesh-grants/internal/grants"
|
|
)
|
|
|
|
// The part's tools, served through the tool runner from its bundle (ADR 0436: a process never serves). They read and
|
|
// write through the runtime as the module: its own states, the reads its manifest names, the bus module's read tools.
|
|
|
|
// runtimeReader reads another part's state through the runtime, `<module>.<state>`.
|
|
type runtimeReader struct{}
|
|
|
|
func (runtimeReader) Keys(state string) ([]string, error) { return stdio.State(state).Keys() }
|
|
|
|
func (runtimeReader) Get(state, key string) ([]byte, bool, error) {
|
|
e, err := stdio.State(state).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, false, err
|
|
}
|
|
return e.Value, true, nil
|
|
}
|
|
|
|
// runtimeStore is the part's own state through the runtime.
|
|
type runtimeStore struct{}
|
|
|
|
func (runtimeStore) Get(state, key string) ([]byte, uint64, bool, error) {
|
|
e, err := stdio.State(state).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, 0, false, err
|
|
}
|
|
return e.Value, e.Revision, true, nil
|
|
}
|
|
|
|
func (runtimeStore) Put(state, key string, value []byte) (uint64, error) {
|
|
return stdio.State(state).Put(key, json.RawMessage(value))
|
|
}
|
|
|
|
// runtimeBus calls tools and says events through the runtime.
|
|
type runtimeBus struct{}
|
|
|
|
func (runtimeBus) Call(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) }
|
|
func (runtimeBus) Emit(event string, body any) error { return stdio.Emit(event, body) }
|
|
|
|
func schema(properties map[string]string, required ...string) map[string]any {
|
|
props := map[string]any{}
|
|
for name, description := range properties {
|
|
props[name] = map[string]any{"type": "string", "description": description}
|
|
}
|
|
out := map[string]any{"type": "object", "properties": props}
|
|
if len(required) > 0 {
|
|
out["required"] = required
|
|
}
|
|
return out
|
|
}
|
|
|
|
// toolsOf are the part's tools over a part (grants.Tools names them).
|
|
func toolsOf(p *grants.Part) []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{Name: grants.ToolGrants, Description: "What the grants part holds of grants (novox/hq ADR 0547 clause 6): in this milestone it " +
|
|
"writes none (grants.<module>.<fp> from the third milestone, clause 5), and shows beside it the grants the controller " +
|
|
"wrote (mesh-controller_grants), all or one module's with the accounts each names. Read-only.",
|
|
Input: schema(map[string]string{"module": "one module"}),
|
|
Run: func(a map[string]any) (any, error) { return p.Grants(grants.Answer(a, "module")) }},
|
|
{Name: grants.ToolMembership, Description: "One module's membership on one node (ADR 0547 clause 6): as composed now from the " +
|
|
"bus's records with the bus composer, and what the part's process published of it at its last pass (published, " +
|
|
"unchanged, or why not: a shadow difference names what publishing would lose; verbs the seat catalogue retired are " +
|
|
"dropped and named), with what the composition cannot read. With neither node nor module: every membership the last " +
|
|
"pass held back, each with what it would lose. Read-only.",
|
|
Input: schema(map[string]string{"node": "the machine (with module)", "module": "the module (with node)"}),
|
|
Run: func(a map[string]any) (any, error) {
|
|
return p.MembershipOf(grants.Answer(a, "node"), grants.Answer(a, "module"))
|
|
}},
|
|
{Name: grants.ToolUserList, Description: "The bus's user list in shadow (ADR 0547 clauses 6 and 8): the list the grants part " +
|
|
"composed (digest, revision, users), what the bus node says it loaded (source, revision, digest, users, error), and how " +
|
|
"they differ: users added, users dropped (and of those, gone from placement), each user's grant differences, the " +
|
|
"assignments left out, every input the bus does not carry, named; and per user whether the vault holds its hash " +
|
|
"(mesh-vault.bus-accounts, ADR 0547 clause 9) and how its line would differ from the loaded one (same, adds, left-out " +
|
|
"for want of a credential, loses-login), never a hash. Read-only.",
|
|
Run: func(map[string]any) (any, error) { return p.UserList() }},
|
|
{Name: grants.ToolComposeNow, Description: "Compose again now (ADR 0547 clause 6), as the part does on every change it hears: asks " +
|
|
"the part's process (which alone holds the grant to publish a membership) to compose the memberships, the user list and " +
|
|
"the assertions, publish each membership that differs from the one last published, and keep what changed in its own " +
|
|
"state (one revision when an input changed, none when not), saying user-list-changed and membership-changed. Answers " +
|
|
"the ask and the last pass; grants_user_list shows the pass that answers it. A membership is published only when it is " +
|
|
"the first or only adds to the last one; any loss stays a shadow difference, named. The user list, assertions and " +
|
|
"grants stay in shadow. It cannot ask the controller to write a module's grant again: the controller serves no such " +
|
|
"verb, and the answer says so.",
|
|
Run: func(a map[string]any) (any, error) { return p.AskCompose(stdio.CallerOf(a)) }},
|
|
{Name: grants.ToolAssert, Description: "One module's buckets, logs and streams (ADR 0547 clause 6), composed from its manifest at " +
|
|
"the indexer's trunk with their limits, beside what the bus holds (through the bus module's nats_streams). When the " +
|
|
"bus lacks one, asks the part's process (which alone holds the grant) to create it now with the manifest's limits, " +
|
|
"as it does by itself when a manifest is registered (issue 1790), and says asserted; one the bus holds is never " +
|
|
"changed, a difference is only said. Answers the comparison and the ask; call again to see what the bus holds.",
|
|
Input: schema(map[string]string{"module": "the module"}, "module"),
|
|
Run: func(a map[string]any) (any, error) { return p.AssertOne(grants.Answer(a, "module")) }},
|
|
{Name: grants.ToolTakeOver, Description: "Ask the operator to switch the bus node to the grants part's list (ADR 0547 clause 8). " +
|
|
"The operator's word is their yes on their channel (a warrant), never a caller's name: this tool only asks, once at a time. " +
|
|
"After the yes, the switch is recorded and takes effect when the bus node loads the part's list; it is refused while the loaded list could not be read, a user differs between the lists " +
|
|
"(an enrolment user or a person the part cannot compose among them), or the vault holds no hash for a user, and the " +
|
|
"refusal is said to the operator naming the users. Every act is recorded in the part's state takeover.",
|
|
Input: schema(map[string]string{"why": "why, kept with the act"}, "why"),
|
|
Run: func(a map[string]any) (any, error) {
|
|
return p.TakeOver(stdio.CallerOf(a), grants.Answer(a, "why"))
|
|
}},
|
|
{Name: grants.ToolHandBack, Description: "Ask the operator to switch the bus node back to the controller's list (ADR 0547 clause 8), " +
|
|
"by their yes on their channel; in shadow there is nothing to hand back, and it says so. Every act is recorded.",
|
|
Input: schema(map[string]string{"why": "why, kept with the act"}, "why"),
|
|
Run: func(a map[string]any) (any, error) {
|
|
return p.HandBack(stdio.CallerOf(a), grants.Answer(a, "why"))
|
|
}},
|
|
{Name: grants.ToolTakeOverState, Description: "Where the switch stands: the list the bus node follows, the ask open now, the request, and " +
|
|
"every act with what became of it (asked, switched, declined, expired, refused and why).",
|
|
Input: schema(nil),
|
|
Run: func(map[string]any) (any, error) { return p.TakeOverState() }},
|
|
}
|
|
}
|
|
|
|
// serveTools serves the tools to the runtime until it stops.
|
|
func serveTools() error {
|
|
p := &grants.Part{Read: runtimeReader{}, Call: runtimeBus{}, Store: runtimeStore{}, Emit: runtimeBus{}}
|
|
return stdio.Serve("", toolsOf(p))
|
|
}
|