Skip to main content

canic_host/deployment_truth/promotion/materialization/
evidence.rs

1use crate::deployment_truth::{
2    BuildMaterializationEvidenceV1, BuildMaterializationInputV1, BuildMaterializationResultV1,
3    BuildRecipeIdentityV1, DEPLOYMENT_TRUTH_SCHEMA_VERSION,
4};
5
6use super::super::digest::{
7    build_materialization_evidence_digest, build_materialization_input_digest,
8};
9use super::super::ensure::{
10    ensure_materialization_field, ensure_materialization_link, ensure_materialization_sha256,
11};
12use super::super::error::PromotionMaterializationIdentityError;
13use super::super::request::BuildMaterializationEvidenceRequest;
14
15pub fn build_materialization_evidence(
16    request: BuildMaterializationEvidenceRequest,
17) -> Result<BuildMaterializationEvidenceV1, PromotionMaterializationIdentityError> {
18    ensure_materialization_field("evidence_id", &request.evidence_id)?;
19    validate_build_recipe_identity(&request.recipe)?;
20    validate_build_materialization_input(&request.materialization_input)?;
21    validate_build_materialization_result(&request.materialization_result)?;
22    let computed_materialization_input_digest =
23        build_materialization_input_digest(&request.materialization_input);
24    let mut evidence = BuildMaterializationEvidenceV1 {
25        schema_version: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
26        evidence_id: request.evidence_id,
27        materialization_evidence_digest: String::new(),
28        recipe_id_matches_input: request.recipe.recipe_id
29            == request.materialization_input.build_recipe_id,
30        recipe_id_matches_result: request.recipe.recipe_id
31            == request.materialization_result.build_recipe_id,
32        materialization_input_digest_matches_result: computed_materialization_input_digest
33            == request.materialization_result.materialization_input_digest,
34        computed_materialization_input_digest,
35        recipe: request.recipe,
36        materialization_input: request.materialization_input,
37        materialization_result: request.materialization_result,
38    };
39    evidence.materialization_evidence_digest = build_materialization_evidence_digest(&evidence);
40    validate_build_materialization_evidence(&evidence)?;
41    Ok(evidence)
42}
43
44pub fn validate_build_materialization_evidence(
45    evidence: &BuildMaterializationEvidenceV1,
46) -> Result<(), PromotionMaterializationIdentityError> {
47    if evidence.schema_version != DEPLOYMENT_TRUTH_SCHEMA_VERSION {
48        return Err(
49            PromotionMaterializationIdentityError::SchemaVersionMismatch {
50                expected: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
51                found: evidence.schema_version,
52            },
53        );
54    }
55    ensure_materialization_field("evidence_id", &evidence.evidence_id)?;
56    ensure_materialization_sha256(
57        "materialization_evidence_digest",
58        &evidence.materialization_evidence_digest,
59    )?;
60    validate_build_recipe_identity(&evidence.recipe)?;
61    validate_build_materialization_input(&evidence.materialization_input)?;
62    validate_build_materialization_result(&evidence.materialization_result)?;
63    ensure_materialization_sha256(
64        "computed_materialization_input_digest",
65        &evidence.computed_materialization_input_digest,
66    )?;
67    ensure_materialization_link(
68        "recipe_id_matches_input",
69        evidence.recipe_id_matches_input
70            == (evidence.recipe.recipe_id == evidence.materialization_input.build_recipe_id),
71    )?;
72    ensure_materialization_link("recipe_id_matches_input", evidence.recipe_id_matches_input)?;
73    ensure_materialization_link(
74        "recipe_id_matches_result",
75        evidence.recipe_id_matches_result
76            == (evidence.recipe.recipe_id == evidence.materialization_result.build_recipe_id),
77    )?;
78    ensure_materialization_link(
79        "recipe_id_matches_result",
80        evidence.recipe_id_matches_result,
81    )?;
82    let computed = build_materialization_input_digest(&evidence.materialization_input);
83    if computed != evidence.computed_materialization_input_digest {
84        return Err(PromotionMaterializationIdentityError::DigestMismatch {
85            field: "computed_materialization_input_digest",
86            expected: computed,
87            found: evidence.computed_materialization_input_digest.clone(),
88        });
89    }
90    ensure_materialization_link(
91        "materialization_input_digest_matches_result",
92        evidence.materialization_input_digest_matches_result
93            == (evidence.computed_materialization_input_digest
94                == evidence.materialization_result.materialization_input_digest),
95    )?;
96    ensure_materialization_link(
97        "materialization_input_digest_matches_result",
98        evidence.materialization_input_digest_matches_result,
99    )?;
100    if evidence.materialization_evidence_digest != build_materialization_evidence_digest(evidence) {
101        return Err(PromotionMaterializationIdentityError::LinkageMismatch {
102            field: "materialization_evidence_digest",
103        });
104    }
105    Ok(())
106}
107
108pub fn validate_build_recipe_identity(
109    recipe: &BuildRecipeIdentityV1,
110) -> Result<(), PromotionMaterializationIdentityError> {
111    ensure_materialization_field("recipe_id", &recipe.recipe_id)?;
112    ensure_materialization_field("source_revision", &recipe.source_revision)?;
113    ensure_materialization_field("package_or_role_selector", &recipe.package_or_role_selector)?;
114    ensure_materialization_field("cargo_profile", &recipe.cargo_profile)?;
115    ensure_materialization_sha256("cargo_features_digest", &recipe.cargo_features_digest)?;
116    ensure_materialization_sha256("cargo_lock_digest", &recipe.cargo_lock_digest)?;
117    ensure_materialization_field("rust_toolchain", &recipe.rust_toolchain)?;
118    ensure_materialization_field("builder_version", &recipe.builder_version)?;
119    ensure_materialization_field("target_triple", &recipe.target_triple)?;
120    ensure_materialization_field("linker_identity", &recipe.linker_identity)?;
121    ensure_materialization_field("deterministic_build_mode", &recipe.deterministic_build_mode)?;
122    ensure_materialization_field("wasm_opt_version", &recipe.wasm_opt_version)?;
123    ensure_materialization_field("compression_identity", &recipe.compression_identity)?;
124    Ok(())
125}
126
127pub fn validate_build_materialization_input(
128    input: &BuildMaterializationInputV1,
129) -> Result<(), PromotionMaterializationIdentityError> {
130    ensure_materialization_field("materialization_input_id", &input.materialization_input_id)?;
131    ensure_materialization_field("build_recipe_id", &input.build_recipe_id)?;
132    ensure_materialization_sha256(
133        "canonical_embedded_config_sha256",
134        &input.canonical_embedded_config_sha256,
135    )?;
136    ensure_materialization_field("network", &input.network)?;
137    ensure_materialization_field("root_trust_anchor", &input.root_trust_anchor)?;
138    ensure_materialization_field("runtime_variant", &input.runtime_variant)?;
139    Ok(())
140}
141
142pub fn validate_build_materialization_result(
143    result: &BuildMaterializationResultV1,
144) -> Result<(), PromotionMaterializationIdentityError> {
145    ensure_materialization_field(
146        "materialization_result_id",
147        &result.materialization_result_id,
148    )?;
149    ensure_materialization_field("build_recipe_id", &result.build_recipe_id)?;
150    ensure_materialization_sha256(
151        "materialization_input_digest",
152        &result.materialization_input_digest,
153    )?;
154    ensure_materialization_sha256("wasm_sha256", &result.wasm_sha256)?;
155    ensure_materialization_sha256("wasm_gz_sha256", &result.wasm_gz_sha256)?;
156    ensure_materialization_sha256("installed_module_hash", &result.installed_module_hash)?;
157    ensure_materialization_sha256("candid_sha256", &result.candid_sha256)?;
158    Ok(())
159}