Skip to main content

router_protocol/
catalog_proof.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3//! Content-free contracts for device-local catalog discovery and qualification.
4
5use crate::{CatalogSourceKind, PhaseId, ProviderService, TransportKind};
6use serde::{Deserialize, Serialize};
7
8pub const CATALOG_PROOF_SCHEMA_VERSION: &str = "1.0.0";
9pub const LIVE_CATALOG_DISCOVERY_CONFIRMATION: &str = "RUN_STEADYMUX_LIVE_CATALOG_DISCOVERY";
10pub const LIVE_MODEL_QUALIFICATION_CONFIRMATION: &str = "RUN_STEADYMUX_LIVE_MODEL_QUALIFICATION";
11pub const MAX_CATALOG_PROOF_PHASES: usize = 64;
12pub const MAX_CATALOG_PROOF_MODELS: usize = 512;
13pub const MAX_CATALOG_QUALIFICATION_TARGETS: usize = 64;
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17pub struct CatalogDiscoveryPreviewRequest {
18    /// Stable device-local authority identity selected for the later
19    /// authenticated apply. This is not an authority digest and does not
20    /// imply that authority has been provisioned.
21    pub configuration_authority_id: String,
22    /// Digest of the immutable pre-authority candidate configuration.
23    pub pre_authority_configuration_sha256: String,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct CatalogDiscoveryApplyRequest {
29    pub preview: CatalogDiscoveryPreview,
30    pub expected_preview_sha256: String,
31    pub confirmation: String,
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct CatalogQualificationPreviewRequest {
37    pub configuration_authority_id: String,
38    pub pre_authority_configuration_sha256: String,
39    pub discovery_receipt_sha256: String,
40    pub targets: Vec<CatalogQualificationTarget>,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct CatalogQualificationApplyRequest {
46    pub preview: CatalogQualificationPreview,
47    pub expected_preview_sha256: String,
48    pub confirmation: String,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(deny_unknown_fields)]
53pub struct CatalogDiscoveryPlanEntry {
54    pub phase_id: PhaseId,
55    pub provider: ProviderService,
56    pub transport: TransportKind,
57    pub source: CatalogSourceKind,
58    pub configured_model_ids: Vec<String>,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(deny_unknown_fields)]
63pub struct CatalogDiscoveryPreview {
64    pub schema_version: String,
65    pub configuration_authority_id: String,
66    pub pre_authority_configuration_sha256: String,
67    pub phases: Vec<CatalogDiscoveryPlanEntry>,
68    pub maximum_provider_requests: u32,
69    pub network_effect: bool,
70    pub inference_effect: bool,
71    pub credential_read_effect: bool,
72    pub preview_sha256: String,
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum CatalogDiscoveryOutcome {
78    Accepted,
79    RetainedLastKnownGood,
80    RejectedNoUsableLastKnownGood,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct CatalogCapabilityClassification {
86    pub context_tokens: u64,
87    pub modalities: Vec<String>,
88    pub tool_support: bool,
89    pub reasoning_modes: Vec<String>,
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(deny_unknown_fields)]
94pub struct CatalogDiscoveredModelReceipt {
95    pub provider_model_id: String,
96    pub model_sha256: String,
97    pub capabilities: CatalogCapabilityClassification,
98    pub qualification_status: CatalogQualificationStatus,
99    pub exclusion_reason: Option<String>,
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(deny_unknown_fields)]
104pub struct CatalogPhaseDiscoveryReceipt {
105    pub phase_id: PhaseId,
106    pub provider: ProviderService,
107    pub source: CatalogSourceKind,
108    pub observed_at_epoch_seconds: u64,
109    pub outcome: CatalogDiscoveryOutcome,
110    pub catalog_version: Option<String>,
111    pub catalog_sha256: Option<String>,
112    pub models: Vec<CatalogDiscoveredModelReceipt>,
113    pub exclusion_reason: Option<String>,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(deny_unknown_fields)]
118pub struct CatalogDiscoveryReceipt {
119    pub schema_version: String,
120    pub configuration_authority_id: String,
121    pub pre_authority_configuration_sha256: String,
122    pub completed_at_epoch_seconds: u64,
123    pub phases: Vec<CatalogPhaseDiscoveryReceipt>,
124    pub receipt_sha256: String,
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum CatalogProofRole {
130    Routine,
131    Standard,
132    Complex,
133    Vision,
134    LongContext,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(deny_unknown_fields)]
139pub struct CatalogQualificationTarget {
140    pub phase_id: PhaseId,
141    pub provider: ProviderService,
142    /// Exact configured profile selected for the one-shot canary. Model IDs
143    /// are not route identities because one model may exist under multiple
144    /// accounts or transports.
145    pub profile_id: String,
146    pub provider_model_id: String,
147    pub roles: Vec<CatalogProofRole>,
148}
149
150#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(deny_unknown_fields)]
152pub struct CatalogQualificationPreview {
153    pub schema_version: String,
154    pub configuration_authority_id: String,
155    pub pre_authority_configuration_sha256: String,
156    pub discovery_receipt_sha256: String,
157    pub targets: Vec<CatalogQualificationTarget>,
158    pub maximum_inference_requests: u32,
159    pub maximum_openrouter_micro_usd: u64,
160    pub network_effect: bool,
161    pub inference_effect: bool,
162    pub credential_read_effect: bool,
163    pub preview_sha256: String,
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum CatalogQualificationStatus {
169    DiscoveredUnqualified,
170    Qualifying,
171    Qualified,
172    Rejected,
173}
174
175#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(deny_unknown_fields)]
177pub struct CatalogModelQualificationReceipt {
178    pub target: CatalogQualificationTarget,
179    pub status: CatalogQualificationStatus,
180    pub catalog_version: String,
181    pub catalog_sha256: String,
182    pub model_sha256: String,
183    pub policy_version: Option<String>,
184    pub policy_sha256: Option<String>,
185    pub conformance_sha256: Option<String>,
186    pub evidence_ref: Option<String>,
187    pub exclusion_reason: Option<String>,
188}
189
190#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
191#[serde(deny_unknown_fields)]
192pub struct CatalogQualificationReceipt {
193    pub schema_version: String,
194    pub configuration_authority_id: String,
195    pub pre_authority_configuration_sha256: String,
196    pub discovery_receipt_sha256: String,
197    pub completed_at_epoch_seconds: u64,
198    pub results: Vec<CatalogModelQualificationReceipt>,
199    /// Exact deterministic artifacts produced from the accepted discovery
200    /// snapshots and completed live qualification attempts. Consumers must
201    /// serialize `content` with the repository's canonical JSON encoder and
202    /// verify `content_sha256` before writing or provisioning.
203    pub artifacts: Vec<CatalogQualificationArtifact>,
204    pub receipt_sha256: String,
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum CatalogQualificationArtifactKind {
210    CatalogBundle,
211    PolicyPack,
212    ConformanceSuite,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(deny_unknown_fields)]
217pub struct CatalogQualificationArtifact {
218    pub kind: CatalogQualificationArtifactKind,
219    pub file_name: String,
220    pub provider: Option<ProviderService>,
221    pub phase_id: Option<PhaseId>,
222    pub content_sha256: String,
223    pub content: serde_json::Value,
224}
225
226#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(deny_unknown_fields)]
228pub struct CatalogProofArtifact {
229    pub kind: String,
230    pub provider: Option<ProviderService>,
231    pub content_sha256: String,
232    pub byte_length: u64,
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(deny_unknown_fields)]
237pub struct CatalogProofArtifactManifest {
238    pub schema_version: String,
239    pub configuration_authority_id: String,
240    pub pre_authority_configuration_sha256: String,
241    pub discovery_receipt_sha256: String,
242    pub qualification_receipt_sha256: Option<String>,
243    pub artifacts: Vec<CatalogProofArtifact>,
244    pub manifest_sha256: String,
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn apply_contracts_deny_unknown_fields_and_keep_confirmation_out_of_receipts() {
253        let value = serde_json::json!({
254            "preview": {
255                "schema_version": CATALOG_PROOF_SCHEMA_VERSION,
256                "configuration_authority_id": "configuration.device.alpha",
257                "pre_authority_configuration_sha256": "a".repeat(64),
258                "phases": [],
259                "maximum_provider_requests": 0,
260                "network_effect": true,
261                "inference_effect": false,
262                "credential_read_effect": true,
263                "preview_sha256": "b".repeat(64)
264            },
265            "expected_preview_sha256": "a".repeat(64),
266            "confirmation": LIVE_CATALOG_DISCOVERY_CONFIRMATION,
267            "secret": "forbidden"
268        });
269        assert!(serde_json::from_value::<CatalogDiscoveryApplyRequest>(value).is_err());
270        let receipt = CatalogDiscoveryReceipt {
271            schema_version: CATALOG_PROOF_SCHEMA_VERSION.to_owned(),
272            configuration_authority_id: "configuration.device.alpha".to_owned(),
273            pre_authority_configuration_sha256: "a".repeat(64),
274            completed_at_epoch_seconds: 1,
275            phases: Vec::new(),
276            receipt_sha256: "b".repeat(64),
277        };
278        let encoded = serde_json::to_string(&receipt).expect("receipt JSON");
279        assert!(!encoded.contains("confirmation"));
280        assert!(!encoded.contains("secret"));
281    }
282}