diff --git a/Cargo.toml b/Cargo.toml index 208ac96..f2e7d01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 15 +# version: 16 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.7-pre.8" +version = "0.4.7-pre.9" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md b/docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md index 452128f..d75c919 100644 --- a/docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md +++ b/docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.4.7` — achèvement de Metaplex Token Metadata @@ -592,3 +592,12 @@ Le développement fonctionnel peut commencer après acceptation des recommandati - aucune soumission automatique ; - CLI inchangé faute de besoin démontré pour une fixture Metaplex dédiée ; - scénarios UI du desktop conservés pour `pre.009`. + + +## État de `0.4.7-pre.009` + +- fenêtre desktop Metaplex dédiée ; +- adaptateurs Tauri limités aux contrats de scénarios ; +- affichage des exigences de préflight, simulation-first et postconditions ; +- scénario sélectionné restauré à la réouverture ; +- aucune logique métier dupliquée et aucune soumission automatique. diff --git a/docs/validation/V0_4_7_PRE_009_METAPLEX_DESKTOP.md b/docs/validation/V0_4_7_PRE_009_METAPLEX_DESKTOP.md new file mode 100644 index 0000000..45a3bd5 --- /dev/null +++ b/docs/validation/V0_4_7_PRE_009_METAPLEX_DESKTOP.md @@ -0,0 +1,37 @@ + + + +# Validation `0.4.7-pre.009` — desktop Metaplex Token Metadata + +## Périmètre + +La prerelease ajoute la fenêtre Tauri `demo_execution_metadata` et adapte les inventaires synthétiques et Devnet de `kb-pipeline-demo-scenarios`. + +## État constaté après `fix-001` + +Le panneau changeait uniquement les contrats JSON affichés. Il ne chargeait aucun profil Devnet, ne préparait aucune base PostgreSQL et n'appelait aucune commande de simulation ou de soumission Metaplex. La présence de scénarios marqués `network_simulation` ne constituait donc pas une validation réseau. + +## Correction `fix-002` + +- ajout du choix d'un profil Devnet compatible ; +- réutilisation de la commande commune `demo_execution_solana_core_options` pour charger les profils ; +- réutilisation de `demo_execution_devnet_prepare_profile` pour vérifier ou initialiser réellement la base PostgreSQL du profil ; +- affichage du rapport de préparation de la base avec le JSON viewer commun ; +- ajout explicite de `networkExecutionPerformed: false` et `networkEvidenceCollected: false` dans les résultats tant qu'aucune transaction Metaplex n'est simulée ; +- suppression de toute présentation des contrats Devnet comme preuve de simulation réseau ; +- conservation des scénarios synthétiques de démonstration. + +## Limite restante + +`fix-002` corrige le faux raccordement Devnet et restaure le chargement du profil et de la base. Il n'ajoute pas encore l'orchestration complète d'une transaction Metaplex Devnet. `pre.009` ne peut donc pas être déclarée validée sur réseau tant que le desktop n'appelle pas un scénario d'exécution réel produisant au minimum une simulation RPC, un slot et les postconditions correspondantes. + +## Validation locale + +```bash +cargo fmt --all +cargo check --workspace +cargo clippy --all-targets +python3 scripts/audit_rust_workspace_rules.py +cargo test -p kb-app-demo-desktop +cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json +``` diff --git a/kb-app-demo-desktop/CHANGELOG.md b/kb-app-demo-desktop/CHANGELOG.md index c4ad700..a3aa352 100644 --- a/kb-app-demo-desktop/CHANGELOG.md +++ b/kb-app-demo-desktop/CHANGELOG.md @@ -1,8 +1,32 @@ - + # CHANGELOG — kb-app-demo-desktop +## 0.4.7-pre.009-fix-002 + +- correction du panneau qui présentait des contrats Devnet sans exécuter de commande réseau ; +- chargement explicite du profil Devnet et préparation réelle de sa base PostgreSQL ; +- affichage du contexte de profil et du rapport de readiness dans le JSON viewer commun ; +- marquage explicite de l'absence de simulation et de preuves réseau ; +- consolidation du rapport de validation `pre.009` et suppression du rapport de correctif dupliqué. + +## 0.4.7-pre.009-fix-001 + +- renommage de la fenêtre en `demo_execution_metadata` ; +- ajout des accordéons extensibles par famille et mode ; +- réutilisation du JSON viewer partagé ; +- ajout des inventaires Devnet Metaplex en parallèle des scénarios synthétiques ; +- alignement des versions Tauri et npm sur `0.4.7`. + +## 0.4.7-pre.009 — panneau Exécution Metadata Devnet + +- ajout d'une fenêtre dédiée aux scénarios Exécution Metadata ; +- adaptateurs Tauri minces vers `kb-pipeline-demo-scenarios` ; +- affichage des opérations, exigences de préflight et preuves attendues ; +- restauration du scénario sélectionné lors de la réouverture de la fenêtre ; +- aucune logique métier ni soumission automatique dans le desktop. + ## 0.4.6 - alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ; diff --git a/kb-app-demo-desktop/README.md b/kb-app-demo-desktop/README.md index eddb811..41fd522 100644 --- a/kb-app-demo-desktop/README.md +++ b/kb-app-demo-desktop/README.md @@ -1,51 +1,35 @@ - + # kb-app-demo-desktop -`kb-app-demo-desktop` est l’application Tauri de démonstration et de validation opérateur de `khadhroony-bot3`. +`kb-app-demo-desktop` est l’application Tauri de démonstration, de diagnostic et de validation opérateur de `khadhroony-bot3`. -La crate reste volontairement mixte : +La crate reste un package mixte bibliothèque et binaire. Elle fournit les fenêtres et adaptateurs nécessaires pour utiliser les capacités réutilisables du workspace sans déplacer leur logique métier dans l’interface. -- bibliothèque `kb_app_demo_desktop_lib` ; -- binaire `kb-app-demo-desktop`. +## Fonctionnalités -## Responsabilités - -- démarrage Tauri et gestion d’une instance unique ; -- chargement de la configuration et du logging ; -- fenêtres de diagnostic et démonstration ; -- commandes Tauri pour HTTP, WebSocket, SQL, backfill, extraction Core et replay ; -- commandes de simulation et exécution Devnet ; -- adaptation TS-RS des résultats des crates réutilisables ; -- gestion de progression, annulation et état applicatif. +- configuration et diagnostics de logging ; +- transport HTTP JSON-RPC et WebSocket ; +- diagnostics PostgreSQL, requêtes et replay candidates ; +- backfill, extraction Core et replay de décodage ; +- démonstrations SPL Token, Token-2022 et ATA ; +- exécution Devnet Solana Core, SPL et metadata ; +- affichage des plans, simulations, confirmations, postconditions et matérialisations ; +- progression, annulation et restauration de l’état des fenêtres. ## Frontière architecturale -La logique générique reste dans : +La logique générique reste dans `kb-lib`, `kb-pipeline`, `kb-pipeline-demo-scenarios`, `kb-onchain-transport`, `kb-store` et `kb-wallet`. Le desktop conserve uniquement les commandes Tauri minces, les modèles TS-RS et la présentation opérateur. -- `kb-pipeline-demo-scenarios` pour les scénarios réutilisables ; -- `kb-pipeline` pour l’orchestration ; -- `kb-onchain-transport` pour les communications réseau ; -- `kb-store` pour PostgreSQL ; -- `kb-lib` pour décodage, exécution et matérialisation ; -- `kb-wallet` pour les signers locaux. - -Le desktop ne doit pas devenir l’unique endroit où un scénario fonctionnel existe. - -## Validation frontend - -La validation autorisée est : +## Validation ```bash cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json ``` -Ne pas utiliser `npm --prefix kb-app-demo-desktop run build` comme validation du desktop. - ## Documentation - [USAGE.md](USAGE.md) - [TODO.md](TODO.md) - [CHANGELOG.md](CHANGELOG.md) -- [Scénarios réutilisables](../kb-pipeline-demo-scenarios/README.md) diff --git a/kb-app-demo-desktop/TODO.md b/kb-app-demo-desktop/TODO.md index 970a099..1fc0fd6 100644 --- a/kb-app-demo-desktop/TODO.md +++ b/kb-app-demo-desktop/TODO.md @@ -5,7 +5,7 @@ ## `0.4.7` -- [ ] Metaplex Token Metadata - ajouter les panneaux nécessaires aux scénarios complétés. +- [ ] Exécution Metadata Devnet - ajouter les panneaux nécessaires aux scénarios complétés. ## Versions ultérieures @@ -17,3 +17,5 @@ - [ ] Registre ElGamal - confirmer le déploiement réseau et la disponibilité des preuves avant de rendre le panneau exécutable. - [ ] Registre ElGamal - raccorder un handler uniquement après validation d’un scénario réutilisable hors desktop. + +- Validation manuelle Metaplex — exécuter les parcours réseau disponibles et confirmer la cohérence préflight/simulation/postconditions pendant `0.4.7-pre.010`. diff --git a/kb-app-demo-desktop/USAGE.md b/kb-app-demo-desktop/USAGE.md index cfada53..c0fe1fa 100644 --- a/kb-app-demo-desktop/USAGE.md +++ b/kb-app-demo-desktop/USAGE.md @@ -244,3 +244,19 @@ La base validée de `pre.062` contient 117 tests pour cette crate. - le desktop est une application de démonstration et validation, pas le worker de production ; - les payloads Tauri sont une API applicative spécifique au frontend ; - l’application nécessite un environnement graphique pour sa validation complète. + +## Ouvrir le panneau Exécution Metadata Devnet + +Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metadata Devnet**. Le panneau charge l’inventaire canonique par la commande Tauri `demo_execution_metadata_scenarios`, puis affiche pour chaque scénario : + +- les codes d’opération ; +- les exigences de collection et de rule set ; +- les contrôles de préflight ; +- la politique simulation-first ; +- les preuves de postcondition requises. + +Le scénario sélectionné est conservé dans le stockage local de la WebView et restauré à la réouverture. + +## Exécution metadata Devnet + +Depuis **Exécution Devnet → Metadata**, ouvrir la fenêtre `demo_execution_metadata`. L’accordéon Metaplex sépare les scénarios synthétiques des scénarios Devnet et conserve une structure extensible pour SPL Token Metadata. Les résultats JSON sont rendus avec le composant `json_viewer` partagé. diff --git a/kb-app-demo-desktop/capabilities/default.json b/kb-app-demo-desktop/capabilities/default.json index 9a233a8..41b298d 100644 --- a/kb-app-demo-desktop/capabilities/default.json +++ b/kb-app-demo-desktop/capabilities/default.json @@ -16,10 +16,11 @@ "demo_core_extraction", "demo_decode_replay", "demo_execution_solana_core", - "demo_execution_spl" + "demo_execution_spl", + "demo_execution_metadata" ], "permissions": [ "core:default", "tracing:default" ] -} +} \ No newline at end of file diff --git a/kb-app-demo-desktop/frontend/demo_execution_metadata.html b/kb-app-demo-desktop/frontend/demo_execution_metadata.html new file mode 100644 index 0000000..41ccc4a --- /dev/null +++ b/kb-app-demo-desktop/frontend/demo_execution_metadata.html @@ -0,0 +1,104 @@ + + + + + + + + + + + Khadhroony Bot3 — Exécution Metadata Devnet + + + +
+ +
+
+
+
+
Cette fenêtre accueille les familles d’exécution metadata. Metaplex Token Metadata est la première famille ; SPL Token Metadata sera ajouté ultérieurement sans changer la frontière de la fenêtre.
+
+
+
+
+

+ +

+
+
+
+
+

+ +

+
+
+
+
+
+

+ +

+
+
+ + + + +
+
État du profilNon chargé
+
+
Cette version prépare réellement le profil et sa base Devnet, mais n’exécute pas encore de transaction Metaplex. Les contrats affichés ne constituent pas une preuve de simulation réseau.
+
+
+
+
+
+ +
+
+
+
+
+
+
+

Exigences

+
+
+
+
+
+
+
+

Préflight et opérations

+
+
+
+
+
+

Simulation et confirmation

+
+
+
+
+
+

Postconditions et preuves

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/kb-app-demo-desktop/frontend/main.html b/kb-app-demo-desktop/frontend/main.html index 5a39646..05ac84c 100644 --- a/kb-app-demo-desktop/frontend/main.html +++ b/kb-app-demo-desktop/frontend/main.html @@ -1,5 +1,5 @@ - + @@ -61,6 +61,7 @@
  • Solana Core
  • SPL
  • +
  • Exécution Metadata Devnet
  • diff --git a/kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts b/kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts new file mode 100644 index 0000000..c6de6b9 --- /dev/null +++ b/kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts @@ -0,0 +1,166 @@ +// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts +// version: 3 +import "bootstrap"; +import "simplebar"; +import { invoke } from "@tauri-apps/api/core"; +import { frontendError } from "./frontend_log.ts"; +import { installFrontendConsoleBridge } from "./frontend_log.ts"; +import { renderJsonViewer } from "./json_viewer.ts"; +import type { DemoExecutionMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts"; +import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts"; +import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts"; + +const storageKey = "kb-app-demo-desktop.metadata.selected-scenario"; +const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile"; +let scenarios: DemoExecutionMetadataScenarioPayload[] = []; +let selectedProfileName: string | null = null; + +function element(selector: string): T { + const value = document.querySelector(selector); + if (value === null) { + throw new Error(`Élément introuvable : ${selector}`); + } + return value; +} + +function activeSelect(): HTMLSelectElement { + return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element("#metadataDevnetScenarioSelect") : element("#metadataSyntheticScenarioSelect"); +} + +function selectedScenario(): DemoExecutionMetadataScenarioPayload | null { + const id = activeSelect().value; + return scenarios.find(scenario => scenario.id === id) ?? null; +} + +function fillSelect(selector: string, mode: string): void { + const select = element(selector); + select.replaceChildren(); + for (const scenario of scenarios.filter(candidate => candidate.mode === mode)) { + const option = document.createElement("option"); + option.value = scenario.id; + option.textContent = `${scenario.assetFamily} — ${scenario.id}`; + select.append(option); + } +} + +function renderSelectedScenario(): void { + const scenario = selectedScenario(); + if (scenario === null) { + return; + } + window.localStorage.setItem(storageKey, scenario.id); + const networkMode = scenario.mode !== "synthetic"; + element("#metadataRequirements").innerHTML = `
    Famille
    ${scenario.assetFamily}
    Mode
    ${scenario.mode}
    Profil
    ${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}
    Collection
    ${scenario.requiresCollection ? "requise" : "non requise"}
    Rule set
    ${scenario.requiresProgrammableRules ? "requis" : "non requis"}
    Validation
    ${scenario.validationStatus}
    `; + renderJsonViewer("#metadataPreflightOutput", { + scenarioId: scenario.id, + operationCodes: scenario.operationCodes, + checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"], + networkExecutionPerformed: false, + }); + renderJsonViewer("#metadataSimulationOutput", { + mode: scenario.mode, + policy: "simulation_first", + networkExecutionPerformed: false, + automaticSubmission: false, + operatorConfirmationRequired: true, + expectedState: scenario.validationStatus, + note: networkMode ? "Le contrat Devnet est sélectionné, mais aucune transaction Metaplex n'est simulée par cette commande de chargement." : "Scénario synthétique déterministe.", + }); + renderJsonViewer("#metadataPostconditionsOutput", { + required: scenario.requiresPostcondition, + evidenceKinds: scenario.requiredEvidence, + networkEvidenceCollected: false, + rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.", + }); +} + +async function loadProfiles(): Promise { + const options = await invoke("demo_execution_solana_core_options"); + const select = element("#metadataProfileSelect"); + select.replaceChildren(); + for (const profile of options.profiles) { + const option = document.createElement("option"); + option.value = profile.name; + option.textContent = `${profile.name} — ${profile.walletAlias}`; + select.append(option); + } + const restored = window.localStorage.getItem(profileStorageKey); + if (restored !== null && Array.from(select.options).some(option => option.value === restored)) { + select.value = restored; + } else if (options.defaultProfileName !== null) { + select.value = options.defaultProfileName; + } + selectedProfileName = select.value.length === 0 ? null : select.value; + renderSelectedScenario(); +} + +async function prepareProfile(): Promise { + const badge = element("#metadataProfileStatusBadge"); + const profileName = element("#metadataProfileSelect").value; + if (profileName.length === 0) { + badge.textContent = "Aucun profil"; + badge.className = "badge text-bg-danger"; + return; + } + badge.textContent = "Préparation"; + badge.className = "badge text-bg-warning"; + try { + const readiness = await invoke("demo_execution_devnet_prepare_profile", { profileName }); + selectedProfileName = profileName; + window.localStorage.setItem(profileStorageKey, profileName); + renderJsonViewer("#metadataProfileOutput", readiness); + badge.textContent = "Base prête"; + badge.className = "badge text-bg-success"; + renderSelectedScenario(); + } catch (caught) { + const message = caught instanceof Error ? caught.message : String(caught); + badge.textContent = "Erreur"; + badge.className = "badge text-bg-danger"; + frontendError("kb-app-demo-desktop.frontend.execution_metadata", message); + } +} + +async function loadScenarios(): Promise { + const badge = element("#metadataStatusBadge"); + badge.textContent = "Chargement"; + badge.className = "badge text-bg-warning"; + try { + scenarios = await invoke("demo_execution_metadata_scenarios"); + fillSelect("#metadataSyntheticScenarioSelect", "synthetic"); + fillSelect("#metadataDevnetScenarioSelect", "network_simulation"); + const restored = window.localStorage.getItem(storageKey); + for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { + const select = element(selector); + if (restored !== null && Array.from(select.options).some(option => option.value === restored)) { + select.value = restored; + } + } + renderSelectedScenario(); + badge.textContent = `${scenarios.length} contrats`; + badge.className = "badge text-bg-success"; + } catch (caught) { + const message = caught instanceof Error ? caught.message : String(caught); + badge.textContent = "Erreur"; + badge.className = "badge text-bg-danger"; + frontendError("kb-app-demo-desktop.frontend.execution_metadata", message); + } +} + +document.addEventListener("DOMContentLoaded", () => { + installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata"); + for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { + element(selector).addEventListener("change", renderSelectedScenario); + } + for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) { + element(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario); + } + element("#metadataProfileSelect").addEventListener("change", event => { + const target = event.currentTarget as HTMLSelectElement; + selectedProfileName = target.value.length === 0 ? null : target.value; + renderSelectedScenario(); + }); + element("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile()); + element("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios()); + void loadProfiles().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); + void loadScenarios(); +}); diff --git a/kb-app-demo-desktop/frontend/ts/main.ts b/kb-app-demo-desktop/frontend/ts/main.ts index fd50464..c6ccf8f 100644 --- a/kb-app-demo-desktop/frontend/ts/main.ts +++ b/kb-app-demo-desktop/frontend/ts/main.ts @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/frontend/ts/main.ts -// version: 4 +// version: 6 import * as bootstrap from "bootstrap"; import MarkdownIt from "markdown-it"; @@ -80,6 +80,7 @@ const pipelineDemoLinks: ReadonlyArray = [ ["openDemoDecodeReplayLink", "open_demo_decode_replay_window"], ["openDemoExecutionSolanaCoreLink", "open_demo_execution_solana_core_window"], ["openDemoExecutionSplLink", "open_demo_execution_spl_window"], + ["openDemoExecutionMetadataLink", "open_demo_execution_metadata_window"], ]; for (const [elementId, command] of pipelineDemoLinks) { const link = document.querySelector(`#${elementId}`); diff --git a/kb-app-demo-desktop/package.json b/kb-app-demo-desktop/package.json index cd722b4..3eef1ac 100644 --- a/kb-app-demo-desktop/package.json +++ b/kb-app-demo-desktop/package.json @@ -1,7 +1,7 @@ { "name": "kb-app-demo-desktop", "private": true, - "version": "0.1.0", + "version": "0.4.7", "type": "module", "scripts": { "dev": "vite", diff --git a/kb-app-demo-desktop/src/demo_execution_metadata.rs b/kb-app-demo-desktop/src/demo_execution_metadata.rs new file mode 100644 index 0000000..d4c0f07 --- /dev/null +++ b/kb-app-demo-desktop/src/demo_execution_metadata.rs @@ -0,0 +1,192 @@ +// file: kb-app-demo-desktop/src/demo_execution_metadata.rs +// version: 2 + +//! Thin desktop adapters for metadata execution demo scenarios. + +/// UI-safe Metaplex scenario row. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] +#[serde(rename_all = "camelCase")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts" +)] +pub(crate) struct DemoExecutionMetadataScenarioPayload { + /// Stable scenario identifier. + pub id: std::string::String, + /// Stable asset-family code. + pub asset_family: std::string::String, + /// Stable execution-mode code. + pub mode: std::string::String, + /// Ordered operation codes. + pub operation_codes: std::vec::Vec, + /// Whether a collection account is required. + pub requires_collection: bool, + /// Whether programmable authorization rules are required. + pub requires_programmable_rules: bool, + /// Whether postconditions are mandatory. + pub requires_postcondition: bool, + /// Current validation status from the canonical matrix. + pub validation_status: std::string::String, + /// Required evidence kinds. + pub required_evidence: std::vec::Vec, +} + +/// Returns the canonical Metaplex scenario inventory for the desktop panel. +pub(crate) fn demo_execution_metadata_scenarios() -> std::result::Result< + std::vec::Vec, + std::string::String, +> { + let matrix = match kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix() + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), + }; + let statuses = matrix + .scenarios + .into_iter() + .map(|scenario| return (scenario.id, (scenario.status, scenario.required_evidence))) + .collect::, + ), + >>(); + let mut payloads = std::vec::Vec::new(); + for scenario in kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios() + .into_iter() + .chain(kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios()) + { + let status = statuses.get(scenario.id.as_str()); + let validation_status = match status { + std::option::Option::Some((value, _)) => validation_status_code(*value), + std::option::Option::None => "not_run".to_string(), + }; + let required_evidence = match status { + std::option::Option::Some((_, evidence)) => evidence.clone(), + std::option::Option::None => std::vec::Vec::new(), + }; + payloads.push(crate::DemoExecutionMetadataScenarioPayload { + id: scenario.id, + asset_family: asset_family_code(scenario.asset_family), + mode: scenario_mode_code(scenario.mode), + operation_codes: scenario.operation_codes, + requires_collection: scenario.requires_collection, + requires_programmable_rules: scenario.requires_programmable_rules, + requires_postcondition: scenario.requires_postcondition, + validation_status, + required_evidence, + }); + } + return std::result::Result::Ok(payloads); +} + +fn asset_family_code( + value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily, +) -> std::string::String { + return match value { + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => "nft".to_string(), + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => "sft".to_string(), + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => { + "fungible".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => { + "collection".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => { + "programmable_nft".to_string() + }, + }; +} + +fn scenario_mode_code( + value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode, +) -> std::string::String { + return match value { + kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::Synthetic => { + "synthetic".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation => { + "network_simulation".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSubmission => { + "network_submission".to_string() + }, + }; +} + +fn validation_status_code( + value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus, +) -> std::string::String { + return match value { + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::NotRun => { + "not_run".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::SyntheticValidated => { + "synthetic_validated".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Simulated => { + "simulated".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Submitted => { + "submitted".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Confirmed => { + "confirmed".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Unavailable => { + "unavailable".to_string() + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Failed => { + "failed".to_string() + }, + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn desktop_inventory_matches_reusable_scenarios_and_matrix() { + let payloads = match crate::demo_execution_metadata_scenarios() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + assert_eq!(std::option::Option::Some(error), std::option::Option::None); + return; + }, + }; + assert_eq!(payloads.len(), 10); + assert!(payloads.iter().all(|payload| return !payload.operation_codes.is_empty())); + assert!(payloads.iter().all(|payload| return payload.requires_postcondition)); + assert!( + payloads + .iter() + .all(|payload| return payload.validation_status != "missing_from_matrix") + ); + assert_eq!( + payloads + .iter() + .filter(|payload| return payload.mode == "network_simulation") + .count(), + 5 + ); + } + + #[test] + fn programmable_and_collection_requirements_remain_explicit() { + let payloads = match crate::demo_execution_metadata_scenarios() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + assert_eq!(std::option::Option::Some(error), std::option::Option::None); + return; + }, + }; + assert_eq!(payloads.iter().filter(|payload| return payload.requires_collection).count(), 2); + assert_eq!( + payloads + .iter() + .filter(|payload| return payload.requires_programmable_rules) + .count(), + 2 + ); + } +} diff --git a/kb-app-demo-desktop/src/lib.rs b/kb-app-demo-desktop/src/lib.rs index f0c7296..4ef9947 100644 --- a/kb-app-demo-desktop/src/lib.rs +++ b/kb-app-demo-desktop/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/lib.rs -// version: 25 +// version: 26 //! Tauri desktop demo application for `khadhroony-bot3`. @@ -14,6 +14,7 @@ mod demo_config; mod demo_core_extraction; mod demo_decode_replay; mod demo_devnet_common; +mod demo_execution_metadata; mod demo_execution_solana_core; mod demo_execution_spl; mod demo_http; @@ -126,6 +127,10 @@ pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayloa pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload; /// Serializes a diagnostic value to indented JSON without panicking. pub(crate) use self::demo_devnet_common::pretty_json; +/// UI-safe Metaplex scenario row. +pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataScenarioPayload; +/// Returns the canonical Metaplex scenario inventory for the desktop panel. +pub(crate) use self::demo_execution_metadata::demo_execution_metadata_scenarios; /// Request sent by the Memo v4 Devnet execution panel. pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest; /// UI-safe result of one Memo v4 Devnet execution orchestration. diff --git a/kb-app-demo-desktop/src/tauri.rs b/kb-app-demo-desktop/src/tauri.rs index 4b99afc..fe18a56 100644 --- a/kb-app-demo-desktop/src/tauri.rs +++ b/kb-app-demo-desktop/src/tauri.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/tauri.rs -// version: 24 +// version: 26 //! Tauri runtime assembly and private command wrappers. @@ -53,6 +53,8 @@ pub fn run() -> kb_core::Result<()> { demo_execution_solana_core_cancel, demo_execution_spl_validation_scenarios, open_demo_execution_spl_window, + open_demo_execution_metadata_window, + demo_execution_metadata_scenarios, demo_execution_spl_token_execute, demo_spl_token_journal, demo_spl_ata_derive, @@ -601,6 +603,26 @@ fn open_demo_execution_spl_window( ); } +#[tauri::command] +fn open_demo_execution_metadata_window( + app_handle: tauri::AppHandle, +) -> std::result::Result<(), std::string::String> { + return open_or_focus_demo_window( + app_handle, + "demo_execution_metadata", + "demo_execution_metadata.html", + "Exécution Metadata Devnet", + ); +} + +#[tauri::command] +fn demo_execution_metadata_scenarios() -> std::result::Result< + std::vec::Vec, + std::string::String, +> { + return crate::demo_execution_metadata_scenarios(); +} + #[tauri::command] async fn demo_execution_spl_token_execute( app_handle: tauri::AppHandle, diff --git a/kb-app-demo-desktop/tauri.conf.json b/kb-app-demo-desktop/tauri.conf.json index fa85d1d..5d3fbbb 100644 --- a/kb-app-demo-desktop/tauri.conf.json +++ b/kb-app-demo-desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Khadhroony Bot3 Demo Desktop", - "version": "0.1.0", + "version": "0.4.7", "identifier": "com.sasedev.kb-app-demo-desktop", "build": { "beforeDevCommand": "npm run dev", diff --git a/kb-pipeline-demo-scenarios/CHANGELOG.md b/kb-pipeline-demo-scenarios/CHANGELOG.md index c40b722..72f17dd 100644 --- a/kb-pipeline-demo-scenarios/CHANGELOG.md +++ b/kb-pipeline-demo-scenarios/CHANGELOG.md @@ -1,5 +1,10 @@ - + + +## 0.4.7-pre.009-fix-001 + +- ajout de l’inventaire Metaplex Devnet simulation et de son test de couverture. + # CHANGELOG — kb-pipeline-demo-scenarios diff --git a/kb-pipeline-demo-scenarios/USAGE.md b/kb-pipeline-demo-scenarios/USAGE.md index 99261aa..03fb891 100644 --- a/kb-pipeline-demo-scenarios/USAGE.md +++ b/kb-pipeline-demo-scenarios/USAGE.md @@ -209,3 +209,14 @@ fn load_metaplex_matrix( ``` Une validation réseau ne peut passer à `confirmed` que lorsque toutes les preuves déclarées sont présentes. Les statuts `not_run` et `unavailable` n’acceptent aucune preuve observée. + +## Inventaire Metaplex Devnet + +```rust +let scenarios = kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios(); +assert!(scenarios.iter().all(|scenario| { + scenario.mode == kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation +})); +``` + +Cet inventaire prépare les tests et campagnes Devnet simulation-first. Il ne constitue pas à lui seul une validation réseau réussie. diff --git a/kb-pipeline-demo-scenarios/src/lib.rs b/kb-pipeline-demo-scenarios/src/lib.rs index cd723a6..9b2c67f 100644 --- a/kb-pipeline-demo-scenarios/src/lib.rs +++ b/kb-pipeline-demo-scenarios/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-pipeline-demo-scenarios/src/lib.rs -// version: 6 +// version: 7 #![forbid(unsafe_code)] #![deny(unreachable_pub)] @@ -73,6 +73,8 @@ pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataAss 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 ordered Metaplex Devnet simulation scenario inventory. +pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_devnet_scenarios; /// 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. diff --git a/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs b/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs index 26f8ac0..e6e05d6 100644 --- a/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs +++ b/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs @@ -1,5 +1,5 @@ // file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs -// version: 1 +// version: 2 //! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns. @@ -120,6 +120,19 @@ pub fn metaplex_token_metadata_synthetic_scenarios() ]; } +/// Returns the ordered Metaplex scenarios intended for opt-in Devnet simulation. +pub fn metaplex_token_metadata_devnet_scenarios() +-> std::vec::Vec { + 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::>(); +} + #[cfg(test)] mod tests { #[test] @@ -148,6 +161,22 @@ mod tests { } } + #[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::>( + ); + assert_eq!(families.len(), 5); + } + #[test] fn programmable_and_collection_requirements_are_declared_only_where_needed() { let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();