use super::super::super::{
digest::promotion_plan_lineage_digest, ensure::ensure_transform_field,
error::PromotionPlanTransformError, identity::role_summary_artifact_identity_changed,
materialization,
};
use crate::deployment_truth::{
DEPLOYMENT_TRUTH_SCHEMA_VERSION, DeploymentPlanV1, PromotionArtifactLevelV1,
PromotionPlanTransformV1, RoleArtifactV1, RolePromotionPlanTransformV1,
};
pub fn validate_promotion_plan_transform(
transform: &PromotionPlanTransformV1,
) -> Result<(), PromotionPlanTransformError> {
if transform.schema_version != DEPLOYMENT_TRUTH_SCHEMA_VERSION {
return Err(PromotionPlanTransformError::SchemaVersionMismatch {
expected: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
found: transform.schema_version,
});
}
ensure_transform_field("transform_id", &transform.transform_id)?;
ensure_transform_field("target_plan_id", &transform.target_plan_id)?;
ensure_transform_field("promoted_plan_id", &transform.promoted_plan_id)?;
ensure_transform_field(
"promotion_plan_lineage_digest",
&transform.promotion_plan_lineage_digest,
)?;
ensure_transform_field("promoted_plan.plan_id", &transform.promoted_plan.plan_id)?;
if transform.promoted_plan.plan_id != transform.promoted_plan_id {
return Err(PromotionPlanTransformError::PromotedPlanIdMismatch {
expected: transform.promoted_plan_id.clone(),
found: transform.promoted_plan.plan_id.clone(),
});
}
ensure_unique_transform_roles(&transform.roles)?;
for role in &transform.roles {
validate_role_plan_transform(role, &transform.promoted_plan)?;
}
let expected = promotion_plan_lineage_digest(
&transform.target_plan_id,
&transform.promoted_plan_id,
&transform.promoted_plan,
&transform.roles,
);
if expected != transform.promotion_plan_lineage_digest {
return Err(PromotionPlanTransformError::RoleStateMismatch {
role: "promotion_plan_lineage".to_string(),
field: "promotion_plan_lineage_digest",
});
}
Ok(())
}
fn validate_role_plan_transform(
role: &RolePromotionPlanTransformV1,
promoted_plan: &DeploymentPlanV1,
) -> Result<(), PromotionPlanTransformError> {
ensure_transform_field("role", &role.role)?;
let Some(promoted_role) = promoted_plan
.role_artifacts
.iter()
.find(|artifact| artifact.role == role.role)
else {
return Err(PromotionPlanTransformError::PromotedRoleMissing {
role: role.role.clone(),
});
};
ensure_role_matches_promoted_artifact(role, promoted_role)?;
ensure_role_transform_flags_are_consistent(role)?;
materialization::validate_role_materialization_link(role, promoted_role)?;
Ok(())
}
fn ensure_role_matches_promoted_artifact(
role: &RolePromotionPlanTransformV1,
promoted_role: &RoleArtifactV1,
) -> Result<(), PromotionPlanTransformError> {
ensure_role_field_matches(
role,
"artifact_source_after",
role.artifact_source_after == promoted_role.source,
)?;
ensure_role_field_matches(
role,
"wasm_sha256_after",
role.wasm_sha256_after == promoted_role.wasm_sha256,
)?;
ensure_role_field_matches(
role,
"wasm_gz_sha256_after",
role.wasm_gz_sha256_after == promoted_role.wasm_gz_sha256,
)?;
ensure_role_field_matches(
role,
"candid_sha256_after",
role.candid_sha256_after == promoted_role.candid_sha256,
)?;
ensure_role_field_matches(
role,
"canonical_embedded_config_sha256_after",
role.canonical_embedded_config_sha256_after
== promoted_role.canonical_embedded_config_sha256,
)
}
fn ensure_role_transform_flags_are_consistent(
role: &RolePromotionPlanTransformV1,
) -> Result<(), PromotionPlanTransformError> {
ensure_role_field_matches(
role,
"artifact_identity_changed",
role.artifact_identity_changed == role_summary_artifact_identity_changed(role),
)?;
ensure_role_field_matches(
role,
"embedded_config_changed",
role.embedded_config_changed
== (role.canonical_embedded_config_sha256_before
!= role.canonical_embedded_config_sha256_after),
)?;
if role.target_materialization_preserved {
ensure_role_field_matches(
role,
"target_materialization_preserved",
role.promotion_level == PromotionArtifactLevelV1::SourceBuild
&& !role.artifact_identity_changed
&& !role.embedded_config_changed,
)?;
}
Ok(())
}
pub(in crate::deployment_truth::promotion) fn ensure_role_field_matches(
role: &RolePromotionPlanTransformV1,
field: &'static str,
matches: bool,
) -> Result<(), PromotionPlanTransformError> {
if matches {
Ok(())
} else {
Err(PromotionPlanTransformError::RoleStateMismatch {
role: role.role.clone(),
field,
})
}
}
fn ensure_unique_transform_roles(
roles: &[RolePromotionPlanTransformV1],
) -> Result<(), PromotionPlanTransformError> {
let mut seen = std::collections::BTreeSet::new();
for role in roles {
if !seen.insert(role.role.as_str()) {
return Err(PromotionPlanTransformError::DuplicateRole {
role: role.role.clone(),
});
}
}
Ok(())
}