203 lines
10 KiB
Markdown
203 lines
10 KiB
Markdown
<!-- file: ks-store/USAGE.md -->
|
||
<!-- version: 10 -->
|
||
|
||
# Utilisation de ks-store
|
||
|
||
## Ouvrir le store
|
||
|
||
Le consommateur transmet uniquement le backend sélectionné et son objet d'options déjà résolu. Il ne construit jamais un pool PostgreSQL.
|
||
|
||
```rust
|
||
let options = match ks_store::StoreOpenOptions::new(
|
||
true,
|
||
"postgres",
|
||
serde_json::json!({
|
||
"url": database_url,
|
||
"max_connections": 10,
|
||
"connect_timeout_ms": 10_000,
|
||
"auto_initialize_schema": true
|
||
}),
|
||
) {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
let store = match ks_store::Store::open(options).await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
```
|
||
|
||
`Store::open` interprète les options du backend actif, crée la connexion, valide d'abord les définitions déjà présentes et n'exécute l'auto-initialisation que pour compléter des ressources gérées absentes lorsque l'option l'autorise. Il capture ensuite un résumé d'initialisation sanitisé. PostgreSQL reste le seul backend opérationnel exigé en `0.5.3`; un code backend inconnu est refusé proprement.
|
||
|
||
Avec PostgreSQL, l'auto-initialisation utilise un **seul orchestrateur transactionnel global**. Il ne faut pas initialiser séparément raw/Core/decode : la cohérence du baseline est vérifiée comme un contrat unique.
|
||
|
||
## Baseline PostgreSQL actif
|
||
|
||
`0.5.3-pre.003` utilise directement le namespace `k_sol_*` et un baseline candidat au gel de 16 tables N1-N3. `ks-store` ne possède que ses ressources `k_sol_*` courantes : les tables étrangères ou historiques, y compris `kb_sol_*`, sont ignorées tant qu'elles ne sont pas référencées par le contrat actif.
|
||
|
||
`auto_initialize_schema` applique uniquement des évolutions additives. Si une table, colonne, PK/FK ou index géré manque, `false` provoque une erreur ; avec `true`, `ks-store` tente de créer uniquement les ressources absentes puis revalide le contrat complet. Une colonne existante avec un type, une nullabilité, une précision numérique ou un défaut incompatible provoque toujours une erreur : l'auto-initialisation n'exécute aucun `DROP COLUMN`, `ALTER COLUMN` ni renommage. Le même principe s'applique aux clés et index existants dont la définition ne correspond pas au contrat.
|
||
|
||
Les ressources actives sont sous :
|
||
|
||
```text
|
||
ks-store/migrations/postgres/
|
||
schema/
|
||
tables/
|
||
constraints/
|
||
indexes/
|
||
maintenance/
|
||
truncate/
|
||
drop/
|
||
```
|
||
|
||
Chaque fichier contient une seule instruction SQL. Les fonctions privées du backend l'embarquent par `include_str!`; le DDL n'est pas dupliqué dans des chaînes Rust.
|
||
|
||
Le baseline conserve 16 tables et compte désormais 240 ressources SQL atomiques et 79 index attendus après la revue des chemins de replay `pre.004`.
|
||
|
||
## Résumés sûrs
|
||
|
||
```rust
|
||
let configuration = store.configuration_summary();
|
||
let initialization = store.initialization_summary();
|
||
let runtime = match store.runtime_summary().await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
```
|
||
|
||
Ces contrats ne contiennent pas les options backend brutes. Le descripteur de connexion éventuellement exposé par `runtime.backend` est masqué par le backend propriétaire.
|
||
|
||
Le résumé d'initialisation classe les ressources par modèles logiques et fournit des compteurs. `objects` expose des catégories physiques sans noms d'objets. PostgreSQL publie actuellement les catégories `table` et `index`; un futur backend peut publier d'autres catégories sans modifier le contrat desktop.
|
||
|
||
## Diagnostics de ressources
|
||
|
||
```rust
|
||
let raw_resources = match store.raw_resource_diagnostics().await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
for resource in raw_resources {
|
||
println!(
|
||
"resource={} model={} available={}",
|
||
resource.resource_code,
|
||
resource.model_code,
|
||
resource.available
|
||
);
|
||
}
|
||
```
|
||
|
||
Les méthodes `core_resource_diagnostics`, `decode_resource_diagnostics` et `known_resource_diagnostics` suivent le même contrat backend-agnostique. Les codes de ressources publics sont logiques ; les noms physiques PostgreSQL restent internes.
|
||
|
||
## Contrats de repositories
|
||
|
||
Les traits publics effectivement implémentés sont :
|
||
|
||
- `StoreHealthStore` ;
|
||
- `RawTransactionStore` ;
|
||
- `CoreTransactionStore` ;
|
||
- `AccountStateStore` ;
|
||
- `CoreExtractionStore` ;
|
||
- `DecodePipelineStore`.
|
||
|
||
`Store` implémente ces traits et délègue au backend privé.
|
||
|
||
Le couple générique observation/état de compte est opérationnel en `pre.004` : `RawTransactionStore` persiste les observations N1, puis `AccountStateStore` sélectionne et normalise de manière versionnée/idempotente vers N2. Une nouvelle version du normalizer n’est pas masquée par la présence d’un ancien état Core : le processing ledger porte l’identité/version/hash du traitement.
|
||
|
||
## Core et replay N2 -> N3
|
||
|
||
Le replay d'instruction Core transporte désormais :
|
||
|
||
- `block_time` nullable ;
|
||
- `stack_height` nullable ;
|
||
- `parent_instruction_path` pour les CPI ;
|
||
- le contexte ordonné des instructions top-level ;
|
||
- la `returnData` transactionnelle lorsqu'elle existe.
|
||
|
||
Le contrat de replay Core est en version 3. Les anciens constructeurs restent utilisables pour les tests/sources synthétiques ; `ks-store` enrichit les inputs issus du Core persistant avec le contexte disponible.
|
||
|
||
Les instructions top-level et CPI restent deux tables physiques, mais la lecture/replay utilise une union logique commune. `CoreInstructionScope` distingue explicitement `TopLevel` et `Inner`, et le lifecycle est mis à jour dans la table physique correspondant au chemin sélectionné.
|
||
|
||
## Matérialisation N3
|
||
|
||
Le journal générique est `k_sol_mat_outputs`. L'API publique conserve la lecture bornée de processing et ajoute une lecture de consultation comptée :
|
||
|
||
- `MaterializedOutputFilter` + `DecodePipelineStore::list_materialized_outputs` pour les workflows bornés existants ;
|
||
- `MaterializedOutputPageFilter` + `Store::materialized_output_page` pour les listings cursorisés ;
|
||
- `MaterializedOutputQueryRow` comme ligne générique commune.
|
||
|
||
`MaterializedOutputPageFilter` peut transporter des groupes bornés de contraintes structurelles sur le payload JSON : tous les groupes doivent correspondre, mais une seule alternative par groupe suffit. Le backend PostgreSQL applique exactement ces mêmes contraintes au `COUNT` et à la page ; le Store reste donc générique et ne connaît ni mint, ni ATA, ni opération SPL.
|
||
|
||
`k_sol_mat_outputs` reste obligatoire même lorsque des projections N4 spécialisées existeront.
|
||
|
||
## Replay et requêtes bornées
|
||
|
||
```rust
|
||
let candidates = match store.replay_transaction_candidates(filter).await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
let programs = match store.replay_program_summaries(program_filter).await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
let entities = match store.replay_entity_summaries(entity_filter).await {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
```
|
||
|
||
Les contrats publics utilisent `ReplayTransaction*`, `ReplayProgram*` et `ReplayEntity*`. Le scope d'instruction utilise `TopLevel`, `Inner` et `Logs`; le terme historique `outer` n'est pas introduit dans la nouvelle API store.
|
||
|
||
## Pagination bornée
|
||
|
||
```rust
|
||
let first_page = ks_store::PageRequest::first_page();
|
||
let next_page = match ks_store::PageRequest::after(
|
||
250,
|
||
cursor,
|
||
) {
|
||
Ok(value) => value,
|
||
Err(error) => return Err(error),
|
||
};
|
||
assert_eq!(first_page.limit, ks_store::DEFAULT_PAGE_SIZE);
|
||
assert!(next_page.limit <= ks_store::MAX_PAGE_SIZE);
|
||
```
|
||
|
||
`PageRequest` n’expose plus d’offset durable. Pour le replay Core, le curseur encode la clé stable `(slot, signature, scope, instruction_path)` ; la requête demande `limit + 1` afin de produire `PageSlice.next_cursor` sans doublon ni saut sur un dataset figé. Les lectures interactives sont bornées à 500 lignes. Les batches de processing Core/decode/account-state utilisent leurs propres limites, plafonnées à 1 000.
|
||
|
||
## Transactions atomiques
|
||
|
||
Les bundles `AccountStatePersistenceBundle`, `CoreExtractionBundle`, `DecodePersistenceBundle` et `MaterializationPersistenceBundle` regroupent les écritures qui doivent réussir ou être annulées ensemble. Les consommateurs ne doivent pas reproduire manuellement ces transactions avec des écritures SQL isolées.
|
||
|
||
## Tests PostgreSQL réels
|
||
|
||
Les tests opt-in utilisent `KS_SECRET_POSTGRES_TEST_URL`. Aucun credential réel ne doit être enregistré dans les fixtures ou logs. Les erreurs d'ouverture de connexion sont volontairement sanitisées.
|
||
|
||
Pour le baseline `0.5.3`, les validations PostgreSQL à exécuter après compilation sont particulièrement importantes :
|
||
|
||
- présence des 16 tables ;
|
||
- compatibilité des 248 colonnes actuellement gérées : type, nullabilité, précision numérique et défaut requis ;
|
||
- compatibilité des 25 clés primaires/étrangères attendues et des 79 index physiques attendus, pas seulement leur nombre ;
|
||
- acceptation des tables étrangères/historiques et des colonnes additives compatibles ;
|
||
- refus d'une dérive structurelle d'une ressource gérée qui casserait les lectures ou écritures courantes ;
|
||
- réouverture idempotente ;
|
||
- roundtrip raw/Core/decode/materialization ;
|
||
- roundtrip account observation -> account state, y compris nouvelle version de normalizer et force replay ;
|
||
- replay top-level + CPI paginé sur plusieurs pages ;
|
||
- invalidation descendante lors d’un remplacement Core/decode ;
|
||
- rollback transactionnel account-state/Core/decode.
|
||
|
||
## Travaux encore ouverts après `pre.004`
|
||
|
||
- validation PostgreSQL réelle des nouveaux chemins account-state, CPI, invalidation et pagination sur dataset représentatif ;
|
||
- revue finale des plans `EXPLAIN` avant de considérer les 79 index comme suffisants ;
|
||
- snapshot machine-readable et gel formel N1/N2/N3 dans la tranche de réconciliation technique ;
|
||
- refonte desktop fonctionnelle finale autour du nouveau baseline en `pre.005` ;
|
||
- projections N4 spécialisées, hors `0.5.3`.
|
||
|
||
## Pagination des listings Store
|
||
|
||
Les lectures interactives restent bornées à 500 lignes par bloc. Les filtres fonctionnels ne portent pas d’offset : le consumer transmet un `PageRequest { limit, cursor }` et reçoit une page stable. Les Replay Candidates et les listings cursorisés de matérialisations utilisent `CountedPageSlice<T>` afin de retourner en plus `total_rows`; le desktop peut ainsi afficher `Bloc N / total` tout en conservant un curseur opaque.
|
||
|
||
Le comptage est rafraîchi à chaque chargement de bloc. Un changement de filtre doit invalider l’historique de curseurs et repartir du premier bloc. La pagination DataTables du desktop est locale aux 500 lignes déjà chargées et ne modifie jamais la pagination repository.
|