alloy-assurance 0.3.0

Shared assurance facade for the Alloy workspace
Documentation
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use crate::api::{
    EvidenceEnvelope, EvidenceLevel, FailureClassification, ReplayBundle, ScenarioClass,
    ScenarioManifest, ScenarioOutcome, scope,
};

/// Summary of the Alloy-owned contract embedded in a replay file.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AlloyContractRecord {
    /// Alloy assurance scope.
    pub scope: String,
    /// Expected replay bundle artifact name.
    pub replay_bundle_artifact_name: String,
    /// Expected evidence report artifact name.
    pub evidence_artifact_name: String,
    /// Compact evidence report line.
    pub evidence_report_line: String,
    /// Compact manifest summary line.
    pub manifest_summary: String,
    /// Whether the evidence still permits a release claim.
    pub permits_release: bool,
}

/// Preconditions declared by a scenario manifest.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ManifestPreconditions {
    /// Dataset family required by the scenario.
    pub dataset_family: String,
    /// Plan profile or evaluation profile.
    pub plan_profile: String,
    /// Cache mode or cache expectation.
    pub cache_mode: String,
    /// Optional transport in use.
    pub transport: Option<String>,
}

/// Portable scenario manifest payload for on-disk replay artifacts.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ReplayManifest {
    /// Scenario identifier.
    pub scenario_id: String,
    /// Human-readable title.
    pub title: String,
    /// Phase identifier.
    pub phase: String,
    /// Supported maturity tier.
    pub maturity_tier: String,
    /// Scenario class.
    pub scenario_class: ScenarioClass,
    /// Producer surfaces participating in the scenario.
    pub producer_surfaces: Vec<String>,
    /// Preconditions for the scenario.
    pub preconditions: ManifestPreconditions,
    /// Stimulus description.
    pub stimulus: String,
    /// Assertions named by the manifest.
    pub assertions: Vec<String>,
    /// Evidence level for the scenario.
    pub evidence_level: EvidenceLevel,
    /// Required artifacts named by the manifest.
    pub required_artifacts: Vec<String>,
    /// Failure classifications expected by the scenario.
    pub failure_classifications: Vec<FailureClassification>,
    /// Reproduction steps recorded by the producer.
    pub reproduction_steps: Vec<String>,
    /// Optional free-form notes.
    pub notes: Vec<String>,
}

/// Assertion result stored in a replay run summary.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AssertionResultRecord {
    /// Assertion text or identifier.
    pub assertion: String,
    /// Whether the assertion passed.
    pub passed: bool,
    /// Optional detail describing the observed result.
    pub detail: Option<String>,
}

/// Portable artifact reference stored in replay files.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FileArtifactRef {
    /// Artifact kind inside the replay package.
    pub kind: String,
    /// Relative or absolute path to the artifact.
    pub path: String,
    /// Optional human-readable description.
    pub description: Option<String>,
}

/// Producer-specific evidence attached to a replay run summary.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ProducerEvidenceRecord {
    /// Producer surface that emitted the evidence.
    pub producer_surface: String,
    /// Dataset family used by the producer.
    pub dataset_family: String,
    /// Workload identifier used by the producer.
    pub workload_id: String,
    /// Optional supporting artifacts emitted by the producer.
    pub supporting_artifacts: Vec<FileArtifactRef>,
    /// Optional metadata bag for producer-specific fields.
    pub metadata: BTreeMap<String, String>,
}

/// Summary of a completed scenario run.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RunSummary {
    /// Overall result for the run.
    pub result: ScenarioOutcome,
    /// Structured assertion results.
    pub assertion_results: Vec<AssertionResultRecord>,
    /// Producer-specific evidence references.
    pub producer_evidence: Vec<ProducerEvidenceRecord>,
    /// Reproduction steps captured during the run.
    pub reproduction_steps: Vec<String>,
}

/// Timeline event inside a replay artifact.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TimelineEntry {
    /// Monotonic sequence number.
    pub sequence: u32,
    /// Short timeline label.
    pub label: String,
    /// Optional detail payload.
    pub detail: Option<String>,
    /// Optional connection identifier for transport and cache correlation.
    pub connection_id: Option<String>,
    /// Optional stream identifier for transport correlation.
    pub stream_id: Option<String>,
    /// Optional fault class for transport and restart correlation.
    pub fault_class: Option<String>,
}

/// Trace summary embedded in a replay artifact.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TraceSummary {
    /// Stable trace identifier.
    pub trace_id: String,
    /// Human-readable summary.
    pub summary: String,
}

/// Metrics snapshot embedded in a replay artifact.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct MetricsSnapshot {
    /// Counter-like metrics captured for the run.
    pub counters: BTreeMap<String, u64>,
}

/// Canonical portable replay file owned by Alloy for Phase 1 artifacts.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ReplayArtifact {
    /// Stable artifact identifier.
    pub artifact_id: String,
    /// Schema version for the file contract.
    pub schema_version: String,
    /// Producing repository.
    pub producing_repo: String,
    /// Producing commit or version.
    pub producing_commit: String,
    /// Scenario identifier.
    pub scenario_id: String,
    /// Scenario class.
    pub scenario_class: ScenarioClass,
    /// Supported maturity tier.
    pub maturity_tier: String,
    /// Dataset family under test.
    pub dataset_family: String,
    /// Workload identifier.
    pub workload_id: String,
    /// Overall result.
    pub result: ScenarioOutcome,
    /// Evidence level.
    pub evidence_level: EvidenceLevel,
    /// Optional failure classification.
    pub failure_classification: Option<FailureClassification>,
    /// RFC3339 start timestamp.
    pub started_at: String,
    /// RFC3339 finish timestamp.
    pub finished_at: String,
    /// Supporting artifacts directly attached to the envelope.
    pub supporting_artifacts: Vec<FileArtifactRef>,
    /// Alloy contract record.
    pub alloy_contract: AlloyContractRecord,
    /// Scenario manifest payload.
    pub manifest: ReplayManifest,
    /// Run summary payload.
    pub run_summary: RunSummary,
    /// Ordered timeline entries.
    pub timeline: Vec<TimelineEntry>,
    /// Optional trace summary.
    pub trace_summary: Option<TraceSummary>,
    /// Optional metrics snapshot.
    pub metrics_snapshot: Option<MetricsSnapshot>,
    /// Optional additional attachments.
    pub attachments: Vec<FileArtifactRef>,
}

impl ReplayArtifact {
    /// Returns whether the file satisfies the required fields for its scenario class.
    #[must_use]
    pub fn validate_phase_1_requirements(&self) -> Phase1ValidationReport {
        let mut missing_sections = Vec::new();

        if self.failure_classification.is_none() {
            missing_sections.push("failure_classification".to_owned());
        }
        if self.timeline.is_empty() {
            missing_sections.push("timeline".to_owned());
        }

        match self.scenario_class {
            ScenarioClass::RestartRecovery => {
                if self.trace_summary.is_none() {
                    missing_sections.push("trace_summary".to_owned());
                }
                if self.metrics_snapshot.is_none() {
                    missing_sections.push("metrics_snapshot".to_owned());
                }
            }
            ScenarioClass::CapabilityRejection => {
                if self.trace_summary.is_none() {
                    missing_sections.push("trace_summary".to_owned());
                }
            }
            ScenarioClass::CacheTransition => {
                if self.metrics_snapshot.is_none() {
                    missing_sections.push("metrics_snapshot".to_owned());
                }
            }
            ScenarioClass::TransportFault => {
                if self.trace_summary.is_none() {
                    missing_sections.push("trace_summary".to_owned());
                }
                if !self.timeline.iter().any(|entry| {
                    entry.connection_id.is_some()
                        && entry.stream_id.is_some()
                        && entry.fault_class.is_some()
                }) {
                    missing_sections.push("timeline.transport_correlation".to_owned());
                }
            }
        }

        Phase1ValidationReport {
            scenario_class: self.scenario_class,
            missing_sections,
        }
    }
}

/// Validation result for the canonical replay artifact contract.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Phase1ValidationReport {
    /// Scenario class checked by the validator.
    pub scenario_class: ScenarioClass,
    /// Missing required sections or scenario-specific fields.
    pub missing_sections: Vec<String>,
}

impl Phase1ValidationReport {
    /// Returns whether the artifact satisfies the Phase 1 contract.
    #[must_use]
    pub fn is_valid(&self) -> bool {
        self.missing_sections.is_empty()
    }
}

/// Builds an Alloy contract record from the shared metadata helpers.
#[must_use]
pub fn canonical_alloy_contract_record(
    replay_bundle: &ReplayBundle<'_>,
    manifest: &ScenarioManifest<'_>,
    envelope: &EvidenceEnvelope<'_>,
) -> AlloyContractRecord {
    AlloyContractRecord {
        scope: scope().to_owned(),
        replay_bundle_artifact_name: replay_bundle.artifact_name(),
        evidence_artifact_name: envelope.artifact_name(),
        evidence_report_line: envelope.report_line(),
        manifest_summary: manifest.summary_line(),
        permits_release: envelope.permits_release(),
    }
}