1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum TierHint {
33 Read,
34 Write,
35 Execute,
36 Destructive,
37 External,
38}
39
40impl TierHint {
41 pub fn as_str(&self) -> &'static str {
42 match self {
43 Self::Read => "read",
44 Self::Write => "write",
45 Self::Execute => "execute",
46 Self::Destructive => "destructive",
47 Self::External => "external",
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy)]
57pub struct VerbSpec {
58 pub ns: &'static str,
59 pub action: &'static str,
60 pub tier_hint: TierHint,
61 pub summary: &'static str,
62}
63
64impl VerbSpec {
65 pub fn dotted(&self) -> String {
67 format!("{}.{}", self.ns, self.action)
68 }
69}
70
71pub const VERBS: &[VerbSpec] = &[
84 VerbSpec {
86 ns: "memory",
87 action: "store",
88 tier_hint: TierHint::Write,
89 summary: "Store a subject-predicate-object fact in semantic memory.",
90 },
91 VerbSpec {
92 ns: "memory",
93 action: "delete",
94 tier_hint: TierHint::Destructive,
95 summary: "Delete matching semantic facts (irreversible).",
96 },
97 VerbSpec {
98 ns: "memory",
99 action: "import",
100 tier_hint: TierHint::Write,
101 summary: "Bulk-import memory entries from a filesystem path.",
102 },
103 VerbSpec {
104 ns: "memory",
105 action: "export",
106 tier_hint: TierHint::Write,
107 summary: "Bulk-export memory entries to a filesystem path.",
108 },
109 VerbSpec {
111 ns: "shell",
112 action: "exec",
113 tier_hint: TierHint::Execute,
114 summary: "Run a one-shot sandboxed command via the action dispatcher.",
115 },
116 VerbSpec {
117 ns: "terminal",
118 action: "open",
119 tier_hint: TierHint::Execute,
120 summary: "Open a persistent PTY session through the terminal bridge.",
121 },
122 VerbSpec {
123 ns: "terminal",
124 action: "close",
125 tier_hint: TierHint::Write,
126 summary: "Close a previously-opened PTY session.",
127 },
128 VerbSpec {
130 ns: "net",
131 action: "http",
132 tier_hint: TierHint::External,
133 summary: "Perform an outbound HTTP request (web search, fetch).",
134 },
135 VerbSpec {
136 ns: "net",
137 action: "check",
138 tier_hint: TierHint::External,
139 summary: "Check reachability of a host: DNS resolution + timed TCP connect.",
140 },
141 VerbSpec {
142 ns: "net",
143 action: "trace",
144 tier_hint: TierHint::External,
145 summary: "Trace the network route (hops) to a host.",
146 },
147 VerbSpec {
148 ns: "net",
149 action: "cert",
150 tier_hint: TierHint::External,
151 summary: "Inspect the TLS certificate chain a host presents (validity, SANs).",
152 },
153 VerbSpec {
154 ns: "notify",
155 action: "send",
156 tier_hint: TierHint::External,
157 summary: "Send a message through the channel dispatcher.",
158 },
159 VerbSpec {
161 ns: "schedule",
165 action: "create",
166 tier_hint: TierHint::External,
167 summary: "Persist a scheduled-intent row.",
168 },
169 VerbSpec {
170 ns: "schedule",
171 action: "cancel",
172 tier_hint: TierHint::Write,
173 summary: "Cancel a scheduled-intent row.",
174 },
175 VerbSpec {
176 ns: "task",
177 action: "decompose",
178 tier_hint: TierHint::Execute,
179 summary: "Decompose a request into an orchestrator task plan.",
180 },
181 VerbSpec {
182 ns: "task",
183 action: "cancel",
184 tier_hint: TierHint::Write,
185 summary: "Abort a running orchestrator task.",
186 },
187 VerbSpec {
188 ns: "signal",
189 action: "cancel",
190 tier_hint: TierHint::Write,
191 summary: "Cancel an in-flight signal pipeline run.",
192 },
193 VerbSpec {
195 ns: "agent",
196 action: "delegate",
197 tier_hint: TierHint::Execute,
198 summary: "Hand a subtask to a discovered subprocess agent.",
199 },
200 VerbSpec {
202 ns: "approval",
203 action: "respond",
204 tier_hint: TierHint::Write,
205 summary: "Approve or reject a pending confirmation by nonce.",
206 },
207 VerbSpec {
208 ns: "approval",
209 action: "revoke",
210 tier_hint: TierHint::Write,
211 summary: "Revoke a standing approval grant.",
212 },
213 VerbSpec {
214 ns: "audit",
215 action: "prune",
216 tier_hint: TierHint::Destructive,
217 summary: "Prune audit-log rows older than the given duration.",
218 },
219 VerbSpec {
220 ns: "security",
221 action: "audit",
222 tier_hint: TierHint::Read,
223 summary: "Audit the security posture of the current configuration.",
224 },
225 VerbSpec {
227 ns: "logs",
228 action: "analyze",
229 tier_hint: TierHint::Read,
230 summary: "Analyse recent logs for recurring error/warning patterns.",
231 },
232 VerbSpec {
233 ns: "baseline",
234 action: "capture",
235 tier_hint: TierHint::Write,
236 summary: "Capture a system-baseline snapshot for later drift detection.",
237 },
238 VerbSpec {
239 ns: "baseline",
240 action: "diff",
241 tier_hint: TierHint::Read,
242 summary: "Diff a baseline against another snapshot or the live state.",
243 },
244 VerbSpec {
245 ns: "baseline",
246 action: "list",
247 tier_hint: TierHint::Read,
248 summary: "List stored baseline snapshots.",
249 },
250 VerbSpec {
252 ns: "channel",
253 action: "configure",
254 tier_hint: TierHint::Write,
255 summary: "Adjust a channel-preference weight or pin.",
256 },
257 VerbSpec {
258 ns: "proactivity",
259 action: "configure",
260 tier_hint: TierHint::Write,
261 summary: "Toggle or rate-limit the proactivity engine.",
262 },
263 VerbSpec {
265 ns: "fs",
266 action: "read",
267 tier_hint: TierHint::Read,
268 summary: "Read a filesystem path inside security.allowed_paths.",
269 },
270 VerbSpec {
272 ns: "mcp",
273 action: "mount",
274 tier_hint: TierHint::External,
275 summary: "Mount an external MCP server (stdio or HTTP transport).",
276 },
277 VerbSpec {
278 ns: "mcp",
279 action: "unmount",
280 tier_hint: TierHint::Write,
281 summary: "Unmount a previously-mounted MCP server.",
282 },
283];
284
285pub fn lookup(ns: &str, action: &str) -> Option<&'static VerbSpec> {
292 VERBS.iter().find(|v| v.ns == ns && v.action == action)
293}
294
295pub fn namespaces() -> Vec<&'static str> {
298 let mut out: Vec<&'static str> = VERBS.iter().map(|v| v.ns).collect();
299 out.sort_unstable();
300 out.dedup();
301 out
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use std::collections::HashSet;
308
309 #[test]
313 fn verb_pairs_are_unique() {
314 let mut seen: HashSet<(&str, &str)> = HashSet::new();
315 for v in VERBS {
316 assert!(
317 seen.insert((v.ns, v.action)),
318 "duplicate verb pair: {}.{}",
319 v.ns,
320 v.action
321 );
322 }
323 }
324
325 #[test]
328 fn every_verb_has_a_summary() {
329 for v in VERBS {
330 assert!(
331 !v.summary.trim().is_empty(),
332 "{}.{} has empty summary",
333 v.ns,
334 v.action
335 );
336 }
337 }
338
339 #[test]
340 fn lookup_finds_known_pair() {
341 let v = lookup("memory", "store").expect("memory.store registered");
342 assert_eq!(v.tier_hint, TierHint::Write);
343 assert_eq!(v.dotted(), "memory.store");
344 }
345
346 #[test]
347 fn tier_hint_string_form_is_stable() {
348 assert_eq!(TierHint::Read.as_str(), "read");
349 assert_eq!(TierHint::Destructive.as_str(), "destructive");
350 }
351
352 #[test]
353 fn lookup_returns_none_for_unknown_pair() {
354 assert!(lookup("not", "real").is_none());
355 assert!(lookup("mcp", "any_tool_name").is_none());
357 }
358
359 #[test]
360 fn namespaces_are_deduped_and_sorted() {
361 let ns = namespaces();
362 let mut sorted = ns.clone();
363 sorted.sort_unstable();
364 assert_eq!(ns, sorted);
365 let mut deduped = ns.clone();
366 deduped.dedup();
367 assert_eq!(ns, deduped);
368 assert!(ns.contains(&"memory"));
369 assert!(ns.contains(&"mcp"));
370 }
371
372 #[test]
373 fn vocabulary_contains_expected_size() {
374 assert_eq!(VERBS.len(), 31);
380 }
381}