Skip to main content

rustev_contract/
output.rs

1//! Supplied semantic backend outputs, `rustev.backend-output/1`.
2//! Validated against the plan before any policy reads them (spec 002, 3.10.2).
3
4use std::collections::BTreeMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::ids::ArtifactId;
9use crate::limits::BACKEND_OUTPUT_V1;
10use crate::schema;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
13#[serde(deny_unknown_fields)]
14pub struct BackendOutputDoc {
15    pub schema: String,
16    pub step: String,
17    pub instance: Vec<String>,
18    /// The artifact that produced it; must be the bound artifact.
19    pub artifact: ArtifactId,
20    pub output: RawOutput,
21}
22
23crate::document::document!(BackendOutputDoc, schema::BACKEND_OUTPUT, BACKEND_OUTPUT_V1);
24
25/// What a backend returned, before validation.
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
27#[serde(rename_all = "snake_case", deny_unknown_fields)]
28pub enum RawOutput {
29    Label(String),
30    Distribution(BTreeMap<String, f64>),
31    Logits(BTreeMap<String, f64>),
32    Scores(BTreeMap<String, f64>),
33    Screen {
34        blocked: bool,
35        categories: Vec<String>,
36        units: ScreenUnits,
37    },
38    TextRecord(BTreeMap<String, TextValue>),
39    TypedRecord {
40        fields: BTreeMap<String, serde_json::Value>,
41        logprobs: Vec<FieldLogprobs>,
42        #[serde(deserialize_with = "provider_reported")]
43        provider_reported: BTreeMap<String, String>,
44    },
45}
46
47/// A screener's reported text units, distinct from runtime computed charges.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
49#[serde(rename_all = "snake_case", deny_unknown_fields)]
50pub enum ScreenUnits {
51    Reported { n: u64 },
52    NotReported,
53}
54
55/// A generated field before plan-dependent validation (spec 037, 3.7).
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
57#[serde(rename_all = "snake_case", deny_unknown_fields)]
58pub enum TextValue {
59    Text(String),
60    List(Vec<String>),
61}
62
63/// Backend option log-probabilities; field eligibility is checked by the core.
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
65#[serde(deny_unknown_fields)]
66pub struct FieldLogprobs {
67    pub field: String,
68    pub item: LogprobItem,
69    #[serde(deserialize_with = "finite_logprobs")]
70    pub values: BTreeMap<String, f64>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
74#[serde(rename_all = "snake_case")]
75pub enum LogprobItem {
76    None,
77    // Per-variant `untagged` (serde 1.0.181+) makes `item` either the string `none` or a bare index, as spec 038 3.10.3 requires.
78    #[serde(untagged)]
79    Index(u64),
80}
81
82fn finite_logprobs<'de, D: serde::Deserializer<'de>>(
83    d: D,
84) -> Result<BTreeMap<String, f64>, D::Error> {
85    let v = BTreeMap::<String, f64>::deserialize(d)?;
86    if v.values().any(|n| !n.is_finite()) {
87        return Err(serde::de::Error::custom("non-finite extraction logprob"));
88    }
89    Ok(v)
90}
91
92fn provider_reported<'de, D: serde::Deserializer<'de>>(
93    d: D,
94) -> Result<BTreeMap<String, String>, D::Error> {
95    let v = BTreeMap::<String, String>::deserialize(d)?;
96    if v.values().any(|s| s.len() > 256) {
97        return Err(serde::de::Error::custom(
98            "provider_reported exceeds 256 bytes",
99        ));
100    }
101    Ok(v)
102}