canic-host 0.70.4

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use crate::deployment_truth::{
    ArtifactPromotionProvenanceReportV1, DEPLOYMENT_TRUTH_SCHEMA_VERSION,
    PromotionReadinessStatusV1, RolePromotionProvenanceV1, SafetyFindingV1, SafetySeverityV1,
};
use std::collections::BTreeSet;

use super::super::super::{
    digest::artifact_promotion_provenance_digest,
    ensure::{ensure_provenance_report_field, ensure_provenance_report_sha256},
    error::ArtifactPromotionProvenanceReportError,
};

pub fn validate_artifact_promotion_provenance_report(
    report: &ArtifactPromotionProvenanceReportV1,
) -> Result<(), ArtifactPromotionProvenanceReportError> {
    if report.schema_version != DEPLOYMENT_TRUTH_SCHEMA_VERSION {
        return Err(
            ArtifactPromotionProvenanceReportError::SchemaVersionMismatch {
                expected: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
                found: report.schema_version,
            },
        );
    }
    ensure_provenance_report_field("report_id", &report.report_id)?;
    ensure_provenance_report_field(
        "artifact_promotion_plan_id",
        &report.artifact_promotion_plan_id,
    )?;
    ensure_provenance_report_sha256(
        "artifact_promotion_plan_digest",
        &report.artifact_promotion_plan_digest,
    )?;
    ensure_provenance_report_field("target_plan_id", &report.target_plan_id)?;
    ensure_provenance_report_field("promoted_plan_id", &report.promoted_plan_id)?;
    ensure_provenance_report_field(
        "promotion_plan_lineage_digest",
        &report.promotion_plan_lineage_digest,
    )?;
    ensure_provenance_report_sha256("provenance_report_digest", &report.provenance_report_digest)?;
    ensure_provenance_report_field("readiness_id", &report.readiness_id)?;
    ensure_provenance_report_field(
        "artifact_identity_report_id",
        &report.artifact_identity_report_id,
    )?;
    ensure_provenance_report_field("transform_id", &report.transform_id)?;
    if let Some(lineage_id) = &report.target_execution_lineage_id {
        ensure_provenance_report_field("target_execution_lineage_id", lineage_id)?;
    }
    if let Some(report_id) = &report.wasm_store_identity_report_id {
        ensure_provenance_report_field("wasm_store_identity_report_id", report_id)?;
    }
    if let Some(digest) = &report.wasm_store_identity_report_digest {
        ensure_provenance_report_sha256("wasm_store_identity_report_digest", digest)?;
        if report.wasm_store_identity_report_id.is_none() {
            return Err(ArtifactPromotionProvenanceReportError::LinkageMismatch {
                field: "wasm_store_identity_report_digest",
            });
        }
    }
    if let Some(verification_id) = &report.wasm_store_catalog_verification_id {
        ensure_provenance_report_field("wasm_store_catalog_verification_id", verification_id)?;
        if report.wasm_store_identity_report_id.is_none() {
            return Err(ArtifactPromotionProvenanceReportError::LinkageMismatch {
                field: "wasm_store_catalog_verification_id",
            });
        }
    }
    if let Some(digest) = &report.wasm_store_catalog_verification_digest {
        ensure_provenance_report_sha256("wasm_store_catalog_verification_digest", digest)?;
        if report.wasm_store_catalog_verification_id.is_none() {
            return Err(ArtifactPromotionProvenanceReportError::LinkageMismatch {
                field: "wasm_store_catalog_verification_digest",
            });
        }
    }
    if let Some(report_id) = &report.materialization_identity_report_id {
        ensure_provenance_report_field("materialization_identity_report_id", report_id)?;
    }
    if let Some(digest) = &report.materialization_identity_report_digest {
        ensure_provenance_report_sha256("materialization_identity_report_digest", digest)?;
        if report.materialization_identity_report_id.is_none() {
            return Err(ArtifactPromotionProvenanceReportError::LinkageMismatch {
                field: "materialization_identity_report_digest",
            });
        }
    }
    ensure_provenance_report_status_matches_blockers(report)?;
    ensure_unique_provenance_roles(&report.roles)?;
    for role in &report.roles {
        validate_role_promotion_provenance(role)?;
    }
    validate_provenance_report_blockers(&report.blockers)?;
    if report.provenance_report_digest != artifact_promotion_provenance_digest(report) {
        return Err(ArtifactPromotionProvenanceReportError::LinkageMismatch {
            field: "provenance_report_digest",
        });
    }
    Ok(())
}

const fn ensure_provenance_report_status_matches_blockers(
    report: &ArtifactPromotionProvenanceReportV1,
) -> Result<(), ArtifactPromotionProvenanceReportError> {
    match (report.status, report.blockers.is_empty()) {
        (PromotionReadinessStatusV1::Ready, false)
        | (PromotionReadinessStatusV1::Blocked, true) => Err(
            ArtifactPromotionProvenanceReportError::StatusBlockerMismatch {
                status: report.status,
                blocker_count: report.blockers.len(),
            },
        ),
        _ => Ok(()),
    }
}

fn ensure_unique_provenance_roles(
    roles: &[RolePromotionProvenanceV1],
) -> Result<(), ArtifactPromotionProvenanceReportError> {
    let mut seen = BTreeSet::new();
    for role in roles {
        if !seen.insert(role.role.as_str()) {
            return Err(ArtifactPromotionProvenanceReportError::DuplicateRole {
                role: role.role.clone(),
            });
        }
    }
    Ok(())
}

fn validate_role_promotion_provenance(
    role: &RolePromotionProvenanceV1,
) -> Result<(), ArtifactPromotionProvenanceReportError> {
    ensure_provenance_report_field("role", &role.role)?;
    if let Some(evidence_id) = &role.materialization_evidence_id {
        ensure_provenance_report_field("materialization_evidence_id", evidence_id)?;
    }
    if let Some(digest) = &role.materialization_evidence_digest {
        ensure_provenance_report_sha256("materialization_evidence_digest", digest)?;
    }
    if let Some(locator) = &role.wasm_store_locator {
        ensure_provenance_report_field("wasm_store_locator", locator)?;
    }
    if let Some(digest) = &role.wasm_store_catalog_observation_digest {
        ensure_provenance_report_sha256("wasm_store_catalog_observation_digest", digest)?;
    }
    Ok(())
}

fn validate_provenance_report_blockers(
    blockers: &[SafetyFindingV1],
) -> Result<(), ArtifactPromotionProvenanceReportError> {
    for blocker in blockers {
        ensure_provenance_report_field("blocker.code", &blocker.code)?;
        ensure_provenance_report_field("blocker.message", &blocker.message)?;
        if blocker.severity != SafetySeverityV1::HardFailure {
            return Err(
                ArtifactPromotionProvenanceReportError::BlockerSeverityMismatch {
                    severity: blocker.severity,
                },
            );
        }
    }
    Ok(())
}