# Delta `0.3.4-pre.004` — mapping PostgreSQL privé et lectures `RawAccountState` ## 1. Base requise ```text 0.3.4-pre.3.fix.1 ``` Le gate opérateur fourni le 2026-08-30 pour `pre.003-fix.001` est entièrement vert : ```text cargo fmt --all PASS audit Rust général / exports / workspace PASS audit Markdown PASS — 239 tables / 139 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 — 45 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 ``` V002 finale et son checksum sont donc considérés acquis avant l'ouverture du mapping account. ## 2. Objectif Implémenter la tranche de lecture de la vertical slice PostgreSQL `RawAccountState` sans ouvrir les writes, la pagination ni les implémentations de traits : ```text mapping SQL privé state mapping SQL privé observation get_raw_account_state get_raw_account_observation hostile-row guards ``` Les méthodes du backend retournent uniquement des modèles `ksp-store-api`. Aucun `tokio_postgres::Row`, SQL, bind, SQLSTATE ou type physique ne traverse le bridge public. ## 3. Version Le workspace passe à : ```text 0.3.4-pre.4 ``` Aucune crate ne redéfinit localement la version héritée. ## 4. Module physique privé Nouveau module : ```text crates/ksp-store-postgres-lib/src/raw_account.rs ``` Il possède les deux SELECT et les codecs physiques de cette tranche. `runtime.rs` ne contient aucun SQL métier et délègue les lectures au module privé. La tranche est strictement read-only : ```text INSERT INTO absent UPDATE absent DELETE FROM absent ON CONFLICT absent FOR UPDATE absent list/cursor account absent ``` Les écritures state+observation restent `pre.005`, l'observation supplémentaire `pre.006`, la pagination/cursor `pre.007` et les quatre `impl RawAccount*` `pre.008`. ## 5. `get_raw_account_state` Le SELECT adresse exactement la clé physique : ```text (pubkey, slot, state_hash) ``` Le mapping reconstruit : ```text network <- backend mono-réseau pubkey <- BYTEA exactement 32 bytes slot <- NUMERIC(20,0)::text -> u64 state_hash <- BYTEA exactement 32 bytes lamports <- NUMERIC(20,0)::text -> u64 owner <- BYTEA exactement 32 bytes executable <- BOOLEAN rent_epoch <- NUMERIC(20,0)::text -> u64 data <- BYTEA exact -> RawAccountState::try_new ``` Le chemin décimal couvre tout le domaine `u64`, y compris les valeurs supérieures à `i64::MAX` et `u64::MAX` lui-même. Aucun narrowing via `BIGINT` n'est introduit. `RawAccountState::try_new` revalide la borne backend-agnostic des bytes account : data vide est valide ; une ligne hostile dépassant 16 MiB devient `DataInvalid`. Après décodage, la référence reconstruite doit rester exactement égale à la référence demandée. Une cardinalité autre que 0/1 est également `DataInvalid`. ## 6. Garde réseau pré-I/O `get_raw_account_state` vérifie avant `pool.get()` : ```text reference.network == backend.network -> continuer sinon -> WrongNetwork ``` Le mauvais réseau ne consomme donc aucune connexion PostgreSQL et n'expose aucune valeur fournie. ## 7. `get_raw_account_observation` `RawObservationKey` ne porte pas de réseau. Le réseau de la `RawAccountStateReference` reconstruite provient donc exclusivement du backend mono-réseau déjà lié par `ksp_store_identity`. Le mapping couvre : ```text observation_key account.pubkey account.slot account.state_hash 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 is_startup optionnel transaction_signature optionnelle write_version optionnel ``` Spécificités physiques : ```text account.slot / write_version -> NUMERIC(20,0)::text -> u64 transaction_signature -> NULL ou exactement 64 bytes hash/key/pubkey -> exactement 32 bytes received/observed -> BIGINT -> u64 -> RawTimestamp source payload size -> BIGINT -> u64 + borne API 64 MiB provenance codes -> constructeurs API fallibles origin -> cinq variantes API uniquement ``` Les metadata Yellowstone restent strictement observation-only. Une colonne NULL reste `None`; aucune valeur `false`, zéro, signature ou write version n'est inventée. ## 8. Hostile-row guards Les tests unitaires couvrent notamment : ```text state slot/lamports/rent_epoch = u64::MAX write_version = u64::MAX account data vide account data > 16 MiB -> DataInvalid pubkey de 31 bytes -> DataInvalid signature transaction de 63 bytes -> DataInvalid décimal négatif ou > u64::MAX -> DataInvalid origin hostile -> DataInvalid observed_at > received_at -> DataInvalid metadata optionnelles absentes -> None wrong network state reference -> WrongNetwork pré-I/O ``` Les erreurs ne retiennent que `PostgresBackendErrorKind` + phase `&'static str`; les chaînes hostiles ne sont jamais interpolées dans l'erreur. ## 9. Surface backend `PostgresBackend` expose désormais le bridge étroit : ```text get_raw_account_state get_raw_account_observation ``` Ces méthodes ne constituent pas encore les implémentations de `RawAccountStateRead` et `RawAccountObservationRead` : le premier trait exige aussi `list_raw_account_states`, réservé à `pre.007`. Les quatre traits account seront ouverts ensemble en `pre.008` afin de passer directement de 6/10 à 10/10 capabilities physiques. ## 10. Canaries transformés Les canaris historiques qui interdisaient tout module account sont ajustés sans perdre leur rôle : - `dependency_boundary.rs` exige un module `raw_account` privé, read-only, sans pagination ni trait impl ; - `hardening_completeness.rs` inclut le nouveau module dans l'inventaire exact et dans les scans anti-secret/anti-reverse-edge ; - les interdictions des quatre `impl RawAccount* for PostgresBackend` restent actives ; - `public_api.rs` prouve que les deux méthodes du bridge n'utilisent que les modèles backend-agnostic. Les canaris V000/health business-free restent inchangés. ## 11. Migrations Aucune ressource sous `migrations/` n'est modifiée. Checksums conservés : ```text V000 = d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450 V001 = 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51 V002 = ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e ``` Le bridge du checksum provisoire `pre.002` reste inchangé. ## 12. Fichiers ajoutés ```text crates/ksp-store-postgres-lib/src/raw_account.rs crates/ksp-store-postgres-lib/unit_tests/raw_account.rs deltas/0.3.4/pre.004.md ``` ## 13. Fichiers modifiés ```text Cargo.toml 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/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.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 après alignement RustRover des tableaux touchés ``` Le contrôle différentiel doit également confirmer qu'aucune ressource V000/V001/V002 n'a changé. ## 16. Validations non exécutées dans l'environnement d'assemblage `cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans cet environnement. Ne sont donc pas revendiqués PASS ici : ```text cargo fmt --all 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. Décisions prises - Réutiliser la frontière éprouvée de `0.3.3-pre.004` : SQL/mapping privé, méthodes backend étroites, traits plus tard. - Garder les conversions `NUMERIC(20,0)` via `::text -> u64` afin de ne pas ajouter de dépendance décimale ni de narrowing. - Revalider les invariants API au read même si V002 possède déjà des CHECK SQL ; la DB peut être externe, héritée ou hostile. - Ne pas factoriser prématurément les codecs transaction/account dans un nouveau module commun : les familles ont des formes différentes et `pre.004` doit rester bornée. - Ne pas ouvrir `RawAccountStateRead` avant que `list_raw_account_states` existe ; l'inventaire de capabilities reste volontairement 6/10 jusqu'à `pre.008`. ## 18. Questions ouvertes Aucune question bloquante nouvelle. Les détails des writes, races et cursor restent ceux figés par le plan `pre.001` et appartiennent respectivement à `pre.005`, `pre.006` et `pre.007`. ## 19. Gate opérateur attendu ```text 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 ``` Aucun test live PostgreSQL account n'est demandé dans cette tranche ; la preuve live complète reste `pre.009`.