# Delta `0.3.6-pre.008` — concurrence bornée, frontier contiguë et checkpoint Backfill ## Base requise ```text 0.3.6-pre.007 appliquée workspace.package.version = 0.3.6-pre.7 ``` 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 / 152 fichiers) cargo check --workspace PASS cargo clippy --workspace --all-targets PASS / aucun warning cargo test -p ksp-job-backfill-lib PASS unitaires 29 PASS dependency_boundary 2 PASS public_api 4 PASS release_completeness 2 PASS cargo tree -p ksp-job-backfill-lib --edges normal exécuté cargo tree -p ksp-job-backfill-lib -e features exécuté ``` ## Objectif Matérialiser exclusivement `pre.008` du plan 027 : exécuter les candidats découverts avec une concurrence métier bornée, réconcilier les fins hors ordre par une frontier contiguë et produire un checkpoint sûr pour reprise contrôlée. La tranche n'ouvre pas encore l'annulation coopérative, le snapshot latest-value concret, les états terminaux complets ou les courses d'annulation de `pre.009`. ## Checkpoint caller-owned La tranche ajoute le type public opaque : ```text BackfillCheckpoint ``` Il contient uniquement : ```text JobId BackfillScopeFingerprint completed_prefix resume_before privé si scope BeforeAddress ``` Il ne contient ni payload RAW, ni URL, ni provider, ni endpoint, ni credential, ni liste de signatures. `BackfillRequest::with_checkpoint` valide obligatoirement : ```text checkpoint.job_id == request.job_id checkpoint.scope_fingerprint == request.scope_fingerprint ``` Un checkpoint incompatible est rejeté par : ```text ERROR_CODE_BACKFILL_CHECKPOINT_INVALID ``` Aucune table Store, migration ou persistance durable de Job n'est créée. Le checkpoint est caller-owned et qualifie uniquement une reprise contrôlée ; la v0.3.6 ne revendique pas une reprise crash-safe multi-processus. ## Sémantique de reprise par scope ### LatestAddress La reprise repart volontairement sans curseur RPC depuis la vue latest courante. L'idempotence Store absorbe les candidats déjà durables. Le préfixe historique du checkpoint n'est jamais utilisé comme offset de la nouvelle vue latest. ### BeforeAddress Le checkpoint conserve comme curseur exclusif le dernier candidat du préfixe contigu durable. Si aucun candidat nouveau n'est durable, le curseur précédent — ou l'ancre initiale — est conservé. Le compteur `completed_prefix` est cumulatif entre reprises contrôlées. ### AfterAddress Le scope borné est rejoué et seul le préfixe contigu validé est ignoré. Si la découverte s'arrête sur : ```text AfterAnchorNotReached ``` aucun progrès nouveau de checkpoint n'est publié, même si certaines hydrations observées ont fini durablement ; le checkpoint précédent est conservé lorsqu'il existe. ### ExplicitSignatures La liste dédupliquée et fingerprintée est rejouée et seul son préfixe contigu déjà validé est ignoré. ## Frontier contiguë Chaque candidat conserve son index stable de découverte. Une bitmap privée bornée à `max_candidates <= 10_000` suit les résultats durables. La frontier avance uniquement pour : ```text Inserted AlreadyPresent SkippedPurged ``` Les résultats suivants créent un trou et ne sont jamais marqués durables : ```text Missing Conflict erreur Transport conversion invalide erreur Store ``` Une completion hors ordre ne saute jamais un index incomplet. Les tests reproduisent explicitement : ```text index 1 durable -> frontier 0 index 0 durable -> frontier 2 index 3 durable -> frontier 2 index 2 durable -> frontier 4 ``` ## Exécution concurrente bornée La tranche ajoute : ```text execute_backfill_discovery BackfillExecutionBatch ``` L'implémentation utilise un `FuturesUnordered` privé et ne garde jamais plus de : ```text request.hydration_concurrency() ``` futures candidates en vol. Aucun `tokio::spawn`, timer Job, retry Job, pacing Job ou choix d'endpoint n'est introduit. Transport reste propriétaire de sa politique réseau et Store de sa persistance. Le batch expose uniquement des faits bornés utiles à la preuve de frontier : candidats, admis, terminés, durables, trous, maximum en vol, préfixe contigu local, checkpoint et premier code d'échec fatal. ## Missing, conflit et erreurs `Missing` est un trou non fatal : les admissions suivantes continuent, mais la frontier reste bloquée tant que ce trou existe. Un `Conflict` ou toute erreur Transport/conversion/Store finale : 1. devient le premier `failure_code` stable du batch ; 2. arrête immédiatement les nouvelles admissions ; 3. laisse finir toutes les futures déjà admises ; 4. produit ensuite le checkpoint correspondant au dernier préfixe réellement durable. Cette phase ne publie pas encore l'état Job terminal `Failed`/`Partial`; cette projection appartient au snapshot/runtime de `pre.009`. ## Horodatage d'acquisition Le chemin public d'exécution produit le `RawTimestamp` de réception à partir de `SystemTime` immédiatement avant l'hydratation. Une horloge avant epoch ou un timestamp non représentable devient une erreur structurée : ```text ERROR_CODE_BACKFILL_EXECUTION_INVALID ``` Aucun clock/provider type n'entre dans l'API publique. ## Dépendances La crate ajoute comme dépendance normale : ```text futures-util = { workspace = true, features = ["std"] } ``` Cette dépendance existe déjà dans `[workspace.dependencies]`; aucune nouvelle version externe n'est introduite. Tokio reste exclusivement en `dev-dependencies` pour les tests async et aucun type Tokio/Futures n'est exposé par la crate root. ## Tests matérialisés La tranche ajoute **10 tests unitaires**, portant le total à **39 unitaires** : - frontier hors ordre strictement contiguë ; - checkpoint rejeté sur mauvais `JobId` ou fingerprint ; - checkpoint Before cumulatif avec curseur du dernier candidat contigu ; - After sans ancre atteinte n'avance jamais le checkpoint ; - Before reprend au curseur checkpoint plutôt qu'à l'ancre initiale ; - Latest reprend sans curseur depuis la vue courante ; - concurrence observée strictement bornée malgré fins hors ordre ; - Missing non fatal mais bloquant pour la frontier ; - conflit arrêtant les admissions et drainant le travail déjà en vol ; - reprise Explicit sautant uniquement le préfixe checkpointé. Une canarie publique supplémentaire porte les canaries d'intégration à **9** : ```text dependency_boundary 2 public_api 5 release_completeness 2 ``` Les canaries verrouillent également l'inventaire exact des modules, `futures-util` comme détail privé normal, l'absence de Tokio normal, l'absence de retry/pacing/endpoint selection Job et la fermeture des surfaces `BackfillSnapshot`/`BackfillJobHandle` de `pre.009`. ## Fichiers ajoutés ```text crates/ksp-job-backfill-lib/src/checkpoint.rs crates/ksp-job-backfill-lib/src/execution.rs crates/ksp-job-backfill-lib/unit_tests/checkpoint.rs crates/ksp-job-backfill-lib/unit_tests/execution.rs deltas/0.3.6/pre.008.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/lib.rs crates/ksp-job-backfill-lib/src/persistence.rs crates/ksp-job-backfill-lib/src/request.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 docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md docs/validation/023-V0_3_6_JOB_API_BACKFILL.md ``` ## Versions d'en-tête Les fichiers Rust/TOML modifiés incrémentent tous leur version d'en-tête exactement d'une unité : ```text Cargo.toml 405 -> 406 crates/ksp-job-backfill-lib/Cargo.toml 2 -> 3 crates/ksp-job-backfill-lib/src/discovery.rs 2 -> 3 crates/ksp-job-backfill-lib/src/error.rs 3 -> 4 crates/ksp-job-backfill-lib/src/lib.rs 3 -> 4 crates/ksp-job-backfill-lib/src/persistence.rs 1 -> 2 crates/ksp-job-backfill-lib/src/request.rs 2 -> 3 crates/ksp-job-backfill-lib/tests/dependency_boundary.rs 3 -> 4 crates/ksp-job-backfill-lib/tests/public_api.rs 3 -> 4 crates/ksp-job-backfill-lib/tests/release_completeness.rs 3 -> 4 crates/ksp-job-backfill-lib/unit_tests/discovery.rs 1 -> 2 ``` Les quatre nouveaux fichiers Rust commencent à `version: 1`. Le plan 027 et la validation 023 passent de `13` à `14`. Le présent delta commence à `version: 1`. ## Version workspace La tranche modifie du Rust : ```text workspace.package.version = 0.3.6-pre.8 delivery = 0.3.6-pre.008 commit = v0.3.6-pre.008 ``` Aucun tag prerelease. ## Fichiers supprimés Aucun. ## Frontières conservées - aucune persistence durable de checkpoint dans Store ; - aucune table ou migration Job ; - aucun retry/pacing/timer Job ; - aucune sélection provider/endpoint Job ; - aucun `ForceRehydrate` ; - aucun `tokio::spawn` ; - aucun type Tokio/Futures public ; - aucune annulation coopérative de `pre.009` ; - aucun `BackfillSnapshot` latest-value concret ; - aucune application ou Worker ; - aucun changement README/USAGE/CHANGELOG/ROADMAP/prompt. ## 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 ``` L'environnement d'assemblage ne fournit ni `cargo`, ni `rustc`, ni `rustfmt`; aucun résultat Rust de `pre.008` n'est donc revendiqué localement. ## Gate opérateur demandé ```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 ``` Résultat attendu après gate : **39 unitaires + 9 canaries**, sans warning. Un échec reste dans la lane `pre.008-fix.001`; un gate vert ouvre `pre.009`.