312 lines
8.0 KiB
Markdown
312 lines
8.0 KiB
Markdown
<!-- file: deltas/0.3.3/pre.006.md -->
|
|
<!-- version: 1 -->
|
|
|
|
# Delta `0.3.3-pre.006` — pagination keyset et cursor V1 RawTransaction
|
|
|
|
## 1. Base et gate d'entrée
|
|
|
|
Base opérateur obligatoire :
|
|
|
|
```text
|
|
0.3.3-pre.5
|
|
```
|
|
|
|
Le gate opérateur fourni le 2026-08-30 est entièrement vert :
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
workspace.package.version = 0.3.3-pre.6
|
|
```
|
|
|
|
## 3. Scope exact
|
|
|
|
Cette tranche ajoute uniquement la navigation backend PostgreSQL des transactions RAW :
|
|
|
|
```text
|
|
PostgresBackend::list_raw_transactions
|
|
RawTransactionQuery
|
|
RawPage<RawTransactionReference>
|
|
RawPageCursor backend-private V1
|
|
```
|
|
|
|
Ne sont pas ouverts :
|
|
|
|
```text
|
|
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` :
|
|
|
|
```text
|
|
Ascending = (slot ASC, signature ASC)
|
|
Descending = (slot DESC, signature DESC)
|
|
```
|
|
|
|
Les deux requêtes privées utilisent :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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é :
|
|
|
|
```text
|
|
KSP/raw-transaction-cursor/v1
|
|
```
|
|
|
|
Il lie de façon non ambiguë :
|
|
|
|
```text
|
|
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()` :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
LIMIT requested + 1
|
|
```
|
|
|
|
La borne physique exacte retenue sur PostgreSQL est donc :
|
|
|
|
```text
|
|
requested <= i64::MAX - 1
|
|
```
|
|
|
|
Une valeur supérieure produit :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
1. la row de probe est retirée du résultat ;
|
|
2. le cursor est construit depuis la dernière row réellement retournée ;
|
|
3. la page contient exactement au plus `requested` ré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 :
|
|
|
|
```text
|
|
PageLimitUnsupported
|
|
QueryInvalid
|
|
```
|
|
|
|
Comme les autres erreurs physiques, ils ne conservent que :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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` :
|
|
|
|
```text
|
|
V000 d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
|
V001 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
|
|
```
|
|
|
|
V001 conserve exactement :
|
|
|
|
```text
|
|
4 tables
|
|
35 contraintes
|
|
1 index
|
|
40 ressources
|
|
```
|
|
|
|
## 12. Fichiers ajoutés/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/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
|
|
|
|
```text
|
|
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
|
|
|
|
```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
|
|
```
|
|
|
|
## 15. Suite après gate vert
|
|
|
|
```text
|
|
0.3.3-pre.007 — archive/purge/tombstone/compare-and-transition + Compacted unsupported stable
|
|
```
|