v0.4.7-pre.013

This commit is contained in:
2026-08-04 17:14:01 +02:00
parent 4d08c75b2b
commit 22228149a3
26 changed files with 2367 additions and 223 deletions

View File

@@ -1,7 +1,7 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
// version: 2
// version: 4
//! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns.
//! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns.
/// Stable asset family covered by one Metaplex scenario.
#[derive(
@@ -15,12 +15,52 @@ pub enum MetaplexTokenMetadataAssetFamily {
Sft,
/// Fungible token with Metaplex metadata.
Fungible,
/// Collection metadata and verification flow.
/// Collection parent and member relationship.
Collection,
/// Programmable non-fungible token.
ProgrammableNft,
}
/// Stable fixture graph required by one scenario journey.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataFixtureKind {
/// Fresh zero-decimal classic SPL mint with profile-owned authorities.
ClassicNftMint,
/// Fresh SFT mint and token account.
SemiFungibleMint,
/// Fresh fungible mint and token account.
FungibleMint,
/// Collection parent plus one unverified member.
CollectionParentAndMember,
/// Programmable NFT with token record and optional rule set.
ProgrammableNftTokenRecord,
}
/// Stable lifecycle state consumed or produced by one scenario journey.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataFixtureState {
/// Mint exists but metadata does not.
MintPrepared,
/// Metadata exists and remains mutable.
MetadataCreated,
/// Master edition exists.
MasterEditionCreated,
/// Collection member exists but is not verified.
CollectionMemberUnverified,
/// Collection member is verified.
CollectionMemberVerified,
/// Programmable token record exists.
ProgrammableTokenRecordCreated,
/// Programmable asset is delegated.
Delegated,
/// Programmable asset is locked.
Locked,
/// Scenario has reached its terminal validated state.
Validated,
}
/// Execution mode accepted by one scenario.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
@@ -33,13 +73,21 @@ pub enum MetaplexTokenMetadataScenarioMode {
NetworkSubmission,
}
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
/// One stable Metaplex journey 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,
/// Operator-visible scenario label.
pub label: std::string::String,
/// Asset family exercised by the scenario.
pub asset_family: crate::MetaplexTokenMetadataAssetFamily,
/// Fixture graph required by the scenario.
pub fixture_kind: crate::MetaplexTokenMetadataFixtureKind,
/// Required initial lifecycle state.
pub initial_state: crate::MetaplexTokenMetadataFixtureState,
/// Expected terminal lifecycle state.
pub resulting_state: crate::MetaplexTokenMetadataFixtureState,
/// Execution mode.
pub mode: crate::MetaplexTokenMetadataScenarioMode,
/// Stable operation codes executed in order.
@@ -50,93 +98,155 @@ pub struct MetaplexTokenMetadataScenario {
pub requires_programmable_rules: bool,
/// Whether a confirmed postcondition is mandatory.
pub requires_postcondition: bool,
/// Canonical account projections expected when materialization is enabled.
pub materialization_targets: std::vec::Vec<std::string::String>,
}
/// Returns the complete ordered synthetic Metaplex scenario inventory.
fn scenario(
id: &str,
label: &str,
asset_family: crate::MetaplexTokenMetadataAssetFamily,
fixture_kind: crate::MetaplexTokenMetadataFixtureKind,
initial_state: crate::MetaplexTokenMetadataFixtureState,
resulting_state: crate::MetaplexTokenMetadataFixtureState,
operation_codes: std::vec::Vec<std::string::String>,
requires_collection: bool,
requires_programmable_rules: bool,
materialization_targets: &[&str],
) -> crate::MetaplexTokenMetadataScenario {
return crate::MetaplexTokenMetadataScenario {
id: id.to_string(),
label: label.to_string(),
asset_family,
fixture_kind,
initial_state,
resulting_state,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes,
requires_collection,
requires_programmable_rules,
requires_postcondition: true,
materialization_targets: materialization_targets
.iter()
.map(|value| return (*value).to_string())
.collect(),
};
}
/// Returns the complete ordered synthetic Metaplex journey 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![
scenario(
"metaplex_nft_create_update",
"NFT classique : création, mise à jour et édition maître",
crate::MetaplexTokenMetadataAssetFamily::Nft,
crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint,
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
crate::MetaplexTokenMetadataFixtureState::MasterEditionCreated,
vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_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![
false,
false,
&["metadata_state", "edition_state"],
),
scenario(
"metaplex_sft_create_mint",
"SFT : création des metadata puis émission de supply",
crate::MetaplexTokenMetadataAssetFamily::Sft,
crate::MetaplexTokenMetadataFixtureKind::SemiFungibleMint,
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
crate::MetaplexTokenMetadataFixtureState::Validated,
vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_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_fungible_create_update".to_string(),
asset_family: crate::MetaplexTokenMetadataAssetFamily::Fungible,
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
operation_codes: vec![
false,
false,
&["metadata_state"],
),
scenario(
"metaplex_fungible_create_update",
"Fungible : création et transitions dautorité",
crate::MetaplexTokenMetadataAssetFamily::Fungible,
crate::MetaplexTokenMetadataFixtureKind::FungibleMint,
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
crate::MetaplexTokenMetadataFixtureState::Validated,
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![
false,
false,
&["metadata_state"],
),
scenario(
"metaplex_collection_verify_unverify",
"Collection : membre non vérifié, vérification puis révocation",
crate::MetaplexTokenMetadataAssetFamily::Collection,
crate::MetaplexTokenMetadataFixtureKind::CollectionParentAndMember,
crate::MetaplexTokenMetadataFixtureState::CollectionMemberUnverified,
crate::MetaplexTokenMetadataFixtureState::CollectionMemberVerified,
vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_SET_COLLECTION_SIZE_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![
true,
false,
&["metadata_state", "edition_state"],
),
scenario(
"metaplex_pnft_delegate_transfer_revoke",
"pNFT : token record, délégation, verrouillage, transfert et révocation",
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
crate::MetaplexTokenMetadataFixtureKind::ProgrammableNftTokenRecord,
crate::MetaplexTokenMetadataFixtureState::ProgrammableTokenRecordCreated,
crate::MetaplexTokenMetadataFixtureState::Validated,
vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_LOCK_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNLOCK_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,
},
false,
true,
&["metadata_state", "programmable_token_state", "delegate_admin_state"],
),
];
}
/// Returns the ordered Metaplex scenarios intended for opt-in Devnet simulation.
/// Returns the ordered Metaplex journeys intended for opt-in Devnet simulation.
pub fn metaplex_token_metadata_devnet_scenarios()
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
return crate::metaplex_token_metadata_synthetic_scenarios()
.into_iter()
.map(|mut scenario| {
scenario.id = format!("{}_devnet_simulation", scenario.id);
scenario.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation;
return scenario;
})
.collect::<std::vec::Vec<crate::MetaplexTokenMetadataScenario>>();
let mut scenario = scenario(
"metaplex_nft_create_update_devnet",
"NFT classique : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::Nft,
crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint,
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
crate::MetaplexTokenMetadataFixtureState::MetadataCreated,
vec![
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
],
false,
false,
&["metadata_state", "edition_state"],
);
scenario.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation;
return vec![scenario];
}
#[cfg(test)]
mod tests {
#[test]
fn synthetic_inventory_is_exact_unique_and_covers_every_required_asset_family() {
fn inventory_is_exact_unique_and_uses_coherent_fixture_graphs() {
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
assert_eq!(scenarios.len(), 5);
let ids = scenarios
@@ -144,56 +254,43 @@ mod tests {
.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);
for scenario in scenarios {
assert!(!scenario.label.trim().is_empty());
assert!(!scenario.operation_codes.is_empty());
assert!(!scenario.materialization_targets.is_empty());
assert!(scenario.requires_postcondition);
}
}
#[test]
fn devnet_inventory_mirrors_every_asset_family_as_network_simulation() {
let synthetic = crate::metaplex_token_metadata_synthetic_scenarios();
let devnet = crate::metaplex_token_metadata_devnet_scenarios();
assert_eq!(devnet.len(), synthetic.len());
assert!(devnet.iter().all(|scenario| return scenario.mode
== crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation));
assert!(devnet.iter().all(|scenario| return scenario.id.ends_with("_devnet_simulation")));
let families = devnet
.iter()
.map(|scenario| return scenario.asset_family)
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
);
assert_eq!(families.len(), 5);
fn collection_and_programmable_journeys_declare_special_requirements() {
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
let collection = scenarios.iter().find(|scenario| {
return scenario.asset_family == crate::MetaplexTokenMetadataAssetFamily::Collection;
});
assert!(collection.is_some_and(|scenario| return scenario.requires_collection));
let programmable = scenarios.iter().find(|scenario| {
return scenario.asset_family
== crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft;
});
assert!(programmable.is_some_and(|scenario| return scenario.requires_programmable_rules));
}
#[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);
fn devnet_inventory_exposes_only_fully_preparable_journeys() {
let devnet = crate::metaplex_token_metadata_devnet_scenarios();
assert_eq!(devnet.len(), 1);
let scenario = &devnet[0];
assert_eq!(scenario.asset_family, crate::MetaplexTokenMetadataAssetFamily::Nft);
assert_eq!(scenario.operation_codes.len(), 2);
assert_eq!(
programmable[0].asset_family,
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft
scenario.operation_codes[0],
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION
);
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);
assert_eq!(
scenario.operation_codes[1],
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
);
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation);
}
}