# Delta `0.3.3-pre.004` — mapping PostgreSQL privé et lectures RawTransaction ## 1. Base requise ```text 0.3.3-pre.3.fix.3 ``` Le gate opérateur fourni pour `pre.003-fix.003` est entièrement vert : ```text cargo fmt --all PASS audit Rust général / exports / workspace PASS audit Markdown PASS — 214 tables / 136 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 — 19 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 ``` La V001 split, son contrat catalogue et les politiques `schema_autocreate/schema_autoupdate` sont donc considérés acquis. ## 2. Objectif Implémenter la tranche lecture de la vertical slice PostgreSQL `RawTransaction` sans ouvrir encore les écritures ni la pagination : ```text mapping SQL privé get_raw_transaction get_raw_transaction_observation get_raw_transaction_retention_state get_raw_transaction_tombstone ``` Les méthodes du backend retournent uniquement des modèles `ksp-store-api`. Aucun `tokio_postgres::Row`, SQL, SQLSTATE ou bind ne traverse le bridge public. ## 3. Version Le workspace passe à : ```text 0.3.3-pre.4 ``` ## 4. Module physique privé Nouveau module : ```text crates/ksp-store-postgres-lib/src/raw_transaction.rs ``` Il possède les SELECT et les codecs physiques de cette tranche. `runtime.rs` ne contient aucun SQL métier et délègue les quatre lectures au module privé. La tranche reste strictement read-only : ```text INSERT INTO absent UPDATE absent DELETE FROM absent ``` Les écritures canonique/observation restent `pre.005`; `list_raw_transactions` et le cursor restent `pre.006`. ## 5. Conversion physique exacte Le mapping respecte les contrats `ksp-store-api` sans narrowing : ```text slot NUMERIC(20,0) -> PostgreSQL ::text -> parse u64 format_version BIGINT -> u32::try_from block/observed/received BIGINT -> u64::try_from -> RawTimestamp source payload size BIGINT -> u64::try_from + borne API signature -> BYTEA exactement 64 bytes hash/key -> BYTEA exactement 32 bytes format/provenance codes -> constructeurs API fallibles ``` Le chemin `slot` couvre explicitement : ```text 0 i64::MAX i64::MAX + 1 u64::MAX ``` Aucune dépendance décimale supplémentaire n'est introduite. ## 6. `get_raw_transaction` La requête lit la ligne canonique avec `LEFT JOIN` du payload archive. Décodage : ```text Full payload chaud obligatoire payload archive interdit -> Some(RawTransaction) Archived payload chaud absent payload archive obligatoire -> Some(RawTransaction) Purged payload chaud absent payload archive absent block_time absent -> None ``` Toute incohérence de forme, entier hors domaine, code invalide, signature/hash mal dimensionné ou payload incompatible produit `DataInvalid` sans conserver la valeur hostile. ## 7. Observation `get_raw_transaction_observation` reconstruit : ```text observation_key transaction signature provider protocol acquisition_method origin received_at capture_session_id optionnel commitment optionnel endpoint_id optionnel filter_id optionnel observed_at optionnel source_payload_hash optionnel source_payload_size_bytes optionnel ``` `RawObservationKey` ne porte pas de réseau ; le `RawTransactionReference` reconstruit utilise donc exclusivement le réseau mono-backend déjà vérifié à l'ouverture. L'ordre temporel `observed_at <= received_at` est revalidé par le constructeur API. ## 8. Rétention et tombstone `get_raw_transaction_retention_state` mappe uniquement les états physiques supportés par V001 : ```text full archived purged ``` Une valeur inconnue ou `compacted` stockée physiquement est une corruption `DataInvalid`; `Compacted` n'a toujours aucune représentation PostgreSQL prétendue. `get_raw_transaction_tombstone` retourne `Some` uniquement pour `Purged`, vérifie également que `block_time` est absent, puis reconstruit exactement : ```text network + signature slot format_id format_version content_hash ``` Pour `Full` ou `Archived`, le résultat est `None`. ## 9. Réseau pré-I/O Les trois lectures portant un `RawTransactionReference` passent par un garde privé commun avant `pool.get()` : ```text reference.network == backend.network -> continuer sinon -> WrongNetwork ``` Le mauvais réseau ne consomme donc aucune connexion PostgreSQL et ne dépend d'aucun SQL. ## 10. Erreurs backend `PostgresBackendErrorKind` ajoute : ```text DataInvalid ReadFailed WrongNetwork ``` Classification : ```text pool wait/connect -> kinds existants PoolTimeout/ConnectFailed SELECT/driver read -> ReadFailed row/cardinality/model -> DataInvalid reference mauvais réseau -> WrongNetwork ``` `PostgresBackendError` reste composé uniquement de : ```text kind phase &'static str ``` Aucune erreur externe n'est retenue ni rendue. ## 11. Surface backend `PostgresBackend` expose désormais : ```text get_raw_transaction get_raw_transaction_observation get_raw_transaction_retention_state get_raw_transaction_tombstone ``` Ces méthodes ne constituent pas encore les implémentations finales des traits `RawTransaction*` : `RawTransactionRead` exige également `list_raw_transactions`, réservé à `pre.006`. Les six traits seront complets avant leur dispatch par `ksp-store-lib` en `pre.008`. ## 12. Tests déterministes Le nouveau miroir `unit_tests/raw_transaction.rs` couvre notamment : ```text Full u64::MAX Archived u64::MAX Purged -> None résidu payload/block_time sur Purged -> DataInvalid provenance complète/optionnelle source payload max slot > u64::MAX -> DataInvalid hash mal dimensionné -> DataInvalid origin hostile -> DataInvalid sans écho états Full/Archived/Purged Compacted physique -> DataInvalid tombstone u64::MAX/u32::MAX wrong-network pré-I/O ``` Les canaris d'intégration figent également : ```text module SQL privé aucun write SQL en pre.004 aucune dépendance nouvelle surface publique sans types PostgreSQL nouveaux error kinds sûrs ``` ## 13. Migrations Aucune ressource de migration n'est modifiée. Les checksums restent : ```text V000 d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450 V001 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51 ``` ## 14. Fichiers ajoutés ```text crates/ksp-store-postgres-lib/src/raw_transaction.rs crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs deltas/0.3.3/pre.004.md ``` ## 15. Fichiers modifiés ```text Cargo.toml crates/ksp-store-postgres-lib/README.md crates/ksp-store-postgres-lib/USAGE.md crates/ksp-store-postgres-lib/src/error.rs crates/ksp-store-postgres-lib/src/lib.rs crates/ksp-store-postgres-lib/src/runtime.rs crates/ksp-store-postgres-lib/tests/dependency_boundary.rs crates/ksp-store-postgres-lib/tests/hardening_completeness.rs crates/ksp-store-postgres-lib/tests/public_api.rs docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md ``` ## 16. Fichiers supprimés Aucun. ## 17. Hors scope confirmé Aucun changement n'est apporté à : ```text ksp-store-api contrats ksp-store-lib dispatch métier écriture RawTransaction écriture observation pagination/cursor transition rétention RawAccountState worker/job/app migration SQL V000/V001 ``` ## 18. Gate opérateur ```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.3 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 ``` La tranche n'est validée qu'après gate opérateur entièrement vert.