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 {
162 name: "select",
163 category: ActionCategory::Dml,
164 lifecycle_state: LifecycleState::Active,
165 gates_description: "read rows from a collection",
166 },
167 ActionEntry {
168 name: "write",
169 category: ActionCategory::Dml,
170 lifecycle_state: LifecycleState::Active,
171 gates_description: "any mutating DML (insert/update/delete)",
172 },
173 ActionEntry {
174 name: "insert",
175 category: ActionCategory::Dml,
176 lifecycle_state: LifecycleState::Active,
177 gates_description: "insert rows into a collection",
178 },
179 ActionEntry {
180 name: "update",
181 category: ActionCategory::Dml,
182 lifecycle_state: LifecycleState::Active,
183 gates_description: "update rows in a collection",
184 },
185 ActionEntry {
186 name: "delete",
187 category: ActionCategory::Dml,
188 lifecycle_state: LifecycleState::Active,
189 gates_description: "delete rows from a collection",
190 },
191 ActionEntry {
192 name: "truncate",
193 category: ActionCategory::Dml,
194 lifecycle_state: LifecycleState::Active,
195 gates_description: "truncate a collection",
196 },
197 ActionEntry {
198 name: "references",
199 category: ActionCategory::Schema,
200 lifecycle_state: LifecycleState::Active,
201 gates_description: "declare a foreign key referencing a table",
202 },
203 ActionEntry {
204 name: "execute",
205 category: ActionCategory::Function,
206 lifecycle_state: LifecycleState::Active,
207 gates_description: "execute a stored function",
208 },
209 ActionEntry {
210 name: "usage",
211 category: ActionCategory::Schema,
212 lifecycle_state: LifecycleState::Active,
213 gates_description: "use a schema namespace",
214 },
215 ActionEntry {
216 name: "grant",
217 category: ActionCategory::Mgmt,
218 lifecycle_state: LifecycleState::Active,
219 gates_description: "grant privileges to another principal",
220 },
221 ActionEntry {
222 name: "revoke",
223 category: ActionCategory::Mgmt,
224 lifecycle_state: LifecycleState::Active,
225 gates_description: "revoke privileges from another principal",
226 },
227 ActionEntry {
228 name: "create",
229 category: ActionCategory::Ddl,
230 lifecycle_state: LifecycleState::Active,
231 gates_description: "create a database object",
232 },
233 ActionEntry {
234 name: "drop",
235 category: ActionCategory::Ddl,
236 lifecycle_state: LifecycleState::Active,
237 gates_description: "drop a database object",
238 },
239 ActionEntry {
240 name: "alter",
241 category: ActionCategory::Ddl,
242 lifecycle_state: LifecycleState::Active,
243 gates_description: "alter a database object",
244 },
245 ActionEntry {
254 name: "schema:write",
255 category: ActionCategory::Schema,
256 lifecycle_state: LifecycleState::Active,
257 gates_description: "grouped DDL on the current schema namespace (foreign table, migration)",
258 },
259 ActionEntry {
260 name: "schema:admin",
261 category: ActionCategory::Admin,
262 lifecycle_state: LifecycleState::Active,
263 gates_description: "namespace-level DDL (CREATE SCHEMA, CREATE SERVER)",
264 },
265 ActionEntry {
267 name: "policy:put",
268 category: ActionCategory::Policy,
269 lifecycle_state: LifecycleState::Active,
270 gates_description: "create or update a managed policy document",
271 },
272 ActionEntry {
273 name: "policy:drop",
274 category: ActionCategory::Policy,
275 lifecycle_state: LifecycleState::Active,
276 gates_description: "delete a managed policy document",
277 },
278 ActionEntry {
279 name: "policy:attach",
280 category: ActionCategory::Policy,
281 lifecycle_state: LifecycleState::Active,
282 gates_description: "attach a policy to a principal",
283 },
284 ActionEntry {
285 name: "policy:detach",
286 category: ActionCategory::Policy,
287 lifecycle_state: LifecycleState::Active,
288 gates_description: "detach a policy from a principal",
289 },
290 ActionEntry {
291 name: "policy:simulate",
292 category: ActionCategory::Policy,
293 lifecycle_state: LifecycleState::Active,
294 gates_description: "run the policy simulator",
295 },
296 ActionEntry {
298 name: "user:create",
299 category: ActionCategory::User,
300 lifecycle_state: LifecycleState::Active,
301 gates_description: "create an auth user",
302 },
303 ActionEntry {
304 name: "user:update",
305 category: ActionCategory::User,
306 lifecycle_state: LifecycleState::Active,
307 gates_description: "update non-credential user metadata",
308 },
309 ActionEntry {
310 name: "user:disable",
311 category: ActionCategory::User,
312 lifecycle_state: LifecycleState::Active,
313 gates_description: "disable an auth user",
314 },
315 ActionEntry {
316 name: "user:delete",
317 category: ActionCategory::User,
318 lifecycle_state: LifecycleState::Active,
319 gates_description: "delete an auth user and revoke their sessions/API keys",
320 },
321 ActionEntry {
322 name: "user:password:change",
323 category: ActionCategory::User,
324 lifecycle_state: LifecycleState::Active,
325 gates_description: "change an auth user's password",
326 },
327 ActionEntry {
328 name: "user:role:update",
329 category: ActionCategory::User,
330 lifecycle_state: LifecycleState::Active,
331 gates_description: "change an auth user's role",
332 },
333 ActionEntry {
334 name: "user:*",
335 category: ActionCategory::Wildcard,
336 lifecycle_state: LifecycleState::Active,
337 gates_description: "any user lifecycle verb",
338 },
339 ActionEntry {
341 name: "kv:invalidate",
342 category: ActionCategory::Other,
343 lifecycle_state: LifecycleState::Active,
344 gates_description: "invalidate cached KV entries",
345 },
346 ActionEntry {
347 name: "kv:read",
348 category: ActionCategory::Other,
349 lifecycle_state: LifecycleState::Active,
350 gates_description: "read user-managed plain KV entries via $kv.*",
351 },
352 ActionEntry {
353 name: "kv:write",
354 category: ActionCategory::Other,
355 lifecycle_state: LifecycleState::Active,
356 gates_description: "write or delete user-managed plain KV entries",
357 },
358 ActionEntry {
360 name: "admin:bootstrap",
361 category: ActionCategory::Admin,
362 lifecycle_state: LifecycleState::Active,
363 gates_description: "execute the bootstrap workflow",
364 },
365 ActionEntry {
366 name: "admin:audit-read",
367 category: ActionCategory::Admin,
368 lifecycle_state: LifecycleState::Active,
369 gates_description: "read the platform audit log",
370 },
371 ActionEntry {
372 name: "admin:reload",
373 category: ActionCategory::Admin,
374 lifecycle_state: LifecycleState::Active,
375 gates_description: "reload runtime configuration",
376 },
377 ActionEntry {
378 name: "admin:lease-promote",
379 category: ActionCategory::Admin,
380 lifecycle_state: LifecycleState::Active,
381 gates_description: "promote a standby instance via lease handoff",
382 },
383 ActionEntry {
385 name: "config:read",
386 category: ActionCategory::Config,
387 lifecycle_state: LifecycleState::Active,
388 gates_description: "read runtime configuration values",
389 },
390 ActionEntry {
391 name: "config:write",
392 category: ActionCategory::Config,
393 lifecycle_state: LifecycleState::Active,
394 gates_description: "mutate runtime configuration values",
395 },
396 ActionEntry {
397 name: "config:*",
398 category: ActionCategory::Wildcard,
399 lifecycle_state: LifecycleState::Active,
400 gates_description: "any runtime configuration verb",
401 },
402 ActionEntry {
404 name: "vault:read_metadata",
405 category: ActionCategory::Vault,
406 lifecycle_state: LifecycleState::Active,
407 gates_description: "read vault entry metadata (no plaintext)",
408 },
409 ActionEntry {
410 name: "vault:read",
411 category: ActionCategory::Vault,
412 lifecycle_state: LifecycleState::Active,
413 gates_description: "reveal vault entry plaintext",
414 },
415 ActionEntry {
416 name: "vault:write",
417 category: ActionCategory::Vault,
418 lifecycle_state: LifecycleState::Active,
419 gates_description: "write or rotate vault entries",
420 },
421 ActionEntry {
422 name: "secret:read",
423 category: ActionCategory::Vault,
424 lifecycle_state: LifecycleState::Active,
425 gates_description: "read user-managed SQL vault secrets",
426 },
427 ActionEntry {
428 name: "secret:write",
429 category: ActionCategory::Vault,
430 lifecycle_state: LifecycleState::Active,
431 gates_description: "write or delete user-managed SQL vault secrets",
432 },
433 ActionEntry {
434 name: "secret:*",
435 category: ActionCategory::Wildcard,
436 lifecycle_state: LifecycleState::Active,
437 gates_description: "any user-managed SQL vault secret verb",
438 },
439 ActionEntry {
440 name: "vault:unseal",
441 category: ActionCategory::Vault,
442 lifecycle_state: LifecycleState::Active,
443 gates_description: "unseal the vault master key for this session",
444 },
445 ActionEntry {
451 name: "vault:unseal_history",
452 category: ActionCategory::Vault,
453 lifecycle_state: LifecycleState::Deprecated {
454 replacement: Some("vault:read_metadata"),
455 since_version: "0.5.0",
456 },
457 gates_description: "read the vault unseal-event audit trail",
458 },
459 ActionEntry {
460 name: "vault:purge",
461 category: ActionCategory::Vault,
462 lifecycle_state: LifecycleState::Active,
463 gates_description: "purge (destructively remove) vault entries",
464 },
465 ActionEntry {
467 name: "evidence:export",
468 category: ActionCategory::Other,
469 lifecycle_state: LifecycleState::Active,
470 gates_description: "export evidence bundles",
471 },
472 ActionEntry {
473 name: "evidence:*",
474 category: ActionCategory::Wildcard,
475 lifecycle_state: LifecycleState::Active,
476 gates_description: "any evidence-pipeline verb",
477 },
478 ActionEntry {
480 name: "red.registry:register",
481 category: ActionCategory::Other,
482 lifecycle_state: LifecycleState::Active,
483 gates_description: "register a new managed-config schema",
484 },
485 ActionEntry {
486 name: "red.registry:supersede",
487 category: ActionCategory::Other,
488 lifecycle_state: LifecycleState::Active,
489 gates_description: "supersede an existing managed-config schema",
490 },
491 ActionEntry {
492 name: "red.registry:*",
493 category: ActionCategory::Wildcard,
494 lifecycle_state: LifecycleState::Active,
495 gates_description: "any registry verb",
496 },
497 ActionEntry {
504 name: "ai:provider:openai",
505 category: ActionCategory::Ai,
506 lifecycle_state: LifecycleState::Active,
507 gates_description: "use the OpenAI provider for ASK / AUTO EMBED / SEARCH SIMILAR",
508 },
509 ActionEntry {
510 name: "ai:provider:anthropic",
511 category: ActionCategory::Ai,
512 lifecycle_state: LifecycleState::Active,
513 gates_description: "use the Anthropic provider for ASK / AUTO EMBED / SEARCH SIMILAR",
514 },
515 ActionEntry {
516 name: "ai:provider:groq",
517 category: ActionCategory::Ai,
518 lifecycle_state: LifecycleState::Active,
519 gates_description: "use the Groq provider for ASK / AUTO EMBED / SEARCH SIMILAR",
520 },
521 ActionEntry {
522 name: "ai:provider:openrouter",
523 category: ActionCategory::Ai,
524 lifecycle_state: LifecycleState::Active,
525 gates_description: "use the OpenRouter provider for ASK / AUTO EMBED / SEARCH SIMILAR",
526 },
527 ActionEntry {
528 name: "ai:provider:together",
529 category: ActionCategory::Ai,
530 lifecycle_state: LifecycleState::Active,
531 gates_description: "use the Together provider for ASK / AUTO EMBED / SEARCH SIMILAR",
532 },
533 ActionEntry {
534 name: "ai:provider:venice",
535 category: ActionCategory::Ai,
536 lifecycle_state: LifecycleState::Active,
537 gates_description: "use the Venice provider for ASK / AUTO EMBED / SEARCH SIMILAR",
538 },
539 ActionEntry {
540 name: "ai:provider:ollama",
541 category: ActionCategory::Ai,
542 lifecycle_state: LifecycleState::Active,
543 gates_description: "use the Ollama provider for ASK / AUTO EMBED / SEARCH SIMILAR",
544 },
545 ActionEntry {
546 name: "ai:provider:deepseek",
547 category: ActionCategory::Ai,
548 lifecycle_state: LifecycleState::Active,
549 gates_description: "use the DeepSeek provider for ASK / AUTO EMBED / SEARCH SIMILAR",
550 },
551 ActionEntry {
552 name: "ai:provider:huggingface",
553 category: ActionCategory::Ai,
554 lifecycle_state: LifecycleState::Active,
555 gates_description: "use the HuggingFace provider for ASK / AUTO EMBED / SEARCH SIMILAR",
556 },
557 ActionEntry {
558 name: "ai:provider:local",
559 category: ActionCategory::Ai,
560 lifecycle_state: LifecycleState::Active,
561 gates_description: "use the local (in-process) embedding provider",
562 },
563 ActionEntry {
564 name: "ai:provider:*",
565 category: ActionCategory::Wildcard,
566 lifecycle_state: LifecycleState::Active,
567 gates_description: "use any AI provider (provider-gate wildcard)",
568 },
569 ActionEntry {
570 name: "ai:*",
571 category: ActionCategory::Wildcard,
572 lifecycle_state: LifecycleState::Active,
573 gates_description: "any AI-namespace verb",
574 },
575 ActionEntry {
581 name: "notify",
582 category: ActionCategory::Notification,
583 lifecycle_state: LifecycleState::Active,
584 gates_description:
585 "publish to / subscribe to ephemeral notification channels in the principal's own tenant",
586 },
587 ActionEntry {
588 name: "notify:cross-tenant",
589 category: ActionCategory::Notification,
590 lifecycle_state: LifecycleState::Active,
591 gates_description:
592 "address ephemeral notification channels in another tenant or the global namespace",
593 },
594 ActionEntry {
595 name: "notify:*",
596 category: ActionCategory::Wildcard,
597 lifecycle_state: LifecycleState::Active,
598 gates_description: "any ephemeral notification verb",
599 },
600 ActionEntry {
607 name: "stream",
608 category: ActionCategory::Stream,
609 lifecycle_state: LifecycleState::Active,
610 gates_description:
611 "append, read, and offset-save on durable streams in the principal's own tenant",
612 },
613 ActionEntry {
614 name: "stream:cross-tenant",
615 category: ActionCategory::Stream,
616 lifecycle_state: LifecycleState::Active,
617 gates_description:
618 "address durable streams in another tenant or the global namespace",
619 },
620 ActionEntry {
621 name: "stream:*",
622 category: ActionCategory::Wildcard,
623 lifecycle_state: LifecycleState::Active,
624 gates_description: "any durable stream verb",
625 },
626 ActionEntry {
634 name: "queue:enqueue",
635 category: ActionCategory::Queue,
636 lifecycle_state: LifecycleState::Active,
637 gates_description: "push / produce a message onto a queue",
638 },
639 ActionEntry {
640 name: "queue:read",
641 category: ActionCategory::Queue,
642 lifecycle_state: LifecycleState::Active,
643 gates_description: "destructive read: pop, group-read, claim",
644 },
645 ActionEntry {
646 name: "queue:peek",
647 category: ActionCategory::Queue,
648 lifecycle_state: LifecycleState::Active,
649 gates_description: "non-destructive read: peek, len, pending, select",
650 },
651 ActionEntry {
652 name: "queue:ack",
653 category: ActionCategory::Queue,
654 lifecycle_state: LifecycleState::Active,
655 gates_description: "acknowledge a delivered queue message",
656 },
657 ActionEntry {
658 name: "queue:nack",
659 category: ActionCategory::Queue,
660 lifecycle_state: LifecycleState::Active,
661 gates_description: "negative-acknowledge / requeue a delivered queue message",
662 },
663 ActionEntry {
664 name: "queue:retry",
665 category: ActionCategory::Queue,
666 lifecycle_state: LifecycleState::Active,
667 gates_description: "override retry policy (e.g. per-failure NACK delay)",
668 },
669 ActionEntry {
670 name: "queue:dlq:move",
671 category: ActionCategory::Queue,
672 lifecycle_state: LifecycleState::Active,
673 gates_description: "move / replay messages between a queue and its DLQ",
674 },
675 ActionEntry {
676 name: "queue:purge",
677 category: ActionCategory::Queue,
678 lifecycle_state: LifecycleState::Active,
679 gates_description: "destructively purge all messages from a queue",
680 },
681 ActionEntry {
682 name: "queue:presence:read",
683 category: ActionCategory::Queue,
684 lifecycle_state: LifecycleState::Active,
685 gates_description: "read consumer presence / heartbeat snapshots",
686 },
687 ActionEntry {
688 name: "queue:*",
689 category: ActionCategory::Wildcard,
690 lifecycle_state: LifecycleState::Active,
691 gates_description: "any queue verb",
692 },
693 ActionEntry {
704 name: "graph:read",
705 category: ActionCategory::Graph,
706 lifecycle_state: LifecycleState::Active,
707 gates_description: "read graph node/edge metadata and graph-wide properties",
708 },
709 ActionEntry {
710 name: "graph:traverse",
711 category: ActionCategory::Graph,
712 lifecycle_state: LifecycleState::Active,
713 gates_description: "execute pattern match / neighborhood / path traversal queries",
714 },
715 ActionEntry {
716 name: "graph:algorithm:run",
717 category: ActionCategory::Graph,
718 lifecycle_state: LifecycleState::Active,
719 gates_description: "run a graph analytics algorithm (centrality, community, components, ...)",
720 },
721 ActionEntry {
722 name: "graph:*",
723 category: ActionCategory::Wildcard,
724 lifecycle_state: LifecycleState::Active,
725 gates_description: "any graph verb",
726 },
727 ActionEntry {
742 name: "ops:read:self",
743 category: ActionCategory::Ops,
744 lifecycle_state: LifecycleState::Active,
745 gates_description: "read single-instance health / lifecycle state",
746 },
747 ActionEntry {
748 name: "ops:read:tenant",
749 category: ActionCategory::Ops,
750 lifecycle_state: LifecycleState::Active,
751 gates_description: "read tenant-scoped operational metrics / aggregates",
752 },
753 ActionEntry {
754 name: "ops:read:cluster",
755 category: ActionCategory::Ops,
756 lifecycle_state: LifecycleState::Active,
757 gates_description:
758 "read cluster topology / replication / backup / full metrics exposition",
759 },
760 ActionEntry {
761 name: "ops:admin",
762 category: ActionCategory::Ops,
763 lifecycle_state: LifecycleState::Active,
764 gates_description:
765 "read security-sensitive operational state (audit log, vault posture)",
766 },
767 ActionEntry {
768 name: "ops:*",
769 category: ActionCategory::Wildcard,
770 lifecycle_state: LifecycleState::Active,
771 gates_description: "any operational read verb",
772 },
773 ActionEntry {
778 name: "cluster:replication:stream",
779 category: ActionCategory::Other,
780 lifecycle_state: LifecycleState::Active,
781 gates_description: "stream primary WAL records and replication snapshots to a replica",
782 },
783 ActionEntry {
784 name: "cluster:replication:ack",
785 category: ActionCategory::Other,
786 lifecycle_state: LifecycleState::Active,
787 gates_description: "acknowledge replica LSN progress to the primary",
788 },
789 ActionEntry {
790 name: "cluster:*",
791 category: ActionCategory::Wildcard,
792 lifecycle_state: LifecycleState::Active,
793 gates_description: "any cluster-scoped capability",
794 },
795 ActionEntry {
807 name: "vector:read",
808 category: ActionCategory::Vector,
809 lifecycle_state: LifecycleState::Active,
810 gates_description: "read vector metadata / data (non-search reads on a vector collection)",
811 },
812 ActionEntry {
813 name: "vector:search",
814 category: ActionCategory::Vector,
815 lifecycle_state: LifecycleState::Active,
816 gates_description: "similarity / text / hybrid search against a vector collection",
817 },
818 ActionEntry {
819 name: "vector:artifact:read",
820 category: ActionCategory::Vector,
821 lifecycle_state: LifecycleState::Active,
822 gates_description: "introspect operational vector index artifacts (pages, status)",
823 },
824 ActionEntry {
825 name: "vector:artifact:rebuild",
826 category: ActionCategory::Vector,
827 lifecycle_state: LifecycleState::Active,
828 gates_description: "rebuild / warmup vector index artifacts",
829 },
830 ActionEntry {
831 name: "vector:admin",
832 category: ActionCategory::Vector,
833 lifecycle_state: LifecycleState::Active,
834 gates_description: "admin operations on a vector collection (clustering, maintenance)",
835 },
836 ActionEntry {
837 name: "vector:*",
838 category: ActionCategory::Wildcard,
839 lifecycle_state: LifecycleState::Active,
840 gates_description: "any vector verb",
841 },
842 ActionEntry {
844 name: "*",
845 category: ActionCategory::Wildcard,
846 lifecycle_state: LifecycleState::Active,
847 gates_description: "any action (escape hatch — audit usage carefully)",
848 },
849 ActionEntry {
850 name: "admin:*",
851 category: ActionCategory::Wildcard,
852 lifecycle_state: LifecycleState::Active,
853 gates_description: "any admin verb",
854 },
855 ActionEntry {
856 name: "vault:*",
857 category: ActionCategory::Wildcard,
858 lifecycle_state: LifecycleState::Active,
859 gates_description: "any vault verb",
860 },
861 ActionEntry {
862 name: "kv:*",
863 category: ActionCategory::Wildcard,
864 lifecycle_state: LifecycleState::Active,
865 gates_description: "any KV verb",
866 },
867 ActionEntry {
868 name: "policy:*",
869 category: ActionCategory::Wildcard,
870 lifecycle_state: LifecycleState::Active,
871 gates_description: "any policy lifecycle verb",
872 },
873];
874
875pub fn is_valid_action(name: &str) -> bool {
879 ACTIONS
880 .iter()
881 .any(|e| e.name == name && !matches!(e.lifecycle_state, LifecycleState::Removed))
882}
883
884pub fn lookup(name: &str) -> Option<&'static ActionEntry> {
886 ACTIONS.iter().find(|e| e.name == name)
887}
888
889#[cfg(test)]
890mod tests {
891 use super::*;
892 use std::collections::HashSet;
893
894 const HISTORICAL_ALLOWLIST: &[&str] = &[
899 "select",
900 "write",
901 "insert",
902 "update",
903 "delete",
904 "truncate",
905 "references",
906 "execute",
907 "usage",
908 "grant",
909 "revoke",
910 "create",
911 "drop",
912 "alter",
913 "policy:put",
914 "policy:drop",
915 "policy:attach",
916 "policy:detach",
917 "policy:simulate",
918 "kv:invalidate",
919 "kv:read",
920 "kv:write",
921 "admin:bootstrap",
922 "admin:audit-read",
923 "admin:reload",
924 "admin:lease-promote",
925 "config:read",
926 "config:write",
927 "config:*",
928 "vault:read_metadata",
929 "vault:read",
930 "vault:write",
931 "secret:read",
932 "secret:write",
933 "secret:*",
934 "vault:unseal",
935 "vault:unseal_history",
936 "vault:purge",
937 "evidence:export",
938 "evidence:*",
939 "red.registry:register",
940 "red.registry:supersede",
941 "red.registry:*",
942 "*",
943 "admin:*",
944 "vault:*",
945 "kv:*",
946 "policy:*",
947 ];
948
949 #[test]
950 fn no_duplicate_names() {
951 let mut seen = HashSet::new();
952 for entry in ACTIONS {
953 assert!(
954 seen.insert(entry.name),
955 "duplicate action name in catalog: {}",
956 entry.name
957 );
958 }
959 }
960
961 #[test]
962 fn covers_historical_allowlist() {
963 let names: HashSet<&'static str> = ACTIONS.iter().map(|e| e.name).collect();
964 for action in HISTORICAL_ALLOWLIST {
965 assert!(
966 names.contains(action),
967 "catalog missing historically-accepted action: {action}",
968 );
969 }
970 }
971
972 #[test]
973 fn historical_allowlist_still_validates() {
974 for action in HISTORICAL_ALLOWLIST {
975 assert!(
976 is_valid_action(action),
977 "action {action} was accepted before the catalog and must still validate",
978 );
979 }
980 }
981
982 #[test]
983 fn has_at_least_one_deprecated_entry() {
984 let count = ACTIONS
985 .iter()
986 .filter(|e| matches!(e.lifecycle_state, LifecycleState::Deprecated { .. }))
987 .count();
988 assert!(
989 count >= 1,
990 "catalog must demonstrate the Deprecated lifecycle state with at least one entry",
991 );
992 }
993
994 #[test]
995 fn removed_entries_are_rejected() {
996 for entry in ACTIONS {
999 if matches!(entry.lifecycle_state, LifecycleState::Removed) {
1000 assert!(
1001 !is_valid_action(entry.name),
1002 "Removed entry {} must not validate",
1003 entry.name,
1004 );
1005 }
1006 }
1007 }
1008
1009 #[test]
1010 fn lookup_finds_known_entries() {
1011 assert!(lookup("policy:put").is_some());
1012 assert!(lookup("definitely-not-an-action").is_none());
1013 }
1014}