58 KiB
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 pargetTransaction, conversion RAW déterministe et persistance parksp-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.mdet les règles sousdocs/rules/;docs/architecture/002-LAYERS_AND_DEPENDENCIES.mdà005-PERSISTENCE_ARCHITECTURE.md, puis009-JOB_AND_WORKER_ARCHITECTURE.md;ROADMAP.md,CHANGELOG.mdet les deltas antérieurs ;- surfaces Rust réelles de Core, Transport et Store ;
- documentation RPC officielle Solana pour
getSignaturesForAddressetgetTransaction; - 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 estksp-job-backfill-lib; ksp-onchain-transport-libfournit déjà les deux wrappers typés, le routage, les délais, la limitation et les retries ;- le retour de
getTransactionne transporte pas encore l'identité de l'endpoint réellement retenu, alors que la provenance Store exige au minimum un fournisseur ; ksp-store-libré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,
ForceRehydrateet 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,CancelledouFailed;JobCompletion:CompleteouPartial, 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 primitivesstd.
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, engagementConfirmedouFinalized;- 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_slotoptionnel 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 :
- https://solana.com/docs/rpc/http/getsignaturesforaddress
- https://solana.com/docs/rpc/http/gettransaction
Décisions :
LatestAddressetBeforeAddresssuivent directement l'ordre de page officiel ;AfterAddressutiliseuntilpour 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
BeforeAddressabsente des pages n'est pas une erreur car elle sert de curseur RPC ; - une ancre
AfterAddressnon 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 = nulldevientMissing, 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 :
getTransactionest demandé enbase64avecmaxSupportedTransactionVersion = 0;- les encodages
jsonParsedet 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
blockTimenégatif, impossible à représenter parRawTimestamp, 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,versionettransactionIndexconservent la distinction champ absent,nullet valeur ;- le slot et le block time sont portés par
RawTransactionet 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
RawTransactionReferencecomposée du réseau et de la signature 64 octets ; - une
RawTransactioncomplète et canonique ; - une
RawAcquisitionProvenanceavec fournisseur, protocole Solana HTTP JSON-RPC, méthodegetTransaction, endpoint sûr, engagement, instant de réception etJobIdcomme session de capture ; - une
RawTransactionObservationdont la clé déterministe est un SHA-256 domain-separated duJobId, 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,AlreadyPresentouSkippedPurged; - observation
Inserted,AlreadyPresentouNotRecorded; - 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
u64mis à 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,PersistingouDraining; - 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,CancelledouFailed.
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 :
CreatedversRunningouCancelledsi l'annulation précède le départ ;RunningversCancelling,CompletedouFailed;CancellingversCancelled,Completedsi la course est déjà entièrement durable, ouFailedsi 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 ;
Debugdes 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-apivers Core uniquement si nécessaire ;ksp-job-backfill-libversksp-job-api, Core, Logging, Onchain Transport etksp-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.
L'audit de pre.006 conclut qu'aucune dépendance Base58 externe n'est nécessaire pour cette frontière étroite : un décodeur privé borné à exactement 64 octets suffit, évite une dépendance publique ou protocolaire supplémentaire et reste couvert par les golden tests. serde_json, déjà centralisé au workspace, devient la seule dépendance normale additionnelle de la crate pour l'écriture canonique et récursive du JSON.
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
nullet 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 : clôturé ; corrigé par pre.005-fix.001, gate opérateur vert.
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 d’inté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 : clôturé ; gate opérateur vert.
Le gate opérateur de pre.005 confirme cargo fmt, les audits Rust/Markdown, cargo check --workspace, les 11 tests unitaires, les 6 canaries d’inté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. Le gate du fix est ensuite intégralement vert : cargo fmt, audits Rust/Markdown, cargo check, Clippy, 11 unitaires, 6 canaries et arbres Cargo passent sur 0.3.6-pre.5.fix.1. Les versions d'en-tête du Cargo.toml racine et du fichier Rust corrigé sont synchronisées dans la base opérateur avant ouverture de pre.006.
pre.006 — Conversion RAW v1 et provenance
Statut : matérialisé ; gate fonctionnel vert avec un warning Clippy.
Budget cible : 15-20 min. Entrée : candidats déterministes et gate pre.005-fix.001 vert. La tranche ajoute une conversion RAW v1 strictement en mémoire : BackfillSignature::to_raw_transaction_signature décode Base58 vers exactement 64 octets sans SDK Solana ni nouvelle dépendance Base58 ; hydrate_backfill_candidate appelle uniquement HttpTransportPool::get_transaction_observed avec base64, engagement explicite et maxSupportedTransactionVersion = 0. Un résultat null devient BackfillHydrationOutcome::Missing(RawTransactionReference) et ne contient structurellement aucune provenance.
Le payload ksp.solana.raw_transaction version 1 conserve la transaction comme tuple [data, "base64"], omet slot et blockTime, préserve les états omitted/null/value de meta, version et transactionIndex, et canonise récursivement les objets JSON par tri lexical des clés avant calcul SHA-256. Les block times négatifs ou hors plage sont rejetés. Une acquisition disponible produit RawTransaction et RawTransactionObservation avec provider, protocole solana.http.json_rpc, méthode getTransaction, endpoint victorieux, engagement, JobId et timestamp de réception. La clé d'observation est domain-separated et dépend du JobId, fingerprint, signature, provider, endpoint, engagement et version du contrat ; changer d'endpoint crée donc une observation distincte sans changer l'identité (network, signature).
Le gate opérateur exécute avec succès 19 unitaires et 7 canaries d'intégration, ainsi que cargo check et les audits. Clippy reste toutefois non warning-free à cause de clippy::large_enum_variant : l'acquisition disponible est beaucoup plus volumineuse que la référence Missing. Aucun appel de persistance Store, checkpoint, concurrence runtime ou snapshot concret n'est ouvert ; ces responsabilités restent aux tranches suivantes.
pre.006-fix.001 — Indirection privée de l'acquisition RAW
Statut : matérialisé ; gate opérateur à rejouer.
Le fix conserve exactement la forme publique BackfillHydrationOutcome::Available(BackfillRawAcquisition) et place uniquement les champs privés transaction + observation derrière une unique Box. Aucun #[allow] n'est utilisé et aucun Box n'apparaît dans l'enum public. Les getters et into_parts restent identiques ; une canarie unitaire verrouille la taille pointeur de BackfillRawAcquisition. La version workspace devient 0.3.6-pre.6.fix.1.
pre.006-fix.002 — Auto-deref idiomatique des accesseurs RAW
Statut : réalisé ; gate opérateur vert.
Le gate opérateur de pre.006-fix.001 confirme les audits, cargo check, les 20 tests unitaires et les 7 canaries, mais Clippy émet encore deux warnings clippy::explicit_auto_deref dans les accesseurs transaction() et observation() de BackfillRawAcquisition. Le correctif supprime uniquement les déréférencements explicites (*self.inner) au profit de l'auto-deref de Rust lors de l'accès aux champs privés. La représentation avec une unique Box, les signatures publiques, la sémantique RAW/provenance et le coût d'allocation restent inchangés. La version workspace devient 0.3.6-pre.6.fix.2. Le gate opérateur de fix.002 est intégralement vert : audits Rust/Markdown, cargo check, Clippy sans warning, 20 unitaires, 7 canaries d'intégration et arbres Cargo passent.
pre.007 — Persistance Store et idempotence
Statut : réalisé ; gate opérateur vert.
Budget cible : 15-20 min. Entrée : RAW v1 figé et gate pre.006-fix.002 vert. La tranche ajoute persist_backfill_hydration et une projection Backfill explicite des outcomes Store sans créer de second contrat de persistance. Une acquisition disponible est consommée par l'unique capacité atomique RawTransactionWrite::persist_raw_transaction_acquisition via ksp-store-lib, toujours en RawTransactionAcquisitionMode::Normal; aucune prélecture Store, écriture d'observation séparée ou ForceRehydrate n'est introduite.
Le réseau du Store est comparé à l'identité (RawNetworkId, signature) avant toute écriture. Missing n'effectue aucune écriture. Les outcomes Store valides sont projetés en dispositions entité/observation distinctes : Inserted/Inserted, AlreadyPresent/Inserted, AlreadyPresent/AlreadyPresent et SkippedPurged/NotRecorded. ERROR_CODE_RAW_CONFLICT devient un outcome Backfill Conflict/NotRecorded, distinct d'un succès idempotent ; les autres erreurs Store restent des erreurs. Les combinaisons impossibles en mode normal, dont Rehydrated, sont rejetées par ERROR_CODE_BACKFILL_PERSISTENCE_INVALID.
La tranche matérialise 29 tests unitaires et 8 canaries d'intégration au total. Les doubles Store restent privés et déterministes ; ils couvrent insert, nouvelle observation sur entité existante, relance idempotente, purge normale, conflit, erreur Store, mismatch réseau et incohérence de référence. Le gate opérateur est intégralement vert : audits Rust/Markdown, cargo check, Clippy sans warning, 29 unitaires, 8 canaries et arbres Cargo passent sur 0.3.6-pre.7. La frontière contiguë, la concurrence et le checkpoint restent fermés jusqu'à pre.008.
pre.008 — Concurrence, frontier et checkpoint
Statut : clôturé ; corrigé par pre.008-fix.001, gate opérateur du fix vert.
Budget cible : 15-20 min. Entrée : résultats Store typés et gate pre.007 vert. La tranche ajoute BackfillCheckpoint, BackfillExecutionBatch et execute_backfill_discovery. Le checkpoint est opaque, caller-owned et validé contre JobId + fingerprint de scope ; aucune table ou persistance durable de Job n'est créée. BackfillRequest::with_checkpoint reste additif afin de préserver les constructeurs existants.
L'exécution utilise un FuturesUnordered privé et maintient au plus hydration_concurrency candidats actifs, sans tokio::spawn, temporisation Job, retry Job ni file non bornée. Les résultats Store durables Inserted, AlreadyPresent et SkippedPurged avancent une bitmap bornée de complétion ; Missing, conflit ou erreur laissent un trou. Un conflit ou toute erreur Transport/conversion/Store arrête immédiatement les nouvelles admissions, puis le batch draine les futures déjà admises avant de publier son checkpoint.
La reprise suit les quatre sémantiques figées : Latest repart de la vue courante et s'appuie sur l'idempotence Store ; Before reprend via le dernier curseur contigu et cumule le préfixe durable ; After et Explicit rejouent le scope et sautent uniquement le préfixe validé. Si une découverte After finit sur AfterAnchorNotReached, aucun progrès nouveau de checkpoint n'est publié ; le checkpoint précédent est conservé s'il existe. La tranche matérialise 39 tests unitaires et 9 canaries d'intégration. L'annulation coopérative, le snapshot latest-value complet et les courses terminales restent fermés jusqu'à pre.009.
pre.008-fix.001 — Masquage du helper request dans la canarie checkpoint
Statut : clôturé ; gate opérateur vert.
Le gate opérateur de pre.008 confirme cargo fmt, les audits Rust/Markdown et cargo check --workspace, puis la compilation des targets de test échoue en E0618/E0282 : dans pre_008_checkpoint_is_bound_to_job_and_scope_fingerprint, la variable locale request masque le helper homonyme avant la construction du second scope. Le fix renomme uniquement cette liaison locale en primary_request et synchronise ses usages ; aucun contrat, test, comportement runtime, checkpoint, dépendance ou feature ne change. Comme un fichier Rust est corrigé, la version workspace devient 0.3.6-pre.8.fix.1. Le gate réduit du fix est ensuite intégralement vert : audits, cargo check, Clippy sans warning, 39 unitaires et 9 canaries d'intégration passent. Les arbres Cargo de pre.008 avaient déjà été inspectés puisque cette tranche ajoutait futures-util; ils n'avaient pas à être rejoués pour ce fix sans changement de graphe.
pre.009 — Annulation et snapshots concrets
Statut : matérialisé ; gate fonctionnel vert mais qualité Rust bloquée, corrigée par pre.009-fix.001.
Budget cible : 15-20 min. Entrée : frontière prouvée et gate pre.008-fix.001 vert. La tranche matérialise BackfillJobRuntime, BackfillJobHandle, BackfillJobSnapshot et BackfillSnapshotSource comme runtime concret du vertical RAW transaction. ksp-job-api reste inchangée, passive et runtime-neutral ; Tokio devient uniquement un détail d'implémentation normal de ksp-job-backfill-lib (macros + sync) et aucun type Tokio n'est exposé dans la racine publique. Le canal concret est un watch latest-value O(1) : les progressions intermédiaires peuvent être coalescées, chaque listener possède son receiver cloné, et la dernière valeur terminale reste lisible tant que le handle/source existe.
L'annulation réutilise JobCancellationToken et ajoute un signal réveillable privé. Les attentes de découverte et d'hydratation pré-Store sont abandonnables ; une vérification supplémentaire intervient après hydratation et avant soumission Store. Dès que persist_backfill_hydration est appelée, l'écriture atomique n'est plus sélectionnée contre l'annulation : toute opération Store déjà soumise est drainée jusqu'à un résultat connu. L'exécuteur arrête ensuite les nouvelles admissions, draine l'in-flight et publie frontier/checkpoint sûrs. Une arbitration atomique sépare ACTIVE, demande d'annulation et états terminaux : une annulation acceptée avant la décision terminale gagne sur une complétion normale, une complétion déjà réclamée refuse l'annulation tardive, et une erreur fatale peut remplacer une annulation pendante afin de ne pas masquer un échec Transport/Store.
Le snapshot complet reste sûr : phase, catégorie de scope, borne de découverte, compteurs d'admission/fin, dispositions entité/observation, missing/conflits/annulations/trous, maximum in-flight, préfixe contigu, checkpoint et éventuel code d'erreur stable ; aucune URL, endpoint/provider, payload RAW ou message fournisseur n'y entre. La tranche matérialise 47 tests unitaires et 10 canaries d'intégration au total, dont listener lent/multi-listeners, rétention terminale, courses terminales, attente pré-Store longue, attente de page RPC pendante et drainage après annulation. Le gate opérateur exécute ces 47 + 10 tests avec succès, mais Clippy révèle deux réexports pub(crate) non consommés via la façade crate-root et une canarie générique à type non utilisé ; l'audit manuel montre en outre que plusieurs usages intra-module des items partagés pub/pub(crate) de la nouvelle crate contournent crate::, en contradiction avec RUST-IMPORT-009, RUST-IMPORT-012 et RUST-API-007. pre.010 reste fermé jusqu'au gate vert du fix.
pre.009-fix.001 — Normalisation crate-root des items partagés et visibilité runtime
Statut : matérialisé ; gate opérateur du fix à rejouer.
Le fix réconcilie l'intégralité de ksp-job-backfill-lib avec les règles de visibilité/import applicables. Tout item public ou crate-wide réexporté au crate-root est désormais référencé via crate::Item, y compris depuis son module propriétaire. BackfillRuntimeControl et TerminalClaim, utilisés uniquement par runtime.rs et ses unit tests attachés, redeviennent strictement privés conformément à RUST-API-007; les unit tests du module runtime y accèdent via super::Item. Les tests discovery/execution ne dépendent plus de cette visibilité artificielle et déclenchent l'annulation avec JobCancellationToken + canal watch directement. La canarie publique utilise désormais son paramètre générique via PhantomData, supprimant clippy::extra_unused_type_parameters. Aucun contrat externe, comportement runtime, dépendance ou feature ne change.
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-liben 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 clippyetcargo testsont verts dans un environnement Rust disponible ; - les documents d'architecture utilisent
ksp-job-backfill-libet 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.