Files
khadhroony-solana-project/docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md

47 KiB
Raw Blame History

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<S>, 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<Store>. 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<S>, 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<T> 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 : réalisé ; corrigé par pre.005-fix.001, gate opérateur du fix à 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. Le gate opérateur exécute avec succès les 11 tests unitaires et les 6 canaries dintégration, ainsi que cargo check; Clippy --all-targets révèle toutefois un unique écart de style implicit_return dans une closure privée de discover_older, corrigé par pre.005-fix.001.

pre.005-fix.001 — Return explicite dans la closure du curseur Before

Statut : matérialisé ; gate opérateur à rejouer.

Le gate opérateur de pre.005 confirme cargo fmt, les audits Rust/Markdown, cargo check --workspace, les 11 tests unitaires, les 6 canaries dintégration et les arbres Cargo. cargo clippy --workspace --all-targets échoue uniquement sur clippy::implicit_return à la construction optionnelle du curseur before dans discover_older. Le fix remplace l'expression implicite de la closure par return value.as_str().to_owned() sans modifier la valeur produite, les branches de scope, la pagination ou le contrat public. Comme un fichier Rust est corrigé, la version workspace devient 0.3.6-pre.5.fix.1.

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.