1use crate::bounded::{ParseError, ParseLimits, parse_bounded};
9use crate::canonical::{CanonicalError, tagged_digest};
10use crate::ids::{ContentDigest, DescriptorId, ExecutionPolicyId, PlanId};
11use crate::plan_bundle::{PlanBundle, PlanBundleSignature, RegistrationId, Signer, SigningMode};
12use crate::scope::Handle;
13use crate::{Document, DocumentError, Timestamp};
14use schemars::{JsonSchema, Schema, SchemaGenerator};
15use serde::{Deserialize, Deserializer, Serialize};
16use std::{borrow::Cow, collections::BTreeMap, marker::PhantomData};
17
18pub const MIB: usize = 1024 * 1024;
19pub const SERVICE_LIMITS: ParseLimits = ParseLimits {
21 max_bytes: 4 * MIB,
22 max_depth: 64,
23 max_string_bytes: 8 * MIB,
24 max_collection_len: 10000,
25 max_total_values: 1100000,
26 max_number_bytes: 40,
27 allow_fractional_numbers: true,
28};
29pub const BUNDLE_LIMITS: ParseLimits = ParseLimits {
30 max_bytes: 16 * MIB,
31 ..SERVICE_LIMITS
32};
33pub const DECISION_LIMITS: ParseLimits = ParseLimits {
34 max_bytes: 9 * MIB,
35 ..SERVICE_LIMITS
36};
37pub const REFUSAL_LIMITS: ParseLimits = ParseLimits {
38 max_bytes: 4096,
39 ..SERVICE_LIMITS
40};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
44#[serde(transparent)]
45pub struct Number<const MIN: u64, const MAX: u64>(u64);
46impl<const MIN: u64, const MAX: u64> Number<MIN, MAX> {
47 pub fn new(value: u64) -> Result<Self, String> {
48 if (MIN..=MAX).contains(&value) {
49 Ok(Self(value))
50 } else {
51 Err(format!("integer outside {MIN}..={MAX}"))
52 }
53 }
54 pub fn get(self) -> u64 {
55 self.0
56 }
57}
58impl<'de, const MIN: u64, const MAX: u64> Deserialize<'de> for Number<MIN, MAX> {
59 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
60 Self::new(u64::deserialize(d)?).map_err(serde::de::Error::custom)
61 }
62}
63impl<const MIN: u64, const MAX: u64> JsonSchema for Number<MIN, MAX> {
64 fn schema_name() -> Cow<'static, str> {
65 format!("Number_{MIN}_{MAX}").into()
66 }
67 fn json_schema(_: &mut SchemaGenerator) -> Schema {
68 if MAX == u64::MAX {
72 schemars::json_schema!({"type":"integer","minimum":MIN})
73 } else {
74 schemars::json_schema!({"type":"integer","minimum":MIN,"maximum":MAX})
75 }
76 }
77}
78#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
80#[serde(transparent)]
81pub struct Text<const MIN: usize, const MAX: usize>(String);
82impl<const MIN: usize, const MAX: usize> Text<MIN, MAX> {
83 pub fn new(value: impl Into<String>) -> Result<Self, String> {
84 let value = value.into();
85 if (MIN..=MAX).contains(&value.len()) {
86 Ok(Self(value))
87 } else {
88 Err(format!("text outside {MIN}..={MAX} bytes"))
89 }
90 }
91 pub fn as_str(&self) -> &str {
92 &self.0
93 }
94}
95impl<'de, const MIN: usize, const MAX: usize> Deserialize<'de> for Text<MIN, MAX> {
96 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
97 Self::new(String::deserialize(d)?).map_err(serde::de::Error::custom)
98 }
99}
100impl<const MIN: usize, const MAX: usize> JsonSchema for Text<MIN, MAX> {
101 fn schema_name() -> Cow<'static, str> {
102 format!("Text_{MIN}_{MAX}").into()
103 }
104 fn json_schema(_: &mut SchemaGenerator) -> Schema {
105 schemars::json_schema!({"type":"string","minLength":MIN,"maxLength":MAX,"description":"Length is enforced in decoded UTF-8 bytes by the parser."})
106 }
107}
108#[derive(Debug, Clone, PartialEq, Serialize)]
109#[serde(transparent)]
110pub struct List<T, const MIN: usize, const MAX: usize>(Vec<T>);
111impl<T, const MIN: usize, const MAX: usize> List<T, MIN, MAX> {
112 pub fn new(values: Vec<T>) -> Result<Self, String> {
113 if (MIN..=MAX).contains(&values.len()) {
114 Ok(Self(values))
115 } else {
116 Err(format!("list outside {MIN}..={MAX} entries"))
117 }
118 }
119 pub fn as_slice(&self) -> &[T] {
120 &self.0
121 }
122}
123impl<'de, T: Deserialize<'de>, const MIN: usize, const MAX: usize> Deserialize<'de>
124 for List<T, MIN, MAX>
125{
126 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
127 Self::new(Vec::<T>::deserialize(d)?).map_err(serde::de::Error::custom)
128 }
129}
130impl<T: JsonSchema, const MIN: usize, const MAX: usize> JsonSchema for List<T, MIN, MAX> {
131 fn schema_name() -> Cow<'static, str> {
132 format!("List_{}_{}_{}", T::schema_name(), MIN, MAX).into()
133 }
134 fn json_schema(g: &mut SchemaGenerator) -> Schema {
135 let items = g.subschema_for::<T>();
136 schemars::json_schema!({"type":"array","items":items,"minItems":MIN,"maxItems":MAX})
137 }
138}
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
141#[serde(rename_all = "snake_case", deny_unknown_fields)]
142pub enum Maybe<T> {
143 None,
144 Some(T),
145}
146
147#[derive(Debug, Clone)]
150pub struct Embedded<T> {
151 raw: Box<serde_json::value::RawValue>,
152 marker: PhantomData<T>,
153}
154impl<T> PartialEq for Embedded<T> {
155 fn eq(&self, other: &Self) -> bool {
156 self.raw.get() == other.raw.get()
157 }
158}
159impl<T: Document> Embedded<T> {
160 pub fn parse(bytes: &[u8]) -> Result<Self, DocumentError> {
161 T::parse(bytes)?;
162 let raw = serde_json::from_slice(bytes).map_err(|e| invalid(e.to_string()))?;
163 Ok(Self {
164 raw,
165 marker: PhantomData,
166 })
167 }
168 pub fn bytes(&self) -> &[u8] {
169 self.raw.get().as_bytes()
170 }
171 pub fn document(&self) -> Result<T, DocumentError> {
172 T::parse(self.bytes())
173 }
174}
175impl<T> Serialize for Embedded<T> {
176 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
177 self.raw.serialize(s)
178 }
179}
180impl<'de, T: Document> Deserialize<'de> for Embedded<T> {
181 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
182 let raw = Box::<serde_json::value::RawValue>::deserialize(d)?;
183 T::parse(raw.get().as_bytes()).map_err(serde::de::Error::custom)?;
184 Ok(Self {
185 raw,
186 marker: PhantomData,
187 })
188 }
189}
190impl<T: Document> JsonSchema for Embedded<T> {
196 fn schema_name() -> Cow<'static, str> {
197 format!("Embedded_{}", T::SCHEMA.replace(['.', '/', '-'], "_")).into()
198 }
199 fn json_schema(_: &mut SchemaGenerator) -> Schema {
200 schemars::json_schema!({
201 "type": "object",
202 "description": format!(
203 "An embedded `{}` document, carried as its original bytes and checked by that document's own bounded parser.",
204 T::SCHEMA
205 )
206 })
207 }
208}
209fn present<'de, D: Deserializer<'de>, T: Deserialize<'de>>(d: D) -> Result<Option<T>, D::Error> {
210 T::deserialize(d).map(Some)
211}
212pub(crate) fn invalid(message: impl Into<String>) -> DocumentError {
213 DocumentError::Parse(ParseError::Schema {
214 message: message.into(),
215 })
216}
217pub(crate) trait Validate {
218 fn validate(&self) -> Result<(), String> {
219 Ok(())
220 }
221}
222macro_rules! service_document {
224 ($name:ident,$marker:ident,$schema:literal,$limits:expr,{$($fields:tt)*})=>{
225 #[derive(Debug,Clone,Copy,PartialEq,Eq,Serialize,Deserialize,JsonSchema)]
226 pub enum $marker {#[serde(rename=$schema)] V1}
227 #[derive(Debug,Clone,PartialEq,Serialize,Deserialize,JsonSchema)]
228 #[serde(deny_unknown_fields)]
229 #[schemars(transform = crate::serve::document_shape)]
230 pub struct $name {pub schema:$marker,$($fields)*}
231 impl Document for $name {
232 const SCHEMA:&'static str=$schema;const LIMITS:ParseLimits=$limits;
233 fn schema(&self)->&str{Self::SCHEMA}
234 fn parse(bytes:&[u8])->Result<Self,DocumentError>{let value:Self=parse_bounded(bytes,&Self::LIMITS).map_err(DocumentError::Parse)?;value.validate().map_err(invalid)?;Ok(value)}
235 fn canonical(&self)->Result<Vec<u8>,CanonicalError>{canonical_envelope(self)}
236 fn record_canonical(&self)->Result<Vec<u8>,CanonicalError>{canonical_envelope(self)}
237 }
238 };
239}
240pub(crate) use service_document;
241pub(crate) fn document_shape(schema: &mut Schema) {
244 let object = schema.ensure_object();
245 if let Some(properties) = object
247 .get_mut("properties")
248 .and_then(serde_json::Value::as_object_mut)
249 {
250 for name in [
251 "snapshot",
252 "context",
253 "judgment",
254 "run_record",
255 "attempts",
256 "compile_refusal",
257 "profile",
258 "variant",
259 "profile_bases",
260 "profile_id",
261 ] {
262 if let Some(property) = properties
263 .get_mut(name)
264 .and_then(serde_json::Value::as_object_mut)
265 {
266 let branches = property
270 .get("anyOf")
271 .and_then(serde_json::Value::as_array)
272 .map(|b| {
273 b.iter()
274 .filter(|v| v.get("type") != Some(&serde_json::json!("null")))
275 .cloned()
276 .collect::<Vec<_>>()
277 });
278 match branches {
279 Some(mut b) if b.len() == 1 => {
280 property.remove("anyOf");
281 if let Some(serde_json::Value::Object(only)) = b.pop() {
282 for (k, v) in only {
283 property.entry(k).or_insert(v);
284 }
285 }
286 }
287 Some(b) => {
288 property.insert("anyOf".into(), serde_json::Value::Array(b));
289 }
290 None => {
291 if let Some(serde_json::Value::Array(types)) = property.get_mut("type") {
292 types.retain(|t| t != "null");
293 }
294 }
295 }
296 }
297 }
298 }
299 let properties = object
300 .get("properties")
301 .and_then(serde_json::Value::as_object)
302 .cloned()
303 .unwrap_or_default();
304 if properties.contains_key("snapshot") && properties.contains_key("context") {
310 object.insert(
311 "description".into(),
312 serde_json::json!("Exactly one of `snapshot` and `context` is present; the parser refuses both or neither."),
313 );
314 }
315 if properties.contains_key("compile_refusal") {
316 object.insert("if".into(),serde_json::json!({"properties":{"code":{"const":"compile_refused"}},"required":["code"]}));
317 object.insert(
318 "then".into(),
319 serde_json::json!({"required":["compile_refusal"]}),
320 );
321 object.insert(
322 "else".into(),
323 serde_json::json!({"not":{"required":["compile_refusal"]}}),
324 );
325 }
326 if properties.contains_key("run_record") {
327 object.insert("allOf".into(),serde_json::json!([
328 {"if":{"properties":{"status":{"const":"evidence_not_delivered"}}},"then":{"required":["run_record"],"properties":{"evidence":{"required":["not_delivered"]}}},"else":{"not":{"required":["run_record"]},"properties":{"evidence":{"not":{"required":["not_delivered"]}}}}},
329 {"if":{"properties":{"status":{"const":"judged"}}},"then":{"required":["judgment"]}},
330 {"if":{"properties":{"status":{"const":"cancelled"}}},"then":{"not":{"required":["judgment"]}}}
331 ]));
332 }
333}
334
335pub(crate) fn canonical_envelope<T: Serialize>(value: &T) -> Result<Vec<u8>, CanonicalError> {
338 fn render(raw: &serde_json::value::RawValue) -> Result<Vec<u8>, CanonicalError> {
339 if raw.get().starts_with('{') {
340 let fields: BTreeMap<String, Box<serde_json::value::RawValue>> =
341 serde_json::from_str(raw.get()).map_err(|e| CanonicalError::Serialize {
342 message: e.to_string(),
343 })?;
344 let evidence = fields.get("schema").is_some_and(|s| {
345 matches!(
346 s.get(),
347 "\"rustev.serve-decision/1\"" | "\"rustev.serve-refusal/1\""
348 )
349 });
350 let mut out = vec![b'{'];
351 for (index, (key, value)) in fields.iter().enumerate() {
352 if index > 0 {
353 out.push(b',');
354 }
355 out.extend(serde_json::to_vec(key).unwrap());
356 out.push(b':');
357 if evidence && matches!(key.as_str(), "judgment" | "run_record" | "compile_refusal")
358 {
359 out.extend(value.get().as_bytes());
360 } else {
361 out.extend(render(value)?);
362 }
363 }
364 out.push(b'}');
365 Ok(out)
366 } else if raw.get().starts_with('[') {
367 let values: Vec<Box<serde_json::value::RawValue>> = serde_json::from_str(raw.get())
368 .map_err(|e| CanonicalError::Serialize {
369 message: e.to_string(),
370 })?;
371 let mut out = vec![b'['];
372 for (index, value) in values.iter().enumerate() {
373 if index > 0 {
374 out.push(b',');
375 }
376 out.extend(render(value)?);
377 }
378 out.push(b']');
379 Ok(out)
380 } else {
381 let value: serde_json::Value =
382 serde_json::from_str(raw.get()).map_err(|e| CanonicalError::Serialize {
383 message: e.to_string(),
384 })?;
385 crate::canonical::record_canonical_bytes(&value)
386 }
387 }
388 let raw = serde_json::value::to_raw_value(value).map_err(|e| CanonicalError::Serialize {
389 message: e.to_string(),
390 })?;
391 render(&raw)
392}
393
394service_document!(ServeRegister,RegisterSchema,"rustev.serve-register/1",BUNDLE_LIMITS,{
395 pub bundle:Embedded<PlanBundle>,pub signature:Maybe<Embedded<PlanBundleSignature>>,
396});
397impl Validate for ServeRegister {}
398#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
399#[serde(deny_unknown_fields)]
400pub struct Context {
401 pub source: Handle,
402}
403service_document!(ServeDecide,DecideSchema,"rustev.serve-decide/1",SERVICE_LIMITS,{
404 pub decision_id:Handle,pub evaluation_time:Timestamp,
405 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub snapshot:Option<Embedded<crate::snapshot::Snapshot>>,
406 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub context:Option<Context>,
407 });
409impl Validate for ServeDecide {
410 fn validate(&self) -> Result<(), String> {
411 if self.snapshot.is_some() == self.context.is_some() {
412 Err("exactly one of snapshot or context is required".into())
413 } else {
414 Ok(())
415 }
416 }
417}
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
419#[serde(rename_all = "snake_case")]
420pub enum DecisionStatus {
421 Judged,
422 Cancelled,
423 EvidenceNotDelivered,
424}
425#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
426#[serde(rename_all = "snake_case", deny_unknown_fields)]
427pub enum EvidenceDisposition {
428 Receipt {
429 receipt: Text<1, 4096>,
430 },
431 Ticket {
432 ticket: Handle,
433 },
434 Dropped {
435 count: Number<1, { u64::MAX }>,
436 },
437 NotDelivered {
438 error: Text<0, 256>,
439 may_have_received: bool,
440 },
441}
442service_document!(ServeDecision,DecisionSchema,"rustev.serve-decision/1",DECISION_LIMITS,{
443 pub status:DecisionStatus,pub decision_id:Handle,pub plan_id:PlanId,pub registration_id:RegistrationId,
444 pub signer:Signer,pub execution_policy_id:Maybe<ExecutionPolicyId>,pub service_build:Handle,
445 pub protocol:Protocol,pub configuration_digest:ContentDigest,
446 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub judgment:Option<Embedded<crate::judgment::Judgment>>,
447 pub run_record_digest:ContentDigest,pub evidence:EvidenceDisposition,pub replayed:bool,
448 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub run_record:Option<Embedded<crate::run::RunRecord>>,
449});
450impl Validate for ServeDecision {
451 fn validate(&self) -> Result<(), String> {
452 if (self.status == DecisionStatus::EvidenceNotDelivered) != self.run_record.is_some() {
453 return Err("run_record is required only for evidence_not_delivered".into());
454 }
455 if (self.status == DecisionStatus::Judged && self.judgment.is_none())
461 || (self.status == DecisionStatus::Cancelled && self.judgment.is_some())
462 {
463 return Err("judgment does not match status".into());
464 }
465 if (self.status == DecisionStatus::EvidenceNotDelivered)
466 != matches!(self.evidence, EvidenceDisposition::NotDelivered { .. })
467 {
468 return Err("evidence disposition does not match status".into());
469 }
470 Ok(())
471 }
472}
473#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
474pub enum Protocol {
475 #[serde(rename = "rustev.serve/1")]
476 V1,
477}
478#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
479#[serde(rename_all = "snake_case")]
480pub enum DecisionState {
481 Queued,
482 Running,
483}
484service_document!(ServeDecisionState,DecisionStateSchema,"rustev.serve-decision-state/1",SERVICE_LIMITS,{
485 pub decision_id:Handle,pub plan_id:PlanId,pub state:DecisionState,
486});
487impl Validate for ServeDecisionState {}
488#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
489#[serde(rename_all = "snake_case")]
490pub enum CancelStatus {
491 Queued,
492 Requested,
493 AlreadyFinished,
494}
495#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
496#[serde(rename_all = "snake_case")]
497pub enum CancelAck {
498 NotRequested,
499 Stopped,
500 Unconfirmed,
501 NoAnswer,
502}
503impl From<crate::run::Cancellation> for CancelAck {
504 fn from(value: crate::run::Cancellation) -> Self {
505 use crate::run::{CancelAnswer, Cancellation};
506 match value {
507 Cancellation::NotRequested => Self::NotRequested,
508 Cancellation::Requested {
509 answer: CancelAnswer::Stopped,
510 } => Self::Stopped,
511 Cancellation::Requested {
512 answer: CancelAnswer::Unconfirmed,
513 } => Self::Unconfirmed,
514 Cancellation::Requested {
515 answer: CancelAnswer::NotObserved,
516 } => Self::NoAnswer,
517 }
518 }
519}
520#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
521#[serde(deny_unknown_fields)]
522pub struct AttemptCancellation {
523 pub attempt_id: Text<1, 4096>,
524 pub cancel_ack: CancelAck,
525 pub remote_end: crate::run::RemoteState,
526}
527service_document!(ServeCancel,CancelSchema,"rustev.serve-cancel/1",SERVICE_LIMITS,{
528 pub status:CancelStatus,
529 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")]pub attempts:Option<List<AttemptCancellation,0,10000>>,
530});
531impl Validate for ServeCancel {}
532pub type CancelWaitMs = Number<0, 60000>;
534pub type DeadlineMs = Number<1, { u64::MAX }>;
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
537#[serde(rename_all = "snake_case")]
538pub enum RefusalCode {
539 Overloaded,
540 IdempotencyWindowFull,
541 QueueDeadline,
542 ShuttingDown,
543 NotReady,
544 ContextUnavailable,
545 CancelledBeforeAdmission,
546 DecisionIdConflict,
547 RegistrationConflict,
548 DecisionInProgress,
549 RegistryFull,
550 InvalidRequest,
551 InvalidDocument,
552 PrincipalInvalid,
553 InvalidTraceContext,
554 InvalidDeadline,
555 Unauthenticated,
556 PlanNotRegistered,
557 DecisionNotFound,
558 EndpointUnavailable,
559 BodyTooLarge,
560 HeadersTooLarge,
561 UnsupportedMediaType,
562 CompileRefused,
563 PlanIdMismatch,
564 BindingUnknown,
565 ProfileUnsupported,
566 BundleTampered,
567 SignatureInvalid,
568 SignatureUntrusted,
569 UnsignedRefused,
570 InternalError,
571}
572#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
573#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
574pub enum GrpcCode {
575 ResourceExhausted,
576 Unavailable,
577 Aborted,
578 AlreadyExists,
579 FailedPrecondition,
580 InvalidArgument,
581 Unauthenticated,
582 NotFound,
583 Internal,
584}
585impl RefusalCode {
586 pub fn http_status(self) -> u16 {
587 use RefusalCode::*;
588 match self {
589 Overloaded | IdempotencyWindowFull => 429,
590 QueueDeadline | ShuttingDown | NotReady => 503,
591 ContextUnavailable => 502,
592 CancelledBeforeAdmission
593 | DecisionIdConflict
594 | RegistrationConflict
595 | DecisionInProgress
596 | RegistryFull => 409,
597 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
598 | InvalidDeadline => 400,
599 Unauthenticated => 401,
600 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => 404,
601 BodyTooLarge => 413,
602 HeadersTooLarge => 431,
603 UnsupportedMediaType => 415,
604 InternalError => 500,
605 _ => 422,
606 }
607 }
608 pub fn grpc_code(self) -> GrpcCode {
609 use RefusalCode::*;
610 match self {
611 Overloaded | IdempotencyWindowFull | BodyTooLarge | HeadersTooLarge => {
612 GrpcCode::ResourceExhausted
613 }
614 QueueDeadline | ShuttingDown | NotReady | ContextUnavailable => GrpcCode::Unavailable,
615 CancelledBeforeAdmission | DecisionInProgress => GrpcCode::Aborted,
616 DecisionIdConflict | RegistrationConflict => GrpcCode::AlreadyExists,
617 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
618 | InvalidDeadline | UnsupportedMediaType => GrpcCode::InvalidArgument,
619 Unauthenticated => GrpcCode::Unauthenticated,
620 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => GrpcCode::NotFound,
621 InternalError => GrpcCode::Internal,
622 _ => GrpcCode::FailedPrecondition,
623 }
624 }
625}
626#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
628#[serde(transparent)]
629pub struct DecisionDidNotRun(#[schemars(extend("const" = false))] bool);
630impl<'de> Deserialize<'de> for DecisionDidNotRun {
631 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
632 if bool::deserialize(d)? {
633 Err(serde::de::Error::custom("decision_ran must be false"))
634 } else {
635 Ok(Self(false))
636 }
637 }
638}
639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
641#[serde(rename_all = "snake_case", deny_unknown_fields)]
642pub enum CompileRefusal {
643 Document(CompilerRefusal),
644 Digest {
645 digest: ContentDigest,
646 length: Number<2049, { u64::MAX }>,
647 },
648}
649#[derive(Debug, Clone)]
653pub struct CompilerRefusal(Box<serde_json::value::RawValue>);
654impl PartialEq for CompilerRefusal {
655 fn eq(&self, other: &Self) -> bool {
656 self.0.get() == other.0.get()
657 }
658}
659impl Serialize for CompilerRefusal {
660 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
661 self.0.serialize(s)
662 }
663}
664impl<'de> Deserialize<'de> for CompilerRefusal {
665 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
666 let raw = Box::<serde_json::value::RawValue>::deserialize(d)?;
667 let _: CompilerRefusalShape = parse_bounded(
668 raw.get().as_bytes(),
669 &ParseLimits {
670 max_bytes: 2048,
671 ..REFUSAL_LIMITS
672 },
673 )
674 .map_err(serde::de::Error::custom)?;
675 Ok(Self(raw))
676 }
677}
678#[derive(Debug, Serialize, Deserialize, JsonSchema)]
683#[serde(untagged)]
684enum CompilerRefusalShape {
685 Compile(CompilerRefusalFields),
686 Profiled(ProfiledCompileFields),
687 Instantiate(InstantiationFields),
688}
689#[derive(Debug, Serialize, Deserialize, JsonSchema)]
690#[serde(deny_unknown_fields)]
691struct ProfiledCompileFields {
692 command: Text<1, 2048>,
693 status: Refused,
694 #[serde(deserialize_with = "compiler_category")]
695 #[schemars(schema_with = "compiler_category_schema")]
696 category: u64,
697 category_name: Text<1, 2048>,
698 subject: Text<0, 2048>,
699 detail: Text<0, 2048>,
700 shortfalls: List<CompilerShortfall, 0, 1024>,
701 stage: CompileStage,
702 parameters: List<Text<1, 2048>, 0, 1024>,
703}
704#[derive(Debug, Serialize, Deserialize, JsonSchema)]
705enum CompileStage {
706 #[serde(rename = "compile")]
707 Compile,
708}
709#[derive(Debug, Serialize, Deserialize, JsonSchema)]
710#[serde(deny_unknown_fields)]
711struct InstantiationFields {
712 command: Text<1, 2048>,
713 status: Refused,
714 stage: InstantiateStage,
715 code: Text<1, 64>,
717 subject: Text<0, 2048>,
718 detail: Text<0, 2048>,
719}
720#[derive(Debug, Serialize, Deserialize, JsonSchema)]
721enum InstantiateStage {
722 #[serde(rename = "instantiate")]
723 Instantiate,
724}
725#[derive(Debug, Serialize, Deserialize, JsonSchema)]
726#[serde(deny_unknown_fields)]
727struct CompilerRefusalFields {
728 command: Text<1, 2048>,
729 status: Refused,
730 #[serde(deserialize_with = "compiler_category")]
736 #[schemars(schema_with = "compiler_category_schema")]
737 category: u64,
738 category_name: Text<1, 2048>,
739 subject: Text<0, 2048>,
740 detail: Text<0, 2048>,
741 shortfalls: List<CompilerShortfall, 0, 1024>,
742}
743fn compiler_category<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
744 let n = u64::deserialize(d)?;
745 if (1..=24).contains(&n) || n == 28 {
746 Ok(n)
747 } else {
748 Err(serde::de::Error::custom("unknown compiler category"))
749 }
750}
751fn compiler_category_schema(_: &mut SchemaGenerator) -> Schema {
752 schemars::json_schema!({"anyOf": [{"type": "integer", "minimum": 1, "maximum": 24}, {"const": 28}]})
753}
754#[derive(Debug, Serialize, Deserialize, JsonSchema)]
755enum Refused {
756 #[serde(rename = "refused")]
757 Refused,
758}
759#[derive(Debug, Serialize, Deserialize, JsonSchema)]
760#[serde(deny_unknown_fields)]
761struct CompilerShortfall {
762 backend_id: Handle,
763 reasons: List<CompilerReason, 0, 1024>,
764}
765#[derive(Debug, Serialize, Deserialize, JsonSchema)]
766#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
767enum CompilerReason {
768 UnknownBackend,
769 Operation {
770 operation: crate::definition::Operation,
771 },
772 OutputKind {
773 offered: crate::descriptor::OutputKind,
774 required: crate::definition::RequiredKind,
775 },
776 Options {
777 max: u64,
778 needed: u64,
779 },
780 InputLimit {
781 max: u64,
782 needed: u64,
783 },
784 Determinism {
785 offered: Text<1, 2048>,
786 required: Text<1, 2048>,
787 },
788 ArtifactPin {
789 pinned: ContentDigest,
790 offered: ContentDigest,
791 },
792 ScreenProfile,
794 ScreenTextLimit {
795 max: u64,
796 needed: u64,
797 },
798 ScreenInputExcess,
799}
800impl JsonSchema for CompilerRefusal {
801 fn schema_name() -> Cow<'static, str> {
802 "CompilerRefusal".into()
803 }
804 fn json_schema(g: &mut SchemaGenerator) -> Schema {
805 g.subschema_for::<CompilerRefusalShape>()
806 }
807}
808service_document!(ServeRefusal,RefusalSchema,"rustev.serve-refusal/1",REFUSAL_LIMITS,{
809 pub code:RefusalCode,pub detail:Text<0,256>,pub decision_ran:DecisionDidNotRun,
810 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")]pub compile_refusal:Option<CompileRefusal>,
811 });
813impl Validate for ServeRefusal {
814 fn validate(&self) -> Result<(), String> {
815 if (self.code == RefusalCode::CompileRefused) != self.compile_refusal.is_some() {
816 Err("compile_refusal is required only for compile_refused".into())
817 } else {
818 Ok(())
819 }
820 }
821}
822
823#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
824#[serde(rename_all = "snake_case", deny_unknown_fields)]
825pub enum StepOutcome {
826 Supplied,
827 Unresolved {
828 reason: crate::judgment::Unresolved,
829 },
830 Skipped,
832}
833#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
836#[serde(deny_unknown_fields)]
837pub struct GeneratedField {
838 pub step: Handle,
839 pub item: Maybe<Handle>,
840 pub field: GeneratedFieldName,
841 pub value: GeneratedValue,
842 pub attempt_id: Text<1, 4096>,
843 pub provisional: Provisional,
844}
845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
846#[serde(rename_all = "snake_case", deny_unknown_fields)]
847pub enum GeneratedValue {
848 Text(Text<0, 16384>),
849 List(List<Text<1, 64>, 0, 64>),
850}
851#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
852#[serde(transparent)]
853pub struct GeneratedFieldName(#[schemars(regex(pattern = "^[a-z][a-z0-9_]{0,63}$"))] Text<1, 64>);
854impl<'de> Deserialize<'de> for GeneratedFieldName {
855 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
856 let value = Text::<1, 64>::deserialize(d)?;
857 let mut bytes = value.as_str().bytes();
858 if !bytes.next().is_some_and(|b| b.is_ascii_lowercase())
859 || !bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
860 {
861 return Err(serde::de::Error::custom("invalid generated field name"));
862 }
863 Ok(Self(value))
864 }
865}
866#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
867#[serde(transparent)]
868pub struct Provisional(#[schemars(extend("const" = true))] bool);
869impl<'de> Deserialize<'de> for Provisional {
870 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
871 if bool::deserialize(d)? {
872 Ok(Self(true))
873 } else {
874 Err(serde::de::Error::custom("field must be provisional"))
875 }
876 }
877}
878#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
879#[serde(rename_all = "snake_case", deny_unknown_fields)]
880pub enum FinalResult {
881 Decision(Embedded<ServeDecision>),
882 Refusal(Embedded<ServeRefusal>),
883}
884#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
885#[serde(rename_all = "snake_case", deny_unknown_fields)]
886pub enum EventKind {
887 Admitted,
888 Step {
889 step_id: Handle,
890 instance_ids: List<Handle, 0, 4096>,
891 outcome: StepOutcome,
892 derivation: crate::judgment::Derivation,
893 },
894 Field(GeneratedField),
895 Final {
896 result: FinalResult,
897 events_dropped: u64,
898 },
899}
900service_document!(ServeEvent,EventSchema,"rustev.serve-event/1",ParseLimits{max_bytes:10*MIB,..SERVICE_LIMITS},{pub sequence:u64,pub kind:EventKind,});
901impl Validate for ServeEvent {}
902
903#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
906#[serde(transparent)]
907pub struct Reference(Text<1, 4096>);
908impl Reference {
909 pub fn as_str(&self) -> &str {
910 self.0.as_str()
911 }
912}
913impl<'de> Deserialize<'de> for Reference {
914 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
915 let value = Text::<1, 4096>::deserialize(d)?;
916 let valid = value
917 .as_str()
918 .split_once(':')
919 .is_some_and(|(prefix, path)| {
920 !prefix.is_empty()
921 && prefix
922 .bytes()
923 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
924 && !path.contains([':', '\\', '\0'])
925 && path
926 .split('/')
927 .all(|s| !s.is_empty() && s != "." && s != "..")
928 });
929 if !valid {
930 return Err(serde::de::Error::custom(
931 "reference must be prefix:relative/path without empty, dot or parent components",
932 ));
933 }
934 Ok(Self(value))
935 }
936}
937#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
938#[serde(rename_all = "snake_case", deny_unknown_fields)]
939#[serde(bound(deserialize = "T: Document", serialize = ""))]
940pub enum DocumentInput<T: Document> {
941 Document(Embedded<T>),
942 Reference(Reference),
943}
944#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
945#[serde(deny_unknown_fields)]
946pub struct ReplayOptions {
947 pub scope: crate::scope::Scope,
948 pub now_ms: Timestamp,
949 pub host_cap_ms: Maybe<Number<1, 2592000000>>,
950}
951service_document!(ServeReplay,ReplaySchema,"rustev.serve-replay/1",SERVICE_LIMITS,{
952 pub bundle:DocumentInput<crate::replay::ReplayBundle>,pub inputs:List<ReferencedInput,0,1024>,pub config:ReplayOptions,
953});
954impl Validate for ServeReplay {}
955#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
957#[serde(deny_unknown_fields)]
958pub struct ReferencedInput {
959 pub reference: Reference,
960 pub input: ReplayInput,
961}
962#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
963#[serde(rename_all = "snake_case", deny_unknown_fields)]
964pub enum ReplayInput {
965 Plan(DocumentInput<crate::plan::Plan>),
966 Snapshot(DocumentInput<crate::snapshot::Snapshot>),
967 Run(DocumentInput<crate::run::RunRecord>),
968 Evidence(DocumentInput<crate::evidence::EvidenceRecord>),
969 Judgment(DocumentInput<crate::judgment::Judgment>),
970 BackendOutput(DocumentInput<crate::output::BackendOutputDoc>),
971 ExecutionPolicy(DocumentInput<crate::execution::ExecutionPolicy>),
972 Calibration(DocumentInput<crate::calibration::CalibrationArtifact>),
973}
974#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
975#[serde(deny_unknown_fields)]
976pub struct EvaluationDependencies {
977 pub descriptors: List<Reference, 0, 1024>,
978 pub rules: List<Reference, 0, 1024>,
979 pub calibrations: List<Reference, 0, 1024>,
980}
981#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
982#[serde(deny_unknown_fields)]
983pub struct RepeatInputs {
984 pub source: RepeatSource,
985 pub config: Reference,
986 pub manifest: Reference,
987 pub runs: List<RepeatRun, 1, 64>,
988}
989#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
990#[serde(rename_all = "snake_case")]
991pub enum RepeatSource {
992 Replay,
993 Captured,
994 Live,
995}
996#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
997#[serde(deny_unknown_fields)]
998pub struct RepeatRun {
999 pub evaluated: Reference,
1000 pub cases: Reference,
1001}
1002service_document!(ServeEvaluate,EvaluateSchema,"rustev.serve-evaluate/1",SERVICE_LIMITS,{
1003 pub split:crate::eval_report::Split,pub replay:ReplayOptions,pub dataset:Reference,pub config:Reference,pub adapter:Reference,pub bundles:Reference,pub candidate_plan:Maybe<Reference>,pub deps:EvaluationDependencies,pub repeat:Maybe<RepeatInputs>,
1004});
1005impl Validate for ServeEvaluate {}
1006
1007#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
1015#[serde(untagged)]
1016pub enum ExplainEvidence {
1017 Record(RecordEvidence),
1018 Bundle(BundleEvidence),
1019}
1020#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1021#[serde(deny_unknown_fields)]
1022pub struct RecordEvidence {
1023 pub record: DocumentInput<crate::run::RunRecord>,
1024 #[serde(
1025 default,
1026 skip_serializing_if = "Option::is_none",
1027 deserialize_with = "present"
1028 )]
1029 pub plan: Option<DocumentInput<crate::plan::Plan>>,
1030}
1031#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1032#[serde(deny_unknown_fields)]
1033pub struct BundleEvidence {
1034 pub bundle: DocumentInput<crate::replay::ReplayBundle>,
1035}
1036impl ExplainEvidence {
1037 pub fn is_bundle(&self) -> bool {
1038 matches!(self, Self::Bundle(_))
1039 }
1040}
1041#[derive(Deserialize)]
1042#[serde(deny_unknown_fields)]
1043struct EvidenceMembers {
1044 #[serde(default, deserialize_with = "present")]
1045 record: Option<DocumentInput<crate::run::RunRecord>>,
1046 #[serde(default, deserialize_with = "present")]
1047 plan: Option<DocumentInput<crate::plan::Plan>>,
1048 #[serde(default, deserialize_with = "present")]
1049 bundle: Option<DocumentInput<crate::replay::ReplayBundle>>,
1050}
1051impl<'de> Deserialize<'de> for ExplainEvidence {
1052 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1053 let m = EvidenceMembers::deserialize(d)?;
1054 match (m.record, m.plan, m.bundle) {
1055 (Some(record), plan, None) => Ok(Self::Record(RecordEvidence { record, plan })),
1056 (None, None, Some(bundle)) => Ok(Self::Bundle(BundleEvidence { bundle })),
1057 _ => Err(serde::de::Error::custom(
1058 "evidence is { record, plan? } or { bundle }",
1059 )),
1060 }
1061 }
1062}
1063#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
1068#[serde(untagged)]
1069pub enum ExplainQuery {
1070 Diff(DiffQuery),
1071 Query(crate::explanation::Query),
1072}
1073impl<'de> Deserialize<'de> for ExplainQuery {
1074 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1075 use serde::de::{Error, IntoDeserializer, MapAccess, Visitor};
1076 struct Shape;
1077 impl<'de> Visitor<'de> for Shape {
1078 type Value = ExplainQuery;
1079 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1080 f.write_str("a query or { diff: { other } }")
1081 }
1082 fn visit_str<E: Error>(self, v: &str) -> Result<ExplainQuery, E> {
1083 crate::explanation::Query::deserialize(v.into_deserializer())
1084 .map(ExplainQuery::Query)
1085 }
1086 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<ExplainQuery, A::Error> {
1087 let Some(key) = map.next_key::<String>()? else {
1088 return Err(A::Error::custom("a query names one member"));
1089 };
1090 let query = if key == "diff" {
1091 ExplainQuery::Diff(DiffQuery {
1092 diff: map.next_value()?,
1093 })
1094 } else {
1095 let value: serde_json::Value = map.next_value()?;
1096 let query = serde_json::Value::Object([(key, value)].into_iter().collect());
1097 ExplainQuery::Query(
1098 crate::explanation::Query::deserialize(query).map_err(A::Error::custom)?,
1099 )
1100 };
1101 if map.next_key::<serde::de::IgnoredAny>()?.is_some() {
1102 return Err(A::Error::custom("a query names one member"));
1103 }
1104 Ok(query)
1105 }
1106 }
1107 d.deserialize_any(Shape)
1108 }
1109}
1110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1111#[serde(deny_unknown_fields)]
1112pub struct DiffQuery {
1113 pub diff: DiffOther,
1114}
1115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1116#[serde(deny_unknown_fields)]
1117pub struct DiffOther {
1118 pub other: ExplainEvidence,
1119}
1120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1124#[serde(deny_unknown_fields)]
1125pub struct ExplainOptions {
1126 #[serde(
1127 default,
1128 skip_serializing_if = "Option::is_none",
1129 deserialize_with = "present"
1130 )]
1131 pub scope: Option<crate::scope::Scope>,
1132 #[serde(
1133 default,
1134 skip_serializing_if = "Option::is_none",
1135 deserialize_with = "present"
1136 )]
1137 pub now_ms: Option<Timestamp>,
1138}
1139service_document!(ServeExplain,ExplainSchema,"rustev.serve-explain/1",SERVICE_LIMITS,{
1140 pub evidence:ExplainEvidence,pub query:ExplainQuery,pub options:ExplainOptions,
1141});
1142impl Validate for ServeExplain {}
1143impl ServeExplain {
1144 pub fn has_bundle(&self) -> bool {
1146 self.evidence.is_bundle()
1147 || matches!(&self.query, ExplainQuery::Diff(d) if d.diff.other.is_bundle())
1148 }
1149}
1150pub const EXPLANATION_LIMITS: ParseLimits = ParseLimits {
1154 max_bytes: 33 * MIB,
1155 max_string_bytes: 16 * MIB,
1156 ..SERVICE_LIMITS
1157};
1158service_document!(ServeExplanation,ExplanationSchema,"rustev.serve-explanation/1",EXPLANATION_LIMITS,{
1159 pub explanation:Embedded<crate::explanation::Explanation>,pub markdown:Text<0,16777216>,
1160});
1161impl Validate for ServeExplanation {}
1162
1163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1164#[serde(rename_all = "snake_case")]
1165pub enum RouteGroup {
1166 Decide,
1167 Admin,
1168 Ops,
1169}
1170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1171pub enum HttpMethod {
1172 GET,
1173 PUT,
1174 POST,
1175}
1176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1177#[serde(rename_all = "snake_case")]
1178pub enum Operation {
1179 Describe,
1180 Register,
1181 Registration,
1182 Decide,
1183 Stream,
1184 Lookup,
1185 Cancel,
1186 Replay,
1187 Evaluate,
1188 Explain,
1189 Invalidations,
1190 Health,
1191 Readiness,
1192 Metrics,
1193}
1194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1195#[serde(deny_unknown_fields)]
1196pub struct OperationDescription {
1197 pub operation: Operation,
1198 pub method: HttpMethod,
1199 pub route: Text<1, 256>,
1200 pub group: RouteGroup,
1201 pub request_schema: Maybe<Text<1, 128>>,
1202 pub response_schemas: List<Text<1, 128>, 0, 4>,
1203 pub streaming: bool,
1204 pub response_format: ResponseFormat,
1205}
1206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1207#[serde(rename_all = "snake_case")]
1208pub enum ResponseFormat {
1209 Document,
1210 SurfaceOutput,
1211 Explanation,
1212 Empty,
1213 Openmetrics,
1214}
1215pub fn operation_table() -> Vec<OperationDescription> {
1217 use HttpMethod::*;
1218 use Operation::{
1219 Cancel, Describe, Evaluate, Explain, Health, Invalidations, Lookup, Metrics, Readiness,
1220 Register, Registration, Replay, Stream,
1221 };
1222 use ResponseFormat::*;
1223 use RouteGroup::*;
1224 let rows = [
1225 (
1226 Describe,
1227 GET,
1228 "/v1/service",
1229 Decide,
1230 None,
1231 &["rustev.serve-service/1"][..],
1232 false,
1233 Document,
1234 ),
1235 (
1236 Register,
1237 PUT,
1238 "/v1/plans/{planId}",
1239 Admin,
1240 Some("rustev.serve-register/1"),
1241 &["rustev.plan-registration/1"],
1242 false,
1243 Document,
1244 ),
1245 (
1246 Registration,
1247 GET,
1248 "/v1/plans/{planId}",
1249 Decide,
1250 None,
1251 &["rustev.plan-registration/1"],
1252 false,
1253 Document,
1254 ),
1255 (
1256 Operation::Decide,
1257 POST,
1258 "/v1/plans/{planId}/decisions",
1259 Decide,
1260 Some("rustev.serve-decide/1"),
1261 &["rustev.serve-decision/1"],
1262 false,
1263 Document,
1264 ),
1265 (
1266 Stream,
1267 POST,
1268 "/v1/plans/{planId}/decisions?stream=1",
1269 Decide,
1270 Some("rustev.serve-decide/1"),
1271 &["rustev.serve-event/1"],
1272 true,
1273 Document,
1274 ),
1275 (
1276 Lookup,
1277 GET,
1278 "/v1/decisions/{decisionId}",
1279 Decide,
1280 None,
1281 &["rustev.serve-decision/1", "rustev.serve-decision-state/1"],
1282 false,
1283 Document,
1284 ),
1285 (
1286 Cancel,
1287 POST,
1288 "/v1/decisions/{decisionId}/cancel",
1289 Decide,
1290 None,
1291 &["rustev.serve-cancel/1"],
1292 false,
1293 Document,
1294 ),
1295 (
1296 Replay,
1297 POST,
1298 "/v1/replay",
1299 Decide,
1300 Some("rustev.serve-replay/1"),
1301 &[],
1302 false,
1303 SurfaceOutput,
1304 ),
1305 (
1306 Evaluate,
1307 POST,
1308 "/v1/evaluate",
1309 Decide,
1310 Some("rustev.serve-evaluate/1"),
1311 &[],
1312 false,
1313 SurfaceOutput,
1314 ),
1315 (
1316 Explain,
1317 POST,
1318 "/v1/explain",
1319 Decide,
1320 Some("rustev.serve-explain/1"),
1321 &["rustev.serve-explanation/1"],
1322 false,
1323 Document,
1324 ),
1325 (
1326 Invalidations,
1327 POST,
1328 "/v1/context/invalidations",
1329 Admin,
1330 Some("rustev.serve-invalidations/1"),
1331 &["rustev.serve-invalidations-ack/1"],
1332 false,
1333 Document,
1334 ),
1335 (Health, GET, "/healthz", Ops, None, &[], false, Empty),
1336 (
1337 Readiness,
1338 GET,
1339 "/readyz",
1340 Ops,
1341 None,
1342 &["rustev.serve-readiness/1"],
1343 false,
1344 Document,
1345 ),
1346 (Metrics, GET, "/metrics", Ops, None, &[], false, Openmetrics),
1347 ];
1348 rows.into_iter()
1349 .map(
1350 |(operation, method, route, group, request, response, streaming, response_format)| {
1351 OperationDescription {
1352 operation,
1353 method,
1354 route: Text::new(route).unwrap(),
1355 group,
1356 request_schema: request
1357 .map_or(Maybe::None, |s| Maybe::Some(Text::new(s).unwrap())),
1358 response_schemas: List::new(
1359 response.iter().map(|s| Text::new(*s).unwrap()).collect(),
1360 )
1361 .unwrap(),
1362 streaming,
1363 response_format,
1364 }
1365 },
1366 )
1367 .collect()
1368}
1369service_document!(Serve,ServeSchema,"rustev.serve/1",SERVICE_LIMITS,{pub operations:List<OperationDescription,14,14>,});
1370impl Validate for Serve {
1371 fn validate(&self) -> Result<(), String> {
1372 if self.operations.as_slice() != operation_table() {
1373 Err("operation table differs from rustev.serve/1".into())
1374 } else {
1375 Ok(())
1376 }
1377 }
1378}
1379#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1380#[serde(rename_all = "snake_case")]
1381pub enum Retention {
1382 MemoryLostOnRestart,
1383}
1384service_document!(ServeService,ServiceSchema,"rustev.serve-service/1",SERVICE_LIMITS,{
1385 pub service_build:Handle,pub configuration_digest:ContentDigest,pub protocol:Protocol,pub available_endpoints:List<Operation,0,14>,pub signing_mode:SigningMode,pub development_bearer_listeners:List<Handle,0,64>,pub idempotency_window_ms:Number<60000,86400000>,pub retention:Retention,
1386});
1387impl Validate for ServeService {}
1388#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1389#[serde(deny_unknown_fields)]
1390pub struct ReadinessFailure {
1391 pub item: Handle,
1392 pub reason: ReadinessReason,
1393}
1394#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1395#[serde(rename_all = "snake_case")]
1396pub enum ReadinessReason {
1397 PreloadUnregistered,
1398 DescribeIncomplete,
1399 Degraded,
1400 InvalidationDegraded,
1401 ShuttingDown,
1402}
1403service_document!(ServeReadiness,ReadinessSchema,"rustev.serve-readiness/1",SERVICE_LIMITS,{
1404 pub failing_items:List<ReadinessFailure,0,4096>,pub signing_mode:SigningMode,pub development_bearer_listeners:List<Handle,0,64>,
1405});
1406impl Validate for ServeReadiness {}
1407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1408#[serde(deny_unknown_fields)]
1409pub struct DecisionIdList {
1410 pub ids: List<Handle, 0, 10000>,
1411 pub total: u64,
1412 pub truncated: bool,
1413}
1414impl DecisionIdList {
1415 fn validate(&self) -> Result<(), String> {
1416 let length = self.ids.as_slice().len() as u64;
1417 if self.total < length || self.truncated != (self.total > length) {
1418 Err("id list total/truncated contradict its length".into())
1419 } else {
1420 Ok(())
1421 }
1422 }
1423}
1424service_document!(ServeShutdown,ShutdownSchema,"rustev.serve-shutdown/1",ParseLimits{max_bytes:8*MIB,..SERVICE_LIMITS},{
1425 pub drained:u64,pub cancelled:u64,pub delivered:u64,pub failed:u64,pub dropped:u64,pub unflushed:u64,pub failed_ids:DecisionIdList,pub unflushed_ids:DecisionIdList,
1426});
1427impl Validate for ServeShutdown {
1428 fn validate(&self) -> Result<(), String> {
1429 self.failed_ids.validate()?;
1430 self.unflushed_ids.validate()?;
1431 if self.failed != self.failed_ids.total || self.unflushed != self.unflushed_ids.total {
1432 Err("shutdown counts must equal id-list totals".into())
1433 } else {
1434 Ok(())
1435 }
1436 }
1437}
1438#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1439#[serde(rename_all = "snake_case")]
1440pub enum InvalidationKind {
1441 Correction,
1442 Revocation,
1443 Erasure,
1444}
1445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1446#[serde(rename_all = "snake_case", deny_unknown_fields)]
1447pub enum InvalidationScope {
1448 Principal(List<u8, 1, 1024>),
1449 AllScopes,
1450}
1451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1454#[serde(deny_unknown_fields)]
1455pub struct InvalidationItem {
1456 pub schema: InvalidationItemSchema,
1457 pub source_id: Handle,
1458 pub sequence: u64,
1459 pub kind: InvalidationKind,
1460 pub fields: List<Handle, 1, 4096>,
1461 pub scope: InvalidationScope,
1462}
1463#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1464pub enum InvalidationItemSchema {
1465 #[serde(rename = "rustev.remote-context-invalidation/1")]
1466 V1,
1467}
1468service_document!(ServeInvalidations,InvalidationsSchema,"rustev.serve-invalidations/1",SERVICE_LIMITS,{pub source_id:Handle,pub items:List<InvalidationItem,0,1024>,});
1469impl Validate for ServeInvalidations {
1470 fn validate(&self) -> Result<(), String> {
1471 if self
1472 .items
1473 .as_slice()
1474 .iter()
1475 .any(|i| i.source_id != self.source_id)
1476 || self
1477 .items
1478 .as_slice()
1479 .windows(2)
1480 .any(|p| p[0].sequence >= p[1].sequence)
1481 {
1482 Err("invalidations must name the batch source and increase in sequence".into())
1483 } else {
1484 Ok(())
1485 }
1486 }
1487}
1488service_document!(ServeInvalidationsAck,InvalidationsAckSchema,"rustev.serve-invalidations-ack/1",SERVICE_LIMITS,{pub highest_sequence:u64,});
1489impl Validate for ServeInvalidationsAck {}
1490
1491#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1492#[serde(deny_unknown_fields)]
1493pub struct ListenerLimits {
1494 pub max_header_bytes: Number<4096, 65536>,
1495 pub registration_body_bytes: Number<1, 16777216>,
1496 pub other_body_bytes: Number<1, 4194304>,
1497 pub max_connections: Number<1, 65536>,
1498 pub max_http2_streams: Number<1, 1024>,
1499 pub requests_per_second: Number<1, 10000>,
1500}
1501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1502#[serde(deny_unknown_fields)]
1503pub struct Tls {
1504 pub certificate_file: Text<1, 4096>,
1505 pub private_key_file: Text<1, 4096>,
1506 pub client_ca_files: List<Text<1, 4096>, 1, 64>,
1507 pub subject_allowlist: List<Text<1, 4096>, 0, 1024>,
1508}
1509#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
1510#[serde(transparent)]
1511pub struct SocketMode(#[schemars(extend("enum" = [384,432]))] u16);
1512impl<'de> Deserialize<'de> for SocketMode {
1513 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1514 match u16::deserialize(d)? {
1515 value @ (0o600 | 0o660) => Ok(Self(value)),
1516 _ => Err(serde::de::Error::custom("socket mode must be 0600 or 0660")),
1517 }
1518 }
1519}
1520#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1521#[serde(rename_all = "snake_case", deny_unknown_fields)]
1522pub enum UnixAuth {
1523 Filesystem,
1524 DevelopmentBearer { token_file: Text<1, 4096> },
1525}
1526#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1527#[serde(rename_all = "snake_case", deny_unknown_fields)]
1528pub enum ListenerTransport {
1529 Tcp {
1530 address: Text<1, 4096>,
1531 tls: Tls,
1532 },
1533 Unix {
1534 path: Text<1, 4096>,
1535 mode: SocketMode,
1536 auth: UnixAuth,
1537 },
1538}
1539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1540#[serde(deny_unknown_fields)]
1541pub struct Listener {
1542 pub name: Handle,
1543 pub groups: List<RouteGroup, 1, 3>,
1544 pub transport: ListenerTransport,
1545 pub limits: ListenerLimits,
1546 pub redaction_policy: Maybe<Embedded<crate::redaction::RedactionPolicy>>,
1547}
1548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1549#[serde(deny_unknown_fields)]
1550pub struct RuntimeConfig {
1551 pub max_in_flight: Number<1, 65536>,
1552 pub max_queued: Number<0, 65536>,
1553 #[serde(
1557 default,
1558 skip_serializing_if = "Option::is_none",
1559 deserialize_with = "present"
1560 )]
1561 pub max_parallel_requests: Option<Number<1, 65536>>,
1562 #[schemars(extend("maxProperties" = 1024))]
1563 pub per_backend_concurrency: BTreeMap<Handle, Number<1, 65536>>,
1564}
1565#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1566#[serde(rename_all = "snake_case", deny_unknown_fields)]
1567pub enum SinkPolicy {
1568 FailDecision {
1569 timeout_ms: Number<1, 600000>,
1570 },
1571 Backpressure {
1572 max_pending: Number<1, 65536>,
1573 enqueue_timeout_ms: Number<1, 600000>,
1574 delivery_timeout_ms: Number<1, 600000>,
1575 },
1576 DropCounted {
1577 max_pending: Number<1, 65536>,
1578 delivery_timeout_ms: Number<1, 600000>,
1579 },
1580}
1581#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1582pub enum RemoteProtocol {
1583 #[serde(rename = "rustev.remote/1")]
1584 V1,
1585 #[serde(rename = "rustev.remote/2")]
1586 V2,
1587}
1588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1589#[serde(deny_unknown_fields)]
1590pub struct RemoteEndpoint {
1591 pub address: Text<1, 4096>,
1592 pub credentials_file: Maybe<Text<1, 4096>>,
1593 pub max_in_flight: Number<1, 65536>,
1594 pub queue_capacity: Number<1, 65536>,
1595 pub readiness_affecting: bool,
1596}
1597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1598#[serde(rename_all = "snake_case", deny_unknown_fields)]
1599pub enum BackendKind {
1600 Compiled {
1601 feature: Handle,
1602 },
1603 Remote {
1604 protocol: RemoteProtocol,
1605 endpoint: RemoteEndpoint,
1606 },
1607}
1608#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1609#[serde(deny_unknown_fields)]
1610pub struct BackendBinding {
1611 pub expected_descriptor_id: DescriptorId,
1612 pub backend: BackendKind,
1613}
1614#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1615#[serde(rename_all = "snake_case", deny_unknown_fields)]
1616pub enum Sink {
1617 Remote(RemoteEndpoint),
1618 Opentelemetry {
1619 endpoint: RemoteEndpoint,
1620 redaction: Embedded<crate::redaction::RedactionPolicy>,
1621 },
1622}
1623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1624#[serde(deny_unknown_fields)]
1625pub struct SigstoreTrust {
1626 pub trusted_root_file: Text<1, 4096>,
1627 pub certificate_identity: Text<1, 4096>,
1628 pub issuer: Text<1, 4096>,
1629}
1630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1631#[serde(deny_unknown_fields)]
1632pub struct SigningKeys {
1633 #[schemars(extend("maxProperties" = 1024))]
1634 pub ed25519: BTreeMap<Handle, Text<1, 4096>>,
1635 pub sigstore: List<SigstoreTrust, 0, 1024>,
1636}
1637service_document!(ServeConfig,ConfigSchema,"rustev.serve-config/1",SERVICE_LIMITS,{
1638 pub listeners:List<Listener,1,64>,pub max_plans:Number<1,4096>,pub runtime:RuntimeConfig,
1639 #[schemars(extend("maxProperties" = 1024))] pub backend_bindings:BTreeMap<Handle,BackendBinding>,#[schemars(extend("maxProperties" = 1024))] pub context_sources:BTreeMap<Handle,RemoteEndpoint>,#[schemars(extend("maxProperties" = 1024))] pub sinks:BTreeMap<Handle,Sink>,pub sink_policy:SinkPolicy,
1640 pub signing_mode:SigningMode,pub signing_keys:SigningKeys,pub preload_bundles:List<Text<1,4096>,0,4096>,#[schemars(extend("maxProperties" = 1024))] pub resolver_roots:BTreeMap<Handle,Text<1,4096>>,
1641 pub idempotency_window_ms:Number<60000,86400000>,pub max_retained:Number<1,1000000>,pub max_retained_bytes:Number<16777216,17179869184>,pub stream_queue:Number<16,65536>,pub blocking_threads:Number<1,64>,pub blocking_queue:Number<1,4096>,pub drain_ms:Number<0,600000>,pub flush_ms:Number<0,600000>,
1642 });
1645impl Validate for ServeConfig {
1646 fn validate(&self) -> Result<(), String> {
1647 for len in [
1648 self.backend_bindings.len(),
1649 self.context_sources.len(),
1650 self.sinks.len(),
1651 self.resolver_roots.len(),
1652 self.runtime.per_backend_concurrency.len(),
1653 self.signing_keys.ed25519.len(),
1654 ] {
1655 if len > 1024 {
1656 return Err("configuration map exceeds 1024 entries".into());
1657 }
1658 }
1659 let mut names = std::collections::BTreeSet::new();
1660 for listener in self.listeners.as_slice() {
1661 if !names.insert(listener.name.as_str()) {
1662 return Err("duplicate listener name".into());
1663 }
1664 let groups = listener.groups.as_slice();
1665 for (i, group) in groups.iter().enumerate() {
1666 if groups[..i].contains(group) {
1667 return Err("duplicate route group".into());
1668 }
1669 }
1670 }
1671 Ok(())
1672 }
1673}
1674impl ServeConfig {
1675 pub fn configuration_digest(&self) -> Result<ContentDigest, CanonicalError> {
1679 let mut value = serde_json::to_value(self).map_err(|e| CanonicalError::Serialize {
1680 message: e.to_string(),
1681 })?;
1682 for listener in value["listeners"].as_array_mut().unwrap() {
1683 if let Some(tcp) = listener["transport"].get_mut("tcp") {
1684 tcp.as_object_mut().unwrap().remove("address");
1685 }
1686 if let Some(unix) = listener["transport"].get_mut("unix") {
1687 unix.as_object_mut().unwrap().remove("path");
1688 }
1689 }
1690 fn strip_addresses(value: &mut serde_json::Value) {
1691 match value {
1692 serde_json::Value::Object(map) => {
1693 map.remove("address");
1694 for value in map.values_mut() {
1695 strip_addresses(value);
1696 }
1697 }
1698 serde_json::Value::Array(values) => values.iter_mut().for_each(strip_addresses),
1699 _ => {}
1700 }
1701 }
1702 for key in ["backend_bindings", "context_sources", "sinks"] {
1703 strip_addresses(&mut value[key]);
1704 }
1705 for root in value["resolver_roots"]
1706 .as_object_mut()
1707 .unwrap()
1708 .values_mut()
1709 {
1710 *root = serde_json::Value::String("configured".into());
1711 }
1712 Ok(ContentDigest::from_digest(tagged_digest(
1713 Self::SCHEMA,
1714 &crate::canonical::canonical_value_bytes(&value)?,
1715 )))
1716 }
1717}