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 {
348 name: "admin:bootstrap",
349 category: ActionCategory::Admin,
350 lifecycle_state: LifecycleState::Active,
351 gates_description: "execute the bootstrap workflow",
352 },
353 ActionEntry {
354 name: "admin:audit-read",
355 category: ActionCategory::Admin,
356 lifecycle_state: LifecycleState::Active,
357 gates_description: "read the platform audit log",
358 },
359 ActionEntry {
360 name: "admin:reload",
361 category: ActionCategory::Admin,
362 lifecycle_state: LifecycleState::Active,
363 gates_description: "reload runtime configuration",
364 },
365 ActionEntry {
366 name: "admin:lease-promote",
367 category: ActionCategory::Admin,
368 lifecycle_state: LifecycleState::Active,
369 gates_description: "promote a standby instance via lease handoff",
370 },
371 ActionEntry {
373 name: "config:read",
374 category: ActionCategory::Config,
375 lifecycle_state: LifecycleState::Active,
376 gates_description: "read runtime configuration values",
377 },
378 ActionEntry {
379 name: "config:write",
380 category: ActionCategory::Config,
381 lifecycle_state: LifecycleState::Active,
382 gates_description: "mutate runtime configuration values",
383 },
384 ActionEntry {
385 name: "config:*",
386 category: ActionCategory::Wildcard,
387 lifecycle_state: LifecycleState::Active,
388 gates_description: "any runtime configuration verb",
389 },
390 ActionEntry {
392 name: "vault:read_metadata",
393 category: ActionCategory::Vault,
394 lifecycle_state: LifecycleState::Active,
395 gates_description: "read vault entry metadata (no plaintext)",
396 },
397 ActionEntry {
398 name: "vault:read",
399 category: ActionCategory::Vault,
400 lifecycle_state: LifecycleState::Active,
401 gates_description: "reveal vault entry plaintext",
402 },
403 ActionEntry {
404 name: "vault:write",
405 category: ActionCategory::Vault,
406 lifecycle_state: LifecycleState::Active,
407 gates_description: "write or rotate vault entries",
408 },
409 ActionEntry {
410 name: "vault:unseal",
411 category: ActionCategory::Vault,
412 lifecycle_state: LifecycleState::Active,
413 gates_description: "unseal the vault master key for this session",
414 },
415 ActionEntry {
421 name: "vault:unseal_history",
422 category: ActionCategory::Vault,
423 lifecycle_state: LifecycleState::Deprecated {
424 replacement: Some("vault:read_metadata"),
425 since_version: "0.5.0",
426 },
427 gates_description: "read the vault unseal-event audit trail",
428 },
429 ActionEntry {
430 name: "vault:purge",
431 category: ActionCategory::Vault,
432 lifecycle_state: LifecycleState::Active,
433 gates_description: "purge (destructively remove) vault entries",
434 },
435 ActionEntry {
437 name: "evidence:export",
438 category: ActionCategory::Other,
439 lifecycle_state: LifecycleState::Active,
440 gates_description: "export evidence bundles",
441 },
442 ActionEntry {
443 name: "evidence:*",
444 category: ActionCategory::Wildcard,
445 lifecycle_state: LifecycleState::Active,
446 gates_description: "any evidence-pipeline verb",
447 },
448 ActionEntry {
450 name: "red.registry:register",
451 category: ActionCategory::Other,
452 lifecycle_state: LifecycleState::Active,
453 gates_description: "register a new managed-config schema",
454 },
455 ActionEntry {
456 name: "red.registry:supersede",
457 category: ActionCategory::Other,
458 lifecycle_state: LifecycleState::Active,
459 gates_description: "supersede an existing managed-config schema",
460 },
461 ActionEntry {
462 name: "red.registry:*",
463 category: ActionCategory::Wildcard,
464 lifecycle_state: LifecycleState::Active,
465 gates_description: "any registry verb",
466 },
467 ActionEntry {
474 name: "ai:provider:openai",
475 category: ActionCategory::Ai,
476 lifecycle_state: LifecycleState::Active,
477 gates_description: "use the OpenAI provider for ASK / AUTO EMBED / SEARCH SIMILAR",
478 },
479 ActionEntry {
480 name: "ai:provider:anthropic",
481 category: ActionCategory::Ai,
482 lifecycle_state: LifecycleState::Active,
483 gates_description: "use the Anthropic provider for ASK / AUTO EMBED / SEARCH SIMILAR",
484 },
485 ActionEntry {
486 name: "ai:provider:groq",
487 category: ActionCategory::Ai,
488 lifecycle_state: LifecycleState::Active,
489 gates_description: "use the Groq provider for ASK / AUTO EMBED / SEARCH SIMILAR",
490 },
491 ActionEntry {
492 name: "ai:provider:openrouter",
493 category: ActionCategory::Ai,
494 lifecycle_state: LifecycleState::Active,
495 gates_description: "use the OpenRouter provider for ASK / AUTO EMBED / SEARCH SIMILAR",
496 },
497 ActionEntry {
498 name: "ai:provider:together",
499 category: ActionCategory::Ai,
500 lifecycle_state: LifecycleState::Active,
501 gates_description: "use the Together provider for ASK / AUTO EMBED / SEARCH SIMILAR",
502 },
503 ActionEntry {
504 name: "ai:provider:venice",
505 category: ActionCategory::Ai,
506 lifecycle_state: LifecycleState::Active,
507 gates_description: "use the Venice provider for ASK / AUTO EMBED / SEARCH SIMILAR",
508 },
509 ActionEntry {
510 name: "ai:provider:ollama",
511 category: ActionCategory::Ai,
512 lifecycle_state: LifecycleState::Active,
513 gates_description: "use the Ollama provider for ASK / AUTO EMBED / SEARCH SIMILAR",
514 },
515 ActionEntry {
516 name: "ai:provider:deepseek",
517 category: ActionCategory::Ai,
518 lifecycle_state: LifecycleState::Active,
519 gates_description: "use the DeepSeek provider for ASK / AUTO EMBED / SEARCH SIMILAR",
520 },
521 ActionEntry {
522 name: "ai:provider:huggingface",
523 category: ActionCategory::Ai,
524 lifecycle_state: LifecycleState::Active,
525 gates_description: "use the HuggingFace provider for ASK / AUTO EMBED / SEARCH SIMILAR",
526 },
527 ActionEntry {
528 name: "ai:provider:local",
529 category: ActionCategory::Ai,
530 lifecycle_state: LifecycleState::Active,
531 gates_description: "use the local (in-process) embedding provider",
532 },
533 ActionEntry {
534 name: "ai:provider:*",
535 category: ActionCategory::Wildcard,
536 lifecycle_state: LifecycleState::Active,
537 gates_description: "use any AI provider (provider-gate wildcard)",
538 },
539 ActionEntry {
540 name: "ai:*",
541 category: ActionCategory::Wildcard,
542 lifecycle_state: LifecycleState::Active,
543 gates_description: "any AI-namespace verb",
544 },
545 ActionEntry {
551 name: "notify",
552 category: ActionCategory::Notification,
553 lifecycle_state: LifecycleState::Active,
554 gates_description:
555 "publish to / subscribe to ephemeral notification channels in the principal's own tenant",
556 },
557 ActionEntry {
558 name: "notify:cross-tenant",
559 category: ActionCategory::Notification,
560 lifecycle_state: LifecycleState::Active,
561 gates_description:
562 "address ephemeral notification channels in another tenant or the global namespace",
563 },
564 ActionEntry {
565 name: "notify:*",
566 category: ActionCategory::Wildcard,
567 lifecycle_state: LifecycleState::Active,
568 gates_description: "any ephemeral notification verb",
569 },
570 ActionEntry {
577 name: "stream",
578 category: ActionCategory::Stream,
579 lifecycle_state: LifecycleState::Active,
580 gates_description:
581 "append, read, and offset-save on durable streams in the principal's own tenant",
582 },
583 ActionEntry {
584 name: "stream:cross-tenant",
585 category: ActionCategory::Stream,
586 lifecycle_state: LifecycleState::Active,
587 gates_description:
588 "address durable streams in another tenant or the global namespace",
589 },
590 ActionEntry {
591 name: "stream:*",
592 category: ActionCategory::Wildcard,
593 lifecycle_state: LifecycleState::Active,
594 gates_description: "any durable stream verb",
595 },
596 ActionEntry {
604 name: "queue:enqueue",
605 category: ActionCategory::Queue,
606 lifecycle_state: LifecycleState::Active,
607 gates_description: "push / produce a message onto a queue",
608 },
609 ActionEntry {
610 name: "queue:read",
611 category: ActionCategory::Queue,
612 lifecycle_state: LifecycleState::Active,
613 gates_description: "destructive read: pop, group-read, claim",
614 },
615 ActionEntry {
616 name: "queue:peek",
617 category: ActionCategory::Queue,
618 lifecycle_state: LifecycleState::Active,
619 gates_description: "non-destructive read: peek, len, pending, select",
620 },
621 ActionEntry {
622 name: "queue:ack",
623 category: ActionCategory::Queue,
624 lifecycle_state: LifecycleState::Active,
625 gates_description: "acknowledge a delivered queue message",
626 },
627 ActionEntry {
628 name: "queue:nack",
629 category: ActionCategory::Queue,
630 lifecycle_state: LifecycleState::Active,
631 gates_description: "negative-acknowledge / requeue a delivered queue message",
632 },
633 ActionEntry {
634 name: "queue:retry",
635 category: ActionCategory::Queue,
636 lifecycle_state: LifecycleState::Active,
637 gates_description: "override retry policy (e.g. per-failure NACK delay)",
638 },
639 ActionEntry {
640 name: "queue:dlq:move",
641 category: ActionCategory::Queue,
642 lifecycle_state: LifecycleState::Active,
643 gates_description: "move / replay messages between a queue and its DLQ",
644 },
645 ActionEntry {
646 name: "queue:purge",
647 category: ActionCategory::Queue,
648 lifecycle_state: LifecycleState::Active,
649 gates_description: "destructively purge all messages from a queue",
650 },
651 ActionEntry {
652 name: "queue:presence:read",
653 category: ActionCategory::Queue,
654 lifecycle_state: LifecycleState::Active,
655 gates_description: "read consumer presence / heartbeat snapshots",
656 },
657 ActionEntry {
658 name: "queue:*",
659 category: ActionCategory::Wildcard,
660 lifecycle_state: LifecycleState::Active,
661 gates_description: "any queue verb",
662 },
663 ActionEntry {
674 name: "graph:read",
675 category: ActionCategory::Graph,
676 lifecycle_state: LifecycleState::Active,
677 gates_description: "read graph node/edge metadata and graph-wide properties",
678 },
679 ActionEntry {
680 name: "graph:traverse",
681 category: ActionCategory::Graph,
682 lifecycle_state: LifecycleState::Active,
683 gates_description: "execute pattern match / neighborhood / path traversal queries",
684 },
685 ActionEntry {
686 name: "graph:algorithm:run",
687 category: ActionCategory::Graph,
688 lifecycle_state: LifecycleState::Active,
689 gates_description: "run a graph analytics algorithm (centrality, community, components, ...)",
690 },
691 ActionEntry {
692 name: "graph:*",
693 category: ActionCategory::Wildcard,
694 lifecycle_state: LifecycleState::Active,
695 gates_description: "any graph verb",
696 },
697 ActionEntry {
712 name: "ops:read:self",
713 category: ActionCategory::Ops,
714 lifecycle_state: LifecycleState::Active,
715 gates_description: "read single-instance health / lifecycle state",
716 },
717 ActionEntry {
718 name: "ops:read:tenant",
719 category: ActionCategory::Ops,
720 lifecycle_state: LifecycleState::Active,
721 gates_description: "read tenant-scoped operational metrics / aggregates",
722 },
723 ActionEntry {
724 name: "ops:read:cluster",
725 category: ActionCategory::Ops,
726 lifecycle_state: LifecycleState::Active,
727 gates_description:
728 "read cluster topology / replication / backup / full metrics exposition",
729 },
730 ActionEntry {
731 name: "ops:admin",
732 category: ActionCategory::Ops,
733 lifecycle_state: LifecycleState::Active,
734 gates_description:
735 "read security-sensitive operational state (audit log, vault posture)",
736 },
737 ActionEntry {
738 name: "ops:*",
739 category: ActionCategory::Wildcard,
740 lifecycle_state: LifecycleState::Active,
741 gates_description: "any operational read verb",
742 },
743 ActionEntry {
748 name: "cluster:replication:stream",
749 category: ActionCategory::Other,
750 lifecycle_state: LifecycleState::Active,
751 gates_description: "stream primary WAL records and replication snapshots to a replica",
752 },
753 ActionEntry {
754 name: "cluster:replication:ack",
755 category: ActionCategory::Other,
756 lifecycle_state: LifecycleState::Active,
757 gates_description: "acknowledge replica LSN progress to the primary",
758 },
759 ActionEntry {
760 name: "cluster:*",
761 category: ActionCategory::Wildcard,
762 lifecycle_state: LifecycleState::Active,
763 gates_description: "any cluster-scoped capability",
764 },
765 ActionEntry {
777 name: "vector:read",
778 category: ActionCategory::Vector,
779 lifecycle_state: LifecycleState::Active,
780 gates_description: "read vector metadata / data (non-search reads on a vector collection)",
781 },
782 ActionEntry {
783 name: "vector:search",
784 category: ActionCategory::Vector,
785 lifecycle_state: LifecycleState::Active,
786 gates_description: "similarity / text / hybrid search against a vector collection",
787 },
788 ActionEntry {
789 name: "vector:artifact:read",
790 category: ActionCategory::Vector,
791 lifecycle_state: LifecycleState::Active,
792 gates_description: "introspect operational vector index artifacts (pages, status)",
793 },
794 ActionEntry {
795 name: "vector:artifact:rebuild",
796 category: ActionCategory::Vector,
797 lifecycle_state: LifecycleState::Active,
798 gates_description: "rebuild / warmup vector index artifacts",
799 },
800 ActionEntry {
801 name: "vector:admin",
802 category: ActionCategory::Vector,
803 lifecycle_state: LifecycleState::Active,
804 gates_description: "admin operations on a vector collection (clustering, maintenance)",
805 },
806 ActionEntry {
807 name: "vector:*",
808 category: ActionCategory::Wildcard,
809 lifecycle_state: LifecycleState::Active,
810 gates_description: "any vector verb",
811 },
812 ActionEntry {
814 name: "*",
815 category: ActionCategory::Wildcard,
816 lifecycle_state: LifecycleState::Active,
817 gates_description: "any action (escape hatch — audit usage carefully)",
818 },
819 ActionEntry {
820 name: "admin:*",
821 category: ActionCategory::Wildcard,
822 lifecycle_state: LifecycleState::Active,
823 gates_description: "any admin verb",
824 },
825 ActionEntry {
826 name: "vault:*",
827 category: ActionCategory::Wildcard,
828 lifecycle_state: LifecycleState::Active,
829 gates_description: "any vault verb",
830 },
831 ActionEntry {
832 name: "kv:*",
833 category: ActionCategory::Wildcard,
834 lifecycle_state: LifecycleState::Active,
835 gates_description: "any KV verb",
836 },
837 ActionEntry {
838 name: "policy:*",
839 category: ActionCategory::Wildcard,
840 lifecycle_state: LifecycleState::Active,
841 gates_description: "any policy lifecycle verb",
842 },
843];
844
845pub fn is_valid_action(name: &str) -> bool {
849 ACTIONS
850 .iter()
851 .any(|e| e.name == name && !matches!(e.lifecycle_state, LifecycleState::Removed))
852}
853
854pub fn lookup(name: &str) -> Option<&'static ActionEntry> {
856 ACTIONS.iter().find(|e| e.name == name)
857}
858
859#[cfg(test)]
860mod tests {
861 use super::*;
862 use std::collections::HashSet;
863
864 const HISTORICAL_ALLOWLIST: &[&str] = &[
869 "select",
870 "write",
871 "insert",
872 "update",
873 "delete",
874 "truncate",
875 "references",
876 "execute",
877 "usage",
878 "grant",
879 "revoke",
880 "create",
881 "drop",
882 "alter",
883 "policy:put",
884 "policy:drop",
885 "policy:attach",
886 "policy:detach",
887 "policy:simulate",
888 "kv:invalidate",
889 "admin:bootstrap",
890 "admin:audit-read",
891 "admin:reload",
892 "admin:lease-promote",
893 "config:read",
894 "config:write",
895 "config:*",
896 "vault:read_metadata",
897 "vault:read",
898 "vault:write",
899 "vault:unseal",
900 "vault:unseal_history",
901 "vault:purge",
902 "evidence:export",
903 "evidence:*",
904 "red.registry:register",
905 "red.registry:supersede",
906 "red.registry:*",
907 "*",
908 "admin:*",
909 "vault:*",
910 "kv:*",
911 "policy:*",
912 ];
913
914 #[test]
915 fn no_duplicate_names() {
916 let mut seen = HashSet::new();
917 for entry in ACTIONS {
918 assert!(
919 seen.insert(entry.name),
920 "duplicate action name in catalog: {}",
921 entry.name
922 );
923 }
924 }
925
926 #[test]
927 fn covers_historical_allowlist() {
928 let names: HashSet<&'static str> = ACTIONS.iter().map(|e| e.name).collect();
929 for action in HISTORICAL_ALLOWLIST {
930 assert!(
931 names.contains(action),
932 "catalog missing historically-accepted action: {action}",
933 );
934 }
935 }
936
937 #[test]
938 fn historical_allowlist_still_validates() {
939 for action in HISTORICAL_ALLOWLIST {
940 assert!(
941 is_valid_action(action),
942 "action {action} was accepted before the catalog and must still validate",
943 );
944 }
945 }
946
947 #[test]
948 fn has_at_least_one_deprecated_entry() {
949 let count = ACTIONS
950 .iter()
951 .filter(|e| matches!(e.lifecycle_state, LifecycleState::Deprecated { .. }))
952 .count();
953 assert!(
954 count >= 1,
955 "catalog must demonstrate the Deprecated lifecycle state with at least one entry",
956 );
957 }
958
959 #[test]
960 fn removed_entries_are_rejected() {
961 for entry in ACTIONS {
964 if matches!(entry.lifecycle_state, LifecycleState::Removed) {
965 assert!(
966 !is_valid_action(entry.name),
967 "Removed entry {} must not validate",
968 entry.name,
969 );
970 }
971 }
972 }
973
974 #[test]
975 fn lookup_finds_known_entries() {
976 assert!(lookup("policy:put").is_some());
977 assert!(lookup("definitely-not-an-action").is_none());
978 }
979}