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 {
359 name: "kv:invalidate",
360 category: ActionCategory::Other,
361 lifecycle_state: LifecycleState::Active,
362 gates_description: "invalidate cached KV entries",
363 },
364 ActionEntry {
365 name: "kv:read",
366 category: ActionCategory::Other,
367 lifecycle_state: LifecycleState::Active,
368 gates_description: "read user-managed plain KV entries via $kv.*",
369 },
370 ActionEntry {
371 name: "kv:write",
372 category: ActionCategory::Other,
373 lifecycle_state: LifecycleState::Active,
374 gates_description: "write or delete user-managed plain KV entries",
375 },
376 ActionEntry {
378 name: "admin:bootstrap",
379 category: ActionCategory::Admin,
380 lifecycle_state: LifecycleState::Active,
381 gates_description: "execute the bootstrap workflow",
382 },
383 ActionEntry {
384 name: "admin:audit-read",
385 category: ActionCategory::Admin,
386 lifecycle_state: LifecycleState::Active,
387 gates_description: "read the platform audit log",
388 },
389 ActionEntry {
390 name: "admin:reload",
391 category: ActionCategory::Admin,
392 lifecycle_state: LifecycleState::Active,
393 gates_description: "reload runtime configuration",
394 },
395 ActionEntry {
396 name: "admin:lease-promote",
397 category: ActionCategory::Admin,
398 lifecycle_state: LifecycleState::Active,
399 gates_description: "promote a standby instance via lease handoff",
400 },
401 ActionEntry {
403 name: "config:read",
404 category: ActionCategory::Config,
405 lifecycle_state: LifecycleState::Active,
406 gates_description: "read runtime configuration values",
407 },
408 ActionEntry {
409 name: "config:write",
410 category: ActionCategory::Config,
411 lifecycle_state: LifecycleState::Active,
412 gates_description: "mutate runtime configuration values",
413 },
414 ActionEntry {
415 name: "config:*",
416 category: ActionCategory::Wildcard,
417 lifecycle_state: LifecycleState::Active,
418 gates_description: "any runtime configuration verb",
419 },
420 ActionEntry {
422 name: "vault:read_metadata",
423 category: ActionCategory::Vault,
424 lifecycle_state: LifecycleState::Active,
425 gates_description: "read vault entry metadata (no plaintext)",
426 },
427 ActionEntry {
428 name: "vault:use",
429 category: ActionCategory::Vault,
430 lifecycle_state: LifecycleState::Active,
431 gates_description: "use a secret inside query evaluation without revealing its value",
432 },
433 ActionEntry {
434 name: "vault:reveal",
435 category: ActionCategory::Vault,
436 lifecycle_state: LifecycleState::Active,
437 gates_description: "explicitly reveal vault entry plaintext",
438 },
439 ActionEntry {
440 name: "vault:reveal_history",
441 category: ActionCategory::Vault,
442 lifecycle_state: LifecycleState::Active,
443 gates_description: "explicitly reveal historical vault entry plaintext",
444 },
445 ActionEntry {
446 name: "vault:read",
447 category: ActionCategory::Vault,
448 lifecycle_state: LifecycleState::Active,
449 gates_description: "compatibility alias for vault:reveal",
450 },
451 ActionEntry {
452 name: "vault:write",
453 category: ActionCategory::Vault,
454 lifecycle_state: LifecycleState::Active,
455 gates_description: "write or rotate vault entries",
456 },
457 ActionEntry {
458 name: "secret:read",
459 category: ActionCategory::Vault,
460 lifecycle_state: LifecycleState::Active,
461 gates_description: "compatibility alias for vault:use on default-vault SQL secrets",
462 },
463 ActionEntry {
464 name: "secret:write",
465 category: ActionCategory::Vault,
466 lifecycle_state: LifecycleState::Active,
467 gates_description: "compatibility alias for vault:write on default-vault SQL secrets",
468 },
469 ActionEntry {
470 name: "secret:*",
471 category: ActionCategory::Wildcard,
472 lifecycle_state: LifecycleState::Active,
473 gates_description: "any user-managed SQL vault secret verb",
474 },
475 ActionEntry {
476 name: "vault:unseal",
477 category: ActionCategory::Vault,
478 lifecycle_state: LifecycleState::Active,
479 gates_description: "unseal the vault master key for this session",
480 },
481 ActionEntry {
483 name: "vault:unseal_history",
484 category: ActionCategory::Vault,
485 lifecycle_state: LifecycleState::Deprecated {
486 replacement: Some("vault:reveal_history"),
487 since_version: "0.5.0",
488 },
489 gates_description: "compatibility alias for vault:reveal_history",
490 },
491 ActionEntry {
492 name: "vault:purge",
493 category: ActionCategory::Vault,
494 lifecycle_state: LifecycleState::Active,
495 gates_description: "purge (destructively remove) vault entries",
496 },
497 ActionEntry {
499 name: "evidence:export",
500 category: ActionCategory::Other,
501 lifecycle_state: LifecycleState::Active,
502 gates_description: "export evidence bundles",
503 },
504 ActionEntry {
505 name: "evidence:*",
506 category: ActionCategory::Wildcard,
507 lifecycle_state: LifecycleState::Active,
508 gates_description: "any evidence-pipeline verb",
509 },
510 ActionEntry {
512 name: "red.registry:register",
513 category: ActionCategory::Other,
514 lifecycle_state: LifecycleState::Active,
515 gates_description: "register a new managed-config schema",
516 },
517 ActionEntry {
518 name: "red.registry:supersede",
519 category: ActionCategory::Other,
520 lifecycle_state: LifecycleState::Active,
521 gates_description: "supersede an existing managed-config schema",
522 },
523 ActionEntry {
524 name: "red.registry:*",
525 category: ActionCategory::Wildcard,
526 lifecycle_state: LifecycleState::Active,
527 gates_description: "any registry verb",
528 },
529 ActionEntry {
536 name: "ai:provider:openai",
537 category: ActionCategory::Ai,
538 lifecycle_state: LifecycleState::Active,
539 gates_description: "use the OpenAI provider for ASK / AUTO EMBED / SEARCH SIMILAR",
540 },
541 ActionEntry {
542 name: "ai:provider:anthropic",
543 category: ActionCategory::Ai,
544 lifecycle_state: LifecycleState::Active,
545 gates_description: "use the Anthropic provider for ASK / AUTO EMBED / SEARCH SIMILAR",
546 },
547 ActionEntry {
548 name: "ai:provider:groq",
549 category: ActionCategory::Ai,
550 lifecycle_state: LifecycleState::Active,
551 gates_description: "use the Groq provider for ASK / AUTO EMBED / SEARCH SIMILAR",
552 },
553 ActionEntry {
554 name: "ai:provider:openrouter",
555 category: ActionCategory::Ai,
556 lifecycle_state: LifecycleState::Active,
557 gates_description: "use the OpenRouter provider for ASK / AUTO EMBED / SEARCH SIMILAR",
558 },
559 ActionEntry {
560 name: "ai:provider:together",
561 category: ActionCategory::Ai,
562 lifecycle_state: LifecycleState::Active,
563 gates_description: "use the Together provider for ASK / AUTO EMBED / SEARCH SIMILAR",
564 },
565 ActionEntry {
566 name: "ai:provider:venice",
567 category: ActionCategory::Ai,
568 lifecycle_state: LifecycleState::Active,
569 gates_description: "use the Venice provider for ASK / AUTO EMBED / SEARCH SIMILAR",
570 },
571 ActionEntry {
572 name: "ai:provider:ollama",
573 category: ActionCategory::Ai,
574 lifecycle_state: LifecycleState::Active,
575 gates_description: "use the Ollama provider for ASK / AUTO EMBED / SEARCH SIMILAR",
576 },
577 ActionEntry {
578 name: "ai:provider:deepseek",
579 category: ActionCategory::Ai,
580 lifecycle_state: LifecycleState::Active,
581 gates_description: "use the DeepSeek provider for ASK / AUTO EMBED / SEARCH SIMILAR",
582 },
583 ActionEntry {
584 name: "ai:provider:huggingface",
585 category: ActionCategory::Ai,
586 lifecycle_state: LifecycleState::Active,
587 gates_description: "use the HuggingFace provider for ASK / AUTO EMBED / SEARCH SIMILAR",
588 },
589 ActionEntry {
590 name: "ai:provider:local",
591 category: ActionCategory::Ai,
592 lifecycle_state: LifecycleState::Active,
593 gates_description: "use the local (in-process) embedding provider",
594 },
595 ActionEntry {
596 name: "ai:provider:*",
597 category: ActionCategory::Wildcard,
598 lifecycle_state: LifecycleState::Active,
599 gates_description: "use any AI provider (provider-gate wildcard)",
600 },
601 ActionEntry {
602 name: "ai:*",
603 category: ActionCategory::Wildcard,
604 lifecycle_state: LifecycleState::Active,
605 gates_description: "any AI-namespace verb",
606 },
607 ActionEntry {
613 name: "notify",
614 category: ActionCategory::Notification,
615 lifecycle_state: LifecycleState::Active,
616 gates_description:
617 "publish to / subscribe to ephemeral notification channels in the principal's own tenant",
618 },
619 ActionEntry {
620 name: "notify:cross-tenant",
621 category: ActionCategory::Notification,
622 lifecycle_state: LifecycleState::Active,
623 gates_description:
624 "address ephemeral notification channels in another tenant or the global namespace",
625 },
626 ActionEntry {
627 name: "notify:*",
628 category: ActionCategory::Wildcard,
629 lifecycle_state: LifecycleState::Active,
630 gates_description: "any ephemeral notification verb",
631 },
632 ActionEntry {
639 name: "stream",
640 category: ActionCategory::Stream,
641 lifecycle_state: LifecycleState::Active,
642 gates_description:
643 "append, read, and offset-save on durable streams in the principal's own tenant",
644 },
645 ActionEntry {
646 name: "stream:cross-tenant",
647 category: ActionCategory::Stream,
648 lifecycle_state: LifecycleState::Active,
649 gates_description:
650 "address durable streams in another tenant or the global namespace",
651 },
652 ActionEntry {
653 name: "stream:*",
654 category: ActionCategory::Wildcard,
655 lifecycle_state: LifecycleState::Active,
656 gates_description: "any durable stream verb",
657 },
658 ActionEntry {
666 name: "queue:enqueue",
667 category: ActionCategory::Queue,
668 lifecycle_state: LifecycleState::Active,
669 gates_description: "push / produce a message onto a queue",
670 },
671 ActionEntry {
672 name: "queue:read",
673 category: ActionCategory::Queue,
674 lifecycle_state: LifecycleState::Active,
675 gates_description: "destructive read: pop, group-read, claim",
676 },
677 ActionEntry {
678 name: "queue:peek",
679 category: ActionCategory::Queue,
680 lifecycle_state: LifecycleState::Active,
681 gates_description: "non-destructive read: peek, len, pending, select",
682 },
683 ActionEntry {
684 name: "queue:ack",
685 category: ActionCategory::Queue,
686 lifecycle_state: LifecycleState::Active,
687 gates_description: "acknowledge a delivered queue message",
688 },
689 ActionEntry {
690 name: "queue:nack",
691 category: ActionCategory::Queue,
692 lifecycle_state: LifecycleState::Active,
693 gates_description: "negative-acknowledge / requeue a delivered queue message",
694 },
695 ActionEntry {
696 name: "queue:retry",
697 category: ActionCategory::Queue,
698 lifecycle_state: LifecycleState::Active,
699 gates_description: "override retry policy (e.g. per-failure NACK delay)",
700 },
701 ActionEntry {
702 name: "queue:dlq:move",
703 category: ActionCategory::Queue,
704 lifecycle_state: LifecycleState::Active,
705 gates_description: "move / replay messages between a queue and its DLQ",
706 },
707 ActionEntry {
708 name: "queue:purge",
709 category: ActionCategory::Queue,
710 lifecycle_state: LifecycleState::Active,
711 gates_description: "destructively purge all messages from a queue",
712 },
713 ActionEntry {
714 name: "queue:presence:read",
715 category: ActionCategory::Queue,
716 lifecycle_state: LifecycleState::Active,
717 gates_description: "read consumer presence / heartbeat snapshots",
718 },
719 ActionEntry {
720 name: "queue:*",
721 category: ActionCategory::Wildcard,
722 lifecycle_state: LifecycleState::Active,
723 gates_description: "any queue verb",
724 },
725 ActionEntry {
736 name: "graph:read",
737 category: ActionCategory::Graph,
738 lifecycle_state: LifecycleState::Active,
739 gates_description: "read graph node/edge metadata and graph-wide properties",
740 },
741 ActionEntry {
742 name: "graph:traverse",
743 category: ActionCategory::Graph,
744 lifecycle_state: LifecycleState::Active,
745 gates_description: "execute pattern match / neighborhood / path traversal queries",
746 },
747 ActionEntry {
748 name: "graph:algorithm:run",
749 category: ActionCategory::Graph,
750 lifecycle_state: LifecycleState::Active,
751 gates_description: "run a graph analytics algorithm (centrality, community, components, ...)",
752 },
753 ActionEntry {
754 name: "graph:*",
755 category: ActionCategory::Wildcard,
756 lifecycle_state: LifecycleState::Active,
757 gates_description: "any graph verb",
758 },
759 ActionEntry {
774 name: "ops:read:self",
775 category: ActionCategory::Ops,
776 lifecycle_state: LifecycleState::Active,
777 gates_description: "read single-instance health / lifecycle state",
778 },
779 ActionEntry {
780 name: "ops:read:tenant",
781 category: ActionCategory::Ops,
782 lifecycle_state: LifecycleState::Active,
783 gates_description: "read tenant-scoped operational metrics / aggregates",
784 },
785 ActionEntry {
786 name: "ops:read:cluster",
787 category: ActionCategory::Ops,
788 lifecycle_state: LifecycleState::Active,
789 gates_description:
790 "read cluster topology / replication / backup / full metrics exposition",
791 },
792 ActionEntry {
793 name: "ops:admin",
794 category: ActionCategory::Ops,
795 lifecycle_state: LifecycleState::Active,
796 gates_description:
797 "read security-sensitive operational state (audit log, vault posture)",
798 },
799 ActionEntry {
800 name: "ops:*",
801 category: ActionCategory::Wildcard,
802 lifecycle_state: LifecycleState::Active,
803 gates_description: "any operational read verb",
804 },
805 ActionEntry {
810 name: "cluster:replication:stream",
811 category: ActionCategory::Other,
812 lifecycle_state: LifecycleState::Active,
813 gates_description: "stream primary WAL records and replication snapshots to a replica",
814 },
815 ActionEntry {
816 name: "cluster:replication:ack",
817 category: ActionCategory::Other,
818 lifecycle_state: LifecycleState::Active,
819 gates_description: "acknowledge replica LSN progress to the primary",
820 },
821 ActionEntry {
822 name: "cluster:*",
823 category: ActionCategory::Wildcard,
824 lifecycle_state: LifecycleState::Active,
825 gates_description: "any cluster-scoped capability",
826 },
827 ActionEntry {
839 name: "vector:read",
840 category: ActionCategory::Vector,
841 lifecycle_state: LifecycleState::Active,
842 gates_description: "read vector metadata / data (non-search reads on a vector collection)",
843 },
844 ActionEntry {
845 name: "vector:search",
846 category: ActionCategory::Vector,
847 lifecycle_state: LifecycleState::Active,
848 gates_description: "similarity / text / hybrid search against a vector collection",
849 },
850 ActionEntry {
851 name: "vector:artifact:read",
852 category: ActionCategory::Vector,
853 lifecycle_state: LifecycleState::Active,
854 gates_description: "introspect operational vector index artifacts (pages, status)",
855 },
856 ActionEntry {
857 name: "vector:artifact:rebuild",
858 category: ActionCategory::Vector,
859 lifecycle_state: LifecycleState::Active,
860 gates_description: "rebuild / warmup vector index artifacts",
861 },
862 ActionEntry {
863 name: "vector:admin",
864 category: ActionCategory::Vector,
865 lifecycle_state: LifecycleState::Active,
866 gates_description: "admin operations on a vector collection (clustering, maintenance)",
867 },
868 ActionEntry {
869 name: "vector:*",
870 category: ActionCategory::Wildcard,
871 lifecycle_state: LifecycleState::Active,
872 gates_description: "any vector verb",
873 },
874 ActionEntry {
876 name: "*",
877 category: ActionCategory::Wildcard,
878 lifecycle_state: LifecycleState::Active,
879 gates_description: "any action (escape hatch — audit usage carefully)",
880 },
881 ActionEntry {
882 name: "admin:*",
883 category: ActionCategory::Wildcard,
884 lifecycle_state: LifecycleState::Active,
885 gates_description: "any admin verb",
886 },
887 ActionEntry {
888 name: "vault:*",
889 category: ActionCategory::Wildcard,
890 lifecycle_state: LifecycleState::Active,
891 gates_description: "any vault verb",
892 },
893 ActionEntry {
894 name: "kv:*",
895 category: ActionCategory::Wildcard,
896 lifecycle_state: LifecycleState::Active,
897 gates_description: "any KV verb",
898 },
899 ActionEntry {
900 name: "policy:*",
901 category: ActionCategory::Wildcard,
902 lifecycle_state: LifecycleState::Active,
903 gates_description: "any policy lifecycle verb",
904 },
905];
906
907pub fn is_valid_action(name: &str) -> bool {
911 ACTIONS
912 .iter()
913 .any(|e| e.name == name && !matches!(e.lifecycle_state, LifecycleState::Removed))
914}
915
916pub fn lookup(name: &str) -> Option<&'static ActionEntry> {
918 ACTIONS.iter().find(|e| e.name == name)
919}
920
921#[cfg(test)]
922mod tests {
923 use super::*;
924 use std::collections::HashSet;
925
926 const HISTORICAL_ALLOWLIST: &[&str] = &[
931 "select",
932 "write",
933 "insert",
934 "update",
935 "delete",
936 "truncate",
937 "references",
938 "execute",
939 "usage",
940 "grant",
941 "revoke",
942 "create",
943 "drop",
944 "alter",
945 "policy:put",
946 "policy:drop",
947 "policy:attach",
948 "policy:detach",
949 "policy:simulate",
950 "kv:invalidate",
951 "kv:read",
952 "kv:write",
953 "admin:bootstrap",
954 "admin:audit-read",
955 "admin:reload",
956 "admin:lease-promote",
957 "config:read",
958 "config:write",
959 "config:*",
960 "vault:read_metadata",
961 "vault:read",
962 "vault:write",
963 "secret:read",
964 "secret:write",
965 "secret:*",
966 "vault:unseal",
967 "vault:unseal_history",
968 "vault:purge",
969 "evidence:export",
970 "evidence:*",
971 "red.registry:register",
972 "red.registry:supersede",
973 "red.registry:*",
974 "*",
975 "admin:*",
976 "vault:*",
977 "kv:*",
978 "policy:*",
979 ];
980
981 #[test]
982 fn no_duplicate_names() {
983 let mut seen = HashSet::new();
984 for entry in ACTIONS {
985 assert!(
986 seen.insert(entry.name),
987 "duplicate action name in catalog: {}",
988 entry.name
989 );
990 }
991 }
992
993 #[test]
994 fn covers_historical_allowlist() {
995 let names: HashSet<&'static str> = ACTIONS.iter().map(|e| e.name).collect();
996 for action in HISTORICAL_ALLOWLIST {
997 assert!(
998 names.contains(action),
999 "catalog missing historically-accepted action: {action}",
1000 );
1001 }
1002 }
1003
1004 #[test]
1005 fn historical_allowlist_still_validates() {
1006 for action in HISTORICAL_ALLOWLIST {
1007 assert!(
1008 is_valid_action(action),
1009 "action {action} was accepted before the catalog and must still validate",
1010 );
1011 }
1012 }
1013
1014 #[test]
1015 fn has_at_least_one_deprecated_entry() {
1016 let count = ACTIONS
1017 .iter()
1018 .filter(|e| matches!(e.lifecycle_state, LifecycleState::Deprecated { .. }))
1019 .count();
1020 assert!(
1021 count >= 1,
1022 "catalog must demonstrate the Deprecated lifecycle state with at least one entry",
1023 );
1024 }
1025
1026 #[test]
1027 fn removed_entries_are_rejected() {
1028 for entry in ACTIONS {
1031 if matches!(entry.lifecycle_state, LifecycleState::Removed) {
1032 assert!(
1033 !is_valid_action(entry.name),
1034 "Removed entry {} must not validate",
1035 entry.name,
1036 );
1037 }
1038 }
1039 }
1040
1041 #[test]
1042 fn lookup_finds_known_entries() {
1043 assert!(lookup("policy:put").is_some());
1044 assert!(lookup("definitely-not-an-action").is_none());
1045 }
1046}