1use crate::bounded::ParseLimits;
8use crate::{Document, schema};
9
10pub(crate) fn digest_schema() -> schemars::Schema {
12 schemars::json_schema!({
13 "type": "string",
14 "pattern": "^sha256:[0-9a-f]{64}$"
15 })
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct EmittedSchema {
22 pub file: String,
23 pub bytes: Vec<u8>,
24}
25
26const COMMENT: &str = "Descriptive, derived from the types the bounded parser reads; the parser \
39is the only authority on validity. Where the parser reads two versions of a document with one \
40type, this schema admits what that type admits, including members the later version adds. \
41Byte, depth and count bounds are recorded in x-rustev-parse-limits (a string length here counts \
42characters, the parser counts bytes), and rules applied when a plan is compiled are stated in \
43field descriptions, not enforced here.";
44
45fn emit<T: schemars::JsonSchema + Document>(version: &str, part: Option<&str>) -> EmittedSchema {
46 emit_adjusted::<T>(version, part, |_| {})
47}
48
49fn emit_adjusted<T: schemars::JsonSchema + Document>(
53 version: &str,
54 part: Option<&str>,
55 adjust: impl FnOnce(&mut serde_json::Value),
56) -> EmittedSchema {
57 let generator = schemars::generate::SchemaSettings::draft2020_12().into_generator();
58 let root = generator.into_root_schema_for::<T>();
59 let mut v = serde_json::to_value(&root).expect("a schema is JSON");
60 adjust(&mut v);
61 let title = match part {
62 Some(p) => format!("{version} {p}"),
63 None => version.to_string(),
64 };
65 v["title"] = title.clone().into();
66 v["$comment"] = COMMENT.into();
67 v.get_mut("properties")
72 .and_then(|p| p.as_object_mut())
73 .unwrap_or_else(|| panic!("{title}: the derived schema has no properties"))
74 .insert(
75 "schema".into(),
76 serde_json::json!({ "type": "string", "const": version }),
77 );
78 v["x-rustev-parse-limits"] = limits(&T::LIMITS);
79 let file = format!(
80 "{}{}.schema.json",
81 version.replace('/', "-"),
82 part.map(|p| format!(".{p}")).unwrap_or_default()
83 );
84 let bytes = crate::canonical::canonical_value_bytes(&v).expect("a schema has canonical bytes");
85 EmittedSchema { file, bytes }
86}
87
88const RUN_V2_MEMBERS: [&str; 2] = ["remote_evidence", "remote_evidence_truncated"];
92
93fn run_members(version: &str) -> impl FnOnce(&mut serde_json::Value) + '_ {
94 move |v| {
95 if version != schema::RUN_V3 {
96 v["$defs"]["EvidenceRecord"]["properties"]
97 .as_object_mut()
98 .unwrap()
99 .remove("generation");
100 v["$defs"]["EvidenceRecord"]["properties"]
101 .as_object_mut()
102 .unwrap()
103 .remove("acquisitions");
104 v["$defs"]["StepEvidence"]["properties"]
105 .as_object_mut()
106 .unwrap()
107 .remove("constructed");
108 v["$defs"]["StepStatus"]["oneOf"]
109 .as_array_mut()
110 .unwrap()
111 .retain(|v| v["properties"].get("skipped").is_none());
112 v["properties"]
113 .as_object_mut()
114 .unwrap()
115 .remove("acquisitions");
116 v["$defs"]["RequestRecord"]["properties"]
117 .as_object_mut()
118 .unwrap()
119 .remove("screen");
120 v["$defs"]["RequestResult"]["oneOf"]
121 .as_array_mut()
122 .unwrap()
123 .retain(|variant| variant["const"] != "withdrawn");
124 prune_defs(v);
125 }
126 if version == schema::RUN_V2 {
127 v["required"]
130 .as_array_mut()
131 .expect("the run record has required members")
132 .push(RUN_V2_MEMBERS[0].into());
133 let list = &mut v["properties"][RUN_V2_MEMBERS[0]];
134 assert_eq!(
135 list["type"],
136 serde_json::json!(["array", "null"]),
137 "the remote evidence list is derived as an optional array"
138 );
139 list["type"] = "array".into();
140 return;
141 }
142 let props = v["properties"]
143 .as_object_mut()
144 .expect("the run record has properties");
145 for m in RUN_V2_MEMBERS {
146 props
147 .remove(m)
148 .unwrap_or_else(|| panic!("the run record has no {m} member"));
149 }
150 prune_defs(v);
151 }
152}
153
154fn plan_members(version: &str) -> impl FnOnce(&mut serde_json::Value) + '_ {
157 move |v| {
158 if version != schema::PLAN_V3 {
159 if let Some(variants) = v["$defs"]["PlanStepDetail"]["oneOf"].as_array_mut() {
160 variants.retain(|v| v["properties"].get("generate").is_none());
161 }
162 v["properties"]
163 .as_object_mut()
164 .unwrap()
165 .remove("acquisitions");
166 v["properties"]
167 .as_object_mut()
168 .expect("the plan has properties")
169 .remove("instantiation")
170 .expect("the plan has an instantiation member");
171 v["properties"]
172 .as_object_mut()
173 .unwrap()
174 .remove("screening")
175 .expect("the plan has a screening member");
176 prune_defs(v);
177 }
178 }
179}
180
181fn prune_defs(v: &mut serde_json::Value) {
184 fn refs(v: &serde_json::Value, out: &mut Vec<String>) {
185 match v {
186 serde_json::Value::Object(m) => {
187 if let Some(r) = m.get("$ref").and_then(|r| r.as_str())
188 && let Some(name) = r.strip_prefix("#/$defs/")
189 {
190 out.push(name.to_string());
191 }
192 m.values().for_each(|x| refs(x, out));
193 }
194 serde_json::Value::Array(a) => a.iter().for_each(|x| refs(x, out)),
195 _ => {}
196 }
197 }
198 let Some(defs) = v.get("$defs").cloned() else {
199 return;
200 };
201 let mut root = v.clone();
202 root.as_object_mut().unwrap().remove("$defs");
203 let mut pending = Vec::new();
204 refs(&root, &mut pending);
205 let mut reached = std::collections::BTreeSet::new();
206 while let Some(name) = pending.pop() {
207 if reached.insert(name.clone())
208 && let Some(d) = defs.get(&name)
209 {
210 refs(d, &mut pending);
211 }
212 }
213 let kept: serde_json::Map<String, serde_json::Value> = defs
214 .as_object()
215 .unwrap()
216 .iter()
217 .filter(|(k, _)| reached.contains(*k))
218 .map(|(k, d)| (k.clone(), d.clone()))
219 .collect();
220 v["$defs"] = kept.into();
221}
222
223fn limits(l: &ParseLimits) -> serde_json::Value {
224 serde_json::json!({
225 "max_bytes": l.max_bytes,
226 "max_depth": l.max_depth,
227 "max_string_bytes": l.max_string_bytes,
228 "max_collection_len": l.max_collection_len,
229 "max_total_values": l.max_total_values,
230 "max_number_bytes": l.max_number_bytes,
231 "allow_fractional_numbers": l.allow_fractional_numbers,
232 })
233}
234
235pub fn document_schemas() -> Vec<EmittedSchema> {
237 use crate::{
238 calibration::CalibrationArtifact, definition::Definition, descriptor::BackendDescriptor,
239 eval_report::EvalReport, evidence::EvidenceRecord, execution::ExecutionPolicy,
240 judgment::Judgment, output::BackendOutputDoc, plan::Plan, reason::RuntimeReasonDoc, remote,
241 replay::ReplayBundle, request::RequestDoc, run::RunRecord, snapshot::Snapshot,
242 source::SourceDescriptor,
243 };
244 use crate::{
247 dynamic::{DynamicCapability, DynamicOutcome, DynamicQuestion},
248 live::LiveReexecution,
249 redaction::{REDACTION, RedactionPolicy},
250 trace::{TRACE, TraceLine},
251 };
252 let mut out = vec![
253 emit::<crate::explanation::Explanation>(schema::EXPLANATION, None),
254 emit::<crate::generation_result::TextResult>(schema::TEXT_RESULT, None),
255 emit::<crate::generation::PromptTemplate>(schema::PROMPT_TEMPLATE, None),
256 emit::<crate::generation::GenerateRequest>(schema::GENERATE_REQUEST, None),
257 emit::<crate::extraction_documents::ExtractRules>(schema::EXTRACT_RULES, None),
258 emit::<crate::extraction_documents::ExtractRequest>(schema::EXTRACT_REQUEST, None),
259 emit::<crate::profile::Profile>(schema::PROFILE, None),
260 emit::<crate::plan_diff::PlanDiff>(schema::PLAN_DIFF, None),
261 emit::<Definition>(schema::DEFINITION, None),
262 emit::<Definition>(schema::DEFINITION_V2, None),
263 emit_adjusted::<Plan>(schema::PLAN, None, plan_members(schema::PLAN)),
264 emit_adjusted::<Plan>(schema::PLAN_V3, None, plan_members(schema::PLAN_V3)),
265 emit::<Snapshot>(schema::SNAPSHOT, None),
266 emit::<Snapshot>(schema::SNAPSHOT_V2, None),
267 emit_adjusted::<EvidenceRecord>(schema::EVIDENCE, None, |v| {
268 v["properties"].as_object_mut().unwrap().remove("screening");
269 v["properties"]
270 .as_object_mut()
271 .unwrap()
272 .remove("generation");
273 v["properties"]
274 .as_object_mut()
275 .unwrap()
276 .remove("acquisitions");
277 v["$defs"]["StepEvidence"]["properties"]
278 .as_object_mut()
279 .unwrap()
280 .remove("constructed");
281 v["$defs"]["StepStatus"]["oneOf"]
282 .as_array_mut()
283 .unwrap()
284 .retain(|v| v["properties"].get("skipped").is_none());
285 prune_defs(v);
286 }),
287 emit::<EvidenceRecord>(schema::EVIDENCE_V2, None),
288 emit_adjusted::<RunRecord>(schema::RUN, None, run_members(schema::RUN)),
289 emit_adjusted::<RunRecord>(schema::RUN_V2, None, run_members(schema::RUN_V2)),
290 emit_adjusted::<RunRecord>(schema::RUN_V3, None, run_members(schema::RUN_V3)),
291 emit::<Judgment>(schema::JUDGMENT, None),
292 emit::<Judgment>(schema::JUDGMENT_V2, None),
293 emit::<BackendDescriptor>(schema::BACKEND, None),
294 emit::<CalibrationArtifact>(schema::CALIBRATION, None),
295 emit::<BackendOutputDoc>(schema::BACKEND_OUTPUT, None),
296 emit::<EvalReport>(schema::EVAL_REPORT, None),
297 emit::<ExecutionPolicy>(schema::EXECUTION, None),
298 emit_adjusted::<ReplayBundle>(schema::REPLAY, None, |v| {
299 for key in ["sources", "acquisition_pages"] {
300 v["properties"].as_object_mut().unwrap().remove(key);
301 }
302 v["$defs"]["SupplyEntry"]["properties"]
303 .as_object_mut()
304 .unwrap()
305 .remove("failure_context");
306 prune_defs(v);
307 }),
308 emit::<ReplayBundle>(schema::REPLAY_V2, None),
309 emit::<RequestDoc>(schema::REQUEST, None),
310 emit::<RuntimeReasonDoc>(schema::RUNTIME_REASON, None),
311 emit::<crate::acquisition::AcquisitionRequest>(schema::ACQUISITION_REQUEST, None),
312 emit::<crate::acquisition::AcquisitionPage>(schema::ACQUISITION_PAGE, None),
313 emit::<SourceDescriptor>(schema::SOURCE, None),
314 emit::<remote::DescribeRequest>(schema::REMOTE_DESCRIBE, Some("request")),
315 emit::<remote::DescribeResponse>(schema::REMOTE_DESCRIBE, Some("response")),
316 emit::<remote::InferRequest>(schema::REMOTE_INFER, Some("request")),
317 emit::<remote::InferResponse>(schema::REMOTE_INFER, Some("response")),
318 emit::<remote::CancelRequest>(schema::REMOTE_CANCEL, Some("request")),
319 emit::<remote::CancelResponse>(schema::REMOTE_CANCEL, Some("response")),
320 emit::<remote::ExchangeRecord>(schema::REMOTE_EXCHANGE, None),
321 emit::<DynamicQuestion>(schema::DYNAMIC_QUESTION, None),
322 emit::<DynamicCapability>(schema::DYNAMIC_CAPABILITY, None),
323 emit::<DynamicOutcome>(schema::DYNAMIC_OUTCOME, None),
324 emit::<LiveReexecution>(schema::LIVE_REEXECUTION, None),
325 emit::<RedactionPolicy>(REDACTION, None),
326 emit::<TraceLine>(TRACE, None),
327 ];
328 out.extend([
329 emit::<crate::remote_context::DescribeRequest>(
330 crate::remote_context::DESCRIBE,
331 Some("request"),
332 ),
333 emit::<crate::remote_context::DescribeResponse>(
334 crate::remote_context::DESCRIBE,
335 Some("response"),
336 ),
337 emit::<crate::remote_context::FetchRequest>(crate::remote_context::FETCH, Some("request")),
338 emit::<crate::remote_context::FetchResponse>(
339 crate::remote_context::FETCH,
340 Some("response"),
341 ),
342 emit::<crate::remote_context::CancelRequest>(
343 crate::remote_context::CANCEL,
344 Some("request"),
345 ),
346 emit::<crate::remote_context::CancelResponse>(
347 crate::remote_context::CANCEL,
348 Some("response"),
349 ),
350 emit::<crate::remote_context::Invalidation>(crate::remote_context::INVALIDATION, None),
351 emit::<crate::remote_context::InvalidationsRequest>(
352 crate::remote_context::INVALIDATIONS,
353 Some("request"),
354 ),
355 emit::<crate::remote_context::InvalidationsResponse>(
356 crate::remote_context::INVALIDATIONS,
357 Some("response"),
358 ),
359 emit::<crate::remote_context::ExchangeRecord>(crate::remote_context::EXCHANGE, None),
360 emit::<crate::remote_sink::DescribeRequest>(crate::remote_sink::DESCRIBE, Some("request")),
361 emit::<crate::remote_sink::DescribeResponse>(
362 crate::remote_sink::DESCRIBE,
363 Some("response"),
364 ),
365 emit::<crate::remote_sink::DeliverRequest>(crate::remote_sink::DELIVER, Some("request")),
366 emit::<crate::remote_sink::DeliverResponse>(crate::remote_sink::DELIVER, Some("response")),
367 emit::<crate::serve::Serve>(crate::serve::Serve::SCHEMA, None),
368 emit::<crate::serve::ServeConfig>(crate::serve::ServeConfig::SCHEMA, None),
369 emit::<crate::serve::ServeService>(crate::serve::ServeService::SCHEMA, None),
370 emit::<crate::serve::ServeRegister>(crate::serve::ServeRegister::SCHEMA, None),
371 emit::<crate::serve::ServeDecide>(crate::serve::ServeDecide::SCHEMA, None),
372 emit::<crate::serve::ServeDecision>(crate::serve::ServeDecision::SCHEMA, None),
373 emit::<crate::serve::ServeDecisionState>(crate::serve::ServeDecisionState::SCHEMA, None),
374 emit::<crate::serve::ServeCancel>(crate::serve::ServeCancel::SCHEMA, None),
375 emit::<crate::serve::ServeEvent>(crate::serve::ServeEvent::SCHEMA, None),
376 emit::<crate::serve::ServeRefusal>(crate::serve::ServeRefusal::SCHEMA, None),
377 emit::<crate::serve::ServeReplay>(crate::serve::ServeReplay::SCHEMA, None),
378 emit::<crate::serve::ServeEvaluate>(crate::serve::ServeEvaluate::SCHEMA, None),
379 emit::<crate::serve::ServeReadiness>(crate::serve::ServeReadiness::SCHEMA, None),
380 emit::<crate::serve::ServeShutdown>(crate::serve::ServeShutdown::SCHEMA, None),
381 emit::<crate::serve::ServeInvalidations>(crate::serve::ServeInvalidations::SCHEMA, None),
382 emit::<crate::serve::ServeInvalidationsAck>(
383 crate::serve::ServeInvalidationsAck::SCHEMA,
384 None,
385 ),
386 emit::<crate::plan_bundle::PlanBundle>(crate::plan_bundle::PlanBundle::SCHEMA, None),
387 emit::<crate::plan_bundle::PlanBundleSignature>(
388 crate::plan_bundle::PlanBundleSignature::SCHEMA,
389 None,
390 ),
391 emit::<crate::plan_bundle::PlanRegistration>(
392 crate::plan_bundle::PlanRegistration::SCHEMA,
393 None,
394 ),
395 ]);
396 out.sort_by(|a, b| a.file.cmp(&b.file));
397 out
398}