Files
2026-09-01 15:14:57 +02:00

11 KiB

Delta 0.3.6-pre.008 — concurrence bornée, frontier contiguë et checkpoint Backfill

Base requise

0.3.6-pre.007 appliquée
workspace.package.version = 0.3.6-pre.7

Le gate opérateur fourni pour cette base confirme :

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 :

BackfillCheckpoint

Il contient uniquement :

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 :

checkpoint.job_id == request.job_id
checkpoint.scope_fingerprint == request.scope_fingerprint

Un checkpoint incompatible est rejeté par :

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 :

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 :

Inserted
AlreadyPresent
SkippedPurged

Les résultats suivants créent un trou et ne sont jamais marqués durables :

Missing
Conflict
erreur Transport
conversion invalide
erreur Store

Une completion hors ordre ne saute jamais un index incomplet. Les tests reproduisent explicitement :

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 :

execute_backfill_discovery
BackfillExecutionBatch

L'implémentation utilise un FuturesUnordered privé et ne garde jamais plus de :

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 :

ERROR_CODE_BACKFILL_EXECUTION_INVALID

Aucun clock/provider type n'entre dans l'API publique.

Dépendances

La crate ajoute comme dépendance normale :

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 :

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

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

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é :

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 :

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

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é

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.