v0.4.7-pre.014

This commit is contained in:
2026-08-04 19:43:03 +02:00
parent 22228149a3
commit b358b12f3b
6 changed files with 249 additions and 39 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 20
# version: 21
[workspace]
resolver = "3"
@@ -18,7 +18,7 @@ members = [
]
[workspace.package]
version = "0.4.7-pre.13"
version = "0.4.7-pre.14"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"

View File

@@ -0,0 +1,42 @@
<!-- file: docs/validation/V0_4_7_PRE_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md -->
<!-- version: 1 -->
# Validation `0.4.7-pre.014` — parcours Metaplex Devnet cohérents
## Objet
Cette prérelease étend le runner Metadata depuis le seul parcours NFT vers cinq parcours Devnet préparables : NFT, SFT, fungible, collection parent et pNFT sans rule set.
Chaque parcours expose d'abord un cycle borné et comparable :
1. création native d'un mint SPL classique par le workspace ;
2. préparation contextuelle de `Create` ;
3. simulation exacte ;
4. soumission et confirmation explicites ;
5. relecture et matérialisation optionnelle des comptes applicables ;
6. préparation de `UpdateAsUpdateAuthorityV2` depuis l'état créé ;
7. simulation, soumission, confirmation et postconditions.
## Familles et contrats Create
| Parcours | `token_standard` | Edition relue | Particularité |
|------------|---------------------------|--------------:|-----------------------------------------|
| NFT | `NonFungible` | oui | `print_supply = Zero` |
| SFT | `FungibleAsset` | non | `decimals = 0`, pas de master edition |
| Fungible | `Fungible` | non | `decimals = 0`, pas de master edition |
| Collection | `NonFungible` | oui | `collection_details = V1(size=0)` |
| pNFT | `ProgrammableNonFungible` | oui | aucun rule set pour ce premier parcours |
## Limite volontaire
Cette prérelease ne déclare pas encore validées les opérations spécialisées `mint`, `verify`, `unverify`, `delegate`, `lock`, `unlock`, `transfer`, `revoke`, `burn`, `resize` et apparentées. Elles doivent être ajoutées comme étapes dépendantes d'un contexte confirmé, pas comme modèles JSON isolés.
## Validation attendue
- `cargo fmt --all` ;
- `cargo check --workspace` ;
- `cargo clippy --all-targets` ;
- `python3 scripts/audit_rust_workspace_rules.py` ;
- `cargo test -p kb-pipeline-demo-scenarios` ;
- `cargo test -p kb-app-demo-desktop` ;
- pour chaque parcours : préparer `Create`, simuler, soumettre, confirmer, puis préparer et valider `Update`.

View File

@@ -1,8 +1,13 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 21 -->
<!-- version: 22 -->
# CHANGELOG — kb-app-demo-desktop
## 0.4.7-pre.014
- Afficher les cinq parcours Metaplex Devnet dans le runner Metadata.
- Adapter automatiquement le JSON `Create`, les lectures de postcondition et les cibles de matérialisation à la famille sélectionnée.
## 0.4.7-pre.013-fix-015
- aligne les tests desktop sur le nombre réel de scénarios synthétiques et Devnet exposés par les registres réutilisables ;

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata.rs
// version: 12
// version: 13
//! Thin desktop adapters for metadata execution demo scenarios.
@@ -323,6 +323,84 @@ pub(crate) async fn demo_execution_metadata_prepare_create_fixture(
});
}
fn adapt_create_operation_json(
operation_json: &str,
asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::result::Result<std::string::String, std::string::String> {
let mut value: serde_json::Value = match serde_json::from_str(operation_json) {
std::result::Result::Ok(parsed) => parsed,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"invalid generated Metaplex Create JSON: {error}"
));
},
};
if matches!(
asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible
) {
value["master_edition"] = serde_json::Value::Null;
}
let create = match value
.get_mut("create_args")
.and_then(|args| return args.get_mut("V1"))
.and_then(serde_json::Value::as_object_mut)
{
std::option::Option::Some(found) => found,
std::option::Option::None => {
return std::result::Result::Err(
"generated Metaplex Create JSON has no create_args.V1 object".to_string(),
);
},
};
match asset_family {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet NFT"));
create.insert("symbol".to_string(), serde_json::json!("KHNFT"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet SFT"));
create.insert("symbol".to_string(), serde_json::json!("KHSFT"));
create.insert("token_standard".to_string(), serde_json::json!("FungibleAsset"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Fungible"));
create.insert("symbol".to_string(), serde_json::json!("KHFUN"));
create.insert("token_standard".to_string(), serde_json::json!("Fungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Collection"));
create.insert("symbol".to_string(), serde_json::json!("KHCOL"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::json!({"V1": {"size": 0}}));
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet pNFT"));
create.insert("symbol".to_string(), serde_json::json!("KHPNFT"));
create
.insert("token_standard".to_string(), serde_json::json!("ProgrammableNonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("rule_set".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
}
return std::result::Result::Ok(crate::pretty_json(&value));
}
/// Prepares one coherent scenario step and all of its stateful reads.
pub(crate) async fn demo_execution_metadata_prepare_step(
state: tauri::State<'_, crate::AppState>,
@@ -374,11 +452,18 @@ pub(crate) async fn demo_execution_metadata_prepare_step(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation_json = match adapt_create_operation_json(
fixture.operation_json.as_str(),
scenario.asset_family,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
(
fixture.mint,
fixture.metadata,
fixture.master_edition,
fixture.operation_json,
operation_json,
if fixture.mint_created {
"mint_prepared".to_string()
} else {
@@ -480,7 +565,13 @@ pub(crate) async fn demo_execution_metadata_prepare_step(
} else {
serde_json::json!([metadata_read.clone()])
};
let postcondition_reads = if operation == "create" {
let create_reads_edition = matches!(
scenario.asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft
);
let postcondition_reads = if operation == "create" && create_reads_edition {
serde_json::json!([metadata_read, edition_read])
} else {
serde_json::json!([metadata_read])
@@ -675,7 +766,7 @@ mod tests {
.iter()
.filter(|payload| return payload.mode == "network_simulation")
.count(),
1
5
);
}

View File

@@ -1,8 +1,13 @@
<!-- file: kb-pipeline-demo-scenarios/CHANGELOG.md -->
<!-- version: 24 -->
<!-- version: 25 -->
# CHANGELOG — kb-pipeline-demo-scenarios
## 0.4.7-pre.014
- Expose cinq parcours Metaplex Devnet préparables : NFT, SFT, fungible, collection parent et pNFT sans rule set.
- Conserver un cycle borné `Create -> UpdateAsUpdateAuthorityV2` pour chaque famille avant d'étendre les opérations spécialisées.
## 0.4.7-pre.013-fix-014
- crée un nouveau mint natif pour chaque nouvelle préparation `Create`, afin de ne jamais réutiliser un PDA metadata déjà créé ;

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
// version: 4
// version: 5
//! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns.
@@ -224,23 +224,85 @@ pub fn metaplex_token_metadata_synthetic_scenarios()
/// Returns the ordered Metaplex journeys intended for opt-in Devnet simulation.
pub fn metaplex_token_metadata_devnet_scenarios()
-> 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];
let definitions = [
(
"metaplex_nft_create_update_devnet",
"NFT classique : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::Nft,
crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint,
false,
false,
&["metadata_state", "edition_state"][..],
),
(
"metaplex_sft_create_update_devnet",
"SFT : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::Sft,
crate::MetaplexTokenMetadataFixtureKind::SemiFungibleMint,
false,
false,
&["metadata_state"][..],
),
(
"metaplex_fungible_create_update_devnet",
"Fungible : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::Fungible,
crate::MetaplexTokenMetadataFixtureKind::FungibleMint,
false,
false,
&["metadata_state"][..],
),
(
"metaplex_collection_create_update_devnet",
"Collection parent : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::Collection,
crate::MetaplexTokenMetadataFixtureKind::CollectionParentAndMember,
true,
false,
&["metadata_state", "edition_state"][..],
),
(
"metaplex_pnft_create_update_devnet",
"pNFT sans rule set : création confirmée puis mise à jour",
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
crate::MetaplexTokenMetadataFixtureKind::ProgrammableNftTokenRecord,
false,
false,
&["metadata_state", "edition_state"][..],
),
];
return definitions
.into_iter()
.map(
|(
id,
label,
asset_family,
fixture_kind,
requires_collection,
requires_programmable_rules,
materialization_targets,
)| {
let mut value = scenario(
id,
label,
asset_family,
fixture_kind,
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(),
],
requires_collection,
requires_programmable_rules,
materialization_targets,
);
value.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation;
return value;
},
)
.collect();
}
#[cfg(test)]
@@ -279,18 +341,23 @@ mod tests {
#[test]
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!(
scenario.operation_codes[0],
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION
);
assert_eq!(
scenario.operation_codes[1],
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
);
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation);
assert_eq!(devnet.len(), 5);
let families = devnet
.iter()
.map(|scenario| return scenario.asset_family)
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(families.len(), 5);
for scenario in devnet {
assert_eq!(scenario.operation_codes.len(), 2);
assert_eq!(
scenario.operation_codes[0],
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION
);
assert_eq!(
scenario.operation_codes[1],
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
);
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation);
}
}
}