Files
khadhroony-solana-project/deltas/0.3.6/pre.009.md
2026-09-01 16:04:58 +02:00

273 lines
11 KiB
Markdown

<!-- file: deltas/0.3.6/pre.009.md -->
<!-- version: 1 -->
# 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<bool>` 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.