Skip to main content

rustev_contract/
serve.rs

1//! Pure service documents and operation metadata (spec 042).
2//!
3//! Engineering bounds: names use the existing 256-byte Handle; file and
4//! endpoint references use 4096 bytes; configuration collections use 1024
5//! entries, listeners 64. Queues and concurrency without a specified range
6//! use 0 or 1 through 65536; timeouts use 1 through 600000 milliseconds.
7//! These values describe data, never permission or transport behavior.
8use 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;
19/// Envelope bounds leave room for nested documents' own scanners.
20pub 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/// Inclusive, validated integer range. No implicit defaults.
43#[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        // `u64::MAX` is not exactly representable as a binary64 JSON number,
69        // so an unbounded-above count states only its minimum; the parser
70        // still enforces the `u64` range.
71        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/// UTF-8 byte bounds, applied after JSON unescaping.
79#[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/// Explicit absence for identity-bearing fields.
140#[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/// An embedded document, checked by its own bounded parser. Serialization
148/// preserves its original bytes, including evidence number representations.
149#[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}
190/// An embedded document is described by name, not inlined: its own schema
191/// file describes it, and its own bounded parser checks it. A schema is never
192/// stricter than the parser (spec 027 3.2.2), so the looser object is exact
193/// enough, and it keeps each envelope from copying the run record, judgment
194/// and bundle schemas into every service document.
195impl<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}
222/// Fixed schema marker fields also reject wrong schemas in nested serde reads.
223macro_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;
241/// Express cross-field rules where JSON Schema can do so. The parser remains
242/// authoritative, including relationships between counts and array lengths.
243pub(crate) fn document_shape(schema: &mut Schema) {
244    let object = schema.ensure_object();
245    // Optional members may be omitted but never explicitly null.
246    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                // Drop the `null` branch serde's `Option` adds, so the schema
263                // and the generated declarations state "absent or a value",
264                // as the parser does, rather than allowing `null`.
265                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    // Exactly one of `snapshot` and `context` is the parser's rule. A
301    // top-level `oneOf` would state it, but the declaration generator turns
302    // such a schema into an open TypeScript type, so it is stated in the
303    // description instead; the schema stays looser than the parser, never
304    // stricter (spec 027 3.2.2).
305    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
331/// Sort envelope keys while retaining the runtime's evidence bytes verbatim.
332/// Other values use the existing record canonicalizer, without changing it.
333pub(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    // Extension point: spec 036 (mid-plan acquisition).
404});
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        // 042 3.4.3: the judgment is absent only when cancelled. Every
452        // `evidence_not_delivered` decision carries its run record (checked
453        // above; a cancelled run still has one), but its judgment may be
454        // present (judged, then delivery failed) or absent (cancelled, then
455        // delivery failed), so neither is refused there.
456        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 {}
528/// Query/header bounds, shared with transport generators.
529pub 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/// Only false is representable. A refusal never claims a decision ran.
623#[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/// An oversized compiler refusal is represented by its digest and byte length.
636#[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/// Surface-owned compiler output, kept byte-for-byte. The contract does not
646/// duplicate the compiler's semantic refusal taxonomy. Its outer renderer
647/// fields are checked here, and the entire document is limited to 2 KiB.
648#[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    // Category numbers are fixed by the specs: 18 and 19 are spec 035's
680    // screening categories, 20 and 21 are spec 036 3.9.3's, and 22 to 24
681    // are spec 037 3.5's. This is a wire range check only; nothing here
682    // converts the number to a `Category`. Spec 039 adds 28, leaving
683    // the absent spec 038 categories 25 to 27 rejected.
684    #[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    // Screener shortfalls (spec 035, 3.5.4), as rustev-surface emits them.
742    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    // No retry_after_ms until a spec gives the runtime a basis for one.
761});
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    // Extension point: spec 036 (skipped by tiered widening).
778}
779/// Spec 037 field events are described now; the service must advertise
780/// their absence until generation is implemented. Values remain provisional.
781#[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/// Resolver references are prefix:relative/path, never URLs or absolute paths.
850/// Filesystem containment and scope are checked by the host resolver.
851#[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/// A resolver binding for a typed embedded replay dependency.
902#[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}
1005/// Single operation source for later deterministic HTTP and gRPC generators.
1006pub 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/// The embedded remote-context item wire shape. The remote lane can reuse
1242/// this payload; it is not a second remote protocol implementation.
1243#[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    // Extension point: spec 034 (tenant profiles and profile signing mode).
1424    // Extension point: spec 036 (acquisition source configuration).
1425});
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    /// Address-free identity. Secret file contents are never document members.
1457    /// Socket paths, endpoint addresses and resolver directory locations are
1458    /// deployment locations, not configuration identity inputs.
1459    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}