# Plan v0.3.7 — Backfill Desk ## 1. But de la version La v0.3.7 crée `ksp-app-backfill-desk`, application Tauri spécialisée qui compose Config, Transport, Store et `ksp-job-backfill-lib` afin de lancer, observer, annuler et reprendre un backfill historique borné. L'application reste une couche de composition et d'interface. Elle ne possède ni découverte RPC, ni retry/rate-limit, ni persistance, ni frontier/checkpoint, ni navigation Store générale. Chaîne cible : ```text Config composite/profile -> Logging -> Transport HTTP -> Store -> AppState Rust -> BackfillRequest -> BackfillJobRuntime -> BackfillJobHandle -> BackfillSnapshotSource -> DTO Tauri sûrs -> UI latest-value ``` ## 2. Base et audits de pre.001 Base autoritaire : archive stable `v0.3.6`, `workspace.package.version = 0.3.6`, avec `deltas/0.3.6/rel.001.md`, `ksp-job-api`, `ksp-job-backfill-lib` et les trois Desks existants. Les deux archives ont été testées intégralement avant utilisation et extraites dans des arbres séparés. kbot3 reste une référence fonctionnelle/UX ; aucun code, DTO, commande Tauri ou algorithme n'en est repris. Sources internes relues : règles racine et `docs/rules/`, architectures Layers/Dependencies/Components/Jobs/Apps, clôture complète de v0.3.6, surfaces Rust Job/Backfill/Config/Transport/Store et gabarits des Desks Config, Wallet et SOL Prices. L'audit externe du 2026-09-02 confirme que le socle Tauri v2, son modèle capabilities/runtime authority et les versions courantes restent compatibles avec les contraintes existantes. Le workspace résout déjà `tauri 2.11.5`, `tauri-build 2.6.3`, `tauri-plugin-tracing 0.3.4` et `ts-rs 12.0.1`; les manifests frontend utilisent `@tauri-apps/api ^2.11`, `@tauri-apps/cli ^2.11`, TypeScript `^7.0` et Vite `^8.2`. Une version plus récente de Vite existe, mais aucun besoin fonctionnel ne justifie une mise à niveau en v0.3.7. ## 3. Surface Backfill réellement disponible `ksp-job-backfill-lib` expose déjà les quatre scopes `LatestAddress`, `BeforeAddress`, `AfterAddress` et `ExplicitSignatures`, les commitments `Confirmed`/`Finalized`, les bornes publiques, `BackfillRequest`, `BackfillCheckpoint`, `BackfillJobRuntime`, `BackfillJobHandle` et `BackfillSnapshotSource`. Le snapshot sûr expose phase, scope kind, boundary, candidats selected/admitted/finished, entities inserted/existing/purged, missing, conflicts, observations inserted/existing, cancelled candidates, holes, maximum in-flight, contiguous completed, présence d'un checkpoint et failure code. L'UI ne fabrique aucun pourcentage global lorsque le dénominateur n'est pas connu. Bornes backend-owned : | Paramètre | Borne publique | |-----------------------|---------------------------------------------:| | page size | 1..=1000 | | max pages | 1..=10000 | | max candidates | 1..=10000 | | hydration concurrency | 1..=64 | | signature Base58 | 64..=88 octets texte, validation Rust exacte | `min_context_slot` est admis pour les scopes adresse et rejeté pour `ExplicitSignatures` par `BackfillRequest`. ## 4. Matrice kbot3 | Capacité historique | Preuve fonctionnelle kbot3 | Équivalent KSP v0.3.6 | Décision | |---------------------------------------------------------|----------------------------------------|---------------------------------------|-----------------------------| | Formulaire de campagne | `demo_backfill.html` + request TS/Rust | `BackfillRequest` explicite et borné | REPRENDRE FONCTIONNELLEMENT | | Signatures explicites | textarea + mode `explicit_signatures` | `BackfillScope::explicit_signatures` | REPRENDRE FONCTIONNELLEMENT | | Adresse + direction | address, anchor, before/after | Latest/Before/After address distincts | REDESSINER POUR KSP | | Commitment | sélecteur confirmed/finalized | `BackfillCommitment` | REPRENDRE FONCTIONNELLEMENT | | Rôle logique | rôle HTTP sélectionnable | `HttpRoleName` + Transport | REDESSINER POUR KSP | | Provider/endpoint affichés | options et summary | Transport possède routage physique | REJETER | | Retry opérateur | `maxRetries` UI | Transport possède retry/pacing | REJETER | | Page size/max pages/limit | inputs bornés | constantes Backfill publiques | REPRENDRE FONCTIONNELLEMENT | | Concurrency | input opérateur | `hydration_concurrency` | REPRENDRE FONCTIONNELLEMENT | | Program/token/pool modes | accordéons dédiés | scopes génériques adresse/signatures | REJETER comme types KSP | | Progression live | événements textuels + log | snapshots latest-value structurés | REDESSINER POUR KSP | | Annulation | bool atomique + commande | `BackfillJobHandle::cancel()` | REDESSINER POUR KSP | | Résumé terminal | payload summary | notification/snapshot terminal | REPRENDRE FONCTIONNELLEMENT | | Compteurs inserted/existing/missing/failed/observations | summary | compteurs `BackfillJobSnapshot` | REDESSINER POUR KSP | | Cursor older-history | `resume_before_signature` | `BackfillCheckpoint` opaque | REDESSINER POUR KSP | | Reprise durable | cursor texte retourné | aucun checkpoint sérialisable public | REPORTER | | Single-run UI | flag `running` | runtime single-run + handle | REPRENDRE FONCTIONNELLEMENT | | Store browsing | demo desktop monolithique | futur Store Desk v0.3.8 | REJETER | ## 5. Screen map Un seul écran principal responsive suffit en V1, en plus du splash commun. ```text main ├─ header : Backfill Desk + statut runtime/config ├─ readiness : profil composite, réseau, rôle(s) compatibles, Store/Transport ready ├─ campaign form │ ├─ scope kind │ ├─ address / anchor / signatures selon scope │ ├─ commitment + rôle logique │ └─ page size / max pages / max candidates / concurrency / min_context_slot ├─ active job │ ├─ lifecycle + phase │ ├─ boundary/frontier │ ├─ compteurs structurés │ ├─ checkpoint présent + contiguous completed │ └─ Start / Cancel selon état ├─ terminal summary │ ├─ outcome/lifecycle │ ├─ failure code sûr │ └─ Resume / New campaign selon checkpoint et scope └─ diagnostics sûrs : codes, jamais secrets/URL/RAW ``` États UI explicites : `booting`, `ready`, `starting`, `running`, `cancelling`, `completed`, `cancelled`, `failed`, `resume-ready`. `cancelling` ne prétend jamais que la persistance déjà soumise est interrompue. ## 6. DTO et command map Les DTOs sont possédés par l'application et dérivés TS-RS uniquement à cette frontière. | Élément | Direction | Contenu | Validation / source de vérité | Sensibilité | |----------------------------------|------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------|--------------| | `BackfillDeskOptionsDto` | Rust -> UI | réseau, rôles, commitments, scopes, bornes, readiness, Program IDs autocomplete | Config + Transport + Core + Backfill | sûre | | `BackfillRequestLimitsDto` | Rust -> UI | defaults + maxima de campagne | constantes publiques Backfill | sûre | | `BackfillStartRequestDto` | UI -> Rust | scope inputs, commitment, rôle, bornes, optional min context slot | conversion stricte vers types KSP | métier borné | | `BackfillRequestPreviewDto` | Rust -> UI | request validée sans valeur d'adresse/signature | mapping applicatif + `BackfillRequest` | sûre | | `BackfillStartResponseDto` | Rust -> UI | JobId backend + état initial | slot single-run | sûre | | `BackfillRunStatusDto` | Rust -> UI | job state, phase, compteurs, boundary, checkpoint_present, contiguous_completed, failure code | `BackfillSnapshotSource` | sûre | | `BackfillCancelResponseDto` | Rust -> UI | accepted + JobId ciblé + état | `BackfillJobHandle` | sûre | | `BackfillResumeResponseDto` | Rust -> UI | accepted + nouveau JobId + état | checkpoint Rust détenu par `BackfillRunState` | sûre | | `ProgramIdAutocompleteOptionDto` | Rust -> UI | program_id + metadata publiques de taxonomie | `ksp_core_lib::entries()` | publique | | `CommandErrorDto` | Rust -> UI | domain/code + message app maîtrisé | mapping applicatif | sûre | Commandes finales : ```text backfill_options() -> BackfillDeskOptionsDto backfill_status() -> Option backfill_validate_request(request) -> BackfillRequestPreviewDto backfill_start(request) -> BackfillStartResponseDto backfill_cancel(job_id) -> BackfillCancelResponseDto backfill_resume() -> BackfillResumeResponseDto ``` Le monitoring continu ne dépend pas du parsing de logs. Le backend possède un bridge latest-value depuis `BackfillSnapshotSource`, projette le snapshot et émet l'événement Tauri stable `ksp-backfill-status`. `backfill_status` reste le chemin de resynchronisation initiale/explicite. Aucun `BackfillCheckpoint`, `Store`, `HttpTransportPool`, sender/receiver Tokio ni payload RAW n'est sérialisé vers le frontend. ## 7. Config et composition Le composite cible est `config/composite.ksp-app-backfill-desk.json` avec trois profils cohérents : | Profil composite | Logging | Transport | Store | Réseau | |------------------|----------------|-------------------------|-----------|----------------| | `devnet` | `supertrace` | `devnet_public` | `devnet` | `devnet` | | `mainnet` | `supertrace` | `mainnet_backfill_pool` | `mainnet` | `mainnet-beta` | | `testnet` | `console_info` | `publicnode_testnet` | `testnet` | `testnet` | Le default V1 est désormais `mainnet`, conformément à la finalité lecture seule du Backfill Desk. `devnet` et `testnet` restent sélectionnables par reconfiguration du composite. Pendant le développement de `0.3.7`, Mainnet conserve le profil Logging `supertrace`. Aucun `std.job_backfill.json` n'est créé : les paramètres de campagne restent opérationnels et backend-validés. Au bootstrap, l'application charge le composite, résout Logging/Transport/Store via `ksp-config-lib`, construit `HttpTransportPool` et ouvre `ksp_store_lib::Store`. Elle compare le réseau Store au(x) cluster(s) HTTP activés et refuse le readiness si une composition mélange des réseaux. Les routes HTTP présentées à l'UI sont dérivées des snapshots/settings Transport, dédupliquées, puis validées côté Rust comme capables de sélectionner à la fois `getSignaturesForAddress` et `getTransaction`. Sur Mainnet, `backfill_pool` regroupe Solana Public et PublicNode à priorité égale, tandis que `backfill_solana_public` et `backfill_publicnode` ciblent un provider unique. Le frontend ne reçoit que le rôle logique et les labels provider sûrs ; URL, credentials, rate-limit interne et retry restent possédés par Transport. Le packaging Config suit le contrat atomique déjà détenu par `ksp-config-lib::prepare_packaged_runtime()` : il traverse l'intégralité de `ConfigFileRegistry::defaults()`. L'ajout du composite Backfill Desk fait donc passer le registre de 13 à 14 descriptors et impose que les quatre applications Desk qui utilisent ce packaging embarquent les 14 resources Config/schema du registre. Backfill Desk embarque ainsi également les documents Wallet/offchain sans les consommer au runtime ; leur présence satisfait le contrat de packaging partagé et n'ouvre aucune dépendance, permission frontend ou responsabilité métier supplémentaire. ## 8. Dependency map Dépendances Rust normales envisagées : ```text ksp-app-backfill-desk ├─ ksp-core-lib # erreurs, Pubkey/app DTO si requis ├─ ksp-logging-lib # façade logging ├─ ksp-config-lib # composition et packaging Config ├─ ksp-job-api # JobState/JobSnapshotSource consommés directement ├─ ksp-job-backfill-lib # request/runtime/handle/snapshot ├─ ksp-onchain-transport-lib # construction HttpTransportPool + role/method readiness ├─ ksp-store-lib # Store::open et ressource runtime ├─ serde # DTO IPC ├─ tauri ├─ tauri-plugin-tracing ├─ tokio # spawn/synchronisation app si gabarit Tauri l'exige └─ ts-rs # bindings DTO applicatifs ``` `ksp-job-backfill-lib` dépend déjà de Store avec `default-features = false`. Le Desk devra cependant ouvrir le Store concret ; la feature backend de `ksp-store-lib` doit donc être explicitement auditée au scaffold. Le pattern cible est une dépendance sur `ksp-store-lib` uniquement, avec le backend compilé par la feature de façade adéquate, jamais une dépendance directe sur `ksp-store-postgres-lib`. Dépendances interdites : `ksp-store-api` direct, `ksp-store-postgres-lib`, `tokio-postgres`, `reqwest`, `tonic`, `yellowstone-grpc-proto`, SDK provider. ## 9. Single-run lifecycle et races AppState conserve au plus : ressources runtime stables, un slot de run actif, le dernier snapshot terminal et le dernier checkpoint sûr associé à la requête/backend state nécessaires à une reprise en session. Règles : - start atomique : refus si un run est `starting/running/cancelling` ; - le handle est installé avant spawn afin que Cancel ne perde pas la course au démarrage ; - Cancel cible le `job_id` backend affiché par le monitoring afin qu'une requête IPC retardée ne puisse jamais annuler un run ultérieur ; - Cancel répété est idempotent au niveau UX, mais le retour expose si la demande a été acceptée ; - un terminal publié gagne sur un Cancel tardif ; un Cancel visant l'ancien JobId après admission d'un nouveau run est rejeté comme mismatch ; - la source latest-value est conservée jusqu'à projection du terminal ; - reset/new campaign n'est autorisé qu'en absence de run actif ; - fermeture de l'application déclenche une demande d'annulation coopérative best-effort puis laisse la destruction du process terminer les ressources ; aucune promesse de drain crash-safe n'est faite ; - aucun registry multi-job générique n'est introduit. ## 10. Reprise et checkpoint V1 conserve le dernier `BackfillCheckpoint` et la requête sémantique terminale uniquement dans Rust. L'UI voit `checkpoint_present` et `contiguous_completed`, jamais les octets/identité internes du checkpoint. Le checkpoint concret reste lié au JobId qui l'a produit. Pour créer un nouveau lifecycle sans affaiblir cette garantie, `ksp-job-backfill-lib` valide d'abord le checkpoint contre la requête terminale d'origine puis `BackfillRequest::resume_for_job` le réémet sur le nouveau JobId backend en conservant exactement scope fingerprint, frontier et cursor interne. La Desk ne fabrique ni ne modifie ces éléments. `Resume` exige en outre que la composition Transport/Store courante reste compatible avec le réseau et le rôle logique de la requête retenue. Toute modification de scope/commitment/bornes sémantiques appartient à une nouvelle campagne et n'utilise pas Resume. Aucun JSON, fichier ou table checkpoint n'est inventé. La reprise après crash/redémarrage est reportée à une évolution explicite du contrat Backfill si un besoin durable est démontré. ## 11. Threat model | Menace | Surface | Contrôle v0.3.7 | |------------------------------|------------------|----------------------------------------------------------------------------------------------| | fuite URI Store | Config/backend | jamais projetée en DTO/log frontend | | fuite URL/token provider | Transport/Config | rôle + labels provider sûrs uniquement ; aucune URL/credential | | fuite RAW transaction | snapshots/IPC | snapshot existant ne contient aucun payload ; aucun Store read général | | fuite liste signatures | request/logging | input transmis au command, non journalisé ; DTO retour n'en contient pas | | fuite adresse | instrumentation | ne pas journaliser la valeur ; seulement scope kind/action code | | frontend falsifie bornes | IPC | validation Rust + `BackfillRequest` autoritaire | | rôle incompatible | Config/Transport | inventory + `select_for_method` pour les deux RPC avant Start | | réseau incohérent | Config | rejet bootstrap/readiness avant création de request | | double Start | AppState | admission atomique single-run | | Cancel/terminal race | AppState/runtime | handle installé avant spawn, terminal autoritaire | | checkpoint forgé | frontend | checkpoint jamais traverse IPC | | permissions Tauri excessives | capabilities | `core:default` + `tracing:default`, aucune permission fs/dialog/network frontend sans besoin | | browser storage de secrets | frontend | interdit ; aucun secret ne doit atteindre JS | Tauri capabilities restent le mécanisme de frontière IPC. La capability par défaut doit cibler seulement `splash` et `main` et ne pas ajouter de plugin filesystem/dialog/network sans besoin réel. ## 12. Gate fonctionnel/live Obligatoire pendant la release : tests déterministes Rust/desktop/security/composition, gates workspace et `cargo tauri build` final lancé depuis `crates/ksp-app-backfill-desk`. Un smoke `Config -> Transport -> Store -> Backfill` peut exister comme test ignoré/opt-in uniquement si l'opérateur dispose d'un PostgreSQL de test et d'un endpoint public sûr. Aucun compte payant, secret fournisseur ou ressource non reproductible n'est requis pour clôturer la release. ## 13. Prévision recalibrée ### pre.001 — audit et planification Archives/règles/surfaces, matrice kbot3, screen map, DTO/commands, Config/composition, dépendances, threat model, lifecycle single-run, role/network/resume et sizing. Aucun scaffold lourd. ### pre.002 — scaffold desktop minimal Créer package lib+bin, shell/splash/tracing/capability, frontend minimal, ports Vite/HMR 1436/1437 stricts et tests desktop de structure. Le scaffold réutilise strictement le gabarit des Desk existantes : mêmes assets splash/logo/font, mêmes familles SASS/Bootstrap/SimpleBar, mêmes dépendances frontend minimales et mêmes `devDependencies`. Le bridge Logging frontend trace dès cette tranche les clics/onglets/transitions significatives ; le bootstrap Logging standard reste transitoire jusqu'au composite Backfill Desk de `pre.003`. ### pre.003 — Config composite et packaging Ajouter le composite Backfill Desk et l'enregistrer dans Config, mettre à jour atomiquement les quatre bundles Desk vers les 14 resources du registre, puis brancher le bootstrap Config/Logging et ses canaries sans encore ouvrir Transport/Store réel. Pendant le développement de l'application, `devnet` sélectionne le profil Logging `supertrace` et le fallback local Backfill Desk reste au niveau `trace`; ce choix est local au Desk et devra être resserré avant publication stable. Le gate opérateur de `pre.003` ouvre `pre.003-fix.001` pour quatre corrections strictement locales : ordre `RUST-FMT-104` d'un bloc de constantes, `return` explicite dans un canari Clippy, réconciliation de la fixture composite Backfill avec les profils réellement disponibles dans le corpus de fixtures Config, et correction d'un canari de dépendance qui doit reconnaître la syntaxe `path` utilisée par les crates KSP internes. Le composite de production, son profil `supertrace` et les frontières runtime de `pre.003` restent inchangés. Le replay opérateur de `pre.003-fix.001` est ensuite intégralement propre : audits Rust/Markdown, `cargo check`, Clippy, suites Config/Config Desk/Wallet Desk/SOL Prices Desk/Backfill Desk et arbres de dépendances Backfill. Ce gate ouvre `pre.004`. ### pre.004 — Transport readiness Construire `HttpTransportPool`, dériver l'inventaire sûr des réseaux/rôles, vérifier le support simultané de `getSignaturesForAddress` et `getTransaction`, puis exposer `backfill_options()` sans provider, endpoint physique ni URL. `BackfillDeskOptionsDto` apparaît ici comme sous-contrat Transport seulement (`configured_networks`, rôles compatibles, diagnostic sûr, readiness) ; `pre.007` l'étendra avec commitments, scopes et bornes après stabilisation du choix HTTP de `pre.006`. Une composition Transport avec plusieurs clusters HTTP actifs ou sans rôle compatible reste non-ready. Une erreur de résolution/construction Transport est conservée comme diagnostic sûr afin que le shell puisse rester ouvrable. Le replay opérateur de `pre.004` est acquis : audits, `cargo check --workspace`, Clippy, toutes les suites ciblées Backfill Desk et les deux arbres de dépendances passent. Le lancement `cargo tauri dev` confirme également le profil Logging `supertrace`, le profil Transport `devnet_public`, un réseau HTTP unique, un rôle compatible et `transport_ready = true`; les clics de tabs frontend apparaissent bien au niveau `DEBUG`. ### pre.005 — Store readiness Ouvrir uniquement `ksp-store-lib::Store` depuis le profil Store sélectionné par le composite. La Desk résout d'abord les settings Store, dérive leur réseau logique et exige un unique réseau Transport identique **avant** `Store::open`; aucun URI, backend physique ni détail de pool n'est projeté vers l'IPC. Après ouverture, un unique `Store::health()` borné produit `store_ready`, tandis que `network_coherent` et `composition_ready` rendent la chaîne Transport + Store explicite. Une erreur Config, ouverture ou health conserve le shell utilisable avec un diagnostic sûr, sans inventer de readiness. Le `Store` ouvert reste détenu côté Rust et doit être fermé explicitement via `Store::close()` lors d'une demande de fermeture de la fenêtre principale. La Desk intercepte cette fermeture une seule fois, empêche la destruction immédiate, lance le close borné puis quitte l'application. Aucun `BackfillRequest`, `BackfillJobRuntime`, accès backend PostgreSQL direct ou capability Tauri supplémentaire n'est admis dans cette tranche. ### pre.006 — Mainnet par défaut + sélection route HTTP Faire de `mainnet` le profil composite par défaut, conserver `supertrace` pendant le développement et ajouter un profil `mainnet_backfill_pool` HTTP-only. Exposer dans `backfill_options()` des routes sûres dérivées des rôles Transport : `backfill_pool` pour Solana Public + PublicNode, `backfill_solana_public` et `backfill_publicnode` pour un ciblage explicite. Le frontend rend ce choix avant Start et trace les changements ; aucune URL/clé ne traverse IPC et `ksp-job-backfill-lib` n'est pas encore ouvert par la Desk. ### pre.007 — DTO/request mapping Ajouter `ksp-job-api` et `ksp-job-backfill-lib` comme dépendances backend directes de la Desk, sans ouvrir encore `BackfillJobRuntime`. Les DTO TS-RS app-owned exposent les quatre scopes HTTP existants, les commitments, `min_context_slot` sous forme de texte décimal, les bornes publiques Job et des defaults applicatifs bornés (`page_size=100`, `max_pages=10`, `max_candidates=1000`, `hydration_concurrency=4`). Le request DTO ne contient ni réseau, ni provider, ni endpoint, ni URL, ni JobId. Le mapping Rust dérive le réseau du Store configuré, revalide `http_role` contre `BackfillDeskOptionsDto::http_routes`, parse adresse/signatures et délègue l'autorité finale des bornes/cohérences à `BackfillRequest::new`. Une commande `backfill_validate_request` utilise exactement ce mapping pour produire une projection sûre réduite à présence/comptage et paramètres non sensibles ; elle ne lance aucun Job. Le frontend matérialise le formulaire de campagne et le bouton **Valider la requête** ; adresse, ancre, signatures explicites et `min_context_slot` ne sont jamais journalisés. ### pre.008 — runtime + Start Ouvrir le Start réel en réutilisant strictement le mapping `pre.007` : JobId généré côté backend, état single-active-run, construction `BackfillJobRuntime`, handle installé atomiquement avant tout spawn, puis exécution non bloquante sur le pool HTTP courant et le Store déjà ouvert. Le Store est temporairement prêté au run puis remis dans le slot applicatif après terminal ; aucune dépendance backend Store supplémentaire n'est ouverte. La réponse Start reste minimale (`job_id`, état initial) et aucun snapshot détaillé n'est encore projeté. La fermeture pendant un run reste une limite transitoire jusqu'au couloir Cancel/races de `pre.010`. ### pre.009 — monitoring latest-value Bridge direct de `BackfillJobHandle::snapshots()` vers un DTO applicatif sûr et complet, sans parser les logs. Le slot single-run conserve la source latest-value pendant le run et retient le dernier terminal après libération afin que `backfill_status` puisse resynchroniser l'UI même si un événement a été manqué. Le backend émet `ksp-backfill-status` à partir de `JobSnapshotSource::wait_for_change`; cette primitive coalesce nativement les mises à jour intermédiaires et transporte toujours la dernière valeur complète. Le DTO expose JobId/kind, sequence décimale, lifecycle/completion, phase/scope/boundary, compteurs candidates/entities/observations/anomalies, maximum in-flight, `checkpoint_present`, `contiguous_completed` et code d'échec stable. Il n'expose ni adresse, signature, endpoint/provider, payload RAW, contexte d'erreur ni checkpoint lui-même. La terminaison réactive le Start frontend ; Cancel reste absent jusqu'à `pre.010`. ### pre.010 — Cancel et races terminales Ajouter `backfill_cancel(job_id)` avec `BackfillCancelResponseDto { accepted, job_id, state }`. Le JobId est celui généré par le backend et projeté par le monitoring ; il cible le slot actif pour empêcher un Cancel IPC retardé d'atteindre un run suivant. Le premier Cancel pré-terminal est accepté, les répétitions sont idempotentes et renvoient `accepted=false`, un terminal gagne sur un Cancel tardif et un JobId périmé est rejeté sans toucher au run courant. Le frontend expose **Annuler** uniquement pour un status actif, trace seulement JobId/état/acceptation et se resynchronise par `backfill_status`. L'état `cancelling` reste l'intention coopérative : le runtime arrête les nouvelles admissions mais draine le travail déjà soumis au Store. À la fermeture de la fenêtre principale, le one-shot shutdown bloque d'abord les nouveaux Starts, demande ensuite best-effort l'annulation du run actif, puis poursuit le shutdown Store/process sans promettre un drain crash-safe. Resume reste hors tranche. ### pre.011 — checkpoint/frontier et Resume Conserver avec le dernier terminal la requête KSP ayant produit le checkpoint, uniquement dans l'état Rust de la Desk. `ksp-job-backfill-lib` ajoute `BackfillRequest::resume_for_job` : l'opération valide le checkpoint contre son JobId et son scope fingerprint d'origine, puis réémet uniquement l'identité Job pour le nouveau lifecycle sans exposer ni modifier cursor/frontier. La commande `backfill_resume()` alloue un nouveau JobId backend, refuse un run actif ou l'absence de checkpoint, revalide la compatibilité réseau/rôle avec la composition courante, reconstruit `BackfillJobRuntime` et réutilise le même chemin de spawn/monitoring que Start. Le frontend expose **Reprendre** seulement sur un terminal avec `checkpoint_present`. Aucun checkpoint, adresse, signature ou paramètre de campagne ne traverse cette commande IPC. ### pre.012 — frontend fonctionnel et polish Formulaire complet, états responsive, instrumentation sûre, summary terminal, erreurs, boutons contextuels, aucun secret/browser storage. L'adresse reste une saisie libre, mais reçoit un autocomplete HTML via `datalist` alimenté depuis le registre canonique `ksp-core-lib::entries()`/`ProgramIdEntry` : aucune copie locale de Program IDs, et une valeur arbitraire valide reste toujours acceptée. Le comportement fonctionnel recherché est celui de l'ancien dataset `ks-program-ids` de kbot3, sans reprise de son code. La tranche matérialise ce contrat via `ProgramIdAutocompleteOptionDto`, ajouté à `BackfillDeskOptionsDto`. Chaque `