# Delta `0.3.6-pre.009` — runtime Backfill, annulation coopérative et snapshots latest-value ## Base requise ```text 0.3.6-pre.008-fix.001 appliquée workspace.package.version = 0.3.6-pre.8.fix.1 ``` Le gate opérateur fourni pour cette base confirme : ```text cargo fmt --all PASS python3 scripts/audit_rust_workspace_rules.py PASS / clean python3 scripts/audit_markdown_tables.py ... PASS / clean (264 tables / 154 fichiers) cargo check --workspace PASS cargo clippy --workspace --all-targets PASS / aucun warning cargo test -p ksp-job-backfill-lib PASS unitaires 39 PASS dependency_boundary 2 PASS public_api 5 PASS release_completeness 2 PASS ``` Les arbres Cargo ne sont pas rejoués pour le fix `pre.008-fix.001`, qui ne modifie ni dépendance ni feature. La présente tranche modifie en revanche le manifeste de `ksp-job-backfill-lib` en promouvant Tokio en dépendance normale privée ; les arbres Cargo redeviennent donc pertinents pour son gate. ## Objectif Matérialiser exclusivement `pre.009` du plan 027 : fournir le runtime concret du premier Backfill RAW avec annulation coopérative, arbitrage terminal déterministe et diffusion latest-value de snapshots sûrs via le contrat runtime-neutral de `ksp-job-api`. La tranche ne crée pas de scheduler global, ne persiste pas les checkpoints, ne modifie pas Store/Transport/Job API et ne prépare pas encore le hardening externe final de `pre.010`. ## Runtime concret La crate expose désormais : ```text BACKFILL_JOB_KIND_CODE BackfillJobPhase BackfillJobSnapshot BackfillSnapshotSource BackfillJobHandle BackfillJobRuntime ERROR_CODE_BACKFILL_RUNTIME_INVALID ``` `BackfillJobRuntime::new` construit un état initial `Created`, un canal latest-value privé et un canal de réveil d'annulation privé. `handle()` est obtenu avant de déplacer le runtime dans `run`, afin que l'hôte conserve une surface cloneable de contrôle et d'observation pendant l'exécution. Le kind stable est : ```text solana.raw_transaction.backfill ``` Aucun type Tokio n'est réexporté à la crate root. ## Snapshot latest-value `BackfillJobSnapshot` expose uniquement des faits sûrs et bornés : ```text phase scope_kind discovery_boundary candidates_selected candidates_admitted candidates_finished entities_inserted entities_existing entities_purged missing conflicts observations_inserted observations_existing cancelled_candidates holes maximum_in_flight contiguous_completed checkpoint failure_code ``` Il n'expose ni URL, provider, endpoint, payload RAW, transaction encodée, credential ou message humain libre. `BackfillSnapshotSource` implémente le trait `ksp_job_api::JobSnapshotSource` avec un `tokio::sync::watch` entièrement privé. La source est latest-value : un listener lent peut coalescer les transitions intermédiaires et récupère toujours la valeur la plus récente. Les listeners clonés sont indépendants et la valeur terminale reste retenue après fermeture logique du Job. Chaque publication incrémente `JobNotificationSequence`. Une source observant déjà la séquence terminale retourne immédiatement le snapshot terminal retenu. ## Annulation coopérative `BackfillJobHandle::cancel` accepte atomiquement la première annulation tant que la décision terminale n'a pas été prise. L'annulation runtime-neutral reste portée par `JobCancellationToken`; un `watch` privé sert uniquement à réveiller immédiatement les futures Tokio en attente. Les opérations abandonnables pré-Store utilisent une sélection coopérative : ```text pagination getSignaturesForAddress hydratation getTransaction future pré-Store encore non soumise ``` Une annulation déjà demandée retourne immédiatement le code interne stable : ```text job_backfill.cancelled ``` Ce code reste crate-internal et sert à distinguer l'annulation d'une erreur fatale avant projection vers `JobState::Cancelled`. ## Drainage après soumission Store La frontière critique reste celle figée par le plan : une opération peut être abandonnée avant soumission durable, mais une persistance Store déjà soumise n'est jamais annulée aveuglément. L'exécuteur : 1. arrête les nouvelles admissions lorsqu'une annulation est observée ; 2. peut annuler les candidats encore dans une phase pré-Store abandonnable ; 3. laisse les persistances déjà soumises produire un résultat connu ; 4. draine toutes les futures déjà admises ; 5. réconcilie la frontier et le checkpoint avant publication terminale. `BackfillExecutionBatch` expose maintenant les compteurs détaillés nécessaires au snapshot concret : inserted, already-present, purged, missing, conflicts, observations, cancelled, holes et état `was_cancelled`. ## Arbitrage terminal Un état atomique privé sérialise la course entre complétion normale, annulation et échec fatal. Règles : ```text annulation acceptée avant claim terminal normal -> Cancelled claim Completed avant annulation -> annulation tardive rejetée échec fatal avant publication terminale -> Failed, même si annulation était pending ``` La publication latest-value suit uniquement des transitions autorisées entre `Created`, `Running`, `Cancelling`, `Completed`, `Cancelled` et `Failed`. Une publication après état terminal est rejetée par `ERROR_CODE_BACKFILL_RUNTIME_INVALID`. ## Projection des résultats En absence d'échec fatal : - batch complet sans trou -> `Completed(Complete)` ; - batch partiel, notamment `Missing` -> `Completed(Partial)` ; - annulation gagnante après drainage -> `Cancelled` ; - conflit ou erreur Transport/conversion/Store terminale -> `Failed` avec code stable conservé dans le snapshot. Le checkpoint terminal est celui du dernier préfixe contigu durable produit par `pre.008`; l'annulation ne peut pas faire avancer la frontier au-delà d'un trou ou d'un candidat annulé. ## Dépendances La tranche promeut Tokio de dev-only à dépendance normale privée dans `ksp-job-backfill-lib` : ```text tokio = { workspace = true, features = ["macros", "sync"] } ``` L'entrée versionnée existe déjà dans `[workspace.dependencies]`; aucune nouvelle version externe n'est introduite. Les tests conservent `rt-multi-thread` uniquement dans `[dev-dependencies]`. `futures-util` reste la dépendance normale privée introduite par `pre.008`. Aucun type Tokio/Futures n'entre dans l'API publique. ## Tests matérialisés La tranche ajoute **8 tests unitaires**, portant le total à **47 unitaires**. Ils couvrent notamment : - latest-value coalescé pour listener lent et indépendance des listeners clonés ; - rétention du snapshot terminal et rejet d'une annulation tardive ; - arbitrage déterministe annulation contre complétion ; - échec fatal gagnant sur annulation pending avant publication terminale ; - future pré-Store longue abandonnée coopérativement ; - annulation pendant une page de découverte RPC pendante ; - arrêt des admissions et drainage du travail déjà admis ; - forme sûre du snapshot sans provenance Transport ni payload RAW. Une canarie publique supplémentaire porte les canaries d'intégration à **10** : ```text dependency_boundary 2 public_api 6 release_completeness 2 ``` Total matérialisé attendu au gate : ```text 47 unitaires 10 canaries d'intégration ``` ## Fichiers ajoutés ```text crates/ksp-job-backfill-lib/src/runtime.rs crates/ksp-job-backfill-lib/unit_tests/runtime.rs deltas/0.3.6/pre.009.md ``` ## Fichiers modifiés ```text Cargo.toml crates/ksp-job-backfill-lib/Cargo.toml crates/ksp-job-backfill-lib/src/discovery.rs crates/ksp-job-backfill-lib/src/error.rs crates/ksp-job-backfill-lib/src/execution.rs crates/ksp-job-backfill-lib/src/lib.rs crates/ksp-job-backfill-lib/tests/dependency_boundary.rs crates/ksp-job-backfill-lib/tests/public_api.rs crates/ksp-job-backfill-lib/tests/release_completeness.rs crates/ksp-job-backfill-lib/unit_tests/discovery.rs crates/ksp-job-backfill-lib/unit_tests/execution.rs docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md docs/validation/023-V0_3_6_JOB_API_BACKFILL.md ``` Aucune suppression. ## Versions d'en-tête Les versions finales reflètent chaque modification réellement enregistrée pendant la matérialisation. Certains fichiers de tests ont donc avancé de plusieurs unités plutôt que d'être artificiellement ramenés à `N + 1` : ```text Cargo.toml 407 -> 408 crates/ksp-job-backfill-lib/Cargo.toml 3 -> 4 crates/ksp-job-backfill-lib/src/discovery.rs 3 -> 4 crates/ksp-job-backfill-lib/src/error.rs 4 -> 5 crates/ksp-job-backfill-lib/src/execution.rs 1 -> 2 crates/ksp-job-backfill-lib/src/lib.rs 4 -> 5 crates/ksp-job-backfill-lib/tests/dependency_boundary.rs 4 -> 6 crates/ksp-job-backfill-lib/tests/public_api.rs 4 -> 6 crates/ksp-job-backfill-lib/tests/release_completeness.rs 4 -> 7 crates/ksp-job-backfill-lib/unit_tests/discovery.rs 2 -> 5 crates/ksp-job-backfill-lib/unit_tests/execution.rs 1 -> 2 docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md 15 -> 16 docs/validation/023-V0_3_6_JOB_API_BACKFILL.md 15 -> 16 ``` Les deux nouveaux fichiers Rust ont été créés puis révisés pendant la tranche et sont livrés à `version: 2`. Le présent delta commence à `version: 1`. ## Version workspace ```text workspace.package.version = 0.3.6-pre.9 delivery = 0.3.6-pre.009 ``` ## Validations exécutées dans l'environnement d'assemblage ```text python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.6 ``` Les audits statiques sont exécutés avant et après création du présent delta. L'environnement d'assemblage ne fournit ni Cargo, ni Rustc, ni Rustfmt ; la compilation, Clippy et les tests Rust `pre.009` restent donc à exécuter par l'opérateur. ## Gate opérateur demandé Le manifeste normal de `ksp-job-backfill-lib` change dans cette tranche, donc les arbres Cargo sont à rejouer : ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.6 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-job-backfill-lib cargo tree -p ksp-job-backfill-lib --edges normal cargo tree -p ksp-job-backfill-lib -e features ``` Attendu : 47 unitaires et 10 canaries d'intégration, sans warning Clippy. Un `cargo clean` n'est pas requis par `cargo tree`; il constitue séparément une raison valable de rejouer les arbres lorsqu'une reconstruction propre est volontairement effectuée.