v0.4.8-pre.013

This commit is contained in:
2026-08-08 22:28:17 +02:00
parent f5efce576b
commit 8b831f8692
77 changed files with 15590 additions and 1585 deletions

View File

@@ -1,8 +1,18 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 29 -->
<!-- version: 33 -->
# CHANGELOG — kb-app-demo-desktop
## 0.4.8-pre.013
- `pre.013-delta-fix-022` restaure la compatibilité `Future + Send` de la commande Tauri Metaplex en corrigeant le runner partagé : l'effacement temporaire `Signer + Sync -> &dyn Signer` est limité à un helper synchrone de signature et n'est plus conservé au travers des `await` ; aucun assouplissement de la commande desktop n'est nécessaire ;
- `pre.013-delta-fix-002` retire la réécriture locale du JSON `Create` par famille et consomme désormais directement la fixture Metaplex réutilisable, afin déviter quun parcours `Fungible` annonce un token standard incompatible avec le mint réellement préparé ;
- conserve laction Create autonome sur une fixture NFT par défaut, tandis que les parcours de scénario sélectionnent explicitement leur famille lors de la préparation.
- `pre.013-delta-fix-001` sérialise explicitement les summaries de backfill, extraction Core et replay dans l'evidence JSON au lieu d'exiger `serde::Serialize` sur les contrats internes de `kb-pipeline` ;
- adapte lappel Metaplex au runner Devnet renforcé sans ajouter encore de nouveau parcours UI ;
- initialise le store PostgreSQL avant une exécution Metaplex réelle et fournit le décodeur ainsi que les matérialiseurs runtime ;
- conserve dans levidence JSON le backfill canonique, lextraction Core, le replay, la seconde passe didempotence, les matérialisations instructionnelles et le diagnostic post-exécution.
## 0.4.8-pre.010
- scinde le panneau Metadata en un module commun, un adaptateur `metadata_metaplex_token_metadata` et un adaptateur `metadata_solana_program` ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# kb-app-demo-desktop
@@ -23,7 +23,7 @@ Les scénarios UI Devnet/Testnet restent dans le desktop. Des tests automatisés
## Exécution Metaplex Token Metadata
Le panneau conserve les scénarios synthétiques et appelle directement le runner réutilisable de `kb-pipeline-demo-scenarios` pour les opérations Metaplex courantes. La simulation RPC est réelle ; la soumission exige deux autorisations distinctes dans la requête desktop.
Le panneau conserve les scénarios synthétiques et appelle directement le runner réutilisable de `kb-pipeline-demo-scenarios` pour les opérations Metaplex courantes. La simulation RPC est réelle ; la soumission exige deux autorisations distinctes dans la requête desktop. Après une confirmation, ladaptateur réutilise PostgreSQL pour conserver lhydratation canonique, lextraction Core, le replay, les matérialisations et la preuve didempotence produites par le runner.
## Exécution Solana Program Metadata

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/USAGE.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# Utilisation de kb-app-demo-desktop
@@ -235,7 +235,7 @@ const summary = await invoke(
console.log(summary);
```
Les valeurs `<...>` dun modèle doivent être remplacées par des comptes réels avant exécution. `submit: true` exige simultanément `operatorConfirmed: true`. Le résultat distingue le plan, le préflight stateful, la simulation RPC, la signature éventuelle, la confirmation et les états avant/après.
Les valeurs `<...>` dun modèle doivent être remplacées par des comptes réels avant exécution. `submit: true` exige simultanément `operatorConfirmed: true`. Le résultat distingue le plan, le préflight stateful, la simulation RPC, la signature éventuelle, la confirmation et les états avant/après. Après soumission confirmée, `evidenceJson` contient également lhydratation canonique, lextraction Core, le replay, la seconde passe didempotence, les matérialisations instructionnelles et le diagnostic post-exécution.
### Diagnostics PostgreSQL

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
// version: 1
// version: 4
//! Desktop adapters for Metaplex Token Metadata execution demo scenarios.
@@ -190,6 +190,13 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let mut execution_request = match kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
request.intent_id,
request.operation_json.as_str(),
@@ -210,9 +217,20 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
execution_request.submit = request.submit;
execution_request.operator_confirmed = request.operator_confirmed;
execution_request.materialize_after_confirmation = request.materialize_after_confirmation;
execution_request.post_validation_max_retries = 20;
execution_request.force_post_validation_replay = false;
if let std::result::Result::Err(error) = execution_request.validate() {
return std::result::Result::Err(error.to_string());
}
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
std::sync::Arc::new(kb_lib::MtMetadataMetaplexTokenMetadataMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
];
let observer = crate::DemoExecutionMetadataObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
@@ -220,9 +238,12 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&execution_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
@@ -241,7 +262,13 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
"confirmation": &summary.confirmation,
"after": &summary.after,
"materializationRequested": summary.materialization_requested,
"materializedSnapshots": &summary.materialized_snapshots
"materializedSnapshots": &summary.materialized_snapshots,
"backfill": summary.backfill.as_ref().map(backfill_summary_json),
"coreExtraction": summary.core_extraction.as_ref().map(core_extraction_summary_json),
"decodeReplay": summary.decode_replay.as_ref().map(decode_replay_summary_json),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(decode_replay_summary_json),
"instructionMaterializations": &summary.materializations,
"postExecution": &summary.post_execution
}));
return std::result::Result::Ok(
crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload {
@@ -263,6 +290,88 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
);
}
fn backfill_summary_json(summary: &kb_pipeline::BackfillSummary) -> serde_json::Value {
return serde_json::json!({
"captureSessionId": summary.capture_session_id,
"filterCode": summary.filter_code,
"role": summary.role,
"provider": summary.provider,
"endpointCode": summary.endpoint_code,
"pagesFetched": summary.pages_fetched,
"candidatesSelected": summary.candidates_selected,
"candidatesStarted": summary.candidates_started,
"candidatesCompleted": summary.candidates_completed,
"candidatesCancelled": summary.candidates_cancelled,
"candidatesNotStarted": summary.candidates_not_started,
"transactionsReceived": summary.transactions_received,
"canonicalInserted": summary.canonical_inserted,
"canonicalSkipped": summary.canonical_skipped,
"existingSkipped": summary.existing_skipped,
"missing": summary.missing,
"failed": summary.failed,
"observationsInserted": summary.observations_inserted,
"attempts": summary.attempts,
"cancelled": summary.cancelled,
"resumeBeforeSignature": summary.resume_before_signature,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn core_extraction_summary_json(summary: &kb_pipeline::CoreExtractionSummary) -> serde_json::Value {
return serde_json::json!({
"processorVersion": summary.processor_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"skipped": summary.skipped,
"extracted": summary.extracted,
"failed": summary.failed,
"cancelledCandidates": summary.cancelled_candidates,
"notStarted": summary.not_started,
"cancelled": summary.cancelled,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
let processors = summary
.processors
.iter()
.map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"dispatched": processor.dispatched,
"skipped": processor.skipped,
"decoded": processor.decoded,
"ignored": processor.ignored,
"unsupported": processor.unsupported,
"failed": processor.failed,
"processingErrors": processor.processing_errors,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
})
.collect::<std::vec::Vec<serde_json::Value>>();
return serde_json::json!({
"campaignId": summary.campaign_id,
"pipelineVersion": summary.pipeline_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"unmatched": summary.unmatched,
"notStarted": summary.not_started,
"failedInputs": summary.failed_inputs,
"processingErrorInputs": summary.processing_error_inputs,
"cancelled": summary.cancelled,
"processors": processors,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn parse_stateful_reads(
value: &str,
) -> std::result::Result<
@@ -280,13 +389,29 @@ fn parse_stateful_reads(
};
}
/// Creates or reuses the Devnet mint fixture used by the named Create scenario.
/// Creates or reuses the Devnet NFT mint fixture used by the standalone Create action.
pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload,
std::string::String,
> {
return prepare_create_fixture_for_family(
state,
profile_name,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
)
.await;
}
async fn prepare_create_fixture_for_family(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload,
std::string::String,
> {
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
std::result::Result::Ok(value) => value,
@@ -303,11 +428,14 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_crea
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let workspace_root = crate::workspace_root_dir();
let options =
kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir)
.with_asset_family(asset_family);
let summary = match kb_pipeline_demo_scenarios::prepare_metaplex_create_fixture(
&http_pool,
&profile,
workspace_root.as_path(),
&kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir),
&options,
)
.await
{
@@ -326,84 +454,6 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_crea
);
}
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_metaplex_token_metadata_prepare_step(
state: tauri::State<'_, crate::AppState>,
@@ -452,27 +502,17 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_step
let (fixture_mint, fixture_metadata, fixture_master_edition, operation_json, fixture_state) =
if operation == "create" {
let fixture =
match crate::demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state,
profile_name,
)
.await
match prepare_create_fixture_for_family(state, profile_name, scenario.asset_family)
.await
{
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,
operation_json,
fixture.operation_json,
if fixture.mint_created {
"mint_prepared".to_string()
} else {