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

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/lib.rs
// version: 5
// version: 6
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -12,6 +12,8 @@ mod environment;
mod solana_ata_execution;
mod solana_execution;
mod solana_memo_execution;
mod solana_metaplex_token_metadata_scenarios;
mod solana_metaplex_token_metadata_validation;
mod solana_token_2022_devnet_execution;
mod solana_token_2022_devnet_scenarios;
mod solana_token_2022_validation;
@@ -65,6 +67,28 @@ pub use self::solana_memo_execution::DevnetMemoExecutionRequest;
pub use self::solana_memo_execution::DevnetMemoExecutionSummary;
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
pub use self::solana_memo_execution::execute_devnet_memo;
/// Stable asset family covered by one Metaplex scenario.
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataAssetFamily;
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenario;
/// Execution mode accepted by one Metaplex scenario.
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenarioMode;
/// Returns the complete ordered synthetic Metaplex scenario inventory.
pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_synthetic_scenarios;
/// Maximum evidence entries accepted by one Metaplex validation scenario.
pub use self::solana_metaplex_token_metadata_validation::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE;
/// One bounded Metaplex validation evidence entry.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationEvidence;
/// Canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrix;
/// One scenario declared by the canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrixScenario;
/// Exact validation status for one Metaplex scenario.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationStatus;
/// Loads and validates the canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::load_metaplex_token_metadata_validation_matrix;
/// Validates the canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::validate_metaplex_token_metadata_validation_matrix;
/// Complete request for one Devnet Token-2022 execution.
pub use self::solana_token_2022_devnet_execution::DevnetSplToken2022ExecutionRequest;
/// Complete result of one Devnet Token-2022 execution.

View File

@@ -0,0 +1,170 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
// version: 1
//! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns.
/// Stable asset family covered by one Metaplex scenario.
#[derive(
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataAssetFamily {
/// Non-fungible token.
Nft,
/// Semi-fungible token.
Sft,
/// Fungible token with Metaplex metadata.
Fungible,
/// Collection metadata and verification flow.
Collection,
/// Programmable non-fungible token.
ProgrammableNft,
}
/// Execution mode accepted by one scenario.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataScenarioMode {
/// Purely deterministic scenario using synthetic accounts and plans.
Synthetic,
/// Devnet or Testnet simulation without submission.
NetworkSimulation,
/// Explicitly authorized Devnet or Testnet submission.
NetworkSubmission,
}
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataScenario {
/// Stable scenario identifier.
pub id: std::string::String,
/// Asset family exercised by the scenario.
pub asset_family: crate::MetaplexTokenMetadataAssetFamily,
/// Execution mode.
pub mode: crate::MetaplexTokenMetadataScenarioMode,
/// Stable operation codes executed in order.
pub operation_codes: std::vec::Vec<std::string::String>,
/// Whether the scenario requires a collection account.
pub requires_collection: bool,
/// Whether the scenario requires programmable authorization rules.
pub requires_programmable_rules: bool,
/// Whether a confirmed postcondition is mandatory.
pub requires_postcondition: bool,
}
/// Returns the complete ordered synthetic Metaplex scenario inventory.
pub fn metaplex_token_metadata_synthetic_scenarios()
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
return vec![
crate::MetaplexTokenMetadataScenario {
id: "metaplex_nft_create_update".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::Nft,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
],
requires_collection: false,
requires_programmable_rules: false,
requires_postcondition: true,
},
crate::MetaplexTokenMetadataScenario {
id: "metaplex_sft_create_mint".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::Sft,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_OPERATION.to_string(),
],
requires_collection: false,
requires_programmable_rules: false,
requires_postcondition: true,
},
crate::MetaplexTokenMetadataScenario {
id: "metaplex_fungible_create_update".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::Fungible,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
],
requires_collection: false,
requires_programmable_rules: false,
requires_postcondition: true,
},
crate::MetaplexTokenMetadataScenario {
id: "metaplex_collection_verify_unverify".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::Collection,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(),
],
requires_collection: true,
requires_programmable_rules: false,
requires_postcondition: true,
},
crate::MetaplexTokenMetadataScenario {
id: "metaplex_pnft_delegate_transfer_revoke".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_TRANSFER_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_REVOKE_OPERATION.to_string(),
],
requires_collection: false,
requires_programmable_rules: true,
requires_postcondition: true,
},
];
}
#[cfg(test)]
mod tests {
#[test]
fn synthetic_inventory_is_exact_unique_and_covers_every_required_asset_family() {
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
assert_eq!(scenarios.len(), 5);
let ids = scenarios
.iter()
.map(|scenario| return scenario.id.as_str())
.collect::<std::collections::BTreeSet<&str>>();
assert_eq!(ids.len(), scenarios.len());
let families = scenarios
.iter()
.map(|scenario| return scenario.asset_family)
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
);
assert_eq!(families.len(), 5);
}
#[test]
fn synthetic_scenarios_are_simulation_safe_and_require_postconditions() {
for scenario in crate::metaplex_token_metadata_synthetic_scenarios() {
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::Synthetic);
assert!(!scenario.operation_codes.is_empty());
assert!(scenario.requires_postcondition);
}
}
#[test]
fn programmable_and_collection_requirements_are_declared_only_where_needed() {
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
let programmable = scenarios
.iter()
.filter(|scenario| return scenario.requires_programmable_rules)
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
assert_eq!(programmable.len(), 1);
assert_eq!(
programmable[0].asset_family,
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft
);
let collection = scenarios
.iter()
.filter(|scenario| return scenario.requires_collection)
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
assert_eq!(collection.len(), 1);
assert_eq!(collection[0].asset_family, crate::MetaplexTokenMetadataAssetFamily::Collection);
}
}

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());
}
}
}