1use std::collections::{BTreeMap, BTreeSet};
24use std::fmt;
25
26use serde::{Deserialize, Serialize};
27
28use crate::bounded::{ParseError, ParseLimits, parse_bounded};
29use crate::canonical::{CanonicalError, canonical_bytes, tagged_digest};
30use crate::decimal::Decimal;
31use crate::definition::ProvenanceClass;
32use crate::descriptor::InputExcess;
33use crate::execution::FailureClass;
34use crate::ids::{ArtifactId, DynamicCapabilityId, DynamicRequestId, TemplateId};
35use crate::judgment::Derivation;
36use crate::limits::{DESCRIPTOR_V1, DYNAMIC_QUESTION_V1, RECORD_V1};
37use crate::remote::{CancelSupport, Disclosure, ServedIdentity, ServedTerms};
38use crate::run::{AttemptCost, AttemptEnd, Cancellation, CostModel, CostSummary, RemoteState};
39use crate::schema;
40use crate::scope::Scope;
41use crate::time::Timestamp;
42use crate::{Document, DocumentError, Identified};
43
44const KIB: usize = 1024;
45
46pub const MAX_QUESTION_BYTES: usize = 4 * KIB;
48pub const MAX_LABEL_BYTES: usize = 128;
50pub const MIN_LABELS: usize = 2;
52pub const MAX_CLASSIFY_LABELS: usize = 32;
54pub const MAX_RUBRIC_LEVELS: usize = 16;
56pub const PROPOSITION_LABELS: [&str; 2] = ["false", "true"];
58pub const MAX_CONTEXT_FIELDS: usize = 64;
60pub const MAX_CONTEXT_VALUE_BYTES: usize = 64 * KIB;
62pub const MAX_CANONICAL_BYTES: usize = 128 * KIB;
64pub const MAX_NAME_BYTES: usize = 128;
68pub const MAX_META_BYTES: usize = 256;
71pub const MAX_DETAIL_BYTES: usize = 256;
74pub const MAX_CAPABILITY_ENTRIES: usize = 64;
77
78#[derive(
81 Debug,
82 Clone,
83 Copy,
84 PartialEq,
85 Eq,
86 PartialOrd,
87 Ord,
88 Hash,
89 Serialize,
90 Deserialize,
91 schemars::JsonSchema,
92)]
93#[serde(rename_all = "snake_case")]
94pub enum DynamicOperation {
95 Proposition,
96 Classify,
97 Rubric,
98}
99
100impl DynamicOperation {
101 pub fn name(self) -> &'static str {
102 match self {
103 DynamicOperation::Proposition => "proposition",
104 DynamicOperation::Classify => "classify",
105 DynamicOperation::Rubric => "rubric",
106 }
107 }
108}
109
110#[derive(
115 Debug,
116 Clone,
117 Copy,
118 PartialEq,
119 Eq,
120 PartialOrd,
121 Ord,
122 Hash,
123 Serialize,
124 Deserialize,
125 schemars::JsonSchema,
126)]
127#[serde(rename_all = "snake_case")]
128pub enum DynamicOutputKind {
129 Label,
130 Distribution,
131 OrdinalDistribution,
132}
133
134impl DynamicOutputKind {
135 pub fn name(self) -> &'static str {
136 match self {
137 DynamicOutputKind::Label => "label",
138 DynamicOutputKind::Distribution => "distribution",
139 DynamicOutputKind::OrdinalDistribution => "ordinal_distribution",
140 }
141 }
142}
143
144#[derive(
146 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema,
147)]
148#[serde(deny_unknown_fields)]
149pub struct TaskFamily {
150 pub name: String,
151 pub version: String,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
157#[serde(rename_all = "snake_case", deny_unknown_fields)]
158pub enum ContextValue {
159 Bool(bool),
160 Integer(i64),
161 Decimal(Decimal),
162 Text(String),
163 Timestamp(Timestamp),
164}
165
166impl ContextValue {
167 pub fn value_bytes(&self) -> usize {
170 match self {
171 ContextValue::Bool(b) => {
172 if *b {
173 4
174 } else {
175 5
176 }
177 }
178 ContextValue::Integer(i) => i.to_string().len(),
179 ContextValue::Decimal(d) => d.to_canonical_string().len(),
180 ContextValue::Text(t) => t.len(),
181 ContextValue::Timestamp(t) => t.as_ms().to_string().len(),
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
189#[serde(deny_unknown_fields)]
190pub struct ContextField {
191 pub name: String,
192 pub value: ContextValue,
193 pub provenance: ProvenanceClass,
194 pub source: String,
196 pub derivation: Derivation,
197 pub source_revision: String,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
205#[serde(rename_all = "snake_case", deny_unknown_fields)]
206pub enum AbstentionPolicy {
207 Thresholds {
211 min_top_mass: Decimal,
212 min_margin: Decimal,
213 },
214 LabelOnly,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
220#[serde(deny_unknown_fields)]
221pub struct DynamicQuestion {
222 pub schema: String,
223 pub request_id: String,
226 pub scope: Scope,
228 pub operation: DynamicOperation,
229 pub task_family: TaskFamily,
230 pub question: String,
232 pub labels: Vec<String>,
235 pub context: Vec<ContextField>,
236 pub output: DynamicOutputKind,
237 pub deadline_ms: u64,
240 pub max_cost_units: u64,
242 pub artifact: ArtifactId,
244 pub privacy: Vec<String>,
248 pub abstention: AbstentionPolicy,
249}
250
251impl Document for DynamicQuestion {
252 const SCHEMA: &'static str = schema::DYNAMIC_QUESTION;
253 const LIMITS: ParseLimits = DYNAMIC_QUESTION_V1;
254 fn schema(&self) -> &str {
255 &self.schema
256 }
257
258 fn parse(bytes: &[u8]) -> Result<Self, DocumentError> {
261 parse_checked(bytes, DynamicQuestion::check_bounds)
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct BoundsError(pub String);
269
270impl fmt::Display for BoundsError {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 f.write_str(&self.0)
273 }
274}
275
276impl std::error::Error for BoundsError {}
277
278fn bounds<T>(msg: impl Into<String>) -> Result<T, BoundsError> {
279 Err(BoundsError(msg.into()))
280}
281
282fn short(s: &str) -> String {
283 s.chars().take(64).collect()
284}
285
286fn sized(what: &str, s: &str, min: usize, max: usize) -> Result<(), BoundsError> {
287 if s.len() < min || s.len() > max {
288 return bounds(format!(
289 "{what} is {} bytes; {min} to {max} are allowed",
290 s.len()
291 ));
292 }
293 Ok(())
294}
295
296fn unique<'a>(what: &str, items: impl Iterator<Item = &'a str>) -> Result<(), BoundsError> {
297 let mut seen = BTreeSet::new();
298 for s in items {
299 if !seen.insert(s) {
300 return bounds(format!("duplicate {what} {:?}", short(s)));
301 }
302 }
303 Ok(())
304}
305
306fn parse_checked<T: Document>(
307 bytes: &[u8],
308 check: fn(&T) -> Result<(), BoundsError>,
309) -> Result<T, DocumentError> {
310 let doc: T = parse_bounded(bytes, &T::LIMITS).map_err(DocumentError::Parse)?;
311 if doc.schema() != T::SCHEMA {
312 return Err(DocumentError::Schema {
313 expected: T::SCHEMA,
314 found: doc.schema().chars().take(80).collect(),
315 });
316 }
317 check(&doc).map_err(|e| DocumentError::Parse(ParseError::Schema { message: e.0 }))?;
318 Ok(doc)
319}
320
321impl DynamicQuestion {
322 pub fn check_bounds(&self) -> Result<(), BoundsError> {
327 sized("request_id", &self.request_id, 1, MAX_META_BYTES)?;
328 sized(
329 "task_family.name",
330 &self.task_family.name,
331 1,
332 MAX_NAME_BYTES,
333 )?;
334 sized(
335 "task_family.version",
336 &self.task_family.version,
337 1,
338 MAX_NAME_BYTES,
339 )?;
340 sized("question", &self.question, 1, MAX_QUESTION_BYTES)?;
341 let max_labels = MAX_CLASSIFY_LABELS.max(MAX_RUBRIC_LEVELS);
342 if self.labels.len() < MIN_LABELS || self.labels.len() > max_labels {
343 return bounds(format!(
344 "{} labels; {MIN_LABELS} to {max_labels} are allowed",
345 self.labels.len()
346 ));
347 }
348 for l in &self.labels {
349 sized("a label", l, 1, MAX_LABEL_BYTES)?;
350 }
351 unique("label", self.labels.iter().map(String::as_str))?;
352 if self.context.len() > MAX_CONTEXT_FIELDS {
353 return bounds(format!(
354 "{} context fields; at most {MAX_CONTEXT_FIELDS} are allowed",
355 self.context.len()
356 ));
357 }
358 let mut value_bytes = 0usize;
359 for c in &self.context {
360 sized("a context field name", &c.name, 1, MAX_NAME_BYTES)?;
361 sized("a context source", &c.source, 1, MAX_META_BYTES)?;
362 sized(
363 "a context source revision",
364 &c.source_revision,
365 1,
366 MAX_META_BYTES,
367 )?;
368 value_bytes = value_bytes.saturating_add(c.value.value_bytes());
369 }
370 unique(
371 "context field",
372 self.context.iter().map(|c| c.name.as_str()),
373 )?;
374 if value_bytes > MAX_CONTEXT_VALUE_BYTES {
375 return bounds(format!(
376 "context values are {value_bytes} bytes; at most {MAX_CONTEXT_VALUE_BYTES} are allowed"
377 ));
378 }
379 if self.privacy.len() > MAX_CAPABILITY_ENTRIES {
380 return bounds("too many privacy options");
381 }
382 for p in &self.privacy {
383 sized("a privacy option", p, 1, MAX_NAME_BYTES)?;
384 }
385 unique("privacy option", self.privacy.iter().map(String::as_str))?;
386 let canonical = self
387 .canonical()
388 .map_err(|e| BoundsError(format!("no canonical form: {e}")))?;
389 if canonical.len() > MAX_CANONICAL_BYTES {
390 return bounds(format!(
391 "the request is {} canonical bytes; at most {MAX_CANONICAL_BYTES} are allowed",
392 canonical.len()
393 ));
394 }
395 Ok(())
396 }
397
398 pub fn context_value_bytes(&self) -> usize {
400 self.context
401 .iter()
402 .map(|c| c.value.value_bytes())
403 .fold(0, usize::saturating_add)
404 }
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
411#[serde(deny_unknown_fields)]
412pub struct TemplateRef {
413 pub name: String,
414 pub version: String,
415 pub id: TemplateId,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
420#[serde(deny_unknown_fields)]
421pub struct DynamicOperationSupport {
422 pub operation: DynamicOperation,
423 pub outputs: Vec<DynamicOutputKind>,
424 pub max_labels: u64,
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
432#[serde(rename_all = "snake_case")]
433pub enum HiddenState {
434 None,
435 Present,
436}
437
438#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
442#[serde(deny_unknown_fields)]
443pub struct DynamicCapability {
444 pub schema: String,
445 pub backend_id: String,
446 pub artifact: ArtifactId,
447 pub operations: Vec<DynamicOperationSupport>,
448 pub max_question_bytes: u64,
449 pub max_context_bytes: u64,
450 pub max_context_fields: u64,
451 pub task_families: Vec<TaskFamily>,
453 pub template: TemplateRef,
454 pub preprocessing: String,
456 pub truncation: InputExcess,
459 pub served: ServedTerms,
460 pub privacy: Vec<String>,
462 pub cancellation: CancelSupport,
463 pub cost: CostModel,
465 pub hidden_state: HiddenState,
466}
467
468impl Document for DynamicCapability {
469 const SCHEMA: &'static str = schema::DYNAMIC_CAPABILITY;
470 const LIMITS: ParseLimits = DESCRIPTOR_V1;
471 fn schema(&self) -> &str {
472 &self.schema
473 }
474
475 fn parse(bytes: &[u8]) -> Result<Self, DocumentError> {
476 parse_checked(bytes, DynamicCapability::check_bounds)
477 }
478}
479
480impl Identified for DynamicCapability {
481 type Id = DynamicCapabilityId;
482 fn wrap(digest: String) -> DynamicCapabilityId {
483 DynamicCapabilityId::from_digest(digest)
484 }
485}
486
487impl DynamicCapability {
488 pub fn check_bounds(&self) -> Result<(), BoundsError> {
491 sized("backend_id", &self.backend_id, 1, MAX_NAME_BYTES)?;
492 sized("template.name", &self.template.name, 1, MAX_NAME_BYTES)?;
493 sized(
494 "template.version",
495 &self.template.version,
496 1,
497 MAX_NAME_BYTES,
498 )?;
499 sized("preprocessing", &self.preprocessing, 1, MAX_NAME_BYTES)?;
500 if self.operations.is_empty() || self.operations.len() > 3 {
501 return bounds("one to three operations are declared");
502 }
503 let mut ops = BTreeSet::new();
504 for o in &self.operations {
505 if !ops.insert(o.operation) {
506 return bounds(format!("duplicate operation {}", o.operation.name()));
507 }
508 if o.outputs.is_empty() || o.outputs.len() > 3 {
509 return bounds("one to three output kinds per operation");
510 }
511 let set: BTreeSet<_> = o.outputs.iter().collect();
512 if set.len() != o.outputs.len() {
513 return bounds("duplicate output kind");
514 }
515 }
516 if self.task_families.is_empty() || self.task_families.len() > MAX_CAPABILITY_ENTRIES {
517 return bounds("one to 64 task families are declared");
518 }
519 for t in &self.task_families {
520 sized("a task family name", &t.name, 1, MAX_NAME_BYTES)?;
521 sized("a task family version", &t.version, 1, MAX_NAME_BYTES)?;
522 }
523 let set: BTreeSet<_> = self.task_families.iter().collect();
524 if set.len() != self.task_families.len() {
525 return bounds("duplicate task family");
526 }
527 if self.privacy.len() > MAX_CAPABILITY_ENTRIES {
528 return bounds("too many privacy options");
529 }
530 for p in &self.privacy {
531 sized("a privacy option", p, 1, MAX_NAME_BYTES)?;
532 }
533 unique("privacy option", self.privacy.iter().map(String::as_str))?;
534 let pinned = self.served.disclosure == Disclosure::Pinned;
535 match &self.served.identity {
536 Some(i) if pinned => sized("served.identity", i, 1, MAX_META_BYTES)?,
537 None if !pinned => {}
538 _ => return bounds("served.identity is present exactly when disclosure is pinned"),
539 }
540 Ok(())
541 }
542
543 pub fn support(&self, operation: DynamicOperation) -> Option<&DynamicOperationSupport> {
545 self.operations.iter().find(|o| o.operation == operation)
546 }
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
555#[serde(deny_unknown_fields)]
556pub struct DynamicIdentity {
557 pub scope: Scope,
558 pub operation: DynamicOperation,
559 pub task_family: TaskFamily,
560 pub question: String,
561 pub labels: Vec<String>,
562 pub context: Vec<ContextField>,
563 pub output: DynamicOutputKind,
564 pub artifact: ArtifactId,
565 pub privacy: Vec<String>,
566 pub template: TemplateRef,
567 pub preprocessing: String,
568}
569
570impl DynamicIdentity {
571 pub fn of(q: &DynamicQuestion, capability: &DynamicCapability) -> DynamicIdentity {
574 DynamicIdentity {
575 scope: q.scope.clone(),
576 operation: q.operation,
577 task_family: q.task_family.clone(),
578 question: q.question.clone(),
579 labels: q.labels.clone(),
580 context: q.context.clone(),
581 output: q.output,
582 artifact: q.artifact.clone(),
583 privacy: q.privacy.clone(),
584 template: capability.template.clone(),
585 preprocessing: capability.preprocessing.clone(),
586 }
587 }
588
589 pub fn id(&self) -> Result<DynamicRequestId, CanonicalError> {
591 Ok(DynamicRequestId::from_digest(tagged_digest(
592 schema::DYNAMIC_QUESTION,
593 &canonical_bytes(self)?,
594 )))
595 }
596}
597
598#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
606#[serde(deny_unknown_fields)]
607pub struct DynamicProjection {
608 pub operation: DynamicOperation,
609 pub task_family: TaskFamily,
610 pub question: String,
611 pub labels: Vec<String>,
612 pub context: Vec<ProjectedField>,
613 pub output: DynamicOutputKind,
614}
615
616#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
618#[serde(deny_unknown_fields)]
619pub struct ProjectedField {
620 pub name: String,
621 pub value: ContextValue,
622}
623
624impl DynamicProjection {
625 pub fn of(q: &DynamicQuestion) -> DynamicProjection {
626 DynamicProjection {
627 operation: q.operation,
628 task_family: q.task_family.clone(),
629 question: q.question.clone(),
630 labels: q.labels.clone(),
631 context: q
632 .context
633 .iter()
634 .map(|c| ProjectedField {
635 name: c.name.clone(),
636 value: c.value.clone(),
637 })
638 .collect(),
639 output: q.output,
640 }
641 }
642}
643
644#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
649#[serde(rename_all = "snake_case", deny_unknown_fields)]
650pub enum DynamicRawOutput {
651 Label(String),
652 Distribution(BTreeMap<String, f64>),
654 Unrecognized {
655 shape: ResponseShape,
656 excerpt: String,
658 },
659}
660
661#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
663#[serde(rename_all = "snake_case")]
664pub enum ResponseShape {
665 ToolCall,
666 Instruction,
667 Schema,
668 UrlFetch,
669 Action,
670 FreeText,
671 NonFiniteMass,
674 Other,
675}
676
677impl ResponseShape {
678 pub fn name(self) -> &'static str {
679 match self {
680 ResponseShape::ToolCall => "tool_call",
681 ResponseShape::Instruction => "instruction",
682 ResponseShape::Schema => "schema",
683 ResponseShape::UrlFetch => "url_fetch",
684 ResponseShape::Action => "action",
685 ResponseShape::FreeText => "free_text",
686 ResponseShape::NonFiniteMass => "non_finite_mass",
687 ResponseShape::Other => "other",
688 }
689 }
690}
691
692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
697#[serde(deny_unknown_fields)]
698pub struct DynamicOutcome {
699 pub schema: String,
700 pub request_id: String,
701 pub request: Option<DynamicRequestId>,
704 pub scope: Scope,
705 pub task_family: TaskFamily,
706 pub operation: DynamicOperation,
707 pub output: DynamicOutputKind,
708 pub backend_id: String,
709 pub requested_artifact: ArtifactId,
711 pub served: ServedIdentity,
713 pub capability: Option<DynamicCapabilityId>,
715 pub template: Option<TemplateRef>,
716 pub privacy: Vec<String>,
718 pub attempt: Option<DynamicAttempt>,
720 pub timing: DynamicTiming,
721 pub cost: CostSummary,
723 pub raw: DynamicRaw,
724 pub validation: DynamicValidation,
725 pub abstention: AbstentionDecision,
726 pub result: DynamicResult,
727}
728
729impl Document for DynamicOutcome {
730 const SCHEMA: &'static str = schema::DYNAMIC_OUTCOME;
731 const LIMITS: ParseLimits = RECORD_V1;
732 fn schema(&self) -> &str {
733 &self.schema
734 }
735}
736
737#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
739#[serde(deny_unknown_fields)]
740pub struct DynamicAttempt {
741 pub attempt_id: String,
742 pub dispatched_ms: u64,
743 pub ended_ms: u64,
744 pub end: AttemptEnd,
745 pub cancellation: Cancellation,
746 pub remote: RemoteState,
747 pub cost: AttemptCost,
748}
749
750#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
752#[serde(deny_unknown_fields)]
753pub struct DynamicTiming {
754 pub elapsed_ms: u64,
755 pub deadline_ms: u64,
756 pub deadline_expired: bool,
757}
758
759#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
761#[serde(rename_all = "snake_case", deny_unknown_fields)]
762pub enum DynamicRaw {
763 NotDispatched,
765 Output(DynamicRawOutput),
767 Failed { class: FailureClass, detail: String },
769 NoAnswer,
771}
772
773#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
776#[serde(rename_all = "snake_case", deny_unknown_fields)]
777pub enum DynamicValidation {
778 NotRun,
779 Valid,
780 Invalid { detail: String },
781}
782
783#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
785#[serde(rename_all = "snake_case")]
786pub enum AbstentionRule {
787 MinTopMass,
788 MinMargin,
789}
790
791impl AbstentionRule {
792 pub fn name(self) -> &'static str {
793 match self {
794 AbstentionRule::MinTopMass => "min_top_mass",
795 AbstentionRule::MinMargin => "min_margin",
796 }
797 }
798}
799
800#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
803#[serde(rename_all = "snake_case", deny_unknown_fields)]
804pub enum AbstentionDecision {
805 NotApplied,
806 LabelOnly,
808 Passed {
809 top_mass: f64,
810 margin: f64,
811 },
812 Abstained {
813 rule: AbstentionRule,
814 top_mass: f64,
815 margin: f64,
816 },
817}
818
819#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
821#[serde(rename_all = "snake_case", deny_unknown_fields)]
822pub enum DynamicResult {
823 Proposal(DynamicProposal),
824 Unresolved(DynamicUnresolved),
825}
826
827#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
830#[serde(deny_unknown_fields)]
831pub struct DynamicProposal {
832 pub answer: DynamicAnswer,
833 pub derivation: Derivation,
834 pub lineage: DynamicLineage,
835}
836
837#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
839#[serde(deny_unknown_fields)]
840pub struct LabelMass {
841 pub label: String,
842 pub mass: f64,
843}
844
845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
849#[serde(rename_all = "snake_case", deny_unknown_fields)]
850pub enum DynamicAnswer {
851 Label {
852 label: String,
853 },
854 UncalibratedDistribution {
856 label: String,
857 masses: Vec<LabelMass>,
858 },
859 OrdinalDistribution {
861 level: String,
862 masses: Vec<LabelMass>,
863 },
864}
865
866#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
868#[serde(deny_unknown_fields)]
869pub struct DynamicLineage {
870 pub request: DynamicRequestId,
871 pub context: Vec<String>,
873 pub backend_id: String,
874 pub artifact: ArtifactId,
875 pub attempt_id: String,
876 pub exact_processing: Vec<String>,
879}
880
881#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
884#[serde(rename_all = "snake_case", deny_unknown_fields)]
885pub enum DynamicUnresolved {
886 Refused {
888 refusal: DynamicRefusal,
889 },
890 BackendFailed {
892 class: FailureClass,
893 detail: String,
894 },
895 InvalidOutput {
898 detail: String,
899 },
900 ServedIdentityMismatch {
902 pinned: String,
903 served: Option<String>,
904 },
905 Abstained {
906 rule: AbstentionRule,
907 },
908 DeadlineExceeded,
909 Cancelled,
910}
911
912impl DynamicUnresolved {
913 pub fn name(&self) -> &'static str {
915 match self {
916 DynamicUnresolved::Refused { .. } => "refused",
917 DynamicUnresolved::BackendFailed { .. } => "backend_failed",
918 DynamicUnresolved::InvalidOutput { .. } => "invalid_output",
919 DynamicUnresolved::ServedIdentityMismatch { .. } => "served_identity_mismatch",
920 DynamicUnresolved::Abstained { .. } => "abstained",
921 DynamicUnresolved::DeadlineExceeded => "deadline_exceeded",
922 DynamicUnresolved::Cancelled => "cancelled",
923 }
924 }
925}
926
927#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
929#[serde(rename_all = "snake_case", deny_unknown_fields)]
930pub enum DynamicRefusal {
931 NoDynamicCapability,
934 CapabilityInvalid {
936 detail: String,
937 },
938 ArtifactMismatch {
941 declared: ArtifactId,
942 },
943 OperationUnsupported {
944 operation: DynamicOperation,
945 output: DynamicOutputKind,
946 },
947 TaskFamilyUndeclared,
948 ExceedsDeclaredLimit {
950 limit: String,
951 declared: u64,
952 requested: u64,
953 },
954 PrivacyUnsupported {
955 option: String,
956 },
957 CostUnenforceable {
959 model: CostModel,
960 },
961 CostUnknown,
963 CostAboveCap {
965 bound: u64,
966 cap: u64,
967 },
968 BudgetExhausted,
970}