1use super::*;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9pub struct BoundaryCompilerProfileV1 {
10 pub profile_id: ArtifactId,
11 pub language: String,
12 pub dialect: String,
13 pub schema_identity: String,
14 pub duplicate_key_policy: String,
15 pub unknown_field_policy: String,
16 pub canonicalization_profile: String,
17 pub repair_policy: String,
18 pub treatment_integrity_required: bool,
19 pub max_bytes: u64,
20 pub max_depth: u32,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub reason_codes: Vec<String>,
23}
24
25impl BoundaryCompilerProfileV1 {
26 pub fn strict_json(schema_identity: impl Into<String>) -> Self {
27 let schema_identity = schema_identity.into();
28 Self {
29 profile_id: generated_artifact_id_from_material(
30 "boundary-profile",
31 &format!("strict-json|{schema_identity}"),
32 ),
33 language: "json".into(),
34 dialect: "json-schema-2020-12-subset".into(),
35 schema_identity,
36 duplicate_key_policy: "reject".into(),
37 unknown_field_policy: "schema-controlled".into(),
38 canonicalization_profile: "stack-ids-json-c14n-v1".into(),
39 repair_policy: "receipt-required-no-treatment-change".into(),
40 treatment_integrity_required: true,
41 max_bytes: 1_048_576,
42 max_depth: 64,
43 reason_codes: vec!["boundary-profile-strict-json".into()],
44 }
45 }
46
47 pub fn rejects_duplicate_keys(&self) -> bool {
48 self.duplicate_key_policy == "reject"
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
53pub struct TreatmentIntegrityReceiptV1 {
54 pub receipt_id: ArtifactId,
55 pub profile_id: ArtifactId,
56 pub before_digest: DisplayDigestV1,
57 pub after_digest: DisplayDigestV1,
58 pub treatment_critical_fields: Vec<String>,
59 pub treatment_changed: bool,
60 pub hard_failed: bool,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
62 pub reason_codes: Vec<String>,
63 pub recorded_at: DateTime<Utc>,
64}
65
66impl TreatmentIntegrityReceiptV1 {
67 pub fn compare(
68 profile: &BoundaryCompilerProfileV1,
69 before: &serde_json::Value,
70 after: &serde_json::Value,
71 treatment_critical_fields: Vec<String>,
72 ) -> Self {
73 let treatment_changed = treatment_critical_fields
74 .iter()
75 .any(|field| before.pointer(field) != after.pointer(field));
76 let hard_failed = treatment_changed && profile.treatment_integrity_required;
77 let material = serde_json::json!({
78 "profile": profile.profile_id,
79 "before": before,
80 "after": after,
81 "fields": treatment_critical_fields,
82 });
83 Self {
84 receipt_id: generated_artifact_id_from_material(
85 "treatment-integrity",
86 &non_authoritative_json_display_digest(&material),
87 ),
88 profile_id: profile.profile_id.clone(),
89 before_digest: DisplayDigestV1::for_json_value(before),
90 after_digest: DisplayDigestV1::for_json_value(after),
91 treatment_critical_fields,
92 treatment_changed,
93 hard_failed,
94 reason_codes: if hard_failed {
95 vec!["treatment-integrity-hard-fail".into()]
96 } else if treatment_changed {
97 vec!["treatment-integrity-degraded".into()]
98 } else {
99 vec!["treatment-integrity-preserved".into()]
100 },
101 recorded_at: Utc::now(),
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
107pub struct BoundaryRepairReceiptV1 {
108 pub receipt_id: ArtifactId,
109 pub profile_id: ArtifactId,
110 pub repair_kind: String,
111 pub before_digest: DisplayDigestV1,
112 pub after_digest: DisplayDigestV1,
113 pub treatment_integrity_receipt_id: ArtifactId,
114 pub accepted: bool,
115 #[serde(default, skip_serializing_if = "Vec::is_empty")]
116 pub reason_codes: Vec<String>,
117 pub recorded_at: DateTime<Utc>,
118}
119
120impl BoundaryRepairReceiptV1 {
121 pub fn new(
122 profile: &BoundaryCompilerProfileV1,
123 repair_kind: impl Into<String>,
124 before: &serde_json::Value,
125 after: &serde_json::Value,
126 treatment: &TreatmentIntegrityReceiptV1,
127 ) -> Self {
128 let repair_kind = repair_kind.into();
129 let accepted = !treatment.hard_failed;
130 let material = format!(
131 "{}|{}|{}",
132 profile.profile_id.0, repair_kind, treatment.receipt_id.0
133 );
134 Self {
135 receipt_id: generated_artifact_id_from_material("boundary-repair", &material),
136 profile_id: profile.profile_id.clone(),
137 repair_kind,
138 before_digest: DisplayDigestV1::for_json_value(before),
139 after_digest: DisplayDigestV1::for_json_value(after),
140 treatment_integrity_receipt_id: treatment.receipt_id.clone(),
141 accepted,
142 reason_codes: if accepted {
143 vec!["boundary-repair-receipted".into()]
144 } else {
145 vec!["boundary-repair-rejected-treatment-change".into()]
146 },
147 recorded_at: Utc::now(),
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
153pub struct BoundaryCompileReceiptV1 {
154 pub receipt_id: ArtifactId,
155 pub profile_id: ArtifactId,
156 pub input_digest: DisplayDigestV1,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub output_digest: Option<DisplayDigestV1>,
159 pub accepted: bool,
160 pub duplicate_keys_rejected: bool,
161 pub schema_validated: bool,
162 pub canonicalized: bool,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub repair_receipt_id: Option<ArtifactId>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub treatment_integrity_receipt_id: Option<ArtifactId>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 pub reason_codes: Vec<String>,
169 pub recorded_at: DateTime<Utc>,
170}
171
172impl BoundaryCompileReceiptV1 {
173 pub fn accepted(
174 profile: &BoundaryCompilerProfileV1,
175 input: &serde_json::Value,
176 output: &serde_json::Value,
177 repair_receipt_id: Option<ArtifactId>,
178 treatment_integrity_receipt_id: Option<ArtifactId>,
179 ) -> Self {
180 let input_digest = DisplayDigestV1::for_json_value(input);
181 let output_digest = DisplayDigestV1::for_json_value(output);
182 let material = format!(
183 "{}|{}|{}",
184 profile.profile_id.0, input_digest.digest, output_digest.digest
185 );
186 Self {
187 receipt_id: generated_artifact_id_from_material("boundary-compile", &material),
188 profile_id: profile.profile_id.clone(),
189 input_digest,
190 output_digest: Some(output_digest),
191 accepted: true,
192 duplicate_keys_rejected: profile.rejects_duplicate_keys(),
193 schema_validated: true,
194 canonicalized: true,
195 repair_receipt_id,
196 treatment_integrity_receipt_id,
197 reason_codes: vec!["boundary-compile-accepted".into()],
198 recorded_at: Utc::now(),
199 }
200 }
201
202 pub fn rejected_duplicate_key(
203 profile: &BoundaryCompilerProfileV1,
204 input: &serde_json::Value,
205 ) -> Self {
206 let input_digest = DisplayDigestV1::for_json_value(input);
207 let material = format!(
208 "{}|{}|duplicate-key",
209 profile.profile_id.0, input_digest.digest
210 );
211 Self {
212 receipt_id: generated_artifact_id_from_material("boundary-compile", &material),
213 profile_id: profile.profile_id.clone(),
214 input_digest,
215 output_digest: None,
216 accepted: false,
217 duplicate_keys_rejected: true,
218 schema_validated: false,
219 canonicalized: false,
220 repair_receipt_id: None,
221 treatment_integrity_receipt_id: None,
222 reason_codes: vec!["duplicate-json-object-key".into()],
223 recorded_at: Utc::now(),
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
234pub struct ConformanceRunReceiptV1 {
235 pub receipt_id: ArtifactId,
236 pub profile_id: ArtifactId,
237 pub fixture_count: u32,
238 pub passed_count: u32,
239 pub failed_count: u32,
240 pub fixture_results: Vec<ConformanceFixtureResultV1>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub environment: Option<ConformanceEnvironmentV1>,
243 #[serde(default, skip_serializing_if = "Vec::is_empty")]
244 pub reason_codes: Vec<String>,
245 pub recorded_at: DateTime<Utc>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
249pub struct ConformanceFixtureResultV1 {
250 pub fixture_id: String,
251 pub passed: bool,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub input_digest: Option<DisplayDigestV1>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub expected_digest: Option<DisplayDigestV1>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub actual_digest: Option<DisplayDigestV1>,
258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
259 pub reason_codes: Vec<String>,
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
263pub struct ConformanceEnvironmentV1 {
264 pub rustc_version: String,
265 pub target_triple: String,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub ci_run_id: Option<String>,
268}
269
270impl ConformanceRunReceiptV1 {
271 pub fn new(
272 profile_id: ArtifactId,
273 fixture_results: Vec<ConformanceFixtureResultV1>,
274 environment: Option<ConformanceEnvironmentV1>,
275 ) -> Self {
276 let passed_count = fixture_results.iter().filter(|r| r.passed).count() as u32;
277 let failed_count = fixture_results.len() as u32 - passed_count;
278 let fixture_count = fixture_results.len() as u32;
279 let material = format!(
280 "{}|{}|{}|{}",
281 profile_id.0,
282 fixture_count,
283 passed_count,
284 fixture_results
285 .iter()
286 .map(|r| if r.passed { "p" } else { "f" })
287 .collect::<Vec<_>>()
288 .join("")
289 );
290 let reason_codes = if failed_count == 0 {
291 vec!["conformance-run-passed".into()]
292 } else {
293 vec!["conformance-run-failures".into()]
294 };
295 Self {
296 receipt_id: generated_artifact_id_from_material("conformance-run", &material),
297 profile_id,
298 fixture_count,
299 passed_count,
300 failed_count,
301 fixture_results,
302 environment,
303 reason_codes,
304 recorded_at: Utc::now(),
305 }
306 }
307}