1use serde::{Deserialize, Serialize};
7
8use crate::actionability::PlanActionability;
9use crate::calibration::CalibrationBinding;
10use crate::definition::{Definition, RequiredKind};
11use crate::descriptor::OutputKind;
12use crate::execution::ExecutionPolicy;
13use crate::grade::ValueGrade;
14use crate::ids::{
15 ArtifactId, CalibrationId, DefinitionId, DescriptorId, ExecutionPolicyId, PlanId, SourceId,
16};
17use crate::judgment::{Derivation, Notice};
18use crate::limits::PLAN_V1;
19use crate::schema;
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
22#[serde(deny_unknown_fields)]
23pub struct Plan {
24 pub schema: String,
25 pub compiler: String,
27 pub registry: String,
30 pub definition_id: DefinitionId,
31 pub definition: Definition,
32 pub order: Vec<String>,
34 pub steps: Vec<PlanStep>,
36 pub notices: Vec<Notice>,
37 pub execution: PlanExecution,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub sources: Option<Vec<BoundSource>>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub grades: Option<Vec<StaticGrade>>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub actionability: Option<PlanActionability>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub instantiation: Option<Instantiation>,
54 #[serde(
56 default,
57 skip_serializing_if = "Option::is_none",
58 deserialize_with = "crate::screening::present"
59 )]
60 #[schemars(with = "crate::screening::PlanScreening")]
61 pub screening: Option<crate::screening::PlanScreening>,
62 #[serde(
64 default,
65 skip_serializing_if = "Option::is_none",
66 deserialize_with = "crate::acquisition_record::list"
67 )]
68 #[schemars(
69 with = "Vec<crate::acquisition_record::PlanAcquisition>",
70 length(min = 1, max = 4096)
71 )]
72 pub acquisitions: Option<Vec<crate::acquisition_record::PlanAcquisition>>,
73}
74
75impl crate::Document for Plan {
76 const SCHEMA: &'static str = schema::PLAN;
77 const LIMITS: crate::bounded::ParseLimits = PLAN_V1;
78
79 fn schema(&self) -> &str {
80 &self.schema
81 }
82
83 fn parse(bytes: &[u8]) -> Result<Self, crate::DocumentError> {
84 use crate::DocumentError;
85 use crate::bounded::{ParseError, parse_bounded};
86
87 let doc: Self = parse_bounded(bytes, &Self::LIMITS).map_err(DocumentError::Parse)?;
88 if doc.schema != schema::PLAN && doc.schema != schema::PLAN_V3 {
89 return Err(DocumentError::Schema {
90 expected: "rustev.plan/2 or rustev.plan/3",
91 found: doc.schema.chars().take(80).collect(),
92 });
93 }
94 if doc.schema != schema::PLAN_V3
95 && doc
96 .steps
97 .iter()
98 .any(|s| matches!(s.detail, PlanStepDetail::Generate(_)))
99 {
100 return Err(DocumentError::Parse(ParseError::Schema {
101 message: "generate requires rustev.plan/3".into(),
102 }));
103 }
104 if doc.schema != schema::PLAN_V3 && doc.acquisitions.is_some() {
105 return Err(DocumentError::Parse(ParseError::Schema {
106 message: "acquisitions require rustev.plan/3".into(),
107 }));
108 }
109 if doc.schema != schema::PLAN_V3 && doc.screening.is_some() {
110 return Err(DocumentError::Parse(ParseError::Schema {
111 message: "screening is a rustev.plan/3 member".into(),
112 }));
113 }
114 if doc.schema != schema::PLAN_V3
118 && serde_json::from_slice::<PlanMemberPresence>(bytes)
119 .map_err(|e| {
120 DocumentError::Parse(ParseError::Schema {
121 message: e.to_string(),
122 })
123 })?
124 .instantiation
125 {
126 return Err(DocumentError::Parse(ParseError::Schema {
127 message: "instantiation is a rustev.plan/3 member".into(),
128 }));
129 }
130 Ok(doc)
131 }
132
133 fn record_digest(&self) -> Result<crate::ids::ContentDigest, crate::canonical::CanonicalError> {
134 Ok(crate::ids::ContentDigest::from_digest(
135 crate::canonical::tagged_digest(&self.schema, &self.record_canonical()?),
136 ))
137 }
138}
139
140#[derive(Deserialize)]
141struct PlanMemberPresence {
142 #[serde(default, deserialize_with = "member_present")]
143 instantiation: bool,
144}
145
146fn member_present<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
147 serde::de::IgnoredAny::deserialize(deserializer).map(|_| true)
148}
149
150impl crate::Identified for Plan {
151 type Id = PlanId;
152
153 fn wrap(digest: String) -> PlanId {
154 PlanId::from_digest(digest)
155 }
156
157 fn id(&self) -> Result<PlanId, crate::canonical::CanonicalError> {
158 Ok(Self::wrap(crate::canonical::tagged_digest(
159 &self.schema,
160 &crate::Document::canonical(self)?,
161 )))
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
167#[serde(deny_unknown_fields)]
168pub struct BoundSource {
169 pub name: String,
170 pub version: String,
171 pub id: SourceId,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
176#[serde(deny_unknown_fields)]
177pub struct StaticGrade {
178 pub subject: String,
180 pub ceiling: ValueGrade,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
184#[serde(rename_all = "snake_case", deny_unknown_fields)]
185pub enum PlanExecution {
186 None,
187 Declared(Box<DeclaredExecution>),
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
191#[serde(deny_unknown_fields)]
192pub struct DeclaredExecution {
193 pub id: ExecutionPolicyId,
194 pub policy: ExecutionPolicy,
195 pub fallbacks: Vec<FallbackBinding>,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
201#[serde(deny_unknown_fields)]
202pub struct FallbackBinding {
203 pub step: String,
204 pub target: u32,
206 pub backend_id: String,
207 pub artifact: ArtifactId,
208 pub descriptor: DescriptorId,
209 pub output: OutputKind,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
214#[serde(deny_unknown_fields)]
215pub struct PlanStep {
216 pub id: String,
217 pub derivation: Derivation,
219 pub detail: PlanStepDetail,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
223#[serde(rename_all = "snake_case", deny_unknown_fields)]
224pub enum PlanStepDetail {
225 Acquire,
227 Widen,
229 Exact {
230 op: String,
231 version: u32,
232 },
233 Semantic(Box<SemanticBinding>),
234 Unsupported {
236 capability: String,
237 },
238 Corroborate,
240 Generate(Box<crate::generation::GenerateBinding>),
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
244#[serde(deny_unknown_fields)]
245pub struct SemanticBinding {
246 pub backend_id: String,
247 pub artifact: ArtifactId,
248 pub descriptor: DescriptorId,
249 pub output: OutputKind,
250 pub requires: RequiredKind,
252 pub normalization: Normalization,
253 pub calibration: BoundCalibration,
254 pub fallback_taken: bool,
255 pub max_requests: u64,
257 pub max_projection_bytes: u64,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
262#[serde(rename_all = "snake_case")]
263pub enum Normalization {
264 None,
265 #[serde(rename = "softmax/1")]
267 Softmax1,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
271#[serde(rename_all = "snake_case", deny_unknown_fields)]
272pub enum BoundCalibration {
273 None,
274 Bound {
275 id: CalibrationId,
276 binding: CalibrationBinding,
277 },
278}
279
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
283#[serde(deny_unknown_fields)]
284pub struct Instantiation {
285 pub template_schema: String,
287 pub template: serde_json::Value,
288 pub template_id: DefinitionId,
289 pub variant: Variant,
290 pub profile: ProfileChain,
291 pub parameters: Vec<ResolvedParameter>,
292}
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
294#[serde(rename_all = "snake_case", deny_unknown_fields)]
295pub enum Variant {
296 None,
297 Named(String),
298}
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
300#[serde(rename_all = "snake_case", deny_unknown_fields)]
301pub enum ProfileChain {
302 None,
303 Chain(Vec<ProfileEntry>),
304}
305#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
306#[serde(deny_unknown_fields)]
307pub struct ProfileEntry {
308 pub profile: String,
309 pub version: String,
310 pub id: crate::ids::ProfileId,
311}
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
313#[serde(deny_unknown_fields)]
314pub struct ResolvedParameter {
315 pub name: String,
316 pub value: serde_json::Value,
317 pub source: ParameterSource,
318 pub sites: Vec<String>,
319}
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
321#[serde(rename_all = "snake_case", deny_unknown_fields)]
322pub enum ParameterSource {
323 Default,
324 Profile(String),
325}