Skip to main content

agentic_graph_spec/
lib.rs

1//! Native Rust support for Agentic Graph Specification (AGS) 1.0.
2//!
3//! The crate parses JSON/YAML graphs, validates structural and semantic rules,
4//! computes RFC 8785 identities, parses AGX expressions, and creates deterministic
5//! conformance-level-0 plans. It never executes a graph.
6
7#![warn(missing_docs)]
8
9mod agx;
10mod canonical;
11mod parse;
12mod plan;
13mod validate;
14
15pub use agx::{AgxCall, AgxError, ParsedExpression, parse_expression};
16pub use canonical::{CanonicalError, canonical_json, graph_digest};
17pub use parse::{ParseError, load, parse};
18pub use plan::{PlanError, graph_effective_edges, plan_graph, topological_order};
19pub use validate::{validate, validate_path};
20
21use serde::{Deserialize, Serialize};
22use serde_json::{Map, Value};
23
24/// AGS specification version implemented by this crate.
25pub const AGS_VERSION: &str = "1.0";
26/// Version of this Rust support library.
27pub const SUPPORT_VERSION: &str = "1.0.4";
28/// A parsed AGS document represented as a JSON-compatible object.
29pub type Document = Map<String, Value>;
30
31/// One machine-readable validation diagnostic.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct Finding {
34    /// Stable diagnostic code from the AGS validation catalog.
35    pub code: String,
36    /// Diagnostic severity, either `error` or `warning`.
37    pub severity: String,
38    /// Human-readable explanation of the diagnostic.
39    pub message: String,
40    #[serde(skip_serializing_if = "String::is_empty")]
41    /// JSON Pointer locating the affected value, when available.
42    pub pointer: String,
43}
44
45/// The complete result of parsing and validating an AGS document.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ValidationReport {
48    #[serde(skip_serializing_if = "Option::is_none")]
49    /// Parsed document, absent only when loading or parsing failed.
50    pub document: Option<Document>,
51    /// All errors and warnings in discovery order.
52    pub findings: Vec<Finding>,
53    /// Findings whose severity is `error`.
54    pub errors: Vec<Finding>,
55    /// Findings whose severity is `warning`.
56    pub warnings: Vec<Finding>,
57    /// Whether the document has no validation errors.
58    pub ok: bool,
59}
60
61impl ValidationReport {
62    fn new(document: Document) -> Self {
63        Self {
64            document: Some(document),
65            findings: vec![],
66            errors: vec![],
67            warnings: vec![],
68            ok: true,
69        }
70    }
71
72    fn add(
73        &mut self,
74        code: &str,
75        severity: &str,
76        message: impl Into<String>,
77        pointer: impl Into<String>,
78    ) {
79        let finding = Finding {
80            code: code.into(),
81            severity: severity.into(),
82            message: message.into(),
83            pointer: pointer.into(),
84        };
85        self.findings.push(finding.clone());
86        if severity == "error" {
87            self.errors.push(finding);
88            self.ok = false;
89        } else {
90            self.warnings.push(finding);
91        }
92    }
93}
94
95/// A normalized dependency or explicitly declared graph edge.
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct EffectiveEdge {
98    /// Source node identifier.
99    pub from: String,
100    /// Destination node identifier.
101    pub to: String,
102    /// AGS edge kind.
103    pub kind: String,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    /// Optional AGX condition attached to the edge.
106    pub when: Option<String>,
107}
108
109/// Deterministic, non-executing conformance-level-0 graph plan.
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct GraphPlan {
112    /// Graph identifier from the source document.
113    pub graph_id: String,
114    /// RFC 8785/SHA-256 identity of the graph.
115    pub graph_digest: String,
116    /// Deterministic topological node order.
117    pub order: Vec<String>,
118    /// Declared graph entrypoints.
119    pub entrypoints: Vec<String>,
120    /// Normalized dependency and explicit edges.
121    pub effective_edges: Vec<EffectiveEdge>,
122    /// Nodes reachable from an entrypoint.
123    pub reachable: Vec<String>,
124    /// Nodes not reachable from any entrypoint.
125    pub unreachable: Vec<String>,
126    /// Count of nodes by intelligence tier.
127    pub tier_histogram: std::collections::BTreeMap<String, usize>,
128    /// Conservative upper bound on node executions.
129    pub worst_case_node_executions: u64,
130    /// Always false because Level 0 plans never execute graphs.
131    pub executable: bool,
132    /// Features that require a higher conformance execution tier.
133    pub unsupported_features: Vec<String>,
134}
135
136pub(crate) fn object(value: Option<&Value>) -> &Map<String, Value> {
137    value.and_then(Value::as_object).unwrap_or_else(|| {
138        static EMPTY: std::sync::LazyLock<Map<String, Value>> = std::sync::LazyLock::new(Map::new);
139        &EMPTY
140    })
141}
142
143pub(crate) fn strings(value: Option<&Value>) -> Vec<String> {
144    value
145        .and_then(Value::as_array)
146        .into_iter()
147        .flatten()
148        .filter_map(Value::as_str)
149        .map(str::to_owned)
150        .collect()
151}