package catalogue import ( "bytes" "encoding/json" "fmt" ) // A Verb is one tool a role serves: its name, what it does, and the schema of its arguments and of // its answer (novox/hq ADR 0132, design 33 §2). // // **A name alone is not callable by something that has never seen the mesh before**, which is the // whole population a tool surface exists for. So a seat's protocol carries the definition, in the // form an agent protocol already uses — a JSON schema for the input — so nothing translates between // a seat's idea of an argument and the caller's. // // A manifest may still write a bare verb name (`"serves": ["price"]`); that is a Verb with only a // name, and the module's runtime answers `tools` with the rest. The two forms read into one type so // nothing downstream cares which was written. type Verb struct { Name string `json:"name"` Description string `json:"description,omitempty"` Input map[string]any `json:"input,omitempty"` Output map[string]any `json:"output,omitempty"` // Optional marks a verb added to a mesh seat whose holder lives in another repository (design 33 §7, // additive within a version): a holder that serves it is accepted, and one that does not yet still // holds the seat. Without it the addition would be a deadlock — this controller refusing the holder that // does not serve the verb, the controller before it refusing the holder that does — and every check of // the catalogue between the two would fail on a module nobody touched. Once every holder serves it, // the mark is removed and the verb is a condition of holding like the rest. Never stored or said: the // seat set's protocol is the compiled one. Optional bool `json:"-"` // Replaces are the shell commands this verb is the mesh's way to do, each as it is typed — // `journalctl`, `systemctl restart` — so the agent finds the verb by the command it would have run // and is told to call it instead (novox/hq ADR 0245): the mesh MCP server's search matches them, the agent's // instructions list them, and the guard on its shell names the verb when it refuses a work-around. // A command line is matched by its words in order, the first being the command's name; `edit // /etc/hosts` and `HOSTALIASES` name the guard's two local work-arounds for a mesh name. Replaces []string `json:"replaces,omitempty"` } func (v *Verb) UnmarshalJSON(raw []byte) error { trimmed := bytes.TrimSpace(raw) if len(trimmed) > 0 && trimmed[0] == '"' { var name string if err := json.Unmarshal(trimmed, &name); err != nil { return err } *v = Verb{Name: name} return nil } // Strictly, like the manifest around it: a misspelt key in a tool's definition would otherwise // describe a tool nobody can call and refuse nothing. type plain Verb var p plain decoder := json.NewDecoder(bytes.NewReader(trimmed)) decoder.DisallowUnknownFields() if err := decoder.Decode(&p); err != nil { return fmt.Errorf("a served verb is a name or {name, description, input, output}: %w", typedUnknown(err)) } if p.Name == "" { return fmt.Errorf("a served verb has no name: %s", trimmed) } *v = Verb(p) return nil } // VerbNames are the names alone, for the grants and the checks that care about nothing else. func VerbNames(verbs []Verb) []string { out := make([]string, 0, len(verbs)) for _, v := range verbs { out = append(out, v.Name) } return out } // RemovedControllerVerbs are the controller seat's verbs release 6 removed (novox/hq ADR 0539, ADR 0429 §8): no // delivery plan, batch, tier or push exists, so nothing starts, stops, retries, follows or reads one. Each stays in // ControllerVerbs as a row with no input, so a caller is answered by name with its replacement, given here. var RemovedControllerVerbs = map[string]string{ "plans": "the delivery view (nox-delivery): each machine's want and have, read from the assigner's wants and the " + "node-engine's haves", "plan": "the node's compositions: mesh-controller_composed holds each module's digest and judgement per node, and " + "mesh-hand-compose prints one module's composition for one node", "push": "a want: the assigner writes want.. by its rules, and by hand the operator's credential writes " + "it, naming a kept composition (ADR 0539 clause 10)", "deliver": "nothing to start: a merged change reaches each machine by its want", "delivery-stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)", "delivery-backups-again": "the assigner's backup ask, answered on the phone or in the delivery view (ADR 0539 clause 16)", "delivery-walks": "the delivery view (nox-delivery) and the wants, haves and compositions on the bus", "delivery-order": "no order to keep: each module reaches its machines by its own want, its providers first by rule 5", } // removedInR6 is the row of a verb release 6 removed from the controller's seat: no input, refused by name. func removedInR6(name string) Verb { return Verb{Name: name, Description: "Removed in R6 (ADR 0539): refused by name, naming its replacement: " + RemovedControllerVerbs[name], Input: schema(nil, nil)} } // ControllerSeatName is the seat the control plane holds, whose tools are the mesh's own verbs. const ControllerSeatName = "mesh-controller" // ControllerVerbs are the mesh's own verbs, as the `mesh-controller` seat's tools (novox/hq ADR 0154). // // **The same function the command line calls, and nothing the tool adds** (ADR 0035): each of these // is a command the controller's binary already answers, run by the holder of the seat with the // arguments below and answered with what the command printed. A verb here is a contract every future // holder must implement, which is why the list is short and made of what an operator asks weekly. // Additive within a version (design 33 §7); a verb that would break a caller takes a new version. var ControllerVerbs = []Verb{ {Name: "tools", Description: "Every seat's tools, from the mesh's own records: what each role " + "answers, whether or not its holder is up. The mesh's own verbs are the mesh-controller seat's.", Input: schema(nil, nil)}, {Name: "calls", Description: "The calls this controller answered lately and what came of each — " + "one still running, one that finished after its caller was told it was running, one whose answer " + "the bus refused — and, given a call's id, its whole answer (novox/hq issue 265). A call that has " + "not finished within ten seconds answers that it is running, with its id; this is where it ends.", Input: schema(map[string]string{ "call": "a call's id, as a running answer or `calls` gives it: that call and its whole answer", }, nil)}, {Name: "status", Description: "What is wrong, what is quiet, what is out of date, and which " + "machines are behind what the mesh would send them.", Input: schema(nil, nil)}, {Name: "nodes", Description: "Every machine the mesh knows, with whether it is converged or adopted.", Input: schema(nil, nil)}, {Name: "node", Description: "What one machine reported it can do, what it is assigned, and why.", Input: schema(map[string]string{"node": "the machine's name"}, []string{"node"}), Replaces: []string{"hostnamectl", "uptime"}}, {Name: "modules", Description: "Every module the mesh holds: version, the commit it was built from, " + "and which machines run it.", Input: schema(nil, nil)}, {Name: "seats", Description: "Every seat the mesh defines, what it delivers, and who holds it.", Input: schema(nil, nil)}, {Name: "builds", Description: "What has been built lately and what came of it, for every module or for one; " + "or, given a build's id, everything the build machine said while building it, line by line, from the bus.", Input: schema(map[string]string{ "module": "one module's name; every module when absent", "log": "a build's id (as `builds` lists it): print what the build machine said, line by line", "limit": "how many builds to list (default 20); not with log", }, nil)}, removedInR6("plans"), // The verbs the delivery's owner asks with (novox/hq ADR 0239): the planner's answer, a group's order and // composed check, and the start and end of a walk. Callable by a person too; mesh-delivery is who needs them. {Name: "delivery-plan", Description: "Removed in R4e (ADR 0501 §5 / ADR 0503 §3): refused by name, naming its replacement", Input: schema(nil, nil)}, removedInR6("delivery-order"), {Name: "delivery-check", Description: "Answers that the checker (mesh-delivery-checker) judges a group (novox/hq " + "ADR 0503 §3); asks nothing. Its inputs stay, since hq's repository check still names it (ADR 0513 §4).", Input: schema(map[string]string{"group": "the group's id (none for one head checked again)", "members": "the members, as JSON, as delivery-order takes them", "ready": "\"true\" when the pull request is marked ready: its check is queued before every other check (novox/hq ADR 0406 §3)"}, []string{"members"})}, removedInR6("deliver"), removedInR6("delivery-stop"), removedInR6("delivery-backups-again"), removedInR6("delivery-walks"), removedInR6("plan"), {Name: "assign", Description: "Put a module on a machine. Refused with the mesh's own words when it cannot resolve there, " + "or when a seat its resources are applied through is held by nothing on the machine (novox/hq ADR 0207). " + "A module not registered yet is looked for (novox/hq issue 325, ADR 0261): while its build runs — a merge " + "that adds a module asks for one — the assignment is kept as pending and the controller makes it when the " + "build registers it; `status` lists it, and if the build fails it expires with why and raises a condition. " + "A module known and not built is said, and build asks for its build; a name with no module and no build " + "request kept is refused with the closest names.", Input: schema(map[string]string{"node": "the machine's name", "module": "the module's name; several comma-separated are judged together", "build": "\"true\": for a module known and not built (its last build failed, or was never asked), ask " + "for its build and keep this assignment pending until the build registers it"}, []string{"node", "module"}, "build")}, {Name: "unassign", Description: "Take a module off a machine. Refused when it holds a seat a module left there depends on.", Input: schema(map[string]string{"node": "the machine's name", "module": "the module's name; several comma-separated are judged together"}, []string{"node", "module"})}, {Name: "pin", Description: "Tell a machine which provider answers a provision for it — the module, and the node " + "it runs on, both. Asked for when more than one could answer; the refusal lists them.", Input: schema(map[string]string{ "node": "the machine's name", "provision": "the provision, as the consumer requires it", "from": "the node the chosen provider runs on", "module": "the module providing it there", }, []string{"node", "provision", "from", "module"})}, {Name: "unpin", Description: "Take that choice back, putting the question to the mesh again.", Input: schema(map[string]string{"node": "the machine's name", "provision": "the provision"}, []string{"node", "provision"})}, removedInR6("push"), {Name: "rotate", Description: "Replace a credential. A pair credential, by provision (and a consuming machine and module, " + "else every holder): both ends are re-sent together. Or a module's own secret, by machine, module and " + "name: made anew and the machine sent, so the module starts again on it — for a secret its definition " + "says it reads at start, whether the mesh made the value or a person gave it (novox/hq ADR 0228); one " + "an outside party issued, or one the module applies to a backend, is refused with the reason.", Input: schema(map[string]string{ "provision": "a pair credential: the provision whose credential to replace", "consumer": "with provision: only the holder on this machine (optional)", "node": "an own secret: the machine", "module": "an own secret: the module; with provision: only this consuming module's credential (optional)", "secret": "an own secret: its name in the module's definition", "why": "an own secret: why it is rotated — recorded in the hand-act log (optional)", "cause": "with why: the cause in a word, the word a second rotation for the same reason uses (optional)", }, nil)}, {Name: "give", Description: "Take a module's own secret from the operator at their desk (novox/hq ADR 0259 " + "§10): the same as secret-ask with the desk named. A prompt that does not show what is typed opens on " + "the machine named by at, its answer comes back sealed to this call alone, and is sealed to the " + "module's machine as `secret accept` seals it. The value is never an argument and never in the answer: " + "the answer says it was taken, or why not. Recorded in the hand-act log as a value given at the desk. " + "The prompt waits 25 seconds; dismissed or unanswered, nothing changes. Then push the machine.", Input: schema(map[string]string{ "node": "the machine the module runs on, which the secret is sealed to", "module": "the module's name", "secret": "the own secret's name in the module's definition", "at": "the machine the operator sits at, where the prompt opens", }, []string{"node", "module", "secret", "at"})}, {Name: "issued", Description: "Take the credential a provider's software issued for a provision — a media " + "server's sign-in token — and keep it as that provision's one credential for every consumer (novox/hq ADR " + "0305): the controller asks the provider's own tool on its machine, in the controller's own name, for the " + "value sealed to a key made for this call alone, seals it to every holder and forgets it, and sends their " + "machines when it changed. Carries no value and answers none; a value every holder already holds is not " + "sealed again, so asking again restarts nothing. Refused for a provision no offer says its software issues.", Input: schema(map[string]string{ "provision": "the provision whose provider's software issues its credential, e.g. plex-api", }, []string{"provision"})}, {Name: "secret-move", Description: "Move every secret the controller holds into the vault, as it is (novox/hq " + "ADR 0349 clause 7, design 57's V3): each machine's node-engine opens what was sealed to it and hands it to " + "the vault, which takes it from that machine alone, never over a value it holds, and reads it back; the " + "record of asks at a desk is copied once. No value is rotated, in the arguments or in the answer, and " + "nothing points at the vault because of it. Answers, per machine, what moved, what was there already and " + "what did not move and why; a machine not answering is moved when it is next heard from. Run again, it " + "moves what is left.", Input: schema(map[string]string{ "node": "only this machine's secrets (optional; every machine's without it)", }, nil)}, {Name: "secret-ask", Description: "Ask the operator for a module's own secret (novox/hq ADR 0277): a prompt " + "that shows nothing of what is typed opens at the desk — the module's machine, or the one at names — " + "naming the module, the secret and who asked; the operator types the value there, never on a channel " + "and never in a verb's argument; the answer comes back sealed to this call alone and is sealed to the " + "module's machine as `secret accept` seals it. The answer says the value was taken, or why not. " + "Refused for a secret the mesh issues itself (the bus account, one the mesh may make) and for a module " + "that runs as an account of its own, whose value is typed at the controller's terminal. Bounded: one " + "open prompt per secret, three asks an hour. Recorded in the hand-act log, with who asked, and " + "announced on every channel. Then push the machine.", Input: schema(map[string]string{ "node": "the machine the module runs on, which the secret is sealed to", "module": "the module's name", "secret": "the own secret's name in the module's definition", "at": "the machine the operator sits at, where the prompt opens; the module's machine when absent", }, []string{"node", "module", "secret"})}, {Name: "issue", Description: "Give a module on a machine its account on the bus: minted, and sealed to the " + "machine as the module's own secret named broker, read at the next push of that machine. For a module " + "whose definition declares that secret; refused with the reason otherwise. Issued again, it replaces the account.", Input: schema(map[string]string{ "node": "the machine that runs the module", "module": "the module's name", }, []string{"node", "module"})}, {Name: "token", Description: "Refused through a verb since novox/hq ADR 0266: the one-time token a machine " + "joins with is answered to its caller, and whoever may call a verb includes agents. Issue it at the " + "controller's terminal: `mesh-controller token issue --new --overlay-key `.", Input: schema(map[string]string{ "node": "a machine the mesh already has a record for", "new": "or the name of a machine to create the record for", "overlay_key": "the public half of the machine's tunnel key", "for": "how long it may be used, as a duration (default 1h)", "adopted": "\"true\" when the machine is in use and joins adopted", }, nil, "adopted"), Replaces: []string{"mesh-controller token issue", "wg set"}}, {Name: "settings", Description: "Read or set what an assignment is configured with: a module's settings for the whole " + "mesh, or for one machine. Without values or clear, answers the layer as it stands — read it before setting it — " + "then every value the module gives a default or a layer sets, with where it comes from: the module's default, " + "the mesh, or the machine (novox/hq ADR 0262); with list \"preferences\", or with no module, every " + "module's preferences and each machine's value; " + "with history, the layers it replaced. Setting replaces that layer whole and answers each key it adds (+), " + "changes (~) and removes (-); a set that would remove a key is refused unless replace says it is meant " + "(novox/hq ADR 0217). Takes effect at the next push. With clear, removes the layer and the module is back to " + "what its definition says; a cleared or replaced layer is kept in the history. A trusted setting — " + "places, accesses, what a provider serves, what a trusted file asks for, the whole layer of a module " + "with a trusted mergeable file — is refused through this verb (novox/hq issue 339, 340) and PROPOSED " + "instead (novox/hq ADR 0277): with propose, the values (or clear) are put to the operator on a channel " + "that proves who answers, with every key and its exact new value, and the layer is set only on their " + "Approve; the answer names the proposal, its fingerprint and when it expires, and nothing changes until " + "then. With proposals, every proposal and where each stands; with proposal, one whole. Once move-config has " + "run, a module in the config form forwards to mesh-config (issue 664): show lists what config lists, set and " + "clear set and clear each value as the module's own on the machine (none for the whole mesh), placement " + "staying beside the assignment, and propose proposes each value through mesh-config.", Input: schema(map[string]string{ "module": "the module's name; with list, only that module's", "values": "the settings as a JSON object, for set or propose", "node": "one machine; the whole mesh when absent", "clear": "\"true\" to remove the layer instead of setting it, or to propose its removal; not with values", "replace": "\"true\": with values, the set (or the proposal) is meant to remove the keys the layer had and it does not name", "history": "\"true\": without values or clear, the layers this one replaced, the latest first", "list": "\"preferences\": every module's preferences — key, default and why — and the value on each " + "machine it is assigned to with where it comes from; module and node narrow it (novox/hq ADR 0262)", "propose": "\"true\": propose the values (or clear) to the operator instead of setting them (novox/hq ADR 0277)", "proposals": "\"true\": every settings proposal, newest first, and where each stands", "proposal": "a proposal's id: that proposal whole, its values and the layer it was shown against", }, nil, "clear", "replace", "history", "propose", "proposals")}, {Name: "command", Description: "Run one reading command line of the controller's own, as you would type it at " + "its shell — `node show ace`, `module list`, `plans`, `conditions show ` — and answer what it " + "printed. The generic verb beside the named ones (novox/hq ADR 0154), and since ADR 0266 it only reads: " + "status, version, help, seats, healers, hand-acts, durations, collection, images, artifacts, data, builds, " + "plan, plans (not stop/close/go), doctor (not run), conditions list/show/history, node list/show, " + "module list, settings show/preferences, retire list, cleanup list, delivery plan/walks, bus, mirrors (not " + "--record/--confirm). What writes has its named verb; what sets a key, issues a credential or a token, or " + "accepts, recovers or exports a secret is the controller's terminal's alone.", Input: schema(map[string]string{ "command": "the command line, as the controller's binary takes it; quotes group a word with spaces", }, []string{"command"})}, // The build queue went with build-agent (novox/hq ADR 0525 §9): asks are the build seats' streams, their backlog the // streams' own state, shown in the delivery view. The verbs are refused by name for one release, naming that. {Name: "queue", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: the build seats' backlog in the delivery view", Input: schema(nil, nil)}, {Name: "cancel", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, {Name: "clear", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, {Name: "replay", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement", Input: schema(nil, nil)}, // novox/hq issue 686: a builder ran a branch's store tests for the first time in its pull request's merge check. {Name: "try", Description: "Try a pushed branch or commit before its pull request (novox/hq issue 686): the " + "checker (mesh-delivery-checker) merges the head onto the repository's trunk and runs its levels on the merge, " + "as it judges a pull request (ADR 0503 §3), its levels asks on the build seats (ADR 0525 §1); no merge-check.sh is run. Answers at " + "once with an id; asked again with that id it waits up to 20 s and answers pass, fail or error, how long it " + "took and the lines naming what failed — or that it is still waiting or running, to ask again. Sets no " + "status on the forge, registers and builds nothing for delivery. One try of a commit at a time — a second " + "ask answers the first's id. Only for a repository of the mesh; the ref must be on the forge.", Input: schema(map[string]string{ "repository": "the repository on the forge, owner/name; with ref", "ref": "the branch or commit to try, as pushed to the forge", "base": "the branch it is merged into (the repository's default branch when none is named)", "tests": "only the tests this `go test -run` pattern names", "store-only": "\"true\": only the integration level", "id": "instead of a repository: the id a try answered, to read what came of it", }, nil, "store-only"), Replaces: []string{"sh merge-check.sh", "docker run postgres"}}, {Name: "kill", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: a build seat's ask ends where it runs", Input: schema(nil, nil)}, {Name: "pause", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: /node-build-.pause", Input: schema(nil, nil)}, {Name: "resume", Description: "Removed in R4g (ADR 0525 §9): refused by name, naming its replacement: /node-build-.resume", Input: schema(nil, nil)}, // Acts done by hand, and what the bounds are set from (novox/hq to-be 45 §7, Phase 0). {Name: "hand-act", Description: "Record an act done by hand outside the mesh — a container restarted, a file " + "edited, a service started on a machine — with why and its cause, in the hand-act log beside the pushes and " + "plans ended by hand (novox/hq to-be 45 §7). A cause recorded twice in a fortnight is a healer wanted.", Input: schema(map[string]string{ "what": "what was done, in a line", "why": "why it had to be done by hand", "cause": "the cause in a word, or a condition's kind — the word a second act for the same reason uses", "condition": "the key of the condition it addressed, if any (optional)", }, []string{"what", "why", "cause"})}, {Name: "drill", Description: "Record a drill — something broken on purpose to see the mesh raise and clear it: " + "a module stopped, a process killed — with what it tests, in the hand-act log. A drill is a person's " + "deliberate test, never a repair: no healer is wanted for it however often it is run (S15).", Input: schema(map[string]string{ "what": "what was done on purpose, in a line", "why": "what the drill tests", "condition": "the key of the condition the drill is meant to raise, if any (optional)", }, []string{"what", "why"})}, {Name: "warranted", Description: "Record in the hand-act log what a module did on the operator's warrant (novox/hq " + "ADR 0274, ADR 0259): who chose, through which channel, with which proofs and which answer are read from the " + "router's own record of that module's ask, never from the caller; recorded once per ask however often it is " + "asked; the caller names the ask and says nothing else. Refused for an ask still open, ended without a choice, " + "or not that module's.", Input: schema(map[string]string{ "asker": "the module that asked, e.g. claude-code", "ask": "its ask's id", }, []string{"asker", "ask"})}, {Name: "hand-acts", Description: "What was done by hand lately — pushes, plans ended, consumers re-made, acts " + "recorded — who, why and the cause of each, and which causes repeat: each repeat is a healer the mesh lacks.", Input: schema(map[string]string{"days": "how many days back (default 14)"}, nil)}, {Name: "durations", Description: "How long things take, as the controller measured them: a send to its machine's " + "report (apply), a plan's tier, a build, and each phase of an " + "ended walk per class (walk-phase, novox/hq ADR 0282) — per machine, repository, module or class/phase, with " + "median, p90 and max. What the core's bounds are set from (novox/hq to-be 45 Phase 0).", Input: schema(map[string]string{ "kind": "one kind: apply, plan-tier, build or walk-phase; every kind when absent", "days": "how many days back (default 14)", }, nil)}, // What is wrong, and the self-check (novox/hq to-be 45 §2, §4). {Name: "conditions", Description: "What is wrong with the mesh now: every open condition, urgent first, " + "then oldest — raised by the watchdogs of the signals table, the self-check's probes and the providers' " + "own words, and cleared when observation says it is resolved, never by hand. Given a key, that one " + "whole with its evidence; with history, every transition lately; with silence, stop one's messages " + "for a while — a hand act, which says why (novox/hq to-be 45 §2).", Input: schema(map[string]string{ "key": "a condition's key: that one whole; with history, only its transitions", "scope": "only this scope: machine, plan, call, build, merge, provider, seat, bus, core, probe or mesh", "severity": "only urgent, or only warning", "machine": "only those about this machine", "history": "\"true\": every raising, change, silence and clearing lately, oldest first", "days": "with history: how many days back (default 7, at most 90)", "silence": "a condition's key: send no message for it for a while; it stays open and in status", "for": "with silence: how long — 30m, 4h, 2d; at most 7d", "why": "with silence: why — required, and recorded in the hand-act log", "cause": "with silence: the cause in a word (the condition's kind when absent)", }, nil, "history")}, // What the healers did (novox/hq to-be 45 §7). {Name: "healers", Description: "The healers (novox/hq to-be 45 §7): each registered response to one kind " + "of condition — its repair (the ordinary path again), its budget, what happens when it is spent — every act " + "they took lately with its outcome, and the mesh-wide brake. A heal is never a hand act; whether it " + "repaired anything is the condition's clearing to say. Each act is also in its condition's tried.", Input: schema(map[string]string{"days": "how many days of acts back (default 7)"}, nil)}, {Name: "doctor", Description: "The self-check (novox/hq to-be 45 §4): the last run's verdict at once — " + "each probe of the design's live invariants passed, failed or could not run, and how long ago. With " + "run, a run now; with probes, the registry; with signals, every row of the signals table and the age " + "of its newest signal. Runs every five minutes on its own; each failure is an open condition.", Input: schema(map[string]string{ "run": "\"true\": run every probe now and answer the verdict", "probes": "\"true\": the registry — what each probe asserts, and the condition it raises", "signals": "\"true\": the signals table, each row with the age of its newest signal", "probe": "one probe's id (D14): its verdict alone, with every finding — the verdict shows at most twenty a probe", }, nil, "run", "probes", "signals")}, // How a module's new builds reach its machines, and the bus's planned step (novox/hq ADR 0236). {Name: "upgrade", Description: "Whether each module's new builds wait for a person (novox/hq ADR 0236, ADR 0539 clause 14): " + "for a module of policy record, the assigner writes no want naming a composition judged to recreate a container or move " + "data until the operator answers its ask; with where the policy comes from (a person, the module, the bus, its " + "irreplaceable data, the default) and why. With module, that one; with policy, a person's choice for it — roll-out, " + "record (with why) or default to take the choice back. The bus is never moved by the assigner (ADR 0539 clause 15).", Input: schema(map[string]string{ "module": "one module", "policy": "with module: roll-out, record or default", "why": "with policy: why — required for record, kept", }, nil)}, {Name: "bus", Description: "The bus as a person's step (novox/hq to-be 45 §8, ADR 0236, ADR 0539 clause 15): what a bus " + "upgrade would do — the bus's build on each machine against the one the mesh holds — and how the last step went. " + "With upgrade, check one: a person's act with why, after the streams are snapshotted (snapshot-taken says where, " + "while the mesh takes none itself), saying first whether it can be reverted; then it answers the want the operator " + "writes with the operator's credential, want.., recorded — nothing is sent, and the assigner never " + "moves the bus.", Input: schema(map[string]string{ "upgrade": "\"true\": start the bus's upgrade", "why": "with upgrade: why — required, recorded in the hand-act log", "cause": "with upgrade: the cause in a word (default bus-upgrade)", "reversible": "\"true\": with upgrade, the new version can be undone by putting the old one back", "irreversible": "\"true\": with upgrade, it cannot — your explicit word that it runs anyway", "snapshot-taken": "with upgrade: where the streams' snapshot you took is", }, nil, "upgrade", "reversible", "irreversible")}, // A consumer the mesh stopped asking for: retired, waiting for a person, deleted only by one // (novox/hq ADR 0230). {Name: "retire", Description: "A consumer the mesh stops asking for is retired by its provider — access " + "disabled, data kept — after the same answer in five passes; more than three at once, or more than half " + "of those held, waits for a person. With no answer, every provider that waits and what it would retire. " + "With answer approve or reject, a node and a module: retire exactly what that provider waits with, or " + "keep it active — a hand act, which says why (novox/hq ADR 0230).", Input: schema(map[string]string{ "answer": "approve or reject: answer what the provider waits with (needs node, module and why)", "node": "with answer: the machine the provider runs on", "module": "with answer: the provider module", "why": "with answer: why — required, and recorded in the hand-act log", "cause": "with answer: the cause in a word (retire-waiting when absent)", }, nil)}, {Name: "cleanup", Description: "Every consumer a provider holds retired — its age, its size where the " + "backend knows, and why it was retired — and every module's own data retired on its machine (ADR 0233). " + "With consumer (and node, module): the provider deletes that one retired consumer — never an active one; " + "for a module's own retired item, consumer names the item and the machine's backup holder takes a last " + "restore point of it, then deletes it. With older-than: every retired consumer older than that many " + "days, listed; deleted only with confirm. Deleting is a hand act, which says why (novox/hq ADR 0230).", Input: schema(map[string]string{ "node": "with consumer: the machine the provider runs on", "module": "with consumer: the provider module", "consumer": "delete this retired consumer (needs node, module and why)", "older-than": "delete every consumer retired more than this many days (needs why; lists only without confirm)", "confirm": "\"true\": with older-than, delete what is listed", "why": "with consumer or older-than: why — required, and recorded in the hand-act log", "cause": "with consumer or older-than: the cause in a word (cleanup-waiting when absent)", }, nil, "confirm")}, {Name: "judge-directories", Description: "Read-only: which of the directories one machine is declared its " + "node-engine's walk that follows no link another account could make would refuse (novox/hq issue 358), one line " + "each: placed, would be refused and why (the link and who could replace it), refused already by the placement " + "guard, or below a home. The engine judges the declaration it keeps and makes, chmods and chowns nothing.", Input: schema(map[string]string{ "node": "the machine's name", }, []string{"node"})}, // What a consumer gave up on (novox/hq issue 330): kept in DEAD_LETTERS until a person acts on it. {Name: "root-free", Description: "Whether each machine named is root-free now (novox/hq ADR 0259 §8): no agent " + "there can become root without a person. Judged when asked, never from a condition: free only when the machine " + "names an account its agents run as, its node-engine judged that account unable to become root within the " + "last 15 minutes, and the login shell's execute is not served there. Anything else, a read that failed " + "included, is not free and says why. The router asks it before an answer from a channel proving its sender " + "may approve. Only reads.", Input: listed(schema(map[string]string{ "machines": "the machines to judge, by name: a list, or one text separated by commas", }, []string{"machines"}), "machines")}, {Name: "dead-letters", Description: "Every message a consumer on the bus gave up on after handing it over " + "as often as it may, kept in DEAD_LETTERS: whose consumer, the subject, how often it was handed over and " + "when it was given up, newest first. With id: that one whole, with what it said. With deliver: hand it " + "again to the consumer that gave it up, and nobody else; with drop: let it go for good. Delivering and " + "dropping are hand acts, which say why (novox/hq issue 330).", Input: schema(map[string]string{ "id": "a dead letter's id, as the list gives it: that one whole, with its message", "consumer": "a consumer's name, or .: only what that one gave up on", "limit": "how many to list, newest first (default 50); only when listing", "deliver": "a dead letter's id: deliver it again to the consumer that gave it up (needs why)", "drop": "a dead letter's id: drop it for good (needs why)", "why": "with deliver or drop: why — required, and recorded in the hand-act log", "cause": "with deliver or drop: the cause in a word (dead-letter when absent)", }, nil)}, // The data every machine declares (novox/hq ADR 0233). {Name: "data", Description: "Every item of data every machine declares, as the self-check last measured it: " + "its class (irreplaceable, rebuildable, cache), where it is, its size, its newest write, its newest good " + "backup and the bound on it — and what is retired: kept after its module left the machine, removed only by " + "`cleanup delete` (novox/hq ADR 0233).", Input: schema(map[string]string{ "machine": "one machine (optional)", "retired": "\"true\": only what is retired", }, nil, "retired")}, {Name: "build", Description: "Removed in R4e (ADR 0501 §5 / ADR 0503 §3): refused by name, naming its replacement", Input: schema(nil, nil)}, // What the records say the registries may keep, asked on demand (novox/hq ADR 0251, to-be 51). {Name: "artifacts", Description: "Every artifact the mesh recorded making that lives in its artifact store, " + "each with its repository, digest, kind (image or archive) and state: kept — and why: a definition names " + "it, or one of the five most recent builds of a module the mesh holds — or eligible: made, kept for no " + "reason, not yet let go. Collected ones are counted, and listed only with collected. Reads only " + "(novox/hq ADR 0251).", Input: schema(map[string]string{ "repository": "only this repository's references, as / (the counts stay whole)", "collected": "\"true\": list the collected references too (they only grow; counted always)", }, nil, "collected")}, // Whether the artifact store holds what a builder's done key names, before the builder reuses it (novox/hq issue // 1937): the store's collector deletes a blob nothing holds, and a key the controller never took in holds nothing. {Name: "held", Description: "Whether the artifact store holds each reference given, asked with one HEAD each: " + "held, gone (the store answered 404) or unknown with why. The builder asks it before it reuses a done key, and " + "builds a key whose artifact is gone again. Reads only: nothing is held or written (novox/hq issue 1937).", Input: listed(schema(map[string]string{ "references": "the artifacts' references, artifact-store:////blobs/sha256:: a list, " + "or one text separated by commas", }, []string{"references"}), "references")}, {Name: "collect", Description: "The artifact store's sweep, now: what the mesh made and keeps for no reason " + "is let go of. Without confirm a dry run — every kept archive asked about with HEADs, what would be let go " + "listed, nothing held or deleted. With confirm and why: every kept archive held first, then each eligible " + "artifact let go of, oldest first, and recorded collected — a hand act, recorded with its why. A confirmed " + "collect lets an index go with the platform manifests no kept index names (read first; any read that " + "fails lets nothing go); the sweep after a build leaves an eligible index for such a collect (novox/hq ADR " + "0257). Bounded by " + "most (500 by default, at most 5000) and 45 seconds; what is left is said. Bytes are reclaimed by the " + "store's nightly collector. Never touches a digest the mesh did not record (novox/hq ADR 0189, ADR 0251).", Input: schema(map[string]string{ "confirm": "\"true\": let go of what is eligible (needs why); without it nothing is done", "why": "with confirm: why — required, and recorded in the hand-act log", "most": "let go of at most this many (500 by default, at most 5000)", }, nil, "confirm")}, {Name: "move-placement", Description: "The move of placement out of the settings layers, run once after the " + "delivery that holds it (novox/hq ADR 0348 clause 11, issue 736): every placement key a settings layer still " + "holds — endpoints, expose, reach and ports, which a layer set before then keeps among its settings — is put " + "beside the assignment, where places, accesses and networks already are. Without confirm it says which layers " + "hold which keys and moves nothing. With confirm and why: in one transaction, each such layer is kept whole " + "in its history first (why: moved to placement), its keys are laid over the placement beside it, and the " + "layer keeps its other keys, when it was set and who set it — and the act is recorded in the hand-act log. " + "Answers each layer moved: its machine (empty for the whole mesh), its module and its keys. Nothing a " + "machine is told changes: composition reads the keys as placement wherever they are. Asked again, it " + "moves nothing.", Input: schema(map[string]string{ "confirm": "\"true\": move them (needs why); without it nothing is moved", "why": "with confirm: why — required, and recorded in the hand-act log", }, nil, "confirm")}, {Name: "move-config", Description: "The move of config: every config value a settings layer holds is set in " + "mesh-config, run once after the delivery that holds it and after move-placement (novox/hq ADR 0348 §9, " + "issue 664). A machine's layer becomes the module's own value on that machine; a whole-mesh layer becomes " + "mesh config for the predefined key its value falls back to where that module alone falls back to it, and " + "otherwise the module's own value on each machine it is assigned to, laid under that machine's layer; a key " + "a layer says by its file's name is set under its config key. Without confirm it says each value it would " + "set, where from, each layer that stays (its module is in the settings form) and anything that refuses the " + "move, and sets nothing. With confirm and why: each value is set through mesh-config in the controller's " + "name, every module's resolved values are read back and compared with what its layers lay today, and only " + "then, in one transaction, is each layer moved kept whole in its history first (why: moved to mesh-config) " + "and emptied, and the controller switched to read mesh-config where it read the layers — the act recorded " + "in the hand-act log. Nothing a machine is told changes. Asked again, it sets nothing set already.", Input: schema(map[string]string{ "confirm": "\"true\": set the values and switch (needs why); without it nothing is set", "why": "with confirm: why — required, and recorded in the hand-act log and with each value", }, nil, "confirm")}, {Name: "mirrors", Description: "The bases the mesh copied into its artifact store for builds to stand on, as " + "the records hold them: each with its state — kept because a kept build stood on it, or eligible — and " + "which build or person recorded it. With record: references of copies no record names (a copy made " + "before copies were recorded, or by a build that failed before then) to record as the mesh's, so the " + "sweep keeps or lets go of them like any other; only a repository the mirror writes is taken " + "(upstream//, or a module's /on-), and each must be in the store. Without " + "confirm a dry run; with confirm and why, recorded — a hand act. A recorded copy is eligible: the next " + "sweep lets it go unless a kept build stood on it. Records only: this verb deletes nothing " + "(novox/hq ADR 0257).", Input: schema(map[string]string{ "record": "references to record, artifact-store://@sha256:, separated by spaces or commas", "confirm": "\"true\": record them (needs why); without it nothing is recorded", "why": "with confirm: why — required, and recorded with each copy and in the hand-act log", }, nil, "confirm")}, {Name: "images", Description: "Every container image one machine's declaration names — the declaration the " + "mesh would send it now and the one it was last sent — each with the resources naming it: what the " + "machine keeps when it prunes its images. sent_known is false when the last one sent is not kept with " + "its images. Reads only (novox/hq ADR 0251).", Input: schema(map[string]string{ "node": "the machine's name", }, []string{"node"})}, } // schema is a JSON schema for an object of string properties, which is every argument the verbs // above take. Kept small on purpose: a schema an agent cannot read is a tool it cannot call. The // switches are the properties that are "true" or "false" and nothing else, said in the schema as an // enum so a caller sees it and the verb can refuse any other word rather than read it as false. // // **The schema is the whole of what a verb takes** (novox/hq issue 244): an argument it does not // name is refused when the verb is called, never passed over. func schema(properties map[string]string, required []string, switches ...string) map[string]any { isSwitch := map[string]bool{} for _, s := range switches { if _, declared := properties[s]; !declared { panic("a switch that is not a property: " + s) } isSwitch[s] = true } props := map[string]any{} for name, description := range properties { p := map[string]any{"type": "string", "description": description} if isSwitch[name] { p["enum"] = []string{"true", "false"} } props[name] = p } out := map[string]any{"type": "object", "properties": props} if len(required) > 0 { out["required"] = required } return out } // listed makes the named properties of a schema lists of text: a caller gives them as a JSON array (or, as // any argument, one text separated by commas). func listed(in map[string]any, names ...string) map[string]any { props, _ := in["properties"].(map[string]any) for _, n := range names { p, _ := props[n].(map[string]any) if p == nil { panic("a list that is not a property: " + n) } props[n] = map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": p["description"]} } return in } // unpromised is what a claim says it serves and the seat's protocol never promised. func unpromised(serves []string, promised []Verb) []string { has := map[string]bool{} for _, v := range promised { has[v.Name] = true } var extra []string for _, s := range serves { if !has[s] { extra = append(extra, s) } } return extra } // unservedVerbs is what a seat promises and a claimant's offer for it does not answer. func unservedVerbs(tools []string, promised []Verb) []string { has := map[string]bool{} for _, t := range tools { has[t] = true } var missing []string for _, v := range promised { if !has[v.Name] && !v.Optional { missing = append(missing, v.Name) } } return missing }