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 extraction_v1(v, "/$defs/EvidenceRecord/properties");
97 v["$defs"]["EvidenceRecord"]["properties"]
98 .as_object_mut()
99 .unwrap()
100 .remove("generation");
101 v["$defs"]["EvidenceRecord"]["properties"]
102 .as_object_mut()
103 .unwrap()
104 .remove("acquisitions");
105 v["$defs"]["StepEvidence"]["properties"]
106 .as_object_mut()
107 .unwrap()
108 .remove("constructed");
109 if let Some(p) = v["$defs"]["StepEvidence"]["properties"].as_object_mut() {
111 p.remove("ranking");
112 p.remove("selection");
113 }
114 v["$defs"]["StepStatus"]["oneOf"]
115 .as_array_mut()
116 .unwrap()
117 .retain(|v| v["properties"].get("skipped").is_none());
118 v["properties"]
119 .as_object_mut()
120 .unwrap()
121 .remove("acquisitions");
122 v["$defs"]["RequestRecord"]["properties"]
123 .as_object_mut()
124 .unwrap()
125 .remove("screen");
126 v["$defs"]["RequestResult"]["oneOf"]
127 .as_array_mut()
128 .unwrap()
129 .retain(|variant| variant["const"] != "withdrawn");
130 prune_defs(v);
131 }
132 if version == schema::RUN_V2 {
133 v["required"]
136 .as_array_mut()
137 .expect("the run record has required members")
138 .push(RUN_V2_MEMBERS[0].into());
139 let list = &mut v["properties"][RUN_V2_MEMBERS[0]];
140 assert_eq!(
141 list["type"],
142 serde_json::json!(["array", "null"]),
143 "the remote evidence list is derived as an optional array"
144 );
145 list["type"] = "array".into();
146 return;
147 }
148 let props = v["properties"]
149 .as_object_mut()
150 .expect("the run record has properties");
151 for m in RUN_V2_MEMBERS {
152 props
153 .remove(m)
154 .unwrap_or_else(|| panic!("the run record has no {m} member"));
155 }
156 prune_defs(v);
157 }
158}
159
160fn plan_members(version: &str) -> impl FnOnce(&mut serde_json::Value) + '_ {
163 move |v| {
164 if version != schema::PLAN_V3 {
165 if let Some(variants) = v["$defs"]["PlanStepDetail"]["oneOf"].as_array_mut() {
166 variants.retain(|v| {
167 [
168 "generate",
169 "extract",
170 "accept_model_derived",
171 "rank",
172 "select",
173 ]
174 .iter()
175 .all(|key| v["properties"].get(*key).is_none())
176 });
177 }
178 v["properties"]
179 .as_object_mut()
180 .unwrap()
181 .remove("acquisitions");
182 v["properties"]
183 .as_object_mut()
184 .expect("the plan has properties")
185 .remove("instantiation")
186 .expect("the plan has an instantiation member");
187 v["properties"]
188 .as_object_mut()
189 .unwrap()
190 .remove("screening")
191 .expect("the plan has a screening member");
192 prune_defs(v);
193 }
194 }
195}
196
197fn extraction_v1(v: &mut serde_json::Value, evidence_properties: &str) {
199 if let Some(props) = v
200 .pointer_mut(evidence_properties)
201 .and_then(|p| p.as_object_mut())
202 {
203 props.remove("extraction");
204 props.remove("acceptance");
205 }
206 for (ty, member) in [("Lineage", "producers"), ("InputEvidence", "producer")] {
207 if let Some(props) = v["$defs"][ty]["properties"].as_object_mut() {
208 props.remove(member);
209 }
210 }
211 if let Some(variants) = v["$defs"]["OutValue"]["oneOf"].as_array_mut() {
212 variants.retain(|v| v["properties"].get("attributed").is_none());
213 }
214}
215
216fn prune_defs(v: &mut serde_json::Value) {
219 fn refs(v: &serde_json::Value, out: &mut Vec<String>) {
220 match v {
221 serde_json::Value::Object(m) => {
222 if let Some(r) = m.get("$ref").and_then(|r| r.as_str())
223 && let Some(name) = r.strip_prefix("#/$defs/")
224 {
225 out.push(name.to_string());
226 }
227 m.values().for_each(|x| refs(x, out));
228 }
229 serde_json::Value::Array(a) => a.iter().for_each(|x| refs(x, out)),
230 _ => {}
231 }
232 }
233 let Some(defs) = v.get("$defs").cloned() else {
234 return;
235 };
236 let mut root = v.clone();
237 root.as_object_mut().unwrap().remove("$defs");
238 let mut pending = Vec::new();
239 refs(&root, &mut pending);
240 let mut reached = std::collections::BTreeSet::new();
241 while let Some(name) = pending.pop() {
242 if reached.insert(name.clone())
243 && let Some(d) = defs.get(&name)
244 {
245 refs(d, &mut pending);
246 }
247 }
248 let kept: serde_json::Map<String, serde_json::Value> = defs
249 .as_object()
250 .unwrap()
251 .iter()
252 .filter(|(k, _)| reached.contains(*k))
253 .map(|(k, d)| (k.clone(), d.clone()))
254 .collect();
255 v["$defs"] = kept.into();
256}
257
258fn limits(l: &ParseLimits) -> serde_json::Value {
259 serde_json::json!({
260 "max_bytes": l.max_bytes,
261 "max_depth": l.max_depth,
262 "max_string_bytes": l.max_string_bytes,
263 "max_collection_len": l.max_collection_len,
264 "max_total_values": l.max_total_values,
265 "max_number_bytes": l.max_number_bytes,
266 "allow_fractional_numbers": l.allow_fractional_numbers,
267 })
268}
269
270pub fn document_schemas() -> Vec<EmittedSchema> {
272 use crate::{
273 calibration::CalibrationArtifact, definition::Definition, descriptor::BackendDescriptor,
274 eval_report::EvalReport, evidence::EvidenceRecord, execution::ExecutionPolicy,
275 judgment::Judgment, output::BackendOutputDoc, plan::Plan, reason::RuntimeReasonDoc, remote,
276 replay::ReplayBundle, request::RequestDoc, run::RunRecord, snapshot::Snapshot,
277 source::SourceDescriptor,
278 };
279 use crate::{
282 dynamic::{DynamicCapability, DynamicOutcome, DynamicQuestion},
283 live::LiveReexecution,
284 redaction::{REDACTION, RedactionPolicy},
285 trace::{TRACE, TraceLine},
286 };
287 let mut out = vec![
288 emit::<crate::explanation::Explanation>(schema::EXPLANATION, None),
289 emit::<crate::generation_result::TextResult>(schema::TEXT_RESULT, None),
290 emit::<crate::generation::PromptTemplate>(schema::PROMPT_TEMPLATE, None),
291 emit::<crate::generation::GenerateRequest>(schema::GENERATE_REQUEST, None),
292 emit::<crate::extraction_documents::ExtractRules>(schema::EXTRACT_RULES, None),
293 emit::<crate::extraction_documents::ExtractRequest>(schema::EXTRACT_REQUEST, None),
294 emit::<crate::profile::Profile>(schema::PROFILE, None),
295 emit::<crate::plan_diff::PlanDiff>(schema::PLAN_DIFF, None),
296 emit::<Definition>(schema::DEFINITION, None),
297 emit::<Definition>(schema::DEFINITION_V2, None),
298 emit_adjusted::<Plan>(schema::PLAN, None, plan_members(schema::PLAN)),
299 emit_adjusted::<Plan>(schema::PLAN_V3, None, plan_members(schema::PLAN_V3)),
300 emit::<Snapshot>(schema::SNAPSHOT, None),
301 emit::<Snapshot>(schema::SNAPSHOT_V2, None),
302 emit_adjusted::<EvidenceRecord>(schema::EVIDENCE, None, |v| {
303 extraction_v1(v, "/properties");
304 v["properties"].as_object_mut().unwrap().remove("screening");
305 v["properties"]
306 .as_object_mut()
307 .unwrap()
308 .remove("generation");
309 v["properties"]
310 .as_object_mut()
311 .unwrap()
312 .remove("acquisitions");
313 v["$defs"]["StepEvidence"]["properties"]
314 .as_object_mut()
315 .unwrap()
316 .remove("constructed");
317 if let Some(p) = v["$defs"]["StepEvidence"]["properties"].as_object_mut() {
319 p.remove("ranking");
320 p.remove("selection");
321 }
322 v["$defs"]["StepStatus"]["oneOf"]
323 .as_array_mut()
324 .unwrap()
325 .retain(|v| v["properties"].get("skipped").is_none());
326 prune_defs(v);
327 }),
328 emit::<EvidenceRecord>(schema::EVIDENCE_V2, None),
329 emit_adjusted::<RunRecord>(schema::RUN, None, run_members(schema::RUN)),
330 emit_adjusted::<RunRecord>(schema::RUN_V2, None, run_members(schema::RUN_V2)),
331 emit_adjusted::<RunRecord>(schema::RUN_V3, None, run_members(schema::RUN_V3)),
332 emit_adjusted::<Judgment>(schema::JUDGMENT, None, |v| {
333 extraction_v1(v, "/properties");
334 prune_defs(v);
335 }),
336 emit::<Judgment>(schema::JUDGMENT_V2, None),
337 emit::<BackendDescriptor>(schema::BACKEND, None),
338 emit::<CalibrationArtifact>(schema::CALIBRATION, None),
339 emit::<BackendOutputDoc>(schema::BACKEND_OUTPUT, None),
340 emit::<EvalReport>(schema::EVAL_REPORT, None),
341 emit::<ExecutionPolicy>(schema::EXECUTION, None),
342 emit_adjusted::<ReplayBundle>(schema::REPLAY, None, |v| {
343 for key in ["sources", "acquisition_pages"] {
344 v["properties"].as_object_mut().unwrap().remove(key);
345 }
346 v["$defs"]["SupplyEntry"]["properties"]
347 .as_object_mut()
348 .unwrap()
349 .remove("failure_context");
350 prune_defs(v);
351 }),
352 emit::<ReplayBundle>(schema::REPLAY_V2, None),
353 emit::<RequestDoc>(schema::REQUEST, None),
354 emit::<RuntimeReasonDoc>(schema::RUNTIME_REASON, None),
355 emit::<crate::acquisition::AcquisitionRequest>(schema::ACQUISITION_REQUEST, None),
356 emit::<crate::acquisition::AcquisitionPage>(schema::ACQUISITION_PAGE, None),
357 emit::<SourceDescriptor>(schema::SOURCE, None),
358 emit::<remote::DescribeRequest>(schema::REMOTE_DESCRIBE, Some("request")),
359 emit::<remote::DescribeResponse>(schema::REMOTE_DESCRIBE, Some("response")),
360 emit::<remote::InferRequest>(schema::REMOTE_INFER, Some("request")),
361 emit::<remote::InferResponse>(schema::REMOTE_INFER, Some("response")),
362 emit::<remote::CancelRequest>(schema::REMOTE_CANCEL, Some("request")),
363 emit::<remote::CancelResponse>(schema::REMOTE_CANCEL, Some("response")),
364 emit::<remote::ExchangeRecord>(schema::REMOTE_EXCHANGE, None),
365 emit::<DynamicQuestion>(schema::DYNAMIC_QUESTION, None),
366 emit::<DynamicCapability>(schema::DYNAMIC_CAPABILITY, None),
367 emit::<DynamicOutcome>(schema::DYNAMIC_OUTCOME, None),
368 emit::<LiveReexecution>(schema::LIVE_REEXECUTION, None),
369 emit::<RedactionPolicy>(REDACTION, None),
370 emit::<TraceLine>(TRACE, None),
371 ];
372 out.extend([
373 emit::<crate::remote_context::DescribeRequest>(
374 crate::remote_context::DESCRIBE,
375 Some("request"),
376 ),
377 emit::<crate::remote_context::DescribeResponse>(
378 crate::remote_context::DESCRIBE,
379 Some("response"),
380 ),
381 emit::<crate::remote_context::FetchRequest>(crate::remote_context::FETCH, Some("request")),
382 emit::<crate::remote_context::FetchResponse>(
383 crate::remote_context::FETCH,
384 Some("response"),
385 ),
386 emit::<crate::remote_context::CancelRequest>(
387 crate::remote_context::CANCEL,
388 Some("request"),
389 ),
390 emit::<crate::remote_context::CancelResponse>(
391 crate::remote_context::CANCEL,
392 Some("response"),
393 ),
394 emit::<crate::remote_context::Invalidation>(crate::remote_context::INVALIDATION, None),
395 emit::<crate::remote_context::InvalidationsRequest>(
396 crate::remote_context::INVALIDATIONS,
397 Some("request"),
398 ),
399 emit::<crate::remote_context::InvalidationsResponse>(
400 crate::remote_context::INVALIDATIONS,
401 Some("response"),
402 ),
403 emit::<crate::remote_context::ExchangeRecord>(crate::remote_context::EXCHANGE, None),
404 emit::<crate::remote_sink::DescribeRequest>(crate::remote_sink::DESCRIBE, Some("request")),
405 emit::<crate::remote_sink::DescribeResponse>(
406 crate::remote_sink::DESCRIBE,
407 Some("response"),
408 ),
409 emit::<crate::remote_sink::DeliverRequest>(crate::remote_sink::DELIVER, Some("request")),
410 emit::<crate::remote_sink::DeliverResponse>(crate::remote_sink::DELIVER, Some("response")),
411 emit::<crate::serve::Serve>(crate::serve::Serve::SCHEMA, None),
412 emit::<crate::serve::ServeConfig>(crate::serve::ServeConfig::SCHEMA, None),
413 emit::<crate::serve::ServeService>(crate::serve::ServeService::SCHEMA, None),
414 emit::<crate::serve::ServeRegister>(crate::serve::ServeRegister::SCHEMA, None),
415 emit::<crate::serve::ServeDecide>(crate::serve::ServeDecide::SCHEMA, None),
416 emit::<crate::serve::ServeDecision>(crate::serve::ServeDecision::SCHEMA, None),
417 emit::<crate::serve::ServeDecisionState>(crate::serve::ServeDecisionState::SCHEMA, None),
418 emit::<crate::serve::ServeCancel>(crate::serve::ServeCancel::SCHEMA, None),
419 emit::<crate::serve::ServeEvent>(crate::serve::ServeEvent::SCHEMA, None),
420 emit::<crate::serve::ServeRefusal>(crate::serve::ServeRefusal::SCHEMA, None),
421 emit::<crate::serve::ServeReplay>(crate::serve::ServeReplay::SCHEMA, None),
422 emit::<crate::serve::ServeEvaluate>(crate::serve::ServeEvaluate::SCHEMA, None),
423 emit::<crate::serve::ServeReadiness>(crate::serve::ServeReadiness::SCHEMA, None),
424 emit::<crate::serve::ServeShutdown>(crate::serve::ServeShutdown::SCHEMA, None),
425 emit::<crate::serve::ServeInvalidations>(crate::serve::ServeInvalidations::SCHEMA, None),
426 emit::<crate::serve::ServeInvalidationsAck>(
427 crate::serve::ServeInvalidationsAck::SCHEMA,
428 None,
429 ),
430 emit::<crate::plan_bundle::PlanBundle>(crate::plan_bundle::PlanBundle::SCHEMA, None),
431 emit::<crate::plan_bundle::PlanBundleSignature>(
432 crate::plan_bundle::PlanBundleSignature::SCHEMA,
433 None,
434 ),
435 emit::<crate::plan_bundle::PlanRegistration>(
436 crate::plan_bundle::PlanRegistration::SCHEMA,
437 None,
438 ),
439 ]);
440 out.sort_by(|a, b| a.file.cmp(&b.file));
441 out
442}