# Delta `0.3.4-pre.007` — pagination account keyset + cursor `KSPA` ## 1. Base requise ```text 0.3.4-pre.6 ``` Le gate opérateur fourni le 2026-08-30 pour `pre.006` est entièrement vert : ```text cargo fmt --all PASS audit Rust général / exports / workspace PASS audit Markdown PASS — 239 tables / 143 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 — 58 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 ``` L'observation supplémentaire account et son contrat transactionnel/cancellation sont donc considérés acquis. ## 2. Objectif Implémenter uniquement la navigation `RawAccountStateQuery` : ```text ordre total (slot, pubkey, state_hash) ASC / DESC complètement inversés filtre pubkey optionnel bornes de slot inclusives keyset pagination uniquement cursor account KSPA V1 LIMIT + 1 sans policy KSP ``` Les quatre capabilities account et leur dispatch `Store` restent hors tranche jusqu'à `pre.008`. ## 3. Version Le workspace passe à : ```text 0.3.4-pre.7 ``` ## 4. Requêtes physiques `raw_account.rs` ajoute quatre SELECT privés : ```text sans pubkey : ASC / DESC avec pubkey : ASC / DESC ``` L'ordre total est : ```text ASC (slot ASC, pubkey ASC, state_hash ASC) DESC (slot DESC, pubkey DESC, state_hash DESC) ``` Les continuations utilisent exclusivement : ```text (slot, pubkey, state_hash) > (last_slot, last_pubkey, last_state_hash) (slot, pubkey, state_hash) < (last_slot, last_pubkey, last_state_hash) ``` Sans filtre, l'index V002 `(slot, pubkey, state_hash)` porte la navigation. Avec filtre pubkey, la PK `(pubkey, slot, state_hash)` porte le préfixe account et le même ordre déterministe. ## 5. Cursor account V1 Nouveau module privé : ```text crates/ksp-store-postgres-lib/src/raw_account/cursor.rs ``` Format fixe : ```text 0..4 magic KSPA 4 version 1 5..13 last_slot u64 big-endian 13..45 last_pubkey 32 bytes 45..77 last_state_hash 32 bytes 77..109 SHA-256 binding digest ``` Domaine : ```text KSP/raw-account-state-cursor/v1 ``` Le digest lie : ```text network pubkey filter presence/value direction slot.start_inclusive slot.end_inclusive last_slot last_pubkey last_state_hash ``` Un cursor transaction `KSPT` ne peut donc pas être rejoué comme cursor account et échoue dès le magic. ## 6. Limite physique de page Comme la verticale transaction, le backend applique `LIMIT + 1` uniquement pour détecter une page suivante. Aucun maximum fonctionnel KSP n'est ajouté. La limite physique exacte reste : ```text 9_223_372_036_854_775_806 ``` car `requested + 1` doit être représentable par un `i64` PostgreSQL. ## 7. Mapping de liste Chaque row de liste revalide : ```text pubkey exact 32 bytes slot NUMERIC(20,0)::text -> u64 exact state_hash exact 32 bytes ``` Le résultat public du bridge est exclusivement : ```text RawPage ``` Aucun row PostgreSQL, SQL ou cursor décodé ne traverse la frontière backend. ## 8. Bridge backend `PostgresBackend` expose : ```text list_raw_account_states( &RawAccountStateQuery, ) -> RawPage ``` Ce bridge reste une méthode étroite. `RawAccountStateRead`, `RawAccountStateWrite`, `RawAccountObservationRead` et `RawAccountObservationWrite` ne sont toujours pas implémentés sur `PostgresBackend`. ## 9. Scope négatif Restent absents : ```text OFFSET batch max 500/1000 priority / worker policy clamp/min artificiel UPDATE / DELETE account ON CONFLICT DO UPDATE impl RawAccountStateRead for PostgresBackend impl RawAccountStateWrite for PostgresBackend impl RawAccountObservationRead for PostgresBackend impl RawAccountObservationWrite for PostgresBackend Store dispatch account ``` Toutes les migrations V000/V001/V002 restent byte-inchangées. Le checksum V002 final reste : ```text ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e ``` ## 10. Fichiers ajoutés ```text crates/ksp-store-postgres-lib/src/raw_account/cursor.rs deltas/0.3.4/pre.007.md ``` ## 11. 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/hardening_completeness.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 ``` ## 12. Fichiers supprimés ```text aucun ``` ## 13. Validations exécutées à l'assemblage ```text python3 scripts/audit_rust_workspace_rules.py PASS python3 scripts/audit_markdown_tables.py ... deltas/0.3.4 PASS migrations V000/V001/V002 byte-identiques à pre.006 PASS scope pre.008 absent PASS ``` Le gate Cargo/Clippy/tests de `pre.007` doit être rejoué par l'opérateur. ## 14. Validations non exécutées ```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 ``` `cargo` n'est pas disponible dans l'environnement d'assemblage. ## 15. Décisions prises - le cursor account reste distinct de `KSPT` par magic et domaine SHA-256 ; - le filtre pubkey fait partie du binding du cursor ; - les deux chemins SQL avec/sans pubkey sont séparés pour conserver une surface physique compatible avec les indexes possédés ; - aucune policy d'exécution n'est ajoutée à la pagination Store ; - les quatre capabilities account restent groupées pour `pre.008`. ## 16. Questions ouvertes Aucune question bloquante pour `pre.008`.