systemprompt_models/feedback/
receipts.rs1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use systemprompt_identifiers::{
10 ConsumerInstallationId, DeviceId, InstallationReceiptId, ManagedResourceId, NativeSessionId,
11 PublicationId, ResourceRevisionId, UserId,
12};
13
14use super::{ContentDigest, EvaluatorClient, FeedbackContractError, validate_relative_path};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct AuthenticatedConsumerDevice {
18 pub consumer_id: UserId,
19 pub device_id: DeviceId,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
23#[serde(rename_all = "snake_case")]
24pub enum ReadbackStatus {
25 Verified,
26 Mismatch,
27 Unavailable,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
31#[serde(deny_unknown_fields)]
32pub struct FileReadback {
33 pub revision_id: ResourceRevisionId,
34 pub path: String,
35 pub digest: ContentDigest,
36 pub bytes: u64,
37 pub executable: bool,
38 pub content_check: ReadbackStatus,
39 pub mode_check: ReadbackStatus,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
43#[serde(deny_unknown_fields)]
44pub struct RuntimeFileReadback {
45 pub path: String,
46 pub digest: ContentDigest,
47 pub bytes: u64,
48 pub executable: bool,
49 pub content_check: ReadbackStatus,
50 pub mode_check: ReadbackStatus,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
54pub struct InstallationPlanFile {
55 pub path: String,
56 pub bytes: Vec<u8>,
57 pub executable: bool,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
61pub struct ConsumerInstallationPlan {
62 pub publication_id: PublicationId,
63 pub resource_id: ManagedResourceId,
64 pub revision_id: ResourceRevisionId,
65 pub generation: i64,
66 pub bundle_digest: ContentDigest,
67 pub host: EvaluatorClient,
68 pub canonical_files: Vec<FileReadback>,
69 pub runtime_files: Vec<InstallationPlanFile>,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
73#[serde(deny_unknown_fields)]
74pub struct ConsumerReceiptRequest {
75 pub installation_id: ConsumerInstallationId,
76 pub publication_id: PublicationId,
77 pub resource_id: ManagedResourceId,
78 pub revision_id: ResourceRevisionId,
79 pub generation: i64,
80 pub bundle_digest: ContentDigest,
81 pub host: EvaluatorClient,
82 pub observed_at: DateTime<Utc>,
83 pub files: Vec<FileReadback>,
84 #[serde(default)]
85 pub runtime_files: Vec<RuntimeFileReadback>,
86}
87
88impl ConsumerReceiptRequest {
89 pub fn validate(&self) -> Result<(), FeedbackContractError> {
90 if self.generation < 1 || self.files.is_empty() || self.files.len() > 4096 {
91 return Err(FeedbackContractError::Bounds);
92 }
93 let mut paths = std::collections::BTreeSet::new();
94 for file in &self.files {
95 validate_relative_path(&file.path)?;
96 if !paths.insert((&file.revision_id, &file.path)) {
97 return Err(FeedbackContractError::IncompleteManifest);
98 }
99 }
100 let mut runtime_paths = std::collections::BTreeSet::new();
101 if self.runtime_files.len() > 8192 {
102 return Err(FeedbackContractError::Bounds);
103 }
104 for file in &self.runtime_files {
105 validate_relative_path(&file.path)?;
106 if !runtime_paths.insert(&file.path) {
107 return Err(FeedbackContractError::IncompleteManifest);
108 }
109 }
110 Ok(())
111 }
112
113 pub fn fully_verified(&self) -> bool {
114 self.validate().is_ok()
115 && !self.runtime_files.is_empty()
116 && self.runtime_files.iter().all(|file| {
117 file.content_check == ReadbackStatus::Verified
118 && file.mode_check == ReadbackStatus::Verified
119 })
120 && self.files.iter().all(|file| {
121 file.content_check == ReadbackStatus::Verified
122 && file.mode_check == ReadbackStatus::Verified
123 })
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
128#[serde(deny_unknown_fields)]
129pub struct SessionBindingRequest {
130 pub receipt_id: InstallationReceiptId,
131 pub host: EvaluatorClient,
132 pub session_id: NativeSessionId,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
136#[serde(rename_all = "snake_case")]
137pub enum ReceiptAcknowledgement {
138 Accepted,
139 IdenticalRetry,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
143pub struct ConsumerReceiptResponse {
144 pub receipt_id: InstallationReceiptId,
145 pub acknowledgement: ReceiptAcknowledgement,
146 pub acknowledged_at: DateTime<Utc>,
147 pub fully_verified: bool,
148}