v0.4.7-pre.009

This commit is contained in:
2026-08-02 17:53:36 +02:00
parent d6cb4b0cce
commit ebdfd30d5d
21 changed files with 657 additions and 46 deletions

View File

@@ -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"

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# 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.

View File

@@ -0,0 +1,37 @@
<!-- file: docs/validation/V0_4_7_PRE_009_METAPLEX_DESKTOP.md -->
<!-- version: 3 -->
# 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
```

View File

@@ -1,8 +1,32 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 7 -->
<!-- version: 10 -->
# 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` ;

View File

@@ -1,51 +1,35 @@
<!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# kb-app-demo-desktop
`kb-app-demo-desktop` est lapplication Tauri de démonstration et de validation opérateur de `khadhroony-bot3`.
`kb-app-demo-desktop` est lapplication 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 linterface.
- bibliothèque `kb_app_demo_desktop_lib` ;
- binaire `kb-app-demo-desktop`.
## Fonctionnalités
## Responsabilités
- démarrage Tauri et gestion dune 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 lorchestration ;
- `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 lunique 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)

View File

@@ -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 dun 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`.

View File

@@ -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 ;
- lapplication 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 linventaire canonique par la commande Tauri `demo_execution_metadata_scenarios`, puis affiche pour chaque scénario :
- les codes dopé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`. Laccordé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é.

View File

@@ -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"
]
}
}

View File

@@ -0,0 +1,104 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_metadata.html -->
<!-- version: 3 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Exécution Metadata Devnet</title>
</head>
<body>
<header class="app-header">
<nav class="navbar h-100 py-0 bg-dark text-light">
<div class="container-fluid px-4">
<div class="navbar-brand d-flex align-items-center"><img alt="Logo" src="imgs/logo.png" class="app-logo"><span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Exécution Metadata Devnet</span></span></div>
<span class="badge text-bg-success" id="metadataStatusBadge">Prêt</span>
</div>
</nav>
</header>
<main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container-fluid px-4 py-4">
<div class="alert alert-info">Cette fenêtre accueille les familles dexé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.</div>
<div class="row g-4">
<div class="col-12 col-xl-5">
<div class="accordion shadow-sm" id="metadataExecutionAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" data-bs-target="#metaplexMetadataCollapse" data-bs-toggle="collapse" type="button">Metaplex Token Metadata</button>
</h2>
<div class="accordion-collapse collapse show" id="metaplexMetadataCollapse" data-bs-parent="#metadataExecutionAccordion">
<div class="accordion-body">
<div class="accordion" id="metaplexScenarioModeAccordion">
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button" data-bs-target="#metaplexSyntheticCollapse" data-bs-toggle="collapse" type="button">Scénarios synthétiques</button>
</h3>
<div class="accordion-collapse collapse show" id="metaplexSyntheticCollapse" data-bs-parent="#metaplexScenarioModeAccordion">
<div class="accordion-body"><select class="form-select" id="metadataSyntheticScenarioSelect"></select></div>
</div>
</div>
<div class="accordion-item">
<h3 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#metaplexDevnetCollapse" data-bs-toggle="collapse" type="button">Scénarios Devnet</button>
</h3>
<div class="accordion-collapse collapse" id="metaplexDevnetCollapse" data-bs-parent="#metaplexScenarioModeAccordion">
<div class="accordion-body">
<label class="form-label" for="metadataProfileSelect">Profil Devnet</label>
<select class="form-select mb-3" id="metadataProfileSelect"></select>
<label class="form-label" for="metadataDevnetScenarioSelect">Contrat de scénario</label>
<select class="form-select" id="metadataDevnetScenarioSelect"></select>
<div class="d-grid mt-3"><button class="btn btn-outline-primary" id="prepareMetadataProfileButton" type="button">Charger le profil et préparer la base</button></div>
<div class="d-flex justify-content-between align-items-center mt-3"><span class="small text-muted">État du profil</span><span class="badge text-bg-secondary" id="metadataProfileStatusBadge">Non chargé</span></div>
<div class="mt-3" id="metadataProfileOutput"></div>
<div class="alert alert-warning mt-3 mb-0">Cette version prépare réellement le profil et sa base Devnet, mais nexécute pas encore de transaction Metaplex. Les contrats affichés ne constituent pas une preuve de simulation réseau.</div>
</div>
</div>
</div>
</div>
<div class="d-grid mt-3">
<button class="btn btn-primary" id="refreshMetadataScenariosButton" type="button">Recharger les contrats</button>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-body">
<h2 class="h5">Exigences</h2>
<dl class="row mb-0" id="metadataRequirements"></dl>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5">Préflight et opérations</h2>
<div id="metadataPreflightOutput"></div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5">Simulation et confirmation</h2>
<div id="metadataSimulationOutput"></div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5">Postconditions et preuves</h2>
<div id="metadataPostconditionsOutput"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script type="module" src="ts/demo_execution_metadata.ts"></script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/main.html -->
<!-- version: 9 -->
<!-- version: 11 -->
<!DOCTYPE html>
<html lang="fr">
@@ -61,6 +61,7 @@
</li>
<li><a id="openDemoExecutionSolanaCoreLink" class="dropdown-item" href="#">Solana Core</a></li>
<li><a id="openDemoExecutionSplLink" class="dropdown-item" href="#">SPL</a></li>
<li><a id="openDemoExecutionMetadataLink" class="dropdown-item" href="#">Exécution Metadata Devnet</a></li>
</ul>
</div>
</div>

View File

@@ -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<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
if (value === null) {
throw new Error(`Élément introuvable : ${selector}`);
}
return value;
}
function activeSelect(): HTMLSelectElement {
return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element<HTMLSelectElement>("#metadataDevnetScenarioSelect") : element<HTMLSelectElement>("#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<HTMLSelectElement>(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<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
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<void> {
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");
const select = element<HTMLSelectElement>("#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<void> {
const badge = element<HTMLElement>("#metadataProfileStatusBadge");
const profileName = element<HTMLSelectElement>("#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<DemoExecutionDevnetStoreReadinessPayload>("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<void> {
const badge = element<HTMLElement>("#metadataStatusBadge");
badge.textContent = "Chargement";
badge.className = "badge text-bg-warning";
try {
scenarios = await invoke<DemoExecutionMetadataScenarioPayload[]>("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<HTMLSelectElement>(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<HTMLSelectElement>(selector).addEventListener("change", renderSelectedScenario);
}
for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) {
element<HTMLElement>(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario);
}
element<HTMLSelectElement>("#metadataProfileSelect").addEventListener("change", event => {
const target = event.currentTarget as HTMLSelectElement;
selectedProfileName = target.value.length === 0 ? null : target.value;
renderSelectedScenario();
});
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
element<HTMLButtonElement>("#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();
});

View File

@@ -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<readonly [string, string]> = [
["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<HTMLAnchorElement>(`#${elementId}`);

View File

@@ -1,7 +1,7 @@
{
"name": "kb-app-demo-desktop",
"private": true,
"version": "0.1.0",
"version": "0.4.7",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -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<std::string::String>,
/// 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<std::string::String>,
}
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
pub(crate) fn demo_execution_metadata_scenarios() -> std::result::Result<
std::vec::Vec<crate::DemoExecutionMetadataScenarioPayload>,
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::<std::collections::BTreeMap<
std::string::String,
(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
std::vec::Vec<std::string::String>,
),
>>();
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
);
}
}

View File

@@ -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.

View File

@@ -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<crate::DemoExecutionMetadataScenarioPayload>,
std::string::String,
> {
return crate::demo_execution_metadata_scenarios();
}
#[tauri::command]
async fn demo_execution_spl_token_execute(
app_handle: tauri::AppHandle,

View File

@@ -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",

View File

@@ -1,5 +1,10 @@
<!-- file: kb-pipeline-demo-scenarios/CHANGELOG.md -->
<!-- version: 9 -->
<!-- version: 10 -->
## 0.4.7-pre.009-fix-001
- ajout de linventaire Metaplex Devnet simulation et de son test de couverture.
# CHANGELOG — kb-pipeline-demo-scenarios

View File

@@ -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` nacceptent 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.

View File

@@ -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.

View File

@@ -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<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>>();
}
#[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::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
);
assert_eq!(families.len(), 5);
}
#[test]
fn programmable_and_collection_requirements_are_declared_only_where_needed() {
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();