# Delta `0.3.6-pre.007` — persistance Store atomique et idempotence Backfill ## Base requise ```text 0.3.6-pre.006-fix.002 appliquée workspace.package.version = 0.3.6-pre.6.fix.2 ``` 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 / 151 fichiers) cargo check --workspace PASS cargo clippy --workspace --all-targets PASS / aucun warning cargo test -p ksp-job-backfill-lib PASS unitaires 20 PASS dependency_boundary 2 PASS public_api 3 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.007` du plan 027 : composer la persistance normale d'un résultat d'hydratation Backfill avec les contrats Store déjà existants, sans prélecture, sans second chemin d'écriture et sans ouvrir la concurrence, la frontier ou le checkpoint de `pre.008`. L'identité canonique reste : ```text RawTransactionReference = (RawNetworkId, RawTransactionSignature) ``` Provider, endpoint et protocole ne participent toujours qu'à la provenance de l'observation. ## Contrat public Backfill La tranche ajoute : ```text persist_backfill_hydration BackfillPersistenceOutcome BackfillEntityPersistence BackfillObservationPersistence ERROR_CODE_BACKFILL_PERSISTENCE_INVALID ``` `BackfillPersistenceOutcome` conserve la référence réseau + signature et projette séparément l'état de l'entité canonique et celui de l'observation. Cette projection évite de réduire un conflit, une purge ou une nouvelle observation à un compteur `skip` ambigu. Dispositions entité : ```text Inserted AlreadyPresent SkippedPurged Missing Conflict ``` Dispositions observation : ```text Inserted AlreadyPresent NotRecorded NotApplicable ``` ## Composition Store atomique Pour `BackfillHydrationOutcome::Available`, la bibliothèque consomme l'acquisition en mémoire puis appelle exclusivement : ```text ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition ``` avec : ```text RawTransactionAcquisitionMode::Normal ``` La transaction et son observation restent donc une seule opération logique atomique possédée par Store. La tranche n'appelle jamais `record_raw_transaction_observation` séparément et n'introduit aucune prélecture `get_raw_transaction` avant écriture. `ForceRehydrate` n'est pas exposé par le chemin Backfill normal. ## Réseau Avant toute écriture, le réseau logique exposé par le snapshot du Store doit être identique au `RawNetworkId` de la référence hydratée. Un mismatch est rejeté par : ```text ERROR_CODE_BACKFILL_PERSISTENCE_INVALID field = store.network ``` Ce contrôle n'utilise ni endpoint, ni provider, ni protocole comme identité. Une incohérence interne entre la référence annoncée, la transaction et l'observation est également rejetée avant soumission Store. ## Outcomes Store admis En mode normal, les combinaisons Store suivantes sont acceptées et projetées explicitement : ```text RawEntityWriteOutcome::Inserted + RawObservationWriteOutcome::Inserted -> Inserted / Inserted RawEntityWriteOutcome::AlreadyPresent + RawObservationWriteOutcome::Inserted -> AlreadyPresent / Inserted RawEntityWriteOutcome::AlreadyPresent + RawObservationWriteOutcome::AlreadyPresent -> AlreadyPresent / AlreadyPresent RawEntityWriteOutcome::SkippedPurged + RawObservationWriteOutcome::NotRecorded -> SkippedPurged / NotRecorded ``` `RawEntityWriteOutcome::Rehydrated` est impossible dans le chemin normal et est rejeté, comme toute autre combinaison incohérente. ## Missing `BackfillHydrationOutcome::Missing(reference)` devient : ```text Missing / NotApplicable ``` sans aucune écriture Store. La tranche ne persiste toujours ni observation `missing`, ni observation d'échec fabriquée ; ces contrats n'existent pas dans le Store RAW transaction actuel. ## Conflit et erreurs Une erreur Store dont le code stable est : ```text ERROR_CODE_RAW_CONFLICT ``` est projetée en : ```text Conflict / NotRecorded ``` Elle reste donc visible et distincte de l'idempotence. Les tranches de frontier/snapshot pourront ensuite la traiter comme trou/échec sans parsing de message d'erreur. Toutes les autres erreurs Store sont propagées telles quelles. ## Idempotence La même acquisition déterministe peut produire successivement : ```text Inserted / Inserted AlreadyPresent / AlreadyPresent ``` sans doublon logique. Une entité déjà présente peut aussi recevoir une nouvelle observation légitime : ```text AlreadyPresent / Inserted ``` La distinction repose entièrement sur les outcomes Store officiels ; Job ne reconstruit aucune logique d'idempotence physique. ## Tests matérialisés La tranche ajoute **9 tests unitaires**, portant le total de `ksp-job-backfill-lib` à **29 unitaires** : - `Missing` ne soumet aucune écriture ; - mismatch réseau rejeté avant Store ; - insert atomique `Inserted/Inserted` ; - entité existante + nouvelle observation ; - relance idempotente `AlreadyPresent/AlreadyPresent` ; - purge normale `SkippedPurged/NotRecorded` ; - conflit Store explicite et non confondu avec idempotence ; - erreur Store non conflit propagée ; - combinaisons impossibles et mismatch transaction/observation rejetés. Une canarie publique supplémentaire porte les canaries d'intégration à **8** : ```text dependency_boundary 2 public_api 4 release_completeness 2 ``` Les canaries statiques verrouillent l'usage de l'écriture atomique, du mode `Normal`, l'absence d'écriture d'observation séparée, l'absence de `ForceRehydrate`, l'absence de backend Store direct et la fermeture du checkpoint/runtime de `pre.008`. ## Fichiers ajoutés ```text crates/ksp-job-backfill-lib/src/persistence.rs crates/ksp-job-backfill-lib/unit_tests/persistence.rs deltas/0.3.6/pre.007.md ``` ## Fichiers modifiés ```text Cargo.toml crates/ksp-job-backfill-lib/src/error.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 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 404 -> 405 crates/ksp-job-backfill-lib/src/error.rs 2 -> 3 crates/ksp-job-backfill-lib/src/lib.rs 2 -> 3 crates/ksp-job-backfill-lib/tests/dependency_boundary.rs 2 -> 3 crates/ksp-job-backfill-lib/tests/public_api.rs 2 -> 3 crates/ksp-job-backfill-lib/tests/release_completeness.rs 2 -> 3 ``` Les deux nouveaux fichiers Rust commencent à `version: 1`. Le plan 027 et la validation 023 passent de `12` à `13`. Le présent delta commence à `version: 1`. ## Version workspace La tranche modifie du Rust : ```text workspace.package.version = 0.3.6-pre.7 delivery = 0.3.6-pre.007 commit = v0.3.6-pre.007 ``` Aucun tag prerelease. ## Fichiers supprimés Aucun. ## Frontières conservées - aucune prélecture Store avant hydratation/persistance ; - aucun backend Store direct ; - aucun `ForceRehydrate` ; - aucune persistence de missing/failed artificielle ; - aucune concurrence d'hydratation concrète ; - aucune frontier contiguë ; - aucun checkpoint/reprise runtime ; - aucun snapshot Backfill concret ; - aucune nouvelle dépendance ou feature ; - aucune modification Transport, Store, Job API, Config, README, USAGE, CHANGELOG ou ROADMAP ; - aucun code kbot3 copié. ## Validations dans l'environnement d'assemblage L'environnement d'assemblage exécute les audits statiques et contrôles d'archive mais ne possède ni `cargo`, ni `rustc`, ni `rustfmt`. Les tests Rust matérialisés ne sont donc pas annoncés comme exécutés 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 : **29 tests unitaires + 8 canaries d'intégration**, aucun warning Clippy, et dépendance Store toujours limitée à `ksp-store-lib` avec `default-features = false`. ## Questions ouvertes Aucune pour `pre.007`. `pre.008` reste fermé tant que ce gate n'est pas vert.