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