Skip to main content

pleiades_data/coverage/
target.rs

1use super::*;
2
3/// Release-state for the packaged-artifact target thresholds.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum PackagedArtifactTargetThresholdState {
6    /// Calibrated fit envelope is recorded, but production thresholds are not yet release-ready.
7    Draft,
8    /// Production thresholds have been finalized for the current profile.
9    ProductionReady,
10}
11
12/// Validation error for a packaged-artifact target-threshold state that is not release-ready.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum PackagedArtifactTargetThresholdStateValidationError {
15    /// The target-threshold state is still draft.
16    Draft,
17}
18
19impl fmt::Display for PackagedArtifactTargetThresholdStateValidationError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Draft => write!(
23                f,
24                "the packaged-artifact target-threshold state is draft; production thresholds are not yet release-ready"
25            ),
26        }
27    }
28}
29
30impl std::error::Error for PackagedArtifactTargetThresholdStateValidationError {}
31
32impl PackagedArtifactTargetThresholdState {
33    /// Returns the compact label used in release-facing summaries.
34    pub const fn label(self) -> &'static str {
35        match self {
36            Self::Draft => {
37                "calibrated fit envelope recorded; production thresholds not yet release-ready"
38            }
39            Self::ProductionReady => "production thresholds recorded",
40        }
41    }
42
43    /// Returns a compact human-readable line for the target-threshold state.
44    pub fn summary_line(self) -> String {
45        format!("target-threshold state: {}", self)
46    }
47
48    /// Returns whether the target thresholds are finalized for production release.
49    pub const fn is_production_ready(self) -> bool {
50        matches!(self, Self::ProductionReady)
51    }
52
53    /// Returns `Ok(())` when the state is release-ready.
54    pub fn validate_production_ready(
55        self,
56    ) -> Result<(), PackagedArtifactTargetThresholdStateValidationError> {
57        if self.is_production_ready() {
58            Ok(())
59        } else {
60            Err(PackagedArtifactTargetThresholdStateValidationError::Draft)
61        }
62    }
63
64    /// Returns the validated target-threshold state as a compact human-readable line.
65    pub fn validated_summary_line(
66        self,
67    ) -> Result<String, PackagedArtifactTargetThresholdStateValidationError> {
68        self.validate_production_ready()?;
69        Ok(self.summary_line())
70    }
71}
72
73impl fmt::Display for PackagedArtifactTargetThresholdState {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        f.write_str(self.label())
76    }
77}
78
79/// Returns the current packaged-artifact target-threshold state after validating the release posture.
80pub fn packaged_artifact_target_threshold_state_for_report() -> String {
81    match PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE.validated_summary_line() {
82        Ok(line) => line,
83        Err(error) => format!("target-threshold state: unavailable ({error})"),
84    }
85}
86
87const PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE: PackagedArtifactTargetThresholdState =
88    PackagedArtifactTargetThresholdState::ProductionReady;
89pub(crate) const PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES: &[&str] = &[
90    "luminaries",
91    "major planets",
92    "pluto",
93    "lunar points",
94    "selected asteroids",
95    "custom bodies",
96];
97
98/// Phase-2 corpus evidence used to keep the packaged-artifact threshold policy aligned
99/// with the current reference, fixture-exactness, comparison, hold-out, selected-asteroid, boundary-overlay, and production-generation corpora.
100#[derive(Clone, Debug, PartialEq)]
101pub struct PackagedArtifactPhase2CorpusAlignmentSummary {
102    /// Source-material evidence from the checked-in reference snapshot.
103    pub reference_snapshot_source: pleiades_jpl::ReferenceSnapshotSourceSummary,
104    /// Body-class coverage evidence from the checked-in reference snapshot.
105    pub reference_snapshot: pleiades_jpl::ReferenceSnapshotBodyClassCoverageSummary,
106    /// Exact J2000 fixture-exactness evidence from the checked-in reference snapshot.
107    pub reference_snapshot_exact_j2000: pleiades_jpl::ReferenceSnapshotExactJ2000EvidenceSummary,
108    /// Source-material evidence from the checked-in comparison snapshot.
109    pub comparison_snapshot_source: pleiades_jpl::ComparisonSnapshotSourceSummary,
110    /// Body-class coverage evidence from the checked-in comparison snapshot.
111    pub comparison_snapshot: pleiades_jpl::ComparisonSnapshotBodyClassCoverageSummary,
112    /// Source-material evidence from the checked-in independent hold-out snapshot.
113    pub independent_holdout_source: pleiades_jpl::IndependentHoldoutSourceSummary,
114    /// Body-class coverage evidence from the checked-in independent hold-out snapshot.
115    pub independent_holdout: pleiades_jpl::IndependentHoldoutSnapshotBodyClassCoverageSummary,
116    /// Source-backed evidence for the selected-asteroid validation corpus.
117    pub selected_asteroid_source: pleiades_jpl::SelectedAsteroidSourceSummary,
118    /// Source-backed window evidence for the selected-asteroid validation corpus.
119    pub selected_asteroid_source_windows: pleiades_jpl::SelectedAsteroidSourceWindowSummary,
120    /// Checked-in request-corpus evidence for the selected-asteroid validation corpus in the ecliptic frame.
121    pub selected_asteroid_source_request_corpus:
122        pleiades_jpl::SelectedAsteroidSourceRequestCorpusSummary,
123    /// Checked-in request-corpus evidence for the selected-asteroid validation corpus in the equatorial frame.
124    pub selected_asteroid_source_request_corpus_equatorial:
125        pleiades_jpl::SelectedAsteroidSourceRequestCorpusSummary,
126    /// Source-material evidence for the checked-in production-generation boundary overlay.
127    pub production_generation_boundary_source: pleiades_jpl::IndependentHoldoutSourceSummary,
128    /// Body-class coverage evidence for the checked-in production-generation corpus.
129    pub production_generation_body_class_coverage:
130        pleiades_jpl::ProductionGenerationSnapshotBodyClassCoverageSummary,
131    /// Combined provenance for the checked-in production-generation corpus.
132    pub production_generation_source: ProductionGenerationSourceSummary,
133}
134
135/// Validation error for a phase-2 corpus alignment summary that drifted from the current posture.
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
137pub enum PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {
138    /// A summary field is out of sync with the current packaged-artifact posture.
139    FieldOutOfSync { field: &'static str },
140}
141
142impl fmt::Display for PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            Self::FieldOutOfSync { field } => write!(
146                f,
147                "the packaged artifact phase-2 corpus alignment summary field `{field}` is out of sync with the current posture"
148            ),
149        }
150    }
151}
152
153impl std::error::Error for PackagedArtifactPhase2CorpusAlignmentSummaryValidationError {}
154
155fn phase2_corpus_alignment_validation_field_path(field: &'static str) -> &'static str {
156    match field {
157        "reference_snapshot_source" => "phase2_corpus_alignment.reference_snapshot_source",
158        "reference_snapshot" => "phase2_corpus_alignment.reference_snapshot",
159        "reference_snapshot_exact_j2000" => {
160            "phase2_corpus_alignment.reference_snapshot_exact_j2000"
161        }
162        "comparison_snapshot_source" => "phase2_corpus_alignment.comparison_snapshot_source",
163        "comparison_snapshot" => "phase2_corpus_alignment.comparison_snapshot",
164        "independent_holdout_source" => "phase2_corpus_alignment.independent_holdout_source",
165        "independent_holdout" => "phase2_corpus_alignment.independent_holdout",
166        "selected_asteroid_source" => "phase2_corpus_alignment.selected_asteroid_source",
167        "selected_asteroid_source_windows" => {
168            "phase2_corpus_alignment.selected_asteroid_source_windows"
169        }
170        "selected_asteroid_source_request_corpus" => {
171            "phase2_corpus_alignment.selected_asteroid_source_request_corpus"
172        }
173        "selected_asteroid_source_request_corpus_equatorial" => {
174            "phase2_corpus_alignment.selected_asteroid_source_request_corpus_equatorial"
175        }
176        "production_generation_boundary_source" => {
177            "phase2_corpus_alignment.production_generation_boundary_source"
178        }
179        "production_generation_body_class_coverage" => {
180            "phase2_corpus_alignment.production_generation_body_class_coverage"
181        }
182        "production_generation_source" => "phase2_corpus_alignment.production_generation_source",
183        _ => "phase2_corpus_alignment",
184    }
185}
186
187impl PackagedArtifactPhase2CorpusAlignmentSummary {
188    /// Returns the phase-2 corpus alignment posture as a compact human-readable line.
189    pub fn summary_line(&self) -> String {
190        format!(
191            "reference source={}; reference snapshot={}; reference exact J2000 evidence={}; comparison source={}; comparison snapshot={}; independent hold-out source={}; independent hold-out={}; selected asteroid source evidence={}; selected asteroid source windows={}; selected asteroid source request corpus={}; selected asteroid source request corpus equatorial={}; production generation boundary source={}; production generation body-class coverage={}; production generation source={}",
192            self.reference_snapshot_source.summary_line(),
193            self.reference_snapshot.summary_line(),
194            self.reference_snapshot_exact_j2000.summary_line(),
195            self.comparison_snapshot_source.summary_line(),
196            self.comparison_snapshot.summary_line(),
197            self.independent_holdout_source.summary_line(),
198            self.independent_holdout.summary_line(),
199            self.selected_asteroid_source.summary_line(),
200            self.selected_asteroid_source_windows.summary_line(),
201            self.selected_asteroid_source_request_corpus.summary_line(),
202            self.selected_asteroid_source_request_corpus_equatorial.summary_line(),
203            pleiades_jpl::format_production_generation_boundary_source_summary(
204                &self.production_generation_boundary_source,
205            ),
206            self.production_generation_body_class_coverage.summary_line(),
207            self.production_generation_source.summary_line(),
208        )
209    }
210
211    /// Returns `Ok(())` when the phase-2 corpus evidence still matches the current packaged-artifact posture.
212    pub fn validate(
213        &self,
214    ) -> Result<(), PackagedArtifactPhase2CorpusAlignmentSummaryValidationError> {
215        let Some(expected) = packaged_artifact_phase2_corpus_alignment_summary_details() else {
216            return Err(
217                PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
218                    field: "phase2_corpus_alignment",
219                },
220            );
221        };
222
223        let field_out_of_sync = |field| {
224            PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync { field }
225        };
226
227        if self.reference_snapshot_source != expected.reference_snapshot_source {
228            return Err(field_out_of_sync("reference_snapshot_source"));
229        }
230        self.reference_snapshot_source
231            .validate()
232            .map_err(|_| field_out_of_sync("reference_snapshot_source"))?;
233
234        if self.reference_snapshot != expected.reference_snapshot {
235            return Err(field_out_of_sync("reference_snapshot"));
236        }
237        self.reference_snapshot
238            .validate()
239            .map_err(|_| field_out_of_sync("reference_snapshot"))?;
240
241        if self.reference_snapshot_exact_j2000 != expected.reference_snapshot_exact_j2000 {
242            return Err(field_out_of_sync("reference_snapshot_exact_j2000"));
243        }
244        self.reference_snapshot_exact_j2000
245            .validate()
246            .map_err(|_| field_out_of_sync("reference_snapshot_exact_j2000"))?;
247
248        if self.comparison_snapshot_source != expected.comparison_snapshot_source {
249            return Err(field_out_of_sync("comparison_snapshot_source"));
250        }
251        self.comparison_snapshot_source
252            .validate()
253            .map_err(|_| field_out_of_sync("comparison_snapshot_source"))?;
254
255        if self.comparison_snapshot != expected.comparison_snapshot {
256            return Err(field_out_of_sync("comparison_snapshot"));
257        }
258        self.comparison_snapshot
259            .validate()
260            .map_err(|_| field_out_of_sync("comparison_snapshot"))?;
261
262        if self.independent_holdout_source != expected.independent_holdout_source {
263            return Err(field_out_of_sync("independent_holdout_source"));
264        }
265        self.independent_holdout_source
266            .validate()
267            .map_err(|_| field_out_of_sync("independent_holdout_source"))?;
268
269        if self.independent_holdout != expected.independent_holdout {
270            return Err(field_out_of_sync("independent_holdout"));
271        }
272        self.independent_holdout
273            .validate()
274            .map_err(|_| field_out_of_sync("independent_holdout"))?;
275
276        if self.selected_asteroid_source != expected.selected_asteroid_source {
277            return Err(field_out_of_sync("selected_asteroid_source"));
278        }
279        self.selected_asteroid_source
280            .validate()
281            .map_err(|_| field_out_of_sync("selected_asteroid_source"))?;
282
283        if self.selected_asteroid_source_windows != expected.selected_asteroid_source_windows {
284            return Err(field_out_of_sync("selected_asteroid_source_windows"));
285        }
286        self.selected_asteroid_source_windows
287            .validate()
288            .map_err(|_| field_out_of_sync("selected_asteroid_source_windows"))?;
289
290        if self.selected_asteroid_source_request_corpus
291            != expected.selected_asteroid_source_request_corpus
292        {
293            return Err(field_out_of_sync("selected_asteroid_source_request_corpus"));
294        }
295        self.selected_asteroid_source_request_corpus
296            .validate()
297            .map_err(|_| field_out_of_sync("selected_asteroid_source_request_corpus"))?;
298
299        if self.selected_asteroid_source_request_corpus_equatorial
300            != expected.selected_asteroid_source_request_corpus_equatorial
301        {
302            return Err(field_out_of_sync(
303                "selected_asteroid_source_request_corpus_equatorial",
304            ));
305        }
306        self.selected_asteroid_source_request_corpus_equatorial
307            .validate()
308            .map_err(|_| field_out_of_sync("selected_asteroid_source_request_corpus_equatorial"))?;
309
310        if self.production_generation_boundary_source
311            != expected.production_generation_boundary_source
312        {
313            return Err(field_out_of_sync("production_generation_boundary_source"));
314        }
315        self.production_generation_boundary_source
316            .validate()
317            .map_err(|_| field_out_of_sync("production_generation_boundary_source"))?;
318
319        if self.production_generation_body_class_coverage
320            != expected.production_generation_body_class_coverage
321        {
322            return Err(field_out_of_sync(
323                "production_generation_body_class_coverage",
324            ));
325        }
326        self.production_generation_body_class_coverage
327            .validate()
328            .map_err(|_| field_out_of_sync("production_generation_body_class_coverage"))?;
329
330        if self.production_generation_source != expected.production_generation_source {
331            return Err(field_out_of_sync("production_generation_source"));
332        }
333        self.production_generation_source
334            .validate()
335            .map_err(|_| field_out_of_sync("production_generation_source"))?;
336
337        Ok(())
338    }
339
340    /// Returns the validated phase-2 corpus alignment posture as a compact human-readable line.
341    pub fn validated_summary_line(
342        &self,
343    ) -> Result<String, PackagedArtifactPhase2CorpusAlignmentSummaryValidationError> {
344        self.validate()?;
345        Ok(self.summary_line())
346    }
347}
348
349impl fmt::Display for PackagedArtifactPhase2CorpusAlignmentSummary {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        f.write_str(&self.summary_line())
352    }
353}
354
355/// Returns the structured phase-2 corpus alignment posture used to keep the packaged-artifact threshold policy aligned with the current corpus.
356pub fn packaged_artifact_phase2_corpus_alignment_summary_details(
357) -> Option<PackagedArtifactPhase2CorpusAlignmentSummary> {
358    Some(PackagedArtifactPhase2CorpusAlignmentSummary {
359        reference_snapshot_source: pleiades_jpl::reference_snapshot_source_summary(),
360        reference_snapshot: pleiades_jpl::reference_snapshot_body_class_coverage_summary()?,
361        reference_snapshot_exact_j2000:
362            pleiades_jpl::reference_snapshot_exact_j2000_evidence_summary()?,
363        comparison_snapshot_source: pleiades_jpl::comparison_snapshot_source_summary(),
364        comparison_snapshot: comparison_snapshot_body_class_coverage_summary()?,
365        independent_holdout_source: pleiades_jpl::independent_holdout_source_summary(),
366        independent_holdout: independent_holdout_snapshot_body_class_coverage_summary()?,
367        selected_asteroid_source: pleiades_jpl::selected_asteroid_source_evidence_summary()?,
368        selected_asteroid_source_windows: pleiades_jpl::selected_asteroid_source_window_summary()?,
369        selected_asteroid_source_request_corpus: selected_asteroid_source_request_corpus_summary(
370            CoordinateFrame::Ecliptic,
371        )?,
372        selected_asteroid_source_request_corpus_equatorial:
373            selected_asteroid_source_request_corpus_summary(CoordinateFrame::Equatorial)?,
374        production_generation_boundary_source:
375            pleiades_jpl::production_generation_boundary_source_summary(),
376        production_generation_body_class_coverage:
377            pleiades_jpl::production_generation_snapshot_body_class_coverage_summary()?,
378        production_generation_source: production_generation_source_summary(),
379    })
380}
381
382/// Returns the current packaged-artifact phase-2 corpus alignment posture after validating the structured evidence.
383pub fn packaged_artifact_phase2_corpus_alignment_summary_for_report() -> String {
384    static SUMMARY: OnceLock<String> = OnceLock::new();
385    SUMMARY
386        .get_or_init(|| {
387            let summary = packaged_artifact_phase2_corpus_alignment_summary_details();
388            match summary.as_ref().map(PackagedArtifactPhase2CorpusAlignmentSummary::validated_summary_line) {
389                Some(Ok(line)) => line,
390                Some(Err(error)) => format!("phase 2 corpus alignment: unavailable ({error})"),
391                None => "phase 2 corpus alignment: unavailable (phase-2 corpus evidence should be available)".to_string(),
392            }
393        })
394        .clone()
395}
396
397/// Structured target-threshold posture for the packaged artifact generator.
398#[derive(Clone, Debug, PartialEq)]
399pub struct PackagedArtifactTargetThresholdSummary {
400    /// Stable identifier for the release profile that the thresholds apply to.
401    pub profile_id: &'static str,
402    /// Current release posture for the production thresholds.
403    pub state: PackagedArtifactTargetThresholdState,
404    /// Body-class scopes covered by the current threshold policy.
405    pub scopes: &'static [&'static str],
406    /// Measured fit envelope captured for the current packaged artifact posture.
407    pub fit_envelope: PackagedArtifactFitEnvelopeSummary,
408    /// Body-class-specific fit envelopes captured for the current packaged artifact posture.
409    pub scope_envelopes: PackagedArtifactTargetThresholdScopeEnvelopesSummary,
410    /// Phase-2 corpus evidence that keeps the threshold posture aligned with the current corpus.
411    pub phase2_corpus_alignment: PackagedArtifactPhase2CorpusAlignmentSummary,
412}
413
414/// Validation error for a packaged-artifact target-threshold summary that drifted from the current posture.
415#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
416pub enum PackagedArtifactTargetThresholdSummaryValidationError {
417    /// A summary field is out of sync with the current packaged-artifact posture.
418    FieldOutOfSync { field: &'static str },
419}
420
421impl fmt::Display for PackagedArtifactTargetThresholdSummaryValidationError {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        match self {
424            Self::FieldOutOfSync { field } => write!(
425                f,
426                "the packaged artifact target-threshold summary field `{field}` is out of sync with the current posture"
427            ),
428        }
429    }
430}
431
432impl std::error::Error for PackagedArtifactTargetThresholdSummaryValidationError {}
433
434impl PackagedArtifactTargetThresholdSummary {
435    /// Returns the target-threshold posture as a compact human-readable line.
436    pub fn summary_line(&self) -> String {
437        format!(
438            "profile id={}; target thresholds: {}; scopes={}; {}; scope envelopes={}; phase 2 corpus alignment={}",
439            self.profile_id,
440            self.state,
441            self.scopes.join(", "),
442            self.fit_envelope.summary_line(),
443            join_display(&self.scope_envelopes.scope_envelopes),
444            self.phase2_corpus_alignment.summary_line(),
445        )
446    }
447
448    /// Returns `Ok(())` when the summary still matches the current packaged-artifact posture.
449    pub fn validate(&self) -> Result<(), PackagedArtifactTargetThresholdSummaryValidationError> {
450        if self.profile_id != ARTIFACT_PROFILE_ID {
451            return Err(
452                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
453                    field: "profile_id",
454                },
455            );
456        }
457        self.state.validate_production_ready().map_err(|_| {
458            PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync { field: "state" }
459        })?;
460        if self.state != PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE {
461            return Err(
462                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
463                    field: "state",
464                },
465            );
466        }
467        if self.scopes != PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES {
468            return Err(
469                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
470                    field: "scopes",
471                },
472            );
473        }
474
475        let expected_fit_envelope = packaged_artifact_fit_envelope_summary_details();
476        if self.fit_envelope != expected_fit_envelope {
477            return Err(
478                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
479                    field: "fit_envelope",
480                },
481            );
482        }
483        if self.state.is_production_ready() {
484            let thresholds = packaged_artifact_fit_threshold_summary_details();
485            self.fit_envelope
486                .validate_against_thresholds(&thresholds)
487                .map_err(|_| {
488                    PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
489                        field: "fit_envelope",
490                    }
491                })?;
492        }
493        let expected_scope_envelopes =
494            packaged_artifact_target_threshold_scope_envelopes_summary_details();
495        if self.scope_envelopes != expected_scope_envelopes {
496            return Err(
497                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
498                    field: "scope_envelopes",
499                },
500            );
501        }
502        self.scope_envelopes.validate().map_err(|_| {
503            PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
504                field: "scope_envelopes",
505            }
506        })?;
507
508        let expected_phase2_corpus_alignment =
509            packaged_artifact_phase2_corpus_alignment_summary_details().ok_or(
510                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
511                    field: "phase2_corpus_alignment",
512                },
513            )?;
514        self.phase2_corpus_alignment
515            .validate()
516            .map_err(|error| match error {
517                PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
518                    field,
519                } => PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
520                    field: phase2_corpus_alignment_validation_field_path(field),
521                },
522            })?;
523        if self.phase2_corpus_alignment != expected_phase2_corpus_alignment {
524            return Err(
525                PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
526                    field: "phase2_corpus_alignment",
527                },
528            );
529        }
530
531        let thresholds = packaged_artifact_fit_threshold_summary_details();
532        for scope_envelope in &self.scope_envelopes.scope_envelopes {
533            scope_envelope
534                .fit_envelope
535                .validate_against_thresholds(&thresholds)
536                .map_err(|_| {
537                    PackagedArtifactTargetThresholdSummaryValidationError::FieldOutOfSync {
538                        field: "scope_envelopes",
539                    }
540                })?;
541        }
542
543        Ok(())
544    }
545
546    /// Returns the validated target-threshold posture as a compact human-readable line.
547    pub fn validated_summary_line(
548        &self,
549    ) -> Result<String, PackagedArtifactTargetThresholdSummaryValidationError> {
550        self.validate()?;
551        Ok(self.summary_line())
552    }
553}
554
555impl fmt::Display for PackagedArtifactTargetThresholdSummary {
556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557        f.write_str(&self.summary_line())
558    }
559}
560
561/// Returns the current packaged-artifact target-threshold summary record.
562pub fn packaged_artifact_target_threshold_summary_details() -> PackagedArtifactTargetThresholdSummary
563{
564    let summary = PackagedArtifactTargetThresholdSummary {
565        profile_id: ARTIFACT_PROFILE_ID,
566        state: PACKAGED_ARTIFACT_TARGET_THRESHOLD_STATE,
567        scopes: PACKAGED_ARTIFACT_TARGET_THRESHOLD_SCOPES,
568        fit_envelope: packaged_artifact_fit_envelope_summary_details(),
569        scope_envelopes: packaged_artifact_target_threshold_scope_envelopes_summary_details(),
570        phase2_corpus_alignment: packaged_artifact_phase2_corpus_alignment_summary_details()
571            .expect("phase-2 corpus evidence should be available"),
572    };
573    debug_assert!(summary.validate().is_ok());
574    summary
575}
576
577/// Returns the current packaged-artifact target-threshold summary after validating the structured posture.
578pub fn packaged_artifact_target_threshold_summary_for_report() -> String {
579    static SUMMARY: OnceLock<String> = OnceLock::new();
580    SUMMARY
581        .get_or_init(|| {
582            let summary = packaged_artifact_target_threshold_summary_details();
583            match summary.validated_summary_line() {
584                Ok(line) => line,
585                Err(error) => format!("target thresholds: unavailable ({error})"),
586            }
587        })
588        .clone()
589}
590
591/// Structured sync summary for the packaged-artifact source-fit and hold-out checks.
592#[derive(Clone, Debug, PartialEq)]
593pub struct PackagedArtifactSourceFitHoldoutSyncSummary {
594    /// Calibrated fit thresholds used by the current packaged-artifact posture.
595    pub fit_thresholds: PackagedArtifactFitThresholdSummary,
596    /// Release-threshold posture that keeps the phase-2 corpus alignment synchronized.
597    pub target_thresholds: PackagedArtifactTargetThresholdSummary,
598    /// Phase-2 corpus evidence that anchors the threshold posture to current source coverage.
599    pub phase2_corpus_alignment: PackagedArtifactPhase2CorpusAlignmentSummary,
600}
601
602/// Validation error for a packaged-artifact source-fit and hold-out sync summary that drifted from the current posture.
603#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
604pub enum PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {
605    /// A summary field is out of sync with the current packaged-artifact posture.
606    FieldOutOfSync { field: &'static str },
607}
608
609impl fmt::Display for PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        match self {
612            Self::FieldOutOfSync { field } => write!(
613                f,
614                "the packaged artifact source-fit and hold-out sync summary field `{field}` is out of sync with the current posture"
615            ),
616        }
617    }
618}
619
620impl std::error::Error for PackagedArtifactSourceFitHoldoutSyncSummaryValidationError {}
621
622impl PackagedArtifactSourceFitHoldoutSyncSummary {
623    /// Returns the source-fit and hold-out sync posture as a compact human-readable line.
624    pub fn summary_line(&self) -> String {
625        format!(
626            "source-fit and hold-out sync: fit thresholds={}; target thresholds={}; phase 2 corpus alignment={}",
627            self.fit_thresholds.summary_line(),
628            self.target_thresholds.summary_line(),
629            self.phase2_corpus_alignment.summary_line(),
630        )
631    }
632
633    /// Returns `Ok(())` when the sync summary still matches the current packaged-artifact posture.
634    pub fn validate(
635        &self,
636    ) -> Result<(), PackagedArtifactSourceFitHoldoutSyncSummaryValidationError> {
637        let expected_fit_thresholds = packaged_artifact_fit_threshold_summary_details();
638        if self.fit_thresholds != expected_fit_thresholds {
639            return Err(
640                PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
641                    field: "fit_thresholds",
642                },
643            );
644        }
645        self.fit_thresholds.validate().map_err(|_| {
646            PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
647                field: "fit_thresholds",
648            }
649        })?;
650
651        let expected_target_thresholds = packaged_artifact_target_threshold_summary_details();
652        if self.target_thresholds != expected_target_thresholds {
653            return Err(
654                PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
655                    field: "target_thresholds",
656                },
657            );
658        }
659        self.target_thresholds.validate().map_err(|_| {
660            PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
661                field: "target_thresholds",
662            }
663        })?;
664
665        let expected_phase2_corpus_alignment =
666            packaged_artifact_phase2_corpus_alignment_summary_details().ok_or(
667                PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
668                    field: "phase2_corpus_alignment",
669                },
670            )?;
671        self.phase2_corpus_alignment
672            .validate()
673            .map_err(|error| match error {
674                PackagedArtifactPhase2CorpusAlignmentSummaryValidationError::FieldOutOfSync {
675                    field,
676                } => PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
677                    field: phase2_corpus_alignment_validation_field_path(field),
678                },
679            })?;
680        if self.phase2_corpus_alignment != expected_phase2_corpus_alignment {
681            return Err(
682                PackagedArtifactSourceFitHoldoutSyncSummaryValidationError::FieldOutOfSync {
683                    field: "phase2_corpus_alignment",
684                },
685            );
686        }
687
688        Ok(())
689    }
690
691    /// Returns the validated source-fit and hold-out sync posture as a compact human-readable line.
692    pub fn validated_summary_line(
693        &self,
694    ) -> Result<String, PackagedArtifactSourceFitHoldoutSyncSummaryValidationError> {
695        self.validate()?;
696        Ok(self.summary_line())
697    }
698}
699
700impl fmt::Display for PackagedArtifactSourceFitHoldoutSyncSummary {
701    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
702        f.write_str(&self.summary_line())
703    }
704}
705
706/// Returns the current packaged-artifact source-fit and hold-out sync summary record.
707pub fn packaged_artifact_source_fit_holdout_sync_summary_details(
708) -> PackagedArtifactSourceFitHoldoutSyncSummary {
709    let summary = PackagedArtifactSourceFitHoldoutSyncSummary {
710        fit_thresholds: packaged_artifact_fit_threshold_summary_details(),
711        target_thresholds: packaged_artifact_target_threshold_summary_details(),
712        phase2_corpus_alignment: packaged_artifact_phase2_corpus_alignment_summary_details()
713            .expect("phase-2 corpus evidence should be available"),
714    };
715    debug_assert!(summary.validate().is_ok());
716    summary
717}
718
719/// Returns the current packaged-artifact source-fit and hold-out sync posture after validating the structured evidence.
720pub fn packaged_artifact_source_fit_holdout_sync_summary_for_report() -> String {
721    static SUMMARY: OnceLock<String> = OnceLock::new();
722    SUMMARY
723        .get_or_init(|| {
724            let summary = packaged_artifact_source_fit_holdout_sync_summary_details();
725            match summary.validated_summary_line() {
726                Ok(line) => line,
727                Err(error) => format!("source-fit and hold-out sync: unavailable ({error})"),
728            }
729        })
730        .clone()
731}
732
733/// Returns the current packaged-artifact body-class target-threshold envelopes after validating the structured posture.
734pub fn packaged_artifact_target_threshold_scope_envelopes_for_report() -> String {
735    static SUMMARY: OnceLock<String> = OnceLock::new();
736    SUMMARY
737        .get_or_init(|| {
738            let summary = packaged_artifact_target_threshold_scope_envelopes_summary_details();
739            match summary.validated_summary_line() {
740                Ok(line) => line,
741                Err(error) => format!("scope envelopes: unavailable ({error})"),
742            }
743        })
744        .clone()
745}