v0.3.8-pre.007-fix.002

This commit is contained in:
2026-09-03 18:51:37 +02:00
parent 32577ea48d
commit c9e52d1926
7 changed files with 148 additions and 23 deletions

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-store-desk/frontend/main.html -->
<!-- version: 3 -->
<!-- version: 4 -->
<!DOCTYPE html>
<html lang="fr">
@@ -197,7 +197,13 @@
</dl>
<div class="d-flex align-items-center justify-content-between gap-3 mb-2">
<h3 class="h6 mb-0">Payload preview hex</h3>
<span id="transactionDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
<div class="d-flex align-items-center gap-2">
<button id="transactionDetailPayloadPreviewCopy" class="btn btn-sm btn-outline-secondary" type="button" title="Copier le preview hex affiché" aria-label="Copier le preview hex affiché" hidden>
<i class="fa-regular fa-copy" aria-hidden="true"></i>
<span>Copier</span>
</button>
<span id="transactionDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
</div>
</div>
<pre id="transactionDetailPayloadPreview" class="small bg-body-tertiary border rounded p-3 overflow-auto text-break mb-0"></pre>
</div>

View File

@@ -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<HTMLElement>("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<HTMLButtonElement>("#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<HTMLButtonElement>("#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);
}

View File

@@ -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"));
}

View File

@@ -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 })"#));
}

View File

@@ -0,0 +1,58 @@
<!-- file: deltas/0.3.8/pre.007-fix.002.md -->
<!-- version: 1 -->
# 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)
```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/025-V0_3_8_STORE_DESK.md -->
<!-- version: 15 -->
<!-- version: 16 -->
# 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.