feat(registry): add workspace-scoped artifact metadata
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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(())
|
||||
}
|
||||
Reference in New Issue
Block a user