# Plan v0.3.6 — Job API et premier backfill RAW ## 1. But de la version La v0.3.6 ouvre la famille des Jobs avec deux crates et un premier vertical réel : - `ksp-job-api`, contrat public passif et runtime-neutral de cycle de vie, annulation et observation latest-value ; - `ksp-job-backfill-lib`, orchestration concrète d'un backfill historique borné ; - découverte par `getSignaturesForAddress`, hydratation par `getTransaction`, conversion RAW déterministe et persistance par `ksp-store-lib` ; - preuve de progression, annulation coopérative, reprise par frontière contiguë et idempotence logique. Cette version ne crée ni Worker API, ni application, ni scheduler général, ni pipeline RAW partagé prématuré. ## 2. Audit préalable et hiérarchie des sources L'audit `pre.001` a été conduit avant toute création de crate. Les archives source et de référence ont été testées intégralement, puis extraites dans des arbres séparés. Les règles canoniques du dépôt priment sur les chemins abrégés du prompt : les fichiers effectivement présents sous `docs/rules/` sont les autorités applicables. Sources lues : - `RULES.md`, `docs/000-README.md` et les règles sous `docs/rules/` ; - `docs/architecture/002-LAYERS_AND_DEPENDENCIES.md` à `005-PERSISTENCE_ARCHITECTURE.md`, puis `009-JOB_AND_WORKER_ARCHITECTURE.md` ; - `ROADMAP.md`, `CHANGELOG.md` et les deltas antérieurs ; - surfaces Rust réelles de Core, Transport et Store ; - documentation RPC officielle Solana pour `getSignaturesForAddress` et `getTransaction` ; - kbot3 v0.5.3-pre.005-fix010, uniquement comme inventaire fonctionnel et corpus de scénarios. Constats structurants : - le dépôt est stable en v0.3.5 et ne contient encore aucune crate Job ; - les documents historiques emploient encore parfois `ksp-job-backfill`, alors que le nom canonique fixé par le prompt est `ksp-job-backfill-lib` ; - `ksp-onchain-transport-lib` fournit déjà les deux wrappers typés, le routage, les délais, la limitation et les retries ; - le retour de `getTransaction` ne transporte pas encore l'identité de l'endpoint réellement retenu, alors que la provenance Store exige au minimum un fournisseur ; - `ksp-store-lib` réexporte le contrat Store, fournit l'écriture atomique transaction plus observation et doit rester l'unique accès Job au Store ; - aucune règle n'autorise une réutilisation de code kbot3. Aucun fichier, extrait ou algorithme n'est copié depuis cette archive. ## 3. Périmètre retenu et non-objectifs Dans le périmètre : - identifiants et états de Job ; - source d'observation multi-listeners à valeur la plus récente ; - token d'annulation indépendant de Tokio ; - quatre portées de backfill : dernière fenêtre d'une adresse, avant une ancre, après une ancre et signatures explicites ; - pagination bornée, déduplication stable, hydratation bornée et persistance RAW ; - résultat observable, compteurs structurés, checkpoint et frontière contiguë ; - tests unitaires, dépendances interdites, canaries externes et intégration déterministe. Hors périmètre : - `ksp-worker-api`, application desktop, CLI, configuration utilisateur et ordonnanceur ; - décodage métier des transactions, normalisation SPL ou indexation sémantique ; - journal persistant des tentatives RPC manquantes ou échouées ; - callbacks producteurs, file de notifications non bornée et historique public complet ; - retry, pacing, sélection d'endpoint ou requête HTTP parallèles au Transport ; - backend Store direct, `ForceRehydrate` et prélecture de présence avant hydratation ; - extraction d'une crate pipeline partagée avant l'arrivée d'un second hôte réel. ## 4. Matrice de décision kbot3 Le tableau décrit des comportements observés ; il ne constitue pas une filiation de code. | Fonction historique | Preuve fichier, test ou run | Sémantique réelle | Statut KSP | Owner KSP cible | Risque ou différence actuelle | Préversion cible | |------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|------------|----------------------------------------------|----------------------------------------------------------------------------|----------------------| | Sources latest, before et after | `ks-pipeline/src/backfill.rs`, `collect_before_candidates`, `collect_after_candidates` | Pagination d'adresse, ancre exclusive et fenêtre plus récente proche | REPRENDRE | Backfill avec wrappers Transport | L'ordre after doit être figé et l'ancre non atteinte observable | `pre.005`, `pre.008` | | Signatures explicites | `explicit_candidates_are_deduplicated_in_input_order` | Première occurrence conservée et ordre d'entrée stable | REPRENDRE | Backfill | Validation Base58 et limite absentes du type KSP actuel | `pre.005` | | Déduplication inter-pages | `nearest_newer_candidates_deduplicate_page_entries` | Un candidat identique n'est admis qu'une fois | REPRENDRE | Backfill | Doit couvrir toutes les directions, pas seulement after | `pre.005` | | Fenêtre after la plus proche | `nearest_newer_candidates_keep_only_entries_closest_to_anchor` | Retient les N signatures plus récentes les plus proches de l'ancre | REPRENDRE | Backfill | Nécessite une preuve officielle et un ordre KSP déterministe | `pre.005` | | Frontière contiguë | `completion_frontier_advances_only_across_contiguous_results` | Une fin hors ordre ne saute pas un candidat incomplet | REPRENDRE | Backfill | Missing et erreurs ne doivent pas être marqués durables | `pre.008` | | Reprise before | `cancelled_before_resume_uses_last_contiguous_candidate`, `cancelled_before_resume_keeps_anchor_when_nothing_completed` | Curseur repris au dernier préfixe contigu ou à l'ancre initiale | REPRENDRE | Backfill et hôte pour garde durable | Le checkpoint doit être lié au JobId et au scope | `pre.008` | | Reprise latest | `cancelled_latest_scan_without_completed_candidate_restarts_from_latest` | Une vue latest vide de complétion repart sans curseur | REPRENDRE | Backfill | Le replay doit dépendre de l'idempotence Store | `pre.008` | | Annulation d'attente et RPC long | `cancellable_retry_wait_returns_immediately_when_already_cancelled`, `long_running_rpc_future_is_cancelled_cooperatively` | Une sélection coopérative abandonne le futur non durable | REPRENDRE | Job API pour token, Backfill pour runtime | Un commit Store soumis ne peut pas être abandonné aveuglément | `pre.002`, `pre.009` | | Taxonomie Program, Token ou Pool | `BackfillAddressKind`, `filter_code_distinguishes_target_and_direction` | Étiquette UI intégrée au filtre historique | REDESSINER | Application v0.3.7 | Une adresse Core ne porte pas ce domaine de présentation | v0.3.7 | | Callback `BackfillObserver` | `ks-pipeline/src/backfill.rs`, `DemoBackfillObserver` dans l'application | Le producteur appelle directement un consumer et émet un message | REDESSINER | Job API et snapshot concret Backfill | Blocage, panic consumer et chaînes humaines possibles | `pre.003`, `pre.009` | | Compteurs `existing_skipped`, `canonical_skipped`, `missing`, `failed` | `BackfillSummary` et `summary_payload` | Plusieurs résultats Store ou RPC sont agrégés avec ambiguïtés | REDESSINER | Backfill snapshot | KSP doit distinguer entité, observation, purge, missing et conflit | `pre.007`, `pre.009` | | Prélecture Store avant hydratation | `hydrate_candidate`, branche `existing_skipped` | Une signature connue saute l'acquisition et l'observation | REJETER | Aucun | Course TOCTOU, purge et acquisition nouvelle perdues | Aucun | | Retry, pause et parsing texte | `retry_delay_ms`, `wait_with_cancellation`, branches HTTP 429 et timeout | Le Job rythme et retente selon des chaînes d'erreur | REJETER | Transport exclusivement | Amplification des retries et contrat d'erreur instable | Aucun | | Persistance missing ou failed | `persist_missing_observation`, `persist_failed_observation` | Une tentative sans transaction canonique devient observation durable | REPORTER | Futur contrat Store de journal d'acquisition | `RawTransactionObservation` KSP exige aujourd'hui une transaction complète | Version ultérieure | | Reprise durable multi-processus | Résumé `resume_before_signature` et application demo | Le caller reçoit une ancre, sans contrat de garde durable KSP | REPORTER | Application ou scheduler futur | Aucune propriété de persistance de checkpoint n'existe en v0.3.6 | Version ultérieure | ## 5. Ownership par couche | Sujet | Propriétaire | Contrat | |---------------------------------------|-----------------------------|-----------------------------------------------------| | Identité, état et annulation d'un Job | `ksp-job-api` | Types publics runtime-neutral et sans effet de bord | | Snapshot concret du backfill | `ksp-job-backfill-lib` | Phase, progression, checkpoint et résultat sûrs | | Pagination et hydratation Solana | `ksp-onchain-transport-lib` | Wrappers RPC typés existants | | Routage, pacing, retry et délai | `ksp-onchain-transport-lib` | Aucune boucle concurrente dans Job | | Conversion en RAW canonique | `ksp-job-backfill-lib` | Adaptateur concret du premier vertical | | Idempotence, conflit et purge | `ksp-store-lib` | Écriture atomique en mode `Normal` | | Logs internes | `ksp-logging-lib` | Champs sûrs, pas de secret ni réponse brute | | Configuration et supervision | Application v0.3.7 | Pas de dépendance Config en v0.3.6 | ## 6. Contrat public de `ksp-job-api` La crate reste passive et ne dépend que de `ksp-core-lib` si un type fondamental existant est requis. Elle n'expose ni Tokio, ni Transport, ni Store, ni Logging, ni serde dans sa surface publique. Types prévus : - `JobId`, identifiant borné, validé et fourni par l'hôte afin de rester stable pendant une reprise ; - `JobKindCode`, code stable et borné sans texte humain libre ; - `JobState` : `Created`, `Running`, `Cancelling`, `Completed`, `Cancelled` ou `Failed` ; - `JobCompletion` : `Complete` ou `Partial`, le second signalant des trous attendus comme une transaction RPC absente ; - `JobNotificationSequence`, compteur strictement croissant vérifié ; - `JobNotification`, enveloppe générique contenant l'identité, la séquence, l'état et le snapshot le plus récent ; - `JobSnapshotSource`, trait à type associé fournissant la valeur courante et une attente abstraite d'un changement après une séquence ; - `JobCancellationToken`, cloneable, idempotent et fondé sur des primitives `std`. Le contrat ne contient pas de booléen redondant avec `JobState`, pas de message humain, pas de timestamp mural dont l'ownership serait ambigu et pas de méthode `spawn`. La surface concrète du backfill accepte `BackfillRequest`, `HttpTransportPool` et un `Arc`. Sa construction rend un exécuteur possédé et un `BackfillJobHandle` cloneable ; l'hôte décide où exécuter la future `run`, tandis que le handle fournit annulation et observation. Les ports étroits employés par le moteur restent privés et servent aux doubles déterministes internes ; l'API publique ne duplique pas les contrats Transport ou Store. ## 7. Notifications latest-value multi-listeners Audit des formes candidates avant choix : | Forme | Runtime-neutralité API | Backpressure | Multi-listener | Livraison terminale | Resynchronisation | Testabilité | Réutilisation Worker future | |-----------------------------|--------------------------------------|-----------------------------|---------------------------------|-------------------------------------------|--------------------------------|-------------------------------------|----------------------------------------------| | Polling snapshot par caller | Forte | Naturellement bornée | Oui | Oui si le caller repolle | Snapshot direct | Simple mais latence artificielle | Possible, coûteux si polling serré | | Callback ou sink | Moyenne | Le producer peut bloquer | Composition manuelle | Fragile sur panic ou abandon | Aucune native | Courses et réentrance difficiles | Rejetée | | Watch ou latest-value | Forte avec futur abstrait | O(1), coalescence explicite | Oui | Oui, valeur retenue | Snapshot complet courant | Déterministe avec séquences | Bon motif, contrat Worker distinct plus tard | | Broadcast ou fan-out | Faible si primitive runtime publique | Lag ou pertes par consumer | Oui | Dépend du buffer et de l'abonnement | Besoin d'un snapshot parallèle | Sensible aux buffers | Ne pas imposer avant Worker API | | File bornée | Moyenne | Blocage ou drop à définir | Non sans fan-out supplémentaire | Peut être coincée derrière la progression | Replay limité au buffer | Nombreuses politiques périphériques | Trop lourde pour le besoin actuel | Le choix est watch ou latest-value avec séquence et snapshot complet. Il conserve l'observabilité sans imposer la primitive d'un runtime à `ksp-job-api`. Le modèle public est une valeur la plus récente, non une file d'événements : - le producteur remplace un unique snapshot en mémoire en O(1) ; - chaque listener conserve sa dernière séquence et demande la valeur courante ou attend une séquence supérieure ; - un listener lent peut coalescer des progressions intermédiaires, mais reçoit toujours l'état courant complet ; - plusieurs listeners ne se bloquent pas mutuellement et n'exécutent jamais de code dans le producteur ; - l'état terminal reste lisible tant que le handle partagé existe ; - une séquence manquée provoque une resynchronisation par snapshot complet, pas une erreur de replay ; - l'échec d'un listener et son abandon n'affectent pas le Job ; - aucune donnée brute RPC, URL, secret, signature en masse ou erreur fournisseur libre n'est publique. L'implémentation Tokio de réveil reste privée à `ksp-job-backfill-lib`. Le trait de l'API retourne un futur abstrait appartenant à la crate, sans type Tokio public. ## 8. Requête et portées du backfill `BackfillRequest` exige tous les paramètres opérationnels ; la bibliothèque ne choisit pas silencieusement des valeurs d'application. Champs communs : - `JobId`, réseau Store attendu, rôle HTTP, engagement `Confirmed` ou `Finalized` ; - taille de page de 1 à 1 000 ; - nombre maximal de pages de 1 à 10 000 ; - limite maximale de 10 000 candidats par Job ; - concurrence d'hydratation de 1 à 64 ; - `min_context_slot` optionnel pour les portées adresse ; - scope et checkpoint optionnel validé contre l'identité du scope. Portées : - `LatestAddress` : adresse, sans ancre, retourne les signatures les plus récentes dans l'ordre RPC ; - `BeforeAddress` : adresse et ancre exclusive, remonte vers les signatures plus anciennes ; - `AfterAddress` : adresse et ancre exclusive, collecte la fenêtre plus récente la plus proche de l'ancre ; - `ExplicitSignatures` : liste non vide de signatures validées et bornées. Le fingerprint de scope couvre réseau, adresse ou digest de liste, direction, ancre, engagement et limites sémantiques. Une reprise avec un checkpoint d'un autre scope est rejetée à l'admission. ## 9. Sémantique RPC officielle figée | Appel | Configuration retenue | Sémantique consommée | |---------------------------|----------------------------------------------------------|--------------------------------------------------------------------------------------| | `getSignaturesForAddress` | engagement, `before`, `until`, `limit`, `minContextSlot` | Résultats du plus récent au plus ancien ; ancres exclusives | | `getTransaction` | engagement, encodage `base64`, version maximale `0` | Transaction confirmée ou `null`, avec slot, block time, transaction, meta et version | Références officielles : - - Décisions : - `LatestAddress` et `BeforeAddress` suivent directement l'ordre de page officiel ; - `AfterAddress` utilise `until` pour rechercher l'ancre, conserve les candidats strictement plus récents et sélectionne les plus proches de l'ancre dans un ordre historique déterministe ; - une ancre `BeforeAddress` absente des pages n'est pas une erreur car elle sert de curseur RPC ; - une ancre `AfterAddress` non atteinte dans la borne de pages est un résultat partiel observable et ne produit aucun checkpoint au-delà du trou ; - les doublons inter-pages et intra-requête sont retirés à première occurrence sans réordonner arbitrairement ; - `getTransaction = null` devient `Missing`, sans écriture Store et sans fabrication de provenance. ## 10. Format RAW v1 et conversion Le premier format concret est `ksp.solana.raw_transaction`, version `1`. Acquisition : - `getTransaction` est demandé en `base64` avec `maxSupportedTransactionVersion = 0` ; - les encodages `jsonParsed` et toute structure interprétée par un fournisseur sont refusés ; - la signature Base58 est décodée vers exactement 64 octets sans introduire le SDK Solana complet ; - un `blockTime` négatif, impossible à représenter par `RawTimestamp`, est une erreur de conversion terminale et n'est jamais supprimé silencieusement. Payload canonique : - UTF-8 JSON compact produit par un sérialiseur déterministe appartenant au backfill ; - jeu et ordre de clés figés par tests ; - transaction binaire conservée comme tuple base64 plus marqueur d'encodage ; - `meta`, `version` et `transactionIndex` conservent la distinction champ absent, `null` et valeur ; - le slot et le block time sont portés par `RawTransaction` et ne sont pas dupliqués dans les bytes ; - hash SHA-256 et taille sont calculés sur les bytes canoniques exacts avant construction de `RawPayload`. Toute évolution de version de transaction supportée ou du format impose une nouvelle version de payload et des canaries de compatibilité. ## 11. Extension Transport minimale pour la provenance Le wrapper typé actuel perd l'identité de l'endpoint finalement utilisé après routage ou retry. La v0.3.6 ajoute une voie additive observée pour `getTransaction` : - elle réutilise exactement la même admission, la même sélection, les mêmes retries et le même décodage ; - elle retourne la valeur typée accompagnée d'un identifiant sûr de fournisseur et d'endpoint effectivement victorieux ; - elle n'expose ni URL, ni en-tête, ni body brut ; - l'API existante reste compatible et peut continuer à ne retourner que la valeur ; - les tests prouvent qu'un reroutage rapporte l'endpoint final, pas le candidat initial. Cette extension ne crée pas un second client RPC et ne déplace aucune politique vers Job. ## 12. Provenance, observation et identité Chaque transaction disponible produit : - une `RawTransactionReference` composée du réseau et de la signature 64 octets ; - une `RawTransaction` complète et canonique ; - une `RawAcquisitionProvenance` avec fournisseur, protocole Solana HTTP JSON-RPC, méthode `getTransaction`, endpoint sûr, engagement, instant de réception et `JobId` comme session de capture ; - une `RawTransactionObservation` dont la clé déterministe est un SHA-256 domain-separated du `JobId`, du fingerprint de scope, de la signature, du fournisseur, de l'endpoint, de l'engagement et de la version du contrat d'acquisition. L'identité transactionnelle logique est donc strictement `(RawNetworkId, signature)` : `mainnet`, `devnet`, `testnet`, `localnet`, `synthetic` ou tout autre réseau logique validé constituent des namespaces distincts. Le rôle Transport, le provider, l'endpoint et le protocole HTTP/WS/gRPC n'entrent jamais dans cette identité. Plusieurs acquisitions du même réseau et de la même signature convergent vers la même transaction canonique, quelle que soit leur voie d'acquisition ; elles peuvent en revanche produire des observations de provenance distinctes. Le backend PostgreSQL actuel lie un Store physique à exactement un `RawNetworkId` via `ksp_store_identity`. Son index physique peut donc rester local au namespace réseau. Tout backend futur capable d'héberger plusieurs réseaux dans un même namespace physique doit inclure le réseau dans sa clé, sa partition ou un mécanisme équivalent garantissant la même identité logique `(network, signature)`. Le fingerprint de scope inclut le réseau et les paramètres sémantiques, mais exclut rôle HTTP, provider, endpoint et protocole. Une reprise du même Job par le même endpoint retrouve donc l'observation. Un autre endpoint constitue une nouvelle acquisition légitime. Une différence de payload canonique pour la même référence reste un conflit Store, jamais un skip. ## 13. Persistance et idempotence Le Job appelle uniquement `ksp-store-lib::persist_raw_transaction_acquisition` en mode `Normal`. Sont distingués dans les compteurs : - entité `Inserted`, `AlreadyPresent` ou `SkippedPurged` ; - observation `Inserted`, `AlreadyPresent` ou `NotRecorded` ; - transaction RPC `Missing` ; - conflit, conversion invalide, erreur Transport et erreur Store. Il n'existe pas de prélecture `has` avant hydratation. Une relance avec le même `JobId` et le même scope est logiquement idempotente : l'entité n'est pas dupliquée, l'observation identique est déjà présente et les différences réelles restent visibles. Les tentatives sans transaction canonique restent dans le résultat runtime. Leur journalisation persistante est reportée à un contrat Store ultérieur explicite. ## 14. Ordonnancement, bornes et backpressure Le Job sépare trois étapes : découverte, hydratation, persistance. - découverte paginée séquentielle afin de préserver les ancres et l'ordre ; - collection bornée à 10 000 candidats et déduplication stable ; - au plus 64 hydratations en vol, limitées par la requête ; - une persistance par candidat hydraté, sans file non bornée ; - admissions arrêtées dès annulation ou erreur fatale ; - fin des opérations Store déjà soumises avant publication de l'état terminal afin de ne jamais ignorer un commit possible ; - compteurs `u64` mis à jour par opérations vérifiées, un overflow devenant une violation d'invariant sûre et non un panic. Le débit externe reste gouverné par le pool Transport. Ajouter une temporisation Job serait une amplification de politique interdite. ## 15. Frontière contiguë et checkpoint Chaque candidat reçoit un index stable après déduplication. Le checkpoint ne progresse que sur le préfixe contigu des résultats durables suivants : - entité insérée ; - entité déjà présente avec contenu identique ; - entité purgée et correctement sautée par la politique Store normale. `Missing`, conflit, erreur de conversion, erreur Transport, erreur Store ou annulation créent un trou. Des candidats ultérieurs peuvent terminer, mais la frontière ne saute jamais ce trou. Le checkpoint contient `JobId`, fingerprint de scope, index de frontière et curseur RPC nécessaire. Il ne contient pas de payload ni de secret. Pour `LatestAddress`, une reprise repart volontairement de la vue latest et s'appuie sur l'idempotence Store ; pour `BeforeAddress`, elle reprend au dernier curseur contigu ; pour `AfterAddress` et `ExplicitSignatures`, elle rejoue le scope borné et ignore uniquement le préfixe validé par le checkpoint. ## 16. Annulation et courses terminales Annuler est idempotent et fait passer `Running` vers `Cancelling` dès que le producteur observe le token. | Situation | Décision | |---------------------------------------------------|------------------------------------------------------------------------------------| | Attente ou page RPC en cours | Le futur Transport est abandonné par sélection coopérative | | Hydratation RPC longue | Le futur est abandonné ; aucune persistance n'est fabriquée | | Candidat pas encore admis | Il ne démarre pas | | Persistance Store déjà soumise | Elle est attendue et son résultat est compté | | Dernier travail finit avant observation du cancel | `Completed` gagne si tout est durable | | Cancel observé avec travail restant | `Cancelled` gagne après drainage sûr | | Erreur fatale et cancel simultanés | L'erreur déjà observée reste la cause ; le snapshot indique l'arrêt des admissions | Le token ne promet pas l'interruption d'un commit externe. La garantie est l'absence de nouvelle admission et une terminaison avec état connu. ## 17. États, phases, résultats, logging et sécurité `BackfillSnapshot` est complet et borné. Il contient : - phase `Discovering`, `Hydrating`, `Persisting` ou `Draining` ; - scope sûr réduit à son type, son fingerprint et ses bornes ; - pages lues, doublons supprimés, candidats sélectionnés, démarrés et terminés ; - hydratés, manquants, insérés, déjà présents et purgés ; - observations insérées, déjà présentes et non enregistrées ; - conflits et échecs classés par code stable ; - concurrence en vol et checkpoint courant ; - résultat final `Complete`, `Partial`, `Cancelled` ou `Failed`. Une transaction `Missing` produit `Completed` avec résultat `Partial` si aucune erreur fatale ne survient. Un conflit, une conversion invalide ou une erreur d'infrastructure après les politiques Transport arrête les admissions et produit `Failed`. Transitions autorisées : - `Created` vers `Running` ou `Cancelled` si l'annulation précède le départ ; - `Running` vers `Cancelling`, `Completed` ou `Failed` ; - `Cancelling` vers `Cancelled`, `Completed` si la course est déjà entièrement durable, ou `Failed` si une erreur déjà observée gagne ; - aucun retour vers un état antérieur et une seule publication terminale immuable. Logging et sécurité : - target constant propre à la crate selon les conventions Logging existantes, sans nouveau document `std.jobs.*` ; - champs sûrs : JobId, fingerprint de scope, phase, codes, bornes et compteurs ; - pas d'URL, credential, body RPC, payload RAW, SQL, liste de signatures ou texte fournisseur ; - `Debug` des types publics borné et redacted, champs privés et enums évolutifs `#[non_exhaustive]` lorsque pertinent ; - erreurs publiques structurées par `ErrorCode`, sans état stringly-typed. ## 18. Politique de retries | Opération | Propriétaire du retry | Politique Job | |---------------------------|------------------------------------|---------------------------------------------------------------| | `getSignaturesForAddress` | Transport | Aucun retry supplémentaire | | `getTransaction` | Transport | Aucun retry supplémentaire | | Écriture Store | Store ou backend selon son contrat | Aucun retry aveugle | | Job complet | Hôte futur | Nouvelle exécution explicite avec même JobId ou nouveau JobId | Les erreurs typées finales du Transport deviennent des codes de résultat Job. Les chaînes de fournisseurs ne sont jamais analysées. ## 19. Dépendances et firewalls Graphe prévu : - `ksp-job-api` vers Core uniquement si nécessaire ; - `ksp-job-backfill-lib` vers `ksp-job-api`, Core, Logging, Onchain Transport et `ksp-store-lib` ; - Tokio, futures, SHA-256, JSON canonique et un décodeur Base58 minimal restent des détails privés du backfill ; - aucune dépendance vers `ksp-store-api`, `ksp-store-postgres-lib`, App, Config, Wallet, Program API ou Interface ; - aucun retour inverse de Core, Transport, Store ou Logging vers Job ; - aucun type Tokio, reqwest, deadpool, PostgreSQL ou fournisseur dans l'API publique. Aucun document Config `std.jobs.*` n'est introduit. Les réglages de Job sont fournis explicitement par l'hôte ; la composition Config appartient à l'application v0.3.7. La dépendance Base58 exacte sera ajoutée seulement après audit de version et de features lors de la tranche de conversion. ## 20. Stratégie de tests et canaries Tests de `ksp-job-api` : - validation et bornes des identifiants ; - transitions autorisées et états terminaux immuables ; - annulation cloneable et idempotente ; - séquence strictement monotone, resynchronisation et conservation terminale ; - listener lent, plusieurs listeners et listener abandonné sans blocage producteur. Tests de `ksp-job-backfill-lib` : - validation de chaque scope et de toutes les bornes ; - pagination latest, before et after selon fixtures officielles ; - ancre after non atteinte, doublons inter-pages et ordre déterministe ; - signatures explicites dédupliquées à première occurrence ; - conversion Base58 exacte, RAW v1 déterministe, absent contre `null` et block time négatif ; - provenance de l'endpoint réellement victorieux ; - Store `Inserted`, `AlreadyPresent`, `SkippedPurged`, observation déjà présente, conflit et missing ; - relance idempotente avec même JobId et nouvelle observation avec endpoint différent ; - concurrence bornée, aucune file croissante et front contigu malgré fin désordonnée ; - annulation pendant découverte, hydratation, attente Transport et persistance ; - course annulation contre complétion et checkpoint sans saut de trou. Canaries externes : - Job API depuis une crate externe, sans construction de champs privés ; - Backfill depuis une crate externe avec ses types de production opaques ; doubles des ports privés uniquement dans les tests internes ; - interdiction de dépendance et de type public ; - smoke opt-in Devnet plus PostgreSQL configuré, sans endpoint payant ni secret versionné. ## 21. Sizing recalibré — prereleases souples Chaque tranche technique vise environ 15 à 20 minutes de travail effectif lorsque le sujet s'y prête. Cette durée est une cible de granularité, pas une durée maximale de compilation, diagnostic ou preuve live. Le forecast reste souple : une tranche peut être scindée, fusionnée ou réordonnée par delta si la réalité technique l'exige, sans fusionner les couloirs de fermeture. Un correctif est inséré sous sa prerelease avec un titre `#### pre.NNN-fix.MMM` afin de conserver une hiérarchie éditable. ### `pre.001` — Audit, décisions, plan et validation **Statut : réalisé ; corrigé par `pre.001-fix.001`.** Budget cible : **15-20 min**. L'entrée exigeait la stable v0.3.5 et les deux archives vérifiées. La tranche a produit l'audit KSP, kbot3 et RPC officiel, les décisions d'ownership, le plan, la validation et le bump `0.3.6-pre.1`, sans crate ni code Rust. La sortie est la surface de travail acceptée pour Job API et Backfill. #### `pre.001-fix.001` — Sizing éditable et audit Markdown effectif **Statut : réalisé ; correctif d'outillage et documentaire.** Le sizing tabulaire est remplacé par les présentes sous-sections afin que chaque correctif futur reste sous sa prerelease. Le validateur Markdown reconnaît désormais les séparateurs plausibles contenant des espaces erronés, applique l'alignement source gauche, droit ou centré indiqué par les marqueurs et possède des tests de régression. Les tableaux de `pre.001` sont réellement réalignés. Comme le script d'audit est du code d'outillage consommé par le gate, la version Cargo devient `0.3.6-pre.1.fix.1`. ### `pre.002` — Identité, lifecycle et annulation Job API **Statut : réalisé ; corrigé par `pre.002-fix.001`.** Budget cible : **15-20 min**. Entrée : plan 027 corrigé, gate Markdown fiable et `cargo check --workspace` opérateur vert sur `pre.001-fix.001`. La crate `ksp-job-api` est ajoutée avec `JobId`, `JobKindCode`, `JobCompletion`, `JobState`, `JobLifecycle` et `JobCancellationToken`. Les identités utilisent un alphabet sûr et une borne de 128 octets ; leur valeur est privée et `JobId` est redacted en `Debug`. Le lifecycle possédé n'est pas cloneable, n'autorise que les transitions décidées et conserve les terminaux immuables. Le token partage un `AtomicBool` par `Arc`, rend la première demande observable et reste indépendant de tout runtime. La tranche ajoute dix tests unitaires et onze canaries d'intégration réparties entre API publique, dépendances, complétude et sécurité. Le gate opérateur a confirmé `cargo fmt`, les audits, `cargo check --workspace` et les deux arbres Cargo. Clippy `--all-targets` et `cargo test -p ksp-job-api` ont toutefois révélé que le helper de fixture `lifecycle()` était masqué par une variable locale homonyme dans un test unitaire. Aucune notification, séquence, snapshot, crate Backfill, dépendance Logging/Transport/Store ou surface Worker n'est ouverte. #### `pre.002-fix.001` — Compilation des tests lifecycle **Statut : réalisé ; gate opérateur vert.** Le helper de fixture est renommé `new_lifecycle()` et tous ses appels sont synchronisés afin d'éliminer le masquage lexical à l'origine des erreurs Rust `E0618` et `E0282`. Le correctif ne modifie ni l'API publique, ni les transitions, ni le nombre de tests. Comme un fichier Rust est corrigé, la version workspace devient `0.3.6-pre.2.fix.1`. Le gate opérateur suivant confirme ensuite `cargo fmt`, audits Rust/Markdown, `cargo check`, Clippy, les dix tests unitaires, les onze canaries Job API, `cargo test --workspace` et les deux arbres Cargo Job API verts. ### `pre.003` — Notifications latest-value génériques **Statut : réalisé ; corrigé par `pre.003-fix.001`, gate opérateur du fix vert.** Budget cible : **15-20 min**. Entrée : transitions Job stables et gate `pre.002-fix.001` vert. La tranche ajoute `JobNotificationSequence`, `JobNotification`, `JobSnapshotFuture` et `JobSnapshotSource` sans nouvelle dépendance. La séquence est ordonnée, avance par `checked_add` et échoue explicitement avant tout wrap ; l'enveloppe reste immutable et son `Debug` masque le snapshot générique. Le trait source expose la valeur courante et une attente abstraite d'une valeur plus récente ; le futur public n'expose que `std::future::Future`, `Pin` et `Box`, jamais Tokio. La sémantique est latest-value : le consumer peut perdre des progressions intermédiaires puis se resynchroniser sur un snapshot complet plus récent. Des canaries externes matérialisent deux listeners indépendants reprenant une séquence ancienne et la conservation d'un snapshot terminal par un handle partagé. Elles prouvent le contrat public ; l'implémentation concrète O(1), le réveil runtime et l'isolation du producteur restent à prouver avec `ksp-job-backfill-lib` dans la tranche d'intégration prévue, sans callback de type kbot3. Aucune surface Backfill, Worker, Transport, Store, Logging, serde, Tauri ou Tokio n'entre dans `ksp-job-api`. La version workspace devient `0.3.6-pre.3`. #### `pre.003-fix.001` — Visibilité du constructeur privé dans le helper d'épuisement **Statut : matérialisé ; gate opérateur à rejouer.** Le gate opérateur de `pre.003` confirme `cargo fmt`, les audits Rust/Markdown et `cargo check --workspace`, puis révèle une erreur Rust `E0423` lorsque Clippy `--all-targets` et `cargo test -p ksp-job-api` compilent le helper `#[cfg(test)]` d'épuisement de séquence. Le helper vit dans `notification.rs`, où le champ tuple privé est visible, mais construisait le type via le re-export racine `crate::JobNotificationSequence`; ce chemin expose le type sans rendre son constructeur tuple visible. Le fix conserve le helper privé et utilise directement `JobNotificationSequence(u64::MAX)` dans son module propriétaire. Aucun contrat public, test, dépendance ou comportement de production ne change. Comme un fichier Rust est corrigé, la version workspace devient `0.3.6-pre.3.fix.1`. ### `pre.004` — Provenance Transport observée **Statut : clôturé ; gate opérateur vert.** Budget cible : **15-20 min**. Entrée : besoin de provenance confirmé par le plan et gate `pre.003-fix.001` vert. La tranche ajoute `HttpObservedValue` comme enveloppe typée ne conservant que la valeur, le nom sûr de l'endpoint victorieux et son provider. `HttpTransportPool::get_transaction_observed` reprend exactement les validations et le moteur `execute_standard_rpc` existants ; le moteur commun possède désormais une voie interne observée et l'API historique continue à ne retourner que la valeur. Les tests couvrent le succès direct `null`, la surface publique et surtout un retry `429` entre deux endpoints de même priorité : le résultat observé rapporte le second endpoint/provider qui a réellement produit la réponse, jamais le candidat initial. URL, headers et body HTTP brut restent absents du contrat, et le `Debug` de l'enveloppe ne rend pas la valeur typée. Le gate opérateur est vert : audits, `cargo check`, Clippy, 385 unitaires Transport, 50 canaries publiques, 43 canaries de complétude, doctests et arbres Cargo passent sans nouvelle dépendance/feature Transport. ### `pre.005` — Fondation Backfill et découverte **Statut : matérialisé ; gate opérateur à rejouer.** Budget cible : **15-20 min**. Entrée : Transport observé stable et gate `pre.004` vert. La tranche crée `ksp-job-backfill-lib` avec dépendances directes Job API, Core, Logging, Onchain Transport, Store façade sans feature backend imposée et SHA-256 déjà centralisé au workspace ; Tokio reste uniquement une dev-dependency pour les tests async de cette tranche. La requête impose explicitement `JobId`, `RawNetworkId`, rôle HTTP, engagement `Confirmed|Finalized`, scope, page size `1..=1000`, pages `1..=10000`, candidats `1..=10000`, future concurrence d'hydratation `1..=64` et `min_context_slot` seulement pour les scopes adresse. Les quatre scopes sont matérialisés. Les signatures explicites sont bornées, Base58-shaped et dédupliquées à première occurrence ; le décodage exact vers 64 octets reste volontairement `pre.006`. `BackfillCandidateIdentity` est explicitement `(RawNetworkId, BackfillSignature)` ; rôle/provider/endpoint/protocole sont exclus du fingerprint de scope et ne peuvent donc pas devenir une identité transactionnelle. La découverte réelle appelle uniquement `HttpTransportPool::get_signatures_for_address`, sans client, retry, pacing ou endpoint policy dans Job. Latest/Before paginent par `before`, After conserve la fenêtre plus récente la plus proche de l'ancre via `until`, et les limites de pages produisent un résultat partiel observable plutôt qu'une fausse complétion. Les doubles déterministes restent privés aux tests. La tranche matérialise 11 tests unitaires et 6 canaries d’intégration ; leur exécution Cargo reste une preuve opérateur. Sortie attendue après gate : latest, before, after et explicite, déduplication stable, frontières partielles et firewall de dépendances verts. ### `pre.006` — Conversion RAW v1 et provenance **Statut : planifié.** Budget cible : **15-20 min**. Entrée : candidats déterministes. Implémenter décodage Base58 minimal, payload canonique et provenance. Sortie : golden bytes, hash, absent contre `null` et block time hostile couverts. ### `pre.007` — Persistance Store et idempotence **Statut : planifié.** Budget cible : **15-20 min**. Entrée : RAW v1 figé. Composer l'écriture atomique Store normale et les outcomes distincts. Sortie : insert, already present, purge, observation, missing, conflit et relance idempotente couverts. ### `pre.008` — Concurrence, frontier et checkpoint **Statut : planifié.** Budget cible : **15-20 min**. Entrée : résultats Store typés. Ajouter admissions bornées, réconciliation hors ordre et reprise. Sortie : frontière contiguë, trous, bornes et checkpoints des quatre scopes prouvés. ### `pre.009` — Annulation et snapshots concrets **Statut : planifié.** Budget cible : **15-20 min**. Entrée : frontière prouvée. Intégrer annulation sur les phases critiques et progression Backfill latest-value. Sortie : courses terminales, drainage Store et listener lent couverts. ### `pre.010` — Hardening et canaries externes **Statut : planifié.** Budget cible : **15-20 min**. Entrée : vertical déterministe complet. Fermer scénarios adversariaux, API externe, dépendances et sécurité ; exécuter la preuve live si justifiée et disponible. Sortie : aucun défaut fonctionnel ou firewall ouvert. ### `pre.011` — Gate technique final **Statut : planifié.** Budget cible : **15-20 min**. Entrée : hardening vert. Rejouer le workspace complet, Clippy, tests et cargo trees, plus le smoke opt-in retenu. Sortie : preuve technique finale consignée sans réconciliation documentaire. ### `pre.012` — Réconciliation documentaire finale **Statut : planifié.** Budget cible : **10-15 min**. Entrée : gate technique final vert. Aligner architecture, index, README, USAGE, plan et validation sur la surface prouvée. Sortie : documentation durable cohérente, sans CHANGELOG, ROADMAP ni prompt suivant. ### `pre.013` — Préparation de publication minimale **Statut : planifié.** Budget cible : **10-15 min**. Entrée : réconciliation documentaire validée. Modifier uniquement la mécanique Cargo/delta, `CHANGELOG.md`, `ROADMAP.md` et le prompt v0.3.7. Sortie : archive minimale prête pour le gate de publication. ### `rel.001` — Stabilisation et tag `v0.3.6` **Statut : planifié.** Entrée : gate de publication vert. Synchroniser la version stable, livrer le delta `rel.001`, effectuer les opérations Git séparées et poser le tag stable. Aucun rattrapage fonctionnel ou documentaire n'est admis. Le smoke live reste conditionnel à un PostgreSQL explicitement configuré. Son absence n'annule pas la preuve déterministe et ne doit pas conduire à inventer un résultat. ## 22. Critères de fermeture La v0.3.6 est fermable lorsque : - les deux crates canoniques existent et respectent les firewalls ; - Job API reste passive, runtime-neutral et sans dépendance de domaine ; - l'observation latest-value est non bloquante, multi-listeners et terminalement persistante ; - les quatre scopes sont bornés, ordonnés, dédupliqués et couverts ; - le wrapper Transport observé prouve la provenance réelle sans dupliquer sa politique ; - le format RAW v1 est déterministe et ses canaries couvrent toutes les variantes wire ; - la persistance passe uniquement par `ksp-store-lib` en mode normal ; - insertion, déjà présent, purge, observation, missing et conflit restent distinguables ; - la frontière ne saute aucun trou sous concurrence ou annulation ; - une relance logique est idempotente ; - unitaires, canaries externes, audits de règles, tables, `cargo check`, `cargo clippy` et `cargo test` sont verts dans un environnement Rust disponible ; - les documents d'architecture utilisent `ksp-job-backfill-lib` et ne promettent aucun Worker ou pipeline partagé prématuré ; - la publication stable suit les lanes documentaires et Git séparées de `VERSION_WORKFLOW.md`.