# Plan v0.3.8 — Store Desk V1 RAW ## 1. But de la version Créer `ksp-app-store-desk`, Desk Tauri KSP read-only destinée à l'inspection backend-agnostique du Store et à la navigation des données RAW réellement persistées. La V1 doit exposer les contrats logiques KSP et jamais le schéma PostgreSQL physique. L'application consomme exclusivement `ksp-store-lib` pour le Store, `ksp-config-lib` pour Config et `ksp-logging-lib` pour le tracing/runtime Logging. ## 2. Base autoritaire et gate d'ouverture Base vérifiée : ```text archive : khadhroony-solana-project-v0.3.7.zip workspace.package.version : 0.3.7 delta stable : deltas/0.3.7/rel.001.md prompt : prompts/027-V0_3_8_START_PROMPT.md ``` L'archive kbot3 est extraite dans un arbre distinct et ne sert que de référence fonctionnelle/UX. ### 2.1 Preuve des archives | Source | SHA-256 | Entrées | Contrôle ZIP | Rôle | |--------------------------------------------------------|--------------------------------------------------------------------|--------:|--------------------------------------------------|----------------------------| | `khadhroony-solana-project-v0.3.7.zip` | `d57132960e4c280a67e59bea02ce08a61665890bdfb6ef16b7faf1b3c57070ae` | 1 702 | propre, aucun traversal/absolu/backslash/symlink | source exclusive de code | | `khadhroony-bot3_v0.5.3-pre.005-fix010-thelatest1.zip` | `ee47643b9f8b582ee8db97b2381ec107e45aef8c009fee44757531514615d318` | 2 501 | propre, aucun traversal/absolu/backslash/symlink | référence fonctionnelle/UX | ### 2.2 Baseline locale Exécuté avant modification : ```text python3 scripts/audit_rust_workspace_rules.py General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas Markdown table audit: clean (301 table(s), 687 file(s)) ``` `cargo` n'est pas installé dans l'environnement d'assemblage. `cargo fmt`, `cargo check`, Clippy et les tests ciblés ne sont donc pas déclarés PASS localement. Le journal opérateur fourni pour la base stable v0.3.7 rapporte un gate complet incluant ces commandes ; il reste une preuve externe de baseline. ## 3. Règles et frontières confirmées ### 3.1 Ownership ```text Config/env/secrets -> ksp-config-lib Store contracts persistés -> ksp-store-api Store facade consumer -> ksp-store-lib PostgreSQL physique -> ksp-store-postgres-lib Logging/tracing runtime -> ksp-logging-lib Store Desk -> composition + DTO + commands + UX ``` ### 3.2 Dépendances applicatives prévues | Dépendance | Statut Store Desk | Justification | |--------------------------|-------------------|-------------------------------------------------| | `ksp-config-lib` | REQUISE | composite, profils, bootstrap Config | | `ksp-logging-lib` | REQUISE | runtime Logging et tracing KSP | | `ksp-store-lib` | REQUISE | unique façade Store autorisée | | `ksp-core-lib` | REQUISE SCAFFOLD | erreurs applicatives typées et gabarit Desk KSP | | `ksp-store-api` | INTERDITE DIRECTE | la Desk ne contourne pas la façade | | `ksp-store-postgres-lib` | INTERDITE | backend physique privé | | `tokio-postgres` | INTERDITE | driver physique | | `deadpool-postgres` | INTERDITE | pool physique | | Transport RPC/gRPC/WS | INTERDIT V1 | inspection Store uniquement | ## 4. Gabarit KSP Store Desk **GABARIT STORE DESK = KSP.** kbot3 n'est jamais une source de package, HTML, SASS, assets, Rust, TypeScript ou versions npm. ### 4.1 Matrice Desk | Élément | Config Desk | Wallet Desk | SOL Prices Desk | Backfill Desk | Choix Store Desk | |-----------------------------|-------------|-------------|-----------------|---------------|-----------------------------------| | package Rust lib + bin | oui | oui | oui | oui | identique | | launcher `main.rs` mince | oui | oui | oui | oui | identique | | Tauri lib `run()` | oui | oui | oui | oui | identique | | `tauri.rs` centralisé | oui | oui | oui | oui | identique | | splash KSP commun | oui | oui | oui | oui | identique, titre Store uniquement | | shell header/sidebar/footer | oui | oui | oui | oui | identique | | fonts/assets KSP | oui | oui | oui | oui | identiques | | tracing frontend | oui | oui | oui | oui | obligatoire | | Vite strict | 1430/1431 | 1432/1433 | 1434/1435 | 1436/1437 | **1438/1439** | | DataTables | oui | oui | non | non | oui, inspection `serverSide` RAW | | Select extension | oui | oui | non | non | non par défaut | La sélection DataTables n'est pas justifiée par une action V1 : le détail s'ouvre par une action explicite dans la row. `datatables.net-select-bs5` n'est donc pas requis tant qu'une sélection persistante n'apporte pas une fonction réelle. ### 4.2 Invariant tracing Store Desk Pendant le développement de `ksp-app-store-desk`, le profil Logging demandé explicitement est `supertrace`; le fallback local reste `Trace`. Cette sélection est temporairement volontairement verbeuse et sera réévaluée avant `rel.001`. Toute interaction opérateur significative du frontend doit être tracée via le bridge KSP, sans contenu sensible : ```text clic bouton / action explicite activation tab / vue changement de filtre ou contrôle Refresh ouverture détail pagination / changement de page length DataTables redraw/query serverSide et échec IPC associé ``` Les événements transportent uniquement des identifiants de contrôle, catégories d'action et compteurs sûrs. Les valeurs de filtres, payloads, URI, credentials et contenu RAW ne sont jamais injectés dans les logs. Les handlers métier peuvent ajouter un événement plus précis, mais aucune interaction nouvellement introduite ne doit rester silencieuse. ## 5. npm / DataTables map Les ranges KSP v0.3.7 observés dans Config/Wallet Desk sont : ```text @fltsci/tauri-plugin-tracing ^0.3 @fortawesome/fontawesome-free ^7.3 @tauri-apps/api ^2.11 bootstrap ^5.3 datatables.net-bs5 ^3.0 datatables.net-select-bs5 ^4.0 resize-observer-polyfill ^1.5 simplebar ^6.3 @tauri-apps/cli ^2.11 @types/bootstrap ^5.2 @types/node ^26.1 sass-embedded ^1.102 typescript ^7.0 vite ^8.2 ``` Audit externe du 2026-09-03 : DataTables stable courant `3.0.3`, Select `4.0.1`. Les ranges KSP `^3.0` / `^4.0` couvrent déjà ces familles ; aucune mise à niveau opportuniste du workspace n'est requise. Choix Store Desk : même socle KSP, `datatables.net-bs5 ^3.0`, sans `datatables.net-select-bs5` tant qu'aucun use case de sélection persistante ne le justifie. DataTables est utilisé en `serverSide: true` avec `ajax` sous forme de fonction afin d'appeler les commands Tauri sans requête HTTP directe. ## 6. Audit DataTables et décision de pagination ### 6.1 Contrat DataTables réellement requis Le mode `serverSide` DataTables échange : ```text request : draw + start + length + search/order metadata response: draw + recordsTotal + recordsFiltered + data ``` `start` est un offset absolu zéro-based et `length` la taille de page. DataTables exige les totaux pour construire son pager et son information de dataset. Son option `ajax` accepte une fonction custom : Store Desk peut donc transformer une demande DataTables en `invoke()` Tauri puis appeler le callback DataTables, sans `fetch` réseau. ### 6.2 Contrat RAW canonique KSP conservé KSP v0.3.7 expose : ```text RawPageRequest { cursor?: RawPageCursor, limit: RawPageLimit } RawPage { items, next_cursor? } ``` Le backend PostgreSQL implémente une pagination keyset déterministe et opaque. Elle reste la primitive canonique pour workers, backfills, replays et parcours séquentiels volumineux. Aucun contrat existant n'est remplacé, renommé ou affaibli. ### 6.3 Nouvelle primitive d'inspection backend-neutral Le brainstorming conclut qu'une UI d'inspection interactive a un besoin distinct et réutilisable : navigation random-access, taille de page, total exact et summaries sans gros payloads. Ajouter une surface Store dédiée, sans nom DataTables : ```text RawInspectionPageRequest offset: u64 limit: RawPageLimit RawInspectionPage items: Vec total_items: u64 filtered_items: u64 RawTransactionInspectionQuery RawAccountStateInspectionQuery RawTransactionSummary RawAccountStateSummary RawTransactionInspectionRead RawAccountStateInspectionRead ``` Sémantique : - `total_items` = nombre total de lignes logiques de la famille dans le network du Store, avant filtres optionnels de la query ; - `filtered_items` = nombre total après filtres Store de la query, avant offset/limit ; - `offset` = position logique dans l'ordre déterministe de la query ; - `RawPage*` cursor/keyset reste inchangé et continue d'être préféré pour les parcours machine ; - aucune structure de Store API ne porte le nom DataTables ou un DTO Tauri. Deux traits d'inspection séparés préservent le caractère fine-grained des capabilities et évitent d'ajouter des méthodes aux traits RAW existants, ce qui casserait inutilement les implémentations externes. ### 6.4 Projection summary obligatoire La nouvelle pagination ne doit pas déclencher un N+1 de `get_*` ni transporter 16 MiB par row. Les pages d'inspection retournent directement des summaries logiques. `RawTransactionSummary` contient au minimum : référence, slot, block time, format id/version, content hash, taille de payload lorsqu'elle est encore connaissable, retention state. Le payload lui-même reste absent. `RawAccountStateSummary` contient au minimum : référence, owner, lamports, executable, rent epoch et data length. Les bytes `data` restent absents. Ces projections sont backend-neutral et utiles hors Tauri : CLI d'inspection, diagnostics admin ou API read-only peuvent les consommer. ### 6.5 Mapping DataTables unique Dans Store Desk : ```text DataTables start -> RawInspectionPageRequest.offset DataTables length -> RawInspectionPageRequest.limit Store total_items -> recordsTotal Store filtered_items -> recordsFiltered Store summary items -> data DataTables draw -> callback draw, jamais une donnée Store ``` **Une seule pagination est visible : le pager DataTables.** Aucun contrôle `Premier / Précédent / Suivant` Store supplémentaire n'est rendu. La page length DataTables `25 / 50 / 100` est également l'unique taille de page visible et devient directement la limite d'inspection Store. ### 6.6 Filtres et ordre V1 Pour éviter de prétendre supporter une recherche globale que le Store ne possède pas : - `searching: false` en V1 ; les filtres Store sont des contrôles explicites hors champ de recherche DataTables ; - Transactions : slot range optionnelle + direction canonique ; - Accounts : pubkey exacte optionnelle + slot range + direction canonique ; - les colonnes arbitraires restent non-orderable tant qu'un ordre backend-neutral n'est pas contracté ; - le contrôle de direction Store provoque un redraw DataTables depuis `start = 0`. Le pager DataTables n'invente donc ni filtre ni tri que le backend ne sait pas appliquer globalement. ### 6.7 Coût accepté et garde-fous Le backend PostgreSQL devra implémenter un chemin d'inspection avec `COUNT` exact et navigation offset. Ce chemin peut être plus coûteux à grande profondeur que keyset ; ce coût est accepté **uniquement pour l'inspection interactive** et ne remplace jamais le chemin cursor. Garde-fous : - page size UX strictement 25/50/100 ; - aucun `length = -1` accepté ; - conversion offset/limit PostgreSQL vérifiée avant I/O ; - réutiliser le count filtré comme total lorsque la query ne possède aucun filtre optionnel ; - aucun cache de count dans Store API ; une optimisation/caching éventuelle reste backend/app-owned et mesurée ; - tests de grand offset et d'overflow ; - si la profondeur réelle devient pathologique, l'UX pourra être recalibrée sans supprimer la pagination keyset canonique. ### 6.8 Recalibrage du prompt initial Le prompt 027 partait de l'hypothèse « pagination Store opaque distincte du paging local DataTables ». `pre.001` avait explicitement pour mission de brainstormer cette frontière. La décision retenue conserve la séparation **des contrats** mais change la composition UI : ```text RAW cursor/keyset = primitive canonique durable, non exposée par le pager Desk RAW inspection offset/page = primitive dédiée à l'inspection interactive DataTables pager = unique représentation visuelle de cette primitive d'inspection ``` C'est une exception locale et bornée aux passages du prompt qui imposaient `paging: false` ou une double notion de page. Toutes les frontières Store/Config/backend, l'interdiction SQL dans l'app et l'objectif read-only restent inchangés. ## 7. Audit fonctionnel kbot3 | Fonction historique | Source kbot3 observée | Valeur UX | Équivalent KSP V1 | Classification | |-------------------------------|----------------------------------|-----------------------------|------------------------------------------------|-----------------------------| | health/diag Store | `demo_store_diag.*` | visibilité opérationnelle | `Store::runtime_snapshot` + `health` | REPRENDRE FONCTIONNELLEMENT | | refresh manuel | vues diag/raw | resynchronisation explicite | refresh Overview/query | REPRENDRE FONCTIONNELLEMENT | | tables horizontales | `demo_store_tables.ts` | lisibilité grands tableaux | DataTables KSP + `scrollX` | REPRENDRE FONCTIONNELLEMENT | | filtre local DataTables | `demo_store_tables.ts` | exploration rapide | filtres Store explicites, pas de search locale | REDESSINER | | page length DataTables locale | `demo_store_tables.ts` | densité configurable | page size inspection Store 25/50/100 | REDESSINER | | pagination backend par blocs | `demo_store_block_pagination.ts` | datasets volumineux | pager DataTables -> inspection Store | REDESSINER | | total rows / total blocks | `demo_store_block_pagination.ts` | repère global | totals exacts inspection | REPRENDRE FONCTIONNELLEMENT | | stack de cursors frontend | `demo_store_block_pagination.ts` | previous | inutile pour le chemin inspection | REJETER | | replay candidates | `demo_store_replay_candidates.*` | diagnostics replay | hors Store Desk RAW V1 | REJETER V1 | | JSON diagnostic détail | tables/raw views | inspection détaillée | détail app-owned borné | REPRENDRE FONCTIONNELLEMENT | | SQL/repositories physiques | backend historique | implémentation | interdit dans Desk | REJETER | kbot3 montre précisément le coût UX de deux paginations successives. KSP reprend l'intention fonctionnelle mais fusionne la navigation visible dans DataTables grâce à la nouvelle capability d'inspection. Aucun code, DTO, SQL ou package kbot3 n'est repris. ## 8. Capability map Store v0.3.7 | Surface / méthode | Entrée | Sortie | Pagination | Taille / sécurité | Utilité UI | |----------------------------------------|-------------------------------------|-------------------------------------|------------|--------------------------|----------------| | `Store::runtime_snapshot()` | aucune | `StoreRuntimeSnapshot` | non | safe, sans I/O | Overview | | `Store::health().await` | aucune | `StoreHealthSnapshot` | non | safe, diagnostics bornés | Overview | | `list_raw_transactions(query)` | network/range/direction/page | `RawPage` | cursor | références seulement | table Tx | | `get_raw_transaction(reference)` | référence Tx | `Option` | non | payload jusqu'à 16 MiB | détail Tx | | `get_raw_transaction_observation(key)` | observation key | `Option` | non | provenance sûre | détail | | retention read Tx | référence Tx | state/tombstone selon capability | non | métadonnées uniquement | table/detail | | `list_raw_account_states(query)` | network/pubkey/range/direction/page | `RawPage` | cursor | références seulement | table Account | | `get_raw_account_state(reference)` | référence Account | `Option` | non | data jusqu'à 16 MiB | détail Account | | `get_raw_account_observation(key)` | observation key | `Option` | non | provenance sûre | détail | ## 9. Gap map ### 9.1 Pagination/summaries d'inspection Gap confirmé : v0.3.7 ne possède ni offset random-access, ni count backend-neutral, ni summary page évitant N+1. | Besoin UI | Gap v0.3.7 | Contrat candidat | Backend | Coût | |--------------------------------|----------------------|-----------------------------------------------------|---------------------|-------| | pager DataTables unique | pas d'offset/count | `RawInspectionPageRequest` + `RawInspectionPage` | PostgreSQL + facade | moyen | | rows Tx sans payload | références seulement | `RawTransactionSummary` + inspection query/read | PostgreSQL + facade | moyen | | rows Account sans data | références seulement | `RawAccountStateSummary` + inspection query/read | PostgreSQL + facade | moyen | | totaux filtrés exacts | aucun count public | `total_items` + `filtered_items` | PostgreSQL | moyen | | navigation machine performante | déjà présente | conserver `RawPage`/cursor keyset | inchangé | aucun | Décision : cette extension est **requise avant les tables métier Store Desk**. Elle est implémentée dans `ksp-store-api`, `ksp-store-lib` et `ksp-store-postgres-lib`, jamais dans l'application. ### 9.2 Observations Gap confirmé : aucune capability backend-neutral de listing des observations transaction/account. | Besoin UI | Gap | Contrat candidat | Backend | Coût | |-----------------------------|-------------------|----------------------------------------------------------|---------------------|-------| | lister observations Tx | pas de query/list | query d'inspection observation + summary/provenance safe | PostgreSQL + facade | moyen | | lister observations Account | pas de query/list | query d'inspection observation + summary/provenance safe | PostgreSQL + facade | moyen | | filtrer par entité | pas de list | filtre référence dans query | PostgreSQL | moyen | Si des tables d'observations dédiées utilisent DataTables, elles emploient le même modèle d'inspection offset/count afin de conserver une seule pagination visuelle. Le read par `RawObservationKey` existant reste inchangé pour le détail. ### 9.3 Diagnostics physiques Les counts exposés par `RawInspectionPage` portent sur les **entités logiques de la query**, jamais sur des noms de tables, indexes, schemas ou ressources PostgreSQL. Aucun diagnostic physique ne traverse la façade. ## 10. Screen map Navigation V1 : ```text Overview RAW Transactions RAW Accounts ``` Les observations sont intégrées au **détail de l'entité** dans la V1 initiale. Une table d'observations dédiée n'est ajoutée qu'après extension Store ; si elle existe, elle utilise le même pager DataTables unique via la primitive d'inspection. ### 10.1 Overview Cartes : ```text Store target/network logique backend kind logique health state migration state/version/pending pool safe counters last stable error code refresh ``` ### 10.2 RAW Transactions ```text filtres Store : slot min/max, direction page size/pagination : contrôles DataTables 25/50/100 + pager unique contrôle applicatif : Refresh filtres Store explicites : dataset complet de la query table bouton/interaction Détail ``` ### 10.3 RAW Accounts Même pattern avec filtre pubkey optionnel. ### 10.4 Detail Un panneau/modal Bootstrap non bloquant charge une seule entité et ses métadonnées. Les observations sont chargées séparément. Aucun payload n'est mis dans les rows du tableau. ## 11. Table / column map ### 11.1 RawTransaction | Colonne | Source Store | IPC | Filtre Store | Order V1 | Copy | Détail seulement | |-----------------|------------------------------------|----------------|--------------|---------------------|------|------------------| | signature | `RawTransactionSummary::reference` | texte encodé | non V1 | tie-breaker interne | oui | non | | slot | `RawTransactionSummary::slot` | texte décimal | range | asc/desc canonique | non | non | | block time | summary | texte décimal? | non | non | non | non | | format id | summary | texte borné | non | non | non | non | | format version | summary | entier JS-safe | non | non | non | non | | payload size | summary | texte décimal? | non | non | non | non | | content hash | summary | texte hex | non | non | oui | non | | retention state | summary | enum/badge | non V1 | non | non | non | | payload bytes | `get_raw_transaction` | détail borné | non | non | non | **oui** | ### 11.2 RawAccountState | Colonne | Source Store | IPC | Filtre Store | Order V1 | Copy | Détail seulement | |-------------|-------------------------------------|---------------|--------------|---------------------|------|------------------| | pubkey | `RawAccountStateSummary::reference` | texte | exact | tie-breaker interne | oui | non | | slot | summary/reference | texte décimal | range | asc/desc canonique | non | non | | state hash | summary/reference | texte hex | non | tie-breaker interne | oui | non | | owner | summary | texte | non | non | oui | non | | lamports | summary | texte décimal | non | non | non | non | | executable | summary | bool | non | non | non | non | | rent epoch | summary | texte décimal | non | non | non | non | | data length | summary | texte décimal | non | non | non | non | | data bytes | `get_raw_account_state` | détail borné | non | non | non | **oui** | Tous les `u64` Store traversent IPC sous forme de chaînes décimales, sauf valeurs bornées explicitement dans un domaine JS-safe. Les summaries ne transportent jamais payload transaction ou account data. ## 12. Pagination map finale | Action / donnée | Propriétaire | Mapping / règle | |----------------------------|---------------------|----------------------------------------------------------------------| | première page | DataTables | `start = 0`, `length = pageLength` | | page N | DataTables | `start = N * length` -> `RawInspectionPageRequest.offset` | | page précédente/suivante | DataTables | même mapping offset, aucun cursor UI | | page length 25/50/100 | DataTables + app | whitelist Rust -> `RawPageLimit` | | total avant filtres | Store inspection | `total_items` -> `recordsTotal` | | total après filtres | Store inspection | `filtered_items` -> `recordsFiltered` | | rows | Store inspection | summaries -> `data` | | `draw` | DataTables/frontend | recopié comme entier dans le callback, jamais persisté par Store | | slot/pubkey/direction | Store query | contrôles explicites, redraw depuis page 1 | | search DataTables | désactivée V1 | aucune recherche locale ou pseudo-globale | | ordre colonnes arbitraires | désactivé V1 | seule direction canonique Store est supportée | | cursor/keyset RAW | Store canonical | conservé pour workers/backfills/replay, hors pager visuel Store Desk | **Invariant UX : un seul composant de pagination est rendu par table, celui de DataTables.** Aucun pager Store parallèle, aucun « bloc Store » imbriqué et aucune seconde page locale. Le frontend utilise `ajax(data, callback)` de DataTables pour appeler une command Tauri ; aucune URL HTTP n'est configurée. Le callback reçoit `draw`, `recordsTotal`, `recordsFiltered` et `data` depuis la projection app-owned. ## 13. DTO / command map Noms candidats stabilisés pour le plan ; les types Store restent réexportés via `ksp-store-lib`, l'application ne dépend pas directement de `ksp-store-api`. | Command | Request | Response | Capability Store | |----------------------------------------|-----------------------------------------------|----------------------------------|------------------------------------| | `store_runtime_status` | aucune | runtime + health safe DTO | `runtime_snapshot`, `health` | | `store_query_transactions` | offset/limit/range/direction | summaries + total/filtered total | Tx inspection read | | `store_get_transaction_detail` | app-owned row identity | detail DTO | get Tx + retention/tombstone | | `store_query_accounts` | offset/limit/pubkey?/range/direction | summaries + total/filtered total | Account inspection read | | `store_get_account_detail` | app-owned row identity | detail DTO | get Account | | `store_query_transaction_observations` | offset/limit + entity filter, après extension | observation summaries + totals | future observation inspection read | | `store_query_account_observations` | offset/limit + entity filter, après extension | observation summaries + totals | future observation inspection read | Request DTO DataTables : seuls `offset`, `limit` et les filtres Store autorisés traversent IPC. `draw`, configuration de colonnes, search strings génériques et metadata DataTables ne deviennent pas un contrat Store. Response DTO app-owned : ```text rows records_total_decimal records_filtered_decimal ``` Le frontend convertit les counts vers `number` uniquement après `Number.isSafeInteger`. Une valeur hors domaine JS-safe produit une erreur UX explicite plutôt qu'un arrondi silencieux. Le risque est théorique à l'échelle actuelle mais le contrat reste exact. ## 14. Composite Config Créer : ```text config/composite.ksp-app-store-desk.json ``` Composition V1 : ```text Logging + Store ``` Aucun Transport. Le profil sélectionné détermine un seul target/network Store. Changer de target, si exposé ultérieurement, implique fermeture puis réouverture explicite ; la V1 utilise le profil composite au démarrage. ## 15. Payload/detail policy Rows IPC : ```text identités u64 en texte metadata de payload/data hashes états aucun byte RAW complet ``` Detail : ```text une entité à la fois chargement explicite preview bornée si affichage texte/hex nécessaire pas de browser persistence pas de logging du contenu pas de duplication dans DataTables ``` Un export exact de 16 MiB n'est pas requis V1. ## 16. Threat model | Menace / erreur | Mitigation V1 | |---------------------------------------|-------------------------------------------------------------------------| | page size 0/pathologique | whitelist UX 25/50/100 + validation Rust | | `length = -1` DataTables | rejet avant Store | | offset > capacité backend | conversion checked, erreur statique sans I/O | | offset très profond | chemin inspection seulement ; keyset canonique conservé pour machines | | count coûteux | exact seulement pour inspection ; reuse total=filtered sans filtre | | total > JS safe integer | DTO décimal + conversion checked côté TS | | range slot inversée | validation DTO + `RawSlotRange::new` | | pubkey invalide | parsing Rust via type KSP réexporté | | payload/data jusqu'à 16 MiB | summaries sans bytes, detail-only | | N+1 massif | supprimé par summaries d'inspection | | refresh/filtre rapide | DataTables `draw` + génération de filtre ; réponse stale ignorée | | redraw race | un seul pipeline `ajax` DataTables par table | | search globale mensongère | `searching: false` V1 | | ordre colonne non supporté | colonnes non-orderable ; direction Store explicite | | Store close pendant query | état Rust de fermeture, refus nouvelle admission, close borné | | URI/backend leak | DTO app-owned safe, aucune erreur Debug backend | | logging payload/query sensible | instrumentation metadata-only | | localStorage/sessionStorage/IndexedDB | interdits et testés statiquement | | SQL/backend physical leak | dependency/security canaries | | confusion DataTables/RAW cursor | types/traits distincts + tests exacts ; aucun cursor dans le pager Desk | ## 17. Instrumentation frontend Tracer sans secrets/payload/cursor ni contenu de filtres : ```text startup / runtime status refresh DataTables draw start/end offset/page length changés filtre Store changé direction Store changée view/tab changé detail opened/closed count/query failure par code stable ``` `draw`, offset et page length peuvent être journalisés comme metadata numérique sûre. Signature, pubkey, hash, payload, URI, credentials et valeurs de filtre ne sont pas journalisés. ## 18. Gate live Smoke final optionnel, read-only : ```text Config -> Store open -> health -> DataTables first inspection page -> next/random page via offset -> filter Store + redraw -> one transaction detail -> one account detail -> close ``` Le smoke vérifie aussi la cohérence `recordsTotal/recordsFiltered` sur un dataset contrôlé. Aucune migration destructive, archive, purge ou force-rehydrate n'est déclenchée. ## 19. Prévision souple recalibrée — prereleases souples ### pre.001 — audit, brainstorming, architecture et planification Archive/règles/prompt, gabarit KSP/npm, audit fonctionnel kbot3, capability/gap/screen/table/pagination/DTO/threat maps. Décision : primitive d'inspection backend-neutral + **un seul pager DataTables visible**. Aucun scaffold lourd. ### pre.002 — scaffold `ksp-app-store-desk` Package lib/bin, ports 1438/1439, shell/splash/assets/fonts/SASS KSP, tracing frontend, capabilities minimales, `datatables.net-bs5` et table skeleton sans données Store. Aucun Store ouvert. Le skeleton peut paginer son dataset vide/local pour valider le rendu DataTables, mais ne matérialise aucun second pager Store et ne préjuge pas du branchement `serverSide` de `pre.007`/`pre.008`. ### pre.003 — contrats Store d'inspection Ajouter dans `ksp-store-api` les types page offset/count, queries Tx/Account, summaries sans gros bytes et deux traits read d'inspection. Réexports `ksp-store-lib`, canaris external backend/public API/security. Aucun SQL encore. ### pre.004 — inspection PostgreSQL RawTransaction Implémenter summary query, counts exacts, offset/limit checked et trait Tx dans `ksp-store-postgres-lib`, puis dispatch façade Store. Conserver intégralement le listage keyset existant. L'inspection utilise une seule instruction SQL par requête afin que `total_items`, `filtered_items` et la page partagent le même snapshot de statement. Le SQL ne sélectionne jamais les payload bytes : il projette uniquement `OCTET_LENGTH` du payload actif/archivé et les marqueurs de présence nécessaires au contrôle de cohérence. Les tombstones `Purged` restent inspectables. Tests no-payload/N+1, counts filtres, offset hostile, pages vides profondes et conformance. ### pre.005 — inspection PostgreSQL RawAccountState Même vertical slice pour Account : filtre exact `pubkey`, range slot inclusif, ordre canonique `(slot, pubkey, state_hash)`, `data_length_bytes` via `OCTET_LENGTH(data)` sans sélectionner `data`, counts exacts et page `LIMIT/OFFSET` dans une seule instruction SQL par direction. `RawAccountStateInspectionRead` est implémenté sur PostgreSQL puis dispatché par `Store`, ce qui porte l'inventaire symétrique à 12 capabilities RAW. Les quatre statements cursor/keyset Account restent strictement OFFSET-free et ne sont jamais réécrits. Le live proof opt-in couvre filtre pubkey, counts, asc/desc, offset et page vide profonde. ### pre.006 — composite Config + lifecycle Store + Overview Créer `config/composite.ksp-app-store-desk.json`, enregistrer son `file_id` dans le registre partagé `ksp-config-lib`, propager la quinzième ressource Config aux cinq bundles Desk utilisant `prepare_packaged_runtime()`, puis brancher Store Desk sur `ksp-store-lib::Store` uniquement. Le composite sélectionne Logging `supertrace` et le profil Store homonyme `devnet`/`mainnet`/`testnet`. L'Overview et Diagnostics exposent uniquement backend logique, network, health, migration et compteurs de pool via un DTO IPC backend-neutral ; aucune URI, credential, SQL, handle physique ou byte RAW. La fermeture de la fenêtre principale déclenche une fermeture Store one-shot et bornée avant sortie Tauri. Les DataTables Transactions/Accounts restent locales et `serverSide:false` jusqu'à `pre.007`/`pre.008`. #### pre.006-fix.001 — conformité crate-root des items visibles Store Desk Le gate opérateur de `pre.006` est fonctionnellement vert mais signale un warning `unused_imports` sur le réexport crate-root de `StoreRuntime`. Le réexport n'est pas supprimé : conformément à `RUST-IMPORT-009`, `RUST-IMPORT-012` et `RUST-API-004`, les items `pub`/`pub(crate)` partagés restent réexportés via `lib.rs` et sont consommés par `crate::Item`, y compris depuis leur module propriétaire. Le correctif normalise les usages Store Desk concernés (`CommandErrorDto`, `FrontendLogPayloadDto`, `SplashSettings`, `SplashOrderDto`, `StoreStartup`, `StoreRuntime`) et rend privés les deux labels de fenêtre qui n'ont aucune consommation crate-wide au lieu de les promouvoir artificiellement. Aucun comportement Config/Store/Tauri/IPC/frontend n'est modifié. ### pre.007 — RawTransaction DataTables serverSide + détail Brancher l'unique pager DataTables via `ajax` function/Tauri invoke, mapping offset/limit/totaux, filtres Store explicites, summary rows, retention badges, détail à la demande et instrumentation. Aucun second pager. Implémentation retenue : `store_query_transactions` reçoit uniquement `offset`, `limit`, `slot_min`, `slot_max` et `direction`, dérive le network du Store déjà ouvert et appelle `RawTransactionInspectionRead`. Les counts restent des chaînes décimales jusqu'au contrôle `Number.MAX_SAFE_INTEGER` côté frontend. Les rows sont payload-free. `store_get_transaction_detail` reçoit uniquement la signature de la row, recharge l'entité via `RawTransactionRead`/`RawTransactionRetentionRead` et expose au maximum 512 bytes sous forme de preview hex, avec taille exacte et indicateur de troncature. `Full`, `Archived` et `Purged` sont distingués ; un `Purged` utilise son tombstone. Les Accounts restent `serverSide:false` jusqu'à `pre.008`. #### pre.007-fix.001 — UX uniforme pour identifiants longs Les signatures et content hashes déjà visibles dans la table Transactions et son modal sont affichés sous une forme tronquée au centre, avec la valeur complète disponible au survol via tooltip natif et un bouton de copie explicite. Le mécanisme reste générique et doit être réutilisé par `pre.008` pour les pubkeys, owners, state hashes et autres identifiants longs. Les actions de copie sont tracées uniquement par identifiant logique de champ ; la valeur copiée n'est jamais journalisée. Aucun plugin clipboard Tauri ni capability supplémentaire n'est ajouté : le frontend utilise l'API Clipboard du WebView avec fallback local `document.execCommand("copy")`. #### pre.007-fix.002 — copie explicite du preview payload Le preview transaction reste borné à 512 bytes côté Rust mais devient copiable explicitement depuis le modal. Le bouton copie uniquement le preview hex déjà présent côté frontend ; il ne charge ni n'expose le payload complet. Les canaris Rust ajoutés en `fix.001` sont corrigés avec des raw string literals valides. #### pre.007-fix.003 — rescope du canari de tracing copie Le canari de sécurité ne bannit plus globalement des variables métier légitimes comme `signature` : il vérifie uniquement que les événements de copie transportent `{ fieldId }` et jamais la valeur copiée. Aucun runtime/frontend n'est modifié. ### pre.008 — RawAccountState DataTables serverSide + détail Brancher `RAW Accounts` sur `RawAccountStateInspectionRead` avec le même modèle server-side que Transactions : `start/length` -> offset/limit, counts exacts, filtres Store explicites `pubkey` exacte + slot min/max + direction, `searching:false`, `ordering:false`, whitelist 25/50/100 et DataTables comme unique pager. Les rows restent data-free et exposent pubkey, slot, state hash, owner, lamports, executable, rent epoch et data length. Le détail reçoit uniquement l'identité de row `(pubkey, slot, state_hash)`, dérive le network du Store ouvert et appelle `RawAccountStateRead`. Les bytes Account ne traversent jamais la table ; le modal reçoit au maximum 512 bytes sous forme de preview hex avec taille exacte et flag de troncature. Pubkey, owner, state hash et preview data réutilisent le helper générique troncature/tooltip/copie stabilisé en `pre.007-fix.001/002/003`. Les traces ne contiennent aucune valeur de pubkey, slot, state hash, owner ou data. ### pre.009 — extension Store observations Étendre le contrat Store backend-neutral avec `RawTransactionObservationInspectionRead` et `RawAccountObservationInspectionRead`, sans modifier les reads par clé existants. Les deux familles utilisent `RawInspectionPageRequest` / `RawInspectionPage` avec counts exacts et random access offset/limit. La requête porte le network obligatoire, une référence d'entité exacte optionnelle et la direction canonique ; la cohérence network/référence est validée avant dispatch. Les summaries d'observation restent strictement sans bytes RAW : clé d'observation, référence durable et `RawAcquisitionProvenance` sûre ; Account conserve seulement les metadata Yellowstone optionnelles déjà présentes dans le modèle (`is_startup`, transaction signature associée, `write_version`). PostgreSQL implémente quatre statements privés, un ASC et un DESC par famille, ordonnés déterministiquement par `(received_at_unix_millis, observation_key)` et utilisant counts + `LEFT JOIN LATERAL` + `LIMIT/OFFSET`. Les queries keyset/read-by-key historiques restent inchangées. L'inventaire symétrique Store/PostgreSQL passe de 12 à 14 capabilities RAW. Aucune migration, index, UI ou DTO DataTables n'est ajouté dans cette tranche ; l'intégration visuelle reste réservée à `pre.010`. ### pre.010 — observations UI + hardening UX Intégrer les observations dans les modals de détail Transaction et Account via deux DataTables server-side dédiées, filtrées implicitement par l'identité exacte de l'entité ouverte. Les tables utilisent exclusivement les capabilities `RawTransactionObservationInspectionRead` / `RawAccountObservationInspectionRead` livrées en `pre.009`, avec ordre `descending` fixé côté application, `start/length -> offset/limit`, whitelist 25/50/100, counts exacts et DataTables comme seul propriétaire de pagination. Aucun cursor, network, search globale ou ordre DataTables arbitraire ne traverse l'IPC. Les rows Observation projettent uniquement `RawAcquisitionProvenance` sûre : provider, protocol, acquisition method, origin, received/observed timestamps, endpoint/session/filter/commitment optionnels et hash/taille source. Account ajoute uniquement les metadata déjà admises (`is_startup`, `write_version`, transaction signature associée). Aucun source payload byte, payload RAW Transaction ni data Account n'est exposé. Les identifiants longs réutilisent le helper troncature/tooltip/copie sans valeur dans le tracing. Le hardening V1 centralise la validation offset/limit côté Rust, rejette les draw/start/length hostiles avant IPC, refuse les counts dépassant `Number.MAX_SAFE_INTEGER`, ignore les réponses Observation devenues stale après changement/fermeture d'entité, ajuste les colonnes à l'ouverture du modal et conserve les erreurs d'observation dans une surface dédiée sans donnée métier. ### pre.011 — security / dependency / release completeness Verrouiller la surface V1 sans ajouter de fonctionnalité métier ni modifier Store API/façade/PostgreSQL. Un nouveau firewall exécutable `release_completeness.rs` doit rendre exacts les inventaires directs Cargo/npm, les modules Rust Store Desk, les commands Tauri et les permissions desktop. La composition Config Store Desk reste exactement `logging + store` sur `devnet/mainnet/testnet`, sans URI ni secret dans le composite. Le frontend est rescanné contre toute ouverture réseau navigateur, stockage navigateur RAW, dialogue natif ou matérialisation PostgreSQL/SQL. Les DTO/IPC doivent rester backend-neutral et metadata-only, avec les previews RAW bornés uniquement dans les DTO de détail déjà admis. Enfin, la coexistence des deux paradigmes de pagination est verrouillée explicitement : `RawPageCursor`/`RawPageRequest` et les statements PostgreSQL `LIST_*` restent keyset et OFFSET-free pour les machines/workers, tandis que `RawInspectionPageRequest` et les statements `INSPECT_*` conservent leur random access `OFFSET` pour Store Desk/DataTables. Aucun runtime n'est ajouté par cette tranche. ### pre.012 — gate technique final Ouvrir exclusivement la preuve technique finale sur la candidate déjà durcie, sans nouveau runtime, frontend, DTO, contrat Store, SQL, migration, Config, dépendance ou test fonctionnel. Le gate est séquentiel : tout échec invalide la tranche même si le shell poursuit ensuite d'autres commandes. Ordre attendu avant le build desktop final : ```bash cargo fmt --all -- --check 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 --all-features -- -D warnings cargo test --workspace --all-targets --all-features cargo check -p ksp-store-lib --no-default-features cargo tree -p ksp-app-store-desk --edges normal cargo tree -p ksp-app-store-desk -e features cargo tree -p ksp-store-lib --edges normal cargo tree -p ksp-store-lib -e features cargo tree --duplicates ``` Si un environnement PostgreSQL opérateur sûr est disponible, un smoke **strictement read-only** est exécuté ensuite via Store Desk : Store `ready`, page Transaction, page Account, détail/observations lorsqu'une row existe, puis fermeture propre. Aucune archive, purge, force-rehydrate ou autre mutation n'est admise. Le build Tauri est l'ultime opération lourde de la tranche : ```bash (cd crates/ksp-app-store-desk && cargo tauri build) ``` Un défaut ouvre `pre.012-fix.NNN`; aucune réconciliation README/USAGE durable n'est faite avant un gate entièrement vert. ### pre.013 — réconciliation documentaire finale README/USAGE Store Desk, architecture/index, plan/validation. Aucun changement runtime. Le gate technique autoritaire est `pre.012-fix.001`, entièrement vert après réalignement du `tsconfig.json` Store Desk sur le gabarit Desk commun. ### pre.014 — préparation publication Prompt suivant + CHANGELOG + ROADMAP uniquement, avec bump prerelease requis. ### rel.001 — publication stable Publication mécanique `v0.3.8`, aucun rattrapage. Le sizing reste crédible pour une session parce que la modification Store est séparée en contrat (`pre.003`), Tx backend (`pre.004`) et Account backend (`pre.005`) avant toute table métier. La pagination cursor existante n'est jamais réécrite. ## 20. Hors périmètre v0.3.8 ```text remplacement/suppression de RawPage cursor/keyset DataTables-specific DTO dans ksp-store-api search globale arbitraire DataTables sans contrat Store tri arbitraire sur toutes les colonnes sans contrat Store SQL app-owned backend PostgreSQL direct actions archive/purge/force-rehydrate backfill/job control transport RPC ingest live worker lifecycle STRUCTURAL/DECODED/DOMAIN/materialization analytics/trading browser persistence RAW ``` L'utilisation de `OFFSET` et `COUNT` est autorisée uniquement dans l'implémentation privée PostgreSQL de la nouvelle capability d'inspection ; elle reste interdite dans l'application et ne remplace pas les queries keyset existantes. ## 21. Réconciliation documentaire finale La surface réellement livrée est désormais réconciliée sans rouvrir le runtime : - `ksp-app-store-desk` est documentée comme application read-only de `ksp-store-lib` composée avec Config/Core/Logging ; - Overview expose uniquement health/runtime portable et diagnostics sûrs ; - Transactions et Accounts utilisent DataTables server-side avec inspection random-access backend-neutral, counts exacts et tailles 25/50/100 ; - les observations sont listées server-side dans le détail de leur entité exacte ; - les rows restent sans payload/data/source bytes et les previews de détail restent bornés à 512 bytes ; - les états Transaction `Full` / `Archived` / `Purged` et tombstones sont lisibles sans action destructive ; - la pagination machine `RawPageCursor`/keyset et l'inspection `RawInspectionPageRequest`/offset restent deux contrats distincts ; - aucun Transport, SQL app-owned, backend PostgreSQL direct, write Store, archive/purge/force-rehydrate, backfill ou worker control n'entre dans Store Desk. Le gate technique final autoritaire a validé 1 592 tests, zéro échec, 15 tests opt-in/operator-only ignorés, Clippy `-D warnings`, la façade Store sans default features et deux bundles Tauri Linux (`deb`, `rpm`). La lane suivante reste strictement publication-only : `pre.014` peut modifier CHANGELOG/ROADMAP et produire le prompt `0.3.9`, sans rouvrir la documentation technique ou le code de `0.3.8`.