8.0 KiB
Delta 0.3.3-pre.006 — pagination keyset et cursor V1 RawTransaction
1. Base et gate d'entrée
Base opérateur obligatoire :
0.3.3-pre.5
Le gate opérateur fourni le 2026-08-30 est entièrement vert :
cargo fmt --all PASS
scripts/audit_rust_workspace_rules.py PASS
scripts/audit_markdown_tables.py PASS
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 (30 unit backend, live foundation ignoré)
cargo test -p ksp-config-lib PASS (128 unit + ownership/public API)
cargo check -p ksp-store-lib --no-default-features PASS
La tranche peut donc ouvrir la pagination sans rouvrir l'écriture atomique acquise en pre.005.
2. Version
workspace.package.version = 0.3.3-pre.6
3. Scope exact
Cette tranche ajoute uniquement la navigation backend PostgreSQL des transactions RAW :
PostgresBackend::list_raw_transactions
RawTransactionQuery
RawPage<RawTransactionReference>
RawPageCursor backend-private V1
Ne sont pas ouverts :
transitions Full -> Archived -> Purged
compare-and-transition de rétention
implémentations complètes des six traits RawTransaction*
dispatch ksp-store-lib
RawAccountState
worker/job policy
4. Ordre physique et keyset
L'ordre total reste celui figé en pre.001 :
Ascending = (slot ASC, signature ASC)
Descending = (slot DESC, signature DESC)
Les deux requêtes privées utilisent :
retention_state <> 'purged'
slot >= start_inclusive si présent
slot <= end_inclusive si présent
(slot, signature) > cursor pour Ascending
(slot, signature) < cursor pour Descending
LIMIT requested + 1
Aucun OFFSET n'est utilisé. La signature 64 octets constitue le tie-breaker déterministe pour plusieurs transactions au même slot.
Le prédicat et l'ordre correspondent à l'index V001 existant :
ix_ksp_raw_transactions_slot_signature
ON ksp_raw_transactions (slot, signature)
WHERE retention_state <> 'purged'
Aucune migration n'est modifiée.
5. Cursor V1
Le cursor est implémenté dans un sous-module privé au domaine raw_transaction.
Format fixe :
magic 4 bytes = KSPT
version 1 byte = 1
last_slot 8 bytes = u64 big-endian
last_signature 64 bytes
binding_digest 32 bytes = SHA-256
--------------------------------------
total 109 bytes
Le digest utilise le domaine séparé :
KSP/raw-transaction-cursor/v1
Il lie de façon non ambiguë :
longueur du network + network
direction
flag start + start slot si présent
flag end + end slot si présent
last_slot
last_signature
Le digest n'est ni un secret ni un mécanisme d'autorisation. Il sert uniquement à détecter corruption et replay sous un autre contexte de navigation.
Le decode rejette avant pool.get() :
taille != 109
magic incorrect
version inconnue
digest divergent
network différent
range différente
direction différente
last_slot hors range
bytes hostiles bornés par RawPageCursor
Ces cas sont classés PostgresBackendErrorKind::QueryInvalid avec phase statique uniquement.
6. Limite physique réelle
RawPageLimit reste sans cap métier KSP.
Le backend doit obtenir une row supplémentaire pour déterminer la présence d'une continuation :
LIMIT requested + 1
La borne physique exacte retenue sur PostgreSQL est donc :
requested <= i64::MAX - 1
Une valeur supérieure produit :
PostgresBackendErrorKind::PageLimitUnsupported
Aucun clamp vers 100, 500, 1000 ou une autre taille de worker/job n'est introduit.
7. Décodage des pages
Toutes les rows retournées par le statement, y compris la row de probe limit+1, passent par le mapping fallible :
signature BYTEA -> [u8; 64]
slot NUMERIC(20,0) -> texte décimal -> u64
Une corruption physique n'est donc pas masquée simplement parce qu'elle se trouve sur la row supplémentaire.
Si plus de requested rows sont valides :
- la row de probe est retirée du résultat ;
- le cursor est construit depuis la dernière row réellement retournée ;
- la page contient exactement au plus
requestedréférences.
Les tombstones Purged ne sont jamais listés.
8. Concurrence et snapshot
Chaque page est cohérente au niveau de son statement PostgreSQL, mais la navigation ne prétend pas fournir un snapshot inter-pages.
Une mutation concurrente qui insère une clé ordonnée avant le cursor déjà consommé peut ne pas être vue par cette navigation. Cette propriété est désormais explicitement documentée et n'est pas confondue avec une garantie de replay transactionnel.
9. Erreurs sûres
Deux kinds backend supplémentaires sont matérialisés :
PageLimitUnsupported
QueryInvalid
Comme les autres erreurs physiques, ils ne conservent que :
kind
phase &'static str
Aucun cursor, network hostile, signature, query SQL, bind, SQLSTATE ou texte PostgreSQL n'est rendu.
10. Tests et canaris
Les tests unitaires ajoutés couvrent :
round-trip cursor V1 exact
109 bytes exacts
magic/version exacts
replay autre network
replay autre direction
replay autre range
last_slot hors range
taille hostile 1/108/109/110/4096
mutation magic/version/digest
borne page i64::MAX - 1
rejet i64::MAX
Les canaris d'intégration figent :
keyset > / <
ASC/DESC total
pas d'OFFSET
exclusion Purged
index V001 compatible
pas de cap 500/1000
pas de DELETE
pas de transition de rétention
pas d'implémentation complète de trait prématurée
La pagination PostgreSQL réelle multi-page avec ties/ranges reste réservée au gate live opt-in pre.009.
11. Migrations
Les migrations sont byte-identiques à pre.005 :
V000 d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
V001 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
V001 conserve exactement :
4 tables
35 contraintes
1 index
40 ressources
12. Fichiers ajoutés/modifiés
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/raw_transaction.rs
crates/ksp-store-postgres-lib/src/raw_transaction/cursor.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_transaction.rs
docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md
docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md
deltas/0.3.3/pre.006.md
Aucun fichier n'est supprimé.
13. Audits exécutables dans l'environnement de génération
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
Les commandes Cargo/rustfmt ne sont pas disponibles dans l'environnement de génération. Elles doivent rester NON EXÉCUTÉES jusqu'au gate opérateur ; elles ne doivent jamais être présentées comme PASS sans sortie réelle.
14. Gate opérateur
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
15. Suite après gate vert
0.3.3-pre.007 — archive/purge/tombstone/compare-and-transition + Compacted unsupported stable