Files
crank/crates/crank-registry/src/migrations/authority/contract.rs
T

141 lines
5.6 KiB
Rust

use sha2::{Digest, Sha256};
use super::*;
fn sha256_hex(bytes: &[u8]) -> String {
format!("{:x}", Sha256::digest(bytes))
}
#[cfg(test)]
pub(super) fn baseline_source_digest() -> String {
let source = include_str!("../baseline_v1.rs");
let (_, baseline) = source
.split_once("// baseline-v1:start\n")
.expect("baseline start marker must exist");
let (baseline, _) = baseline
.split_once("// baseline-v1:end")
.expect("baseline end marker must exist");
sha256_hex(baseline.as_bytes())
}
pub(super) fn validate_descriptors(
descriptors: &[MigrationDescriptor],
) -> Result<(), MigrationError> {
if descriptors.is_empty() || descriptors.len() > 1_024 {
return Err(MigrationError::new(
"invalid_contract",
"contract.sequence",
None,
"contact_operator",
));
}
for (index, descriptor) in descriptors.iter().enumerate() {
let expected_version = i64::try_from(index + 1).unwrap_or(i64::MAX);
let valid_name = !descriptor.name.is_empty()
&& descriptor.name.len() <= 128
&& descriptor
.name
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
&& !descriptors[..index]
.iter()
.any(|prior| prior.name == descriptor.name);
let valid_source_digest = descriptor.source_digest.len() == 64
&& descriptor
.source_digest
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase());
let valid_checksum = if descriptor.version == 1 {
descriptor.checksum == BASELINE_CHECKSUM
} else {
descriptor.checksum == descriptor.source_digest
};
let valid_window = descriptor.readable_schema_min >= 1
&& descriptor.readable_schema_min <= descriptor.readable_schema_max
&& descriptor.readable_schema_max <= descriptor.version;
let valid_phase = match descriptor.phase {
"expand" => descriptor.backfill == BackfillPolicy::None,
"migrate" => matches!(
descriptor.backfill,
BackfillPolicy::Bounded {
max_batch_rows: 1..=10_000,
max_batch_ms: 1..=60_000,
resumable: true,
}
),
"contract" => {
descriptor.compatibility == "window-closed"
&& descriptor.contract_evidence.is_some_and(|evidence| {
!evidence.is_empty()
&& evidence.len() <= 256
&& !evidence.starts_with('/')
&& !evidence.contains("..")
})
&& descriptor.backfill == BackfillPolicy::None
}
_ => false,
};
let valid_compatibility = matches!(
descriptor.compatibility,
"legacy-baseline" | "n-minus-one-readable" | "window-open" | "window-closed"
);
let valid_owner = !descriptor.owner.is_empty()
&& descriptor.owner.len() <= 128
&& descriptor
.owner
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-');
if descriptor.version != expected_version
|| !valid_name
|| !valid_source_digest
|| !valid_checksum
|| !valid_phase
|| !valid_compatibility
|| !valid_owner
|| !valid_window
|| !descriptor.transactional
{
return Err(MigrationError::new(
"invalid_contract",
"contract.sequence",
Some(descriptor.version),
"contact_operator",
));
}
}
if descriptors.len() != usize::try_from(CURRENT_VERSION).unwrap_or_default()
|| descriptors
.iter()
.map(|descriptor| descriptor.version)
.ne(IMPLEMENTED_VERSIONS.iter().copied())
|| sha256_hex(CONSOLIDATION_SOURCE.as_bytes()) != CONSOLIDATION_SOURCE_SHA256
|| sha256_hex(REQUEST_TRACE_IDENTITY_SOURCE.as_bytes())
!= REQUEST_TRACE_IDENTITY_SOURCE_SHA256
|| sha256_hex(OPERATION_LIFECYCLE_SOURCE.as_bytes()) != OPERATION_LIFECYCLE_SOURCE_SHA256
|| sha256_hex(execution_outcome_v5::SOURCE.as_bytes())
!= execution_outcome_v5::SOURCE_SHA256
|| sha256_hex(platform_key_name_reuse_v6::SOURCE.as_bytes())
!= platform_key_name_reuse_v6::SOURCE_SHA256
|| sha256_hex(master_key_identity_v7::SOURCE.as_bytes())
!= master_key_identity_v7::SOURCE_SHA256
|| sha256_hex(admin_auth_lifecycle_v8::SOURCE.as_bytes())
!= admin_auth_lifecycle_v8::SOURCE_SHA256
|| sha256_hex(agent_catalog_lifecycle_v9::SOURCE.as_bytes())
!= agent_catalog_lifecycle_v9::SOURCE_SHA256
|| sha256_hex(approval_side_effects_v10::SOURCE.as_bytes())
!= approval_side_effects_v10::SOURCE_SHA256
|| sha256_hex(onboarding_product_events_v11::SOURCE.as_bytes())
!= onboarding_product_events_v11::SOURCE_SHA256
|| sha256_hex(artifact_metadata_v12::SOURCE.as_bytes())
!= artifact_metadata_v12::SOURCE_SHA256
{
return Err(MigrationError::new(
"invalid_contract",
"contract.implementation",
Some(CURRENT_VERSION),
"contact_operator",
));
}
Ok(())
}