agent_graph_mcp/
policy.rs1use crate::spec::{GraphSpec, MAX_INPUT_BYTES, MAX_ITERATIONS, MAX_OUTPUT_BYTES};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct PolicyFinding {
5 pub code: &'static str,
6 pub detail: String,
7}
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct PolicyReport {
11 pub substantive: bool,
12 pub findings: Vec<PolicyFinding>,
13}
14
15pub fn preflight(spec: &GraphSpec, provider: &str) -> PolicyReport {
17 let mut findings = Vec::new();
18 if spec.spec_version != "2" {
19 findings.push(PolicyFinding {
20 code: "GRAPH_VERSION_UNSUPPORTED",
21 detail: spec.spec_version.clone(),
22 });
23 }
24 if spec.max_iterations.unwrap_or(MAX_ITERATIONS) > MAX_ITERATIONS {
25 findings.push(PolicyFinding {
26 code: "BUDGET_EXCEEDED",
27 detail: "max_iterations".into(),
28 });
29 }
30 if provider.trim().is_empty() {
31 findings.push(PolicyFinding {
32 code: "PROVIDER_DESTINATION_MISSING",
33 detail: "provider destination is empty".into(),
34 });
35 }
36 if spec
37 .nodes
38 .iter()
39 .any(|n| n.max_tokens.unwrap_or(0) > MAX_OUTPUT_BYTES)
40 {
41 findings.push(PolicyFinding {
42 code: "OUTPUT_BUDGET_EXCEEDED",
43 detail: "max_tokens".into(),
44 });
45 }
46 let _ = MAX_INPUT_BYTES;
47 for node in &spec.nodes {
48 if let Err(error) = GraphSpec::executable_node_type(&node.node_type) {
49 findings.push(PolicyFinding {
50 code: "UNSUPPORTED_NODE_TYPE",
51 detail: error,
52 });
53 }
54 if node.evidence_required {
55 findings.push(PolicyFinding {
56 code: "WITNESS_REQUIREMENT",
57 detail: node.id.clone(),
58 });
59 }
60 }
61 PolicyReport {
62 substantive: true,
63 findings,
64 }
65}