Skip to main content

ncp_runtime/
manifest.rs

1#![allow(dead_code)]
2// NOTE: This module mirrors the NCP spec schema. Many fields are intentionally
3// not read yet in Phase 2, but must deserialize for forward compatibility.
4
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8use anyhow::{Context, Result};
9use serde::Deserialize;
10
11// ── Brick Manifest ──────────────────────────────────────────────────
12
13#[derive(Debug, Deserialize)]
14pub struct BrickManifest {
15    pub brick_id: String,
16    pub version: String,
17    pub ncp_protocol_range: String,
18    #[serde(default)]
19    pub required_runtime_features: Vec<String>,
20    pub status: String,
21    #[serde(default)]
22    pub successor: Option<String>,
23    #[serde(default)]
24    pub confidence_threshold: Option<f64>,
25    pub artifact: Artifact,
26    pub schemas: Schemas,
27    pub limits: Limits,
28    #[serde(default)]
29    pub capabilities: Vec<String>,
30    pub determinism: Determinism,
31    pub time_model: String,
32    pub carry_state_class: String,
33    pub carry_state_transport: String,
34    pub carry_state_max_bytes: u64,
35    #[serde(default)]
36    pub carry_state_side_effects_schema: Option<String>,
37    #[serde(default)]
38    pub graph_ref_slots: Vec<GraphRefSlot>,
39}
40
41#[derive(Debug, Deserialize)]
42pub struct Artifact {
43    pub format: String,
44    pub entrypoint: String,
45    pub digest: String,
46    pub size_bytes: u64,
47    #[serde(default)]
48    pub path: Option<PathBuf>,
49}
50
51#[derive(Debug, Deserialize)]
52pub struct Schemas {
53    pub input: String,
54    pub output: String,
55    pub carry_state: Option<String>,
56    #[serde(default)]
57    pub metadata: Option<String>,
58}
59
60#[derive(Debug, Deserialize)]
61pub struct Limits {
62    pub max_ms: u64,
63    pub max_mem_mb: u64,
64    pub max_output_bytes: u64,
65    #[serde(default)]
66    pub max_input_bytes: Option<u64>,
67    #[serde(default)]
68    pub estimated_cost_per_invoke_usd: Option<f64>,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct Determinism {
73    pub mode: String,
74    #[serde(default)]
75    pub epsilon: Option<f64>,
76    #[serde(default)]
77    pub seed_source: Option<String>,
78    #[serde(default)]
79    pub model_pin: Option<String>,
80    #[serde(default)]
81    pub reproducibility_level: Option<String>,
82}
83
84#[derive(Debug, Deserialize)]
85pub struct GraphRefSlot {
86    pub slot: String,
87    #[serde(rename = "type")]
88    pub slot_type: String,
89    pub access: String,
90    pub ref_consistency: String,
91}
92
93// ── Graph Manifest ──────────────────────────────────────────────────
94
95#[derive(Debug, Deserialize)]
96pub struct GraphManifest {
97    pub graph_id: String,
98    pub graph_version: String,
99    pub synaptic_state_version: String,
100    pub nodes: Vec<Node>,
101    pub edges: Vec<Edge>,
102}
103
104#[derive(Debug, Deserialize)]
105pub struct Node {
106    pub node_id: String,
107    pub brick: NodeBrick,
108    #[serde(default)]
109    pub activation: Option<Activation>,
110    #[serde(default)]
111    pub bindings: Bindings,
112    #[serde(default)]
113    pub carry_state_lifecycle: Option<CarryStateLifecycle>,
114}
115
116#[derive(Debug, Deserialize)]
117pub struct NodeBrick {
118    pub brick_id: String,
119    pub version_or_range: String,
120}
121
122#[derive(Debug, Deserialize)]
123pub struct Activation {
124    pub mode: String,
125    #[serde(default)]
126    pub timeout_ms: Option<u64>,
127    #[serde(default)]
128    pub allow_partial: Option<bool>,
129    #[serde(default)]
130    pub required_fields: Option<Vec<String>>,
131    #[serde(default)]
132    pub defaults: Option<HashMap<String, serde_yaml::Value>>,
133    #[serde(default)]
134    pub conflict_resolution: Option<String>,
135    #[serde(default)]
136    pub quorum_n: Option<u64>,
137    #[serde(default)]
138    pub quorum_m: Option<u64>,
139}
140
141#[derive(Debug, Default, Deserialize)]
142pub struct Bindings {
143    #[serde(default)]
144    pub graph_ref_slot_values: HashMap<String, serde_yaml::Value>,
145}
146
147#[derive(Debug, Deserialize)]
148pub struct CarryStateLifecycle {
149    #[serde(default)]
150    pub init: Option<String>,
151    #[serde(default)]
152    pub ttl_seconds: Option<u64>,
153    #[serde(default)]
154    pub on_graph_version_change: Option<String>,
155}
156
157#[derive(Debug, Clone, Deserialize)]
158pub struct Edge {
159    pub edge_id: String,
160    pub source_node: String,
161    pub target_node: String,
162    #[serde(default)]
163    pub mapping: Vec<FieldMapping>,
164    #[serde(default)]
165    pub on_success: Option<OnSuccess>,
166    #[serde(default)]
167    pub on_error: Option<HashMap<String, Vec<String>>>,
168    #[serde(default)]
169    pub priority: Option<i64>,
170}
171
172#[derive(Debug, Clone, Deserialize)]
173pub struct FieldMapping {
174    pub from: String,
175    pub to: String,
176}
177
178#[derive(Debug, Clone, Deserialize)]
179pub struct OnSuccess {
180    #[serde(default)]
181    pub weight: Option<f64>,
182    #[serde(default)]
183    pub threshold: Option<f64>,
184}
185
186// ── Loading ─────────────────────────────────────────────────────────
187
188pub fn load_graph(path: &Path) -> Result<GraphManifest> {
189    let contents = std::fs::read_to_string(path)
190        .with_context(|| format!("reading graph manifest '{}'", path.display()))?;
191    let graph: GraphManifest = serde_yaml::from_str(&contents)
192        .with_context(|| format!("parsing graph manifest '{}'", path.display()))?;
193    Ok(graph)
194}
195
196pub fn load_brick(path: &Path) -> Result<BrickManifest> {
197    let contents = std::fs::read_to_string(path)
198        .with_context(|| format!("reading brick manifest '{}'", path.display()))?;
199    let brick: BrickManifest = serde_yaml::from_str(&contents)
200        .with_context(|| format!("parsing brick manifest '{}'", path.display()))?;
201    Ok(brick)
202}