diff --git a/Cargo.toml b/Cargo.toml index 7cecbd1..761754c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 459 +# version: 460 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.3.8-pre.7.fix.1" +version = "0.3.8-pre.7.fix.2" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-app-store-desk/frontend/main.html b/crates/ksp-app-store-desk/frontend/main.html index 55834a3..6985bdf 100644 --- a/crates/ksp-app-store-desk/frontend/main.html +++ b/crates/ksp-app-store-desk/frontend/main.html @@ -1,5 +1,5 @@ - + @@ -197,7 +197,13 @@

Payload preview hex

- +
+ + +
diff --git a/crates/ksp-app-store-desk/frontend/ts/main.ts b/crates/ksp-app-store-desk/frontend/ts/main.ts index 46915d3..471acc1 100644 --- a/crates/ksp-app-store-desk/frontend/ts/main.ts +++ b/crates/ksp-app-store-desk/frontend/ts/main.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-store-desk/frontend/ts/main.ts -// version: 5 +// version: 6 import DataTable from "datatables.net-bs5"; import "datatables.net-bs5/css/dataTables.bootstrap5.css"; @@ -133,6 +133,7 @@ async function copyLongText(button: HTMLButtonElement, value: string, fieldId: s } const icon = button.querySelector("i"); const previousClassName = icon?.className ?? ""; + const previousTitle = button.title; if (icon) { icon.className = "fa-solid fa-check"; } @@ -142,7 +143,7 @@ async function copyLongText(button: HTMLButtonElement, value: string, fieldId: s if (icon) { icon.className = previousClassName; } - button.title = "Copier la valeur complète"; + button.title = previousTitle; }, COPY_FEEDBACK_MILLISECONDS); } @@ -428,6 +429,12 @@ function clearTransactionDetail(): void { } setVisible("transactionDetailError", false); setVisible("transactionDetailPreviewTruncated", false); + const payloadPreviewCopy = document.querySelector("#transactionDetailPayloadPreviewCopy"); + if (payloadPreviewCopy) { + delete payloadPreviewCopy.dataset.copyLongText; + delete payloadPreviewCopy.dataset.copyField; + payloadPreviewCopy.hidden = true; + } } function renderTransactionDetail(detail: StoreTransactionDetailDto): void { @@ -438,7 +445,18 @@ function renderTransactionDetail(detail: StoreTransactionDetailDto): void { setCopyableLongText("transactionDetailContentHash", detail.contentHash, "transaction-detail-content-hash"); setText("transactionDetailRetention", detail.retentionState); setText("transactionDetailPayloadSize", detail.payloadSizeDecimal ?? "—"); - setText("transactionDetailPayloadPreview", detail.payloadPreviewHex ?? "Payload non disponible dans cet état de rétention."); + const payloadPreview = detail.payloadPreviewHex; + setText("transactionDetailPayloadPreview", payloadPreview ?? "Payload non disponible dans cet état de rétention."); + const payloadPreviewCopy = document.querySelector("#transactionDetailPayloadPreviewCopy"); + if (payloadPreviewCopy && payloadPreview) { + payloadPreviewCopy.dataset.copyLongText = payloadPreview; + payloadPreviewCopy.dataset.copyField = "transaction-payload-preview"; + payloadPreviewCopy.hidden = false; + } else if (payloadPreviewCopy) { + delete payloadPreviewCopy.dataset.copyLongText; + delete payloadPreviewCopy.dataset.copyField; + payloadPreviewCopy.hidden = true; + } setVisible("transactionDetailPreviewTruncated", detail.payloadPreviewTruncated); } diff --git a/crates/ksp-app-store-desk/tests/desktop_contract.rs b/crates/ksp-app-store-desk/tests/desktop_contract.rs index d97ca38..8fd6ebf 100644 --- a/crates/ksp-app-store-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-store-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-store-desk/tests/desktop_contract.rs -// version: 5 +// version: 6 //! Desktop template and frontend contract tests for the Store Desk scaffold. @@ -88,7 +88,8 @@ fn pre_007_shell_exposes_transaction_filters_detail_and_keeps_account_skeleton_u } assert!(html.contains("id=\"rawTransactionsTable\"")); assert!(html.contains("id=\"rawAccountsTable\"")); - for overview_id in ["refreshOverview", "overviewStoreProfile", "overviewStoreTarget", "overviewStoreHealth", "overviewStorePool", "overviewStoreMigration"] { + for overview_id in ["refreshOverview", "overviewStoreProfile", "overviewStoreTarget", "overviewStoreHealth", "overviewStorePool", "overviewStoreMigration"] + { assert!(html.contains(&format!("id=\"{overview_id}\"")), "missing Store Overview field: {overview_id}"); } for transaction_id in ["refreshTransactions", "transactionSlotMin", "transactionSlotMax", "transactionDirection", "transactionDetailModal"] { @@ -202,14 +203,31 @@ fn pre_007_fix_001_long_transaction_identifiers_are_truncated_tooltipped_and_cop let frontend = include_str!("../frontend/ts/main.ts"); let sass = include_str!("../frontend/sass/_app.scss"); assert!(frontend.contains("truncateLongText")); - assert!(frontend.contains("renderCopyableLongText(String(data), "transaction-signature")")); - assert!(frontend.contains("renderCopyableLongText(String(data), "transaction-content-hash")")); - assert!(frontend.contains("setCopyableLongText("transactionDetailSignature"")); - assert!(frontend.contains("setCopyableLongText("transactionDetailContentHash"")); - assert!(frontend.contains("title="${escapedValue}"")); + assert!(frontend.contains(r#"renderCopyableLongText(String(data), "transaction-signature")"#)); + assert!(frontend.contains(r#"renderCopyableLongText(String(data), "transaction-content-hash")"#)); + assert!(frontend.contains(r#"setCopyableLongText("transactionDetailSignature""#)); + assert!(frontend.contains(r#"setCopyableLongText("transactionDetailContentHash""#)); + assert!(frontend.contains(r#"title="${escapedValue}""#)); assert!(frontend.contains("data-copy-long-text")); assert!(frontend.contains("navigator.clipboard.writeText")); - assert!(frontend.contains("document.execCommand("copy")")); + assert!(frontend.contains(r#"document.execCommand("copy")"#)); assert!(sass.contains(".app-copyable-long-text__value")); assert!(sass.contains("text-overflow: ellipsis")); } + +#[test] +fn pre_007_fix_002_payload_preview_remains_bounded_and_is_explicitly_copyable() { + let frontend = include_str!("../frontend/ts/main.ts"); + let html = include_str!("../frontend/main.html"); + let runtime = include_str!("../src/store_runtime.rs"); + assert!(runtime.contains("const TRANSACTION_DETAIL_PREVIEW_BYTES: usize = 512;")); + assert!(html.contains("transactionDetailPayloadPreviewCopy")); + assert!(html.contains("Copier le preview hex affiché")); + assert!(html.contains("Preview tronquée à 512 bytes")); + assert!(frontend.contains(r#"payloadPreviewCopy.dataset.copyField = "transaction-payload-preview""#)); + assert!(frontend.contains("payloadPreviewCopy.dataset.copyLongText = payloadPreview")); + assert!(frontend.contains("payloadPreviewCopy.hidden = false")); + assert!(frontend.contains("payloadPreviewCopy.hidden = true")); + assert!(frontend.contains("const previousTitle = button.title")); + assert!(frontend.contains("button.title = previousTitle")); +} diff --git a/crates/ksp-app-store-desk/tests/desktop_security.rs b/crates/ksp-app-store-desk/tests/desktop_security.rs index 0498df7..1123e4d 100644 --- a/crates/ksp-app-store-desk/tests/desktop_security.rs +++ b/crates/ksp-app-store-desk/tests/desktop_security.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-store-desk/tests/desktop_security.rs -// version: 6 +// version: 7 //! Security boundary tests for the Store Desk scaffold. @@ -137,13 +137,22 @@ fn pre_007_fix_001_copy_tracing_never_logs_long_identifier_values() { assert!(frontend.contains("Store Desk long text copy failed")); assert!(frontend.contains("{ fieldId }")); for forbidden in [ - "copy requested", { value", - "copy completed", { value", - "copy failed", { value", - "copy requested", { signature", - "copy completed", { signature", - "copy failed", { signature", + r#"{ fieldId, value }"#, + r#"{ value, fieldId }"#, + r#"{ value }"#, + r#"{ signature }"#, + r#"{ fieldId, signature }"#, + r#"{ signature, fieldId }"#, ] { assert!(!frontend.contains(forbidden), "long identifier value leaked into copy tracing: {forbidden}"); } } + +#[test] +fn pre_007_fix_002_payload_preview_copy_never_logs_preview_material() { + let frontend = include_str!("../frontend/ts/main.ts"); + assert!(frontend.contains(r#"payloadPreviewCopy.dataset.copyField = "transaction-payload-preview""#)); + assert!(!frontend.contains(r#"frontendDebug("main", "Store Desk long text copy requested", { fieldId, value })"#)); + assert!(!frontend.contains(r#"frontendDebug("main", "Store Desk long text copy completed", { fieldId, value })"#)); + assert!(!frontend.contains(r#"frontendError("main", "Store Desk long text copy failed", { fieldId, value })"#)); +} diff --git a/deltas/0.3.8/pre.007-fix.002.md b/deltas/0.3.8/pre.007-fix.002.md new file mode 100644 index 0000000..9011d02 --- /dev/null +++ b/deltas/0.3.8/pre.007-fix.002.md @@ -0,0 +1,58 @@ + + + +# Delta `0.3.8-pre.007-fix.002` — canaris copie et preview payload copiable + +## Base requise + +Base directe attendue : `0.3.8-pre.007-fix.001` (`workspace.package.version = 0.3.8-pre.7.fix.1`). + +## Objet + +Corriger les raw string canaries Rust introduits par `pre.007-fix.001` et rendre copiable le preview hex du payload transaction sans supprimer la borne de 512 bytes du détail IPC. + +## Comportement + +Le preview transaction reste volontairement limité à 512 bytes côté Rust. Le frontend ajoute un bouton `Copier` adjacent au badge de troncature lorsqu'un preview existe. Le bouton copie exactement la chaîne hex affichée et porte le tooltip `Copier le preview hex affiché`. + +Le helper générique de copie mémorise désormais le titre initial du bouton et le restaure après le feedback temporaire `Copié`, afin que les boutons spécialisés conservent leur libellé. + +## Correction du gate + +Les canaris `desktop_contract.rs` et `desktop_security.rs` de `pre.007-fix.001` contenaient des littéraux Rust invalides à cause de guillemets TypeScript non échappés. Ils utilisent désormais des raw string literals Rust valides et gardent les mêmes garanties de sécurité. + +## Sécurité / tracing + +La copie du preview utilise le même chemin frontend local que les identifiants longs. Le tracing transporte uniquement `fieldId = transaction-payload-preview`; le contenu hex n'est jamais journalisé. Le payload complet n'est pas chargé par cette fonctionnalité. + +## Hors scope + +Aucun changement Store API, Store façade, PostgreSQL, SQL, migration, DTO IPC, Config, command Tauri ou capability. `pre.008` reste la tranche RAW Accounts server-side. + +## Version + +```text +delivery = 0.3.8-pre.007-fix.002 +workspace.package.version = 0.3.8-pre.7.fix.2 +commit = v0.3.8-pre.007-fix.002 +tag = aucun +``` + +## Gate requis + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-app-store-desk +cargo test -p ksp-store-lib +cargo check -p ksp-store-lib --no-default-features +``` + +Smoke recommandé : + +```bash +(cd crates/ksp-app-store-desk && cargo tauri dev) +``` diff --git a/docs/validation/025-V0_3_8_STORE_DESK.md b/docs/validation/025-V0_3_8_STORE_DESK.md index b21ff2e..882c0ee 100644 --- a/docs/validation/025-V0_3_8_STORE_DESK.md +++ b/docs/validation/025-V0_3_8_STORE_DESK.md @@ -1,5 +1,5 @@ - + # Validation v0.3.8 — Store Desk V1 RAW @@ -609,3 +609,19 @@ Le smoke `cargo tauri dev` confirme le chemin réel `serverSide` Transactions : Le gate Cargo de ce fix reste à rejouer après application du delta. +## 30. `pre.007-fix.002` — correction des canaris copie + copie du preview payload + +Le gate opérateur de `pre.007-fix.001` confirme que le runtime/frontend démarre et que les boutons de copie sont présents, mais les nouveaux canaris Rust ne compilent pas : plusieurs chaînes de test contiennent des guillemets TypeScript non échappés. Le défaut est strictement dans `desktop_contract.rs` / `desktop_security.rs`; le binaire Store Desk reste compilable et le smoke Tauri atteint le détail transaction. + +Le même smoke montre également le badge `Preview tronquée à 512 bytes`. Cette borne reste intentionnelle : le détail IPC ne transporte jamais plus de 512 bytes de RAW payload. `pre.007-fix.002` ajoute toutefois une copie explicite du **preview hex déjà chargé**, sans lever cette borne : + +- [X] les canaris `pre.007-fix.001` utilisent des raw string literals Rust valides pour inspecter le TypeScript ; +- [X] le preview payload reste borné à 512 bytes côté Rust ; +- [X] un bouton `Copier` est affiché uniquement lorsqu'un preview existe ; +- [X] la copie porte exactement sur le preview hex affiché, jamais sur un payload complet non chargé ; +- [X] le bouton est libellé `Copier le preview hex affiché` afin de ne pas induire l'utilisateur en erreur lorsque le badge de troncature est visible ; +- [X] le helper de copie restaure désormais le titre initial du bouton après le feedback `Copié`, ce qui conserve le libellé spécifique du preview ; +- [X] le tracing utilise uniquement `fieldId = transaction-payload-preview` et ne journalise jamais le contenu hex ; +- [X] aucun changement Store API, Store façade, PostgreSQL, SQL, DTO IPC ou capability Tauri n'est introduit. + +Le gate Cargo de ce fix reste à rejouer après application du delta.