1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ActionCategory {
36 Dml,
38 Ddl,
40 Schema,
42 Function,
44 Mgmt,
46 Policy,
48 User,
50 Admin,
52 Config,
54 Vault,
56 Wildcard,
58 Ai,
60 Notification,
63 Stream,
67 Queue,
71 Graph,
76 Ops,
84 Vector,
90 Other,
93}
94
95impl ActionCategory {
96 pub fn as_str(&self) -> &'static str {
100 match self {
101 ActionCategory::Dml => "dml",
102 ActionCategory::Ddl => "ddl",
103 ActionCategory::Schema => "schema",
104 ActionCategory::Function => "function",
105 ActionCategory::Mgmt => "mgmt",
106 ActionCategory::Policy => "policy",
107 ActionCategory::User => "user",
108 ActionCategory::Admin => "admin",
109 ActionCategory::Config => "config",
110 ActionCategory::Vault => "vault",
111 ActionCategory::Wildcard => "wildcard",
112 ActionCategory::Ai => "ai",
113 ActionCategory::Notification => "notification",
114 ActionCategory::Stream => "stream",
115 ActionCategory::Queue => "queue",
116 ActionCategory::Graph => "graph",
117 ActionCategory::Ops => "ops",
118 ActionCategory::Vector => "vector",
119 ActionCategory::Other => "other",
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126pub enum LifecycleState {
127 Active,
129 Deprecated {
131 replacement: Option<&'static str>,
133 since_version: &'static str,
135 },
136 Removed,
140}
141
142#[derive(Debug, Clone)]
144pub struct ActionEntry {
145 pub name: &'static str,
146 pub category: ActionCategory,
147 pub lifecycle_state: LifecycleState,
148 pub gates_description: &'static str,
149}
150
151pub const ACTIONS: &[ActionEntry] = &[
160 ActionEntry {
161 name: "cache:read",
162 category: ActionCategory::Other,
163 lifecycle_state: LifecycleState::Active,
164 gates_description: "read or probe a native Blob Cache namespace",
165 },
166 ActionEntry {
167 name: "cache:write",
168 category: ActionCategory::Other,
169 lifecycle_state: LifecycleState::Active,
170 gates_description: "write values to a native Blob Cache namespace",
171 },
172 ActionEntry {
173 name: "cache:invalidate",
174 category: ActionCategory::Other,
175 lifecycle_state: LifecycleState::Active,
176 gates_description: "invalidate or flush a native Blob Cache namespace",
177 },
178 ActionEntry {
180 name: "select",
181 category: ActionCategory::Dml,
182 lifecycle_state: LifecycleState::Active,
183 gates_description: "read rows from a collection",
184 },
185 ActionEntry {
186 name: "write",
187 category: ActionCategory::Dml,
188 lifecycle_state: LifecycleState::Active,
189 gates_description: "any mutating DML (insert/update/delete)",
190 },
191 ActionEntry {
192 name: "insert",
193 category: ActionCategory::Dml,
194 lifecycle_state: LifecycleState::Active,
195 gates_description: "insert rows into a collection",
196 },
197 ActionEntry {
198 name: "update",
199 category: ActionCategory::Dml,
200 lifecycle_state: LifecycleState::Active,
201 gates_description: "update rows in a collection",
202 },
203 ActionEntry {
204 name: "delete",
205 category: ActionCategory::Dml,
206 lifecycle_state: LifecycleState::Active,
207 gates_description: "delete rows from a collection",
208 },
209 ActionEntry {
210 name: "truncate",
211 category: ActionCategory::Dml,
212 lifecycle_state: LifecycleState::Active,
213 gates_description: "truncate a collection",
214 },
215 ActionEntry {
216 name: "references",
217 category: ActionCategory::Schema,
218 lifecycle_state: LifecycleState::Active,
219 gates_description: "declare a foreign key referencing a table",
220 },
221 ActionEntry {
222 name: "execute",
223 category: ActionCategory::Function,
224 lifecycle_state: LifecycleState::Active,
225 gates_description: "execute a stored function",
226 },
227 ActionEntry {
228 name: "usage",
229 category: ActionCategory::Schema,
230 lifecycle_state: LifecycleState::Active,
231 gates_description: "use a schema namespace",
232 },
233 ActionEntry {
234 name: "grant",
235 category: ActionCategory::Mgmt,
236 lifecycle_state: LifecycleState::Active,
237 gates_description: "grant privileges to another principal",
238 },
239 ActionEntry {
240 name: "revoke",
241 category: ActionCategory::Mgmt,
242 lifecycle_state: LifecycleState::Active,
243 gates_description: "revoke privileges from another principal",
244 },
245 ActionEntry {
246 name: "create",
247 category: ActionCategory::Ddl,
248 lifecycle_state: LifecycleState::Active,
249 gates_description: "create a database object",
250 },
251 ActionEntry {
252 name: "drop",
253 category: ActionCategory::Ddl,
254 lifecycle_state: LifecycleState::Active,
255 gates_description: "drop a database object",
256 },
257 ActionEntry {
258 name: "alter",
259 category: ActionCategory::Ddl,
260 lifecycle_state: LifecycleState::Active,
261 gates_description: "alter a database object",
262 },
263 ActionEntry {
272 name: "schema:write",
273 category: ActionCategory::Schema,
274 lifecycle_state: LifecycleState::Active,
275 gates_description: "grouped DDL on the current schema namespace (foreign table, migration)",
276 },
277 ActionEntry {
278 name: "schema:admin",
279 category: ActionCategory::Admin,
280 lifecycle_state: LifecycleState::Active,
281 gates_description: "namespace-level DDL (CREATE SCHEMA, CREATE SERVER)",
282 },
283 ActionEntry {
285 name: "policy:put",
286 category: ActionCategory::Policy,
287 lifecycle_state: LifecycleState::Active,
288 gates_description: "create or update a managed policy document",
289 },
290 ActionEntry {
291 name: "policy:drop",
292 category: ActionCategory::Policy,
293 lifecycle_state: LifecycleState::Active,
294 gates_description: "delete a managed policy document",
295 },
296 ActionEntry {
297 name: "policy:attach",
298 category: ActionCategory::Policy,
299 lifecycle_state: LifecycleState::Active,
300 gates_description: "attach a policy to a principal",
301 },
302 ActionEntry {
303 name: "policy:detach",
304 category: ActionCategory::Policy,
305 lifecycle_state: LifecycleState::Active,
306 gates_description: "detach a policy from a principal",
307 },
308 ActionEntry {
309 name: "policy:simulate",
310 category: ActionCategory::Policy,
311 lifecycle_state: LifecycleState::Active,
312 gates_description: "run the policy simulator",
313 },
314 ActionEntry {
316 name: "user:create",
317 category: ActionCategory::User,
318 lifecycle_state: LifecycleState::Active,
319 gates_description: "create an auth user",
320 },
321 ActionEntry {
322 name: "user:update",
323 category: ActionCategory::User,
324 lifecycle_state: LifecycleState::Active,
325 gates_description: "update non-credential user metadata",
326 },
327 ActionEntry {
328 name: "user:disable",
329 category: ActionCategory::User,
330 lifecycle_state: LifecycleState::Active,
331 gates_description: "disable an auth user",
332 },
333 ActionEntry {
334 name: "user:delete",
335 category: ActionCategory::User,
336 lifecycle_state: LifecycleState::Active,
337 gates_description: "delete an auth user and revoke their sessions/API keys",
338 },
339 ActionEntry {
340 name: "user:password:change",
341 category: ActionCategory::User,
342 lifecycle_state: LifecycleState::Active,
343 gates_description: "change an auth user's password",
344 },
345 ActionEntry {
346 name: "user:role:update",
347 category: ActionCategory::User,
348 lifecycle_state: LifecycleState::Active,
349 gates_description: "change an auth user's role",
350 },
351 ActionEntry {
352 name: "user:*",
353 category: ActionCategory::Wildcard,
354 lifecycle_state: LifecycleState::Active,
355 gates_description: "any user lifecycle verb",
356 },
357 ActionEntry {
358 name: "tenant:assume",
359 category: ActionCategory::Admin,
360 lifecycle_state: LifecycleState::Active,
361 gates_description: "assume or clear a tenant routing scope",
362 },
363 ActionEntry {
364 name: "identity:assume",
365 category: ActionCategory::Admin,
366 lifecycle_state: LifecycleState::Active,
367 gates_description: "project another user or role into statement scope",
368 },
369 ActionEntry {
370 name: "metrics:cross-tenant",
371 category: ActionCategory::Admin,
372 lifecycle_state: LifecycleState::Active,
373 gates_description: "read or write Prometheus data for another tenant",
374 },
375 ActionEntry {
377 name: "kv:invalidate",
378 category: ActionCategory::Other,
379 lifecycle_state: LifecycleState::Active,
380 gates_description: "invalidate cached KV entries",
381 },
382 ActionEntry {
383 name: "kv:read",
384 category: ActionCategory::Other,
385 lifecycle_state: LifecycleState::Active,
386 gates_description: "read user-managed plain KV entries via $kv.*",
387 },
388 ActionEntry {
389 name: "kv:write",
390 category: ActionCategory::Other,
391 lifecycle_state: LifecycleState::Active,
392 gates_description: "write or delete user-managed plain KV entries",
393 },
394 ActionEntry {
396 name: "admin:bootstrap",
397 category: ActionCategory::Admin,
398 lifecycle_state: LifecycleState::Active,
399 gates_description: "execute the bootstrap workflow",
400 },
401 ActionEntry {
402 name: "admin:audit-read",
403 category: ActionCategory::Admin,
404 lifecycle_state: LifecycleState::Active,
405 gates_description: "read the platform audit log",
406 },
407 ActionEntry {
408 name: "admin:reload",
409 category: ActionCategory::Admin,
410 lifecycle_state: LifecycleState::Active,
411 gates_description: "reload runtime configuration",
412 },
413 ActionEntry {
414 name: "admin:lease-promote",
415 category: ActionCategory::Admin,
416 lifecycle_state: LifecycleState::Active,
417 gates_description: "promote a standby instance via lease handoff",
418 },
419 ActionEntry {
421 name: "config:read",
422 category: ActionCategory::Config,
423 lifecycle_state: LifecycleState::Active,
424 gates_description: "read runtime configuration values",
425 },
426 ActionEntry {
427 name: "config:write",
428 category: ActionCategory::Config,
429 lifecycle_state: LifecycleState::Active,
430 gates_description: "mutate runtime configuration values",
431 },
432 ActionEntry {
433 name: "config:*",
434 category: ActionCategory::Wildcard,
435 lifecycle_state: LifecycleState::Active,
436 gates_description: "any runtime configuration verb",
437 },
438 ActionEntry {
440 name: "vault:read_metadata",
441 category: ActionCategory::Vault,
442 lifecycle_state: LifecycleState::Active,
443 gates_description: "read vault entry metadata (no plaintext)",
444 },
445 ActionEntry {
446 name: "vault:use",
447 category: ActionCategory::Vault,
448 lifecycle_state: LifecycleState::Active,
449 gates_description: "use a secret inside query evaluation without revealing its value",
450 },
451 ActionEntry {
452 name: "vault:reveal",
453 category: ActionCategory::Vault,
454 lifecycle_state: LifecycleState::Active,
455 gates_description: "explicitly reveal vault entry plaintext",
456 },
457 ActionEntry {
458 name: "vault:reveal_history",
459 category: ActionCategory::Vault,
460 lifecycle_state: LifecycleState::Active,
461 gates_description: "explicitly reveal historical vault entry plaintext",
462 },
463 ActionEntry {
464 name: "vault:read",
465 category: ActionCategory::Vault,
466 lifecycle_state: LifecycleState::Active,
467 gates_description: "compatibility alias for vault:reveal",
468 },
469 ActionEntry {
470 name: "vault:write",
471 category: ActionCategory::Vault,
472 lifecycle_state: LifecycleState::Active,
473 gates_description: "write or rotate vault entries",
474 },
475 ActionEntry {
476 name: "secret:read",
477 category: ActionCategory::Vault,
478 lifecycle_state: LifecycleState::Active,
479 gates_description: "compatibility alias for vault:use on default-vault SQL secrets",
480 },
481 ActionEntry {
482 name: "secret:write",
483 category: ActionCategory::Vault,
484 lifecycle_state: LifecycleState::Active,
485 gates_description: "compatibility alias for vault:write on default-vault SQL secrets",
486 },
487 ActionEntry {
488 name: "secret:*",
489 category: ActionCategory::Wildcard,
490 lifecycle_state: LifecycleState::Active,
491 gates_description: "any user-managed SQL vault secret verb",
492 },
493 ActionEntry {
494 name: "vault:unseal",
495 category: ActionCategory::Vault,
496 lifecycle_state: LifecycleState::Active,
497 gates_description: "unseal the vault master key for this session",
498 },
499 ActionEntry {
501 name: "vault:unseal_history",
502 category: ActionCategory::Vault,
503 lifecycle_state: LifecycleState::Deprecated {
504 replacement: Some("vault:reveal_history"),
505 since_version: "0.5.0",
506 },
507 gates_description: "compatibility alias for vault:reveal_history",
508 },
509 ActionEntry {
510 name: "vault:purge",
511 category: ActionCategory::Vault,
512 lifecycle_state: LifecycleState::Active,
513 gates_description: "purge (destructively remove) vault entries",
514 },
515 ActionEntry {
517 name: "evidence:export",
518 category: ActionCategory::Other,
519 lifecycle_state: LifecycleState::Active,
520 gates_description: "export evidence bundles",
521 },
522 ActionEntry {
523 name: "evidence:*",
524 category: ActionCategory::Wildcard,
525 lifecycle_state: LifecycleState::Active,
526 gates_description: "any evidence-pipeline verb",
527 },
528 ActionEntry {
530 name: "red.registry:register",
531 category: ActionCategory::Other,
532 lifecycle_state: LifecycleState::Active,
533 gates_description: "register a new managed-config schema",
534 },
535 ActionEntry {
536 name: "red.registry:supersede",
537 category: ActionCategory::Other,
538 lifecycle_state: LifecycleState::Active,
539 gates_description: "supersede an existing managed-config schema",
540 },
541 ActionEntry {
542 name: "red.registry:*",
543 category: ActionCategory::Wildcard,
544 lifecycle_state: LifecycleState::Active,
545 gates_description: "any registry verb",
546 },
547 ActionEntry {
554 name: "ai:provider:openai",
555 category: ActionCategory::Ai,
556 lifecycle_state: LifecycleState::Active,
557 gates_description: "use the OpenAI provider for ASK / AUTO EMBED / SEARCH SIMILAR",
558 },
559 ActionEntry {
560 name: "ai:provider:anthropic",
561 category: ActionCategory::Ai,
562 lifecycle_state: LifecycleState::Active,
563 gates_description: "use the Anthropic provider for ASK / AUTO EMBED / SEARCH SIMILAR",
564 },
565 ActionEntry {
566 name: "ai:provider:groq",
567 category: ActionCategory::Ai,
568 lifecycle_state: LifecycleState::Active,
569 gates_description: "use the Groq provider for ASK / AUTO EMBED / SEARCH SIMILAR",
570 },
571 ActionEntry {
572 name: "ai:provider:openrouter",
573 category: ActionCategory::Ai,
574 lifecycle_state: LifecycleState::Active,
575 gates_description: "use the OpenRouter provider for ASK / AUTO EMBED / SEARCH SIMILAR",
576 },
577 ActionEntry {
578 name: "ai:provider:together",
579 category: ActionCategory::Ai,
580 lifecycle_state: LifecycleState::Active,
581 gates_description: "use the Together provider for ASK / AUTO EMBED / SEARCH SIMILAR",
582 },
583 ActionEntry {
584 name: "ai:provider:venice",
585 category: ActionCategory::Ai,
586 lifecycle_state: LifecycleState::Active,
587 gates_description: "use the Venice provider for ASK / AUTO EMBED / SEARCH SIMILAR",
588 },
589 ActionEntry {
590 name: "ai:provider:ollama",
591 category: ActionCategory::Ai,
592 lifecycle_state: LifecycleState::Active,
593 gates_description: "use the Ollama provider for ASK / AUTO EMBED / SEARCH SIMILAR",
594 },
595 ActionEntry {
596 name: "ai:provider:deepseek",
597 category: ActionCategory::Ai,
598 lifecycle_state: LifecycleState::Active,
599 gates_description: "use the DeepSeek provider for ASK / AUTO EMBED / SEARCH SIMILAR",
600 },
601 ActionEntry {
602 name: "ai:provider:huggingface",
603 category: ActionCategory::Ai,
604 lifecycle_state: LifecycleState::Active,
605 gates_description: "use the HuggingFace provider for ASK / AUTO EMBED / SEARCH SIMILAR",
606 },
607 ActionEntry {
608 name: "ai:provider:local",
609 category: ActionCategory::Ai,
610 lifecycle_state: LifecycleState::Active,
611 gates_description: "use the local (in-process) embedding provider",
612 },
613 ActionEntry {
614 name: "ai:provider:*",
615 category: ActionCategory::Wildcard,
616 lifecycle_state: LifecycleState::Active,
617 gates_description: "use any AI provider (provider-gate wildcard)",
618 },
619 ActionEntry {
620 name: "ai:*",
621 category: ActionCategory::Wildcard,
622 lifecycle_state: LifecycleState::Active,
623 gates_description: "any AI-namespace verb",
624 },
625 ActionEntry {
631 name: "notify",
632 category: ActionCategory::Notification,
633 lifecycle_state: LifecycleState::Active,
634 gates_description:
635 "publish to / subscribe to ephemeral notification channels in the principal's own tenant",
636 },
637 ActionEntry {
638 name: "notify:cross-tenant",
639 category: ActionCategory::Notification,
640 lifecycle_state: LifecycleState::Active,
641 gates_description:
642 "address ephemeral notification channels in another tenant or the global namespace",
643 },
644 ActionEntry {
645 name: "notify:*",
646 category: ActionCategory::Wildcard,
647 lifecycle_state: LifecycleState::Active,
648 gates_description: "any ephemeral notification verb",
649 },
650 ActionEntry {
657 name: "stream",
658 category: ActionCategory::Stream,
659 lifecycle_state: LifecycleState::Active,
660 gates_description:
661 "append, read, and offset-save on durable streams in the principal's own tenant",
662 },
663 ActionEntry {
664 name: "stream:cross-tenant",
665 category: ActionCategory::Stream,
666 lifecycle_state: LifecycleState::Active,
667 gates_description:
668 "address durable streams in another tenant or the global namespace",
669 },
670 ActionEntry {
671 name: "stream:*",
672 category: ActionCategory::Wildcard,
673 lifecycle_state: LifecycleState::Active,
674 gates_description: "any durable stream verb",
675 },
676 ActionEntry {
684 name: "queue:enqueue",
685 category: ActionCategory::Queue,
686 lifecycle_state: LifecycleState::Active,
687 gates_description: "push / produce a message onto a queue",
688 },
689 ActionEntry {
690 name: "queue:read",
691 category: ActionCategory::Queue,
692 lifecycle_state: LifecycleState::Active,
693 gates_description: "destructive read: pop, group-read, claim",
694 },
695 ActionEntry {
696 name: "queue:peek",
697 category: ActionCategory::Queue,
698 lifecycle_state: LifecycleState::Active,
699 gates_description: "non-destructive read: peek, len, pending, select",
700 },
701 ActionEntry {
702 name: "queue:ack",
703 category: ActionCategory::Queue,
704 lifecycle_state: LifecycleState::Active,
705 gates_description: "acknowledge a delivered queue message",
706 },
707 ActionEntry {
708 name: "queue:nack",
709 category: ActionCategory::Queue,
710 lifecycle_state: LifecycleState::Active,
711 gates_description: "negative-acknowledge / requeue a delivered queue message",
712 },
713 ActionEntry {
714 name: "queue:retry",
715 category: ActionCategory::Queue,
716 lifecycle_state: LifecycleState::Active,
717 gates_description: "override retry policy (e.g. per-failure NACK delay)",
718 },
719 ActionEntry {
720 name: "queue:dlq:move",
721 category: ActionCategory::Queue,
722 lifecycle_state: LifecycleState::Active,
723 gates_description: "move / replay messages between a queue and its DLQ",
724 },
725 ActionEntry {
726 name: "queue:purge",
727 category: ActionCategory::Queue,
728 lifecycle_state: LifecycleState::Active,
729 gates_description: "destructively purge all messages from a queue",
730 },
731 ActionEntry {
732 name: "queue:presence:read",
733 category: ActionCategory::Queue,
734 lifecycle_state: LifecycleState::Active,
735 gates_description: "read consumer presence / heartbeat snapshots",
736 },
737 ActionEntry {
738 name: "queue:*",
739 category: ActionCategory::Wildcard,
740 lifecycle_state: LifecycleState::Active,
741 gates_description: "any queue verb",
742 },
743 ActionEntry {
754 name: "graph:read",
755 category: ActionCategory::Graph,
756 lifecycle_state: LifecycleState::Active,
757 gates_description: "read graph node/edge metadata and graph-wide properties",
758 },
759 ActionEntry {
760 name: "graph:traverse",
761 category: ActionCategory::Graph,
762 lifecycle_state: LifecycleState::Active,
763 gates_description: "execute pattern match / neighborhood / path traversal queries",
764 },
765 ActionEntry {
766 name: "graph:algorithm:run",
767 category: ActionCategory::Graph,
768 lifecycle_state: LifecycleState::Active,
769 gates_description: "run a graph analytics algorithm (centrality, community, components, ...)",
770 },
771 ActionEntry {
772 name: "graph:*",
773 category: ActionCategory::Wildcard,
774 lifecycle_state: LifecycleState::Active,
775 gates_description: "any graph verb",
776 },
777 ActionEntry {
792 name: "ops:read:self",
793 category: ActionCategory::Ops,
794 lifecycle_state: LifecycleState::Active,
795 gates_description: "read single-instance health / lifecycle state",
796 },
797 ActionEntry {
798 name: "ops:read:tenant",
799 category: ActionCategory::Ops,
800 lifecycle_state: LifecycleState::Active,
801 gates_description: "read tenant-scoped operational metrics / aggregates",
802 },
803 ActionEntry {
804 name: "ops:read:cluster",
805 category: ActionCategory::Ops,
806 lifecycle_state: LifecycleState::Active,
807 gates_description:
808 "read cluster topology / replication / backup / full metrics exposition",
809 },
810 ActionEntry {
811 name: "ops:admin",
812 category: ActionCategory::Ops,
813 lifecycle_state: LifecycleState::Active,
814 gates_description:
815 "read security-sensitive operational state (audit log, vault posture)",
816 },
817 ActionEntry {
818 name: "ops:*",
819 category: ActionCategory::Wildcard,
820 lifecycle_state: LifecycleState::Active,
821 gates_description: "any operational read verb",
822 },
823 ActionEntry {
828 name: "cluster:replication:stream",
829 category: ActionCategory::Other,
830 lifecycle_state: LifecycleState::Active,
831 gates_description: "stream primary WAL records and replication snapshots to a replica",
832 },
833 ActionEntry {
834 name: "cluster:replication:ack",
835 category: ActionCategory::Other,
836 lifecycle_state: LifecycleState::Active,
837 gates_description: "acknowledge replica LSN progress to the primary",
838 },
839 ActionEntry {
840 name: "cluster:*",
841 category: ActionCategory::Wildcard,
842 lifecycle_state: LifecycleState::Active,
843 gates_description: "any cluster-scoped capability",
844 },
845 ActionEntry {
857 name: "vector:read",
858 category: ActionCategory::Vector,
859 lifecycle_state: LifecycleState::Active,
860 gates_description: "read vector metadata / data (non-search reads on a vector collection)",
861 },
862 ActionEntry {
863 name: "vector:search",
864 category: ActionCategory::Vector,
865 lifecycle_state: LifecycleState::Active,
866 gates_description: "similarity / text / hybrid search against a vector collection",
867 },
868 ActionEntry {
869 name: "vector:artifact:read",
870 category: ActionCategory::Vector,
871 lifecycle_state: LifecycleState::Active,
872 gates_description: "introspect operational vector index artifacts (pages, status)",
873 },
874 ActionEntry {
875 name: "vector:artifact:rebuild",
876 category: ActionCategory::Vector,
877 lifecycle_state: LifecycleState::Active,
878 gates_description: "rebuild / warmup vector index artifacts",
879 },
880 ActionEntry {
881 name: "vector:admin",
882 category: ActionCategory::Vector,
883 lifecycle_state: LifecycleState::Active,
884 gates_description: "admin operations on a vector collection (clustering, maintenance)",
885 },
886 ActionEntry {
887 name: "vector:*",
888 category: ActionCategory::Wildcard,
889 lifecycle_state: LifecycleState::Active,
890 gates_description: "any vector verb",
891 },
892 ActionEntry {
894 name: "*",
895 category: ActionCategory::Wildcard,
896 lifecycle_state: LifecycleState::Active,
897 gates_description: "any action (escape hatch — audit usage carefully)",
898 },
899 ActionEntry {
900 name: "admin:*",
901 category: ActionCategory::Wildcard,
902 lifecycle_state: LifecycleState::Active,
903 gates_description: "any admin verb",
904 },
905 ActionEntry {
906 name: "vault:*",
907 category: ActionCategory::Wildcard,
908 lifecycle_state: LifecycleState::Active,
909 gates_description: "any vault verb",
910 },
911 ActionEntry {
912 name: "kv:*",
913 category: ActionCategory::Wildcard,
914 lifecycle_state: LifecycleState::Active,
915 gates_description: "any KV verb",
916 },
917 ActionEntry {
918 name: "policy:*",
919 category: ActionCategory::Wildcard,
920 lifecycle_state: LifecycleState::Active,
921 gates_description: "any policy lifecycle verb",
922 },
923];
924
925pub fn is_valid_action(name: &str) -> bool {
929 ACTIONS
930 .iter()
931 .any(|e| e.name == name && !matches!(e.lifecycle_state, LifecycleState::Removed))
932}
933
934pub fn lookup(name: &str) -> Option<&'static ActionEntry> {
936 ACTIONS.iter().find(|e| e.name == name)
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942 use std::collections::HashSet;
943
944 const HISTORICAL_ALLOWLIST: &[&str] = &[
949 "select",
950 "write",
951 "insert",
952 "update",
953 "delete",
954 "truncate",
955 "references",
956 "execute",
957 "usage",
958 "grant",
959 "revoke",
960 "create",
961 "drop",
962 "alter",
963 "policy:put",
964 "policy:drop",
965 "policy:attach",
966 "policy:detach",
967 "policy:simulate",
968 "kv:invalidate",
969 "kv:read",
970 "kv:write",
971 "admin:bootstrap",
972 "admin:audit-read",
973 "admin:reload",
974 "admin:lease-promote",
975 "config:read",
976 "config:write",
977 "config:*",
978 "vault:read_metadata",
979 "vault:read",
980 "vault:write",
981 "secret:read",
982 "secret:write",
983 "secret:*",
984 "vault:unseal",
985 "vault:unseal_history",
986 "vault:purge",
987 "evidence:export",
988 "evidence:*",
989 "red.registry:register",
990 "red.registry:supersede",
991 "red.registry:*",
992 "*",
993 "admin:*",
994 "vault:*",
995 "kv:*",
996 "policy:*",
997 ];
998
999 #[test]
1000 fn no_duplicate_names() {
1001 let mut seen = HashSet::new();
1002 for entry in ACTIONS {
1003 assert!(
1004 seen.insert(entry.name),
1005 "duplicate action name in catalog: {}",
1006 entry.name
1007 );
1008 }
1009 }
1010
1011 #[test]
1012 fn covers_historical_allowlist() {
1013 let names: HashSet<&'static str> = ACTIONS.iter().map(|e| e.name).collect();
1014 for action in HISTORICAL_ALLOWLIST {
1015 assert!(
1016 names.contains(action),
1017 "catalog missing historically-accepted action: {action}",
1018 );
1019 }
1020 }
1021
1022 #[test]
1023 fn historical_allowlist_still_validates() {
1024 for action in HISTORICAL_ALLOWLIST {
1025 assert!(
1026 is_valid_action(action),
1027 "action {action} was accepted before the catalog and must still validate",
1028 );
1029 }
1030 }
1031
1032 #[test]
1033 fn has_at_least_one_deprecated_entry() {
1034 let count = ACTIONS
1035 .iter()
1036 .filter(|e| matches!(e.lifecycle_state, LifecycleState::Deprecated { .. }))
1037 .count();
1038 assert!(
1039 count >= 1,
1040 "catalog must demonstrate the Deprecated lifecycle state with at least one entry",
1041 );
1042 }
1043
1044 #[test]
1045 fn removed_entries_are_rejected() {
1046 for entry in ACTIONS {
1049 if matches!(entry.lifecycle_state, LifecycleState::Removed) {
1050 assert!(
1051 !is_valid_action(entry.name),
1052 "Removed entry {} must not validate",
1053 entry.name,
1054 );
1055 }
1056 }
1057 }
1058
1059 #[test]
1060 fn lookup_finds_known_entries() {
1061 assert!(lookup("policy:put").is_some());
1062 assert!(lookup("definitely-not-an-action").is_none());
1063 }
1064}