Skip to main content

aidens_contracts/
schema_catalog.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
4pub struct BoundaryRepairReportV1 {
5    pub receipt_id: ArtifactId,
6    pub changed: bool,
7    pub repair_kind: String,
8    pub before_digest: Option<String>,
9    pub after_digest: Option<String>,
10    #[serde(default, skip_serializing_if = "Vec::is_empty")]
11    pub canonical_repair_record_ids: Vec<StackBoundaryRepairRecordId>,
12    #[serde(default, skip_serializing_if = "Vec::is_empty")]
13    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
14    pub warnings: Vec<String>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18pub struct DisplayDigestV1 {
19    pub algorithm: String,
20    pub canonicalization: String,
21    pub digest: String,
22    #[serde(default = "default_true")]
23    pub non_authoritative: bool,
24    #[serde(default, skip_serializing_if = "Vec::is_empty")]
25    pub reason_codes: Vec<String>,
26}
27
28impl DisplayDigestV1 {
29    pub fn for_json_value(value: &serde_json::Value) -> Self {
30        Self {
31            algorithm: "blake3".into(),
32            canonicalization: "stack-ids-json-c14n-v1-display-only".into(),
33            digest: non_authoritative_json_display_digest(value),
34            non_authoritative: true,
35            reason_codes: vec!["display-only-not-artifact-identity".into()],
36        }
37    }
38
39    pub fn for_text(text: &str) -> Self {
40        Self {
41            algorithm: "blake3".into(),
42            canonicalization: "stack-ids-utf8-text-v1-display-only".into(),
43            digest: non_authoritative_text_display_digest(text),
44            non_authoritative: true,
45            reason_codes: vec!["display-only-not-artifact-identity".into()],
46        }
47    }
48
49    /// Construct a DisplayDigestV1 from a pre-computed hex digest string.
50    ///
51    /// Use this when the digest has already been computed by a boundary
52    /// compiler or other subsystem that produces SHA-256 hex strings.
53    pub fn from_hex(hex_digest: impl Into<String>) -> Self {
54        Self {
55            algorithm: "sha256".into(),
56            canonicalization: "boundary-compiler-sha256-hex-display-only".into(),
57            digest: hex_digest.into(),
58            non_authoritative: true,
59            reason_codes: vec!["display-only-not-artifact-identity".into()],
60        }
61    }
62
63    pub fn from_stack_content_digest_for_display(
64        digest: StackContentDigest,
65        canonicalization: impl Into<String>,
66    ) -> Self {
67        Self {
68            algorithm: "blake3".into(),
69            canonicalization: format!("{}-display-only", canonicalization.into()),
70            digest: non_authoritative_display_digest_string(&digest),
71            non_authoritative: true,
72            reason_codes: vec!["display-only-not-artifact-identity".into()],
73        }
74    }
75}
76
77fn default_true() -> bool {
78    true
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
82pub struct DuplicateKeyFindingV1 {
83    pub finding_id: ArtifactId,
84    pub path: String,
85    pub key: String,
86    pub first_offset: Option<usize>,
87    pub duplicate_offset: Option<usize>,
88    #[serde(default, skip_serializing_if = "Vec::is_empty")]
89    pub reason_codes: Vec<String>,
90}
91
92impl DuplicateKeyFindingV1 {
93    pub fn new(
94        path: impl Into<String>,
95        key: impl Into<String>,
96        first_offset: Option<usize>,
97        duplicate_offset: Option<usize>,
98    ) -> Self {
99        Self {
100            finding_id: display_only_unstable_id("duplicate-key-finding"),
101            path: path.into(),
102            key: key.into(),
103            first_offset,
104            duplicate_offset,
105            reason_codes: vec!["duplicate-json-object-key".into()],
106        }
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
111pub struct JsonBoundaryRepairDisplayReportV1 {
112    pub receipt_id: ArtifactId,
113    pub kind: ArtifactKindV1,
114    pub changed: bool,
115    pub repair_kind: String,
116    pub degraded: bool,
117    pub before_raw_digest: Option<String>,
118    pub after_raw_digest: Option<String>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub before_display_digest: Option<DisplayDigestV1>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub after_display_digest: Option<DisplayDigestV1>,
123    #[serde(default, skip_serializing_if = "Vec::is_empty")]
124    pub treatment_critical_fields: Vec<String>,
125    #[serde(default, skip_serializing_if = "Vec::is_empty")]
126    pub treatment_integrity_warnings: Vec<String>,
127    #[serde(default)]
128    pub hard_failed: bool,
129    #[serde(default, skip_serializing_if = "Vec::is_empty")]
130    pub warnings: Vec<String>,
131    #[serde(default, skip_serializing_if = "Vec::is_empty")]
132    pub reason_codes: Vec<String>,
133    #[serde(default, skip_serializing_if = "Vec::is_empty")]
134    pub canonical_repair_record_ids: Vec<StackBoundaryRepairRecordId>,
135    #[serde(default, skip_serializing_if = "Vec::is_empty")]
136    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
137}
138
139impl JsonBoundaryRepairDisplayReportV1 {
140    pub fn none() -> Self {
141        Self {
142            receipt_id: display_only_unstable_id("json-repair"),
143            kind: ArtifactKindV1::BoundaryRepair,
144            changed: false,
145            repair_kind: "none".into(),
146            degraded: false,
147            before_raw_digest: None,
148            after_raw_digest: None,
149            before_display_digest: None,
150            after_display_digest: None,
151            treatment_critical_fields: Vec::new(),
152            treatment_integrity_warnings: Vec::new(),
153            hard_failed: false,
154            warnings: Vec::new(),
155            reason_codes: Vec::new(),
156            canonical_repair_record_ids: Vec::new(),
157            canonical_backpointers: canonical_owner_backpointer(
158                "verification-control",
159                "BoundaryRepairRecord",
160                "canonical-boundary-repair-owner",
161            ),
162        }
163    }
164}
165
166impl From<JsonBoundaryRepairDisplayReportV1> for BoundaryRepairReportV1 {
167    fn from(receipt: JsonBoundaryRepairDisplayReportV1) -> Self {
168        Self {
169            receipt_id: receipt.receipt_id,
170            changed: receipt.changed,
171            repair_kind: receipt.repair_kind,
172            before_digest: receipt.before_raw_digest,
173            after_digest: receipt.after_raw_digest,
174            canonical_repair_record_ids: receipt.canonical_repair_record_ids,
175            canonical_backpointers: receipt.canonical_backpointers,
176            warnings: receipt
177                .warnings
178                .into_iter()
179                .chain(receipt.treatment_integrity_warnings)
180                .collect(),
181        }
182    }
183}
184
185impl From<&JsonBoundaryRepairDisplayReportV1> for BoundaryRepairReportV1 {
186    fn from(receipt: &JsonBoundaryRepairDisplayReportV1) -> Self {
187        receipt.clone().into()
188    }
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
192pub struct SchemaValidationReportV1 {
193    pub receipt_id: ArtifactId,
194    pub kind: ArtifactKindV1,
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub run_id: Option<ArtifactId>,
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub attempt_id: Option<ArtifactId>,
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub tool_id: Option<String>,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub schema_digest: Option<DisplayDigestV1>,
203    pub input_digest: DisplayDigestV1,
204    pub valid: bool,
205    #[serde(default, skip_serializing_if = "Vec::is_empty")]
206    pub errors: Vec<String>,
207    #[serde(default, skip_serializing_if = "Vec::is_empty")]
208    pub reason_codes: Vec<String>,
209    #[serde(default, skip_serializing_if = "Vec::is_empty")]
210    pub canonical_control_receipt_ids: Vec<StackControlReceiptId>,
211    #[serde(default, skip_serializing_if = "Vec::is_empty")]
212    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
213    pub checked_at: DateTime<Utc>,
214}
215
216impl SchemaValidationReportV1 {
217    pub fn new(
218        schema: Option<&serde_json::Value>,
219        input: &serde_json::Value,
220        errors: Vec<String>,
221    ) -> Self {
222        let valid = errors.is_empty();
223        Self {
224            receipt_id: display_only_unstable_id("schema-validation"),
225            kind: ArtifactKindV1::SchemaValidation,
226            run_id: None,
227            attempt_id: None,
228            tool_id: None,
229            schema_digest: schema.map(DisplayDigestV1::for_json_value),
230            input_digest: DisplayDigestV1::for_json_value(input),
231            valid,
232            errors,
233            reason_codes: if valid {
234                vec!["schema-validation-passed".into()]
235            } else {
236                vec!["schema-validation-failed".into()]
237            },
238            canonical_control_receipt_ids: Vec::new(),
239            canonical_backpointers: canonical_owner_backpointer(
240                "verification-control",
241                "ControlReceipt",
242                "canonical-schema-validation-owner",
243            ),
244            checked_at: Utc::now(),
245        }
246    }
247
248    pub fn with_tool_id(mut self, tool_id: impl Into<String>) -> Self {
249        self.tool_id = Some(tool_id.into());
250        self
251    }
252
253    pub fn with_execution_context(mut self, context: &AidensRunContextV1) -> Self {
254        self.run_id = Some(context.run_id.clone());
255        self.attempt_id = Some(context.attempt_id.clone());
256        self
257    }
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
261pub struct BoundaryCompileRequestV1 {
262    pub request_id: ArtifactId,
263    pub input: String,
264    #[serde(default, skip_serializing_if = "Option::is_none")]
265    pub schema: Option<serde_json::Value>,
266    pub schema_dialect: String,
267    pub allow_markdown_fence_repair: bool,
268    pub allow_json_substring_extract: bool,
269    #[serde(default, skip_serializing_if = "Vec::is_empty")]
270    pub treatment_critical_fields: Vec<String>,
271    #[serde(default)]
272    pub hard_fail_on_treatment_change: bool,
273}
274
275impl BoundaryCompileRequestV1 {
276    pub fn new(input: impl Into<String>) -> Self {
277        Self {
278            request_id: display_only_unstable_id("boundary-compile-request"),
279            input: input.into(),
280            schema: None,
281            schema_dialect: "json-schema-2020-12-subset".into(),
282            allow_markdown_fence_repair: false,
283            allow_json_substring_extract: false,
284            treatment_critical_fields: Vec::new(),
285            hard_fail_on_treatment_change: false,
286        }
287    }
288
289    pub fn with_schema(mut self, schema: serde_json::Value) -> Self {
290        self.schema = Some(schema);
291        self
292    }
293
294    pub fn with_treatment_critical_fields(mut self, fields: Vec<String>) -> Self {
295        self.treatment_critical_fields = fields;
296        self
297    }
298
299    pub fn with_hard_fail_on_treatment_change(mut self, hard_fail: bool) -> Self {
300        self.hard_fail_on_treatment_change = hard_fail;
301        self
302    }
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
306pub struct BoundaryCompileOutcomeV1 {
307    pub outcome_id: ArtifactId,
308    pub request_id: ArtifactId,
309    pub accepted: bool,
310    pub degraded: bool,
311    #[serde(default, skip_serializing_if = "Option::is_none")]
312    pub value: Option<serde_json::Value>,
313    #[serde(default, skip_serializing_if = "Option::is_none")]
314    pub display_digest: Option<DisplayDigestV1>,
315    #[serde(default, skip_serializing_if = "Vec::is_empty")]
316    pub duplicate_key_findings: Vec<DuplicateKeyFindingV1>,
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub schema_validation: Option<SchemaValidationReportV1>,
319    #[serde(default, skip_serializing_if = "Option::is_none")]
320    pub repair_receipt: Option<JsonBoundaryRepairDisplayReportV1>,
321    #[serde(default, skip_serializing_if = "Vec::is_empty")]
322    pub reason_codes: Vec<String>,
323    pub compiled_at: DateTime<Utc>,
324}
325
326impl BoundaryCompileOutcomeV1 {
327    pub fn rejected(request_id: ArtifactId, reason_codes: Vec<String>) -> Self {
328        Self {
329            outcome_id: display_only_unstable_id("boundary-compile-outcome"),
330            request_id,
331            accepted: false,
332            degraded: true,
333            value: None,
334            display_digest: None,
335            duplicate_key_findings: Vec::new(),
336            schema_validation: None,
337            repair_receipt: None,
338            reason_codes,
339            compiled_at: Utc::now(),
340        }
341    }
342}
343
344/// AiDENs-local, non-authoritative schema index for product/display/report DTOs.
345///
346/// Canonical stack artifact family schemas are owned by their owner crates and
347/// `contract-schema-gen`, not by this registry.
348#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
349pub struct ArtifactFamilyRegistryV1 {
350    pub registry_id: ArtifactId,
351    pub registry_version: u32,
352    pub families: Vec<ArtifactFamilyRegistrationV1>,
353    #[serde(default = "default_schema_canonical_truth_owner")]
354    pub canonical_truth_owner: String,
355    #[serde(default = "schema_registry_governance_local_display")]
356    pub governance_status: SchemaRegistryGovernanceStatusV1,
357    #[serde(default, skip_serializing_if = "Vec::is_empty")]
358    pub reason_codes: Vec<String>,
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
362#[serde(rename_all = "kebab-case")]
363pub enum SchemaRegistryGovernanceStatusV1 {
364    LocalDisplayIndexOnly,
365    QuarantinedExternalFamilies,
366    AdmittedByCanonicalOwner,
367}
368
369fn default_schema_canonical_truth_owner() -> String {
370    "contract-schema-gen".into()
371}
372
373fn schema_registry_governance_local_display() -> SchemaRegistryGovernanceStatusV1 {
374    SchemaRegistryGovernanceStatusV1::LocalDisplayIndexOnly
375}
376
377impl ArtifactFamilyRegistryV1 {
378    pub fn new(mut families: Vec<ArtifactFamilyRegistrationV1>) -> Self {
379        families.sort_by(|left, right| {
380            left.family
381                .cmp(&right.family)
382                .then(left.version.cmp(&right.version))
383        });
384        Self {
385            registry_id: ArtifactId("artifact-family-registry:v1".into()),
386            registry_version: 1,
387            families,
388            canonical_truth_owner: "contract-schema-gen".into(),
389            governance_status: SchemaRegistryGovernanceStatusV1::LocalDisplayIndexOnly,
390            reason_codes: vec!["aidens-local-non-authoritative-schema-index".into()],
391        }
392    }
393
394    pub fn contains_family_version(&self, family: &str, version: u32) -> bool {
395        self.families
396            .iter()
397            .any(|entry| entry.family == family && entry.version == version)
398    }
399}
400
401/// AiDENs-local schema registration metadata.
402///
403/// Entries in this structure are not canonical stack artifact ownership claims.
404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
405pub struct ArtifactFamilyRegistrationV1 {
406    pub family: String,
407    pub version: u32,
408    #[serde(default, skip_serializing_if = "String::is_empty")]
409    pub schema_identity: String,
410    pub rust_type: String,
411    pub owner_crate: String,
412    #[serde(default = "schema_family_admission_local_display")]
413    pub admission: SchemaFamilyAdmissionV1,
414    pub first_pass: String,
415    pub schema_path: String,
416    #[serde(default, skip_serializing_if = "Option::is_none")]
417    pub fixture_path: Option<String>,
418    pub compatibility_policy: String,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
422#[serde(rename_all = "kebab-case")]
423pub enum SchemaFamilyAdmissionV1 {
424    LocalDisplayOnly,
425    QuarantinedExternal,
426    AdmittedByCanonicalOwner,
427}
428
429fn schema_family_admission_local_display() -> SchemaFamilyAdmissionV1 {
430    SchemaFamilyAdmissionV1::LocalDisplayOnly
431}
432
433impl SchemaFamilyAdmissionV1 {
434    pub fn allows_local_generation(self) -> bool {
435        matches!(
436            self,
437            Self::LocalDisplayOnly | Self::AdmittedByCanonicalOwner
438        )
439    }
440
441    pub fn allows_truth_ownership_claim(self) -> bool {
442        matches!(self, Self::AdmittedByCanonicalOwner)
443    }
444}
445
446impl ArtifactFamilyRegistrationV1 {
447    pub fn new(
448        family: impl Into<String>,
449        version: u32,
450        rust_type: impl Into<String>,
451        first_pass: impl Into<String>,
452        fixture_path: Option<String>,
453        compatibility_policy: impl Into<String>,
454    ) -> Self {
455        let family = family.into();
456        Self {
457            schema_path: format!("{family}/v{version}.schema.json"),
458            schema_identity: format!("schema:{family}:v{version}"),
459            family,
460            version,
461            rust_type: rust_type.into(),
462            owner_crate: "aidens-orchestration".into(),
463            admission: SchemaFamilyAdmissionV1::LocalDisplayOnly,
464            first_pass: first_pass.into(),
465            fixture_path,
466            compatibility_policy: compatibility_policy.into(),
467        }
468    }
469
470    pub fn quarantined_external(
471        family: impl Into<String>,
472        version: u32,
473        schema_path: impl Into<String>,
474        reason: impl Into<String>,
475    ) -> Self {
476        let family = family.into();
477        let reason = reason.into();
478        Self {
479            schema_identity: format!("schema:{family}:v{version}:external-quarantined"),
480            schema_path: schema_path.into(),
481            family,
482            version,
483            rust_type: "external-unadmitted".into(),
484            owner_crate: "external-unadmitted".into(),
485            admission: SchemaFamilyAdmissionV1::QuarantinedExternal,
486            first_pass: "external".into(),
487            fixture_path: None,
488            compatibility_policy: format!("quarantined:{reason}"),
489        }
490    }
491}
492
493/// AiDENs-local, non-authoritative manifest for generated display/report schemas.
494///
495/// Canonical family schema generation is delegated to `contract-schema-gen`.
496#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
497pub struct GeneratedSchemaManifestV1 {
498    pub manifest_id: ArtifactId,
499    pub manifest_version: u32,
500    pub registry_digest: String,
501    pub schemas: Vec<GeneratedSchemaEntryV1>,
502    #[serde(default, skip_serializing_if = "Vec::is_empty")]
503    pub reason_codes: Vec<String>,
504}
505
506impl GeneratedSchemaManifestV1 {
507    pub fn new(
508        registry: &ArtifactFamilyRegistryV1,
509        mut schemas: Vec<GeneratedSchemaEntryV1>,
510    ) -> Self {
511        schemas.sort_by(|left, right| {
512            left.family
513                .cmp(&right.family)
514                .then(left.version.cmp(&right.version))
515        });
516        Self {
517            manifest_id: ArtifactId("generated-schema-manifest:v1".into()),
518            manifest_version: 1,
519            registry_digest: non_authoritative_json_display_digest(
520                &serde_json::to_value(registry).unwrap_or(serde_json::Value::Null),
521            ),
522            schemas,
523            reason_codes: vec!["aidens-local-display-report-schemas-only".into()],
524        }
525    }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
529pub struct GeneratedSchemaEntryV1 {
530    pub family: String,
531    pub version: u32,
532    #[serde(default, skip_serializing_if = "String::is_empty")]
533    pub schema_identity: String,
534    pub rust_type: String,
535    pub schema_path: String,
536    pub schema_digest: String,
537}
538
539/// AiDENs-local, non-authoritative generated schema document.
540#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
541pub struct GeneratedSchemaDocumentV1 {
542    pub registration: ArtifactFamilyRegistrationV1,
543    pub schema: serde_json::Value,
544}
545
546impl GeneratedSchemaDocumentV1 {
547    pub fn entry(&self) -> GeneratedSchemaEntryV1 {
548        GeneratedSchemaEntryV1 {
549            family: self.registration.family.clone(),
550            version: self.registration.version,
551            schema_identity: self.content_addressed_identity(),
552            rust_type: self.registration.rust_type.clone(),
553            schema_path: self.registration.schema_path.clone(),
554            schema_digest: non_authoritative_json_display_digest(&self.schema),
555        }
556    }
557
558    pub fn content_addressed_identity(&self) -> String {
559        format!(
560            "{}:{}",
561            self.registration.schema_identity,
562            non_authoritative_json_display_digest(&self.schema)
563        )
564    }
565
566    pub fn pretty_json(&self) -> String {
567        let mut encoded = serde_json::to_string_pretty(&self.schema).unwrap_or_default();
568        encoded.push('\n');
569        encoded
570    }
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
574#[serde(rename_all = "kebab-case")]
575pub enum SchemaCompatibilityModeV1 {
576    Backward,
577    Forward,
578    Full,
579    Transitive,
580}
581
582impl fmt::Display for SchemaCompatibilityModeV1 {
583    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584        f.write_str(match self {
585            Self::Backward => "backward",
586            Self::Forward => "forward",
587            Self::Full => "full",
588            Self::Transitive => "transitive",
589        })
590    }
591}
592
593#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
594#[serde(rename_all = "kebab-case")]
595pub enum SchemaChangeClassV1 {
596    Exact,
597    AdditiveMinor,
598    MajorBreaking,
599    UnknownIncompatible,
600}
601
602#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
603pub struct SchemaCompatibilityCheckV1 {
604    pub family: String,
605    pub version: u32,
606    pub mode: SchemaCompatibilityModeV1,
607    #[serde(default = "schema_change_class_unknown_incompatible")]
608    pub change_class: SchemaChangeClassV1,
609    #[serde(default)]
610    pub requires_major_bump: bool,
611    pub compatible: bool,
612    #[serde(default, skip_serializing_if = "Vec::is_empty")]
613    pub reason_codes: Vec<String>,
614}
615
616fn schema_change_class_unknown_incompatible() -> SchemaChangeClassV1 {
617    SchemaChangeClassV1::UnknownIncompatible
618}
619
620impl SchemaCompatibilityCheckV1 {
621    pub fn exact(family: impl Into<String>, version: u32, mode: SchemaCompatibilityModeV1) -> Self {
622        Self {
623            family: family.into(),
624            version,
625            mode,
626            change_class: SchemaChangeClassV1::Exact,
627            requires_major_bump: false,
628            compatible: true,
629            reason_codes: vec![format!("schema-compatible-{mode}")],
630        }
631    }
632
633    pub fn incompatible(
634        family: impl Into<String>,
635        version: u32,
636        mode: SchemaCompatibilityModeV1,
637        reason_codes: Vec<String>,
638    ) -> Self {
639        Self {
640            family: family.into(),
641            version,
642            mode,
643            change_class: SchemaChangeClassV1::UnknownIncompatible,
644            requires_major_bump: true,
645            compatible: false,
646            reason_codes,
647        }
648    }
649}
650
651#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
652pub struct SchemaCompatibilityReportV1 {
653    pub report_id: ArtifactId,
654    pub registry_digest: String,
655    pub compatible: bool,
656    pub checked_schema_count: usize,
657    #[serde(default, skip_serializing_if = "Vec::is_empty")]
658    pub checks: Vec<SchemaCompatibilityCheckV1>,
659    #[serde(default, skip_serializing_if = "Vec::is_empty")]
660    pub missing_schema_paths: Vec<String>,
661    #[serde(default, skip_serializing_if = "Vec::is_empty")]
662    pub unregistered_schema_paths: Vec<String>,
663    #[serde(default, skip_serializing_if = "Vec::is_empty")]
664    pub incompatible_schema_paths: Vec<String>,
665    #[serde(default, skip_serializing_if = "Vec::is_empty")]
666    pub path_collision_findings: Vec<SchemaPathCollisionFindingV1>,
667    #[serde(default, skip_serializing_if = "Vec::is_empty")]
668    pub reason_codes: Vec<String>,
669}
670
671#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
672pub struct SchemaPathCollisionFindingV1 {
673    pub finding_id: ArtifactId,
674    pub normalized_path: String,
675    pub colliding_paths: Vec<String>,
676    #[serde(default, skip_serializing_if = "Vec::is_empty")]
677    pub reason_codes: Vec<String>,
678}
679
680impl SchemaPathCollisionFindingV1 {
681    pub fn new(normalized_path: impl Into<String>, colliding_paths: Vec<String>) -> Self {
682        Self {
683            finding_id: display_only_unstable_id("schema-path-collision"),
684            normalized_path: normalized_path.into(),
685            colliding_paths: sorted_unique_strings(colliding_paths),
686            reason_codes: vec!["schema-path-case-fold-collision".into()],
687        }
688    }
689}
690
691impl SchemaCompatibilityReportV1 {
692    pub fn new(
693        registry: &ArtifactFamilyRegistryV1,
694        checked_schema_count: usize,
695        checks: Vec<SchemaCompatibilityCheckV1>,
696        missing_schema_paths: Vec<String>,
697        unregistered_schema_paths: Vec<String>,
698        incompatible_schema_paths: Vec<String>,
699        path_collision_findings: Vec<SchemaPathCollisionFindingV1>,
700    ) -> Self {
701        let compatible = missing_schema_paths.is_empty()
702            && unregistered_schema_paths.is_empty()
703            && incompatible_schema_paths.is_empty()
704            && path_collision_findings.is_empty()
705            && checks.iter().all(|check| check.compatible);
706        let mut reason_codes = Vec::new();
707        if compatible {
708            reason_codes.push("schema-compatibility-passed".into());
709        }
710        if !missing_schema_paths.is_empty() {
711            reason_codes.push("registered-schema-missing".into());
712        }
713        if !unregistered_schema_paths.is_empty() {
714            reason_codes.push("unregistered-artifact-family-schema".into());
715        }
716        if !incompatible_schema_paths.is_empty() {
717            reason_codes.push("schema-content-drift-without-major-bump".into());
718        }
719        if !path_collision_findings.is_empty() {
720            reason_codes.push("schema-path-case-fold-collision".into());
721        }
722        Self {
723            report_id: ArtifactId("schema-compatibility-report:v1".into()),
724            registry_digest: non_authoritative_json_display_digest(
725                &serde_json::to_value(registry).unwrap_or(serde_json::Value::Null),
726            ),
727            compatible,
728            checked_schema_count,
729            checks,
730            missing_schema_paths,
731            unregistered_schema_paths,
732            incompatible_schema_paths,
733            path_collision_findings,
734            reason_codes,
735        }
736    }
737}
738
739#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
740#[serde(rename_all = "kebab-case")]
741pub enum MigrationPhaseV1 {
742    Expand,
743    Backfill,
744    FlipRead,
745    Contract,
746}
747
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
749pub struct MigrationPlanV1 {
750    pub plan_id: ArtifactId,
751    pub artifact_family: String,
752    pub from_version: u32,
753    pub to_version: u32,
754    pub phases: Vec<MigrationPhaseV1>,
755    pub requires_major_bump: bool,
756    pub compatible_without_backfill: bool,
757    #[serde(default, skip_serializing_if = "Vec::is_empty")]
758    pub reason_codes: Vec<String>,
759}
760
761impl MigrationPlanV1 {
762    pub fn expand_backfill_flip_contract(
763        artifact_family: impl Into<String>,
764        from_version: u32,
765        to_version: u32,
766        requires_major_bump: bool,
767    ) -> Self {
768        Self {
769            plan_id: ArtifactId("migration-plan:v1".into()),
770            artifact_family: artifact_family.into(),
771            from_version,
772            to_version,
773            phases: vec![
774                MigrationPhaseV1::Expand,
775                MigrationPhaseV1::Backfill,
776                MigrationPhaseV1::FlipRead,
777                MigrationPhaseV1::Contract,
778            ],
779            requires_major_bump,
780            compatible_without_backfill: !requires_major_bump,
781            reason_codes: vec!["expand-backfill-flip-read-contract".into()],
782        }
783    }
784}
785
786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
787pub struct BackfillReportV1 {
788    pub receipt_id: ArtifactId,
789    pub kind: ArtifactKindV1,
790    pub migration_plan_id: ArtifactId,
791    pub artifact_family: String,
792    pub from_version: u32,
793    pub to_version: u32,
794    pub migrated_fixture_count: usize,
795    pub succeeded: bool,
796    #[serde(default, skip_serializing_if = "Vec::is_empty")]
797    pub source_fixture_paths: Vec<String>,
798    #[serde(default, skip_serializing_if = "Vec::is_empty")]
799    pub reason_codes: Vec<String>,
800    pub backfilled_at: DateTime<Utc>,
801}
802
803impl BackfillReportV1 {
804    pub fn succeeded(
805        plan: &MigrationPlanV1,
806        migrated_fixture_count: usize,
807        source_fixture_paths: Vec<String>,
808    ) -> Self {
809        Self {
810            receipt_id: display_only_unstable_id("backfill"),
811            kind: ArtifactKindV1::Backfill,
812            migration_plan_id: plan.plan_id.clone(),
813            artifact_family: plan.artifact_family.clone(),
814            from_version: plan.from_version,
815            to_version: plan.to_version,
816            migrated_fixture_count,
817            succeeded: true,
818            source_fixture_paths,
819            reason_codes: vec!["historical-fixtures-readable".into()],
820            backfilled_at: Utc::now(),
821        }
822    }
823}
824
825#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
826#[serde(rename_all = "kebab-case")]
827pub enum ReferenceDomainV1 {
828    PlanConfig,
829    ProviderRoute,
830    ToolExposure,
831    Permit,
832    BoundaryRepair,
833    ReceiptLineage,
834    TemporalQuery,
835    ProofDebt,
836    SemanticState,
837    ViewDisclosure,
838}
839
840impl fmt::Display for ReferenceDomainV1 {
841    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842        f.write_str(match self {
843            Self::PlanConfig => "plan-config",
844            Self::ProviderRoute => "provider-route",
845            Self::ToolExposure => "tool-exposure",
846            Self::Permit => "permit",
847            Self::BoundaryRepair => "boundary-repair",
848            Self::ReceiptLineage => "receipt-lineage",
849            Self::TemporalQuery => "temporal-query",
850            Self::ProofDebt => "proof-debt",
851            Self::SemanticState => "semantic-state",
852            Self::ViewDisclosure => "view-disclosure",
853        })
854    }
855}
856
857#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
858pub struct ReferenceCaseV1 {
859    pub case_id: ArtifactId,
860    pub domain: ReferenceDomainV1,
861    pub title: String,
862    pub input: serde_json::Value,
863    pub expected: serde_json::Value,
864    #[serde(default, skip_serializing_if = "Vec::is_empty")]
865    pub covered_provider_kinds: Vec<String>,
866    #[serde(default, skip_serializing_if = "Vec::is_empty")]
867    pub covered_risk_classes: Vec<CanonicalToolSideEffectClass>,
868    #[serde(default, skip_serializing_if = "Vec::is_empty")]
869    pub covered_memory_modes: Vec<MemoryModeV1>,
870    #[serde(default, skip_serializing_if = "Vec::is_empty")]
871    pub covered_receipt_levels: Vec<ReportLevelV1>,
872    #[serde(default, skip_serializing_if = "Vec::is_empty")]
873    pub covered_tool_lifecycle_states: Vec<ToolLifecycleStateV1>,
874    #[serde(default, skip_serializing_if = "Vec::is_empty")]
875    pub source_fixture_paths: Vec<String>,
876    #[serde(default, skip_serializing_if = "Vec::is_empty")]
877    pub reason_codes: Vec<String>,
878}
879
880impl ReferenceCaseV1 {
881    pub fn new(
882        domain: ReferenceDomainV1,
883        title: impl Into<String>,
884        input: serde_json::Value,
885        expected: serde_json::Value,
886    ) -> Self {
887        Self {
888            case_id: display_only_unstable_id("reference-case"),
889            domain,
890            title: title.into(),
891            input,
892            expected,
893            covered_provider_kinds: Vec::new(),
894            covered_risk_classes: Vec::new(),
895            covered_memory_modes: Vec::new(),
896            covered_receipt_levels: Vec::new(),
897            covered_tool_lifecycle_states: Vec::new(),
898            source_fixture_paths: Vec::new(),
899            reason_codes: vec!["reference-semantics-case".into()],
900        }
901    }
902
903    pub fn with_provider_kind(mut self, provider_kind: impl Into<String>) -> Self {
904        self.covered_provider_kinds.push(provider_kind.into());
905        self
906    }
907
908    pub fn with_risk_class(mut self, risk_class: CanonicalToolSideEffectClass) -> Self {
909        self.covered_risk_classes.push(risk_class);
910        self
911    }
912
913    pub fn with_memory_mode(mut self, memory_mode: MemoryModeV1) -> Self {
914        self.covered_memory_modes.push(memory_mode);
915        self
916    }
917
918    pub fn with_receipt_level(mut self, receipt_level: ReportLevelV1) -> Self {
919        self.covered_receipt_levels.push(receipt_level);
920        self
921    }
922
923    pub fn with_tool_lifecycle_state(mut self, state: ToolLifecycleStateV1) -> Self {
924        self.covered_tool_lifecycle_states.push(state);
925        self
926    }
927
928    pub fn with_source_fixture(mut self, path: impl Into<String>) -> Self {
929        self.source_fixture_paths.push(path.into());
930        self
931    }
932}
933
934#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
935pub struct DifferentialConformanceFindingV1 {
936    pub finding_id: ArtifactId,
937    pub case_id: ArtifactId,
938    pub domain: ReferenceDomainV1,
939    pub production_subject: String,
940    pub path: String,
941    pub expected: serde_json::Value,
942    pub actual: serde_json::Value,
943    pub human_diff: String,
944    #[serde(default, skip_serializing_if = "Vec::is_empty")]
945    pub reason_codes: Vec<String>,
946}
947
948impl DifferentialConformanceFindingV1 {
949    pub fn mismatch(
950        case: &ReferenceCaseV1,
951        production_subject: impl Into<String>,
952        path: impl Into<String>,
953        expected: serde_json::Value,
954        actual: serde_json::Value,
955    ) -> Self {
956        let path = path.into();
957        let human_diff = format!(
958            "{} mismatch at {path}: expected {}, actual {}",
959            case.title,
960            display_json_string(&expected),
961            display_json_string(&actual)
962        );
963        Self {
964            finding_id: display_only_unstable_id("differential-conformance-finding"),
965            case_id: case.case_id.clone(),
966            domain: case.domain,
967            production_subject: production_subject.into(),
968            path,
969            expected,
970            actual,
971            human_diff,
972            reason_codes: vec!["reference-production-mismatch".into()],
973        }
974    }
975}
976
977#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
978pub struct ReferenceInterpreterReportV1 {
979    pub report_id: ArtifactId,
980    pub interpreter_id: String,
981    pub case_count: usize,
982    pub passed: bool,
983    #[serde(default, skip_serializing_if = "Vec::is_empty")]
984    pub findings: Vec<DifferentialConformanceFindingV1>,
985    #[serde(default, skip_serializing_if = "Vec::is_empty")]
986    pub covered_provider_kinds: Vec<String>,
987    #[serde(default, skip_serializing_if = "Vec::is_empty")]
988    pub covered_risk_classes: Vec<CanonicalToolSideEffectClass>,
989    #[serde(default, skip_serializing_if = "Vec::is_empty")]
990    pub covered_memory_modes: Vec<MemoryModeV1>,
991    #[serde(default, skip_serializing_if = "Vec::is_empty")]
992    pub covered_receipt_levels: Vec<ReportLevelV1>,
993    #[serde(default, skip_serializing_if = "Vec::is_empty")]
994    pub covered_tool_lifecycle_states: Vec<ToolLifecycleStateV1>,
995    #[serde(default, skip_serializing_if = "Vec::is_empty")]
996    pub reason_codes: Vec<String>,
997    pub generated_at: DateTime<Utc>,
998}
999
1000impl ReferenceInterpreterReportV1 {
1001    pub fn new(
1002        interpreter_id: impl Into<String>,
1003        case_count: usize,
1004        findings: Vec<DifferentialConformanceFindingV1>,
1005        cases: &[ReferenceCaseV1],
1006    ) -> Self {
1007        let passed = findings.is_empty();
1008        Self {
1009            report_id: display_only_unstable_id("reference-interpreter-report"),
1010            interpreter_id: interpreter_id.into(),
1011            case_count,
1012            passed,
1013            findings,
1014            covered_provider_kinds: sorted_unique_strings(
1015                cases
1016                    .iter()
1017                    .flat_map(|case| case.covered_provider_kinds.iter().cloned())
1018                    .collect(),
1019            ),
1020            covered_risk_classes: sorted_unique_risk_classes(
1021                cases
1022                    .iter()
1023                    .flat_map(|case| case.covered_risk_classes.iter().cloned())
1024                    .collect(),
1025            ),
1026            covered_memory_modes: unique_memory_modes(
1027                cases
1028                    .iter()
1029                    .flat_map(|case| case.covered_memory_modes.iter().cloned())
1030                    .collect(),
1031            ),
1032            covered_receipt_levels: unique_receipt_levels(
1033                cases
1034                    .iter()
1035                    .flat_map(|case| case.covered_receipt_levels.iter().cloned())
1036                    .collect(),
1037            ),
1038            covered_tool_lifecycle_states: unique_tool_lifecycle_states(
1039                cases
1040                    .iter()
1041                    .flat_map(|case| case.covered_tool_lifecycle_states.iter().cloned())
1042                    .collect(),
1043            ),
1044            reason_codes: if passed {
1045                vec!["reference-conformance-passed".into()]
1046            } else {
1047                vec!["reference-conformance-failed".into()]
1048            },
1049            generated_at: Utc::now(),
1050        }
1051    }
1052}
1053
1054#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1055pub struct GoldenFixtureManifestV1 {
1056    pub manifest_id: ArtifactId,
1057    pub fixture_paths: Vec<String>,
1058    pub reference_case_ids: Vec<ArtifactId>,
1059    pub provider_kinds: Vec<String>,
1060    pub risk_classes: Vec<CanonicalToolSideEffectClass>,
1061    pub memory_modes: Vec<MemoryModeV1>,
1062    pub receipt_levels: Vec<ReportLevelV1>,
1063    pub tool_lifecycle_states: Vec<ToolLifecycleStateV1>,
1064    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1065    pub reason_codes: Vec<String>,
1066    pub generated_at: DateTime<Utc>,
1067}
1068
1069impl GoldenFixtureManifestV1 {
1070    pub fn new(fixture_paths: Vec<String>, cases: &[ReferenceCaseV1]) -> Self {
1071        Self {
1072            manifest_id: display_only_unstable_id("golden-fixture-manifest"),
1073            fixture_paths: sorted_unique_strings(fixture_paths),
1074            reference_case_ids: cases.iter().map(|case| case.case_id.clone()).collect(),
1075            provider_kinds: sorted_unique_strings(
1076                cases
1077                    .iter()
1078                    .flat_map(|case| case.covered_provider_kinds.iter().cloned())
1079                    .collect(),
1080            ),
1081            risk_classes: sorted_unique_risk_classes(
1082                cases
1083                    .iter()
1084                    .flat_map(|case| case.covered_risk_classes.iter().cloned())
1085                    .collect(),
1086            ),
1087            memory_modes: unique_memory_modes(
1088                cases
1089                    .iter()
1090                    .flat_map(|case| case.covered_memory_modes.iter().cloned())
1091                    .collect(),
1092            ),
1093            receipt_levels: unique_receipt_levels(
1094                cases
1095                    .iter()
1096                    .flat_map(|case| case.covered_receipt_levels.iter().cloned())
1097                    .collect(),
1098            ),
1099            tool_lifecycle_states: unique_tool_lifecycle_states(
1100                cases
1101                    .iter()
1102                    .flat_map(|case| case.covered_tool_lifecycle_states.iter().cloned())
1103                    .collect(),
1104            ),
1105            reason_codes: vec!["golden-fixture-coverage-manifest".into()],
1106            generated_at: Utc::now(),
1107        }
1108    }
1109}
1110
1111pub(crate) fn sorted_unique_artifact_ids(mut values: Vec<ArtifactId>) -> Vec<ArtifactId> {
1112    values.sort();
1113    values.dedup();
1114    values
1115}
1116
1117pub(crate) fn sorted_unique_invariants(
1118    mut values: Vec<PreservedInvariantV1>,
1119) -> Vec<PreservedInvariantV1> {
1120    values.sort();
1121    values.dedup();
1122    values
1123}
1124
1125pub(crate) fn sorted_unique_strings(mut values: Vec<String>) -> Vec<String> {
1126    values.sort();
1127    values.dedup();
1128    values
1129}
1130
1131fn sorted_unique_risk_classes(
1132    mut values: Vec<CanonicalToolSideEffectClass>,
1133) -> Vec<CanonicalToolSideEffectClass> {
1134    values.sort();
1135    values.dedup();
1136    values
1137}
1138
1139fn unique_memory_modes(values: Vec<MemoryModeV1>) -> Vec<MemoryModeV1> {
1140    let mut out = Vec::new();
1141    for candidate in [
1142        MemoryModeV1::Disabled,
1143        MemoryModeV1::Optional,
1144        MemoryModeV1::Required,
1145    ] {
1146        if values.contains(&candidate) {
1147            out.push(candidate);
1148        }
1149    }
1150    out
1151}
1152
1153fn unique_receipt_levels(values: Vec<ReportLevelV1>) -> Vec<ReportLevelV1> {
1154    let mut out = Vec::new();
1155    for candidate in [
1156        ReportLevelV1::Minimal,
1157        ReportLevelV1::Standard,
1158        ReportLevelV1::Full,
1159    ] {
1160        if values.contains(&candidate) {
1161            out.push(candidate);
1162        }
1163    }
1164    out
1165}
1166
1167fn unique_tool_lifecycle_states(values: Vec<ToolLifecycleStateV1>) -> Vec<ToolLifecycleStateV1> {
1168    let mut out = Vec::new();
1169    for candidate in [
1170        ToolLifecycleStateV1::Declared,
1171        ToolLifecycleStateV1::Registered,
1172        ToolLifecycleStateV1::Executable,
1173        ToolLifecycleStateV1::Exposed,
1174        ToolLifecycleStateV1::ExposedThisTurn,
1175        ToolLifecycleStateV1::Invoked,
1176        ToolLifecycleStateV1::Succeeded,
1177        ToolLifecycleStateV1::Failed,
1178        ToolLifecycleStateV1::Hidden,
1179        ToolLifecycleStateV1::Blocked,
1180    ] {
1181        if values.contains(&candidate) {
1182            out.push(candidate);
1183        }
1184    }
1185    out
1186}
1187
1188pub fn current_artifact_family_registry() -> ArtifactFamilyRegistryV1 {
1189    ArtifactFamilyRegistryV1::new(artifact_family_registrations())
1190}
1191
1192pub fn generated_schema_documents() -> Vec<GeneratedSchemaDocumentV1> {
1193    generated_schema_documents_inner()
1194}
1195
1196pub fn generated_schema_manifest() -> GeneratedSchemaManifestV1 {
1197    let registry = current_artifact_family_registry();
1198    GeneratedSchemaManifestV1::new(
1199        &registry,
1200        generated_schema_documents()
1201            .into_iter()
1202            .map(|document| document.entry())
1203            .collect(),
1204    )
1205}
1206
1207pub fn generated_schema_manifest_pretty_json() -> String {
1208    let mut encoded =
1209        serde_json::to_string_pretty(&generated_schema_manifest()).unwrap_or_default();
1210    encoded.push('\n');
1211    encoded
1212}
1213
1214macro_rules! artifact_registration {
1215    ($family:literal, $version:literal, $type_name:literal, $pass:literal, $fixture:expr, $policy:literal) => {{
1216        let fixture_path: Option<&str> = $fixture;
1217        ArtifactFamilyRegistrationV1::new(
1218            $family,
1219            $version,
1220            $type_name,
1221            $pass,
1222            fixture_path.map(str::to_string),
1223            $policy,
1224        )
1225    }};
1226}
1227
1228macro_rules! schema_document {
1229    ($family:literal, $version:literal, $ty:ty, $type_name:literal, $pass:literal, $fixture:expr, $policy:literal) => {
1230        GeneratedSchemaDocumentV1 {
1231            registration: artifact_registration!(
1232                $family, $version, $type_name, $pass, $fixture, $policy
1233            ),
1234            schema: serde_json::to_value(schemars::schema_for!($ty))
1235                .unwrap_or(serde_json::Value::Null),
1236        }
1237    };
1238}
1239
1240fn artifact_family_registrations() -> Vec<ArtifactFamilyRegistrationV1> {
1241    generated_schema_documents_inner()
1242        .into_iter()
1243        .map(|document| document.registration)
1244        .collect()
1245}
1246
1247fn generated_schema_documents_inner() -> Vec<GeneratedSchemaDocumentV1> {
1248    vec![
1249        schema_document!(
1250            "agent-spec",
1251            1,
1252            AgentSpecV1,
1253            "AgentSpecV1",
1254            "P26",
1255            Some("tests/fixtures/p26/agent_spec_v1.json"),
1256            "local-agent-declarative-contract-major-immutable"
1257        ),
1258        schema_document!(
1259            "api-honesty-receipt",
1260            1,
1261            ApiHonestyReportV1,
1262            "ApiHonestyReportV1",
1263            "P01",
1264            Some("tests/fixtures/p01/api_honesty_receipt_v1.json"),
1265            "additive-compatible-with-defaults"
1266        ),
1267        schema_document!(
1268            "approval-decision",
1269            1,
1270            ApprovalDecisionV1,
1271            "ApprovalDecisionV1",
1272            "P04",
1273            Some("tests/fixtures/p04/approval_decision_v1.json"),
1274            "append-only-approval-outcome"
1275        ),
1276        schema_document!(
1277            "approval-request",
1278            1,
1279            ApprovalRequestV1,
1280            "ApprovalRequestV1",
1281            "P04",
1282            Some("tests/fixtures/p04/approval_request_v1.json"),
1283            "scope-additions-require-defaults"
1284        ),
1285        schema_document!(
1286            "artifact-family-registry",
1287            1,
1288            ArtifactFamilyRegistryV1,
1289            "ArtifactFamilyRegistryV1",
1290            "P07",
1291            Some("tests/fixtures/p07/artifact_family_registry_v1.json"),
1292            "registered-family-additions-only"
1293        ),
1294        schema_document!(
1295            "artifact-envelope",
1296            1,
1297            ArtifactEnvelopeV1,
1298            "ArtifactEnvelopeV1",
1299            "P30",
1300            None,
1301            "aidens-local-envelope-additive-compatible"
1302        ),
1303        schema_document!(
1304            "backfill-receipt",
1305            1,
1306            BackfillReportV1,
1307            "BackfillReportV1",
1308            "P07",
1309            Some("tests/fixtures/p07/backfill_receipt_v1.json"),
1310            "append-only-backfill-evidence"
1311        ),
1312        schema_document!(
1313            "boundary-compile-outcome",
1314            1,
1315            BoundaryCompileOutcomeV1,
1316            "BoundaryCompileOutcomeV1",
1317            "P06",
1318            Some("tests/fixtures/p06/boundary_compile_outcome_v1.json"),
1319            "additive-compatible-with-defaults"
1320        ),
1321        schema_document!(
1322            "boundary-compile-request",
1323            1,
1324            BoundaryCompileRequestV1,
1325            "BoundaryCompileRequestV1",
1326            "P06",
1327            Some("tests/fixtures/p06/boundary_compile_request_v1.json"),
1328            "policy-field-additions-require-defaults"
1329        ),
1330        schema_document!(
1331            "budget-exhaustion-receipt",
1332            1,
1333            BudgetExhaustionReportV1,
1334            "BudgetExhaustionReportV1",
1335            "P03",
1336            Some("tests/fixtures/p03/budget_exhaustion_receipt_v1.json"),
1337            "receipt-major-immutable"
1338        ),
1339        schema_document!(
1340            "display-digest",
1341            1,
1342            DisplayDigestV1,
1343            "DisplayDigestV1",
1344            "P06",
1345            Some("tests/fixtures/p06/display_digest_v1.json"),
1346            "algorithm-change-requires-new-version"
1347        ),
1348        schema_document!(
1349            "capability-gate-decision",
1350            1,
1351            CapabilityGateDecisionV1,
1352            "CapabilityGateDecisionV1",
1353            "P04",
1354            Some("tests/fixtures/p04/capability_gate_decision_v1.json"),
1355            "additive-compatible-with-defaults"
1356        ),
1357        schema_document!(
1358            "config-apply-receipt",
1359            1,
1360            ConfigApplyReportV1,
1361            "ConfigApplyReportV1",
1362            "P01",
1363            Some("tests/fixtures/p01/config_apply_receipt_v1.json"),
1364            "receipt-major-immutable"
1365        ),
1366        schema_document!(
1367            "codex-packet",
1368            1,
1369            CodexPacketV1,
1370            "CodexPacketV1",
1371            "P10",
1372            Some("tests/fixtures/p10/codex_packet_v1.json"),
1373            "handoff-context-major-immutable"
1374        ),
1375        schema_document!(
1376            "command-run-receipt",
1377            1,
1378            CommandRunReportV1,
1379            "CommandRunReportV1",
1380            "P10",
1381            Some("tests/fixtures/p10/command_run_receipt_v1.json"),
1382            "command-evidence-major-immutable"
1383        ),
1384        schema_document!(
1385            "completion-audit-report",
1386            1,
1387            CompletionAuditReportV1,
1388            "CompletionAuditReportV1",
1389            "P19",
1390            Some("tests/fixtures/p19/completion_audit_report_v1.json"),
1391            "completion-state-and-release-bar-major-immutable"
1392        ),
1393        schema_document!(
1394            "example-app-manifest",
1395            1,
1396            ExampleAppManifestV1,
1397            "ExampleAppManifestV1",
1398            "P14",
1399            Some("tests/fixtures/p14/example_app_manifest_v1.json"),
1400            "example-coverage-major-immutable"
1401        ),
1402        schema_document!(
1403            "duplicate-key-finding",
1404            1,
1405            DuplicateKeyFindingV1,
1406            "DuplicateKeyFindingV1",
1407            "P06",
1408            Some("tests/fixtures/p06/duplicate_key_finding_v1.json"),
1409            "finding-major-immutable"
1410        ),
1411        schema_document!(
1412            "cross-pass-traceability-matrix",
1413            1,
1414            CrossPassTraceabilityMatrixV1,
1415            "CrossPassTraceabilityMatrixV1",
1416            "P19",
1417            Some("tests/fixtures/p19/cross_pass_traceability_matrix_v1.json"),
1418            "requirement-evidence-mapping-major-immutable"
1419        ),
1420        schema_document!(
1421            "fake-ready-finding",
1422            1,
1423            FakeReadyFindingV1,
1424            "FakeReadyFindingV1",
1425            "P00",
1426            Some("tests/fixtures/p00/fake_ready_finding_v1.json"),
1427            "finding-major-immutable"
1428        ),
1429        schema_document!(
1430            "generated-schema-manifest",
1431            1,
1432            GeneratedSchemaManifestV1,
1433            "GeneratedSchemaManifestV1",
1434            "P07",
1435            Some("tests/fixtures/p07/generated_schema_manifest_v1.json"),
1436            "generated-output-major-immutable"
1437        ),
1438        schema_document!(
1439            "golden-fixture-manifest",
1440            1,
1441            GoldenFixtureManifestV1,
1442            "GoldenFixtureManifestV1",
1443            "P08",
1444            Some("tests/fixtures/reference/golden_fixture_manifest_v1.json"),
1445            "fixture-coverage-major-immutable"
1446        ),
1447        schema_document!(
1448            "install-smoke-report",
1449            1,
1450            InstallSmokeReportV1,
1451            "InstallSmokeReportV1",
1452            "P14",
1453            Some("tests/fixtures/p14/install_smoke_receipt_v1.json"),
1454            "operator-smoke-evidence-major-immutable"
1455        ),
1456        schema_document!(
1457            "known-limitations-register",
1458            1,
1459            KnownLimitationsRegisterV1,
1460            "KnownLimitationsRegisterV1",
1461            "P19",
1462            Some("tests/fixtures/p19/known_limitations_register_v1.json"),
1463            "limitations-disclosure-major-immutable"
1464        ),
1465        schema_document!(
1466            "job",
1467            1,
1468            JobV1,
1469            "JobV1",
1470            "P11",
1471            Some("tests/fixtures/p11/job_v1.json"),
1472            "job-identity-idempotency-major-immutable"
1473        ),
1474        schema_document!(
1475            "queue-lease",
1476            1,
1477            QueueLeaseV1,
1478            "QueueLeaseV1",
1479            "P11",
1480            Some("tests/fixtures/p11/queue_lease_v1.json"),
1481            "lease-owner-expiry-major-immutable"
1482        ),
1483        schema_document!(
1484            "daemon-namespace",
1485            1,
1486            DaemonNamespaceV1,
1487            "DaemonNamespaceV1",
1488            "P11",
1489            Some("tests/fixtures/p11/daemon_namespace_v1.json"),
1490            "daemon-namespace-owner-major-immutable"
1491        ),
1492        schema_document!(
1493            "duplicate-suppression-receipt",
1494            1,
1495            DuplicateSuppressionReportV1,
1496            "DuplicateSuppressionReportV1",
1497            "P11",
1498            Some("tests/fixtures/p11/duplicate_suppression_receipt_v1.json"),
1499            "idempotency-suppression-major-immutable"
1500        ),
1501        schema_document!(
1502            "migration-plan",
1503            1,
1504            MigrationPlanV1,
1505            "MigrationPlanV1",
1506            "P07",
1507            Some("tests/fixtures/p07/migration_plan_v1.json"),
1508            "phase-law-major-immutable"
1509        ),
1510        schema_document!(
1511            "patch-apply-receipt",
1512            1,
1513            PatchApplyReportV1,
1514            "PatchApplyReportV1",
1515            "P10",
1516            Some("tests/fixtures/p10/patch_apply_receipt_v1.json"),
1517            "write-evidence-major-immutable"
1518        ),
1519        schema_document!(
1520            "operator-status-report",
1521            1,
1522            OperatorStatusReportV1,
1523            "OperatorStatusReportV1",
1524            "P14",
1525            Some("tests/fixtures/p14/operator_status_report_v1.json"),
1526            "operator-diagnostics-additive-compatible"
1527        ),
1528        schema_document!(
1529            "patch-proposal",
1530            1,
1531            PatchProposalV1,
1532            "PatchProposalV1",
1533            "P10",
1534            Some("tests/fixtures/p10/patch_proposal_v1.json"),
1535            "proposal-is-non-mutating"
1536        ),
1537        schema_document!(
1538            "permit-grant",
1539            1,
1540            PermitGrantV1,
1541            "PermitGrantV1",
1542            "P04",
1543            Some("tests/fixtures/p04/permit_grant_v1.json"),
1544            "scope-narrowing-requires-new-version"
1545        ),
1546        schema_document!(
1547            "permit-use-receipt",
1548            1,
1549            PermitUseReportV1,
1550            "PermitUseReportV1",
1551            "P04",
1552            Some("tests/fixtures/p04/permit_use_receipt_v1.json"),
1553            "receipt-major-immutable"
1554        ),
1555        schema_document!(
1556            "plan-runtime-parity-report",
1557            1,
1558            PlanRuntimeParityReportV1,
1559            "PlanRuntimeParityReportV1",
1560            "P01",
1561            Some("tests/fixtures/p01/plan_runtime_parity_report_v1.json"),
1562            "additive-compatible-with-defaults"
1563        ),
1564        schema_document!(
1565            "queue-hop-receipt",
1566            1,
1567            QueueHopReportV1,
1568            "QueueHopReportV1",
1569            "P11",
1570            Some("tests/fixtures/p11/queue_hop_receipt_v1.json"),
1571            "queue-state-transition-major-immutable"
1572        ),
1573        schema_document!(
1574            "provider-backend-matrix",
1575            1,
1576            ProviderBackendMatrixV1,
1577            "ProviderBackendMatrixV1",
1578            "P02",
1579            Some("tests/fixtures/p02/provider_backend_matrix_v1.json"),
1580            "provider-additions-compatible"
1581        ),
1582        schema_document!(
1583            "provider-certification-fixture",
1584            1,
1585            ProviderCertificationFixtureV1,
1586            "ProviderCertificationFixtureV1",
1587            "P02",
1588            Some("tests/fixtures/p02/provider_certification_fixture_v1.json"),
1589            "expectation-meaning-major-immutable"
1590        ),
1591        schema_document!(
1592            "provider-readiness-receipt",
1593            1,
1594            ProviderReadinessReportV1,
1595            "ProviderReadinessReportV1",
1596            "P02",
1597            Some("tests/fixtures/p02/provider_readiness_receipt_v1.json"),
1598            "receipt-major-immutable"
1599        ),
1600        schema_document!(
1601            "provider-route-receipt",
1602            2,
1603            ProviderRouteReportV2,
1604            "ProviderRouteReportV2",
1605            "P02",
1606            Some("tests/fixtures/p02/provider_route_receipt_v2.json"),
1607            "native-capability-major-immutable"
1608        ),
1609        schema_document!(
1610            "release-readiness-report",
1611            1,
1612            ReleaseReadinessReportV1,
1613            "ReleaseReadinessReportV1",
1614            "P14",
1615            Some("tests/fixtures/p14/release_readiness_report_v1.json"),
1616            "release-blocking-semantics-major-immutable"
1617        ),
1618        schema_document!(
1619            "release-artifact-manifest",
1620            1,
1621            ReleaseArtifactManifestV1,
1622            "ReleaseArtifactManifestV1",
1623            "P19",
1624            Some("tests/fixtures/p19/release_artifact_manifest_v1.json"),
1625            "release-package-content-major-immutable"
1626        ),
1627        schema_document!(
1628            "regression-debt-ledger",
1629            1,
1630            RegressionDebtLedgerV1,
1631            "RegressionDebtLedgerV1",
1632            "P19",
1633            Some("tests/fixtures/p19/regression_debt_ledger_v1.json"),
1634            "regression-debt-status-major-immutable"
1635        ),
1636        schema_document!(
1637            "repo-list-receipt",
1638            1,
1639            RepoListReportV1,
1640            "RepoListReportV1",
1641            "P10",
1642            Some("tests/fixtures/p10/repo_list_receipt_v1.json"),
1643            "sandbox-list-evidence-major-immutable"
1644        ),
1645        schema_document!(
1646            "repo-read-receipt",
1647            1,
1648            RepoReadReportV1,
1649            "RepoReadReportV1",
1650            "P10",
1651            Some("tests/fixtures/p10/repo_read_receipt_v1.json"),
1652            "sandbox-read-evidence-major-immutable"
1653        ),
1654        schema_document!(
1655            "run-receipt",
1656            1,
1657            RunReportV1,
1658            "RunReportV1",
1659            "P05",
1660            Some("tests/fixtures/p05/run_receipt_v1.json"),
1661            "receipt-major-immutable"
1662        ),
1663        schema_document!(
1664            "aidens-run-bundle",
1665            2,
1666            AiDENsRunBundleV2,
1667            "AiDENsRunBundleV2",
1668            "P24",
1669            Some("tests/fixtures/p24/aidens_run_bundle_v2.json"),
1670            "operator-bundle-major-immutable"
1671        ),
1672        schema_document!(
1673            "aidens-run-bundle",
1674            3,
1675            AiDENsRunBundleV3,
1676            "AiDENsRunBundleV3",
1677            "P26",
1678            Some("tests/fixtures/p26/aidens_run_bundle_v3.json"),
1679            "operator-bundle-major-immutable"
1680        ),
1681        schema_document!(
1682            "scaffold-surface-report",
1683            1,
1684            ScaffoldSurfaceReportV1,
1685            "ScaffoldSurfaceReportV1",
1686            "P00",
1687            Some("tests/fixtures/p00/scaffold_surface_report_v1.json"),
1688            "status-meaning-major-immutable"
1689        ),
1690        schema_document!(
1691            "schema-compatibility-report",
1692            1,
1693            SchemaCompatibilityReportV1,
1694            "SchemaCompatibilityReportV1",
1695            "P07",
1696            Some("tests/fixtures/p07/schema_compatibility_report_v1.json"),
1697            "report-additions-compatible"
1698        ),
1699        schema_document!(
1700            "sandbox-capability-truth",
1701            1,
1702            SandboxCapabilityTruthV1,
1703            "SandboxCapabilityTruthV1",
1704            "P10",
1705            Some("tests/fixtures/p10/sandbox_capability_truth_v1.json"),
1706            "sandbox-policy-major-immutable"
1707        ),
1708        schema_document!(
1709            "safe-mode-receipt",
1710            1,
1711            SafeModeReportV1,
1712            "SafeModeReportV1",
1713            "P11",
1714            Some("tests/fixtures/p11/safe_mode_receipt_v1.json"),
1715            "safe-mode-law-major-immutable"
1716        ),
1717        schema_document!(
1718            "schedule-occurrence",
1719            1,
1720            ScheduleOccurrenceV1,
1721            "ScheduleOccurrenceV1",
1722            "P11",
1723            Some("tests/fixtures/p11/schedule_occurrence_v1.json"),
1724            "occurrence-idempotency-major-immutable"
1725        ),
1726        schema_document!(
1727            "source-basis-lock",
1728            1,
1729            SourceBasisLockV1,
1730            "SourceBasisLockV1",
1731            "P00",
1732            Some("tests/fixtures/p00/source_basis_lock_v1.json"),
1733            "source-basis-major-immutable"
1734        ),
1735        schema_document!(
1736            "super-pass-status",
1737            1,
1738            SuperPassStatusV1,
1739            "SuperPassStatusV1",
1740            "P00",
1741            Some("tests/fixtures/p00/super_pass_status_v1.json"),
1742            "status-meaning-major-immutable"
1743        ),
1744        schema_document!(
1745            "tool-call-request",
1746            1,
1747            ToolCallRequestV1,
1748            "ToolCallRequestV1",
1749            "P03",
1750            Some("tests/fixtures/p03/tool_call_request_v1.json"),
1751            "parser-fallback-meaning-major-immutable"
1752        ),
1753        schema_document!(
1754            "tool-call-result",
1755            1,
1756            ToolCallResultV1,
1757            "ToolCallResultV1",
1758            "P03",
1759            Some("tests/fixtures/p03/tool_call_result_v1.json"),
1760            "digest-link-major-immutable"
1761        ),
1762        schema_document!(
1763            "tool-exposure-plan",
1764            2,
1765            ToolExposurePlanV1,
1766            "ToolExposurePlanV2",
1767            "P04",
1768            Some("tests/fixtures/p04/tool_exposure_plan_v2.json"),
1769            "lifecycle-meaning-major-immutable"
1770        ),
1771        schema_document!(
1772            "tool-invocation-receipt",
1773            1,
1774            ToolInvocationReportV1,
1775            "ToolInvocationReportV1",
1776            "P03",
1777            Some("tests/fixtures/p03/tool_invocation_receipt_v1.json"),
1778            "receipt-major-immutable"
1779        ),
1780        schema_document!(
1781            "turn-execution-plan",
1782            1,
1783            TurnExecutionPlanV1,
1784            "TurnExecutionPlanV1",
1785            "P03",
1786            Some("tests/fixtures/p03/turn_execution_plan_v1.json"),
1787            "mode-meaning-major-immutable"
1788        ),
1789        schema_document!(
1790            "turn-receipt",
1791            1,
1792            TurnReportV1,
1793            "TurnReportV1",
1794            "P03",
1795            Some("tests/fixtures/p03/turn_receipt_v1.json"),
1796            "final-state-major-immutable"
1797        ),
1798        schema_document!(
1799            "wake-signal",
1800            1,
1801            WakeSignalV1,
1802            "WakeSignalV1",
1803            "P11",
1804            Some("tests/fixtures/p11/wake_signal_v1.json"),
1805            "wake-idempotency-major-immutable"
1806        ),
1807    ]
1808}