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 ] {
258 if let Some(property) = properties
259 .get_mut(name)
260 .and_then(serde_json::Value::as_object_mut)
261 {
262 let branches = property
266 .get("anyOf")
267 .and_then(serde_json::Value::as_array)
268 .map(|b| {
269 b.iter()
270 .filter(|v| v.get("type") != Some(&serde_json::json!("null")))
271 .cloned()
272 .collect::<Vec<_>>()
273 });
274 match branches {
275 Some(mut b) if b.len() == 1 => {
276 property.remove("anyOf");
277 if let Some(serde_json::Value::Object(only)) = b.pop() {
278 for (k, v) in only {
279 property.entry(k).or_insert(v);
280 }
281 }
282 }
283 Some(b) => {
284 property.insert("anyOf".into(), serde_json::Value::Array(b));
285 }
286 None => {
287 if let Some(serde_json::Value::Array(types)) = property.get_mut("type") {
288 types.retain(|t| t != "null");
289 }
290 }
291 }
292 }
293 }
294 }
295 let properties = object
296 .get("properties")
297 .and_then(serde_json::Value::as_object)
298 .cloned()
299 .unwrap_or_default();
300 if properties.contains_key("snapshot") && properties.contains_key("context") {
306 object.insert(
307 "description".into(),
308 serde_json::json!("Exactly one of `snapshot` and `context` is present; the parser refuses both or neither."),
309 );
310 }
311 if properties.contains_key("compile_refusal") {
312 object.insert("if".into(),serde_json::json!({"properties":{"code":{"const":"compile_refused"}},"required":["code"]}));
313 object.insert(
314 "then".into(),
315 serde_json::json!({"required":["compile_refusal"]}),
316 );
317 object.insert(
318 "else".into(),
319 serde_json::json!({"not":{"required":["compile_refusal"]}}),
320 );
321 }
322 if properties.contains_key("run_record") {
323 object.insert("allOf".into(),serde_json::json!([
324 {"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"]}}}}},
325 {"if":{"properties":{"status":{"const":"judged"}}},"then":{"required":["judgment"]}},
326 {"if":{"properties":{"status":{"const":"cancelled"}}},"then":{"not":{"required":["judgment"]}}}
327 ]));
328 }
329}
330
331pub(crate) fn canonical_envelope<T: Serialize>(value: &T) -> Result<Vec<u8>, CanonicalError> {
334 fn render(raw: &serde_json::value::RawValue) -> Result<Vec<u8>, CanonicalError> {
335 if raw.get().starts_with('{') {
336 let fields: BTreeMap<String, Box<serde_json::value::RawValue>> =
337 serde_json::from_str(raw.get()).map_err(|e| CanonicalError::Serialize {
338 message: e.to_string(),
339 })?;
340 let evidence = fields.get("schema").is_some_and(|s| {
341 matches!(
342 s.get(),
343 "\"rustev.serve-decision/1\"" | "\"rustev.serve-refusal/1\""
344 )
345 });
346 let mut out = vec![b'{'];
347 for (index, (key, value)) in fields.iter().enumerate() {
348 if index > 0 {
349 out.push(b',');
350 }
351 out.extend(serde_json::to_vec(key).unwrap());
352 out.push(b':');
353 if evidence && matches!(key.as_str(), "judgment" | "run_record" | "compile_refusal")
354 {
355 out.extend(value.get().as_bytes());
356 } else {
357 out.extend(render(value)?);
358 }
359 }
360 out.push(b'}');
361 Ok(out)
362 } else if raw.get().starts_with('[') {
363 let values: Vec<Box<serde_json::value::RawValue>> = serde_json::from_str(raw.get())
364 .map_err(|e| CanonicalError::Serialize {
365 message: e.to_string(),
366 })?;
367 let mut out = vec![b'['];
368 for (index, value) in values.iter().enumerate() {
369 if index > 0 {
370 out.push(b',');
371 }
372 out.extend(render(value)?);
373 }
374 out.push(b']');
375 Ok(out)
376 } else {
377 let value: serde_json::Value =
378 serde_json::from_str(raw.get()).map_err(|e| CanonicalError::Serialize {
379 message: e.to_string(),
380 })?;
381 crate::canonical::record_canonical_bytes(&value)
382 }
383 }
384 let raw = serde_json::value::to_raw_value(value).map_err(|e| CanonicalError::Serialize {
385 message: e.to_string(),
386 })?;
387 render(&raw)
388}
389
390service_document!(ServeRegister,RegisterSchema,"rustev.serve-register/1",BUNDLE_LIMITS,{
391 pub bundle:Embedded<PlanBundle>,pub signature:Maybe<Embedded<PlanBundleSignature>>,
392});
393impl Validate for ServeRegister {}
394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
395#[serde(deny_unknown_fields)]
396pub struct Context {
397 pub source: Handle,
398}
399service_document!(ServeDecide,DecideSchema,"rustev.serve-decide/1",SERVICE_LIMITS,{
400 pub decision_id:Handle,pub evaluation_time:Timestamp,
401 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub snapshot:Option<Embedded<crate::snapshot::Snapshot>>,
402 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub context:Option<Context>,
403 });
405impl Validate for ServeDecide {
406 fn validate(&self) -> Result<(), String> {
407 if self.snapshot.is_some() == self.context.is_some() {
408 Err("exactly one of snapshot or context is required".into())
409 } else {
410 Ok(())
411 }
412 }
413}
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
415#[serde(rename_all = "snake_case")]
416pub enum DecisionStatus {
417 Judged,
418 Cancelled,
419 EvidenceNotDelivered,
420}
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
422#[serde(rename_all = "snake_case", deny_unknown_fields)]
423pub enum EvidenceDisposition {
424 Receipt {
425 receipt: Text<1, 4096>,
426 },
427 Ticket {
428 ticket: Handle,
429 },
430 Dropped {
431 count: Number<1, { u64::MAX }>,
432 },
433 NotDelivered {
434 error: Text<0, 256>,
435 may_have_received: bool,
436 },
437}
438service_document!(ServeDecision,DecisionSchema,"rustev.serve-decision/1",DECISION_LIMITS,{
439 pub status:DecisionStatus,pub decision_id:Handle,pub plan_id:PlanId,pub registration_id:RegistrationId,
440 pub signer:Signer,pub execution_policy_id:Maybe<ExecutionPolicyId>,pub service_build:Handle,
441 pub protocol:Protocol,pub configuration_digest:ContentDigest,
442 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub judgment:Option<Embedded<crate::judgment::Judgment>>,
443 pub run_record_digest:ContentDigest,pub evidence:EvidenceDisposition,pub replayed:bool,
444 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")] pub run_record:Option<Embedded<crate::run::RunRecord>>,
445});
446impl Validate for ServeDecision {
447 fn validate(&self) -> Result<(), String> {
448 if (self.status == DecisionStatus::EvidenceNotDelivered) != self.run_record.is_some() {
449 return Err("run_record is required only for evidence_not_delivered".into());
450 }
451 if (self.status == DecisionStatus::Judged && self.judgment.is_none())
457 || (self.status == DecisionStatus::Cancelled && self.judgment.is_some())
458 {
459 return Err("judgment does not match status".into());
460 }
461 if (self.status == DecisionStatus::EvidenceNotDelivered)
462 != matches!(self.evidence, EvidenceDisposition::NotDelivered { .. })
463 {
464 return Err("evidence disposition does not match status".into());
465 }
466 Ok(())
467 }
468}
469#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
470pub enum Protocol {
471 #[serde(rename = "rustev.serve/1")]
472 V1,
473}
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
475#[serde(rename_all = "snake_case")]
476pub enum DecisionState {
477 Queued,
478 Running,
479}
480service_document!(ServeDecisionState,DecisionStateSchema,"rustev.serve-decision-state/1",SERVICE_LIMITS,{
481 pub decision_id:Handle,pub plan_id:PlanId,pub state:DecisionState,
482});
483impl Validate for ServeDecisionState {}
484#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
485#[serde(rename_all = "snake_case")]
486pub enum CancelStatus {
487 Queued,
488 Requested,
489 AlreadyFinished,
490}
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
492#[serde(rename_all = "snake_case")]
493pub enum CancelAck {
494 NotRequested,
495 Stopped,
496 Unconfirmed,
497 NoAnswer,
498}
499impl From<crate::run::Cancellation> for CancelAck {
500 fn from(value: crate::run::Cancellation) -> Self {
501 use crate::run::{CancelAnswer, Cancellation};
502 match value {
503 Cancellation::NotRequested => Self::NotRequested,
504 Cancellation::Requested {
505 answer: CancelAnswer::Stopped,
506 } => Self::Stopped,
507 Cancellation::Requested {
508 answer: CancelAnswer::Unconfirmed,
509 } => Self::Unconfirmed,
510 Cancellation::Requested {
511 answer: CancelAnswer::NotObserved,
512 } => Self::NoAnswer,
513 }
514 }
515}
516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
517#[serde(deny_unknown_fields)]
518pub struct AttemptCancellation {
519 pub attempt_id: Text<1, 4096>,
520 pub cancel_ack: CancelAck,
521 pub remote_end: crate::run::RemoteState,
522}
523service_document!(ServeCancel,CancelSchema,"rustev.serve-cancel/1",SERVICE_LIMITS,{
524 pub status:CancelStatus,
525 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")]pub attempts:Option<List<AttemptCancellation,0,10000>>,
526});
527impl Validate for ServeCancel {}
528pub type CancelWaitMs = Number<0, 60000>;
530pub type DeadlineMs = Number<1, { u64::MAX }>;
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
533#[serde(rename_all = "snake_case")]
534pub enum RefusalCode {
535 Overloaded,
536 IdempotencyWindowFull,
537 QueueDeadline,
538 ShuttingDown,
539 NotReady,
540 ContextUnavailable,
541 CancelledBeforeAdmission,
542 DecisionIdConflict,
543 RegistrationConflict,
544 DecisionInProgress,
545 RegistryFull,
546 InvalidRequest,
547 InvalidDocument,
548 PrincipalInvalid,
549 InvalidTraceContext,
550 InvalidDeadline,
551 Unauthenticated,
552 PlanNotRegistered,
553 DecisionNotFound,
554 EndpointUnavailable,
555 BodyTooLarge,
556 HeadersTooLarge,
557 UnsupportedMediaType,
558 CompileRefused,
559 PlanIdMismatch,
560 BindingUnknown,
561 ProfileUnsupported,
562 BundleTampered,
563 SignatureInvalid,
564 SignatureUntrusted,
565 UnsignedRefused,
566 InternalError,
567}
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
569#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
570pub enum GrpcCode {
571 ResourceExhausted,
572 Unavailable,
573 Aborted,
574 AlreadyExists,
575 FailedPrecondition,
576 InvalidArgument,
577 Unauthenticated,
578 NotFound,
579 Internal,
580}
581impl RefusalCode {
582 pub fn http_status(self) -> u16 {
583 use RefusalCode::*;
584 match self {
585 Overloaded | IdempotencyWindowFull => 429,
586 QueueDeadline | ShuttingDown | NotReady => 503,
587 ContextUnavailable => 502,
588 CancelledBeforeAdmission
589 | DecisionIdConflict
590 | RegistrationConflict
591 | DecisionInProgress
592 | RegistryFull => 409,
593 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
594 | InvalidDeadline => 400,
595 Unauthenticated => 401,
596 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => 404,
597 BodyTooLarge => 413,
598 HeadersTooLarge => 431,
599 UnsupportedMediaType => 415,
600 InternalError => 500,
601 _ => 422,
602 }
603 }
604 pub fn grpc_code(self) -> GrpcCode {
605 use RefusalCode::*;
606 match self {
607 Overloaded | IdempotencyWindowFull | BodyTooLarge | HeadersTooLarge => {
608 GrpcCode::ResourceExhausted
609 }
610 QueueDeadline | ShuttingDown | NotReady | ContextUnavailable => GrpcCode::Unavailable,
611 CancelledBeforeAdmission | DecisionInProgress => GrpcCode::Aborted,
612 DecisionIdConflict | RegistrationConflict => GrpcCode::AlreadyExists,
613 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
614 | InvalidDeadline | UnsupportedMediaType => GrpcCode::InvalidArgument,
615 Unauthenticated => GrpcCode::Unauthenticated,
616 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => GrpcCode::NotFound,
617 InternalError => GrpcCode::Internal,
618 _ => GrpcCode::FailedPrecondition,
619 }
620 }
621}
622#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
624#[serde(transparent)]
625pub struct DecisionDidNotRun(#[schemars(extend("const" = false))] bool);
626impl<'de> Deserialize<'de> for DecisionDidNotRun {
627 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
628 if bool::deserialize(d)? {
629 Err(serde::de::Error::custom("decision_ran must be false"))
630 } else {
631 Ok(Self(false))
632 }
633 }
634}
635#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
637#[serde(rename_all = "snake_case", deny_unknown_fields)]
638pub enum CompileRefusal {
639 Document(CompilerRefusal),
640 Digest {
641 digest: ContentDigest,
642 length: Number<2049, { u64::MAX }>,
643 },
644}
645#[derive(Debug, Clone)]
649pub struct CompilerRefusal(Box<serde_json::value::RawValue>);
650impl PartialEq for CompilerRefusal {
651 fn eq(&self, other: &Self) -> bool {
652 self.0.get() == other.0.get()
653 }
654}
655impl Serialize for CompilerRefusal {
656 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
657 self.0.serialize(s)
658 }
659}
660impl<'de> Deserialize<'de> for CompilerRefusal {
661 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
662 let raw = Box::<serde_json::value::RawValue>::deserialize(d)?;
663 let _: CompilerRefusalFields = parse_bounded(
664 raw.get().as_bytes(),
665 &ParseLimits {
666 max_bytes: 2048,
667 ..REFUSAL_LIMITS
668 },
669 )
670 .map_err(serde::de::Error::custom)?;
671 Ok(Self(raw))
672 }
673}
674#[derive(Debug, Serialize, Deserialize, JsonSchema)]
675#[serde(deny_unknown_fields)]
676struct CompilerRefusalFields {
677 command: Text<1, 2048>,
678 status: Refused,
679 #[serde(deserialize_with = "compiler_category")]
685 #[schemars(schema_with = "compiler_category_schema")]
686 category: u64,
687 category_name: Text<1, 2048>,
688 subject: Text<0, 2048>,
689 detail: Text<0, 2048>,
690 shortfalls: List<CompilerShortfall, 0, 1024>,
691}
692fn compiler_category<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
693 let n = u64::deserialize(d)?;
694 if (1..=24).contains(&n) || n == 28 {
695 Ok(n)
696 } else {
697 Err(serde::de::Error::custom("unknown compiler category"))
698 }
699}
700fn compiler_category_schema(_: &mut SchemaGenerator) -> Schema {
701 schemars::json_schema!({"anyOf": [{"type": "integer", "minimum": 1, "maximum": 24}, {"const": 28}]})
702}
703#[derive(Debug, Serialize, Deserialize, JsonSchema)]
704enum Refused {
705 #[serde(rename = "refused")]
706 Refused,
707}
708#[derive(Debug, Serialize, Deserialize, JsonSchema)]
709#[serde(deny_unknown_fields)]
710struct CompilerShortfall {
711 backend_id: Handle,
712 reasons: List<CompilerReason, 0, 1024>,
713}
714#[derive(Debug, Serialize, Deserialize, JsonSchema)]
715#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
716enum CompilerReason {
717 UnknownBackend,
718 Operation {
719 operation: crate::definition::Operation,
720 },
721 OutputKind {
722 offered: crate::descriptor::OutputKind,
723 required: crate::definition::RequiredKind,
724 },
725 Options {
726 max: u64,
727 needed: u64,
728 },
729 InputLimit {
730 max: u64,
731 needed: u64,
732 },
733 Determinism {
734 offered: Text<1, 2048>,
735 required: Text<1, 2048>,
736 },
737 ArtifactPin {
738 pinned: ContentDigest,
739 offered: ContentDigest,
740 },
741 ScreenProfile,
743 ScreenTextLimit {
744 max: u64,
745 needed: u64,
746 },
747 ScreenInputExcess,
748}
749impl JsonSchema for CompilerRefusal {
750 fn schema_name() -> Cow<'static, str> {
751 "CompilerRefusal".into()
752 }
753 fn json_schema(g: &mut SchemaGenerator) -> Schema {
754 g.subschema_for::<CompilerRefusalFields>()
755 }
756}
757service_document!(ServeRefusal,RefusalSchema,"rustev.serve-refusal/1",REFUSAL_LIMITS,{
758 pub code:RefusalCode,pub detail:Text<0,256>,pub decision_ran:DecisionDidNotRun,
759 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")]pub compile_refusal:Option<CompileRefusal>,
760 });
762impl Validate for ServeRefusal {
763 fn validate(&self) -> Result<(), String> {
764 if (self.code == RefusalCode::CompileRefused) != self.compile_refusal.is_some() {
765 Err("compile_refusal is required only for compile_refused".into())
766 } else {
767 Ok(())
768 }
769 }
770}
771
772#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
773#[serde(rename_all = "snake_case", deny_unknown_fields)]
774pub enum StepOutcome {
775 Supplied,
776 Unresolved { reason: crate::judgment::Unresolved },
777 }
779#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
782#[serde(deny_unknown_fields)]
783pub struct GeneratedField {
784 pub step: Handle,
785 pub item: Maybe<Handle>,
786 pub field: GeneratedFieldName,
787 pub value: GeneratedValue,
788 pub attempt_id: Text<1, 4096>,
789 pub provisional: Provisional,
790}
791#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
792#[serde(rename_all = "snake_case", deny_unknown_fields)]
793pub enum GeneratedValue {
794 Text(Text<0, 16384>),
795 List(List<Text<1, 64>, 0, 64>),
796}
797#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
798#[serde(transparent)]
799pub struct GeneratedFieldName(#[schemars(regex(pattern = "^[a-z][a-z0-9_]{0,63}$"))] Text<1, 64>);
800impl<'de> Deserialize<'de> for GeneratedFieldName {
801 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
802 let value = Text::<1, 64>::deserialize(d)?;
803 let mut bytes = value.as_str().bytes();
804 if !bytes.next().is_some_and(|b| b.is_ascii_lowercase())
805 || !bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
806 {
807 return Err(serde::de::Error::custom("invalid generated field name"));
808 }
809 Ok(Self(value))
810 }
811}
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
813#[serde(transparent)]
814pub struct Provisional(#[schemars(extend("const" = true))] bool);
815impl<'de> Deserialize<'de> for Provisional {
816 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
817 if bool::deserialize(d)? {
818 Ok(Self(true))
819 } else {
820 Err(serde::de::Error::custom("field must be provisional"))
821 }
822 }
823}
824#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
825#[serde(rename_all = "snake_case", deny_unknown_fields)]
826pub enum FinalResult {
827 Decision(Embedded<ServeDecision>),
828 Refusal(Embedded<ServeRefusal>),
829}
830#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
831#[serde(rename_all = "snake_case", deny_unknown_fields)]
832pub enum EventKind {
833 Admitted,
834 Step {
835 step_id: Handle,
836 instance_ids: List<Handle, 0, 4096>,
837 outcome: StepOutcome,
838 derivation: crate::judgment::Derivation,
839 },
840 Field(GeneratedField),
841 Final {
842 result: FinalResult,
843 events_dropped: u64,
844 },
845}
846service_document!(ServeEvent,EventSchema,"rustev.serve-event/1",ParseLimits{max_bytes:10*MIB,..SERVICE_LIMITS},{pub sequence:u64,pub kind:EventKind,});
847impl Validate for ServeEvent {}
848
849#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
852#[serde(transparent)]
853pub struct Reference(Text<1, 4096>);
854impl Reference {
855 pub fn as_str(&self) -> &str {
856 self.0.as_str()
857 }
858}
859impl<'de> Deserialize<'de> for Reference {
860 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
861 let value = Text::<1, 4096>::deserialize(d)?;
862 let valid = value
863 .as_str()
864 .split_once(':')
865 .is_some_and(|(prefix, path)| {
866 !prefix.is_empty()
867 && prefix
868 .bytes()
869 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
870 && !path.contains([':', '\\', '\0'])
871 && path
872 .split('/')
873 .all(|s| !s.is_empty() && s != "." && s != "..")
874 });
875 if !valid {
876 return Err(serde::de::Error::custom(
877 "reference must be prefix:relative/path without empty, dot or parent components",
878 ));
879 }
880 Ok(Self(value))
881 }
882}
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
884#[serde(rename_all = "snake_case", deny_unknown_fields)]
885#[serde(bound(deserialize = "T: Document", serialize = ""))]
886pub enum DocumentInput<T: Document> {
887 Document(Embedded<T>),
888 Reference(Reference),
889}
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
891#[serde(deny_unknown_fields)]
892pub struct ReplayOptions {
893 pub scope: crate::scope::Scope,
894 pub now_ms: Timestamp,
895 pub host_cap_ms: Maybe<Number<1, 2592000000>>,
896}
897service_document!(ServeReplay,ReplaySchema,"rustev.serve-replay/1",SERVICE_LIMITS,{
898 pub bundle:DocumentInput<crate::replay::ReplayBundle>,pub inputs:List<ReferencedInput,0,1024>,pub config:ReplayOptions,
899});
900impl Validate for ServeReplay {}
901#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
903#[serde(deny_unknown_fields)]
904pub struct ReferencedInput {
905 pub reference: Reference,
906 pub input: ReplayInput,
907}
908#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
909#[serde(rename_all = "snake_case", deny_unknown_fields)]
910pub enum ReplayInput {
911 Plan(DocumentInput<crate::plan::Plan>),
912 Snapshot(DocumentInput<crate::snapshot::Snapshot>),
913 Run(DocumentInput<crate::run::RunRecord>),
914 Evidence(DocumentInput<crate::evidence::EvidenceRecord>),
915 Judgment(DocumentInput<crate::judgment::Judgment>),
916 BackendOutput(DocumentInput<crate::output::BackendOutputDoc>),
917 ExecutionPolicy(DocumentInput<crate::execution::ExecutionPolicy>),
918 Calibration(DocumentInput<crate::calibration::CalibrationArtifact>),
919}
920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
921#[serde(deny_unknown_fields)]
922pub struct EvaluationDependencies {
923 pub descriptors: List<Reference, 0, 1024>,
924 pub rules: List<Reference, 0, 1024>,
925 pub calibrations: List<Reference, 0, 1024>,
926}
927#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
928#[serde(deny_unknown_fields)]
929pub struct RepeatInputs {
930 pub source: RepeatSource,
931 pub config: Reference,
932 pub manifest: Reference,
933 pub runs: List<RepeatRun, 1, 64>,
934}
935#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
936#[serde(rename_all = "snake_case")]
937pub enum RepeatSource {
938 Replay,
939 Captured,
940 Live,
941}
942#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
943#[serde(deny_unknown_fields)]
944pub struct RepeatRun {
945 pub evaluated: Reference,
946 pub cases: Reference,
947}
948service_document!(ServeEvaluate,EvaluateSchema,"rustev.serve-evaluate/1",SERVICE_LIMITS,{
949 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>,
950});
951impl Validate for ServeEvaluate {}
952
953#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
954#[serde(rename_all = "snake_case")]
955pub enum RouteGroup {
956 Decide,
957 Admin,
958 Ops,
959}
960#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
961pub enum HttpMethod {
962 GET,
963 PUT,
964 POST,
965}
966#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
967#[serde(rename_all = "snake_case")]
968pub enum Operation {
969 Describe,
970 Register,
971 Registration,
972 Decide,
973 Stream,
974 Lookup,
975 Cancel,
976 Replay,
977 Evaluate,
978 Explain,
979 Invalidations,
980 Health,
981 Readiness,
982 Metrics,
983}
984#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
985#[serde(deny_unknown_fields)]
986pub struct OperationDescription {
987 pub operation: Operation,
988 pub method: HttpMethod,
989 pub route: Text<1, 256>,
990 pub group: RouteGroup,
991 pub request_schema: Maybe<Text<1, 128>>,
992 pub response_schemas: List<Text<1, 128>, 0, 4>,
993 pub streaming: bool,
994 pub response_format: ResponseFormat,
995}
996#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
997#[serde(rename_all = "snake_case")]
998pub enum ResponseFormat {
999 Document,
1000 SurfaceOutput,
1001 Explanation,
1002 Empty,
1003 Openmetrics,
1004}
1005pub fn operation_table() -> Vec<OperationDescription> {
1007 use HttpMethod::*;
1008 use Operation::{
1009 Cancel, Describe, Evaluate, Explain, Health, Invalidations, Lookup, Metrics, Readiness,
1010 Register, Registration, Replay, Stream,
1011 };
1012 use ResponseFormat::*;
1013 use RouteGroup::*;
1014 let rows = [
1015 (
1016 Describe,
1017 GET,
1018 "/v1/service",
1019 Decide,
1020 None,
1021 &["rustev.serve-service/1"][..],
1022 false,
1023 Document,
1024 ),
1025 (
1026 Register,
1027 PUT,
1028 "/v1/plans/{planId}",
1029 Admin,
1030 Some("rustev.serve-register/1"),
1031 &["rustev.plan-registration/1"],
1032 false,
1033 Document,
1034 ),
1035 (
1036 Registration,
1037 GET,
1038 "/v1/plans/{planId}",
1039 Decide,
1040 None,
1041 &["rustev.plan-registration/1"],
1042 false,
1043 Document,
1044 ),
1045 (
1046 Operation::Decide,
1047 POST,
1048 "/v1/plans/{planId}/decisions",
1049 Decide,
1050 Some("rustev.serve-decide/1"),
1051 &["rustev.serve-decision/1"],
1052 false,
1053 Document,
1054 ),
1055 (
1056 Stream,
1057 POST,
1058 "/v1/plans/{planId}/decisions?stream=1",
1059 Decide,
1060 Some("rustev.serve-decide/1"),
1061 &["rustev.serve-event/1"],
1062 true,
1063 Document,
1064 ),
1065 (
1066 Lookup,
1067 GET,
1068 "/v1/decisions/{decisionId}",
1069 Decide,
1070 None,
1071 &["rustev.serve-decision/1", "rustev.serve-decision-state/1"],
1072 false,
1073 Document,
1074 ),
1075 (
1076 Cancel,
1077 POST,
1078 "/v1/decisions/{decisionId}/cancel",
1079 Decide,
1080 None,
1081 &["rustev.serve-cancel/1"],
1082 false,
1083 Document,
1084 ),
1085 (
1086 Replay,
1087 POST,
1088 "/v1/replay",
1089 Decide,
1090 Some("rustev.serve-replay/1"),
1091 &[],
1092 false,
1093 SurfaceOutput,
1094 ),
1095 (
1096 Evaluate,
1097 POST,
1098 "/v1/evaluate",
1099 Decide,
1100 Some("rustev.serve-evaluate/1"),
1101 &[],
1102 false,
1103 SurfaceOutput,
1104 ),
1105 (
1106 Explain,
1107 POST,
1108 "/v1/explain",
1109 Decide,
1110 None,
1111 &["rustev.explanation/1"],
1112 false,
1113 Explanation,
1114 ),
1115 (
1116 Invalidations,
1117 POST,
1118 "/v1/context/invalidations",
1119 Admin,
1120 Some("rustev.serve-invalidations/1"),
1121 &["rustev.serve-invalidations-ack/1"],
1122 false,
1123 Document,
1124 ),
1125 (Health, GET, "/healthz", Ops, None, &[], false, Empty),
1126 (
1127 Readiness,
1128 GET,
1129 "/readyz",
1130 Ops,
1131 None,
1132 &["rustev.serve-readiness/1"],
1133 false,
1134 Document,
1135 ),
1136 (Metrics, GET, "/metrics", Ops, None, &[], false, Openmetrics),
1137 ];
1138 rows.into_iter()
1139 .map(
1140 |(operation, method, route, group, request, response, streaming, response_format)| {
1141 OperationDescription {
1142 operation,
1143 method,
1144 route: Text::new(route).unwrap(),
1145 group,
1146 request_schema: request
1147 .map_or(Maybe::None, |s| Maybe::Some(Text::new(s).unwrap())),
1148 response_schemas: List::new(
1149 response.iter().map(|s| Text::new(*s).unwrap()).collect(),
1150 )
1151 .unwrap(),
1152 streaming,
1153 response_format,
1154 }
1155 },
1156 )
1157 .collect()
1158}
1159service_document!(Serve,ServeSchema,"rustev.serve/1",SERVICE_LIMITS,{pub operations:List<OperationDescription,14,14>,});
1160impl Validate for Serve {
1161 fn validate(&self) -> Result<(), String> {
1162 if self.operations.as_slice() != operation_table() {
1163 Err("operation table differs from rustev.serve/1".into())
1164 } else {
1165 Ok(())
1166 }
1167 }
1168}
1169#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1170#[serde(rename_all = "snake_case")]
1171pub enum Retention {
1172 MemoryLostOnRestart,
1173}
1174service_document!(ServeService,ServiceSchema,"rustev.serve-service/1",SERVICE_LIMITS,{
1175 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,
1176});
1177impl Validate for ServeService {}
1178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1179#[serde(deny_unknown_fields)]
1180pub struct ReadinessFailure {
1181 pub item: Handle,
1182 pub reason: ReadinessReason,
1183}
1184#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1185#[serde(rename_all = "snake_case")]
1186pub enum ReadinessReason {
1187 PreloadUnregistered,
1188 DescribeIncomplete,
1189 Degraded,
1190 InvalidationDegraded,
1191 ShuttingDown,
1192}
1193service_document!(ServeReadiness,ReadinessSchema,"rustev.serve-readiness/1",SERVICE_LIMITS,{
1194 pub failing_items:List<ReadinessFailure,0,4096>,pub signing_mode:SigningMode,pub development_bearer_listeners:List<Handle,0,64>,
1195});
1196impl Validate for ServeReadiness {}
1197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1198#[serde(deny_unknown_fields)]
1199pub struct DecisionIdList {
1200 pub ids: List<Handle, 0, 10000>,
1201 pub total: u64,
1202 pub truncated: bool,
1203}
1204impl DecisionIdList {
1205 fn validate(&self) -> Result<(), String> {
1206 let length = self.ids.as_slice().len() as u64;
1207 if self.total < length || self.truncated != (self.total > length) {
1208 Err("id list total/truncated contradict its length".into())
1209 } else {
1210 Ok(())
1211 }
1212 }
1213}
1214service_document!(ServeShutdown,ShutdownSchema,"rustev.serve-shutdown/1",ParseLimits{max_bytes:8*MIB,..SERVICE_LIMITS},{
1215 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,
1216});
1217impl Validate for ServeShutdown {
1218 fn validate(&self) -> Result<(), String> {
1219 self.failed_ids.validate()?;
1220 self.unflushed_ids.validate()?;
1221 if self.failed != self.failed_ids.total || self.unflushed != self.unflushed_ids.total {
1222 Err("shutdown counts must equal id-list totals".into())
1223 } else {
1224 Ok(())
1225 }
1226 }
1227}
1228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1229#[serde(rename_all = "snake_case")]
1230pub enum InvalidationKind {
1231 Correction,
1232 Revocation,
1233 Erasure,
1234}
1235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1236#[serde(rename_all = "snake_case", deny_unknown_fields)]
1237pub enum InvalidationScope {
1238 Principal(List<u8, 1, 1024>),
1239 AllScopes,
1240}
1241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1244#[serde(deny_unknown_fields)]
1245pub struct InvalidationItem {
1246 pub schema: InvalidationItemSchema,
1247 pub source_id: Handle,
1248 pub sequence: u64,
1249 pub kind: InvalidationKind,
1250 pub fields: List<Handle, 1, 4096>,
1251 pub scope: InvalidationScope,
1252}
1253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1254pub enum InvalidationItemSchema {
1255 #[serde(rename = "rustev.remote-context-invalidation/1")]
1256 V1,
1257}
1258service_document!(ServeInvalidations,InvalidationsSchema,"rustev.serve-invalidations/1",SERVICE_LIMITS,{pub source_id:Handle,pub items:List<InvalidationItem,0,1024>,});
1259impl Validate for ServeInvalidations {
1260 fn validate(&self) -> Result<(), String> {
1261 if self
1262 .items
1263 .as_slice()
1264 .iter()
1265 .any(|i| i.source_id != self.source_id)
1266 || self
1267 .items
1268 .as_slice()
1269 .windows(2)
1270 .any(|p| p[0].sequence >= p[1].sequence)
1271 {
1272 Err("invalidations must name the batch source and increase in sequence".into())
1273 } else {
1274 Ok(())
1275 }
1276 }
1277}
1278service_document!(ServeInvalidationsAck,InvalidationsAckSchema,"rustev.serve-invalidations-ack/1",SERVICE_LIMITS,{pub highest_sequence:u64,});
1279impl Validate for ServeInvalidationsAck {}
1280
1281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1282#[serde(deny_unknown_fields)]
1283pub struct ListenerLimits {
1284 pub max_header_bytes: Number<4096, 65536>,
1285 pub registration_body_bytes: Number<1, 16777216>,
1286 pub other_body_bytes: Number<1, 4194304>,
1287 pub max_connections: Number<1, 65536>,
1288 pub max_http2_streams: Number<1, 1024>,
1289 pub requests_per_second: Number<1, 10000>,
1290}
1291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1292#[serde(deny_unknown_fields)]
1293pub struct Tls {
1294 pub certificate_file: Text<1, 4096>,
1295 pub private_key_file: Text<1, 4096>,
1296 pub client_ca_files: List<Text<1, 4096>, 1, 64>,
1297 pub subject_allowlist: List<Text<1, 4096>, 0, 1024>,
1298}
1299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
1300#[serde(transparent)]
1301pub struct SocketMode(#[schemars(extend("enum" = [384,432]))] u16);
1302impl<'de> Deserialize<'de> for SocketMode {
1303 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1304 match u16::deserialize(d)? {
1305 value @ (0o600 | 0o660) => Ok(Self(value)),
1306 _ => Err(serde::de::Error::custom("socket mode must be 0600 or 0660")),
1307 }
1308 }
1309}
1310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1311#[serde(rename_all = "snake_case", deny_unknown_fields)]
1312pub enum UnixAuth {
1313 Filesystem,
1314 DevelopmentBearer { token_file: Text<1, 4096> },
1315}
1316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1317#[serde(rename_all = "snake_case", deny_unknown_fields)]
1318pub enum ListenerTransport {
1319 Tcp {
1320 address: Text<1, 4096>,
1321 tls: Tls,
1322 },
1323 Unix {
1324 path: Text<1, 4096>,
1325 mode: SocketMode,
1326 auth: UnixAuth,
1327 },
1328}
1329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1330#[serde(deny_unknown_fields)]
1331pub struct Listener {
1332 pub name: Handle,
1333 pub groups: List<RouteGroup, 1, 3>,
1334 pub transport: ListenerTransport,
1335 pub limits: ListenerLimits,
1336 pub redaction_policy: Maybe<Embedded<crate::redaction::RedactionPolicy>>,
1337}
1338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1339#[serde(deny_unknown_fields)]
1340pub struct RuntimeConfig {
1341 pub max_in_flight: Number<1, 65536>,
1342 pub max_queued: Number<0, 65536>,
1343 #[schemars(extend("maxProperties" = 1024))]
1344 pub per_backend_concurrency: BTreeMap<Handle, Number<1, 65536>>,
1345}
1346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1347#[serde(rename_all = "snake_case", deny_unknown_fields)]
1348pub enum SinkPolicy {
1349 FailDecision {
1350 timeout_ms: Number<1, 600000>,
1351 },
1352 Backpressure {
1353 max_pending: Number<1, 65536>,
1354 enqueue_timeout_ms: Number<1, 600000>,
1355 delivery_timeout_ms: Number<1, 600000>,
1356 },
1357 DropCounted {
1358 max_pending: Number<1, 65536>,
1359 delivery_timeout_ms: Number<1, 600000>,
1360 },
1361}
1362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1363pub enum RemoteProtocol {
1364 #[serde(rename = "rustev.remote/1")]
1365 V1,
1366 #[serde(rename = "rustev.remote/2")]
1367 V2,
1368}
1369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1370#[serde(deny_unknown_fields)]
1371pub struct RemoteEndpoint {
1372 pub address: Text<1, 4096>,
1373 pub credentials_file: Maybe<Text<1, 4096>>,
1374 pub max_in_flight: Number<1, 65536>,
1375 pub queue_capacity: Number<1, 65536>,
1376 pub readiness_affecting: bool,
1377}
1378#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1379#[serde(rename_all = "snake_case", deny_unknown_fields)]
1380pub enum BackendKind {
1381 Compiled {
1382 feature: Handle,
1383 },
1384 Remote {
1385 protocol: RemoteProtocol,
1386 endpoint: RemoteEndpoint,
1387 },
1388}
1389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1390#[serde(deny_unknown_fields)]
1391pub struct BackendBinding {
1392 pub expected_descriptor_id: DescriptorId,
1393 pub backend: BackendKind,
1394}
1395#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1396#[serde(rename_all = "snake_case", deny_unknown_fields)]
1397pub enum Sink {
1398 Remote(RemoteEndpoint),
1399 Opentelemetry {
1400 endpoint: RemoteEndpoint,
1401 redaction: Embedded<crate::redaction::RedactionPolicy>,
1402 },
1403}
1404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1405#[serde(deny_unknown_fields)]
1406pub struct SigstoreTrust {
1407 pub trusted_root_file: Text<1, 4096>,
1408 pub certificate_identity: Text<1, 4096>,
1409 pub issuer: Text<1, 4096>,
1410}
1411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1412#[serde(deny_unknown_fields)]
1413pub struct SigningKeys {
1414 #[schemars(extend("maxProperties" = 1024))]
1415 pub ed25519: BTreeMap<Handle, Text<1, 4096>>,
1416 pub sigstore: List<SigstoreTrust, 0, 1024>,
1417}
1418service_document!(ServeConfig,ConfigSchema,"rustev.serve-config/1",SERVICE_LIMITS,{
1419 pub listeners:List<Listener,1,64>,pub max_plans:Number<1,4096>,pub runtime:RuntimeConfig,
1420 #[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,
1421 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>>,
1422 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>,
1423 });
1426impl Validate for ServeConfig {
1427 fn validate(&self) -> Result<(), String> {
1428 for len in [
1429 self.backend_bindings.len(),
1430 self.context_sources.len(),
1431 self.sinks.len(),
1432 self.resolver_roots.len(),
1433 self.runtime.per_backend_concurrency.len(),
1434 self.signing_keys.ed25519.len(),
1435 ] {
1436 if len > 1024 {
1437 return Err("configuration map exceeds 1024 entries".into());
1438 }
1439 }
1440 let mut names = std::collections::BTreeSet::new();
1441 for listener in self.listeners.as_slice() {
1442 if !names.insert(listener.name.as_str()) {
1443 return Err("duplicate listener name".into());
1444 }
1445 let groups = listener.groups.as_slice();
1446 for (i, group) in groups.iter().enumerate() {
1447 if groups[..i].contains(group) {
1448 return Err("duplicate route group".into());
1449 }
1450 }
1451 }
1452 Ok(())
1453 }
1454}
1455impl ServeConfig {
1456 pub fn configuration_digest(&self) -> Result<ContentDigest, CanonicalError> {
1460 let mut value = serde_json::to_value(self).map_err(|e| CanonicalError::Serialize {
1461 message: e.to_string(),
1462 })?;
1463 for listener in value["listeners"].as_array_mut().unwrap() {
1464 if let Some(tcp) = listener["transport"].get_mut("tcp") {
1465 tcp.as_object_mut().unwrap().remove("address");
1466 }
1467 if let Some(unix) = listener["transport"].get_mut("unix") {
1468 unix.as_object_mut().unwrap().remove("path");
1469 }
1470 }
1471 fn strip_addresses(value: &mut serde_json::Value) {
1472 match value {
1473 serde_json::Value::Object(map) => {
1474 map.remove("address");
1475 for value in map.values_mut() {
1476 strip_addresses(value);
1477 }
1478 }
1479 serde_json::Value::Array(values) => values.iter_mut().for_each(strip_addresses),
1480 _ => {}
1481 }
1482 }
1483 for key in ["backend_bindings", "context_sources", "sinks"] {
1484 strip_addresses(&mut value[key]);
1485 }
1486 for root in value["resolver_roots"]
1487 .as_object_mut()
1488 .unwrap()
1489 .values_mut()
1490 {
1491 *root = serde_json::Value::String("configured".into());
1492 }
1493 Ok(ContentDigest::from_digest(tagged_digest(
1494 Self::SCHEMA,
1495 &crate::canonical::canonical_value_bytes(&value)?,
1496 )))
1497 }
1498}