Skip to main content

pmcp_code_mode/policy/
types.rs

1//! Domain types for policy evaluation.
2//!
3//! These types represent the entities used in Cedar policy evaluation.
4//! They are pure domain types with no AWS SDK dependency.
5
6use crate::graphql::GraphQLQueryInfo;
7use std::collections::HashSet;
8
9/// Server configuration for policy evaluation.
10///
11/// Uses unified attribute names that match the Cedar schema:
12/// - `allow_write`, `allow_delete`, `allow_admin` (unified action flags)
13/// - `blocked_operations`, `allowed_operations` (unified operation lists)
14#[derive(Debug, Clone)]
15pub struct ServerConfigEntity {
16    /// Server ID
17    pub server_id: String,
18
19    /// Server type (e.g., "graphql")
20    pub server_type: String,
21
22    /// Whether write operations (mutations) are allowed
23    pub allow_write: bool,
24
25    /// Whether delete operations are allowed
26    pub allow_delete: bool,
27
28    /// Whether admin operations (introspection) are allowed
29    pub allow_admin: bool,
30
31    /// Allowed operation names (allowlist mode)
32    pub allowed_operations: HashSet<String>,
33
34    /// Blocked operation names (blocklist mode)
35    pub blocked_operations: HashSet<String>,
36
37    /// Maximum query depth
38    pub max_depth: u32,
39
40    /// Maximum field count
41    pub max_field_count: u32,
42
43    /// Maximum estimated cost
44    pub max_cost: u32,
45
46    /// Maximum API calls (for compatibility with unified schema)
47    pub max_api_calls: u32,
48
49    /// Fields that should be blocked
50    pub blocked_fields: HashSet<String>,
51
52    /// Allowed sensitive data categories
53    pub allowed_sensitive_categories: HashSet<String>,
54}
55
56impl Default for ServerConfigEntity {
57    fn default() -> Self {
58        Self {
59            server_id: "unknown".to_string(),
60            server_type: "graphql".to_string(),
61            allow_write: false,
62            allow_delete: false,
63            allow_admin: false,
64            allowed_operations: HashSet::new(),
65            blocked_operations: HashSet::new(),
66            max_depth: 10,
67            max_field_count: 100,
68            max_cost: 1000,
69            max_api_calls: 50,
70            blocked_fields: HashSet::new(),
71            allowed_sensitive_categories: HashSet::new(),
72        }
73    }
74}
75
76/// Operation entity for policy evaluation.
77#[derive(Debug, Clone)]
78pub struct OperationEntity {
79    /// Unique ID for this operation
80    pub id: String,
81
82    /// Operation type: "query", "mutation", or "subscription"
83    pub operation_type: String,
84
85    /// Operation name (if provided)
86    pub operation_name: String,
87
88    /// Root fields accessed
89    pub root_fields: HashSet<String>,
90
91    /// Types accessed
92    pub accessed_types: HashSet<String>,
93
94    /// Fields accessed (Type.field format)
95    pub accessed_fields: HashSet<String>,
96
97    /// Query nesting depth
98    pub depth: u32,
99
100    /// Total field count
101    pub field_count: u32,
102
103    /// Estimated query cost
104    pub estimated_cost: u32,
105
106    /// Whether introspection is used
107    pub has_introspection: bool,
108
109    /// Whether sensitive data is accessed
110    pub accesses_sensitive_data: bool,
111
112    /// Sensitive data categories accessed
113    pub sensitive_categories: HashSet<String>,
114}
115
116impl OperationEntity {
117    /// Create from GraphQL query info.
118    pub fn from_query_info(query_info: &GraphQLQueryInfo) -> Self {
119        use crate::graphql::GraphQLOperationType;
120
121        let operation_type = match query_info.operation_type {
122            GraphQLOperationType::Query => "query",
123            GraphQLOperationType::Mutation => "mutation",
124            GraphQLOperationType::Subscription => "subscription",
125        };
126
127        Self {
128            id: query_info
129                .operation_name
130                .clone()
131                .unwrap_or_else(|| "anonymous".to_string()),
132            operation_type: operation_type.to_string(),
133            operation_name: query_info.operation_name.clone().unwrap_or_default(),
134            root_fields: query_info.root_fields.iter().cloned().collect(),
135            accessed_types: query_info.types_accessed.iter().cloned().collect(),
136            accessed_fields: query_info.fields_accessed.iter().cloned().collect(),
137            depth: query_info.max_depth as u32,
138            field_count: query_info.fields_accessed.len() as u32,
139            estimated_cost: query_info.fields_accessed.len() as u32,
140            has_introspection: query_info.has_introspection,
141            accesses_sensitive_data: false,
142            sensitive_categories: HashSet::new(),
143        }
144    }
145}
146
147/// Authorization decision from policy evaluation.
148#[derive(Debug, Clone)]
149pub struct AuthorizationDecision {
150    /// Whether the operation is allowed
151    pub allowed: bool,
152
153    /// Policy IDs that determined the decision
154    pub determining_policies: Vec<String>,
155
156    /// Error messages (if any)
157    pub errors: Vec<String>,
158}
159
160/// Script entity for policy evaluation (OpenAPI Code Mode).
161///
162/// Unlike GraphQL's single Operation entity, OpenAPI Code Mode validates
163/// JavaScript scripts that can contain multiple API calls with loops and logic.
164#[cfg(feature = "openapi-code-mode")]
165#[derive(Debug, Clone)]
166pub struct ScriptEntity {
167    /// Unique ID for this script validation
168    pub id: String,
169
170    /// Script type: "read_only", "mixed", or "write_only"
171    pub script_type: String,
172
173    /// Whether script contains any write operations (POST/PUT/PATCH/DELETE)
174    pub has_writes: bool,
175
176    /// Whether script contains DELETE operations
177    pub has_deletes: bool,
178
179    /// Total number of API calls in the script
180    pub total_api_calls: u32,
181
182    /// Number of GET calls
183    pub read_calls: u32,
184
185    /// Number of POST/PUT/PATCH calls
186    pub write_calls: u32,
187
188    /// Number of DELETE calls
189    pub delete_calls: u32,
190
191    /// Set of all paths accessed
192    pub accessed_paths: HashSet<String>,
193
194    /// Set of all HTTP methods used
195    pub accessed_methods: HashSet<String>,
196
197    /// Normalized path patterns (IDs replaced with *)
198    pub path_patterns: HashSet<String>,
199
200    /// Called operations in "METHOD:pathPattern" format for allowlist/blocklist matching
201    pub called_operations: HashSet<String>,
202
203    /// Maximum loop iterations (from .slice() bounds)
204    pub loop_iterations: u32,
205
206    /// Maximum nesting depth in the AST
207    pub nesting_depth: u32,
208
209    /// Script length in characters
210    pub script_length: u32,
211
212    /// Whether script accesses sensitive paths (/admin, /internal, etc.)
213    pub accesses_sensitive_path: bool,
214
215    /// Whether script has an unbounded loop
216    pub has_unbounded_loop: bool,
217
218    /// Whether script uses dynamic path interpolation
219    pub has_dynamic_path: bool,
220
221    /// Whether script has a @returns output declaration
222    pub has_output_declaration: bool,
223
224    /// Fields declared in the @returns annotation
225    pub output_fields: HashSet<String>,
226
227    /// Whether script uses spread operators in output (potential field leakage)
228    pub has_spread_in_output: bool,
229}
230
231#[cfg(feature = "openapi-code-mode")]
232impl ScriptEntity {
233    /// Build from JavaScript code analysis.
234    pub fn from_javascript_info(
235        info: &crate::javascript::JavaScriptCodeInfo,
236        sensitive_patterns: &[String],
237    ) -> Self {
238        use crate::javascript::HttpMethod;
239
240        let mut accessed_paths = HashSet::new();
241        let mut accessed_methods = HashSet::new();
242        let mut path_patterns = HashSet::new();
243        let mut called_operations = HashSet::new();
244        let mut read_calls = 0u32;
245        let mut write_calls = 0u32;
246        let mut delete_calls = 0u32;
247        let mut has_dynamic_path = false;
248        let mut accesses_sensitive_path = false;
249
250        for api_call in &info.api_calls {
251            accessed_paths.insert(api_call.path.clone());
252            let method_str = format!("{:?}", api_call.method).to_uppercase();
253            accessed_methods.insert(method_str.clone());
254
255            // Normalize path to pattern
256            let pattern = normalize_path_to_pattern(&api_call.path);
257            path_patterns.insert(pattern.clone());
258
259            // Build called operation string
260            called_operations.insert(format!("{}:{}", method_str, pattern));
261
262            // Count by method type
263            match api_call.method {
264                HttpMethod::Get | HttpMethod::Head | HttpMethod::Options => read_calls += 1,
265                HttpMethod::Delete => delete_calls += 1,
266                _ => write_calls += 1,
267            }
268
269            // Track dynamic paths
270            if api_call.is_dynamic_path {
271                has_dynamic_path = true;
272            }
273
274            // Check for sensitive path access
275            let path_lower = api_call.path.to_lowercase();
276            for pattern in sensitive_patterns {
277                if path_lower.contains(&pattern.to_lowercase()) {
278                    accesses_sensitive_path = true;
279                    break;
280                }
281            }
282        }
283
284        // Determine script type
285        let has_writes = write_calls > 0 || delete_calls > 0;
286        let has_reads = read_calls > 0;
287        let script_type = match (has_reads, has_writes) {
288            (true, false) => "read_only",
289            (false, true) => "write_only",
290            (true, true) => "mixed",
291            (false, false) => "empty",
292        };
293
294        Self {
295            id: info
296                .api_calls
297                .first()
298                .map(|c| format!("{}:{}", format!("{:?}", c.method).to_uppercase(), c.path))
299                .unwrap_or_else(|| "script".to_string()),
300            script_type: script_type.to_string(),
301            has_writes,
302            has_deletes: delete_calls > 0,
303            total_api_calls: info.api_calls.len() as u32,
304            read_calls,
305            write_calls,
306            delete_calls,
307            accessed_paths,
308            accessed_methods,
309            path_patterns,
310            called_operations,
311            loop_iterations: 0,
312            nesting_depth: info.max_depth as u32,
313            script_length: 0,
314            accesses_sensitive_path,
315            has_unbounded_loop: !info.all_loops_bounded && info.loop_count > 0,
316            has_dynamic_path,
317            has_output_declaration: info.output_declaration.has_declaration,
318            output_fields: info.output_declaration.declared_fields.clone(),
319            has_spread_in_output: info.output_declaration.has_spread_risk
320                || info.has_output_spread_risk,
321        }
322    }
323
324    /// Get the policy action for this script using unified action model.
325    pub fn action(&self) -> &'static str {
326        match self.script_type.as_str() {
327            "read_only" | "empty" => "Read",
328            "write_only" | "mixed" => {
329                if self.has_deletes {
330                    "Delete"
331                } else {
332                    "Write"
333                }
334            }
335            _ => "Read",
336        }
337    }
338}
339
340/// Normalize a path to a pattern by replacing numeric/UUID segments with *.
341#[cfg(feature = "openapi-code-mode")]
342pub fn normalize_path_to_pattern(path: &str) -> String {
343    path.split('/')
344        .map(|segment| {
345            if segment.chars().all(|c| c.is_ascii_digit())
346                || (segment.len() == 36 && segment.contains('-'))
347            {
348                "*"
349            } else {
350                segment
351            }
352        })
353        .collect::<Vec<_>>()
354        .join("/")
355}
356
357/// Normalize an operation string to the canonical "METHOD:/path" format.
358#[cfg(feature = "openapi-code-mode")]
359pub fn normalize_operation_format(op: &str) -> String {
360    let trimmed = op.trim();
361
362    let (method, path) = if let Some(idx) = trimmed.find(':') {
363        let potential_path = trimmed[idx + 1..].trim();
364        if potential_path.starts_with('/') {
365            let method = trimmed[..idx].trim();
366            (method, potential_path)
367        } else {
368            return trimmed.to_string();
369        }
370    } else if let Some(idx) = trimmed.find(' ') {
371        let method = trimmed[..idx].trim();
372        let path = trimmed[idx + 1..].trim();
373        (method, path)
374    } else {
375        return trimmed.to_string();
376    };
377
378    let method_upper = method.to_uppercase();
379
380    let normalized_path = path
381        .split('/')
382        .map(|segment| {
383            if segment.starts_with('{') && segment.ends_with('}') {
384                "*"
385            } else if segment.starts_with(':') {
386                "*"
387            } else if segment.chars().all(|c| c.is_ascii_digit()) {
388                "*"
389            } else if segment.len() == 36 && segment.contains('-') {
390                "*"
391            } else {
392                segment
393            }
394        })
395        .collect::<Vec<_>>()
396        .join("/");
397
398    format!("{}:{}", method_upper, normalized_path)
399}
400
401/// Server configuration for OpenAPI Code Mode.
402#[cfg(feature = "openapi-code-mode")]
403#[derive(Debug, Clone)]
404pub struct OpenAPIServerEntity {
405    pub server_id: String,
406    pub server_type: String,
407
408    // Unified action flags
409    pub allow_write: bool,
410    pub allow_delete: bool,
411    pub allow_admin: bool,
412
413    // Write mode: "allow_all", "deny_all", "allowlist", "blocklist"
414    pub write_mode: String,
415
416    // Unified limits
417    pub max_depth: u32,
418    pub max_cost: u32,
419    pub max_api_calls: u32,
420
421    // OpenAPI-specific limits
422    pub max_loop_iterations: u32,
423    pub max_script_length: u32,
424    pub max_nesting_depth: u32,
425    pub execution_timeout_seconds: u32,
426
427    // Unified operation lists
428    pub allowed_operations: HashSet<String>,
429    pub blocked_operations: HashSet<String>,
430
431    // OpenAPI-specific method controls
432    pub allowed_methods: HashSet<String>,
433    pub blocked_methods: HashSet<String>,
434    pub allowed_path_patterns: HashSet<String>,
435    pub blocked_path_patterns: HashSet<String>,
436    pub sensitive_path_patterns: HashSet<String>,
437
438    // Auto-approval settings
439    pub auto_approve_read_only: bool,
440    pub max_api_calls_for_auto_approve: u32,
441
442    // Field control (two-tier blocklist)
443    pub internal_blocked_fields: HashSet<String>,
444    pub output_blocked_fields: HashSet<String>,
445    pub require_output_declaration: bool,
446}
447
448#[cfg(feature = "openapi-code-mode")]
449impl Default for OpenAPIServerEntity {
450    fn default() -> Self {
451        Self {
452            server_id: "unknown".to_string(),
453            server_type: "openapi".to_string(),
454            allow_write: false,
455            allow_delete: false,
456            allow_admin: false,
457            write_mode: "deny_all".to_string(),
458            max_depth: 10,
459            max_cost: 1000,
460            max_api_calls: 50,
461            max_loop_iterations: 100,
462            max_script_length: 10000,
463            max_nesting_depth: 10,
464            execution_timeout_seconds: 30,
465            allowed_operations: HashSet::new(),
466            blocked_operations: HashSet::new(),
467            allowed_methods: HashSet::new(),
468            blocked_methods: HashSet::new(),
469            allowed_path_patterns: HashSet::new(),
470            blocked_path_patterns: ["/admin".into(), "/internal".into()].into_iter().collect(),
471            sensitive_path_patterns: ["/admin".into(), "/internal".into(), "/debug".into()]
472                .into_iter()
473                .collect(),
474            auto_approve_read_only: true,
475            max_api_calls_for_auto_approve: 10,
476            internal_blocked_fields: HashSet::new(),
477            output_blocked_fields: HashSet::new(),
478            require_output_declaration: false,
479        }
480    }
481}
482
483/// Get the Cedar schema in JSON format.
484///
485/// Uses unified action model with Read/Write/Delete/Admin actions.
486pub fn get_code_mode_schema_json() -> serde_json::Value {
487    let applies_to = serde_json::json!({
488        "principalTypes": ["Operation"],
489        "resourceTypes": ["Server"],
490        "context": {
491            "type": "Record",
492            "attributes": {
493                "serverId": { "type": "String", "required": true },
494                "serverType": { "type": "String", "required": true },
495                "userId": { "type": "String", "required": false },
496                "sessionId": { "type": "String", "required": false }
497            }
498        }
499    });
500
501    serde_json::json!({
502        "CodeMode": {
503            "entityTypes": {
504                "Operation": {
505                    "shape": {
506                        "type": "Record",
507                        "attributes": {
508                            "operationType": { "type": "String", "required": true },
509                            "operationName": { "type": "String", "required": true },
510                            "rootFields": { "type": "Set", "element": { "type": "String" } },
511                            "accessedTypes": { "type": "Set", "element": { "type": "String" } },
512                            "accessedFields": { "type": "Set", "element": { "type": "String" } },
513                            "depth": { "type": "Long", "required": true },
514                            "fieldCount": { "type": "Long", "required": true },
515                            "estimatedCost": { "type": "Long", "required": true },
516                            "hasIntrospection": { "type": "Boolean", "required": true },
517                            "accessesSensitiveData": { "type": "Boolean", "required": true },
518                            "sensitiveCategories": { "type": "Set", "element": { "type": "String" } }
519                        }
520                    }
521                },
522                "Server": {
523                    "shape": {
524                        "type": "Record",
525                        "attributes": {
526                            "serverId": { "type": "String", "required": true },
527                            "serverType": { "type": "String", "required": true },
528                            "maxDepth": { "type": "Long", "required": true },
529                            "maxCost": { "type": "Long", "required": true },
530                            "maxApiCalls": { "type": "Long", "required": true },
531                            "allowWrite": { "type": "Boolean", "required": true },
532                            "allowDelete": { "type": "Boolean", "required": true },
533                            "allowAdmin": { "type": "Boolean", "required": true },
534                            "blockedOperations": { "type": "Set", "element": { "type": "String" } },
535                            "allowedOperations": { "type": "Set", "element": { "type": "String" } },
536                            "blockedFields": { "type": "Set", "element": { "type": "String" } }
537                        }
538                    }
539                }
540            },
541            "actions": {
542                "Read": { "appliesTo": applies_to },
543                "Write": { "appliesTo": applies_to },
544                "Delete": { "appliesTo": applies_to },
545                "Admin": { "appliesTo": applies_to }
546            }
547        }
548    })
549}
550
551/// Get the Cedar schema for OpenAPI Code Mode in JSON format.
552#[cfg(feature = "openapi-code-mode")]
553pub fn get_openapi_code_mode_schema_json() -> serde_json::Value {
554    let applies_to = serde_json::json!({
555        "principalTypes": ["Script"],
556        "resourceTypes": ["Server"],
557        "context": {
558            "type": "Record",
559            "attributes": {
560                "serverId": { "type": "String", "required": true },
561                "serverType": { "type": "String", "required": true },
562                "userId": { "type": "String", "required": false },
563                "sessionId": { "type": "String", "required": false }
564            }
565        }
566    });
567
568    serde_json::json!({
569        "CodeMode": {
570            "entityTypes": {
571                "Script": {
572                    "shape": {
573                        "type": "Record",
574                        "attributes": {
575                            "scriptType": { "type": "String", "required": true },
576                            "hasWrites": { "type": "Boolean", "required": true },
577                            "hasDeletes": { "type": "Boolean", "required": true },
578                            "totalApiCalls": { "type": "Long", "required": true },
579                            "readCalls": { "type": "Long", "required": true },
580                            "writeCalls": { "type": "Long", "required": true },
581                            "deleteCalls": { "type": "Long", "required": true },
582                            "accessedPaths": { "type": "Set", "element": { "type": "String" } },
583                            "accessedMethods": { "type": "Set", "element": { "type": "String" } },
584                            "pathPatterns": { "type": "Set", "element": { "type": "String" } },
585                            "calledOperations": { "type": "Set", "element": { "type": "String" } },
586                            "loopIterations": { "type": "Long", "required": true },
587                            "nestingDepth": { "type": "Long", "required": true },
588                            "scriptLength": { "type": "Long", "required": true },
589                            "accessesSensitivePath": { "type": "Boolean", "required": true },
590                            "hasUnboundedLoop": { "type": "Boolean", "required": true },
591                            "hasDynamicPath": { "type": "Boolean", "required": true },
592                            "outputFields": { "type": "Set", "element": { "type": "String" } },
593                            "hasOutputDeclaration": { "type": "Boolean", "required": true },
594                            "hasSpreadInOutput": { "type": "Boolean", "required": true }
595                        }
596                    }
597                },
598                "Server": {
599                    "shape": {
600                        "type": "Record",
601                        "attributes": {
602                            "serverId": { "type": "String", "required": true },
603                            "serverType": { "type": "String", "required": true },
604                            "maxDepth": { "type": "Long", "required": true },
605                            "maxCost": { "type": "Long", "required": true },
606                            "maxApiCalls": { "type": "Long", "required": true },
607                            "allowWrite": { "type": "Boolean", "required": true },
608                            "allowDelete": { "type": "Boolean", "required": true },
609                            "allowAdmin": { "type": "Boolean", "required": true },
610                            "blockedOperations": { "type": "Set", "element": { "type": "String" } },
611                            "allowedOperations": { "type": "Set", "element": { "type": "String" } },
612                            "blockedFields": { "type": "Set", "element": { "type": "String" } },
613                            "maxLoopIterations": { "type": "Long", "required": true },
614                            "maxScriptLength": { "type": "Long", "required": true },
615                            "maxNestingDepth": { "type": "Long", "required": true },
616                            "executionTimeoutSeconds": { "type": "Long", "required": true },
617                            "allowedMethods": { "type": "Set", "element": { "type": "String" } },
618                            "blockedMethods": { "type": "Set", "element": { "type": "String" } },
619                            "allowedPathPatterns": { "type": "Set", "element": { "type": "String" } },
620                            "blockedPathPatterns": { "type": "Set", "element": { "type": "String" } },
621                            "sensitivePathPatterns": { "type": "Set", "element": { "type": "String" } },
622                            "autoApproveReadOnly": { "type": "Boolean", "required": true },
623                            "maxApiCallsForAutoApprove": { "type": "Long", "required": true },
624                            "internalBlockedFields": { "type": "Set", "element": { "type": "String" } },
625                            "outputBlockedFields": { "type": "Set", "element": { "type": "String" } },
626                            "requireOutputDeclaration": { "type": "Boolean", "required": true }
627                        }
628                    }
629                }
630            },
631            "actions": {
632                "Read": { "appliesTo": applies_to },
633                "Write": { "appliesTo": applies_to },
634                "Delete": { "appliesTo": applies_to },
635                "Admin": { "appliesTo": applies_to }
636            }
637        }
638    })
639}
640
641/// Get baseline Cedar policies for OpenAPI Code Mode.
642#[cfg(feature = "openapi-code-mode")]
643pub fn get_openapi_baseline_policies() -> Vec<(&'static str, &'static str, &'static str)> {
644    vec![
645        (
646            "permit_reads",
647            "Permit all read operations (GET scripts)",
648            r#"permit(principal, action == CodeMode::Action::"Read", resource);"#,
649        ),
650        (
651            "permit_writes",
652            "Permit write operations (when enabled)",
653            r#"permit(principal, action == CodeMode::Action::"Write", resource) when { resource.allowWrite == true };"#,
654        ),
655        (
656            "permit_deletes",
657            "Permit delete operations (when enabled)",
658            r#"permit(principal, action == CodeMode::Action::"Delete", resource) when { resource.allowDelete == true };"#,
659        ),
660        (
661            "forbid_sensitive_paths",
662            "Block scripts accessing sensitive paths",
663            r#"forbid(principal, action, resource) when { principal.accessesSensitivePath == true };"#,
664        ),
665        (
666            "forbid_unbounded_loops",
667            "Block scripts with unbounded loops",
668            r#"forbid(principal, action, resource) when { principal.hasUnboundedLoop == true };"#,
669        ),
670        (
671            "forbid_excessive_api_calls",
672            "Enforce API call limit",
673            r#"forbid(principal, action, resource) when { principal.totalApiCalls > resource.maxApiCalls };"#,
674        ),
675        (
676            "forbid_excessive_nesting",
677            "Enforce nesting depth limit",
678            r#"forbid(principal, action, resource) when { principal.nestingDepth > resource.maxNestingDepth };"#,
679        ),
680        (
681            "forbid_output_blocked_fields",
682            "Block scripts that return output-blocked fields",
683            r#"forbid(principal, action, resource) when { principal.outputFields.containsAny(resource.outputBlockedFields) };"#,
684        ),
685        (
686            "forbid_spread_without_declaration",
687            "Block scripts with spread in output when output declaration is required",
688            r#"forbid(principal, action, resource) when { principal.hasSpreadInOutput == true && resource.requireOutputDeclaration == true };"#,
689        ),
690        (
691            "forbid_missing_output_declaration",
692            "Block scripts without output declaration when required",
693            r#"forbid(principal, action, resource) when { principal.hasOutputDeclaration == false && resource.requireOutputDeclaration == true };"#,
694        ),
695    ]
696}
697
698/// Get the baseline Cedar policies.
699pub fn get_baseline_policies() -> Vec<(&'static str, &'static str, &'static str)> {
700    vec![
701        (
702            "permit_reads",
703            "Permit all read operations (queries)",
704            r#"permit(principal, action == CodeMode::Action::"Read", resource);"#,
705        ),
706        (
707            "permit_writes",
708            "Permit write operations (when enabled)",
709            r#"permit(principal, action == CodeMode::Action::"Write", resource) when { resource.allowWrite == true };"#,
710        ),
711        (
712            "permit_deletes",
713            "Permit delete operations (when enabled)",
714            r#"permit(principal, action == CodeMode::Action::"Delete", resource) when { resource.allowDelete == true };"#,
715        ),
716        (
717            "permit_admin",
718            "Permit admin operations (when enabled)",
719            r#"permit(principal, action == CodeMode::Action::"Admin", resource) when { resource.allowAdmin == true };"#,
720        ),
721        (
722            "forbid_blocked_operations",
723            "Block operations in blocklist",
724            r#"forbid(principal, action, resource) when { resource.blockedOperations.contains(principal.operationName) };"#,
725        ),
726        (
727            "forbid_blocked_fields",
728            "Block access to blocked fields",
729            r#"forbid(principal, action, resource) when { resource.blockedFields.containsAny(principal.accessedFields) };"#,
730        ),
731        (
732            "forbid_excessive_depth",
733            "Enforce maximum query depth",
734            r#"forbid(principal, action, resource) when { principal.depth > resource.maxDepth };"#,
735        ),
736        (
737            "forbid_excessive_cost",
738            "Enforce maximum query cost",
739            r#"forbid(principal, action, resource) when { principal.estimatedCost > resource.maxCost };"#,
740        ),
741    ]
742}