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}
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
568#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
569pub enum GrpcCode {
570 ResourceExhausted,
571 Unavailable,
572 Aborted,
573 AlreadyExists,
574 FailedPrecondition,
575 InvalidArgument,
576 Unauthenticated,
577 NotFound,
578}
579impl RefusalCode {
580 pub fn http_status(self) -> u16 {
581 use RefusalCode::*;
582 match self {
583 Overloaded | IdempotencyWindowFull => 429,
584 QueueDeadline | ShuttingDown | NotReady => 503,
585 ContextUnavailable => 502,
586 CancelledBeforeAdmission
587 | DecisionIdConflict
588 | RegistrationConflict
589 | DecisionInProgress
590 | RegistryFull => 409,
591 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
592 | InvalidDeadline => 400,
593 Unauthenticated => 401,
594 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => 404,
595 BodyTooLarge => 413,
596 HeadersTooLarge => 431,
597 UnsupportedMediaType => 415,
598 _ => 422,
599 }
600 }
601 pub fn grpc_code(self) -> GrpcCode {
602 use RefusalCode::*;
603 match self {
604 Overloaded | IdempotencyWindowFull | BodyTooLarge | HeadersTooLarge => {
605 GrpcCode::ResourceExhausted
606 }
607 QueueDeadline | ShuttingDown | NotReady | ContextUnavailable => GrpcCode::Unavailable,
608 CancelledBeforeAdmission | DecisionInProgress => GrpcCode::Aborted,
609 DecisionIdConflict | RegistrationConflict => GrpcCode::AlreadyExists,
610 InvalidRequest | InvalidDocument | PrincipalInvalid | InvalidTraceContext
611 | InvalidDeadline | UnsupportedMediaType => GrpcCode::InvalidArgument,
612 Unauthenticated => GrpcCode::Unauthenticated,
613 PlanNotRegistered | DecisionNotFound | EndpointUnavailable => GrpcCode::NotFound,
614 _ => GrpcCode::FailedPrecondition,
615 }
616 }
617}
618#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
620#[serde(transparent)]
621pub struct DecisionDidNotRun(#[schemars(extend("const" = false))] bool);
622impl<'de> Deserialize<'de> for DecisionDidNotRun {
623 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
624 if bool::deserialize(d)? {
625 Err(serde::de::Error::custom("decision_ran must be false"))
626 } else {
627 Ok(Self(false))
628 }
629 }
630}
631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
633#[serde(rename_all = "snake_case", deny_unknown_fields)]
634pub enum CompileRefusal {
635 Document(CompilerRefusal),
636 Digest {
637 digest: ContentDigest,
638 length: Number<2049, { u64::MAX }>,
639 },
640}
641#[derive(Debug, Clone)]
645pub struct CompilerRefusal(Box<serde_json::value::RawValue>);
646impl PartialEq for CompilerRefusal {
647 fn eq(&self, other: &Self) -> bool {
648 self.0.get() == other.0.get()
649 }
650}
651impl Serialize for CompilerRefusal {
652 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
653 self.0.serialize(s)
654 }
655}
656impl<'de> Deserialize<'de> for CompilerRefusal {
657 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
658 let raw = Box::<serde_json::value::RawValue>::deserialize(d)?;
659 let _: CompilerRefusalFields = parse_bounded(
660 raw.get().as_bytes(),
661 &ParseLimits {
662 max_bytes: 2048,
663 ..REFUSAL_LIMITS
664 },
665 )
666 .map_err(serde::de::Error::custom)?;
667 Ok(Self(raw))
668 }
669}
670#[derive(Debug, Serialize, Deserialize, JsonSchema)]
671#[serde(deny_unknown_fields)]
672struct CompilerRefusalFields {
673 command: Text<1, 2048>,
674 status: Refused,
675 category: Number<1, 24>,
680 category_name: Text<1, 2048>,
681 subject: Text<0, 2048>,
682 detail: Text<0, 2048>,
683 shortfalls: List<CompilerShortfall, 0, 1024>,
684}
685#[derive(Debug, Serialize, Deserialize, JsonSchema)]
686enum Refused {
687 #[serde(rename = "refused")]
688 Refused,
689}
690#[derive(Debug, Serialize, Deserialize, JsonSchema)]
691#[serde(deny_unknown_fields)]
692struct CompilerShortfall {
693 backend_id: Handle,
694 reasons: List<CompilerReason, 0, 1024>,
695}
696#[derive(Debug, Serialize, Deserialize, JsonSchema)]
697#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
698enum CompilerReason {
699 UnknownBackend,
700 Operation {
701 operation: crate::definition::Operation,
702 },
703 OutputKind {
704 offered: crate::descriptor::OutputKind,
705 required: crate::definition::RequiredKind,
706 },
707 Options {
708 max: u64,
709 needed: u64,
710 },
711 InputLimit {
712 max: u64,
713 needed: u64,
714 },
715 Determinism {
716 offered: Text<1, 2048>,
717 required: Text<1, 2048>,
718 },
719 ArtifactPin {
720 pinned: ContentDigest,
721 offered: ContentDigest,
722 },
723 ScreenProfile,
725 ScreenTextLimit {
726 max: u64,
727 needed: u64,
728 },
729 ScreenInputExcess,
730}
731impl JsonSchema for CompilerRefusal {
732 fn schema_name() -> Cow<'static, str> {
733 "CompilerRefusal".into()
734 }
735 fn json_schema(g: &mut SchemaGenerator) -> Schema {
736 g.subschema_for::<CompilerRefusalFields>()
737 }
738}
739service_document!(ServeRefusal,RefusalSchema,"rustev.serve-refusal/1",REFUSAL_LIMITS,{
740 pub code:RefusalCode,pub detail:Text<0,256>,pub decision_ran:DecisionDidNotRun,
741 # [serde(default,skip_serializing_if="Option::is_none",deserialize_with="present")]pub compile_refusal:Option<CompileRefusal>,
742 });
744impl Validate for ServeRefusal {
745 fn validate(&self) -> Result<(), String> {
746 if (self.code == RefusalCode::CompileRefused) != self.compile_refusal.is_some() {
747 Err("compile_refusal is required only for compile_refused".into())
748 } else {
749 Ok(())
750 }
751 }
752}
753
754#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
755#[serde(rename_all = "snake_case", deny_unknown_fields)]
756pub enum StepOutcome {
757 Supplied,
758 Unresolved { reason: crate::judgment::Unresolved },
759 }
761#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
764#[serde(deny_unknown_fields)]
765pub struct GeneratedField {
766 pub step: Handle,
767 pub item: Maybe<Handle>,
768 pub field: GeneratedFieldName,
769 pub value: GeneratedValue,
770 pub attempt_id: Text<1, 4096>,
771 pub provisional: Provisional,
772}
773#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
774#[serde(rename_all = "snake_case", deny_unknown_fields)]
775pub enum GeneratedValue {
776 Text(Text<0, 16384>),
777 List(List<Text<1, 64>, 0, 64>),
778}
779#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
780#[serde(transparent)]
781pub struct GeneratedFieldName(#[schemars(regex(pattern = "^[a-z][a-z0-9_]{0,63}$"))] Text<1, 64>);
782impl<'de> Deserialize<'de> for GeneratedFieldName {
783 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
784 let value = Text::<1, 64>::deserialize(d)?;
785 let mut bytes = value.as_str().bytes();
786 if !bytes.next().is_some_and(|b| b.is_ascii_lowercase())
787 || !bytes.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
788 {
789 return Err(serde::de::Error::custom("invalid generated field name"));
790 }
791 Ok(Self(value))
792 }
793}
794#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
795#[serde(transparent)]
796pub struct Provisional(#[schemars(extend("const" = true))] bool);
797impl<'de> Deserialize<'de> for Provisional {
798 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
799 if bool::deserialize(d)? {
800 Ok(Self(true))
801 } else {
802 Err(serde::de::Error::custom("field must be provisional"))
803 }
804 }
805}
806#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
807#[serde(rename_all = "snake_case", deny_unknown_fields)]
808pub enum FinalResult {
809 Decision(Embedded<ServeDecision>),
810 Refusal(Embedded<ServeRefusal>),
811}
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
813#[serde(rename_all = "snake_case", deny_unknown_fields)]
814pub enum EventKind {
815 Admitted,
816 Step {
817 step_id: Handle,
818 instance_ids: List<Handle, 0, 4096>,
819 outcome: StepOutcome,
820 derivation: crate::judgment::Derivation,
821 },
822 Field(GeneratedField),
823 Final {
824 result: FinalResult,
825 events_dropped: u64,
826 },
827}
828service_document!(ServeEvent,EventSchema,"rustev.serve-event/1",ParseLimits{max_bytes:10*MIB,..SERVICE_LIMITS},{pub sequence:u64,pub kind:EventKind,});
829impl Validate for ServeEvent {}
830
831#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
834#[serde(transparent)]
835pub struct Reference(Text<1, 4096>);
836impl Reference {
837 pub fn as_str(&self) -> &str {
838 self.0.as_str()
839 }
840}
841impl<'de> Deserialize<'de> for Reference {
842 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
843 let value = Text::<1, 4096>::deserialize(d)?;
844 let valid = value
845 .as_str()
846 .split_once(':')
847 .is_some_and(|(prefix, path)| {
848 !prefix.is_empty()
849 && prefix
850 .bytes()
851 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
852 && !path.contains([':', '\\', '\0'])
853 && path
854 .split('/')
855 .all(|s| !s.is_empty() && s != "." && s != "..")
856 });
857 if !valid {
858 return Err(serde::de::Error::custom(
859 "reference must be prefix:relative/path without empty, dot or parent components",
860 ));
861 }
862 Ok(Self(value))
863 }
864}
865#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
866#[serde(rename_all = "snake_case", deny_unknown_fields)]
867#[serde(bound(deserialize = "T: Document", serialize = ""))]
868pub enum DocumentInput<T: Document> {
869 Document(Embedded<T>),
870 Reference(Reference),
871}
872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
873#[serde(deny_unknown_fields)]
874pub struct ReplayOptions {
875 pub scope: crate::scope::Scope,
876 pub now_ms: Timestamp,
877 pub host_cap_ms: Maybe<Number<1, 2592000000>>,
878}
879service_document!(ServeReplay,ReplaySchema,"rustev.serve-replay/1",SERVICE_LIMITS,{
880 pub bundle:DocumentInput<crate::replay::ReplayBundle>,pub inputs:List<ReferencedInput,0,1024>,pub config:ReplayOptions,
881});
882impl Validate for ServeReplay {}
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
885#[serde(deny_unknown_fields)]
886pub struct ReferencedInput {
887 pub reference: Reference,
888 pub input: ReplayInput,
889}
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
891#[serde(rename_all = "snake_case", deny_unknown_fields)]
892pub enum ReplayInput {
893 Plan(DocumentInput<crate::plan::Plan>),
894 Snapshot(DocumentInput<crate::snapshot::Snapshot>),
895 Run(DocumentInput<crate::run::RunRecord>),
896 Evidence(DocumentInput<crate::evidence::EvidenceRecord>),
897 Judgment(DocumentInput<crate::judgment::Judgment>),
898 BackendOutput(DocumentInput<crate::output::BackendOutputDoc>),
899 ExecutionPolicy(DocumentInput<crate::execution::ExecutionPolicy>),
900 Calibration(DocumentInput<crate::calibration::CalibrationArtifact>),
901}
902#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
903#[serde(deny_unknown_fields)]
904pub struct EvaluationDependencies {
905 pub descriptors: List<Reference, 0, 1024>,
906 pub rules: List<Reference, 0, 1024>,
907 pub calibrations: List<Reference, 0, 1024>,
908}
909#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
910#[serde(deny_unknown_fields)]
911pub struct RepeatInputs {
912 pub source: RepeatSource,
913 pub config: Reference,
914 pub manifest: Reference,
915 pub runs: List<RepeatRun, 1, 64>,
916}
917#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
918#[serde(rename_all = "snake_case")]
919pub enum RepeatSource {
920 Replay,
921 Captured,
922 Live,
923}
924#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
925#[serde(deny_unknown_fields)]
926pub struct RepeatRun {
927 pub evaluated: Reference,
928 pub cases: Reference,
929}
930service_document!(ServeEvaluate,EvaluateSchema,"rustev.serve-evaluate/1",SERVICE_LIMITS,{
931 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>,
932});
933impl Validate for ServeEvaluate {}
934
935#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
936#[serde(rename_all = "snake_case")]
937pub enum RouteGroup {
938 Decide,
939 Admin,
940 Ops,
941}
942#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
943pub enum HttpMethod {
944 GET,
945 PUT,
946 POST,
947}
948#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
949#[serde(rename_all = "snake_case")]
950pub enum Operation {
951 Describe,
952 Register,
953 Registration,
954 Decide,
955 Stream,
956 Lookup,
957 Cancel,
958 Replay,
959 Evaluate,
960 Explain,
961 Invalidations,
962 Health,
963 Readiness,
964 Metrics,
965}
966#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
967#[serde(deny_unknown_fields)]
968pub struct OperationDescription {
969 pub operation: Operation,
970 pub method: HttpMethod,
971 pub route: Text<1, 256>,
972 pub group: RouteGroup,
973 pub request_schema: Maybe<Text<1, 128>>,
974 pub response_schemas: List<Text<1, 128>, 0, 4>,
975 pub streaming: bool,
976 pub response_format: ResponseFormat,
977}
978#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
979#[serde(rename_all = "snake_case")]
980pub enum ResponseFormat {
981 Document,
982 SurfaceOutput,
983 Explanation,
984 Empty,
985 Openmetrics,
986}
987pub fn operation_table() -> Vec<OperationDescription> {
989 use HttpMethod::*;
990 use Operation::{
991 Cancel, Describe, Evaluate, Explain, Health, Invalidations, Lookup, Metrics, Readiness,
992 Register, Registration, Replay, Stream,
993 };
994 use ResponseFormat::*;
995 use RouteGroup::*;
996 let rows = [
997 (
998 Describe,
999 GET,
1000 "/v1/service",
1001 Decide,
1002 None,
1003 &["rustev.serve-service/1"][..],
1004 false,
1005 Document,
1006 ),
1007 (
1008 Register,
1009 PUT,
1010 "/v1/plans/{planId}",
1011 Admin,
1012 Some("rustev.serve-register/1"),
1013 &["rustev.plan-registration/1"],
1014 false,
1015 Document,
1016 ),
1017 (
1018 Registration,
1019 GET,
1020 "/v1/plans/{planId}",
1021 Decide,
1022 None,
1023 &["rustev.plan-registration/1"],
1024 false,
1025 Document,
1026 ),
1027 (
1028 Operation::Decide,
1029 POST,
1030 "/v1/plans/{planId}/decisions",
1031 Decide,
1032 Some("rustev.serve-decide/1"),
1033 &["rustev.serve-decision/1"],
1034 false,
1035 Document,
1036 ),
1037 (
1038 Stream,
1039 POST,
1040 "/v1/plans/{planId}/decisions?stream=1",
1041 Decide,
1042 Some("rustev.serve-decide/1"),
1043 &["rustev.serve-event/1"],
1044 true,
1045 Document,
1046 ),
1047 (
1048 Lookup,
1049 GET,
1050 "/v1/decisions/{decisionId}",
1051 Decide,
1052 None,
1053 &["rustev.serve-decision/1", "rustev.serve-decision-state/1"],
1054 false,
1055 Document,
1056 ),
1057 (
1058 Cancel,
1059 POST,
1060 "/v1/decisions/{decisionId}/cancel",
1061 Decide,
1062 None,
1063 &["rustev.serve-cancel/1"],
1064 false,
1065 Document,
1066 ),
1067 (
1068 Replay,
1069 POST,
1070 "/v1/replay",
1071 Decide,
1072 Some("rustev.serve-replay/1"),
1073 &[],
1074 false,
1075 SurfaceOutput,
1076 ),
1077 (
1078 Evaluate,
1079 POST,
1080 "/v1/evaluate",
1081 Decide,
1082 Some("rustev.serve-evaluate/1"),
1083 &[],
1084 false,
1085 SurfaceOutput,
1086 ),
1087 (
1088 Explain,
1089 POST,
1090 "/v1/explain",
1091 Decide,
1092 None,
1093 &["rustev.explanation/1"],
1094 false,
1095 Explanation,
1096 ),
1097 (
1098 Invalidations,
1099 POST,
1100 "/v1/context/invalidations",
1101 Admin,
1102 Some("rustev.serve-invalidations/1"),
1103 &["rustev.serve-invalidations-ack/1"],
1104 false,
1105 Document,
1106 ),
1107 (Health, GET, "/healthz", Ops, None, &[], false, Empty),
1108 (
1109 Readiness,
1110 GET,
1111 "/readyz",
1112 Ops,
1113 None,
1114 &["rustev.serve-readiness/1"],
1115 false,
1116 Document,
1117 ),
1118 (Metrics, GET, "/metrics", Ops, None, &[], false, Openmetrics),
1119 ];
1120 rows.into_iter()
1121 .map(
1122 |(operation, method, route, group, request, response, streaming, response_format)| {
1123 OperationDescription {
1124 operation,
1125 method,
1126 route: Text::new(route).unwrap(),
1127 group,
1128 request_schema: request
1129 .map_or(Maybe::None, |s| Maybe::Some(Text::new(s).unwrap())),
1130 response_schemas: List::new(
1131 response.iter().map(|s| Text::new(*s).unwrap()).collect(),
1132 )
1133 .unwrap(),
1134 streaming,
1135 response_format,
1136 }
1137 },
1138 )
1139 .collect()
1140}
1141service_document!(Serve,ServeSchema,"rustev.serve/1",SERVICE_LIMITS,{pub operations:List<OperationDescription,14,14>,});
1142impl Validate for Serve {
1143 fn validate(&self) -> Result<(), String> {
1144 if self.operations.as_slice() != operation_table() {
1145 Err("operation table differs from rustev.serve/1".into())
1146 } else {
1147 Ok(())
1148 }
1149 }
1150}
1151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1152#[serde(rename_all = "snake_case")]
1153pub enum Retention {
1154 MemoryLostOnRestart,
1155}
1156service_document!(ServeService,ServiceSchema,"rustev.serve-service/1",SERVICE_LIMITS,{
1157 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,
1158});
1159impl Validate for ServeService {}
1160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1161#[serde(deny_unknown_fields)]
1162pub struct ReadinessFailure {
1163 pub item: Handle,
1164 pub reason: ReadinessReason,
1165}
1166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1167#[serde(rename_all = "snake_case")]
1168pub enum ReadinessReason {
1169 PreloadUnregistered,
1170 DescribeIncomplete,
1171 Degraded,
1172 InvalidationDegraded,
1173 ShuttingDown,
1174}
1175service_document!(ServeReadiness,ReadinessSchema,"rustev.serve-readiness/1",SERVICE_LIMITS,{
1176 pub failing_items:List<ReadinessFailure,0,4096>,pub signing_mode:SigningMode,pub development_bearer_listeners:List<Handle,0,64>,
1177});
1178impl Validate for ServeReadiness {}
1179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1180#[serde(deny_unknown_fields)]
1181pub struct DecisionIdList {
1182 pub ids: List<Handle, 0, 10000>,
1183 pub total: u64,
1184 pub truncated: bool,
1185}
1186impl DecisionIdList {
1187 fn validate(&self) -> Result<(), String> {
1188 let length = self.ids.as_slice().len() as u64;
1189 if self.total < length || self.truncated != (self.total > length) {
1190 Err("id list total/truncated contradict its length".into())
1191 } else {
1192 Ok(())
1193 }
1194 }
1195}
1196service_document!(ServeShutdown,ShutdownSchema,"rustev.serve-shutdown/1",ParseLimits{max_bytes:8*MIB,..SERVICE_LIMITS},{
1197 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,
1198});
1199impl Validate for ServeShutdown {
1200 fn validate(&self) -> Result<(), String> {
1201 self.failed_ids.validate()?;
1202 self.unflushed_ids.validate()?;
1203 if self.failed != self.failed_ids.total || self.unflushed != self.unflushed_ids.total {
1204 Err("shutdown counts must equal id-list totals".into())
1205 } else {
1206 Ok(())
1207 }
1208 }
1209}
1210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1211#[serde(rename_all = "snake_case")]
1212pub enum InvalidationKind {
1213 Correction,
1214 Revocation,
1215 Erasure,
1216}
1217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1218#[serde(rename_all = "snake_case", deny_unknown_fields)]
1219pub enum InvalidationScope {
1220 Principal(List<u8, 1, 1024>),
1221 AllScopes,
1222}
1223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1226#[serde(deny_unknown_fields)]
1227pub struct InvalidationItem {
1228 pub schema: InvalidationItemSchema,
1229 pub source_id: Handle,
1230 pub sequence: u64,
1231 pub kind: InvalidationKind,
1232 pub fields: List<Handle, 1, 4096>,
1233 pub scope: InvalidationScope,
1234}
1235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1236pub enum InvalidationItemSchema {
1237 #[serde(rename = "rustev.remote-context-invalidation/1")]
1238 V1,
1239}
1240service_document!(ServeInvalidations,InvalidationsSchema,"rustev.serve-invalidations/1",SERVICE_LIMITS,{pub source_id:Handle,pub items:List<InvalidationItem,0,1024>,});
1241impl Validate for ServeInvalidations {
1242 fn validate(&self) -> Result<(), String> {
1243 if self
1244 .items
1245 .as_slice()
1246 .iter()
1247 .any(|i| i.source_id != self.source_id)
1248 || self
1249 .items
1250 .as_slice()
1251 .windows(2)
1252 .any(|p| p[0].sequence >= p[1].sequence)
1253 {
1254 Err("invalidations must name the batch source and increase in sequence".into())
1255 } else {
1256 Ok(())
1257 }
1258 }
1259}
1260service_document!(ServeInvalidationsAck,InvalidationsAckSchema,"rustev.serve-invalidations-ack/1",SERVICE_LIMITS,{pub highest_sequence:u64,});
1261impl Validate for ServeInvalidationsAck {}
1262
1263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1264#[serde(deny_unknown_fields)]
1265pub struct ListenerLimits {
1266 pub max_header_bytes: Number<4096, 65536>,
1267 pub registration_body_bytes: Number<1, 16777216>,
1268 pub other_body_bytes: Number<1, 4194304>,
1269 pub max_connections: Number<1, 65536>,
1270 pub max_http2_streams: Number<1, 1024>,
1271 pub requests_per_second: Number<1, 10000>,
1272}
1273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1274#[serde(deny_unknown_fields)]
1275pub struct Tls {
1276 pub certificate_file: Text<1, 4096>,
1277 pub private_key_file: Text<1, 4096>,
1278 pub client_ca_files: List<Text<1, 4096>, 1, 64>,
1279 pub subject_allowlist: List<Text<1, 4096>, 0, 1024>,
1280}
1281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
1282#[serde(transparent)]
1283pub struct SocketMode(#[schemars(extend("enum" = [384,432]))] u16);
1284impl<'de> Deserialize<'de> for SocketMode {
1285 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1286 match u16::deserialize(d)? {
1287 value @ (0o600 | 0o660) => Ok(Self(value)),
1288 _ => Err(serde::de::Error::custom("socket mode must be 0600 or 0660")),
1289 }
1290 }
1291}
1292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1293#[serde(rename_all = "snake_case", deny_unknown_fields)]
1294pub enum UnixAuth {
1295 Filesystem,
1296 DevelopmentBearer { token_file: Text<1, 4096> },
1297}
1298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1299#[serde(rename_all = "snake_case", deny_unknown_fields)]
1300pub enum ListenerTransport {
1301 Tcp {
1302 address: Text<1, 4096>,
1303 tls: Tls,
1304 },
1305 Unix {
1306 path: Text<1, 4096>,
1307 mode: SocketMode,
1308 auth: UnixAuth,
1309 },
1310}
1311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1312#[serde(deny_unknown_fields)]
1313pub struct Listener {
1314 pub name: Handle,
1315 pub groups: List<RouteGroup, 1, 3>,
1316 pub transport: ListenerTransport,
1317 pub limits: ListenerLimits,
1318 pub redaction_policy: Maybe<Embedded<crate::redaction::RedactionPolicy>>,
1319}
1320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1321#[serde(deny_unknown_fields)]
1322pub struct RuntimeConfig {
1323 pub max_in_flight: Number<1, 65536>,
1324 pub max_queued: Number<0, 65536>,
1325 #[schemars(extend("maxProperties" = 1024))]
1326 pub per_backend_concurrency: BTreeMap<Handle, Number<1, 65536>>,
1327}
1328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1329#[serde(rename_all = "snake_case", deny_unknown_fields)]
1330pub enum SinkPolicy {
1331 FailDecision {
1332 timeout_ms: Number<1, 600000>,
1333 },
1334 Backpressure {
1335 max_pending: Number<1, 65536>,
1336 enqueue_timeout_ms: Number<1, 600000>,
1337 delivery_timeout_ms: Number<1, 600000>,
1338 },
1339 DropCounted {
1340 max_pending: Number<1, 65536>,
1341 delivery_timeout_ms: Number<1, 600000>,
1342 },
1343}
1344#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1345pub enum RemoteProtocol {
1346 #[serde(rename = "rustev.remote/1")]
1347 V1,
1348 #[serde(rename = "rustev.remote/2")]
1349 V2,
1350}
1351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1352#[serde(deny_unknown_fields)]
1353pub struct RemoteEndpoint {
1354 pub address: Text<1, 4096>,
1355 pub credentials_file: Maybe<Text<1, 4096>>,
1356 pub max_in_flight: Number<1, 65536>,
1357 pub queue_capacity: Number<1, 65536>,
1358 pub readiness_affecting: bool,
1359}
1360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1361#[serde(rename_all = "snake_case", deny_unknown_fields)]
1362pub enum BackendKind {
1363 Compiled {
1364 feature: Handle,
1365 },
1366 Remote {
1367 protocol: RemoteProtocol,
1368 endpoint: RemoteEndpoint,
1369 },
1370}
1371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1372#[serde(deny_unknown_fields)]
1373pub struct BackendBinding {
1374 pub expected_descriptor_id: DescriptorId,
1375 pub backend: BackendKind,
1376}
1377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1378#[serde(rename_all = "snake_case", deny_unknown_fields)]
1379pub enum Sink {
1380 Remote(RemoteEndpoint),
1381 Opentelemetry {
1382 endpoint: RemoteEndpoint,
1383 redaction: Embedded<crate::redaction::RedactionPolicy>,
1384 },
1385}
1386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1387#[serde(deny_unknown_fields)]
1388pub struct SigstoreTrust {
1389 pub trusted_root_file: Text<1, 4096>,
1390 pub certificate_identity: Text<1, 4096>,
1391 pub issuer: Text<1, 4096>,
1392}
1393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1394#[serde(deny_unknown_fields)]
1395pub struct SigningKeys {
1396 #[schemars(extend("maxProperties" = 1024))]
1397 pub ed25519: BTreeMap<Handle, Text<1, 4096>>,
1398 pub sigstore: List<SigstoreTrust, 0, 1024>,
1399}
1400service_document!(ServeConfig,ConfigSchema,"rustev.serve-config/1",SERVICE_LIMITS,{
1401 pub listeners:List<Listener,1,64>,pub max_plans:Number<1,4096>,pub runtime:RuntimeConfig,
1402 #[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,
1403 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>>,
1404 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>,
1405 });
1408impl Validate for ServeConfig {
1409 fn validate(&self) -> Result<(), String> {
1410 for len in [
1411 self.backend_bindings.len(),
1412 self.context_sources.len(),
1413 self.sinks.len(),
1414 self.resolver_roots.len(),
1415 self.runtime.per_backend_concurrency.len(),
1416 self.signing_keys.ed25519.len(),
1417 ] {
1418 if len > 1024 {
1419 return Err("configuration map exceeds 1024 entries".into());
1420 }
1421 }
1422 let mut names = std::collections::BTreeSet::new();
1423 for listener in self.listeners.as_slice() {
1424 if !names.insert(listener.name.as_str()) {
1425 return Err("duplicate listener name".into());
1426 }
1427 let groups = listener.groups.as_slice();
1428 for (i, group) in groups.iter().enumerate() {
1429 if groups[..i].contains(group) {
1430 return Err("duplicate route group".into());
1431 }
1432 }
1433 }
1434 Ok(())
1435 }
1436}
1437impl ServeConfig {
1438 pub fn configuration_digest(&self) -> Result<ContentDigest, CanonicalError> {
1442 let mut value = serde_json::to_value(self).map_err(|e| CanonicalError::Serialize {
1443 message: e.to_string(),
1444 })?;
1445 for listener in value["listeners"].as_array_mut().unwrap() {
1446 if let Some(tcp) = listener["transport"].get_mut("tcp") {
1447 tcp.as_object_mut().unwrap().remove("address");
1448 }
1449 if let Some(unix) = listener["transport"].get_mut("unix") {
1450 unix.as_object_mut().unwrap().remove("path");
1451 }
1452 }
1453 fn strip_addresses(value: &mut serde_json::Value) {
1454 match value {
1455 serde_json::Value::Object(map) => {
1456 map.remove("address");
1457 for value in map.values_mut() {
1458 strip_addresses(value);
1459 }
1460 }
1461 serde_json::Value::Array(values) => values.iter_mut().for_each(strip_addresses),
1462 _ => {}
1463 }
1464 }
1465 for key in ["backend_bindings", "context_sources", "sinks"] {
1466 strip_addresses(&mut value[key]);
1467 }
1468 for root in value["resolver_roots"]
1469 .as_object_mut()
1470 .unwrap()
1471 .values_mut()
1472 {
1473 *root = serde_json::Value::String("configured".into());
1474 }
1475 Ok(ContentDigest::from_digest(tagged_digest(
1476 Self::SCHEMA,
1477 &crate::canonical::canonical_value_bytes(&value)?,
1478 )))
1479 }
1480}