v0.3.4-pre.001
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 363
|
# version: 364
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.3"
|
version = "0.3.4-pre.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
206
deltas/0.3.4/pre.001.md
Normal file
206
deltas/0.3.4/pre.001.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<!-- file: deltas/0.3.4/pre.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.4-pre.001` — audit et physical design `RawAccountState` PostgreSQL
|
||||||
|
|
||||||
|
## 1. Objet
|
||||||
|
|
||||||
|
Ouverture de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.4 — Store/PostgreSQL RawAccountState + complétude/conformance RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette tranche reste volontairement un gate de lecture/audit/design/sizing. Aucun SQL V002, aucune table account, aucun repository account et aucune impl capability account ne sont ajoutés.
|
||||||
|
|
||||||
|
## 2. Base
|
||||||
|
|
||||||
|
Base canonique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.3
|
||||||
|
workspace.package.version = 0.3.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni avec la reprise est propre sur audits Rust/Markdown, checks, Clippy et suites ciblées/workspace de la release stable ; les live tests restent opt-in/ignored.
|
||||||
|
|
||||||
|
## 3. Version
|
||||||
|
|
||||||
|
Version workspace après overlay :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.4-pre.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Label de livraison :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.4-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md
|
||||||
|
docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md
|
||||||
|
deltas/0.3.4/pre.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Fichier modifié
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifications :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version : 0.3.3 -> 0.3.4-pre.1
|
||||||
|
file header version : 363 -> 364
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## 7. Audit effectué
|
||||||
|
|
||||||
|
Ont été relus avant design : règles globales/KSP/Rust/dépendances/docs/versioning ; architectures Store/data/execution/workers ; plans/validations `0.3.1`, `0.3.2`, `0.3.3` ; source/tests Store API/façade/backend ; Config `std.store` ; migrations V000/V001 ; archive kbot3 ciblée account.
|
||||||
|
|
||||||
|
Inventaire confirmé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
10 capabilities RAW contractuelles
|
||||||
|
6 RawTransaction implémentées physiquement dans PostgresBackend + Store
|
||||||
|
4 RawAccount encore non implémentées et explicitement fermées par canaries 0.3.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Checksums recalculés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 = d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
||||||
|
V001 = 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Décisions majeures
|
||||||
|
|
||||||
|
### 8.1 V002
|
||||||
|
|
||||||
|
V002 sera additive, sous `migrations/v002_raw_account_state/`, avec seulement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_raw_account_states
|
||||||
|
ksp_raw_account_observations
|
||||||
|
```
|
||||||
|
|
||||||
|
V000/V001 restent byte-immutables.
|
||||||
|
|
||||||
|
### 8.2 Identité state
|
||||||
|
|
||||||
|
```text
|
||||||
|
network API + (pubkey, slot, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le réseau est le binding DB existant. PK physique `(pubkey, slot, state_hash)`. Pubkey/owner/hash en BYTEA fixed-width ; slot/lamports/rent_epoch en NUMERIC(20,0). Data account en BYTEA complet, vide autorisé, maximum 16 MiB.
|
||||||
|
|
||||||
|
### 8.3 Observation
|
||||||
|
|
||||||
|
L'observation référence le state par FK composite et conserve provenance + `is_startup` + `transaction_signature` + `write_version`. Ces metadata restent observation-only. Aucune FK transaction n'est ajoutée.
|
||||||
|
|
||||||
|
### 8.4 Atomicité/idempotence
|
||||||
|
|
||||||
|
Pattern retenu : transaction PostgreSQL, `INSERT ... ON CONFLICT DO NOTHING RETURNING`, puis `SELECT ... FOR UPDATE` et comparaison exacte sur collision. Aucun `ON CONFLICT DO UPDATE`, aucun `has_*`, aucun overwrite silencieux.
|
||||||
|
|
||||||
|
`record_raw_account_observation` vérifie le state préexistant et ne le crée jamais implicitement.
|
||||||
|
|
||||||
|
### 8.5 Pagination
|
||||||
|
|
||||||
|
Ordre total :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(slot, pubkey, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cursor V1 account fixe 109 bytes, magic `KSPA`, domaine `KSP/raw-account-state-cursor/v1`, digest lié au réseau, filtre pubkey, range, direction et dernière clé. Le cursor transaction `KSPT` est donc non rejouable.
|
||||||
|
|
||||||
|
Index métier additionnel unique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(slot, pubkey, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun index owner/provider/time/status.
|
||||||
|
|
||||||
|
## 9. Audit kbot3
|
||||||
|
|
||||||
|
Classification :
|
||||||
|
|
||||||
|
- **REPRENDRE** : invariants unicité/FK et principe d'index account+slot ; NUMERIC pour valeurs monétaires ;
|
||||||
|
- **REDESSINER** : identité durable, BYTEA fixed-width, bytes complets, u64 exact, observation séparée, atomicité/conflict/cursor ;
|
||||||
|
- **REPORTER** : normalisation CORE, processing ledger, batch 1000, indexes owner/provider/time/status ;
|
||||||
|
- **REJETER** : sqlx/monolithe, BIGSERIAL comme identité publique, base64 physique, BIGINT narrowing, status/error RAW, maintenance destructive comme comportement normal.
|
||||||
|
|
||||||
|
Aucun code historique n'est copié.
|
||||||
|
|
||||||
|
## 10. Threat model
|
||||||
|
|
||||||
|
Le plan couvre mauvaise DB/réseau, fixed-width hostile, overflow u64, 16 MiB, observation orpheline, rollback partiel, même pubkey+slot multi-state, conflits exacts, collisions observation, races, cursor hostile/cross-query/cross-family, ordre pagination, metadata Yellowstone, fuite SQL/server text, cancellation et migration mismatch.
|
||||||
|
|
||||||
|
## 11. Sizing recalibré
|
||||||
|
|
||||||
|
Le forecast est étendu à `pre.013` afin de séparer V002 registry/tables de ses contraintes/schema compatibility, puis acquisition write de la concurrence/observation supplémentaire. Le gate technique final reste suivi d'une lane documentaire et d'une lane publication distinctes.
|
||||||
|
|
||||||
|
## 12. Hors scope conservé
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api redesign
|
||||||
|
account retention/archive/purge
|
||||||
|
STRUCTURAL/CORE/DECODED/DOMAIN persistence
|
||||||
|
Interface events
|
||||||
|
workers/jobs/backfills
|
||||||
|
batch/priorité/backlog policy
|
||||||
|
nouveaux indexes sans query
|
||||||
|
nouveaux backends/apps
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question de contrat bloquante après `pre.001`. Les numéros exacts de ressources SQL et le découpage de helpers privés sont laissés à `pre.002/pre.003` sans pouvoir modifier les invariants figés.
|
||||||
|
|
||||||
|
## 14. Validations exécutées
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit: clean
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.4
|
||||||
|
Markdown table audit: clean (240 table(s), 134 file(s))
|
||||||
|
|
||||||
|
assertions structurelles locales
|
||||||
|
workspace.package.version = 0.3.4-pre.1
|
||||||
|
Cargo.toml header version = 364
|
||||||
|
plan 025 / validation 021 / delta pre.001 présents
|
||||||
|
aucun répertoire migrations/v002_raw_account_state
|
||||||
|
aucun module backend raw_account créé
|
||||||
|
```
|
||||||
|
|
||||||
|
## 15. Validations non exécutées dans le conteneur d'assemblage
|
||||||
|
|
||||||
|
L'exécutable `cargo` n'est pas disponible dans cet environnement. L'appel `cargo fmt --all` s'arrête donc immédiatement avec `cargo: command not found`; aucune commande Cargo post-overlay n'est déclarée PASS.
|
||||||
|
|
||||||
|
À exécuter côté opérateur :
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de la base `v0.3.3` fourni avec la reprise était intégralement vert avant cet overlay. Cet historique ne remplace pas le gate post-overlay ci-dessus.
|
||||||
610
docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md
Normal file
610
docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
<!-- file: docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Plan `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
||||||
|
|
||||||
|
## 1. Statut du gate `pre.001`
|
||||||
|
|
||||||
|
`0.3.4-pre.001` est un gate de conception. Il ne crée **aucun** SQL V002, aucune table account et aucun repository account. Il fixe la forme physique et le séquencement nécessaires avant implémentation.
|
||||||
|
|
||||||
|
Base canonique auditée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.3
|
||||||
|
workspace.package.version = 0.3.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Version de travail de cette prerelease :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.4-pre.1
|
||||||
|
label = 0.3.4-pre.001
|
||||||
|
```
|
||||||
|
|
||||||
|
Décision de scope : `ksp-store-api` reste inchangée. L'audit n'a révélé aucun gap backend-agnostic bloquant ; la difficulté restante est exclusivement l'implémentation physique PostgreSQL et son dispatch par la façade.
|
||||||
|
|
||||||
|
## 2. Invariants hérités et frontières
|
||||||
|
|
||||||
|
La trajectoire reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.4` ferme seulement la complétude physique **RAW Store/PostgreSQL**. Store persiste et sert ; il ne choisit ni batch-size, ni priorité, ni backlog, ni retry job, ni stratégie de backfill.
|
||||||
|
|
||||||
|
Frontières inchangées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib -> ksp-store-api
|
||||||
|
ksp-store-lib[postgres] -> ksp-store-postgres-lib
|
||||||
|
ksp-store-postgres-lib -> ksp-store-api
|
||||||
|
ksp-store-postgres-lib -X-> ksp-store-lib
|
||||||
|
Config -> Store autorisé
|
||||||
|
Store/backend -X-> Config
|
||||||
|
Transport -X-> Store
|
||||||
|
consumer ordinaire -> ksp-store-lib
|
||||||
|
consumer ordinaire -X-> ksp-store-postgres-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une instance `Store` reste liée à un `RawNetworkId` et une base physique. Aucun multiplexage réseau/database n'est ajouté.
|
||||||
|
|
||||||
|
## 3. Inventaire de capabilities : 6/10 -> 10/10
|
||||||
|
|
||||||
|
| Capability | Impl physique stable | État base | Action 0.3.4 |
|
||||||
|
|--------------------------------|-------------------------|---------------|--------------|
|
||||||
|
| RawTransactionRead | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawTransactionWrite | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawTransactionObservationRead | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawTransactionObservationWrite | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawTransactionRetentionRead | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawTransactionRetentionWrite | PostgresBackend + Store | stable 0.3.3 | préserver |
|
||||||
|
| RawAccountStateRead | aucune impl physique | pending 0.3.4 | implémenter |
|
||||||
|
| RawAccountStateWrite | aucune impl physique | pending 0.3.4 | implémenter |
|
||||||
|
| RawAccountObservationRead | aucune impl physique | pending 0.3.4 | implémenter |
|
||||||
|
| RawAccountObservationWrite | aucune impl physique | pending 0.3.4 | implémenter |
|
||||||
|
|
||||||
|
Canaries qui ferment encore volontairement la surface account dans `v0.3.3` :
|
||||||
|
|
||||||
|
- `crates/ksp-store-lib/tests/dependency_boundary.rs` interdit les quatre `impl RawAccount* for Store` ;
|
||||||
|
- `crates/ksp-store-lib/tests/hardening_completeness.rs` fige six impl transaction et interdit les quatre account ;
|
||||||
|
- `crates/ksp-store-postgres-lib/tests/dependency_boundary.rs` interdit les quatre `impl RawAccount* for PostgresBackend` ;
|
||||||
|
- `crates/ksp-store-postgres-lib/tests/hardening_completeness.rs` interdit encore `impl ksp_store_api::RawAccount*` et toute matérialisation `ksp_raw_account` dans la slice transaction.
|
||||||
|
|
||||||
|
Ces canaries devront être **transformées**, pas simplement supprimées : à `pre.008`, elles devront prouver exactement six capabilities transaction + quatre account. Les canaries qui imposent que V000 et `health.rs` restent business-free demeurent valides et ne doivent pas être assouplies.
|
||||||
|
|
||||||
|
### 3.1 Matrice détaillée des quatre capabilities account
|
||||||
|
|
||||||
|
| Capability | Input | Output | Guard réseau | SQL/transaction | Idempotence | Conflit | Absence/référence | Pagination | Error mapping | Preuve live |
|
||||||
|
|----------------------------|---------------------|---------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------|----------------------------------|----------------------------------------|-----------------------------------|---------------------|---------------------------------------------------------------------------|------------------------------------------------|
|
||||||
|
| RawAccountStateRead | reference / query | Option<State> / Page<Reference> | reference/query network == backend | SELECT ; list keyset | n/a | n/a | get absent -> None | oui, cursor account | WrongNetwork, QueryInvalid, PageLimitUnsupported, DataInvalid, ReadFailed | get exact + list matrix |
|
||||||
|
| RawAccountStateWrite | state + observation | RawAcquisitionWriteOutcome | state + observation networks + exact same reference | transaction state+observation | exact existing -> AlreadyPresent | same full ref divergent -> RawConflict | n/a | non | WrongNetwork, Conflict, DataInvalid, WriteFailed | atomicité + identical/divergent + cancellation |
|
||||||
|
| RawAccountObservationRead | observation_key | Option<Observation> | aucun input réseau ; backend fournit son network à la référence reconstruite | SELECT by PK | n/a | n/a | absent -> None | non | DataInvalid, ReadFailed | round-trip optional metadata |
|
||||||
|
| RawAccountObservationWrite | observation | RawObservationWriteOutcome | observation.account.network == backend | transaction ; state FOR KEY SHARE + observation insert | exact existing -> AlreadyPresent | same key divergent -> RawConflict | state absent -> ReferenceNotFound | non | WrongNetwork, ReferenceNotFound, Conflict, DataInvalid, WriteFailed | missing ref + collision + concurrence |
|
||||||
|
|
||||||
|
## 4. Immutabilité V000/V001
|
||||||
|
|
||||||
|
Recalcul indépendant sur les bytes de l'archive stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 legacy SQL SHA-256 = d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
||||||
|
V001 logical resources = 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
|
||||||
|
V001 resource count = 40
|
||||||
|
```
|
||||||
|
|
||||||
|
L'algorithme V001 est celui du moteur stable : domaine `ksp-migration-resources-v1\0`, puis pour chaque ressource ordonnée `id + NUL + SQL bytes + NUL` avant SHA-256.
|
||||||
|
|
||||||
|
Règles V002 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 : immuable
|
||||||
|
V001 : immuable
|
||||||
|
V002 : additive uniquement
|
||||||
|
nom logique proposé : raw_account_state
|
||||||
|
répertoire proposé : migrations/v002_raw_account_state/
|
||||||
|
resources proposées : tables/ + constraints/ + indexes/
|
||||||
|
hook migration supplémentaire : aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
Le binding réseau singleton V001 est réutilisé ; V002 ne duplique pas `network` dans chaque ligne métier.
|
||||||
|
|
||||||
|
## 5. Audit field-by-field du contrat account
|
||||||
|
|
||||||
|
### 5.1 `RawAccountStateReference` + `RawAccountState`
|
||||||
|
|
||||||
|
Matrice complète modèle -> physique :
|
||||||
|
|
||||||
|
| Champ API | Rust | Cardinalité | PostgreSQL | Borne/constraint | Mapping read | Mapping write | Risque perte |
|
||||||
|
|------------|----------------|-------------|--------------------|-----------------------|----------------------------------|------------------------|---------------------------------|
|
||||||
|
| network | RawNetworkId | 1 | ksp_store_identity | exact backend network | binding runtime -> reference | aucune colonne row | aucun si guard exact |
|
||||||
|
| pubkey | Pubkey | 1 | BYTEA | exact 32 bytes | fixed bytes -> Pubkey | Pubkey bytes -> BYTEA | aucun |
|
||||||
|
| slot | u64 | 1 | NUMERIC(20,0) | 0..u64::MAX | decimal exact -> u64 | u64 -> decimal NUMERIC | narrowing interdit |
|
||||||
|
| state_hash | RawContentHash | 1 | BYTEA | exact 32 bytes | fixed bytes -> RawContentHash | hash bytes -> BYTEA | aucun ; pas de recalcul backend |
|
||||||
|
| lamports | u64 | 1 | NUMERIC(20,0) | 0..u64::MAX | decimal exact -> u64 | u64 -> decimal NUMERIC | narrowing interdit |
|
||||||
|
| owner | Pubkey | 1 | BYTEA | exact 32 bytes | fixed bytes -> Pubkey | Pubkey bytes -> BYTEA | aucun |
|
||||||
|
| executable | bool | 1 | BOOLEAN | NOT NULL | bool direct | bool direct | aucun |
|
||||||
|
| rent_epoch | u64 | 1 | NUMERIC(20,0) | 0..u64::MAX | decimal exact -> u64 | u64 -> decimal NUMERIC | narrowing interdit |
|
||||||
|
| data | Box<[u8]> | 1 | BYTEA | 0..=16 MiB | bytes + borne -> RawAccountState | bytes exacts -> BYTEA | base64/parse interdits |
|
||||||
|
|
||||||
|
Vue synthétique de projection :
|
||||||
|
|
||||||
|
| Champ API | Type API | Projection V002 | Invariant |
|
||||||
|
|------------|---------------------------|------------------------------------------|-----------------------------------------------------------------|
|
||||||
|
| network | RawNetworkId | binding DB existant `ksp_store_identity` | pas de colonne par ligne ; guard exact avant I/O |
|
||||||
|
| pubkey | Pubkey / 32 bytes | BYTEA NOT NULL | `octet_length = 32` ; composant PK |
|
||||||
|
| slot | u64 | NUMERIC(20,0) NOT NULL | 0..18446744073709551615 ; aucun narrowing |
|
||||||
|
| state_hash | RawContentHash / 32 bytes | BYTEA NOT NULL | `octet_length = 32` ; composant PK ; jamais recalculé par Store |
|
||||||
|
| lamports | u64 | NUMERIC(20,0) NOT NULL | domaine u64 exact |
|
||||||
|
| owner | Pubkey / 32 bytes | BYTEA NOT NULL | `octet_length = 32` |
|
||||||
|
| executable | bool | BOOLEAN NOT NULL | mapping direct |
|
||||||
|
| rent_epoch | u64 | NUMERIC(20,0) NOT NULL | domaine u64 exact |
|
||||||
|
| data | Box<[u8]> | BYTEA NOT NULL | 0..=16 MiB ; vide autorisé ; aucun base64 |
|
||||||
|
|
||||||
|
L'identité physique canonique retenue est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PRIMARY KEY (pubkey, slot, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le réseau fait partie de la référence API mais est porté physiquement par `ksp_store_identity` pour toute la base. Cette clé permet explicitement plusieurs états du même pubkey au même slot lorsque `state_hash` diffère.
|
||||||
|
|
||||||
|
`state_hash` est un digest **producer-owned**. Le backend ne le recalcule pas et ne suppose pas qu'il s'agit du hash de `data` seulement. L'idempotence vérifie donc le contenu complet malgré l'égalité du hash.
|
||||||
|
|
||||||
|
### 5.2 `RawAccountObservation`
|
||||||
|
|
||||||
|
Matrice complète modèle -> physique :
|
||||||
|
|
||||||
|
| Champ API | Rust | Cardinalité | PostgreSQL | Borne/constraint | Mapping read | Mapping write | Risque perte |
|
||||||
|
|---------------------------|---------------------------------|-------------|--------------------|------------------------|----------------------------|------------------------|---------------------------|
|
||||||
|
| observation_key | RawObservationKey | 1 | BYTEA | exact 32 bytes | fixed bytes -> key | key bytes -> BYTEA | aucun |
|
||||||
|
| account.pubkey | Pubkey | 1 | BYTEA | exact 32 bytes + FK | fixed bytes -> reference | bytes -> FK columns | aucun |
|
||||||
|
| account.slot | u64 | 1 | NUMERIC(20,0) | 0..u64::MAX + FK | decimal exact -> reference | decimal NUMERIC | narrowing interdit |
|
||||||
|
| account.state_hash | RawContentHash | 1 | BYTEA | exact 32 bytes + FK | fixed bytes -> reference | bytes -> FK columns | aucun |
|
||||||
|
| provider | RawProvenanceCode | 1 | TEXT | 1..128 + safe alphabet | validate common code | code text | aucun |
|
||||||
|
| protocol | RawProvenanceCode | 1 | TEXT | 1..128 + safe alphabet | validate common code | code text | aucun |
|
||||||
|
| acquisition_method | RawProvenanceCode | 1 | TEXT | 1..128 + safe alphabet | validate common code | code text | aucun |
|
||||||
|
| origin | RawAcquisitionOrigin | 1 | TEXT | 5 valeurs stables | static decode | static encode | unknown DB -> DataInvalid |
|
||||||
|
| received_at | RawTimestamp | 1 | BIGINT | 0..253402300799999 | checked i64 -> timestamp | u64 bounded -> i64 | borne API < i64::MAX |
|
||||||
|
| capture_session_id | Option<RawProvenanceCode> | 0..1 | TEXT NULL | NULL ou code sûr | NULL/validate | Option -> NULL/text | aucun |
|
||||||
|
| commitment | Option<RawProvenanceCode> | 0..1 | TEXT NULL | NULL ou code sûr | NULL/validate | Option -> NULL/text | aucun |
|
||||||
|
| endpoint_id | Option<RawProvenanceCode> | 0..1 | TEXT NULL | NULL ou code sûr | NULL/validate | Option -> NULL/text | aucun |
|
||||||
|
| filter_id | Option<RawProvenanceCode> | 0..1 | TEXT NULL | NULL ou code sûr | NULL/validate | Option -> NULL/text | aucun |
|
||||||
|
| observed_at | Option<RawTimestamp> | 0..1 | BIGINT NULL | borne + <= received_at | NULL/checked timestamp | Option -> NULL/i64 | borne API < i64::MAX |
|
||||||
|
| source_payload_hash | Option<RawContentHash> | 0..1 | BYTEA NULL | NULL ou 32 bytes | NULL/fixed bytes | Option -> NULL/BYTEA | aucun |
|
||||||
|
| source_payload_size_bytes | Option<u64> | 0..1 | BIGINT NULL | 0..=64 MiB | NULL/checked u64 | Option -> NULL/i64 | borne API < i64::MAX |
|
||||||
|
| is_startup | Option<bool> | 0..1 | BOOLEAN NULL | nullable | NULL/bool | Option -> NULL/bool | ne pas inventer false |
|
||||||
|
| transaction_signature | Option<RawTransactionSignature> | 0..1 | BYTEA NULL | NULL ou 64 bytes | NULL/fixed bytes | Option -> NULL/BYTEA | aucune FK transaction |
|
||||||
|
| write_version | Option<u64> | 0..1 | NUMERIC(20,0) NULL | NULL ou 0..u64::MAX | NULL/decimal exact | Option -> NULL/NUMERIC | narrowing interdit |
|
||||||
|
|
||||||
|
Vue synthétique de projection :
|
||||||
|
|
||||||
|
| Champ API | Type API | Projection V002 | Invariant |
|
||||||
|
|---------------------------|---------------------------------|------------------------|--------------------------------------------------|
|
||||||
|
| observation_key | RawObservationKey / 32 bytes | BYTEA NOT NULL | PK family-local ; collision = comparaison exacte |
|
||||||
|
| account.pubkey | Pubkey / 32 bytes | BYTEA NOT NULL | FK composite vers state |
|
||||||
|
| account.slot | u64 | NUMERIC(20,0) NOT NULL | domaine u64 exact ; FK composite |
|
||||||
|
| account.state_hash | RawContentHash / 32 bytes | BYTEA NOT NULL | FK composite |
|
||||||
|
| provider | RawProvenanceCode | TEXT NOT NULL | 1..128 octets + alphabet logique stable V001 |
|
||||||
|
| protocol | RawProvenanceCode | TEXT NOT NULL | idem V001 |
|
||||||
|
| acquisition_method | RawProvenanceCode | TEXT NOT NULL | idem V001 |
|
||||||
|
| origin | RawAcquisitionOrigin | TEXT NOT NULL | backfill/import/live/repair/replay |
|
||||||
|
| received_at | RawTimestamp | BIGINT NOT NULL | 0..253402300799999 |
|
||||||
|
| capture_session_id | Option<RawProvenanceCode> | TEXT NULL | mêmes checks V001 |
|
||||||
|
| commitment | Option<RawProvenanceCode> | TEXT NULL | mêmes checks V001 |
|
||||||
|
| endpoint_id | Option<RawProvenanceCode> | TEXT NULL | mêmes checks V001 |
|
||||||
|
| filter_id | Option<RawProvenanceCode> | TEXT NULL | mêmes checks V001 |
|
||||||
|
| observed_at | Option<RawTimestamp> | BIGINT NULL | borne + `observed_at <= received_at` |
|
||||||
|
| source_payload_hash | Option<RawContentHash> | BYTEA NULL | NULL ou 32 bytes |
|
||||||
|
| source_payload_size_bytes | Option<u64> | BIGINT NULL | 0..=64 MiB ; borne API tient dans BIGINT |
|
||||||
|
| is_startup | Option<bool> | BOOLEAN NULL | Yellowstone metadata observation-only |
|
||||||
|
| transaction_signature | Option<RawTransactionSignature> | BYTEA NULL | NULL ou 64 bytes ; aucune FK transaction |
|
||||||
|
| write_version | Option<u64> | NUMERIC(20,0) NULL | NULL ou domaine u64 exact |
|
||||||
|
|
||||||
|
La relation observation -> state est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
FOREIGN KEY (account_pubkey, account_slot, account_state_hash)
|
||||||
|
REFERENCES ksp_raw_account_states(pubkey, slot, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `ON DELETE CASCADE` n'est nécessaire : l'API account ne possède ni delete ni retention. Aucune FK n'est créée vers `ksp_raw_transactions` pour `transaction_signature`; cette signature est une metadata source optionnelle et la transaction correspondante n'est pas garantie présente dans le Store.
|
||||||
|
|
||||||
|
`observation_key` est unique **dans la famille account**. Le contrat API expose des reads family-specific et ne définit aucune unicité globale transaction+account ; une registry globale ajouterait un couplage physique non requis.
|
||||||
|
|
||||||
|
### 5.3 `RawAccountStateQuery`
|
||||||
|
|
||||||
|
| Entrée query | Cardinalité | Projection physique |
|
||||||
|
|-----------------------|------------------------|------------------------------------------------------------------|
|
||||||
|
| network | obligatoire | guard DB mono-réseau + digest cursor |
|
||||||
|
| pubkey | optionnel | filtre égalité ; aucune query owner |
|
||||||
|
| slots.start_inclusive | optionnel | `slot >=` |
|
||||||
|
| slots.end_inclusive | optionnel | `slot <=` |
|
||||||
|
| direction | Ascending / Descending | ordre total entièrement inversé |
|
||||||
|
| page.limit | u64 > 0 | limite physique PostgreSQL seulement ; `LIMIT + 1` |
|
||||||
|
| page.cursor | opaque <= 4 KiB | V1 account fixe, authentification de contexte par digest SHA-256 |
|
||||||
|
|
||||||
|
Aucune query API par owner/provider/time/status n'existe. Aucun index pour ces dimensions ne sera introduit dans V002.
|
||||||
|
|
||||||
|
## 6. Audit kbot3 ciblé
|
||||||
|
|
||||||
|
L'archive historique contient notamment `k_sol_obs_account_observations`, `k_sol_core_account_states`, `AccountStateStore` et le pipeline de normalisation `account_state_normalization`. Cette surface mélangeait acquisition, normalisation CORE et processing policy ; elle n'est pas transposable telle quelle.
|
||||||
|
|
||||||
|
| Élément historique | Classement | Décision KSP |
|
||||||
|
|----------------------------------------------------------|------------|-----------------------------------------------------------------------|
|
||||||
|
| unicité observation_key et FK comme invariants physiques | REPRENDRE | garder le principe, avec clés KSP actuelles |
|
||||||
|
| index account+slot pour navigation déterministe | REPRENDRE | réutiliser le principe seulement pour les queries réellement exposées |
|
||||||
|
| NUMERIC(20,0) pour lamports/rent_epoch | REPRENDRE | étendre à slot/write_version pour le domaine u64 complet |
|
||||||
|
| BIGSERIAL/id technique comme identité | REDESSINER | identité durable = `(pubkey, slot, state_hash)` |
|
||||||
|
| pubkey/owner/hash en TEXT/base58 | REDESSINER | BYTEA fixed-width |
|
||||||
|
| account data en base64 + `space` indépendant | REDESSINER | BYTEA complet ; longueur dérivée ; aucun double truth |
|
||||||
|
| observation duplique l’état canonique | REDESSINER | observation référence un state complet séparé |
|
||||||
|
| slot BIGINT/i64 | REDESSINER | NUMERIC(20,0) sans narrowing |
|
||||||
|
| normalisation N1 -> CORE + processing ledger/version | REPORTER | future CORE/jobs/workers ; hors Store RAW 0.3.4 |
|
||||||
|
| batch maximum 1000 / policy de sélection | REPORTER | policy worker/job ; pas pagination Store |
|
||||||
|
| indexes owner/provider/method/time/status | REPORTER | aucune query publique actuelle ne les justifie |
|
||||||
|
| status/error_message persistés dans observation RAW | REJETER | observation réussie uniquement ; erreurs sûres hors ligne métier |
|
||||||
|
| sqlx et Store monolithique | REJETER | backend tokio-postgres séparé derrière ksp-store-lib |
|
||||||
|
| maintenance CASCADE/TRUNCATE comme surface normale | REJETER | aucune destructive capability account |
|
||||||
|
|
||||||
|
Aucun code kbot3 ne sera copié. Les optional Yellowstone actuelles (`write_version`, `transaction_signature`, `is_startup`) n'existaient pas comme contrat durable équivalent dans cette persistence historique et restent donc définies uniquement par `ksp-store-api` actuel.
|
||||||
|
|
||||||
|
Points héritage supplémentaires explicitement classés :
|
||||||
|
|
||||||
|
- **REPRENDRE** : l'ancien repository utilisait déjà une transaction pour rendre atomiques state CORE + mutation observation + processing ledger ; seul le principe transactionnel est repris, jamais le couplage CORE/ledger.
|
||||||
|
- **REPRENDRE** : l'unicité `observation_key` et la validation additive des contraintes/indexes sont de bons invariants physiques.
|
||||||
|
- **REDESSINER** : `write_version`, `transaction_signature` et `is_startup` n'ont pas d'équivalent persistant account fiable dans cette surface historique ; V002 suit exclusivement les optionalités du contrat KSP actuel.
|
||||||
|
- **REDESSINER** : l'idempotence historique `source_observation_id` + `ON CONFLICT DO UPDATE` est remplacée par clé producer-owned + comparaison exacte + conflit stable.
|
||||||
|
- **REJETER** : les erreurs historiques interpolent l'erreur SQL dans `ks_core::Error::db(...)`; KSP conserve uniquement kind/phase statiques et sûres.
|
||||||
|
|
||||||
|
## 7. Design physique V002 figé par `pre.001`
|
||||||
|
|
||||||
|
### 7.1 Tables minimales
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_raw_account_states
|
||||||
|
ksp_raw_account_observations
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune table archive/tombstone account n'est ajoutée : `ksp-store-api` ne définit aucune capability de retention account.
|
||||||
|
|
||||||
|
### 7.2 Colonnes `ksp_raw_account_states`
|
||||||
|
|
||||||
|
```text
|
||||||
|
pubkey BYTEA NOT NULL
|
||||||
|
slot NUMERIC(20,0) NOT NULL
|
||||||
|
state_hash BYTEA NOT NULL
|
||||||
|
lamports NUMERIC(20,0) NOT NULL
|
||||||
|
owner BYTEA NOT NULL
|
||||||
|
executable BOOLEAN NOT NULL
|
||||||
|
rent_epoch NUMERIC(20,0) NOT NULL
|
||||||
|
data BYTEA NOT NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
Contraintes minimales : fixed width 32 pour `pubkey`, `owner`, `state_hash`; domaine u64 pour `slot`, `lamports`, `rent_epoch`; `octet_length(data) <= 16777216`; aucune borne basse sur data.
|
||||||
|
|
||||||
|
### 7.3 Colonnes `ksp_raw_account_observations`
|
||||||
|
|
||||||
|
```text
|
||||||
|
observation_key BYTEA NOT NULL
|
||||||
|
account_pubkey BYTEA NOT NULL
|
||||||
|
account_slot NUMERIC(20,0) NOT NULL
|
||||||
|
account_state_hash BYTEA NOT NULL
|
||||||
|
provider TEXT NOT NULL
|
||||||
|
protocol TEXT NOT NULL
|
||||||
|
acquisition_method TEXT NOT NULL
|
||||||
|
origin TEXT NOT NULL
|
||||||
|
received_at_unix_millis BIGINT NOT NULL
|
||||||
|
capture_session_id TEXT NULL
|
||||||
|
commitment TEXT NULL
|
||||||
|
endpoint_id TEXT NULL
|
||||||
|
filter_id TEXT NULL
|
||||||
|
observed_at_unix_millis BIGINT NULL
|
||||||
|
source_payload_hash BYTEA NULL
|
||||||
|
source_payload_size_bytes BIGINT NULL
|
||||||
|
is_startup BOOLEAN NULL
|
||||||
|
transaction_signature BYTEA NULL
|
||||||
|
write_version NUMERIC(20,0) NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
Les contraintes provenance doivent être sémantiquement identiques à V001 : code 1..128 octets/alphabet sûr, origin fermé, timestamps bornés/ordonnés, source hash 32 bytes et source payload <= 64 MiB.
|
||||||
|
|
||||||
|
### 7.4 Indexes justifiés
|
||||||
|
|
||||||
|
Indexes provenant des PK :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PK ksp_raw_account_states(pubkey, slot, state_hash)
|
||||||
|
PK ksp_raw_account_observations(observation_key)
|
||||||
|
```
|
||||||
|
|
||||||
|
Un seul index métier additionnel est justifié par la query non filtrée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ix_ksp_raw_account_states_slot_pubkey_state_hash(slot, pubkey, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le PK couvre déjà efficacement la navigation lorsque `pubkey = ?` est imposé. Aucun index owner/provider/protocol/method/time/hash isolé n'est admis dans cette release.
|
||||||
|
|
||||||
|
## 8. Ordre total et pagination keyset
|
||||||
|
|
||||||
|
Ordre canonique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ascending : (slot ASC, pubkey ASC, state_hash ASC)
|
||||||
|
Descending : (slot DESC, pubkey DESC, state_hash DESC)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le backend PostgreSQL compare `BYTEA` selon son ordre btree déterministe. Aucun `OFFSET` n'est utilisé.
|
||||||
|
|
||||||
|
Cursor account V1 proposé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bytes 0..4 magic = "KSPA"
|
||||||
|
byte 4 version = 1
|
||||||
|
bytes 5..13 last_slot, u64 big-endian
|
||||||
|
bytes 13..45 last_pubkey, 32 bytes
|
||||||
|
bytes 45..77 last_state_hash, 32 bytes
|
||||||
|
bytes 77..109 SHA-256 binding digest
|
||||||
|
longueur fixe = 109 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
Domaine de digest :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP/raw-account-state-cursor/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le digest lie au minimum : network, présence/valeur du filtre pubkey, direction, start/end slot, last slot, last pubkey, last state_hash. Le magic/domain distinct de `KSPT`/`KSP/raw-transaction-cursor/v1` rend le replay cross-family impossible même si les deux cursors ont 109 octets.
|
||||||
|
|
||||||
|
Le plafond physique PostgreSQL reste le précédent réel nécessaire à `LIMIT + 1` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
9_223_372_036_854_775_806
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce plafond n'est pas une policy métier KSP.
|
||||||
|
|
||||||
|
## 9. Algorithmes d'écriture
|
||||||
|
|
||||||
|
### 9.1 Acquisition state + observation atomiques
|
||||||
|
|
||||||
|
Préconditions avant I/O métier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
state.reference.network == backend.network
|
||||||
|
observation.account.network == backend.network
|
||||||
|
observation.account == state.reference
|
||||||
|
```
|
||||||
|
|
||||||
|
Algorithme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
BEGIN
|
||||||
|
INSERT state ... ON CONFLICT (pubkey, slot, state_hash) DO NOTHING RETURNING ...
|
||||||
|
if inserted:
|
||||||
|
entity_outcome = Inserted
|
||||||
|
else:
|
||||||
|
SELECT canonical state BY PK FOR UPDATE
|
||||||
|
decode + compare exact lamports/owner/executable/rent_epoch/data
|
||||||
|
identical -> AlreadyPresent
|
||||||
|
divergent -> ERROR_CODE_RAW_CONFLICT
|
||||||
|
|
||||||
|
INSERT observation ... ON CONFLICT (observation_key) DO NOTHING RETURNING ...
|
||||||
|
if inserted:
|
||||||
|
observation_outcome = Inserted
|
||||||
|
else:
|
||||||
|
SELECT observation BY key FOR UPDATE
|
||||||
|
decode + compare exact reference + provenance + all optional metadata
|
||||||
|
identical -> AlreadyPresent
|
||||||
|
divergent -> ERROR_CODE_RAW_CONFLICT
|
||||||
|
COMMIT
|
||||||
|
```
|
||||||
|
|
||||||
|
Une divergence d'observation après insertion d'un nouveau state fait rollback de ce state. Aucun `ON CONFLICT DO UPDATE`, aucun `has_*` préalable, aucun overwrite silencieux.
|
||||||
|
|
||||||
|
### 9.2 Observation supplémentaire
|
||||||
|
|
||||||
|
```text
|
||||||
|
validate network before I/O
|
||||||
|
BEGIN
|
||||||
|
SELECT state BY composite reference FOR KEY SHARE
|
||||||
|
absent -> ReferenceNotFound
|
||||||
|
INSERT observation ... ON CONFLICT (observation_key) DO NOTHING RETURNING ...
|
||||||
|
collision -> SELECT FOR UPDATE + exact compare
|
||||||
|
COMMIT
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette opération ne crée jamais implicitement le state et ne retransmet pas ses bytes.
|
||||||
|
|
||||||
|
## 10. Matrice de concurrence
|
||||||
|
|
||||||
|
| Scénario | Résultat permis | Invariant |
|
||||||
|
|------------------------------------------------------------|-------------------------------------------------------|---------------------------------------------------------------|
|
||||||
|
| 2 acquisitions identiques, même ref + même observation key | 1 Inserted/Inserted ; 1 AlreadyPresent/AlreadyPresent | attente unique + comparaison exacte ; aucun doublon |
|
||||||
|
| 2 acquisitions même ref, contenu divergent | un gagnant durable ; autre `RAW_CONFLICT` | aucun overwrite ; résultat gagnant dépend de l’ordonnancement |
|
||||||
|
| même pubkey+slot, state_hash différents | les deux states sont représentables | PK inclut state_hash |
|
||||||
|
| observation key identique, observations divergentes | une observation gagne ; autre `RAW_CONFLICT` | transaction perdante rollback son state nouvellement créé |
|
||||||
|
| record observation identique concurrent | 1 Inserted ; 1 AlreadyPresent | state préexistant inchangé |
|
||||||
|
| record observation divergente concurrent | 1 Inserted ; 1 `RAW_CONFLICT` | aucun update silencieux |
|
||||||
|
| annulation après insertion state avant observation | aucune moitié durable | transaction PostgreSQL rollback/drop |
|
||||||
|
|
||||||
|
Le backend ne promet pas quel concurrent divergent gagne ; il promet qu'un seul contenu incompatible devient durable et que l'autre reçoit le conflit stable.
|
||||||
|
|
||||||
|
## 11. Taxonomie d'erreurs
|
||||||
|
|
||||||
|
Aucun nouveau `PostgresBackendErrorKind` n'est requis par le design actuel.
|
||||||
|
|
||||||
|
| Cas | Taxonomie stable | Règle |
|
||||||
|
|---------------------------------------------------|------------------------------------------------------|---------------------------------|
|
||||||
|
| mauvais réseau opération | WrongNetwork -> `store.wrong_network` | guard avant SQL métier |
|
||||||
|
| référence absente pour observation supplémentaire | ReferenceNotFound -> `store.raw_reference_not_found` | pas de state implicite |
|
||||||
|
| contenu divergent / collision observation | Conflict -> `store_api.raw_conflict` | message/context statiques |
|
||||||
|
| cursor/query invalide | QueryInvalid -> `store_api.raw_query_invalid` | aucun bytes hostile dans erreur |
|
||||||
|
| page > limite physique PostgreSQL | PageLimitUnsupported | limite physique, pas policy KSP |
|
||||||
|
| row DB incompatible/malformée | DataInvalid | aucune valeur hostile recopiée |
|
||||||
|
| échec SELECT | ReadFailed | pas de server text exposé |
|
||||||
|
| échec INSERT/transaction | WriteFailed | pas de server text exposé |
|
||||||
|
| V002 absente/incompatible/divergente | MigrationMismatch / MigrationFailed / SchemaNewer | réutiliser taxonomy stable |
|
||||||
|
|
||||||
|
Les erreurs PostgreSQL externes ne doivent jamais être conservées comme source publique ni recopiées dans message/context.
|
||||||
|
|
||||||
|
## 12. Threat model `RawAccountState`
|
||||||
|
|
||||||
|
| Menace | Risque | Mitigation | Preuve prévue |
|
||||||
|
|---------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------|----------------------------|
|
||||||
|
| URI pointe vers DB d’un autre réseau | corruption cross-network | binding `ksp_store_identity` + guard réseau avant opération | unit + live |
|
||||||
|
| pubkey/hash/signature DB de mauvaise largeur | construction de modèle invalide | CHECK SQL + mapping `DataInvalid` statique | unit hostile rows |
|
||||||
|
| slot/lamports/rent_epoch/write_version > i64::MAX | narrowing/perte | NUMERIC(20,0) + conversion décimale fallible | unit limites u64 |
|
||||||
|
| data account > 16 MiB | allocation/persistence hostile | admission API + CHECK `octet_length <= 16777216` | unit + live boundary |
|
||||||
|
| data account vide | rejet incorrect | aucune borne basse SQL ; vide explicitement valide | unit |
|
||||||
|
| observation sans state | orphelin | FK composite + record pré-vérifie state | unit + live |
|
||||||
|
| échec observation après state insert | demi-acquisition durable | une transaction PostgreSQL commune | live cancellation/rollback |
|
||||||
|
| même ref, contenu divergent | overwrite silencieux | ON CONFLICT DO NOTHING + SELECT FOR UPDATE + comparaison exacte | unit + live concurrent |
|
||||||
|
| même pubkey/slot, hashes distincts | collision d’identité | PK `(pubkey,slot,state_hash)` | unit + live |
|
||||||
|
| collision observation key | fusion de sources distinctes | comparaison exacte de tous les champs ; conflict sinon | unit + live |
|
||||||
|
| cursor transaction injecté dans account | replay cross-family | magic/domain account distincts | unit cursor |
|
||||||
|
| cursor account rejoué sur autre query | pagination incohérente | digest lie network/pubkey/range/direction/last key | unit cursor matrix |
|
||||||
|
| ordre même slot ambigu | duplication/skip entre pages | ordre total `(slot,pubkey,state_hash)` | unit + live pagination |
|
||||||
|
| metadata Yellowstone promue au state | couplage provider | is_startup/transaction_signature/write_version uniquement observation | schema + mapping canary |
|
||||||
|
| transaction_signature impose transaction row | couplage cross-family non garanti | aucune FK vers `ksp_raw_transactions` | schema canary |
|
||||||
|
| SQL/server text fuit | secret/PII leak | projection PostgresBackendError statique | hardening |
|
||||||
|
| V000/V001 modifiées | history incompatible | checksums figés et canaries exactes | migration tests |
|
||||||
|
| index non justifié | write amplification/bloat | un seul index métier V002 pour query unfiltered | schema completeness |
|
||||||
|
|
||||||
|
## 13. Compatibilité schema/migration
|
||||||
|
|
||||||
|
V002 devra suivre le moteur resource-owned de V001 : ressources ordonnées, checksums logiques, vérification catalogique et mutation selon les policies foundation existantes.
|
||||||
|
|
||||||
|
Principes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
schema_creation = disabled : aucune création implicite
|
||||||
|
schema_creation = create_if_missing : bootstrap seulement selon contrat existant
|
||||||
|
schema_update = disabled : historique incomplet -> mismatch sûr
|
||||||
|
schema_update = update_if_needed : V002 appliquée additivement
|
||||||
|
history > latest embedded : SchemaNewer
|
||||||
|
history V000/V001 divergente : MigrationMismatch
|
||||||
|
extra columns externes : acceptables seulement si elles ne peuvent bloquer les INSERT KSP
|
||||||
|
colonnes/constraints/index requis incompatibles : mismatch avant ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Les numéros de fichiers SQL exacts sont une responsabilité de `pre.002/pre.003`; leur sémantique est figée ici, pas leurs noms séquentiels.
|
||||||
|
|
||||||
|
## 14. Mapping/read privé
|
||||||
|
|
||||||
|
Le module backend proposé est `raw_account` privé, analogue structurellement à `raw_transaction` mais sans retention/archive.
|
||||||
|
|
||||||
|
Il devra contenir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
mapping row -> RawAccountState
|
||||||
|
mapping row -> RawAccountObservation
|
||||||
|
mapping u64 <-> NUMERIC text sans narrowing
|
||||||
|
get state par composite reference
|
||||||
|
get observation par observation_key
|
||||||
|
list references par keyset
|
||||||
|
cursor account privé
|
||||||
|
write acquisition/observation privé
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun row/statement/client PostgreSQL ne traverse `ksp-store-postgres-lib` publiquement ni `ksp-store-lib`.
|
||||||
|
|
||||||
|
## 15. Stratégie de preuve PostgreSQL réelle
|
||||||
|
|
||||||
|
La preuve live restera `#[ignore]`, opt-in, avec une URI dédiée lue depuis stdin et sans environnement direct. Elle devra refuser de détruire des objets KSP préexistants non créés par le test.
|
||||||
|
|
||||||
|
Scénarios minimum :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bootstrap V000+V001+V002 et health ready
|
||||||
|
réouverture idempotente du schéma
|
||||||
|
state bytes vide et 16 MiB
|
||||||
|
u64 au-delà de i64::MAX et u64::MAX pour colonnes NUMERIC
|
||||||
|
acquisition identique puis divergente
|
||||||
|
same pubkey + same slot + distinct state_hash
|
||||||
|
observation supplémentaire / reference not found
|
||||||
|
collision observation
|
||||||
|
pagination filtered/unfiltered ASC/DESC multi-page
|
||||||
|
cursor hostile / replay query / replay transaction
|
||||||
|
concurrence identique/divergente
|
||||||
|
cancellation entre state et observation -> rollback
|
||||||
|
coexistence avec au moins un canari RawTransaction stable dans la même DB V002
|
||||||
|
cleanup vérifié uniquement pour les objets créés par le test
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun appel Solana réseau n'est nécessaire à cette preuve.
|
||||||
|
|
||||||
|
## 16. Conformance cross-family finale
|
||||||
|
|
||||||
|
À `pre.008+`, les canaries devront compter exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgresBackend : 10 impl RAW
|
||||||
|
Store : 10 impl RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
La façade doit continuer de n'exposer que les modèles/capabilities backend-agnostic. Le test `--no-default-features` de `ksp-store-lib` doit rester vert : un build sans backend PostgreSQL compile et rejette proprement un backend non compilé au runtime.
|
||||||
|
|
||||||
|
La conformance finale doit aussi démontrer que l'ajout account ne modifie pas les six comportements transaction : lecture/write/observation/list/retention/force-rehydrate restent couverts par leurs suites existantes.
|
||||||
|
|
||||||
|
## 17. Sizing recalibré
|
||||||
|
|
||||||
|
Le forecast initial du prompt concentrait migration et schema compatibility dans une seule tranche, ainsi que write+concurrence. Ces lots sont séparés pour rester dans la cible opérateur `~15-20 min/pre`.
|
||||||
|
|
||||||
|
| Tranche | Objet | Budget | Note |
|
||||||
|
|---------|-------------------------------------------------------------------|-----------|----------------------------------------|
|
||||||
|
| pre.001 | audit, kbot3, threat model, V002 design, sizing, plan/validation | 15-20 min | cette tranche |
|
||||||
|
| pre.002 | V002 registry + deux tables + PK/FK de base, sans repository | 15-20 min | split du forecast initial |
|
||||||
|
| pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | 15-20 min | split du forecast initial |
|
||||||
|
| pre.004 | mapping privé state/observation + get reads + hostile rows | 15-20 min | pas de write |
|
||||||
|
| pre.005 | acquisition atomique state+observation + idempotence/conflict | 15-20 min | write principal |
|
||||||
|
| pre.006 | observation supplémentaire + races/cancellation unitaires | 15-20 min | concurrence isolée |
|
||||||
|
| pre.007 | list RawAccountStateQuery + keyset cursor V1 account | 15-20 min | navigation uniquement |
|
||||||
|
| pre.008 | 4 impl backend + 4 dispatch Store + conformance 10/10 | 15-20 min | frontières cross-crate |
|
||||||
|
| pre.009 | preuve PostgreSQL live account + coexistence RawTransaction | 15-20 min | ignored/opt-in |
|
||||||
|
| pre.010 | hardening/completeness cross-family + canaries ownership | 15-20 min | sécurité/completude |
|
||||||
|
| pre.011 | gate technique final + replay live ciblé + graphes | 15-20 min | avant docs finale |
|
||||||
|
| pre.012 | réconciliation docs finale sans rouvrir le code | 10-15 min | README/USAGE/plan/validation si requis |
|
||||||
|
| pre.013 | publication prep minimale + prompt 0.3.5 | 10-15 min | lane publication |
|
||||||
|
| rel.001 | stabilisation/tag après gate opérateur | 5-10 min | aucun scope nouveau |
|
||||||
|
|
||||||
|
## 18. Hors scope explicite
|
||||||
|
|
||||||
|
```text
|
||||||
|
modification ksp-store-api
|
||||||
|
retention/archivage/purge account
|
||||||
|
STRUCTURAL/CORE materialization
|
||||||
|
Interface events
|
||||||
|
workers/jobs/backfill executors
|
||||||
|
batch-size/priorité/backlog policy
|
||||||
|
Transport -> Store direct
|
||||||
|
index owner/provider/time/status sans query
|
||||||
|
compression account
|
||||||
|
nouveau backend autre que PostgreSQL
|
||||||
|
application Store Desk
|
||||||
|
```
|
||||||
|
|
||||||
|
## 19. Questions ouvertes après `pre.001`
|
||||||
|
|
||||||
|
Aucune question **bloquante** de contrat ne subsiste.
|
||||||
|
|
||||||
|
Les détails suivants restent volontairement d'implémentation : numérotation exacte des ressources V002, découpage précis des helpers de mapping et noms internes des statements. Ils ne doivent pas modifier les invariants de ce plan.
|
||||||
|
|
||||||
|
## 20. Gate d'acceptation `pre.001`
|
||||||
|
|
||||||
|
`pre.001` est acceptable si :
|
||||||
|
|
||||||
|
```text
|
||||||
|
base v0.3.3 vérifiée
|
||||||
|
V000/V001 checksums recalculés et figés
|
||||||
|
inventaire 6/10 -> 10/10 explicite
|
||||||
|
field mapping account complet
|
||||||
|
kbot3 classifié REPRENDRE/REDESSINER/REPORTER/REJETER
|
||||||
|
V002 figée sans SQL écrit
|
||||||
|
ordre/cursor figés
|
||||||
|
algorithmes atomicité/idempotence/concurrence figés
|
||||||
|
threat model complet
|
||||||
|
forecast recalibré
|
||||||
|
aucune modification fonctionnelle hors version prerelease
|
||||||
|
audits statiques applicables verts ; gate Cargo opérateur rejoué
|
||||||
|
```
|
||||||
327
docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md
Normal file
327
docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
<!-- file: docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Validation `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
||||||
|
|
||||||
|
## 1. Portée
|
||||||
|
|
||||||
|
Cette matrice est ouverte par `0.3.4-pre.001`. La tranche courante valide **le design et le sizing uniquement** ; elle n'affirme aucune présence de V002 ou d'implémentation account.
|
||||||
|
|
||||||
|
Base :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Cible release :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgresBackend = 10/10 capabilities RAW
|
||||||
|
Store = 10/10 capabilities RAW
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Gate base `pre.001`
|
||||||
|
|
||||||
|
| Preuve | Attendu | État pre.001 |
|
||||||
|
|------------------------------------|------------------|-------------------------------------------------|
|
||||||
|
| workspace.package.version base | 0.3.3 | PASS audit |
|
||||||
|
| V000 checksum | d29068b8…46a1450 | PASS recalcul indépendant |
|
||||||
|
| V001 checksum | 31488cda…d72cc51 | PASS recalcul indépendant / 40 resources |
|
||||||
|
| capabilities transaction physiques | 6 | PASS inventaire source |
|
||||||
|
| capabilities account physiques | 0 | PASS inventaire source ; volontairement pending |
|
||||||
|
| canaries anti-account 0.3.3 | présentes | PASS inventaire tests |
|
||||||
|
| ksp-store-api change nécessaire | non | PASS audit |
|
||||||
|
| SQL V002 créé en pre.001 | non | PASS scope |
|
||||||
|
|
||||||
|
## 3. Matrice capability finale
|
||||||
|
|
||||||
|
| Capability | Base v0.3.3 | Cible 0.3.4 | Preuve finale |
|
||||||
|
|--------------------------------|-------------|-------------|-------------------------------|
|
||||||
|
| RawTransactionRead | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawTransactionWrite | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawTransactionObservationRead | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawTransactionObservationWrite | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawTransactionRetentionRead | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawTransactionRetentionWrite | PASS | PASS | régression suite 0.3.3 |
|
||||||
|
| RawAccountStateRead | PENDING | PASS | tests backend + façade + live |
|
||||||
|
| RawAccountStateWrite | PENDING | PASS | tests backend + façade + live |
|
||||||
|
| RawAccountObservationRead | PENDING | PASS | tests backend + façade + live |
|
||||||
|
| RawAccountObservationWrite | PENDING | PASS | tests backend + façade + live |
|
||||||
|
|
||||||
|
### 3.1 Contrat opérationnel détaillé des quatre capabilities account
|
||||||
|
|
||||||
|
| Capability | Input | Output | Guard réseau | SQL/transaction | Idempotence | Conflit | Absence/référence | Pagination | Error mapping | Preuve live |
|
||||||
|
|----------------------------|---------------------|---------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------|----------------------------------|----------------------------------------|-----------------------------------|---------------------|---------------------------------------------------------------------------|------------------------------------------------|
|
||||||
|
| RawAccountStateRead | reference / query | Option<State> / Page<Reference> | reference/query network == backend | SELECT ; list keyset | n/a | n/a | get absent -> None | oui, cursor account | WrongNetwork, QueryInvalid, PageLimitUnsupported, DataInvalid, ReadFailed | get exact + list matrix |
|
||||||
|
| RawAccountStateWrite | state + observation | RawAcquisitionWriteOutcome | state + observation networks + exact same reference | transaction state+observation | exact existing -> AlreadyPresent | same full ref divergent -> RawConflict | n/a | non | WrongNetwork, Conflict, DataInvalid, WriteFailed | atomicité + identical/divergent + cancellation |
|
||||||
|
| RawAccountObservationRead | observation_key | Option<Observation> | aucun input réseau ; backend fournit son network à la référence reconstruite | SELECT by PK | n/a | n/a | absent -> None | non | DataInvalid, ReadFailed | round-trip optional metadata |
|
||||||
|
| RawAccountObservationWrite | observation | RawObservationWriteOutcome | observation.account.network == backend | transaction ; state FOR KEY SHARE + observation insert | exact existing -> AlreadyPresent | same key divergent -> RawConflict | state absent -> ReferenceNotFound | non | WrongNetwork, ReferenceNotFound, Conflict, DataInvalid, WriteFailed | missing ref + collision + concurrence |
|
||||||
|
|
||||||
|
## 4. Matrice `RawAccountState`
|
||||||
|
|
||||||
|
| Champ | Représentation décidée | Validation requise |
|
||||||
|
|------------|------------------------------------------|-----------------------------------------------------------------|
|
||||||
|
| network | binding DB existant `ksp_store_identity` | pas de colonne par ligne ; guard exact avant I/O |
|
||||||
|
| pubkey | BYTEA NOT NULL | `octet_length = 32` ; composant PK |
|
||||||
|
| slot | NUMERIC(20,0) NOT NULL | 0..18446744073709551615 ; aucun narrowing |
|
||||||
|
| state_hash | BYTEA NOT NULL | `octet_length = 32` ; composant PK ; jamais recalculé par Store |
|
||||||
|
| lamports | NUMERIC(20,0) NOT NULL | domaine u64 exact |
|
||||||
|
| owner | BYTEA NOT NULL | `octet_length = 32` |
|
||||||
|
| executable | BOOLEAN NOT NULL | mapping direct |
|
||||||
|
| rent_epoch | NUMERIC(20,0) NOT NULL | domaine u64 exact |
|
||||||
|
| data | BYTEA NOT NULL | 0..=16 MiB ; vide autorisé ; aucun base64 |
|
||||||
|
|
||||||
|
Points structurants :
|
||||||
|
|
||||||
|
```text
|
||||||
|
identity = network API + (pubkey, slot, state_hash)
|
||||||
|
network SQL = singleton ksp_store_identity, pas colonne row
|
||||||
|
PK state = (pubkey, slot, state_hash)
|
||||||
|
state_hash n'est pas une preuve suffisante d'idempotence : comparaison contenu exacte obligatoire
|
||||||
|
empty data = valide
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Matrice observation
|
||||||
|
|
||||||
|
| Champ | Représentation décidée | Validation requise |
|
||||||
|
|---------------------------|------------------------|--------------------------------------------------|
|
||||||
|
| observation_key | BYTEA NOT NULL | PK family-local ; collision = comparaison exacte |
|
||||||
|
| account.pubkey | BYTEA NOT NULL | FK composite vers state |
|
||||||
|
| account.slot | NUMERIC(20,0) NOT NULL | domaine u64 exact ; FK composite |
|
||||||
|
| account.state_hash | BYTEA NOT NULL | FK composite |
|
||||||
|
| provider | TEXT NOT NULL | 1..128 octets + alphabet logique stable V001 |
|
||||||
|
| protocol | TEXT NOT NULL | idem V001 |
|
||||||
|
| acquisition_method | TEXT NOT NULL | idem V001 |
|
||||||
|
| origin | TEXT NOT NULL | backfill/import/live/repair/replay |
|
||||||
|
| received_at | BIGINT NOT NULL | 0..253402300799999 |
|
||||||
|
| capture_session_id | TEXT NULL | mêmes checks V001 |
|
||||||
|
| commitment | TEXT NULL | mêmes checks V001 |
|
||||||
|
| endpoint_id | TEXT NULL | mêmes checks V001 |
|
||||||
|
| filter_id | TEXT NULL | mêmes checks V001 |
|
||||||
|
| observed_at | BIGINT NULL | borne + `observed_at <= received_at` |
|
||||||
|
| source_payload_hash | BYTEA NULL | NULL ou 32 bytes |
|
||||||
|
| source_payload_size_bytes | BIGINT NULL | 0..=64 MiB ; borne API tient dans BIGINT |
|
||||||
|
| is_startup | BOOLEAN NULL | Yellowstone metadata observation-only |
|
||||||
|
| transaction_signature | BYTEA NULL | NULL ou 64 bytes ; aucune FK transaction |
|
||||||
|
| write_version | NUMERIC(20,0) NULL | NULL ou domaine u64 exact |
|
||||||
|
|
||||||
|
Invariants additionnels :
|
||||||
|
|
||||||
|
```text
|
||||||
|
FK observation -> state composite
|
||||||
|
record_raw_account_observation n'insère jamais le state
|
||||||
|
transaction_signature observation-only, sans FK transaction
|
||||||
|
write_version/is_startup observation-only
|
||||||
|
observation_key family-local unique
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Query/pagination
|
||||||
|
|
||||||
|
| Élément | Décision | Canari requis |
|
||||||
|
|-----------------|------------------------------------------------|--------------------------------|
|
||||||
|
| ordre ASC | slot, pubkey, state_hash ASC | multi-page sans duplicate/skip |
|
||||||
|
| ordre DESC | slot, pubkey, state_hash DESC | symétrique ASC |
|
||||||
|
| filtre pubkey | égalité optionnelle | cursor lié au filtre |
|
||||||
|
| slot range | inclusive | cursor lié aux deux bornes |
|
||||||
|
| cursor magic | KSPA | rejette KSPT transaction |
|
||||||
|
| cursor version | 1 | rejette version hostile |
|
||||||
|
| cursor longueur | 109 bytes | rejette taille hostile |
|
||||||
|
| cursor domain | KSP/raw-account-state-cursor/v1 | digest family-bound |
|
||||||
|
| cursor digest | network+pubkey filter+range+direction+last key | anti-replay query |
|
||||||
|
| pagination | keyset seulement | aucun OFFSET |
|
||||||
|
| limit | limite physique PostgreSQL uniquement | pas de batch policy |
|
||||||
|
|
||||||
|
## 7. Migration/schema V002
|
||||||
|
|
||||||
|
| Objet | Décision pre.001 | État |
|
||||||
|
|----------------------------------|---------------------------------------|-------------------------|
|
||||||
|
| V000 | immuable | PASS design |
|
||||||
|
| V001 | immuable | PASS design |
|
||||||
|
| V002 logical name | raw_account_state | PASS design |
|
||||||
|
| table state | ksp_raw_account_states | PASS design |
|
||||||
|
| table observation | ksp_raw_account_observations | PASS design |
|
||||||
|
| archive/tombstone account | aucune | PASS negative scope |
|
||||||
|
| index métier | (slot,pubkey,state_hash) | PASS design |
|
||||||
|
| index owner/provider/time/status | aucun | PASS negative scope |
|
||||||
|
| SQL/resource files | pas encore créés | PENDING pre.002/pre.003 |
|
||||||
|
| V002 checksum | inconnu tant que resources non créées | PENDING pre.003 |
|
||||||
|
|
||||||
|
## 8. Idempotence et concurrence
|
||||||
|
|
||||||
|
| Scénario | Résultat attendu | État |
|
||||||
|
|------------------------------------------------------------|-------------------------------------------------------|-------------------|
|
||||||
|
| 2 acquisitions identiques, même ref + même observation key | 1 Inserted/Inserted ; 1 AlreadyPresent/AlreadyPresent | PENDING impl/live |
|
||||||
|
| 2 acquisitions même ref, contenu divergent | un gagnant durable ; autre `RAW_CONFLICT` | PENDING impl/live |
|
||||||
|
| même pubkey+slot, state_hash différents | les deux states sont représentables | PENDING impl/live |
|
||||||
|
| observation key identique, observations divergentes | une observation gagne ; autre `RAW_CONFLICT` | PENDING impl/live |
|
||||||
|
| record observation identique concurrent | 1 Inserted ; 1 AlreadyPresent | PENDING impl/live |
|
||||||
|
| record observation divergente concurrent | 1 Inserted ; 1 `RAW_CONFLICT` | PENDING impl/live |
|
||||||
|
| annulation après insertion state avant observation | aucune moitié durable | PENDING impl/live |
|
||||||
|
|
||||||
|
Le test de contenu divergent doit comparer les fields complets et ne jamais traiter l'égalité de `state_hash` comme preuve suffisante.
|
||||||
|
|
||||||
|
## 9. Threat model
|
||||||
|
|
||||||
|
| Menace | Mitigation figée | Preuve |
|
||||||
|
|---------------------------------------------------|-----------------------------------------------------------------------|----------------------------|
|
||||||
|
| URI pointe vers DB d’un autre réseau | binding `ksp_store_identity` + guard réseau avant opération | unit + live |
|
||||||
|
| pubkey/hash/signature DB de mauvaise largeur | CHECK SQL + mapping `DataInvalid` statique | unit hostile rows |
|
||||||
|
| slot/lamports/rent_epoch/write_version > i64::MAX | NUMERIC(20,0) + conversion décimale fallible | unit limites u64 |
|
||||||
|
| data account > 16 MiB | admission API + CHECK `octet_length <= 16777216` | unit + live boundary |
|
||||||
|
| data account vide | aucune borne basse SQL ; vide explicitement valide | unit |
|
||||||
|
| observation sans state | FK composite + record pré-vérifie state | unit + live |
|
||||||
|
| échec observation après state insert | une transaction PostgreSQL commune | live cancellation/rollback |
|
||||||
|
| même ref, contenu divergent | ON CONFLICT DO NOTHING + SELECT FOR UPDATE + comparaison exacte | unit + live concurrent |
|
||||||
|
| même pubkey/slot, hashes distincts | PK `(pubkey,slot,state_hash)` | unit + live |
|
||||||
|
| collision observation key | comparaison exacte de tous les champs ; conflict sinon | unit + live |
|
||||||
|
| cursor transaction injecté dans account | magic/domain account distincts | unit cursor |
|
||||||
|
| cursor account rejoué sur autre query | digest lie network/pubkey/range/direction/last key | unit cursor matrix |
|
||||||
|
| ordre même slot ambigu | ordre total `(slot,pubkey,state_hash)` | unit + live pagination |
|
||||||
|
| metadata Yellowstone promue au state | is_startup/transaction_signature/write_version uniquement observation | schema + mapping canary |
|
||||||
|
| transaction_signature impose transaction row | aucune FK vers `ksp_raw_transactions` | schema canary |
|
||||||
|
| SQL/server text fuit | projection PostgresBackendError statique | hardening |
|
||||||
|
| V000/V001 modifiées | checksums figés et canaries exactes | migration tests |
|
||||||
|
| index non justifié | un seul index métier V002 pour query unfiltered | schema completeness |
|
||||||
|
|
||||||
|
## 10. Audit historique kbot3
|
||||||
|
|
||||||
|
| Élément | Classement | Disposition |
|
||||||
|
|----------------------------------------------------------|------------|-----------------------------------------------------------------------|
|
||||||
|
| unicité observation_key et FK comme invariants physiques | REPRENDRE | garder le principe, avec clés KSP actuelles |
|
||||||
|
| index account+slot pour navigation déterministe | REPRENDRE | réutiliser le principe seulement pour les queries réellement exposées |
|
||||||
|
| NUMERIC(20,0) pour lamports/rent_epoch | REPRENDRE | étendre à slot/write_version pour le domaine u64 complet |
|
||||||
|
| BIGSERIAL/id technique comme identité | REDESSINER | identité durable = `(pubkey, slot, state_hash)` |
|
||||||
|
| pubkey/owner/hash en TEXT/base58 | REDESSINER | BYTEA fixed-width |
|
||||||
|
| account data en base64 + `space` indépendant | REDESSINER | BYTEA complet ; longueur dérivée ; aucun double truth |
|
||||||
|
| observation duplique l’état canonique | REDESSINER | observation référence un state complet séparé |
|
||||||
|
| slot BIGINT/i64 | REDESSINER | NUMERIC(20,0) sans narrowing |
|
||||||
|
| normalisation N1 -> CORE + processing ledger/version | REPORTER | future CORE/jobs/workers ; hors Store RAW 0.3.4 |
|
||||||
|
| batch maximum 1000 / policy de sélection | REPORTER | policy worker/job ; pas pagination Store |
|
||||||
|
| indexes owner/provider/method/time/status | REPORTER | aucune query publique actuelle ne les justifie |
|
||||||
|
| status/error_message persistés dans observation RAW | REJETER | observation réussie uniquement ; erreurs sûres hors ligne métier |
|
||||||
|
| sqlx et Store monolithique | REJETER | backend tokio-postgres séparé derrière ksp-store-lib |
|
||||||
|
| maintenance CASCADE/TRUNCATE comme surface normale | REJETER | aucune destructive capability account |
|
||||||
|
|
||||||
|
Le pipeline historique `k_sol_obs_account_observations -> k_sol_core_account_states` reste une source de comparaison, pas une architecture cible. La normalisation versionnée et ses caps de batch sont hors scope `0.3.4`.
|
||||||
|
|
||||||
|
## 11. Canaries `0.3.3` à faire évoluer
|
||||||
|
|
||||||
|
À l'ouverture effective de la surface account :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-lib/tests/dependency_boundary.rs
|
||||||
|
remplacer interdiction RawAccount par exigence des 4 impl + absence types SQL
|
||||||
|
|
||||||
|
ksp-store-lib/tests/hardening_completeness.rs
|
||||||
|
remplacer inventaire 6 + RawAccount fermé par inventaire exact 10
|
||||||
|
|
||||||
|
ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||||
|
ajouter module raw_account privé et exiger les 4 impl dans runtime bridge
|
||||||
|
conserver V000 et health business-free
|
||||||
|
|
||||||
|
ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||||
|
remplacer interdiction RawAccount globale par inventaire exact 10
|
||||||
|
remplacer l'absence V002/account par checks de surface V002 minimale
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests historiques nommés `pre_XXX` peuvent conserver leurs noms si le contrat qu'ils vérifient reste valide ; seul leur contenu doit être élargi lorsqu'il fige intentionnellement le scope `0.3.3`.
|
||||||
|
|
||||||
|
## 12. Taxonomie d'erreur à prouver
|
||||||
|
|
||||||
|
| Cas | Code/projection attendue | État |
|
||||||
|
|---------------------------------------------------|------------------------------------------------------|--------------|
|
||||||
|
| mauvais réseau opération | WrongNetwork -> `store.wrong_network` | PENDING impl |
|
||||||
|
| référence absente pour observation supplémentaire | ReferenceNotFound -> `store.raw_reference_not_found` | PENDING impl |
|
||||||
|
| contenu divergent / collision observation | Conflict -> `store_api.raw_conflict` | PENDING impl |
|
||||||
|
| cursor/query invalide | QueryInvalid -> `store_api.raw_query_invalid` | PENDING impl |
|
||||||
|
| page > limite physique PostgreSQL | PageLimitUnsupported | PENDING impl |
|
||||||
|
| row DB incompatible/malformée | DataInvalid | PENDING impl |
|
||||||
|
| échec SELECT | ReadFailed | PENDING impl |
|
||||||
|
| échec INSERT/transaction | WriteFailed | PENDING impl |
|
||||||
|
| V002 absente/incompatible/divergente | MigrationMismatch / MigrationFailed / SchemaNewer | PENDING impl |
|
||||||
|
|
||||||
|
Aucune nouvelle variante backend n'est justifiée au gate `pre.001`.
|
||||||
|
|
||||||
|
## 13. Preuve live finale
|
||||||
|
|
||||||
|
Le test live account devra être opt-in/ignored, URI stdin, sans environnement ni fuite de secret. Il doit couvrir V002 et rejouer un canari transaction dans la même base afin de prouver la coexistence cross-family.
|
||||||
|
|
||||||
|
| Scénario live | Obligation |
|
||||||
|
|-----------------------------------------|-------------|
|
||||||
|
| schema update V001 -> V002 | obligatoire |
|
||||||
|
| reopen idempotent | obligatoire |
|
||||||
|
| u64 > i64::MAX / u64::MAX | obligatoire |
|
||||||
|
| data empty / 16 MiB | obligatoire |
|
||||||
|
| identical/divergent acquisition | obligatoire |
|
||||||
|
| same pubkey+slot / distinct hash | obligatoire |
|
||||||
|
| additional observation + missing ref | obligatoire |
|
||||||
|
| collision observation | obligatoire |
|
||||||
|
| keyset filtered/unfiltered ASC/DESC | obligatoire |
|
||||||
|
| cursor hostile/cross-query/cross-family | obligatoire |
|
||||||
|
| concurrency | obligatoire |
|
||||||
|
| cancellation rollback | obligatoire |
|
||||||
|
| RawTransaction regression canary | obligatoire |
|
||||||
|
| cleanup isolation | obligatoire |
|
||||||
|
|
||||||
|
## 14. Sizing
|
||||||
|
|
||||||
|
| Tranche | Objet | État |
|
||||||
|
|---------|-------------------------------------------------------------------|---------|
|
||||||
|
| pre.001 | audit, kbot3, threat model, V002 design, sizing, plan/validation | CURRENT |
|
||||||
|
| pre.002 | V002 registry + deux tables + PK/FK de base, sans repository | PLANNED |
|
||||||
|
| pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | PLANNED |
|
||||||
|
| pre.004 | mapping privé state/observation + get reads + hostile rows | PLANNED |
|
||||||
|
| pre.005 | acquisition atomique state+observation + idempotence/conflict | PLANNED |
|
||||||
|
| pre.006 | observation supplémentaire + races/cancellation unitaires | PLANNED |
|
||||||
|
| pre.007 | list RawAccountStateQuery + keyset cursor V1 account | PLANNED |
|
||||||
|
| pre.008 | 4 impl backend + 4 dispatch Store + conformance 10/10 | PLANNED |
|
||||||
|
| pre.009 | preuve PostgreSQL live account + coexistence RawTransaction | PLANNED |
|
||||||
|
| pre.010 | hardening/completeness cross-family + canaries ownership | PLANNED |
|
||||||
|
| pre.011 | gate technique final + replay live ciblé + graphes | PLANNED |
|
||||||
|
| pre.012 | réconciliation docs finale sans rouvrir le code | PLANNED |
|
||||||
|
| pre.013 | publication prep minimale + prompt 0.3.5 | PLANNED |
|
||||||
|
| rel.001 | stabilisation/tag après gate opérateur | PLANNED |
|
||||||
|
|
||||||
|
## 15. Negative scope final
|
||||||
|
|
||||||
|
| Surface | Doit rester absent en 0.3.4 |
|
||||||
|
|--------------------------------------------|-----------------------------|
|
||||||
|
| ksp-store-api redesign | oui |
|
||||||
|
| account retention/archive/purge | oui |
|
||||||
|
| CORE/STRUCTURAL persistence | oui |
|
||||||
|
| workers/jobs/backfills | oui |
|
||||||
|
| Store batch/priorité policy | oui |
|
||||||
|
| owner/provider/time/status queries/indexes | oui |
|
||||||
|
| Transport -> Store coupling | oui |
|
||||||
|
| PostgreSQL types via ksp-store-lib | oui |
|
||||||
|
| nouveau backend | oui |
|
||||||
|
|
||||||
|
## 16. Gate opérateur prévu pour clôture technique
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.4
|
||||||
|
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 test --workspace
|
||||||
|
cargo tree -p ksp-store-lib --edges normal
|
||||||
|
cargo tree -p ksp-store-lib -e features
|
||||||
|
cargo tree -p ksp-store-postgres-lib --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Les preuves live restent séparées et explicitement `--ignored --nocapture`.
|
||||||
|
|
||||||
|
## 17. Verdict `pre.001`
|
||||||
|
|
||||||
|
Design : **PASS**. Les audits Rust/Markdown de l'overlay sont propres.
|
||||||
|
|
||||||
|
Compilation/Clippy/tests post-overlay : **NON EXÉCUTÉS dans le conteneur d'assemblage**, faute d'exécutable `cargo`; ils restent à rejouer par l'opérateur.
|
||||||
|
|
||||||
|
Implémentation V002/account : **PENDING**, volontairement non ouverte dans cette tranche.
|
||||||
Reference in New Issue
Block a user