0.4.7-pre.008

This commit is contained in:
2026-08-02 11:58:34 +02:00
parent d831623980
commit d6cb4b0cce
11 changed files with 534 additions and 11 deletions

View File

@@ -0,0 +1,192 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
// version: 1
//! Machine-readable Metaplex Token Metadata validation contract.
/// Maximum evidence entries accepted by one Metaplex validation scenario.
pub const MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE: usize = 64;
/// Exact validation status for one Metaplex scenario.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataValidationStatus {
/// Scenario has not been executed.
NotRun,
/// Scenario passed deterministic synthetic validation.
SyntheticValidated,
/// Scenario was simulated on Devnet or Testnet.
Simulated,
/// Scenario was submitted but confirmation evidence is incomplete.
Submitted,
/// Scenario was confirmed and all postconditions passed.
Confirmed,
/// Scenario is unavailable in the selected environment.
Unavailable,
/// Scenario ran and failed.
Failed,
}
/// One bounded validation evidence entry.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataValidationEvidence {
/// Stable evidence kind.
pub kind: std::string::String,
/// Bounded evidence value.
pub value: std::string::String,
}
/// One scenario declared by the canonical Metaplex validation matrix.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MetaplexTokenMetadataValidationMatrixScenario {
/// Stable scenario identifier.
pub id: std::string::String,
/// Required scenario mode.
pub mode: crate::MetaplexTokenMetadataScenarioMode,
/// Exact validation status.
pub status: crate::MetaplexTokenMetadataValidationStatus,
/// Evidence kinds required before confirmation.
pub required_evidence: std::vec::Vec<std::string::String>,
/// Observed evidence.
#[serde(default)]
pub evidence: std::vec::Vec<crate::MetaplexTokenMetadataValidationEvidence>,
}
/// Canonical Metaplex validation matrix.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MetaplexTokenMetadataValidationMatrix {
/// Matrix schema version.
pub matrix_version: u32,
/// Owning milestone.
pub milestone: std::string::String,
/// Ordered validation scenarios.
pub scenarios: std::vec::Vec<crate::MetaplexTokenMetadataValidationMatrixScenario>,
}
/// Loads and validates the canonical Metaplex validation matrix.
pub fn load_metaplex_token_metadata_validation_matrix()
-> kb_core::Result<crate::MetaplexTokenMetadataValidationMatrix> {
let parsed =
match serde_json::from_str::<crate::MetaplexTokenMetadataValidationMatrix>(include_str!(
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_VALIDATION_MATRIX.json"
)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_matrix_invalid_json",
format!("Metaplex validation matrix JSON is invalid: {error}"),
));
},
};
if let std::result::Result::Err(error) =
validate_metaplex_token_metadata_validation_matrix(&parsed)
{
return std::result::Result::Err(error);
}
return std::result::Result::Ok(parsed);
}
/// Validates the canonical Metaplex validation matrix.
pub fn validate_metaplex_token_metadata_validation_matrix(
matrix: &crate::MetaplexTokenMetadataValidationMatrix,
) -> kb_core::Result<()> {
if matrix.matrix_version != 1 || matrix.milestone != "0.4.7" {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_matrix_contract_mismatch",
"Metaplex validation matrix must use version 1 for milestone 0.4.7",
));
}
let expected = crate::metaplex_token_metadata_synthetic_scenarios()
.iter()
.map(|scenario| return scenario.id.clone())
.collect::<std::vec::Vec<std::string::String>>();
let actual = matrix
.scenarios
.iter()
.map(|scenario| return scenario.id.clone())
.collect::<std::vec::Vec<std::string::String>>();
if actual != expected {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_matrix_inventory_mismatch",
"Metaplex validation matrix inventory differs from the compiled scenario inventory",
));
}
let mut ids = std::collections::BTreeSet::<&str>::new();
for scenario in &matrix.scenarios {
if !ids.insert(scenario.id.as_str()) || scenario.required_evidence.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_matrix_scenario_invalid",
"Metaplex validation scenarios must be unique and declare required evidence",
));
}
if scenario.evidence.len() > crate::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_evidence_limit_exceeded",
"Metaplex validation evidence exceeds the compiled bound",
));
}
if matches!(
scenario.status,
crate::MetaplexTokenMetadataValidationStatus::NotRun
| crate::MetaplexTokenMetadataValidationStatus::Unavailable
) && !scenario.evidence.is_empty()
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_unobserved_with_evidence",
"Not-run or unavailable Metaplex scenarios must not contain evidence",
));
}
if scenario.status == crate::MetaplexTokenMetadataValidationStatus::Confirmed {
let observed = scenario
.evidence
.iter()
.map(|evidence| return evidence.kind.as_str())
.collect::<std::collections::BTreeSet<&str>>();
if !scenario
.required_evidence
.iter()
.all(|kind| return observed.contains(kind.as_str()))
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_validation_confirmed_without_required_evidence",
"Confirmed Metaplex scenario lacks required evidence",
));
}
}
}
return std::result::Result::Ok(());
}
#[cfg(test)]
mod tests {
#[test]
fn canonical_matrix_is_machine_readable_and_matches_synthetic_inventory() {
let result = crate::load_metaplex_token_metadata_validation_matrix();
assert!(result.is_ok());
if let std::result::Result::Ok(matrix) = result {
assert_eq!(matrix.scenarios.len(), 5);
}
}
#[test]
fn confirmed_scenario_requires_every_declared_evidence_kind() {
let result = crate::load_metaplex_token_metadata_validation_matrix();
assert!(result.is_ok());
if let std::result::Result::Ok(mut matrix) = result {
matrix.scenarios[0].status = crate::MetaplexTokenMetadataValidationStatus::Confirmed;
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_err());
matrix.scenarios[0].evidence = matrix.scenarios[0]
.required_evidence
.iter()
.map(|kind| {
return crate::MetaplexTokenMetadataValidationEvidence {
kind: kind.clone(),
value: "synthetic-proof".to_string(),
};
})
.collect();
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_ok());
}
}
}