Say what each verb replaces, so the agent is pointed at it instead of a shell command (hq ADR 0245)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
A seat's verb names the shell commands it is the mesh's way to do, and a module says it for its own tools in its manifest; the tools verb and module list --json carry both, for the mesh MCP server's search, the agent's instructions and the guard on its shell.
This commit is contained in:
@@ -426,6 +426,13 @@ type Manifest struct {
|
||||
// module claiming a seat answers what that seat's protocol promises (novox/hq ADR 0118).
|
||||
Tools []string `json:"tools,omitempty"`
|
||||
|
||||
// Replaces says, for a tool of this module's own, the shell commands it is the mesh's way to do —
|
||||
// `{"docker_logs": ["docker logs"]}` — as a seat's verb says it in its definition (novox/hq ADR
|
||||
// 0241). A seat's verb carries its own: what a role replaces is the role's, so a module never says it
|
||||
// for a verb it serves under a claim. The console's search, the agent's instructions and the guard on
|
||||
// its shell are built from both.
|
||||
Replaces map[string][]string `json:"replaces,omitempty"`
|
||||
|
||||
// Instances says whether this module's instances are the same anywhere — `interchangeable` —
|
||||
// so a call that names no machine may be answered by any of them (novox/hq ADR 0160). A fact
|
||||
// about the software, not about the bus: a stateless web tool says it; a database does not,
|
||||
@@ -1692,6 +1699,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
}
|
||||
}
|
||||
problems = append(problems, invokeProblems(m)...)
|
||||
problems = append(problems, replacesProblems(m)...)
|
||||
problems = append(problems, endpointNameProblems(m)...)
|
||||
problems = append(problems, RouteProblems(m)...)
|
||||
for _, port := range m.Guards {
|
||||
@@ -2306,6 +2314,53 @@ func EndpointPort(m Manifest, name string) (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// MaxReplaced is the longest command a tool may say it replaces: a command's name and the words that
|
||||
// make it this command, never a script.
|
||||
const MaxReplaced = 80
|
||||
|
||||
// replacesProblems judges what a module says its tools replace (novox/hq ADR 0245): each key a tool it
|
||||
// declares, each entry a command line of one line, short, and said once.
|
||||
func replacesProblems(m Manifest) []string {
|
||||
var problems []string
|
||||
tools := map[string]bool{}
|
||||
for _, t := range m.Tools {
|
||||
tools[t] = true
|
||||
}
|
||||
keys := make([]string, 0, len(m.Replaces))
|
||||
for k := range m.Replaces {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, tool := range keys {
|
||||
if !tools[tool] {
|
||||
problems = append(problems, fmt.Sprintf("%s says what %q replaces, and declares no such tool: "+
|
||||
"a module says it for a tool in its `tools`; a seat's verb says it in the seat's definition "+
|
||||
"(novox/hq ADR 0245)", m.Module, tool))
|
||||
continue
|
||||
}
|
||||
if len(m.Replaces[tool]) == 0 {
|
||||
problems = append(problems, fmt.Sprintf("%s says %s replaces nothing: leave it out", m.Module, tool))
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, c := range m.Replaces[tool] {
|
||||
c = strings.TrimSpace(c)
|
||||
switch {
|
||||
case c == "":
|
||||
problems = append(problems, fmt.Sprintf("%s: %s replaces an empty command", m.Module, tool))
|
||||
case strings.ContainsAny(c, "\n\r"):
|
||||
problems = append(problems, fmt.Sprintf("%s: %s replaces %q, which is more than one line", m.Module, tool, c))
|
||||
case len(c) > MaxReplaced:
|
||||
problems = append(problems, fmt.Sprintf("%s: %s replaces %q, longer than %d characters: "+
|
||||
"the command's name and the words that make it this command", m.Module, tool, c, MaxReplaced))
|
||||
case seen[c]:
|
||||
problems = append(problems, fmt.Sprintf("%s: %s replaces %q twice", m.Module, tool, c))
|
||||
}
|
||||
seen[c] = true
|
||||
}
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
// invokeProblems judges what a module says it calls (novox/hq ADR 0152).
|
||||
//
|
||||
// Refused here, in the manifest's words, rather than at the next composition of the bus's user
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// What a move does to a module's containers (novox/hq ADR 0242).
|
||||
// What a move does to a module's containers (novox/hq ADR 0245).
|
||||
//
|
||||
// A container whose declaration changes is recreated, whatever changed in it: an image, an environment
|
||||
// variable, a health check adopted. A module whose containers are recreated in one send is down while
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The verbs an agent works around most say what they replace (novox/hq ADR 0245): the journal, a
|
||||
// restart, a unit's status, the hosts file — each the command the agent would otherwise type over ssh.
|
||||
func TestTheVerbsWorkedAroundSayWhatTheyReplace(t *testing.T) {
|
||||
want := map[string]string{
|
||||
ServiceManagerSeat + ".journal": "journalctl",
|
||||
ServiceManagerSeat + ".restart": "systemctl restart",
|
||||
ServiceManagerSeat + ".status": "systemctl status",
|
||||
"node-hostname.add": "edit /etc/hosts",
|
||||
ControllerSeatName + ".node": "hostnamectl",
|
||||
}
|
||||
for _, s := range DefaultSeats() {
|
||||
for _, v := range s.Serves {
|
||||
cmd, ok := want[s.Name+"."+v.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
delete(want, s.Name+"."+v.Name)
|
||||
found := false
|
||||
for _, r := range v.Replaces {
|
||||
found = found || r == cmd
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("%s.%s does not say it replaces %q: %v", s.Name, v.Name, cmd, v.Replaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
for verb := range want {
|
||||
t.Errorf("%s is not a verb of the compiled seats", verb)
|
||||
}
|
||||
}
|
||||
|
||||
// Every command a seat's verb replaces is one short line, said once — what the guard and the search match.
|
||||
func TestEveryReplacedCommandIsOneShortLine(t *testing.T) {
|
||||
for _, s := range DefaultSeats() {
|
||||
for _, v := range s.Serves {
|
||||
seen := map[string]bool{}
|
||||
for _, r := range v.Replaces {
|
||||
if strings.TrimSpace(r) == "" || strings.ContainsAny(r, "\n\r") || len(r) > MaxReplaced || seen[r] {
|
||||
t.Errorf("%s.%s replaces %q, which is not one short line said once", s.Name, v.Name, r)
|
||||
}
|
||||
seen[r] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A verb's definition in a manifest may say what it replaces, like the mesh's own.
|
||||
func TestAVerbDefinitionCarriesWhatItReplaces(t *testing.T) {
|
||||
m, err := ParseManifest([]byte(`{"module":"till","version":"1","tools":["refund"],` +
|
||||
`"seats":[{"name":"shop-till","serves":[{"name":"refund","replaces":["psql -c refund"]}]}],` +
|
||||
`"claims":[{"name":"shop-till","scope":"mesh"}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := m.DefinesSeats[0].Serves[0].Replaces; len(got) != 1 || got[0] != "psql -c refund" {
|
||||
t.Fatalf("replaces not read: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A module says what its own tools replace, and only for a tool it declares.
|
||||
func TestAModuleSaysWhatItsOwnToolsReplace(t *testing.T) {
|
||||
m, err := ParseManifest([]byte(`{"module":"docker","version":"1","tools":["docker_logs","docker_list"],` +
|
||||
`"replaces":{"docker_logs":["docker logs"],"docker_list":["docker ps"]}}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.Replaces["docker_logs"][0] != "docker logs" || m.Replaces["docker_list"][0] != "docker ps" {
|
||||
t.Fatalf("replaces not read: %v", m.Replaces)
|
||||
}
|
||||
for _, c := range []struct{ manifest, says string }{
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_lgos":["docker logs"]}}`,
|
||||
"declares no such tool"},
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_logs":[]}}`, "replaces nothing"},
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_logs":[" "]}}`, "empty command"},
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_logs":["docker logs\nrm -rf /"]}}`,
|
||||
"more than one line"},
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_logs":["docker logs","docker logs"]}}`,
|
||||
"twice"},
|
||||
{`{"module":"docker","version":"1","tools":["docker_logs"],"replaces":{"docker_logs":["` + strings.Repeat("x", MaxReplaced+1) + `"]}}`,
|
||||
"longer than"},
|
||||
} {
|
||||
if _, err := ParseManifest([]byte(c.manifest)); err == nil || !strings.Contains(err.Error(), c.says) {
|
||||
t.Errorf("%s: want a refusal saying %q, got %v", c.manifest, c.says, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A seat row the store seeded before `replaces` existed carries none; the working set takes the compiled
|
||||
// one, so the `tools` answer says it on a mesh whose rows exist.
|
||||
func TestASeededRowTakesWhatItsVerbsReplaceFromTheCompiledSeat(t *testing.T) {
|
||||
was := Seats()
|
||||
t.Cleanup(func() { UseSeats(was) })
|
||||
var row Seat
|
||||
for _, s := range DefaultSeats() {
|
||||
if s.Name == ServiceManagerSeat {
|
||||
row = s
|
||||
}
|
||||
}
|
||||
stored := make([]Verb, len(row.Serves))
|
||||
for i, v := range row.Serves {
|
||||
v.Replaces = nil
|
||||
stored[i] = v
|
||||
}
|
||||
row.Serves = stored
|
||||
UseSeats([]Seat{row})
|
||||
got, _ := SeatNamed(ServiceManagerSeat)
|
||||
for _, v := range got.Serves {
|
||||
if v.Name == "journal" && (len(v.Replaces) == 0 || v.Replaces[0] != "journalctl") {
|
||||
t.Fatalf("the seeded journal verb says it replaces %v", v.Replaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-25
@@ -102,10 +102,10 @@ var defaultSeats = append([]Seat{
|
||||
{Name: "mesh-store", Scope: ScopeMesh, Delivers: "postgres-database", Decision: "novox/hq ADR 0079",
|
||||
Serves: []Verb{
|
||||
{Name: "databases", Description: "Every database the store holds, with its on-disk size.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
Input: schema(map[string]string{}, nil), Replaces: []string{"psql -l"}},
|
||||
{Name: "query", Description: "One read-only statement against one database the store holds.",
|
||||
Input: schema(map[string]string{"database": "the database to query", "sql": "the read-only statement"},
|
||||
[]string{"database", "sql"})},
|
||||
[]string{"database", "sql"}), Replaces: []string{"psql"}},
|
||||
}},
|
||||
// **Delivers the mesh's own bus, not `amqp`.** Those were the same word until
|
||||
// ADR 0127 separated them: `amqp` is a backing service a module may require, and this seat is
|
||||
@@ -174,15 +174,16 @@ var defaultSeats = append([]Seat{
|
||||
Serves: []Verb{
|
||||
{Name: "entries", Description: "Every line of this machine's /etc/hosts, each marked whose it is: " +
|
||||
"the operator's, or the block of the module or tool that writes it.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
Input: schema(map[string]string{}, nil), Replaces: []string{"cat /etc/hosts", "getent hosts"}},
|
||||
{Name: "add", Description: "Add one address and its names to the operator's lines of this machine's " +
|
||||
"/etc/hosts — a name for this machine's own programs, not the mesh's.",
|
||||
Input: schema(map[string]string{"address": "the IPv4 or IPv6 address",
|
||||
"names": "the names for it, separated by spaces"}, []string{"address", "names"})},
|
||||
"names": "the names for it, separated by spaces"}, []string{"address", "names"}),
|
||||
Replaces: []string{"edit /etc/hosts", "HOSTALIASES"}},
|
||||
{Name: "remove", Description: "Remove one name, or every line of one address, from the operator's " +
|
||||
"lines of this machine's /etc/hosts. A line a module writes is refused, naming the module.",
|
||||
Input: schema(map[string]string{"name": "a host name, or an address to remove every line of"},
|
||||
[]string{"name"})},
|
||||
[]string{"name"}), Replaces: []string{"sed -i /etc/hosts"}},
|
||||
}},
|
||||
// The intrusion prevention's verbs (novox/hq ADR 0179): what a person asks a machine's ban list
|
||||
// whatever keeps it — who is banned and why, ban one address, let one go. Every holder serves all
|
||||
@@ -191,15 +192,17 @@ var defaultSeats = append([]Seat{
|
||||
Serves: []Verb{
|
||||
{Name: "status", Description: "Every jail on this machine with how many it is watching and " +
|
||||
"holding now, and the totals since the jail started; one jail's detail when named.",
|
||||
Input: schema(map[string]string{"jail": "one jail (optional)"}, nil)},
|
||||
Input: schema(map[string]string{"jail": "one jail (optional)"}, nil), Replaces: []string{"fail2ban-client status"}},
|
||||
{Name: "banned", Description: "Every address banned on this machine right now, with the jail " +
|
||||
"that holds it and when the ban ends.",
|
||||
Input: schema(map[string]string{"jail": "one jail (optional)"}, nil)},
|
||||
Input: schema(map[string]string{"jail": "one jail (optional)"}, nil), Replaces: []string{"fail2ban-client banned"}},
|
||||
{Name: "ban", Description: "Ban one address in one jail now, for the jail's ban time — an " +
|
||||
"operator's act on the live ban list, which the mesh never writes itself.",
|
||||
Input: schema(map[string]string{"ip": "the address", "jail": "the jail to hold it"}, []string{"ip", "jail"})},
|
||||
Input: schema(map[string]string{"ip": "the address", "jail": "the jail to hold it"}, []string{"ip", "jail"}),
|
||||
Replaces: []string{"fail2ban-client set banip"}},
|
||||
{Name: "unban", Description: "Let one address go, from one jail or from every jail when none is named.",
|
||||
Input: schema(map[string]string{"ip": "the address", "jail": "one jail (optional)"}, []string{"ip"})},
|
||||
Input: schema(map[string]string{"ip": "the address", "jail": "one jail (optional)"}, []string{"ip"}),
|
||||
Replaces: []string{"fail2ban-client unban", "fail2ban-client set unbanip"}},
|
||||
}},
|
||||
// The packet filter's verbs (novox/hq ADR 0170): what a person asks a machine's filter whatever
|
||||
// filter answers — the rules as enforced, reload the mesh's own, remove one thing the mesh did
|
||||
@@ -210,15 +213,17 @@ var defaultSeats = append([]Seat{
|
||||
"ruleset and, where the tool exists, the legacy filter's listings. Narrowed to one table or " +
|
||||
"chain when asked.",
|
||||
Input: schema(map[string]string{"table": "one nftables table, as `family name` (optional)",
|
||||
"chain": "one chain of that table (optional)"}, nil)},
|
||||
"chain": "one chain of that table (optional)"}, nil),
|
||||
Replaces: []string{"nft list ruleset", "iptables -L", "iptables-save"}},
|
||||
{Name: "reload", Description: "Load the mesh's own filter again from the file the mesh writes, " +
|
||||
"and answer with the mesh's table as loaded.",
|
||||
Input: schema(map[string]string{}, nil)},
|
||||
Input: schema(map[string]string{}, nil), Replaces: []string{"nft -f"}},
|
||||
{Name: "remove", Description: "Remove one rule set the mesh did not write, named exactly as the " +
|
||||
"host reports it (novox/hq ADR 0168) — `chain X (iptables-legacy)` or `table ip6 filter, chain " +
|
||||
"DOCKER-USER`. Refuses the mesh's tables, the runtime's own chains, a built-in chain and an " +
|
||||
"active found firewall's chains. An operator's act, by name, never a flush.",
|
||||
Input: schema(map[string]string{"where": "the rule set, as `node show` lists it"}, []string{"where"})},
|
||||
Input: schema(map[string]string{"where": "the rule set, as `node show` lists it"}, []string{"where"}),
|
||||
Replaces: []string{"nft delete", "iptables -X"}},
|
||||
}},
|
||||
// The machine's service manager (novox/hq ADR 0177). The host applies every declared unit,
|
||||
// system or user scope; the holder answers questions and operator acts about them, each verb
|
||||
@@ -349,19 +354,32 @@ func UseSeats(s []Seat) {
|
||||
// is never stored (Verb.Optional), and a row is what the working set holds once the store is read: a verb
|
||||
// the seeding added to the row — `failed`, `checks` — would otherwise come back required, and every holder
|
||||
// not serving it yet would be refused at registration and at handover, which the mark exists to prevent.
|
||||
//
|
||||
// **And what a verb replaces is the compiled one** (novox/hq ADR 0245): a row seeded before the field
|
||||
// existed carries none, and re-seeding widens a verb's arguments only, so read from the row alone the
|
||||
// `tools` answer — what the mesh MCP server's search and the agent's instructions are built from — would
|
||||
// say no verb replaces anything on a mesh whose rows exist. The mesh's statement of what a role replaces
|
||||
// is this binary's, like the optional mark.
|
||||
func optionalAsCompiled(row, compiled []Verb) []Verb {
|
||||
optional := map[string]bool{}
|
||||
replaces := map[string][]string{}
|
||||
for _, v := range compiled {
|
||||
if v.Optional {
|
||||
optional[v.Name] = true
|
||||
}
|
||||
if len(v.Replaces) > 0 {
|
||||
replaces[v.Name] = v.Replaces
|
||||
}
|
||||
}
|
||||
if len(optional) == 0 {
|
||||
if len(optional) == 0 && len(replaces) == 0 {
|
||||
return row
|
||||
}
|
||||
out := make([]Verb, len(row))
|
||||
for i, v := range row {
|
||||
v.Optional = optional[v.Name]
|
||||
if r, ok := replaces[v.Name]; ok {
|
||||
v.Replaces = r
|
||||
}
|
||||
out[i] = v
|
||||
}
|
||||
return out
|
||||
@@ -597,19 +615,19 @@ func serviceManagerVerbs() []Verb {
|
||||
unit := map[string]string{"unit": "the unit's name, as the service manager knows it"}
|
||||
return []Verb{
|
||||
{Name: "units", Description: "The units the service manager knows in a scope, each with its load, active and sub state; narrowed to a pattern when asked.",
|
||||
Input: scoped(map[string]string{"pattern": "a glob the unit's name must match (optional)"}, nil)},
|
||||
Input: scoped(map[string]string{"pattern": "a glob the unit's name must match (optional)"}, nil), Replaces: []string{"systemctl list-units"}},
|
||||
{Name: "status", Description: "One unit as the service manager sees it now: its states, whether it starts at boot, its main process, and whether the mesh declares it.",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl status", "systemctl is-active"}},
|
||||
{Name: "start", Description: "Start one unit. For a unit the mesh declares, the answer says the host will restore what its declaration says at the next apply.",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl start"}},
|
||||
{Name: "stop", Description: "Stop one unit; for a mesh-declared unit the answer says the host will restore its declared state.",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl stop"}},
|
||||
{Name: "restart", Description: "Restart one unit.",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl restart"}},
|
||||
{Name: "enable", Description: "Make one unit start at boot (or at the account's login, in user scope).",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl enable"}},
|
||||
{Name: "disable", Description: "Stop one unit starting at boot (or at login, in user scope).",
|
||||
Input: scoped(unit, []string{"unit"})},
|
||||
Input: scoped(unit, []string{"unit"}), Replaces: []string{"systemctl disable"}},
|
||||
// **A window, not only a tail** (the operator's direction 2026-10-07): an incident is read for the
|
||||
// minutes it happened in, and with no window on the verb a person reached for a shell. Every
|
||||
// argument is the holder's to validate — passed to journalctl as one word of its own, never through
|
||||
@@ -624,14 +642,16 @@ func serviceManagerVerbs() []Verb {
|
||||
"until": "the window's end, in the same forms (optional; now when absent)",
|
||||
"match": "only the lines holding this text, as written — a fixed string, not a pattern (optional)",
|
||||
"priority": "only entries this severe or more: 0-7 or emerg, alert, crit, err, warning, notice, info, debug (optional)",
|
||||
}, []string{"unit"})},
|
||||
}, []string{"unit"}),
|
||||
Replaces: []string{"journalctl"}},
|
||||
// What has failed, on the seat rather than as one holder's own tool: whatever holds the role answers
|
||||
// it, so a caller asks every machine the same way. **Optional while its holders catch up**: the
|
||||
// systemd module running today serves it as its own systemd_failed, and a required verb would
|
||||
// refuse it before the version serving `failed` could be delivered.
|
||||
{Name: "failed", Optional: true, Description: "Every failed unit on this machine, in the system manager and in the operator " +
|
||||
"account's; a manager that does not answer is reported with its error, never as nothing failed.",
|
||||
Input: schema(map[string]string{"scope": "\"system\" or \"user\": only that manager (both when absent)"}, nil)},
|
||||
Input: schema(map[string]string{"scope": "\"system\" or \"user\": only that manager (both when absent)"}, nil),
|
||||
Replaces: []string{"systemctl --failed"}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,10 +664,10 @@ func backupVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "backed-up", Description: "What this machine backs up: each module, what it declared, " +
|
||||
"its last good night, how many restore points are kept and the repository's size.",
|
||||
Input: schema(map[string]string{"module": "one module (optional)"}, nil)},
|
||||
Input: schema(map[string]string{"module": "one module (optional)"}, nil), Replaces: []string{"restic snapshots"}},
|
||||
{Name: "now", Description: "Take a backup now, of one module or of every module on this " +
|
||||
"machine — before a migration, a retirement or anything else that could go wrong.",
|
||||
Input: schema(map[string]string{"module": "one module (optional)"}, nil)},
|
||||
Input: schema(map[string]string{"module": "one module (optional)"}, nil), Replaces: []string{"restic backup"}},
|
||||
{Name: "restore", Description: "Restore one module's data from a restore point BESIDE the live " +
|
||||
"data, never over it: each directory as <path>.restored-<date>. Swapping it in is a " +
|
||||
"person's act. Lists the restore points when none is named.",
|
||||
@@ -655,7 +675,7 @@ func backupVerbs() []Verb {
|
||||
"module": "the module",
|
||||
"snapshot": "the restore point (from `backed-up`; the newest when omitted)",
|
||||
"path": "one of the module's directories (all of them when omitted)",
|
||||
}, []string{"module"})},
|
||||
}, []string{"module"}), Replaces: []string{"restic restore"}},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ type Verb struct {
|
||||
// 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 {
|
||||
@@ -94,7 +101,8 @@ var ControllerVerbs = []Verb{
|
||||
{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"})},
|
||||
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)},
|
||||
|
||||
Reference in New Issue
Block a user