# Delta `0.3.4-pre.005` — acquisition atomique et idempotence `RawAccountState` ## 1. Base requise ```text 0.3.4-pre.4.fix.1 ``` Le gate opérateur fourni le 2026-08-30 pour `pre.004-fix.001` est entièrement vert : ```text cargo fmt --all PASS audit Rust général / exports / workspace PASS audit Markdown PASS — 239 tables / 141 files cargo check --workspace PASS cargo clippy --workspace --all-targets PASS cargo test -p ksp-store-api PASS cargo test -p ksp-store-lib PASS cargo test -p ksp-store-postgres-lib PASS — 52 unit tests + canaris, live ignored cargo test -p ksp-config-lib PASS — 128 unit tests + ownership/public API cargo check -p ksp-store-lib --no-default-features PASS ``` Le mapping/read account de `pre.004` est donc considéré acquis avant l'ouverture des writes d'acquisition. ## 2. Objectif Implémenter uniquement l'écriture atomique du couple : ```text RawAccountState + RawAccountObservation ``` avec les invariants figés par `pre.001` : ```text guards réseau/référence avant I/O state insert idempotent collision state -> FOR UPDATE + comparaison exacte observation insert idempotente collision observation -> FOR UPDATE + comparaison exacte commit unique aucun overwrite silencieux ``` L'observation supplémentaire sans retransmettre les bytes account reste réservée à `pre.006`. ## 3. Version Le workspace passe à : ```text 0.3.4-pre.5 ``` Aucune crate ne redéfinit localement la version héritée. ## 4. Préconditions avant I/O `persist_raw_account_acquisition` valide avant `pool.get()` : ```text state.reference.network == backend.network observation.account.network == backend.network observation.account == state.reference ``` Un mauvais réseau devient `WrongNetwork`. Une référence observation/state divergente devient `Conflict`. Aucune connexion PostgreSQL n'est consommée avant ces gardes. ## 5. Insertion canonique du state Le state utilise : ```text INSERT INTO ksp_raw_account_states (...) ON CONFLICT (pubkey, slot, state_hash) DO NOTHING RETURNING pubkey ``` Les domaines `u64` restent encodés sans narrowing via chaînes décimales et cast PostgreSQL `TEXT::NUMERIC` : ```text slot lamports rent_epoch ``` Les pubkeys/hash restent en bytes fixes et `data` est persistée sans base64 ni copie structurale. Si l'INSERT gagne : ```text RawEntityWriteOutcome::Inserted ``` Si l'identité existe déjà, le backend charge la ligne canonique sous : ```text FOR UPDATE ``` et compare exactement : ```text reference lamports owner executable rent_epoch data ``` Le `state_hash` ne suffit donc jamais à déclarer l'idempotence. Contenu identique -> `AlreadyPresent`; contenu divergent -> `Conflict`. ## 6. Insertion idempotente de l'observation L'observation est encodée avec toute la provenance et toutes les metadata optionnelles : ```text observation_key account pubkey/slot/state_hash provider protocol acquisition_method origin received_at capture_session_id commitment endpoint_id filter_id observed_at source_payload_hash source_payload_size_bytes is_startup transaction_signature write_version ``` Le SQL utilise : ```text ON CONFLICT (observation_key) DO NOTHING ``` En cas de collision, la ligne existante est relue `FOR UPDATE`, décodée avec le codec hostile-row de `pre.004`, puis comparée au `RawAccountObservation` entrant complet. Identique -> `AlreadyPresent`; divergent -> `Conflict`. Aucun champ optionnel n'est ignoré dans l'idempotence. ## 7. Atomicité State et observation vivent dans la même transaction PostgreSQL : ```text BEGIN state insert/compare observation insert/compare COMMIT ``` Si un nouveau state a été inséré mais que l'observation collide avec un contenu divergent, le chemin retourne une erreur avant commit. Le drop de la transaction annule alors l'insertion du state : aucune moitié d'acquisition durable n'est admise. Aucun pré-check `has_*`, aucun `ON CONFLICT DO UPDATE`, aucun `UPDATE` et aucun `DELETE` account n'est introduit. ## 8. Surface backend `PostgresBackend` expose le bridge étroit : ```text persist_raw_account_acquisition( RawAccountState, RawAccountObservation, ) -> RawAcquisitionWriteOutcome ``` Le bridge reste backend-agnostic et n'expose aucun type PostgreSQL. Cette méthode n'implémente pas encore `RawAccountStateWrite`. Les quatre traits account restent volontairement absents jusqu'à `pre.008` afin d'ouvrir la conformance physique 10/10 en une seule tranche. ## 9. Tests et canaries Les tests unitaires ajoutés couvrent : ```text guard réseau state référence observation/state exacte state identique -> comparaison vraie même référence/hash + lamports divergent -> comparaison fausse observation identique -> égalité exacte même clé/référence + write_version divergent -> non-identique encodage exact des cinq RawAcquisitionOrigin ``` Le canari `dependency_boundary` exige maintenant : ```text INSERT_ACCOUNT_STATE_SQL INSERT_ACCOUNT_OBSERVATION_SQL LOCK_ACCOUNT_STATE_SQL LOCK_ACCOUNT_OBSERVATION_SQL ON CONFLICT ... DO NOTHING FOR UPDATE persist_raw_account_acquisition ``` et continue d'interdire : ```text UPDATE / DELETE account ON CONFLICT DO UPDATE record_raw_account_observation list_raw_account_states OFFSET les quatre impl RawAccount* ``` `public_api.rs` vérifie le nouveau bridge backend-agnostic. ## 10. Scope explicitement reporté ```text record_raw_account_observation pre.006 races/cancellation ciblées pre.006 list_raw_account_states pre.007 cursor KSPA pre.007 4 impl RawAccount* PostgresBackend pre.008 4 dispatch Store pre.008 live PostgreSQL account pre.009 ``` ## 11. Migrations Aucune ressource sous `migrations/` n'est modifiée. Checksums conservés : ```text V000 = d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450 V001 = 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51 V002 = ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e ``` ## 12. Fichiers modifiés ```text Cargo.toml crates/ksp-store-postgres-lib/src/lib.rs crates/ksp-store-postgres-lib/src/raw_account.rs crates/ksp-store-postgres-lib/src/runtime.rs crates/ksp-store-postgres-lib/tests/dependency_boundary.rs crates/ksp-store-postgres-lib/tests/public_api.rs crates/ksp-store-postgres-lib/unit_tests/raw_account.rs docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md ``` ## 13. Fichier ajouté ```text deltas/0.3.4/pre.005.md ``` ## 14. Fichiers supprimés ```text aucun ``` ## 15. Validations exécutées dans l'environnement d'assemblage ```text python3 scripts/audit_rust_workspace_rules.py General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.4 PASS ``` Les contrôles différentiels doivent également confirmer l'identité byte-à-byte de V000/V001/V002 et l'absence de surface `pre.006+`. ## 16. Validations non exécutées dans l'environnement d'assemblage `cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Aucun résultat Cargo post-`pre.005` n'est revendiqué ici. Gate opérateur à 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.4 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-store-api cargo test -p ksp-store-lib cargo test -p ksp-store-postgres-lib cargo test -p ksp-config-lib cargo check -p ksp-store-lib --no-default-features ``` ## 17. Verdict `pre.005` ouvre uniquement l'acquisition atomique `RawAccountState + RawAccountObservation`. L'observation supplémentaire, les races/cancellations dédiées, la pagination et les capabilities account restent fermées conformément au sizing souple de `0.3.4`.