7.9 KiB
Delta 0.3.3-pre.005 — écriture atomique RawTransaction + observation
1. Base requise
0.3.3-pre.4
Le gate opérateur fourni pour pre.004 est entièrement vert :
cargo fmt --all PASS
audit Rust général / exports / workspace PASS
audit Markdown PASS — 214 tables / 137 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 — 25 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
Les lectures physiques RawTransaction de pre.004 sont donc acquises.
2. Objectif
Implémenter la tranche écriture de la vertical slice PostgreSQL sans ouvrir encore pagination ni transitions mutantes de rétention :
persist_raw_transaction_acquisition
record_raw_transaction_observation
La première opération doit rendre le canonical et son observation atomiques. Les deux opérations doivent être réellement idempotentes et ne jamais confondre une collision de clé avec un contenu identique.
3. Version
Le workspace passe à :
0.3.3-pre.5
4. Pré-I/O
Avant pool.get() :
transaction.reference.network == backend.network
observation.transaction.network == backend.network
observation.transaction == transaction.reference
Un mauvais réseau retourne WrongNetwork. Une acquisition composée de deux références logiques différentes retourne Conflict sans toucher PostgreSQL.
record_raw_transaction_observation valide également son réseau avant toute acquisition du pool.
5. Acquisition atomique canonique + observation
L'algorithme est :
BEGIN
INSERT canonical
ON CONFLICT (signature) DO NOTHING
RETURNING signature
si inséré
-> candidate Inserted
sinon
SELECT canonical FOR UPDATE
SELECT archive payload éventuel
comparer le contenu réel
écrire/idempotenter observation
COMMIT
Toute erreur après l'insert canonique, notamment une collision d'observation divergente, fait sortir sans commit : PostgreSQL rollbacke donc l'ensemble de l'acquisition.
Aucun has_* ni SELECT préventif n'est introduit.
6. Idempotence canonique
Pour Full et Archived, l'égalité exige exactement :
reference
slot
block_time
format_id
format_version
content_hash
payload bytes
Le hash n'est jamais utilisé seul lorsque les octets restent disponibles.
Résultats :
première insertion -> Inserted
même identité + contenu identique -> AlreadyPresent
même identité + contenu divergent -> Conflict
Le conflit backend sera projeté vers ERROR_CODE_RAW_CONFLICT par la façade dans pre.008.
7. Tombstone et ForceRehydrate
Lorsqu'une signature existe en Purged, le backend exige que la forme physique soit réellement minimale :
payload hot absent
payload archive absent
block_time absent
Il compare ensuite uniquement les métadonnées conservées par le tombstone :
reference
slot
format_id
format_version
content_hash
Une divergence produit Conflict.
Pour un tombstone compatible :
Normal
-> SkippedPurged / NotRecorded
ForceRehydrate
-> UPDATE payload + block_time + state=full
-> observation dans la même transaction
-> Rehydrated
Le ForceRehydrate ne masque donc jamais une divergence détectable.
8. Observation idempotente
L'observation utilise :
INSERT ...
ON CONFLICT (observation_key) DO NOTHING
RETURNING observation_key
Après collision :
SELECT observation ... FOR UPDATE
Puis le modèle reconstruit est comparé intégralement à l'observation entrante : référence transaction + provenance complète.
Résultats :
key absente + insert réussi -> Inserted
key présente + contenu identique -> AlreadyPresent
key présente + contenu divergent -> Conflict
9. Observation write séparée
record_raw_transaction_observation ne crée jamais un canonical implicite.
Sous transaction :
SELECT retention_state FROM canonical FOR UPDATE
Puis :
canonical absent -> ReferenceNotFound
canonical Purged -> NotRecorded
Full / Archived -> insertion/idempotence observation
Le verrou canonical prépare aussi la sérialisation future avec purge/rehydrate de pre.007.
10. Erreurs backend
PostgresBackendErrorKind ajoute :
Conflict
ReferenceNotFound
WriteFailed
Ces erreurs restent composées exclusivement de :
kind
phase &'static str
Aucun texte PostgreSQL, SQLSTATE, query, bind, URI, signature, hash ou payload n'est retenu.
11. Surface backend
PostgresBackend expose désormais en plus :
persist_raw_transaction_acquisition
record_raw_transaction_observation
Les traits RawTransactionWrite / RawTransactionObservationWrite ne sont pas encore implémentés directement : la tranche finale des six traits attend list_raw_transactions en pre.006, puis la rétention mutante en pre.007 et le dispatch façade en pre.008.
12. Tests déterministes
Le miroir unit_tests/raw_transaction.rs couvre en plus :
pré-I/O réseau + référence acquisition exacte
égalité canonique stricte
même hash mais payload différent -> Conflict
tombstone compatible -> Purged match
tombstone divergent -> Conflict
mapping exact des origins d'observation
Les canaris d'intégration figent :
INSERT unique direct sans has_*
FOR UPDATE après collision
observation unique idempotente
ForceRehydrate limité à UPDATE
aucun DELETE
aucune pagination/cursor
aucune transition retention mutante
aucune impl complète de trait prématurée
La concurrence PostgreSQL réelle et les preuves de rollback restent réservées au test live pre.009.
13. Migrations
Aucune ressource de migration n'est modifiée.
Checksums inchangés :
V000 d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
V001 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
14. Fichiers 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/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
15. Fichiers ajoutés
deltas/0.3.3/pre.005.md
16. Fichiers supprimés
Aucun.
17. Hors scope confirmé
Aucun changement n'est apporté à :
ksp-store-api contrats
ksp-store-lib dispatch métier
list_raw_transactions / cursor
transition Full -> Archived -> Purged
Compacted physique
RawAccountState
worker/job/app
migration SQL V000/V001
18. 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
Le test PostgreSQL live de fondation reste #[ignore]. Aucune URI réelle n'est requise pour cette tranche.