Skip to main content

aidens_contracts/
release_completion.rs

1//! Release readiness, traceability, limitations, debt, and completion audit artifacts.
2//!
3//! These gates prevent false-green release claims and do not waive canonical proof obligations.
4
5use super::*;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
8#[serde(rename_all = "kebab-case")]
9pub enum ReleaseSurfaceStateV1 {
10    Supported,
11    Partial,
12    Deferred,
13    Degraded,
14    Blocked,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18pub struct ReleaseSurfaceV1 {
19    pub surface_id: String,
20    pub state: ReleaseSurfaceStateV1,
21    pub reason: String,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub command: Option<String>,
24}
25
26impl ReleaseSurfaceV1 {
27    pub fn new(
28        surface_id: impl Into<String>,
29        state: ReleaseSurfaceStateV1,
30        reason: impl Into<String>,
31    ) -> Self {
32        Self {
33            surface_id: surface_id.into(),
34            state,
35            reason: reason.into(),
36            command: None,
37        }
38    }
39
40    pub fn with_command(mut self, command: impl Into<String>) -> Self {
41        self.command = Some(command.into());
42        self
43    }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47pub struct PublicDocFindingV1 {
48    pub path: String,
49    pub line: u32,
50    pub surface_id: String,
51    pub reason_code: String,
52    pub excerpt: String,
53}
54
55impl PublicDocFindingV1 {
56    pub fn scaffold_claim(
57        path: impl Into<String>,
58        line: u32,
59        surface_id: impl Into<String>,
60        excerpt: impl Into<String>,
61    ) -> Self {
62        Self {
63            path: path.into(),
64            line,
65            surface_id: surface_id.into(),
66            reason_code: "public-doc-claims-scaffold-complete".into(),
67            excerpt: excerpt.into(),
68        }
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73pub struct ExampleAppEntryV1 {
74    pub example_id: ArtifactId,
75    pub path: String,
76    pub profile_id: String,
77    pub provider_kind: String,
78    pub memory_mode: MemoryModeV1,
79    pub status: ReleaseSurfaceStateV1,
80    #[serde(default, skip_serializing_if = "Vec::is_empty")]
81    pub commands: Vec<String>,
82    #[serde(default, skip_serializing_if = "Vec::is_empty")]
83    pub reason_codes: Vec<String>,
84}
85
86impl ExampleAppEntryV1 {
87    pub fn new(
88        path: impl Into<String>,
89        profile_id: impl Into<String>,
90        provider_kind: impl Into<String>,
91        memory_mode: MemoryModeV1,
92        status: ReleaseSurfaceStateV1,
93    ) -> Self {
94        Self {
95            example_id: display_only_unstable_id("example-app"),
96            path: path.into(),
97            profile_id: profile_id.into(),
98            provider_kind: provider_kind.into(),
99            memory_mode,
100            status,
101            commands: Vec::new(),
102            reason_codes: vec!["example-profile-declared".into()],
103        }
104    }
105
106    pub fn with_command(mut self, command: impl Into<String>) -> Self {
107        self.commands.push(command.into());
108        self
109    }
110
111    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
112        self.reason_codes.push(reason.into());
113        self.reason_codes.sort();
114        self.reason_codes.dedup();
115        self
116    }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
120pub struct ExampleAppManifestV1 {
121    pub manifest_id: ArtifactId,
122    pub kind: ArtifactKindV1,
123    pub examples: Vec<ExampleAppEntryV1>,
124    pub profiles_covered: Vec<String>,
125    pub unsupported_advanced_features: Vec<String>,
126    #[serde(default, skip_serializing_if = "Vec::is_empty")]
127    pub reason_codes: Vec<String>,
128    pub generated_at: DateTime<Utc>,
129}
130
131impl ExampleAppManifestV1 {
132    pub fn new(
133        mut examples: Vec<ExampleAppEntryV1>,
134        mut unsupported_advanced_features: Vec<String>,
135    ) -> Self {
136        examples.sort_by(|left, right| left.path.cmp(&right.path));
137        let mut profiles_covered = examples
138            .iter()
139            .map(|example| example.profile_id.clone())
140            .collect::<Vec<_>>();
141        profiles_covered.sort();
142        profiles_covered.dedup();
143        unsupported_advanced_features.sort();
144        unsupported_advanced_features.dedup();
145        Self {
146            manifest_id: display_only_unstable_id("example-app-manifest"),
147            kind: ArtifactKindV1::ExampleAppManifest,
148            examples,
149            profiles_covered,
150            unsupported_advanced_features,
151            reason_codes: vec!["examples-declare-supported-and-deferred-surfaces".into()],
152            generated_at: Utc::now(),
153        }
154    }
155
156    pub fn covers_profile(&self, profile_id: &str) -> bool {
157        self.profiles_covered
158            .iter()
159            .any(|profile| profile == profile_id)
160    }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
164pub struct InstallSmokeStepV1 {
165    pub step: String,
166    pub command: String,
167    pub passed: bool,
168    pub output_digest: String,
169    #[serde(default, skip_serializing_if = "Vec::is_empty")]
170    pub reason_codes: Vec<String>,
171}
172
173impl InstallSmokeStepV1 {
174    pub fn passed(step: impl Into<String>, command: impl Into<String>, output: &str) -> Self {
175        Self {
176            step: step.into(),
177            command: command.into(),
178            passed: true,
179            output_digest: non_authoritative_text_display_digest(output),
180            reason_codes: vec!["install-smoke-step-passed".into()],
181        }
182    }
183
184    pub fn failed(
185        step: impl Into<String>,
186        command: impl Into<String>,
187        output: &str,
188        reason: impl Into<String>,
189    ) -> Self {
190        Self {
191            step: step.into(),
192            command: command.into(),
193            passed: false,
194            output_digest: non_authoritative_text_display_digest(output),
195            reason_codes: vec![reason.into()],
196        }
197    }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
201pub struct InstallSmokeReportV1 {
202    pub receipt_id: ArtifactId,
203    pub kind: ArtifactKindV1,
204    pub passed: bool,
205    pub steps: Vec<InstallSmokeStepV1>,
206    #[serde(default, skip_serializing_if = "Vec::is_empty")]
207    pub reason_codes: Vec<String>,
208    pub recorded_at: DateTime<Utc>,
209}
210
211impl InstallSmokeReportV1 {
212    pub fn new(steps: Vec<InstallSmokeStepV1>) -> Self {
213        let passed = steps.iter().all(|step| step.passed);
214        Self {
215            receipt_id: display_only_unstable_id("install-smoke"),
216            kind: ArtifactKindV1::InstallSmokeReport,
217            passed,
218            steps,
219            reason_codes: if passed {
220                vec!["install-smoke-passed".into()]
221            } else {
222                vec!["install-smoke-failed".into()]
223            },
224            recorded_at: Utc::now(),
225        }
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
230pub struct OperatorStatusReportV1 {
231    pub report_id: ArtifactId,
232    pub kind: ArtifactKindV1,
233    pub app_id: String,
234    pub config_status: String,
235    pub provider_route_label: String,
236    pub memory_mode: MemoryModeV1,
237    pub receipt_store_configured: bool,
238    pub doctor: AiDENsDoctorReportV1,
239    #[serde(default, skip_serializing_if = "Vec::is_empty")]
240    pub degraded_modes: Vec<String>,
241    #[serde(default, skip_serializing_if = "Vec::is_empty")]
242    pub blocked_modes: Vec<String>,
243    #[serde(default, skip_serializing_if = "Vec::is_empty")]
244    pub next_commands: Vec<String>,
245    pub generated_at: DateTime<Utc>,
246}
247
248impl OperatorStatusReportV1 {
249    pub fn new(
250        app_id: impl Into<String>,
251        config_status: impl Into<String>,
252        provider_route_label: impl Into<String>,
253        memory_mode: MemoryModeV1,
254        receipt_store_configured: bool,
255        doctor: AiDENsDoctorReportV1,
256    ) -> Self {
257        let mut degraded_modes = Vec::new();
258        let mut blocked_modes = Vec::new();
259        for truth in doctor.sections.values().flat_map(|section| section.iter()) {
260            if truth.states.contains(&CapabilityStateV1::Degraded) {
261                degraded_modes.push(truth.capability_id.clone());
262            }
263            if truth.states.contains(&CapabilityStateV1::BlockedByPolicy)
264                || truth.states.contains(&CapabilityStateV1::Unavailable)
265            {
266                blocked_modes.push(truth.capability_id.clone());
267            }
268        }
269        degraded_modes.sort();
270        degraded_modes.dedup();
271        blocked_modes.sort();
272        blocked_modes.dedup();
273        Self {
274            report_id: display_only_unstable_id("operator-status"),
275            kind: ArtifactKindV1::OperatorStatusReport,
276            app_id: app_id.into(),
277            config_status: config_status.into(),
278            provider_route_label: provider_route_label.into(),
279            memory_mode,
280            receipt_store_configured,
281            doctor,
282            degraded_modes,
283            blocked_modes,
284            next_commands: vec![
285                "aidens provider-check --config <config>".into(),
286                "aidens inspect-tools --config <config>".into(),
287                "aidens receipts list --config <config>".into(),
288            ],
289            generated_at: Utc::now(),
290        }
291    }
292
293    pub fn exposes_degraded_modes(&self) -> bool {
294        !self.degraded_modes.is_empty() || !self.blocked_modes.is_empty()
295    }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
299pub struct ReleaseReadinessReportV1 {
300    pub report_id: ArtifactId,
301    pub kind: ArtifactKindV1,
302    pub ready: bool,
303    pub surfaces: Vec<ReleaseSurfaceV1>,
304    #[serde(default, skip_serializing_if = "Vec::is_empty")]
305    pub public_doc_findings: Vec<PublicDocFindingV1>,
306    pub example_manifest: ExampleAppManifestV1,
307    pub install_smoke: InstallSmokeReportV1,
308    #[serde(default, skip_serializing_if = "Vec::is_empty")]
309    pub reason_codes: Vec<String>,
310    pub generated_at: DateTime<Utc>,
311}
312
313impl ReleaseReadinessReportV1 {
314    pub fn new(
315        mut surfaces: Vec<ReleaseSurfaceV1>,
316        public_doc_findings: Vec<PublicDocFindingV1>,
317        example_manifest: ExampleAppManifestV1,
318        install_smoke: InstallSmokeReportV1,
319    ) -> Self {
320        surfaces.sort_by(|left, right| left.surface_id.cmp(&right.surface_id));
321        let has_blocked_surface = surfaces
322            .iter()
323            .any(|surface| surface.state == ReleaseSurfaceStateV1::Blocked);
324        let has_degraded_surface = surfaces
325            .iter()
326            .any(|surface| surface.state == ReleaseSurfaceStateV1::Degraded);
327        let ready = public_doc_findings.is_empty()
328            && install_smoke.passed
329            && !has_blocked_surface
330            && !has_degraded_surface;
331        let mut reason_codes = if ready {
332            vec!["release-readiness-passed".into()]
333        } else {
334            vec!["release-readiness-blocked".into()]
335        };
336        if has_blocked_surface {
337            reason_codes.push("blocked-surfaces-present".into());
338        }
339        if has_degraded_surface {
340            reason_codes.push("degraded-surfaces-require-explicit-waiver".into());
341        }
342        reason_codes.sort();
343        reason_codes.dedup();
344        Self {
345            report_id: display_only_unstable_id("release-readiness"),
346            kind: ArtifactKindV1::ReleaseReadinessReport,
347            ready,
348            surfaces,
349            public_doc_findings,
350            example_manifest,
351            install_smoke,
352            reason_codes,
353            generated_at: Utc::now(),
354        }
355    }
356
357    pub fn blocks_release(&self) -> bool {
358        !self.ready
359    }
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
363#[serde(rename_all = "kebab-case")]
364pub enum CompletionAuditStateV1 {
365    Complete,
366    NearComplete,
367    DeferredHorizon,
368    Blocked,
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
372#[serde(rename_all = "kebab-case")]
373pub enum PassCompletionStateV1 {
374    Done,
375    Partial,
376    Deferred,
377    Blocked,
378    Waived,
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
382#[serde(rename_all = "kebab-case")]
383pub enum GateCommandStatusV1 {
384    Passed,
385    Failed,
386    Waived,
387    NotRun,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
391pub struct GateCommandResultV1 {
392    pub command: String,
393    pub status: GateCommandStatusV1,
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub exit_code: Option<i32>,
396    #[serde(default, skip_serializing_if = "Option::is_none")]
397    pub output_digest: Option<String>,
398    #[serde(default, skip_serializing_if = "Option::is_none")]
399    pub evidence_ref: Option<String>,
400    #[serde(default, skip_serializing_if = "Vec::is_empty")]
401    pub reason_codes: Vec<String>,
402    pub completed_at: DateTime<Utc>,
403}
404
405impl GateCommandResultV1 {
406    pub fn passed(command: impl Into<String>, evidence_ref: impl Into<String>) -> Self {
407        Self {
408            command: command.into(),
409            status: GateCommandStatusV1::Passed,
410            exit_code: Some(0),
411            output_digest: None,
412            evidence_ref: Some(evidence_ref.into()),
413            reason_codes: vec!["gate-command-passed".into()],
414            completed_at: Utc::now(),
415        }
416    }
417
418    pub fn failed(
419        command: impl Into<String>,
420        exit_code: i32,
421        output: impl Into<String>,
422        reason: impl Into<String>,
423    ) -> Self {
424        Self {
425            command: command.into(),
426            status: GateCommandStatusV1::Failed,
427            exit_code: Some(exit_code),
428            output_digest: Some(non_authoritative_text_display_digest(&output.into())),
429            evidence_ref: None,
430            reason_codes: vec![reason.into()],
431            completed_at: Utc::now(),
432        }
433    }
434
435    pub fn not_run(command: impl Into<String>, reason: impl Into<String>) -> Self {
436        Self {
437            command: command.into(),
438            status: GateCommandStatusV1::NotRun,
439            exit_code: None,
440            output_digest: None,
441            evidence_ref: None,
442            reason_codes: vec![reason.into()],
443            completed_at: Utc::now(),
444        }
445    }
446
447    pub fn waived(command: impl Into<String>, waiver_receipt_id: impl Into<String>) -> Self {
448        Self {
449            command: command.into(),
450            status: GateCommandStatusV1::Waived,
451            exit_code: None,
452            output_digest: None,
453            evidence_ref: Some(waiver_receipt_id.into()),
454            reason_codes: vec!["gate-command-waived".into()],
455            completed_at: Utc::now(),
456        }
457    }
458
459    pub fn is_satisfied(&self) -> bool {
460        matches!(
461            self.status,
462            GateCommandStatusV1::Passed | GateCommandStatusV1::Waived
463        )
464    }
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
468#[serde(rename_all = "kebab-case")]
469pub enum ReleaseArtifactKindV1 {
470    Source,
471    Document,
472    Schema,
473    Fixture,
474    Example,
475    Script,
476    Ci,
477    Handoff,
478    Manifest,
479    Generated,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
483pub struct ReleaseArtifactEntryV1 {
484    pub path: String,
485    pub artifact_kind: ReleaseArtifactKindV1,
486    pub required: bool,
487    #[serde(default, skip_serializing_if = "Option::is_none")]
488    pub content_digest: Option<String>,
489    pub byte_len: u64,
490    #[serde(default, skip_serializing_if = "Vec::is_empty")]
491    pub reason_codes: Vec<String>,
492}
493
494impl ReleaseArtifactEntryV1 {
495    pub fn present(
496        path: impl Into<String>,
497        artifact_kind: ReleaseArtifactKindV1,
498        required: bool,
499        content_digest: impl Into<String>,
500        byte_len: u64,
501    ) -> Self {
502        Self {
503            path: path.into(),
504            artifact_kind,
505            required,
506            content_digest: Some(content_digest.into()),
507            byte_len,
508            reason_codes: vec!["release-artifact-present".into()],
509        }
510    }
511
512    pub fn missing(
513        path: impl Into<String>,
514        artifact_kind: ReleaseArtifactKindV1,
515        required: bool,
516    ) -> Self {
517        Self {
518            path: path.into(),
519            artifact_kind,
520            required,
521            content_digest: None,
522            byte_len: 0,
523            reason_codes: vec!["release-artifact-missing".into()],
524        }
525    }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
529pub struct ReleaseArtifactManifestV1 {
530    pub manifest_id: ArtifactId,
531    pub kind: ArtifactKindV1,
532    pub package_name: String,
533    pub source_basis: String,
534    pub artifacts: Vec<ReleaseArtifactEntryV1>,
535    pub missing_required_paths: Vec<String>,
536    pub complete: bool,
537    #[serde(default, skip_serializing_if = "Vec::is_empty")]
538    pub reason_codes: Vec<String>,
539    pub generated_at: DateTime<Utc>,
540}
541
542impl ReleaseArtifactManifestV1 {
543    pub fn new(
544        package_name: impl Into<String>,
545        source_basis: impl Into<String>,
546        mut artifacts: Vec<ReleaseArtifactEntryV1>,
547    ) -> Self {
548        artifacts.sort_by(|left, right| left.path.cmp(&right.path));
549        let missing_required_paths = artifacts
550            .iter()
551            .filter(|artifact| artifact.required && artifact.content_digest.is_none())
552            .map(|artifact| artifact.path.clone())
553            .collect::<Vec<_>>();
554        let complete = missing_required_paths.is_empty();
555        Self {
556            manifest_id: display_only_unstable_id("release-artifact-manifest"),
557            kind: ArtifactKindV1::ReleaseArtifactManifest,
558            package_name: package_name.into(),
559            source_basis: source_basis.into(),
560            artifacts,
561            missing_required_paths,
562            complete,
563            reason_codes: if complete {
564                vec!["release-artifact-manifest-complete".into()]
565            } else {
566                vec!["release-artifact-manifest-missing-required".into()]
567            },
568            generated_at: Utc::now(),
569        }
570    }
571
572    pub fn blocks_release(&self) -> bool {
573        !self.complete
574    }
575}
576
577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
578pub struct CrossPassTraceabilityRowV1 {
579    pub requirement_id: String,
580    pub pass_id: String,
581    pub requirement: String,
582    pub state: PassCompletionStateV1,
583    pub crates: Vec<String>,
584    pub artifact_families: Vec<String>,
585    pub tests: Vec<String>,
586    pub docs: Vec<String>,
587    pub acceptance_gates: Vec<String>,
588    pub evidence_refs: Vec<String>,
589    #[serde(default, skip_serializing_if = "Vec::is_empty")]
590    pub waiver_receipt_ids: Vec<String>,
591    #[serde(default, skip_serializing_if = "Vec::is_empty")]
592    pub reason_codes: Vec<String>,
593}
594
595impl CrossPassTraceabilityRowV1 {
596    pub fn new(
597        requirement_id: impl Into<String>,
598        pass_id: impl Into<String>,
599        requirement: impl Into<String>,
600        state: PassCompletionStateV1,
601    ) -> Self {
602        Self {
603            requirement_id: requirement_id.into(),
604            pass_id: pass_id.into(),
605            requirement: requirement.into(),
606            state,
607            crates: Vec::new(),
608            artifact_families: Vec::new(),
609            tests: Vec::new(),
610            docs: Vec::new(),
611            acceptance_gates: Vec::new(),
612            evidence_refs: Vec::new(),
613            waiver_receipt_ids: Vec::new(),
614            reason_codes: vec!["traceability-row-declared".into()],
615        }
616    }
617
618    pub fn with_crates(mut self, crates: Vec<String>) -> Self {
619        self.crates = crates;
620        self
621    }
622
623    pub fn with_artifacts(mut self, artifact_families: Vec<String>) -> Self {
624        self.artifact_families = artifact_families;
625        self
626    }
627
628    pub fn with_tests(mut self, tests: Vec<String>) -> Self {
629        self.tests = tests;
630        self
631    }
632
633    pub fn with_docs(mut self, docs: Vec<String>) -> Self {
634        self.docs = docs;
635        self
636    }
637
638    pub fn with_acceptance_gates(mut self, acceptance_gates: Vec<String>) -> Self {
639        self.acceptance_gates = acceptance_gates;
640        self
641    }
642
643    pub fn with_evidence(mut self, evidence_refs: Vec<String>) -> Self {
644        self.evidence_refs = evidence_refs;
645        self
646    }
647
648    pub fn with_waiver(mut self, waiver_receipt_id: impl Into<String>) -> Self {
649        self.waiver_receipt_ids.push(waiver_receipt_id.into());
650        self
651    }
652
653    pub fn is_satisfied(&self) -> bool {
654        matches!(
655            self.state,
656            PassCompletionStateV1::Done | PassCompletionStateV1::Waived
657        )
658    }
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
662pub struct CrossPassTraceabilityMatrixV1 {
663    pub matrix_id: ArtifactId,
664    pub kind: ArtifactKindV1,
665    pub rows: Vec<CrossPassTraceabilityRowV1>,
666    pub uncovered_requirement_ids: Vec<String>,
667    pub complete: bool,
668    #[serde(default, skip_serializing_if = "Vec::is_empty")]
669    pub reason_codes: Vec<String>,
670    pub generated_at: DateTime<Utc>,
671}
672
673impl CrossPassTraceabilityMatrixV1 {
674    pub fn new(mut rows: Vec<CrossPassTraceabilityRowV1>) -> Self {
675        rows.sort_by(|left, right| left.requirement_id.cmp(&right.requirement_id));
676        let uncovered_requirement_ids = rows
677            .iter()
678            .filter(|row| {
679                !row.is_satisfied()
680                    || row.acceptance_gates.is_empty()
681                    || row.docs.is_empty()
682                    || row.tests.is_empty()
683                    || row.artifact_families.is_empty()
684            })
685            .map(|row| row.requirement_id.clone())
686            .collect::<Vec<_>>();
687        let complete = !rows.is_empty() && uncovered_requirement_ids.is_empty();
688        Self {
689            matrix_id: display_only_unstable_id("cross-pass-traceability-matrix"),
690            kind: ArtifactKindV1::CrossPassTraceabilityMatrix,
691            rows,
692            uncovered_requirement_ids,
693            complete,
694            reason_codes: if complete {
695                vec!["cross-pass-traceability-complete".into()]
696            } else {
697                vec!["cross-pass-traceability-has-gaps".into()]
698            },
699            generated_at: Utc::now(),
700        }
701    }
702
703    pub fn blocks_completion(&self) -> bool {
704        !self.complete
705    }
706}
707
708#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
709pub struct KnownLimitationV1 {
710    pub limitation_id: ArtifactId,
711    pub surface_id: String,
712    pub state: ReleaseSurfaceStateV1,
713    pub description: String,
714    pub impact: String,
715    #[serde(default, skip_serializing_if = "Option::is_none")]
716    pub workaround: Option<String>,
717    #[serde(default, skip_serializing_if = "Option::is_none")]
718    pub review_after_pass: Option<String>,
719    #[serde(default, skip_serializing_if = "Vec::is_empty")]
720    pub reason_codes: Vec<String>,
721}
722
723impl KnownLimitationV1 {
724    pub fn deferred(
725        surface_id: impl Into<String>,
726        description: impl Into<String>,
727        impact: impl Into<String>,
728    ) -> Self {
729        Self {
730            limitation_id: display_only_unstable_id("known-limitation"),
731            surface_id: surface_id.into(),
732            state: ReleaseSurfaceStateV1::Deferred,
733            description: description.into(),
734            impact: impact.into(),
735            workaround: None,
736            review_after_pass: None,
737            reason_codes: vec!["limitation-deferred-not-hidden".into()],
738        }
739    }
740
741    pub fn partial(
742        surface_id: impl Into<String>,
743        description: impl Into<String>,
744        impact: impl Into<String>,
745    ) -> Self {
746        Self {
747            limitation_id: display_only_unstable_id("known-limitation"),
748            surface_id: surface_id.into(),
749            state: ReleaseSurfaceStateV1::Partial,
750            description: description.into(),
751            impact: impact.into(),
752            workaround: None,
753            review_after_pass: None,
754            reason_codes: vec!["limitation-partial-not-hidden".into()],
755        }
756    }
757
758    pub fn blocked(
759        surface_id: impl Into<String>,
760        description: impl Into<String>,
761        impact: impl Into<String>,
762        reason: impl Into<String>,
763    ) -> Self {
764        Self {
765            limitation_id: display_only_unstable_id("known-limitation"),
766            surface_id: surface_id.into(),
767            state: ReleaseSurfaceStateV1::Blocked,
768            description: description.into(),
769            impact: impact.into(),
770            workaround: None,
771            review_after_pass: None,
772            reason_codes: vec![reason.into()],
773        }
774    }
775
776    pub fn with_workaround(mut self, workaround: impl Into<String>) -> Self {
777        self.workaround = Some(workaround.into());
778        self
779    }
780
781    pub fn with_review_after_pass(mut self, pass_id: impl Into<String>) -> Self {
782        self.review_after_pass = Some(pass_id.into());
783        self
784    }
785}
786
787#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
788pub struct KnownLimitationsRegisterV1 {
789    pub register_id: ArtifactId,
790    pub kind: ArtifactKindV1,
791    pub current: bool,
792    pub limitations: Vec<KnownLimitationV1>,
793    #[serde(default, skip_serializing_if = "Vec::is_empty")]
794    pub blocking_limitation_ids: Vec<ArtifactId>,
795    #[serde(default, skip_serializing_if = "Vec::is_empty")]
796    pub reason_codes: Vec<String>,
797    pub generated_at: DateTime<Utc>,
798}
799
800impl KnownLimitationsRegisterV1 {
801    pub fn new(mut limitations: Vec<KnownLimitationV1>) -> Self {
802        limitations.sort_by(|left, right| left.surface_id.cmp(&right.surface_id));
803        let blocking_limitation_ids = limitations
804            .iter()
805            .filter(|limitation| limitation.state == ReleaseSurfaceStateV1::Blocked)
806            .map(|limitation| limitation.limitation_id.clone())
807            .collect::<Vec<_>>();
808        let empty_register = limitations.is_empty();
809        let current = true;
810        Self {
811            register_id: display_only_unstable_id("known-limitations-register"),
812            kind: ArtifactKindV1::KnownLimitationsRegister,
813            current,
814            limitations,
815            blocking_limitation_ids,
816            reason_codes: if current {
817                if empty_register {
818                    vec!["known-limitations-empty-register-current".into()]
819                } else {
820                    vec!["known-limitations-current".into()]
821                }
822            } else {
823                vec!["known-limitations-register-empty".into()]
824            },
825            generated_at: Utc::now(),
826        }
827    }
828
829    pub fn blocks_completion(&self) -> bool {
830        !self.blocking_limitation_ids.is_empty()
831    }
832}
833
834#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
835#[serde(rename_all = "kebab-case")]
836pub enum RegressionDebtStatusV1 {
837    Guarded,
838    Accepted,
839    Deferred,
840    Blocked,
841}
842
843#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
844pub struct RegressionDebtItemV1 {
845    pub debt_id: ArtifactId,
846    pub surface_id: String,
847    pub status: RegressionDebtStatusV1,
848    pub description: String,
849    pub detection: String,
850    pub guardrail_tests: Vec<String>,
851    pub owner: String,
852    #[serde(default, skip_serializing_if = "Vec::is_empty")]
853    pub reason_codes: Vec<String>,
854}
855
856impl RegressionDebtItemV1 {
857    pub fn guarded(
858        surface_id: impl Into<String>,
859        description: impl Into<String>,
860        detection: impl Into<String>,
861        guardrail_tests: Vec<String>,
862    ) -> Self {
863        Self {
864            debt_id: display_only_unstable_id("regression-debt"),
865            surface_id: surface_id.into(),
866            status: RegressionDebtStatusV1::Guarded,
867            description: description.into(),
868            detection: detection.into(),
869            guardrail_tests,
870            owner: "release".into(),
871            reason_codes: vec!["regression-debt-guarded".into()],
872        }
873    }
874
875    pub fn blocked(
876        surface_id: impl Into<String>,
877        description: impl Into<String>,
878        detection: impl Into<String>,
879    ) -> Self {
880        Self {
881            debt_id: display_only_unstable_id("regression-debt"),
882            surface_id: surface_id.into(),
883            status: RegressionDebtStatusV1::Blocked,
884            description: description.into(),
885            detection: detection.into(),
886            guardrail_tests: Vec::new(),
887            owner: "release".into(),
888            reason_codes: vec!["regression-debt-blocks-release".into()],
889        }
890    }
891}
892
893#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
894pub struct RegressionDebtLedgerV1 {
895    pub ledger_id: ArtifactId,
896    pub kind: ArtifactKindV1,
897    pub items: Vec<RegressionDebtItemV1>,
898    #[serde(default, skip_serializing_if = "Vec::is_empty")]
899    pub blocking_debt_ids: Vec<ArtifactId>,
900    #[serde(default, skip_serializing_if = "Vec::is_empty")]
901    pub reason_codes: Vec<String>,
902    pub generated_at: DateTime<Utc>,
903}
904
905impl RegressionDebtLedgerV1 {
906    pub fn new(mut items: Vec<RegressionDebtItemV1>) -> Self {
907        items.sort_by(|left, right| left.surface_id.cmp(&right.surface_id));
908        let blocking_debt_ids = items
909            .iter()
910            .filter(|item| item.status == RegressionDebtStatusV1::Blocked)
911            .map(|item| item.debt_id.clone())
912            .collect::<Vec<_>>();
913        let reason_codes = if blocking_debt_ids.is_empty() {
914            vec!["regression-debt-ledger-non-blocking".into()]
915        } else {
916            vec!["regression-debt-ledger-blocking".into()]
917        };
918        Self {
919            ledger_id: display_only_unstable_id("regression-debt-ledger"),
920            kind: ArtifactKindV1::RegressionDebtLedger,
921            items,
922            blocking_debt_ids,
923            reason_codes,
924            generated_at: Utc::now(),
925        }
926    }
927
928    pub fn blocks_completion(&self) -> bool {
929        !self.blocking_debt_ids.is_empty()
930    }
931}
932
933#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
934pub struct CompletionAuditReportV1 {
935    pub report_id: ArtifactId,
936    pub kind: ArtifactKindV1,
937    pub completion_state: CompletionAuditStateV1,
938    pub release_bar_passed: bool,
939    pub source_basis: String,
940    pub gate_results: Vec<GateCommandResultV1>,
941    pub release_readiness: ReleaseReadinessReportV1,
942    pub traceability_matrix: CrossPassTraceabilityMatrixV1,
943    pub release_artifact_manifest: ReleaseArtifactManifestV1,
944    pub known_limitations: KnownLimitationsRegisterV1,
945    pub regression_debt: RegressionDebtLedgerV1,
946    pub partial_surfaces: Vec<String>,
947    pub deferred_surfaces: Vec<String>,
948    pub blocked_surfaces: Vec<String>,
949    #[serde(default, skip_serializing_if = "Vec::is_empty")]
950    pub waiver_receipt_ids: Vec<String>,
951    #[serde(default, skip_serializing_if = "Vec::is_empty")]
952    pub reason_codes: Vec<String>,
953    pub generated_at: DateTime<Utc>,
954}
955
956impl CompletionAuditReportV1 {
957    pub fn new(
958        source_basis: impl Into<String>,
959        gate_results: Vec<GateCommandResultV1>,
960        release_readiness: ReleaseReadinessReportV1,
961        traceability_matrix: CrossPassTraceabilityMatrixV1,
962        release_artifact_manifest: ReleaseArtifactManifestV1,
963        known_limitations: KnownLimitationsRegisterV1,
964        regression_debt: RegressionDebtLedgerV1,
965    ) -> Self {
966        let source_basis = source_basis.into();
967        let required_common_gate_commands = [
968            "cargo fmt --all --check",
969            "cargo check --workspace --all-targets --all-features",
970            "cargo test --workspace --all-targets --all-features",
971            "cargo clippy --workspace --all-targets --all-features -- -D warnings",
972        ];
973        let verifier_gate_commands = [
974            "bash scripts/p24_verify.sh",
975            "P24_REQUIRE_CARGO=1 bash scripts/p24_verify.sh",
976            "P24_PACKAGE_SELF_REPLAY=target/p24/package/AiDENs-p24-codex-context.zip bash scripts/p24_verify.sh",
977            "P22_REQUIRE_CARGO=1 bash scripts/p22_verify.sh",
978        ];
979        let gate_map = gate_results
980            .iter()
981            .map(|gate| (gate.command.as_str(), gate.is_satisfied()))
982            .collect::<BTreeMap<_, _>>();
983        let gates_satisfied = required_common_gate_commands
984            .iter()
985            .all(|command| gate_map.get(command).copied().unwrap_or(false))
986            && verifier_gate_commands
987                .iter()
988                .any(|command| gate_map.get(command).copied().unwrap_or(false));
989        let partial_surfaces = release_readiness
990            .surfaces
991            .iter()
992            .filter(|surface| surface.state == ReleaseSurfaceStateV1::Partial)
993            .map(|surface| surface.surface_id.clone())
994            .chain(
995                known_limitations
996                    .limitations
997                    .iter()
998                    .filter(|limitation| limitation.state == ReleaseSurfaceStateV1::Partial)
999                    .map(|limitation| limitation.surface_id.clone()),
1000            )
1001            .collect::<BTreeSet<_>>()
1002            .into_iter()
1003            .collect::<Vec<_>>();
1004        let deferred_surfaces = release_readiness
1005            .surfaces
1006            .iter()
1007            .filter(|surface| surface.state == ReleaseSurfaceStateV1::Deferred)
1008            .map(|surface| surface.surface_id.clone())
1009            .chain(
1010                known_limitations
1011                    .limitations
1012                    .iter()
1013                    .filter(|limitation| limitation.state == ReleaseSurfaceStateV1::Deferred)
1014                    .map(|limitation| limitation.surface_id.clone()),
1015            )
1016            .collect::<BTreeSet<_>>()
1017            .into_iter()
1018            .collect::<Vec<_>>();
1019        let blocked_surfaces = release_readiness
1020            .surfaces
1021            .iter()
1022            .filter(|surface| surface.state == ReleaseSurfaceStateV1::Blocked)
1023            .map(|surface| surface.surface_id.clone())
1024            .chain(
1025                known_limitations
1026                    .limitations
1027                    .iter()
1028                    .filter(|limitation| limitation.state == ReleaseSurfaceStateV1::Blocked)
1029                    .map(|limitation| limitation.surface_id.clone()),
1030            )
1031            .collect::<BTreeSet<_>>()
1032            .into_iter()
1033            .collect::<Vec<_>>();
1034        let hard_release_bar_clear = gates_satisfied
1035            && !release_readiness.blocks_release()
1036            && !traceability_matrix.blocks_completion()
1037            && !release_artifact_manifest.blocks_release()
1038            && !known_limitations.blocks_completion()
1039            && !regression_debt.blocks_completion()
1040            && blocked_surfaces.is_empty();
1041        let completion_state = if !hard_release_bar_clear {
1042            CompletionAuditStateV1::Blocked
1043        } else if !deferred_surfaces.is_empty() {
1044            CompletionAuditStateV1::DeferredHorizon
1045        } else if !partial_surfaces.is_empty() {
1046            CompletionAuditStateV1::NearComplete
1047        } else {
1048            CompletionAuditStateV1::Complete
1049        };
1050        let release_bar_passed = completion_state == CompletionAuditStateV1::Complete;
1051        let reason_codes = if release_bar_passed {
1052            vec!["release-bar-passed".into()]
1053        } else if completion_state == CompletionAuditStateV1::DeferredHorizon {
1054            vec![
1055                "release-bar-blocked".into(),
1056                "deferred-horizon-surfaces-disclosed".into(),
1057            ]
1058        } else {
1059            vec!["release-bar-blocked".into()]
1060        };
1061        Self {
1062            report_id: display_only_unstable_id("completion-audit-report"),
1063            kind: ArtifactKindV1::CompletionAuditReport,
1064            completion_state,
1065            release_bar_passed,
1066            source_basis,
1067            gate_results,
1068            release_readiness,
1069            traceability_matrix,
1070            release_artifact_manifest,
1071            known_limitations,
1072            regression_debt,
1073            partial_surfaces,
1074            deferred_surfaces,
1075            blocked_surfaces,
1076            waiver_receipt_ids: Vec::new(),
1077            reason_codes,
1078            generated_at: Utc::now(),
1079        }
1080    }
1081
1082    pub fn blocks_completion(&self) -> bool {
1083        !self.release_bar_passed
1084    }
1085}