29 KiB
Validation 0.3.3 — Store/PostgreSQL RawTransaction vertical slice
1. Objet
Cette validation accompagne :
0.3.3 — Store/PostgreSQL RawTransaction vertical slice
Elle démarre en 0.3.3-pre.001 comme matrice de preuve. Les lignes non encore implémentées restent explicitement À FAIRE; elles ne sont pas présentées comme acquises.
2. Baseline stable
| Preuve | Attendu pre.001 |
Statut |
|---|---|---|
| Base de travail | archive/tag v0.3.2 |
PASS |
| workspace package version avant overlay | 0.3.2 |
PASS |
Gate opérateur 0.3.2 |
audits/check/clippy/tests Store+Config/no-default-features propres | PASS |
PostgreSQL live métier 0.3.2 |
absent ; seule fondation opt-in existe | PASS |
| Prompt release | prompts/022-V0_3_3_START_PROMPT.md |
PASS |
| Archive historique kbot3 | audit ciblé uniquement | PASS |
2.1 Inventaire exact 0.3.2
ksp-store-api modules=capability,error,model root pub use=60
ksp-store-lib modules=constants,error,health,settings,store root pub use=84, default feature=postgres
ksp-store-postgres-lib modules=constants,error,health,migration,runtime root pub use=7
V000__bootstrap.sql SHA-256=d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
std.store format_version 1
std.store profiles devnet/devnet, mainnet/mainnet-beta, testnet/testnet
foundation live PASS PostgreSQL 17 en pre.008 et pre.010
Le test live est #[ignore] dans les gates ordinaires mais sa preuve réelle antérieure est documentée dans 019-V0_3_2_STORE_POSTGRES_FOUNDATION.md.
3. Scope exact des capacités
| Capacité | Backend PostgreSQL | Façade Store | Preuve finale |
|---|---|---|---|
RawTransactionRead |
À FAIRE | À FAIRE | unit + integration + live |
RawTransactionWrite |
À FAIRE | À FAIRE | unit + concurrency live |
RawTransactionObservationRead |
À FAIRE | À FAIRE | unit + live |
RawTransactionObservationWrite |
À FAIRE | À FAIRE | unit + concurrency live |
RawTransactionRetentionRead |
À FAIRE | À FAIRE | unit + live |
RawTransactionRetentionWrite |
À FAIRE | À FAIRE | unit + race live |
Aucune capacité RawAccountState, structurale N2, job ou worker n'est admise dans cette matrice.
3.1 Matrice RawTransactionRead
| Axe | Preuve/attendu |
|---|---|
| input | reference pour get; query pour list |
| invariants pré-I/O | network exact; cursor lié à query |
| transaction SQL | aucune; statement cohérent |
| rows touchées | transaction + archive pour get; index transaction pour list |
| idempotence | N/A |
| conflit | N/A; corruption != conflict métier |
| absence/not-found | get None; list vide |
| error mapping | query/wrong-network/data/query-failed sûrs |
| concurrence | statement snapshot; aucun snapshot inter-pages |
| test déterministe | full/archive/purged, u64 max, ranges, cursor hostile |
| test PostgreSQL live | get exact + pages multi-slot/ties dans deux directions |
3.2 Matrice RawTransactionWrite
| Axe | Preuve/attendu |
|---|---|
| input | transaction + observation + acquisition mode |
| invariants pré-I/O | deux networks exacts; observation reference = canonical reference |
| transaction SQL | une transaction atomique |
| rows touchées | canonical + observation + archive si état existant Archived |
| idempotence | contenu exact identique -> AlreadyPresent |
| conflit | divergence même signature -> ERROR_CODE_RAW_CONFLICT |
| absence/not-found | insert; Purged Normal compatible -> SkippedPurged/NotRecorded |
| error mapping | API model/conflict; physical write/data sûrs |
| concurrence | unique insert + FOR UPDATE du gagnant |
| test déterministe | equality/conflict/tombstone/modes/redaction |
| test PostgreSQL live | identical/divergent concurrent + rollback + force |
3.3 Matrice RawTransactionObservationRead
| Axe | Preuve/attendu |
|---|---|
| input | observation key |
| invariants pré-I/O | key API valide; database network déjà bound |
| transaction SQL | aucune |
| rows touchées | observation |
| idempotence | N/A |
| conflit | N/A |
| absence/not-found | None |
| error mapping | stored-row invalid/query-failed sûrs |
| concurrence | observation immuable, statement snapshot |
| test déterministe | provenance complète/optionnelle + hostile row |
| test PostgreSQL live | observation round-trip exact |
3.4 Matrice RawTransactionObservationWrite
| Axe | Preuve/attendu |
|---|---|
| input | observation |
| invariants pré-I/O | observation transaction network exact |
| transaction SQL | oui |
| rows touchées | canonical verrouillé + observation |
| idempotence | same key + fields identical -> AlreadyPresent |
| conflit | same key + divergence -> ERROR_CODE_RAW_CONFLICT |
| absence/not-found | canonical absent -> safe not-found; Purged -> NotRecorded |
| error mapping | provenance/conflict API + not-found/write sûrs |
| concurrence | canonical FOR UPDATE + unique observation key |
| test déterministe | equality/missing/purged/error canaries |
| test PostgreSQL live | identical/divergent observation races |
3.5 Matrice RawTransactionRetentionRead
| Axe | Preuve/attendu |
|---|---|
| input | transaction reference |
| invariants pré-I/O | network exact |
| transaction SQL | aucune |
| rows touchées | canonical metadata |
| idempotence | N/A |
| conflit | N/A |
| absence/not-found | state unknown -> None; tombstone only Purged |
| error mapping | wrong-network/stored-metadata invalid sûrs |
| concurrence | état committé par statement |
| test déterministe | Full/Archived/Purged + tombstone minimal |
| test PostgreSQL live | reads après chaque transition |
3.6 Matrice RawTransactionRetentionWrite
| Axe | Preuve/attendu |
|---|---|
| input | retention transition |
| invariants pré-I/O | network exact; Compacted non supporté rejeté avant pool |
| transaction SQL | oui |
| rows touchées | canonical + archive |
| idempotence | current == target -> AlreadyAtTarget |
| conflit | expected perdu -> ExpectedStateMismatch |
| absence/not-found | unknown reference -> safe not-found |
| error mapping | invalid transition API; unsupported/physical write sûrs |
| concurrence | FOR UPDATE canonique |
| test déterministe | transition matrix + Compacted unsupported |
| test PostgreSQL live | transition/purge/write/force races |
4. Design physique V001
| Élément | Décision pre.001 |
Statut |
|---|---|---|
| Migration | V001 embedded, après généralisation du moteur V000 | PASS design |
| Database identity | singleton ksp_store_identity |
PASS design |
| Réseau | un réseau exact par base V001 | PASS design |
| Transaction key | signature BYTEA(64) logique |
PASS design |
| Observation key | BYTEA(32) |
PASS design |
| Slot | NUMERIC(20,0) ; aucun narrowing u64 |
PASS design |
| Format version | BIGINT, check domaine u32 non nul |
PASS design |
| Timestamp | BIGINT, check borne RawTimestamp |
PASS design |
| Payload | BYTEA, 1..=16 MiB |
PASS design |
| Archive | relation dédiée hors index hot-path | PASS design |
| Purge | tombstone minimal dans ligne canonique | PASS design |
| Compacted | non simulé ; rejet stable tant qu'aucun codec réel | PASS design |
| Navigation | index partiel (slot, signature) hors purged |
PASS design |
5. Inventaire SQL minimal prévu
5.1 Tables
ksp_store_identity
ksp_raw_transactions
ksp_raw_transaction_observations
ksp_raw_transaction_archive_payloads
Statut pre.001 : design seulement. Aucun de ces objets V001 n'est encore créé.
5.2 Indexes
PK ksp_raw_transactions(signature)
PK ksp_raw_transaction_observations(observation_key)
PK ksp_raw_transaction_archive_payloads(signature)
ix_ksp_raw_transactions_slot_signature(slot, signature)
WHERE retention_state <> 'purged'
Tout index additionnel doit être justifié par une requête effectivement ajoutée à la release.
6. Mapping numérique sans narrowing
| Valeur API | Représentation SQL | Cas limites obligatoires |
|---|---|---|
slot: u64 |
NUMERIC(20,0) |
0, i64::MAX, i64::MAX + 1, u64::MAX |
format_version: u32 |
BIGINT |
1, i32::MAX + 1, u32::MAX |
RawTimestamp |
BIGINT |
0, max documenté, max+1 DB hostile |
| source payload size | BIGINT |
0, 64 MiB, >64 MiB DB hostile |
| page limit | bind signé de limit+1 seulement si représentable |
i64::MAX - 1, valeur supérieure rejetée |
Critère : aucune branche ne doit utiliser as i64, as i32, clamp ou saturation pour faire tenir une valeur valide API.
7. Binding réseau
7.1 Bootstrap
| Cas | Résultat attendu |
|---|---|
| V001 appliquée pour la première fois | identity créée avec le réseau runtime dans la transaction de bootstrap |
| réouverture même réseau | succès idempotent |
| réouverture autre réseau | erreur terminale sûre |
| V001 présente mais identity absente | mismatch/corruption, jamais rebind silencieuse |
| identity malformée | erreur terminale sûre |
| migration pending + auto_migrate disabled | backend non prêt, aucune persistence métier |
7.2 Pré-I/O capability
Pour tout input portant un RawNetworkId, un mismatch avec Store.network ou PostgresBackend.network doit être détecté avant acquisition d'un client du pool.
8. Idempotence canonique
| État existant | Input | Mode | Outcome attendu |
|---|---|---|---|
| absent | canonique+observation valides | Normal | Inserted / Inserted |
| Full identique | observation identique | Normal | AlreadyPresent / AlreadyPresent |
| Full identique | observation nouvelle | Normal | AlreadyPresent / Inserted |
| Full divergent | n'importe quelle observation | Normal | ERROR_CODE_RAW_CONFLICT, aucun write |
| Archived identique | observation nouvelle | Normal | AlreadyPresent / Inserted |
| Purged compatible | observation | Normal | SkippedPurged / NotRecorded |
| Purged divergent sur tombstone | observation | Normal | ERROR_CODE_RAW_CONFLICT |
| Purged compatible | observation | ForceRehydrate | Rehydrated / Inserted ou AlreadyPresent |
| Purged divergent | observation | ForceRehydrate | ERROR_CODE_RAW_CONFLICT |
L'égalité canonique compare les octets réels lorsqu'ils sont encore retenus. Le content hash seul n'est jamais une preuve suffisante dans Full ou Archived.
9. Atomicité canonique + observation
9.1 Cas obligatoires
| Scénario | Preuve attendue |
|---|---|
| nouvel insert, observation valide | les deux commit |
| nouvel insert, observation divergente sur key déjà existante | rollback canonique + observation |
| canonical conflict | aucune nouvelle observation |
| cancellation avant commit | aucun succès partiel visible |
| erreur SQL observation | rollback canonique |
9.2 FK
Le FK observation -> transaction est :
- non nullable ;
- sans
ON DELETE SET NULL; - impossible à contourner dans le repository métier ;
- cohérent avec le fait que purge conserve la ligne canonique/tombstone.
10. Concurrence
| Race | Attendu |
|---|---|
| deux inserts identiques même signature | un Inserted, l'autre AlreadyPresent; données identiques |
| deux inserts divergents même signature | un gagnant, l'autre ERROR_CODE_RAW_CONFLICT |
| deux observations identiques même key | un Inserted, l'autre AlreadyPresent |
| deux observations divergentes même key | un gagnant, l'autre conflit |
| deux transitions vers même target | un transitionne, l'autre AlreadyAtTarget |
| transitions avec expected incompatibles | gagnant déterministe + ExpectedStateMismatch |
| purge vs acquisition | sérialisation sur ligne canonique |
| purge vs ForceRehydrate | aucun état moitié purgé/moitié full |
| deux ForceRehydrate | idempotence ou conflit selon contenu |
La preuve live doit utiliser de vraies tâches concurrentes, pas une simulation séquentielle renommée « concurrency ».
11. Observation write séparée
| Cas | Attendu |
|---|---|
| canonique Full présent, key absente | Inserted |
| canonique Archived présent, key absente | Inserted |
| key identique | AlreadyPresent |
| key divergente | ERROR_CODE_RAW_CONFLICT |
| canonique absent | erreur sûre, aucune création implicite |
| canonique Purged | NotRecorded |
| mauvais réseau | rejet pré-I/O |
L'absence du canonical requis est classée store.raw_reference_not_found. Ce code runtime ne modifie pas les invariants de construction de ksp-store-api.
12. Lecture
12.1 RawTransaction
| État physique | get_raw_transaction |
|---|---|
| Full cohérent | Some(RawTransaction) depuis payload hot |
| Archived cohérent | Some(RawTransaction) depuis archive relation |
| Purged | None |
| row inconnue | None |
| état/bytes/taille/numérique incohérents | erreur sûre de donnée physique |
12.2 Observation
| Cas | Résultat |
|---|---|
| key connue | observation reconstruite avec network du backend bound |
| key inconnue | None |
| bytes/provenance invalides en DB | erreur sûre |
13. Pagination
13.1 Ordre
Ascending = (slot ASC, signature ASC)
Descending = (slot DESC, signature DESC)
13.2 Dataset obligatoire
Le test doit contenir au minimum :
- plusieurs slots ;
- au moins trois signatures distinctes sur le même slot ;
- suffisamment de rows pour trois pages ;
- une row Purged exclue de la liste ;
- bornes start/end présentes et absentes.
13.3 Cursor V1
| Test | Attendu |
|---|---|
| round-trip | exact |
| taille != 109 | rejet |
| magic modifié | rejet |
| version inconnue | rejet |
| digest modifié | rejet |
| direction différente | rejet |
| range différente | rejet |
| network différent | rejet |
| last slot hors range | rejet |
| random bytes <=4096 | rejet sans panic/echo |
| cursor >4096 au niveau API | déjà rejeté par RawPageCursor |
13.4 Pas de cap métier
Aucun test ne doit imposer 100, 500 ou 1000 comme limite Store. La seule erreur de taille côté backend doit correspondre à la représentation physique nécessaire au LIMIT + 1.
14. Rétention
14.1 États supportés physiquement en 0.3.3
Full
Archived
Purged
Compacted reste un état logique API connu, explicitement optionnel depuis le plan 0.3.1, mais non représenté mensongèrement par PostgreSQL 0.3.3.
14.2 Matrice
| Current | Expected | Target | Attendu PostgreSQL 0.3.3 |
|---|---|---|---|
| Full | Full | Archived | Transitioned |
| Archived | Full | Archived | AlreadyAtTarget |
| Archived | Archived | Purged | Transitioned |
| Purged | Archived | Purged | AlreadyAtTarget |
| Full | Archived | Purged | input transition invalide au niveau API |
| Full | Full | Compacted | store.postgres_retention_compaction_unsupported, pré-I/O |
| Compacted attendu | Compacted | Archived | store.postgres_retention_compaction_unsupported, pré-I/O |
| Full | Archived | Archived | ExpectedStateMismatch |
14.3 Tombstone
Après purge, la reconstruction autorisée contient exactement :
reference
slot
format_id
format_version
content_hash
Le block time et les octets de payload ne restent pas dans la table hot ni dans la table archive.
15. ForceRehydrate
| Cas | Attendu |
|---|---|
| Purged + tombstone compatible | restaure Full et payload, outcome Rehydrated |
| Purged + slot divergent | conflict |
| Purged + format id/version divergent | conflict |
| Purged + content hash divergent | conflict |
| Full/Archived identique + Force | idempotence normale, pas faux Rehydrated |
| race avec purge | sérialisation ; état final cohérent |
16. Erreurs et redaction
16.1 Canary hostile
Les tests devront injecter des valeurs sentinelles dans :
URI
server error text
SQL text simulé
signature
observation key
content hash
payload
provider/protocol/method
cursor bytes
row DB invalide
Aucune sentinelle ne doit apparaître dans Display, Debug ou message sûr retourné.
16.2 Classification
| Classe | Interface attendue |
|---|---|
| input/model invalide | codes ksp-store-api acquis |
| canonical/observation divergent | ERROR_CODE_RAW_CONFLICT |
| retention representation non supportée | store.postgres_retention_compaction_unsupported |
| réseau incohérent | code statique, pas de réseau hostile rendu |
| DB row invalide | code statique de corruption/data invalid |
| query/write PostgreSQL | code statique backend/runtime |
| pool/cancel/migration | catégories runtime existantes ou extension statique justifiée |
SQLSTATE et texte PostgreSQL ne font pas partie du contrat public.
17. Migration engine
17.1 Multi-version
Preuves nécessaires avant V001 :
- V000 reste checksum-identique ;
- registre embedded ordonné
[V000, V001]; - historique vide -> bootstrap correct ;
- V000 seule + auto migrate -> V001 appliquée ;
- V000 seule + auto migrate disabled -> pending/non-ready ;
- V001 checksum divergent -> mismatch ;
- version > V001 -> schema newer ;
- ligne de migration manquante/divergente -> mismatch ;
- deux open concurrents -> advisory lock, historique unique.
17.2 Binding réseau atomique
L'identité réseau doit être créée/validée avant commit du bootstrap V001. Un crash ne doit pas pouvoir laisser V001 « appliquée » avec une base prête mais sans identité exploitable.
18. Boundaries
Les scans/tests doivent garantir :
ksp-store-lib -X-> tokio-postgres/deadpool-postgres/rustls
ksp-store-lib -X-> SQL literal
ksp-store-lib -X-> std::env / dotenv / libpq files
ksp-store-postgres-lib -X-> ksp-store-lib
ksp-store-postgres-lib -X-> direct Config ownership
ksp-store-api -X-> backend physical dependency
La façade ne publie aucun Client, Transaction, pool, TLS type, SQL row ou type backend.
19. Feature matrix
| Build | Attendu |
|---|---|
ksp-store-lib default |
feature postgres active, six capacités dispatchables une fois implémentées |
ksp-store-lib --no-default-features |
compile ; aucune dépendance backend tirée |
| config backend postgres sans feature | erreur backend_not_compiled avant I/O |
| malformed postgres URI avec feature | erreur safe pré-I/O comme 0.3.2 |
20. Audit historique — preuves de non-régression
Les tests/source scans devront empêcher le retour des patterns historiques rejetés :
has_raw_transaction_signature
BIGSERIAL id comme identity publique
slot BIGINT dans V001
canonical_json JSONB
processing_state
ON DELETE SET NULL
raw SQL error text dans Error
cap 500/1000 dans Store pagination
21. Planning de validation par tranche
pre.001
- documents plan/validation ;
- version
0.3.3-pre.1; - aucun SQL métier ;
- gate workspace actuel.
pre.002
- migration registry multi-version ;
- tests V000 conservés ;
- préparation binding.
pre.003
- V001 + tables/constraints/indexes ;
- binding réseau ;
- checksum/inventory tests.
pre.004
- row codecs et lectures ;
- malformed DB matrix.
pre.005
- atomic writes/idempotence/conflict ;
- observation writes.
pre.006
- list/cursor/order/ranges ;
- hostile cursor matrix.
pre.007
- archive/purge/tombstone/ForceRehydrate ;
- Compacted unsupported stable.
pre.008
- façade/feature dispatch ;
- no physical leak ;
- pre-I/O mismatch.
pre.009
- PostgreSQL live opt-in complet.
pre.010
- hardening/completeness.
pre.011
- gate technique final.
pre.012
- réconciliation documentaire.
pre.013
- publication prep.
rel.001
- publication stable.
22. Gate courant pre.001
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
23. Critère de clôture de release
0.3.3 ne pourra être déclarée stable que si :
- les six capacités sont réellement implémentées sur
PostgresBackendet dispatchées parStore; - aucune valeur valide API n'est narrowée ;
- l'idempotence distingue strictement identité identique et contenu divergent ;
- canonical+observation est atomique ;
- pagination est déterministe et cursorisée ;
- réseau physique incorrect est refusé ;
- rétention supportée est honnête et atomique ;
Compactedn'est pas prétendu sans représentation réelle ;- tombstone et ForceRehydrate respectent les contrats acquis ;
- les erreurs ne fuient aucun secret/SQL/server text ;
- le live PostgreSQL opt-in couvre concurrence et rollback ;
- tous les gates techniques/doc sont propres ;
- aucun scope
RawAccountState/worker/app/N2 n'a été ouvert.