v0.5.3-pre.002

This commit is contained in:
2026-08-11 22:22:40 +02:00
parent 01d78b5845
commit 8448ad1079
134 changed files with 4518 additions and 3595 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 68 # version: 69
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -19,7 +19,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.5.3-pre.1" version = "0.5.3-pre.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"

View File

@@ -1,5 +1,5 @@
<!-- file: config/README.md --> <!-- file: config/README.md -->
<!-- version: 26 --> <!-- version: 27 -->
# Configuration locale # Configuration locale
@@ -111,7 +111,7 @@ Les defaults portent les timeouts, capacités de channels et `auto_reconnect`. U
## Store ## Store
`store.config.json` possède la sélection et les paramètres PostgreSQL/SQLite. Le split reste structurel : aucune migration SQL n'est introduite par `0.5.1`. `store.config.json` possède la sélection du backend et un objet `backend_options` propre au profil sélectionné. `ks-config` conserve ce bloc opaque après composition/résolution ; `ks-store` seul interprète les champs PostgreSQL actuels (`url`, pool, timeout, auto-initialisation). Ajouter un futur moteur ne doit donc pas ajouter ses paramètres au contrat runtime commun tant qu'il n'est pas sélectionné.
## Frontière source/runtime/public/diagnostic ## Frontière source/runtime/public/diagnostic

View File

@@ -6,19 +6,11 @@
"database": { "database": {
"enabled": true, "enabled": true,
"backend": "postgres", "backend": "postgres",
"postgres": { "backend_options": {
"url": "${KS_SECRET_POSTGRES_DEVNET_URL}", "url": "${KS_SECRET_POSTGRES_DEVNET_URL}",
"max_connections": 8, "max_connections": 8,
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"auto_initialize_schema": true "auto_initialize_schema": true
},
"sqlite": {
"path": "data/local.sqlite",
"create_if_missing": true,
"busy_timeout_ms": 5000,
"max_connections": 1,
"auto_initialize_schema": true,
"use_wal": true
} }
} }
} }

View File

@@ -59,85 +59,17 @@
"required": [ "required": [
"enabled", "enabled",
"backend", "backend",
"postgres", "backend_options"
"sqlite"
], ],
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean" "type": "boolean"
}, },
"backend": { "backend": {
"type": "string",
"enum": [
"postgres",
"sqlite"
]
},
"postgres": {
"$ref": "#/$defs/postgres"
},
"sqlite": {
"$ref": "#/$defs/sqlite"
}
}
},
"postgres": {
"type": "object",
"additionalProperties": false,
"required": [
"url",
"max_connections",
"connect_timeout_ms",
"auto_initialize_schema"
],
"properties": {
"url": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
}, },
"max_connections": { "backend_options": {
"type": "integer", "type": "object"
"minimum": 1
},
"connect_timeout_ms": {
"type": "integer",
"minimum": 1
},
"auto_initialize_schema": {
"type": "boolean"
}
}
},
"sqlite": {
"type": "object",
"additionalProperties": false,
"required": [
"path",
"create_if_missing",
"busy_timeout_ms",
"max_connections",
"auto_initialize_schema",
"use_wal"
],
"properties": {
"path": {
"$ref": "#/$defs/non_empty_string"
},
"create_if_missing": {
"type": "boolean"
},
"busy_timeout_ms": {
"type": "integer",
"minimum": 1
},
"max_connections": {
"type": "integer",
"minimum": 1
},
"auto_initialize_schema": {
"type": "boolean"
},
"use_wal": {
"type": "boolean"
} }
} }
}, },

View File

@@ -31,85 +31,17 @@
"required": [ "required": [
"enabled", "enabled",
"backend", "backend",
"postgres", "backend_options"
"sqlite"
], ],
"properties": { "properties": {
"enabled": { "enabled": {
"type": "boolean" "type": "boolean"
}, },
"backend": { "backend": {
"type": "string",
"enum": [
"postgres",
"sqlite"
]
},
"postgres": {
"$ref": "#/$defs/postgres"
},
"sqlite": {
"$ref": "#/$defs/sqlite"
}
}
},
"postgres": {
"type": "object",
"additionalProperties": false,
"required": [
"url",
"max_connections",
"connect_timeout_ms",
"auto_initialize_schema"
],
"properties": {
"url": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
}, },
"max_connections": { "backend_options": {
"type": "integer", "type": "object"
"minimum": 1
},
"connect_timeout_ms": {
"type": "integer",
"minimum": 1
},
"auto_initialize_schema": {
"type": "boolean"
}
}
},
"sqlite": {
"type": "object",
"additionalProperties": false,
"required": [
"path",
"create_if_missing",
"busy_timeout_ms",
"max_connections",
"auto_initialize_schema",
"use_wal"
],
"properties": {
"path": {
"$ref": "#/$defs/non_empty_string"
},
"create_if_missing": {
"type": "boolean"
},
"busy_timeout_ms": {
"type": "integer",
"minimum": 1
},
"max_connections": {
"type": "integer",
"minimum": 1
},
"auto_initialize_schema": {
"type": "boolean"
},
"use_wal": {
"type": "boolean"
} }
} }
}, },

View File

@@ -6,19 +6,11 @@
"database": { "database": {
"enabled": true, "enabled": true,
"backend": "postgres", "backend": "postgres",
"postgres": { "backend_options": {
"url": "${KS_SECRET_POSTGRES_DEVNET_URL}", "url": "${KS_SECRET_POSTGRES_DEVNET_URL}",
"max_connections": 8, "max_connections": 8,
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"auto_initialize_schema": true "auto_initialize_schema": true
},
"sqlite": {
"path": "data/local.sqlite",
"create_if_missing": true,
"busy_timeout_ms": 5000,
"max_connections": 1,
"auto_initialize_schema": true,
"use_wal": true
} }
} }
}, },
@@ -27,19 +19,11 @@
"database": { "database": {
"enabled": true, "enabled": true,
"backend": "postgres", "backend": "postgres",
"postgres": { "backend_options": {
"url": "${KS_SECRET_POSTGRES_MAINNET_URL}", "url": "${KS_SECRET_POSTGRES_MAINNET_URL}",
"max_connections": 16, "max_connections": 16,
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"auto_initialize_schema": true "auto_initialize_schema": true
},
"sqlite": {
"path": "data/mainnet-import.sqlite",
"create_if_missing": true,
"busy_timeout_ms": 5000,
"max_connections": 1,
"auto_initialize_schema": true,
"use_wal": true
} }
} }
}, },
@@ -48,19 +32,11 @@
"database": { "database": {
"enabled": true, "enabled": true,
"backend": "postgres", "backend": "postgres",
"postgres": { "backend_options": {
"url": "${KS_SECRET_POSTGRES_MAINNET_URL}", "url": "${KS_SECRET_POSTGRES_MAINNET_URL}",
"max_connections": 16, "max_connections": 16,
"connect_timeout_ms": 5000, "connect_timeout_ms": 5000,
"auto_initialize_schema": true "auto_initialize_schema": true
},
"sqlite": {
"path": "data/mainnet.sqlite",
"create_if_missing": true,
"busy_timeout_ms": 5000,
"max_connections": 1,
"auto_initialize_schema": true,
"use_wal": true
} }
} }
} }

View File

@@ -1,5 +1,5 @@
<!-- file: docs/DEVNET_EXECUTION_GUIDE.md --> <!-- file: docs/DEVNET_EXECUTION_GUIDE.md -->
<!-- version: 27 --> <!-- version: 28 -->
# Guide dexécution Devnet # Guide dexécution Devnet
@@ -30,7 +30,7 @@ Les commandes réutilisables sont centralisées en section 4. Chaque scénario r
- le wallet temporaire persistant nest jamais copié dans les logs ou les preuves ; - le wallet temporaire persistant nest jamais copié dans les logs ou les preuves ;
- le financement est réalisé par un faucet Web Devnet, pas par `solana airdrop`. - le financement est réalisé par un faucet Web Devnet, pas par `solana airdrop`.
Lorsque `database.postgres.auto_initialize_schema` vaut `true`, louverture dune fenêtre Devnet crée les tables manquantes. Lorsquil vaut `false`, le schéma doit être préparé avant louverture. Depuis `0.5.3-pre.002`, le desktop ne lit plus une section PostgreSQL concrète. Le profil sélectionne `database.backend` et transporte ses paramètres dans `database.backend_options`, interprétés uniquement par `ks-store`. Lorsque loption backend `auto_initialize_schema` vaut `true`, louverture persistante du `Store` peut initialiser le schéma du backend actif. Lorsquelle vaut `false`, le modèle de stockage doit déjà être disponible.
## 3. Organisation des terminaux ## 3. Organisation des terminaux
@@ -81,7 +81,7 @@ Attendre avant tout scénario :
- le démarrage de Vite ; - le démarrage de Vite ;
- le lancement du binaire `kb-app-demo-desktop` ; - le lancement du binaire `kb-app-demo-desktop` ;
- louverture de la fenêtre ; - louverture de la fenêtre ;
- le message PostgreSQL indiquant que les 13 tables sont prêtes ; - le résumé store indiquant que le backend actif est joignable et que les modèles logiques attendus sont prêts ;
- la résolution correcte du profil `local_devnet`. - la résolution correcte du profil `local_devnet`.
Ne pas exécuter les commandes `C06` ou `C07` dans ce terminal. Ne pas exécuter les commandes `C06` ou `C07` dans ce terminal.
@@ -330,7 +330,7 @@ Un scénario nest validé que si le replay ne produit ni échec fonctionnel n
1. Dans le terminal B, exécuter `C01`, `C02`, `C03` et `C04`. 1. Dans le terminal B, exécuter `C01`, `C02`, `C03` et `C04`.
2. Dans le terminal B, exécuter `C05` pour repartir dune base propre. 2. Dans le terminal B, exécuter `C05` pour repartir dune base propre.
3. Dans le terminal A, exécuter `T01`. 3. Dans le terminal A, exécuter `T01`.
4. Attendre la création des 13 tables et la disponibilité de la fenêtre. 4. Attendre que le résumé store confirme la disponibilité des modèles logiques attendus, puis la disponibilité de la fenêtre.
5. Dans la fenêtre Configuration, vérifier que `local_devnet` utilise la base Devnet. 5. Dans la fenêtre Configuration, vérifier que `local_devnet` utilise la base Devnet.
6. Dans le terminal B, exécuter le contrôle de tables de `C05`. 6. Dans le terminal B, exécuter le contrôle de tables de `C05`.
7. Financer la pubkey par un faucet Web Devnet si le solde est insuffisant. 7. Financer la pubkey par un faucet Web Devnet si le solde est insuffisant.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/STORAGE_ARCHITECTURE.md --> <!-- file: docs/architecture/STORAGE_ARCHITECTURE.md -->
<!-- version: 5 --> <!-- version: 7 -->
# Architecture du stockage # Architecture du stockage
@@ -7,30 +7,38 @@
`ks-store` réunit : `ks-store` réunit :
- la façade publique backend-agnostique `Store` et ses options d'ouverture ;
- les contrats store-neutral ; - les contrats store-neutral ;
- les DTO et entités persistées ; - les DTO et entités persistées ;
- la pagination et les rapports de santé ; - la pagination, le replay et les rapports de santé ;
- les traits de repositories ; - les traits de repositories ;
- limplémentation PostgreSQL ; - les résumés runtime/init sanitisés ;
- les migrations, requêtes et mécanismes de replay associés. - limplémentation PostgreSQL privée ;
- les migrations et requêtes appartenant au backend actif.
La consolidation remplace lancien découpage entre plusieurs crates de stockage sans supprimer la séparation interne entre contrats et adaptateur PostgreSQL. Depuis `0.5.3-pre.002`, les consommateurs ne construisent plus un backend concret. `ks-store` possède la connexion et le dispatch interne ; `PostgresStore`, `PgPool` et SQLx ne traversent plus sa frontière publique.
## 2. Frontières ## 2. Frontières
### 2.1 Contrats store-neutral ### 2.1 Contrats store-neutral
Les contrats ne doivent pas dépendre des détails SQL lorsquune abstraction stable est suffisante. Ils décrivent les entrées, sorties, identifiants, pages et erreurs nécessaires aux consommateurs. Les contrats ne dépendent pas des détails SQL lorsquune abstraction stable est suffisante. Ils décrivent les entrées, sorties, identifiants, pages, diagnostics et erreurs nécessaires aux consommateurs. `StoreOpenOptions` transporte un code backend et un objet d'options opaque ; seul `ks-store` interprète le moteur sélectionné.
Le résumé runtime expose des modèles logiques et des compteurs d'objets par catégorie sans publier les noms physiques. PostgreSQL peut ainsi exposer des compteurs `table`/`index`, tandis qu'un futur backend peut utiliser d'autres catégories sans modifier le desktop.
### 2.2 Adaptateur PostgreSQL ### 2.2 Adaptateur PostgreSQL
Le module PostgreSQL possède : Le module PostgreSQL possède :
- la validation de ses options ;
- la connexion et linitialisation ; - la connexion et linitialisation ;
- lapplication idempotente des migrations ; - lapplication idempotente des migrations ;
- les requêtes typées ; - les requêtes typées ;
- les diagnostics ; - les diagnostics physiques ;
- les opérations de replay et de sélection de candidats. - les opérations de replay et de sélection de candidats ;
- le target de tracing canonique `ks-store`, enrichi des champs `backend="postgres"`, `domain="ks-store.pg"` et `action` pour les opérations PostgreSQL.
Les détails physiques restent privés. Les erreurs et résumés traversant la façade ne doivent pas divulguer de DSN, password ou option sensible.
### 2.3 Modèles partagés ### 2.3 Modèles partagés
@@ -38,14 +46,14 @@ Les modèles métier communs restent dans `ks-lib` lorsquils dépassent la se
## 3. Catégories de données ## 3. Catégories de données
Le stockage couvre plusieurs niveaux : Le stockage distingue quatre niveaux durables :
- données brutes acquises ; - **N1** — acquisition et raw canonique ;
- transactions et instructions canoniques ; - **N2** — Core Solana canonique ;
- événements de décodage et diagnostics ; - **N3** — decode/materialization générique et journaux versionnés ;
- matérialisations ; - **N4** — projections spécialisées/queryables additives.
- états de campagne et candidats de replay ;
- informations opérationnelles et de santé. N1/N2/N3 constituent la fondation fortement gelée après `0.5.3`, sous réserve d'une urgence ou d'une omission structurelle majeure. N4 reste plus évolutif. Les replays N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être exécutés indépendamment et de manière idempotente.
Les noms de tables, contrats de replay et APIs publiques sont documentés dans `ks-store/USAGE.md` à partir des exports et migrations actuels. Les noms de tables, contrats de replay et APIs publiques sont documentés dans `ks-store/USAGE.md` à partir des exports et migrations actuels.

View File

@@ -1,32 +1,37 @@
<!-- file: docs/guides/POSTGRES_STORAGE.md --> <!-- file: docs/guides/POSTGRES_STORAGE.md -->
<!-- version: 3 --> <!-- version: 5 -->
# Guide PostgreSQL et contrats de stockage # Guide PostgreSQL et contrats de stockage
## Objectif ## Objectif
`ks-store` consolide les contrats de stockage Core, raw, decode et PostgreSQL de bot2 dans une crate unique. `ks-store` porte la frontière de stockage généraliste. PostgreSQL reste le backend opérationnel actif, mais il est encapsulé derrière la façade publique backend-agnostique `Store`.
## Connexion ## Connexion
Les consommateurs ne construisent plus `PostgresStore`, `PostgresStoreOptions` ni un pool SQLx. Ils transmettent le backend sélectionné et ses options résolues à `StoreOpenOptions`; `ks-store` valide et interprète ensuite les paramètres PostgreSQL.
```rust ```rust
let options = match ks_store::PostgresStoreOptions::new( let options = match ks_store::StoreOpenOptions::new(
database_url,
10,
10_000,
true, true,
"postgres",
serde_json::json!({
"url": database_url,
"max_connections": 10,
"connect_timeout_ms": 10_000,
"auto_initialize_schema": true
}),
) { ) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store = match ks_store::Store::open(options).await {
let store = match ks_store::PostgresStore::connect(options).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
``` ```
Toujours utiliser `masked_dsn()` dans les diagnostics. Le descripteur public éventuellement affiché provient du résumé runtime et est déjà masqué par `ks-store`. Le DSN brut et les options backend ne doivent jamais être propagés dans les DTO, erreurs applicatives ou logs normaux.
## Domaines ## Domaines
@@ -37,7 +42,9 @@ Toujours utiliser `masked_dsn()` dans les diagnostics.
## Migrations ## Migrations
Les migrations sont idempotentes et ordonnées. Une nouvelle migration ne doit pas modifier rétroactivement une migration déjà publiée. `0.5.3-pre.002` conserve temporairement le schéma PostgreSQL historique `kb_sol_*` et son mécanisme d'initialisation afin de réaliser d'abord la rupture d'API backend. `pre.003` reconstruit le baseline sous `k_sol_*`, range les ressources sous `ks-store/migrations/postgres/` et impose une instruction SQL par fichier exécutée par les fonctions privées du backend.
Les bases Devnet/Mainnet/test `0.5.2` sont reconstructibles : aucune compatibilité in-place n'est requise pour ce changement de baseline.
## Compatibilité des identités de processeurs ## Compatibilité des identités de processeurs
@@ -47,15 +54,20 @@ Aucune migration SQL automatique de ces valeurs n'est ajoutée en `0.5.1` : avan
## Repositories ## Repositories
Les traits publics séparent le contrat de limplémentation PostgreSQL. Les opérations de lecture utilisent des filtres et paginations bornés. Les traits publics et la façade `Store` décrivent des opérations fonctionnelles indépendantes du moteur. PostgreSQL implémente ces contrats à lintérieur de `ks-store`; aucune crate consommatrice ne reçoit `PgPool`, un type SQLx ou une requête concrète. Les opérations de lecture utilisent des filtres et paginations bornés.
## Diagnostics ## Diagnostics
La surface publique expose :
- health snapshot ; - health snapshot ;
- migration snapshot ; - migration snapshot ;
- backend diagnostics ; - backend descriptor masqué ;
- diagnostics des tables raw, Core et decode ; - résumé de configuration sanitisé ;
- validation des noms de tables. - résumé d'initialisation/vérification par modèle logique ;
- diagnostics de ressources raw/Core/decode/materialization sans nom physique d'objet.
Les noms de tables, index, contraintes et le SQL restent des détails backend-internes. Les traces PostgreSQL utilisent lunique `target: crate::TRACING_TARGET` (`ks-store`) avec les champs structurés `backend="postgres"`, `domain="ks-store.pg"` et un `action` précis (`connection`, `migration`, `query`, `health`, `replay`, etc.).
## Invariants ## Invariants
@@ -63,7 +75,7 @@ Les traits publics séparent le contrat de limplémentation PostgreSQL. Les o
- les écritures rejouables doivent être idempotentes ; - les écritures rejouables doivent être idempotentes ;
- la progression de campagne doit rester cohérente avec les lignes effectivement traitées ; - la progression de campagne doit rester cohérente avec les lignes effectivement traitées ;
- les requêtes dynamiques nacceptent que des identifiants validés ; - les requêtes dynamiques nacceptent que des identifiants validés ;
- les erreurs PostgreSQL restent distinctes des erreurs de contrat. - les détails PostgreSQL utiles au diagnostic restent backend-internes ; les erreurs traversant la façade publique ne doivent jamais divulguer DSN, password ou options sensibles.
## Références ## Références

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md --> <!-- file: docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md -->
<!-- version: 5 --> <!-- version: 7 -->
# Plan `0.5.3` — audit et normalisation de `ks-store` # Plan `0.5.3` — audit et normalisation de `ks-store`
@@ -55,7 +55,7 @@ Les décisions suivantes sont retenues pour les tranches suivantes :
14. **`ks-store` ne dépend pas de `ks-config`** : la frontière applicative transporte une configuration résolue, mais l'interprétation du backend et la création de la connexion sont internes à `ks-store` ; 14. **`ks-store` ne dépend pas de `ks-config`** : la frontière applicative transporte une configuration résolue, mais l'interprétation du backend et la création de la connexion sont internes à `ks-store` ;
15. **le bloc de configuration backend devient sélectionné et opaque jusqu'à `ks-store`**, afin que `ks-config` n'impose plus simultanément les paramètres de backends non sélectionnés ; 15. **le bloc de configuration backend devient sélectionné et opaque jusqu'à `ks-store`**, afin que `ks-config` n'impose plus simultanément les paramètres de backends non sélectionnés ;
16. **`ks-store` expose un résumé runtime sans secrets** destiné notamment à `kb-app-demo-desktop` : backend actif, état de connexion, options publiques retenues, auto-initialisation, version/état du schéma et tables créées/vérifiées au démarrage ; 16. **`ks-store` expose un résumé runtime sans secrets** destiné notamment à `kb-app-demo-desktop` : backend actif, état de connexion, options publiques retenues, auto-initialisation, version/état du schéma et tables créées/vérifiées au démarrage ;
17. **les surfaces `demo_sql` du desktop seront adaptées** à cette façade et à ces diagnostics génériques lorsque l'API change ; aucune logique SQL/PostgreSQL ne doit revenir dans l'application ; 17. **les surfaces historiques `demo_sql_*` du desktop sont renommées structurellement en `demo_store_*` dès `pre.002`** ; la refonte fonctionnelle finale des tableaux/compteurs reste planifiée plus tard et aucune logique SQL/PostgreSQL ne doit revenir dans l'application ;
18. **toutes les opérations PostgreSQL portent un target de logging sous `ks-store.pg`** ; le SQL brut peut être journalisé au niveau `trace`, sans valeurs bindées ni secrets, tandis que les niveaux normaux journalisent l'identité d'opération, la durée et le résultat ; 18. **toutes les opérations PostgreSQL portent un target de logging sous `ks-store.pg`** ; le SQL brut peut être journalisé au niveau `trace`, sans valeurs bindées ni secrets, tandis que les niveaux normaux journalisent l'identité d'opération, la durée et le résultat ;
19. **le contrat de page publique abandonne les gros offsets comme mécanisme durable** au profit d'une pagination stable/cursorisée pour les lectures qui en ont besoin ; aucune requête UI ne doit pouvoir demander 100 000 lignes en un appel ; 19. **le contrat de page publique abandonne les gros offsets comme mécanisme durable** au profit d'une pagination stable/cursorisée pour les lectures qui en ont besoin ; aucune requête UI ne doit pouvoir demander 100 000 lignes en un appel ;
20. **les index non uniques sont des objets physiques évolutifs**, exclus du gel logique des tables ; les PK, FK, uniques, checks, types, nullabilités et sémantiques de colonnes sont gelés ; 20. **les index non uniques sont des objets physiques évolutifs**, exclus du gel logique des tables ; les PK, FK, uniques, checks, types, nullabilités et sémantiques de colonnes sont gelés ;
@@ -67,7 +67,7 @@ Les décisions suivantes sont retenues pour les tranches suivantes :
26. **les replays inter-niveaux sont des contrats de premier rang** : N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être rejoués indépendamment, de façon idempotente, versionnée et avec invalidation descendante contrôlée ; 26. **les replays inter-niveaux sont des contrats de premier rang** : N1 -> N2, N2 -> N3 et N3 -> N4 doivent pouvoir être rejoués indépendamment, de façon idempotente, versionnée et avec invalidation descendante contrôlée ;
27. **`k_sol_mat_outputs` est conservée impérativement comme journal N3 générique** même lorsque des projections N4 existent ; une projection spécialisée n'est jamais un remplacement de la sortie matérialisée canonique/versionnée ; 27. **`k_sol_mat_outputs` est conservée impérativement comme journal N3 générique** même lorsque des projections N4 existent ; une projection spécialisée n'est jamais un remplacement de la sortie matérialisée canonique/versionnée ;
28. **les metadata auront à terme deux projections N4 conceptuelles distinctes** : une projection canonique d'asset/token alimentable par Metaplex Token Metadata et Token-2022, et une projection de metadata de programme pour Solana Program Metadata (SPM) ; elles ne sont pas créées en `0.5.3`, mais leur séparation conceptuelle est retenue ; 28. **les metadata auront à terme deux projections N4 conceptuelles distinctes** : une projection canonique d'asset/token alimentable par Metaplex Token Metadata et Token-2022, et une projection de metadata de programme pour Solana Program Metadata (SPM) ; elles ne sont pas créées en `0.5.3`, mais leur séparation conceptuelle est retenue ;
29. **le desktop ne doit plus exposer le backend concret** : les surfaces `demo_sql` seront refondues vers des résultats tabulaires génériques (avec préférence pour une nomenclature `demo_store_*`), `demo_config` consommera un résumé store sanitisé sans champs PostgreSQL/SQLite concrets, et le splash vérifiera des modèles logiques avec compteurs d'objets sans afficher les noms physiques des tables/index ; 29. **le desktop ne doit plus exposer le backend concret** : les surfaces portent la nomenclature `demo_store_*` dès `pre.002` et seront refondues fonctionnellement vers des résultats tabulaires génériques, `demo_config` consommera un résumé store sanitisé sans champs PostgreSQL/SQLite concrets, et le splash vérifiera des modèles logiques avec compteurs d'objets sans afficher les noms physiques des tables/index ;
30. **la façade `Store` ouverte au démarrage est destinée à être conservée dans l'état applicatif** plutôt que recréée par chaque commande desktop ; `ks-store` possède ainsi la connexion, l'initialisation et les diagnostics runtime, tandis que l'application ne conserve qu'une capacité backend-agnostique ; 30. **la façade `Store` ouverte au démarrage est destinée à être conservée dans l'état applicatif** plutôt que recréée par chaque commande desktop ; `ks-store` possède ainsi la connexion, l'initialisation et les diagnostics runtime, tandis que l'application ne conserve qu'une capacité backend-agnostique ;
31. **le gel N1-N3 est conditionné par un canari de compatibilité avec les futurs mécanismes de décodage**, Anchor/IDL étant le premier cas concret : l'ajout d'un décodeur futur doit pouvoir se faire sans modification structurelle normale de N1/N2/N3. `0.5.3` n'implémente pas Anchor, mais doit vérifier que les niveaux gelés conservent les bytes d'instruction, comptes ordonnés et leurs attributs, hiérarchie CPI, logs, return data lorsqu'elle existe, temporalité, provenance et observations/états de comptes nécessaires à un redécodage ultérieur ; toute omission générique découverte par cet audit doit être corrigée avant gel ; 31. **le gel N1-N3 est conditionné par un canari de compatibilité avec les futurs mécanismes de décodage**, Anchor/IDL étant le premier cas concret : l'ajout d'un décodeur futur doit pouvoir se faire sans modification structurelle normale de N1/N2/N3. `0.5.3` n'implémente pas Anchor, mais doit vérifier que les niveaux gelés conservent les bytes d'instruction, comptes ordonnés et leurs attributs, hiérarchie CPI, logs, return data lorsqu'elle existe, temporalité, provenance et observations/états de comptes nécessaires à un redécodage ultérieur ; toute omission générique découverte par cet audit doit être corrigée avant gel ;
32. **les 13 tables existantes constituent la baseline auditée, pas un quota de tables finales** : leur séparation top-level/CPI est conservée, mais le nombre final peut augmenter en `0.5.3` si le canari de readiness démontre qu'un fait Solana générique indispensable manque dans N1/N2/N3. Aucune nouvelle table ne doit être ajoutée pour Anchor en tant que technologie ; seules des structures canoniques Solana indépendantes du mécanisme de décodage sont admissibles. 32. **les 13 tables existantes constituent la baseline auditée, pas un quota de tables finales** : leur séparation top-level/CPI est conservée, mais le nombre final peut augmenter en `0.5.3` si le canari de readiness démontre qu'un fait Solana générique indispensable manque dans N1/N2/N3. Aucune nouvelle table ne doit être ajoutée pour Anchor en tant que technologie ; seules des structures canoniques Solana indépendantes du mécanisme de décodage sont admissibles.
@@ -824,31 +824,17 @@ Le contrat visé couvre au minimum :
- compteurs d'objets attendus, présents, créés et vérifiés, notamment tables et index lorsque le backend possède ces notions ; - compteurs d'objets attendus, présents, créés et vérifiés, notamment tables et index lorsque le backend possède ces notions ;
- éventuelles actions de migration/init exécutées, identifiées sans SQL sensible ni paramètres secrets. - éventuelles actions de migration/init exécutées, identifiées sans SQL sensible ni paramètres secrets.
Le résumé public **n'expose pas les noms physiques des tables, index ou contraintes**. Ces détails restent backend-internes et peuvent être tracés dans `ks-store.pg.migration`. Un backend non relationnel peut produire le même résumé logique avec ses propres objets physiques. Le résumé public **n'expose pas les noms physiques des tables, index ou contraintes**. Ces détails restent backend-internes et peuvent être tracés sous le target canonique `ks-store` avec les champs structurés `backend="postgres"`, `domain="ks-store.pg"` et `action="migration"`. Un backend non relationnel peut produire le même résumé logique avec ses propres objets physiques.
Les noms exacts (`StoreRuntimeSummary`, `StoreInitializationSummary`, `StoreModelVerificationSummary`, etc.) seront figés lors de `pre.002`. Le desktop consomme uniquement ces DTO génériques. Les panneaux `demo_sql` seront refondus vers des résultats tabulaires génériques et pourront être renommés `demo_store_*`; `demo_config` ne doit plus lire directement la configuration PostgreSQL/SQLite. Le même résumé logique alimente les informations store du splash. Les noms exacts (`StoreRuntimeSummary`, `StoreInitializationSummary`, `StoreModelVerificationSummary`, etc.) seront figés lors de `pre.002`. Le desktop consomme uniquement ces DTO génériques. Les panneaux portent la nomenclature `demo_store_*` dès `pre.002`; leur présentation tabulaire finale sera refondue en `pre.005`. `demo_config` ne doit plus lire directement la configuration PostgreSQL/SQLite. Le même résumé logique alimente les informations store du splash.
### 18.5 Logging PostgreSQL ### 18.5 Logging PostgreSQL
Le domaine racine retenu est : Le target Rust reste lunique `crate::TRACING_TARGET` de la crate, soit `ks-store`, conformément aux règles du workspace. Le backend PostgreSQL est distingué par des champs structurés stables : `backend = "postgres"`, `domain = "ks-store.pg"` et `action = ...`.
```text
ks-store.pg
```
avec des sous-targets possibles et cohérents :
```text
ks-store.pg.connection
ks-store.pg.migration
ks-store.pg.query
ks-store.pg.health
ks-store.pg.replay
```
Règles : Règles :
- toute exécution SQL PostgreSQL est traçable par un target `ks-store.pg.*` ; - toute exécution SQL PostgreSQL est traçable sous `target: crate::TRACING_TARGET` avec `backend = "postgres"` et `domain = "ks-store.pg"` ;
- `info`/`debug` journalisent l'identité d'opération, le temps d'exécution, le nombre de lignes et l'issue ; - `info`/`debug` journalisent l'identité d'opération, le temps d'exécution, le nombre de lignes et l'issue ;
- le texte SQL brut peut être journalisé à `trace` pour le diagnostic ; - le texte SQL brut peut être journalisé à `trace` pour le diagnostic ;
- les valeurs bindées ne sont pas journalisées par défaut ; - les valeurs bindées ne sont pas journalisées par défaut ;
@@ -1235,7 +1221,7 @@ Le découpage suivant est une **trajectoire fonctionnelle**. Il ne force pas la
- supprimer les quatre traits/DTO historiques non implémentés ; - supprimer les quatre traits/DTO historiques non implémentés ;
- remodeler le bloc store config en backend + options sélectionnées/opaques ; - remodeler le bloc store config en backend + options sélectionnées/opaques ;
- déplacer toute validation/interprétation PostgreSQL dans `ks-store` ; - déplacer toute validation/interprétation PostgreSQL dans `ks-store` ;
- introduire les targets `ks-store.pg.*` et les canaris backend/secret ; - introduire linstrumentation PostgreSQL sous le `TRACING_TARGET` canonique avec `backend="postgres"`, `domain="ks-store.pg"` et les canaris backend/secret ;
- migrer les helpers de connexion sans changer encore les tables ; - migrer les helpers de connexion sans changer encore les tables ;
- préparer une façade `Store` persistante dans létat applicatif afin que les commandes desktop ne recréent pas de connexion backend. - préparer une façade `Store` persistante dans létat applicatif afin que les commandes desktop ne recréent pas de connexion backend.
@@ -1263,12 +1249,12 @@ Le découpage suivant est une **trajectoire fonctionnelle**. Il ne force pas la
- ajouter les index justifiés ; - ajouter les index justifiés ;
- couvrir les bornes BIGINT ; - couvrir les bornes BIGINT ;
- vérifier atomicité et reprise après erreur ; - vérifier atomicité et reprise après erreur ;
- instrumenter les requêtes PostgreSQL sous `ks-store.pg.query`. - instrumenter les requêtes PostgreSQL sous `target: crate::TRACING_TARGET`, `backend="postgres"`, `domain="ks-store.pg"` et `action` explicite.
### `0.5.3-pre.005` — consommateurs, `demo_sql` et validation PostgreSQL réelle ### `0.5.3-pre.005` — finalisation `demo_store`, consommateurs et validation PostgreSQL réelle
- supprimer les derniers `Postgres*` de `ks-pipeline-demo-scenarios` ; - supprimer les derniers `Postgres*` de `ks-pipeline-demo-scenarios` ;
- refaire les surfaces `demo_sql` concernées de `kb-app-demo-desktop` autour de résultats tabulaires génériques indépendants du backend, avec migration de nomenclature vers `demo_store_*` si elle reste cohérente avec la surface UI ; - finaliser les surfaces `demo_store_*` de `kb-app-demo-desktop` autour de résultats tabulaires génériques indépendants du backend ; la nomenclature technique a déjà été migrée en `pre.002` ;
- refaire `demo_config` afin qu'elle n'expose/interprète plus les champs PostgreSQL/SQLite et consomme uniquement le résumé store sanitisé ; - refaire `demo_config` afin qu'elle n'expose/interprète plus les champs PostgreSQL/SQLite et consomme uniquement le résumé store sanitisé ;
- refaire les informations store du splash autour d'une vérification par modèle logique et de compteurs (tables/index attendus, présents, créés), sans afficher les noms physiques ; - refaire les informations store du splash autour d'une vérification par modèle logique et de compteurs (tables/index attendus, présents, créés), sans afficher les noms physiques ;
- confirmer que `ks-pipeline` reste inchangé dans son rôle d'orchestration ; - confirmer que `ks-pipeline` reste inchangé dans son rôle d'orchestration ;
@@ -1346,7 +1332,7 @@ La version ne peut être clôturée que si :
- les index ajoutés correspondent à des requêtes réelles ; - les index ajoutés correspondent à des requêtes réelles ;
- aucune API publique de `ks-store` n'expose PostgreSQL ou SQLx ; - aucune API publique de `ks-store` n'expose PostgreSQL ou SQLx ;
- aucune crate externe n'interprète les paramètres PostgreSQL ; - aucune crate externe n'interprète les paramètres PostgreSQL ;
- PostgreSQL reste entièrement fonctionnel comme backend actif, avec ses SQL ressources sous `migrations/postgres/` et ses logs sous `ks-store.pg.*` ; - PostgreSQL reste entièrement fonctionnel comme backend actif, avec ses SQL ressources sous `migrations/postgres/` et ses logs sous le target canonique `ks-store` avec domaine structuré `ks-store.pg` ;
- aucun secret n'apparaît dans diagnostics/erreurs/résumés runtime/logs ; - aucun secret n'apparaît dans diagnostics/erreurs/résumés runtime/logs ;
- les tables futures sont guidées par des faits canoniques, pas par des Program IDs ; - les tables futures sont guidées par des faits canoniques, pas par des Program IDs ;
- aucune table DEX spéculative n'a été gelée sans matérialiseur réel ; - aucune table DEX spéculative n'a été gelée sans matérialiseur réel ;
@@ -1368,18 +1354,102 @@ Les retours de revue de `pre.001` fixent désormais les points suivants :
8. SQL PostgreSQL sous `ks-store/migrations/postgres/`, une instruction par fichier, fonctions privées `include_str!`, tables/keys/index/drop/truncate séparés et orchestrateur d'init ; 8. SQL PostgreSQL sous `ks-store/migrations/postgres/`, une instruction par fichier, fonctions privées `include_str!`, tables/keys/index/drop/truncate séparés et orchestrateur d'init ;
9. façade publique `Store` backend-agnostique et PostgreSQL entièrement privé ; 9. façade publique `Store` backend-agnostique et PostgreSQL entièrement privé ;
10. configuration backend opaque jusqu'à `ks-store`, sans dépendance `ks-store -> ks-config` ; 10. configuration backend opaque jusqu'à `ks-store`, sans dépendance `ks-store -> ks-config` ;
11. résumé runtime/init sans secrets exposé à `kb-app-demo-desktop` et réconciliation future de `demo_sql` ; 11. résumé runtime/init sans secrets exposé à `kb-app-demo-desktop` et renommage structurel immédiat des surfaces `demo_sql_*` vers `demo_store_*` ;
12. logging PostgreSQL sous `ks-store.pg.*`, sans secrets ni valeurs bindées par défaut ; 12. logging PostgreSQL sous le target canonique `ks-store` avec `backend="postgres"` et `domain="ks-store.pg"`, sans secrets ni valeurs bindées par défaut ;
13. pagination cursorisée/bornée et distinction gel logique/index physiques ; 13. pagination cursorisée/bornée et distinction gel logique/index physiques ;
14. architecture future préservée et précisée : N1 Worker 1/raw, N2 Worker 2/Core, N3 decode/materialization générique avec `k_sol_mat_outputs`, N4 projections spécialisées/queryables ; 14. architecture future préservée et précisée : N1 Worker 1/raw, N2 Worker 2/Core, N3 decode/materialization générique avec `k_sol_mat_outputs`, N4 projections spécialisées/queryables ;
15. les contrats N1/N2/N3 sont fortement gelés après `0.5.3` sauf urgence ou omission structurelle majeure ; les tables/modèles N4 restent plus souples et évolutifs ; 15. les contrats N1/N2/N3 sont fortement gelés après `0.5.3` sauf urgence ou omission structurelle majeure ; les tables/modèles N4 restent plus souples et évolutifs ;
16. replays explicites et indépendants N1 -> N2, N2 -> N3 et N3 -> N4, avec idempotence, version de processor/projector et invalidation descendante ; 16. replays explicites et indépendants N1 -> N2, N2 -> N3 et N3 -> N4, avec idempotence, version de processor/projector et invalidation descendante ;
17. aucune troisième table `outer` : les instructions historiquement dites outer sont les top-level ; les nouveaux contrats utilisent `top-level` et la table CPI conserve `stack_height` ainsi que le parent CPI immédiat reconstructible ; 17. aucune troisième table `outer` : les instructions historiquement dites outer sont les top-level ; les nouveaux contrats utilisent `top-level` et la table CPI conserve `stack_height` ainsi que le parent CPI immédiat reconstructible ;
18. metadata N4 prévue en deux projections : asset/token metadata commune Metaplex + Token-2022 et program metadata distincte pour SPM, toutes deux reconstructibles depuis N3 ; 18. metadata N4 prévue en deux projections : asset/token metadata commune Metaplex + Token-2022 et program metadata distincte pour SPM, toutes deux reconstructibles depuis N3 ;
19. `demo_sql` évolue vers des résultats tabulaires backend-agnostiques, `demo_config` cesse d'exposer PostgreSQL/SQLite, et le splash vérifie des modèles logiques avec compteurs sans noms de tables/index ; 19. `demo_store_*` devient la nomenclature technique dès `pre.002`; sa présentation finale évolue vers des résultats tabulaires backend-agnostiques, `demo_config` cesse d'exposer PostgreSQL/SQLite, et le splash vérifie des modèles logiques avec compteurs sans noms de tables/index ;
20. la façade `Store` est destinée à être ouverte une fois et conservée dans `AppState`, les commandes desktop réutilisant cette capacité générique ; 20. la façade `Store` est destinée à être ouverte une fois et conservée dans `AppState`, les commandes desktop réutilisant cette capacité générique ;
21. `pre.007` est un premier candidat de clôture, jamais une obligation : la clôture est décalée si des manques apparaissent ; 21. `pre.007` est un premier candidat de clôture, jamais une obligation : la clôture est décalée si des manques apparaissent ;
22. Anchor reste hors périmètre fonctionnel de `0.5.3`, mais devient un canari explicite du gel : N1/N2/N3 doivent conserver sans perte les informations génériques nécessaires aux futurs decoders (instructions/CPI, comptes ordonnés et flags, logs, `returnData` réelle, observations/états de comptes, temporalité/provenance) et N3 doit pouvoir tracer le decoder + schéma/IDL/version/hash utilisés ; 22. Anchor reste hors périmètre fonctionnel de `0.5.3`, mais devient un canari explicite du gel : N1/N2/N3 doivent conserver sans perte les informations génériques nécessaires aux futurs decoders (instructions/CPI, comptes ordonnés et flags, logs, `returnData` réelle, observations/états de comptes, temporalité/provenance) et N3 doit pouvoir tracer le decoder + schéma/IDL/version/hash utilisés ;
23. les 13 tables sont la baseline existante, pas un quota final : si ce canari révèle une omission Solana générique, `0.5.3` peut ajouter avant gel un contrat N1/N2/N3 canonique non nommé d'après Anchor. 23. les 13 tables sont la baseline existante, pas un quota final : si ce canari révèle une omission Solana générique, `0.5.3` peut ajouter avant gel un contrat N1/N2/N3 canonique non nommé d'après Anchor.
Ces décisions remplacent les propositions initiales de fusion des instructions et de conservation immuable des migrations `0001` à `0004`. `pre.002` peut démarrer sur cette base ; aucune migration SQL massive n'est encore incluse dans `pre.001`. Ces décisions remplacent les propositions initiales de fusion des instructions et de conservation immuable des migrations `0001` à `0004`. `pre.002` peut démarrer sur cette base ; aucune migration SQL massive n'est encore incluse dans `pre.001`.
## 32. État réalisé de `0.5.3-pre.002`
La tranche `0.5.3-pre.002` réalise la première rupture effective de dépendance au backend concret, sans modifier le schéma SQL historique qui reste volontairement réservé à `pre.003`.
### 32.1 Façade et possession de la connexion
- `ks-store` expose désormais `Store` et `StoreOpenOptions` comme frontière publique douverture et dutilisation du stockage ;
- `PostgresStore`, `PostgresStoreOptions`, le pool SQLx et les helpers PostgreSQL restent internes à `ks-store` ;
- `Store` implémente les contrats fonctionnels déjà consommés par les pipelines et délègue au backend actif sans faire remonter le dispatch dans les crates consommatrices ;
- le desktop conserve un `Store` ouvert une seule fois dans `AppState` via `tokio::sync::OnceCell`, puis les commandes réutilisent cette capacité ;
- `ks-pipeline-demo-scenarios` utilise également la façade générique et ne construit plus le backend PostgreSQL.
### 32.2 Configuration backend opaque
Le contrat runtime de `ks-config` conserve désormais uniquement :
```text
enabled
backend
backend_options
```
`backend_options` reste un objet JSON opaque pour `ks-config`. La sélection, la validation des paramètres PostgreSQL, la création de la connexion et lauto-initialisation sont interprétées par `ks-store`. `ks-store` ne dépend pas de `ks-config`.
PostgreSQL est le seul backend opérationnel de cette tranche. Un backend inconnu ou incomplet est refusé par une erreur structurée qui ne recopie pas ses options sensibles.
### 32.3 Diagnostics et résumé runtime génériques
La surface publique fournit des DTO backend-agnostiques pour :
- configuration sanitisée ;
- backend descriptor masqué ;
- health ;
- état de migration ;
- ressources logiques et statistiques ;
- vérification par modèle logique ;
- résumé dinitialisation ;
- résumé runtime complet.
Le résumé dinitialisation regroupe actuellement les ressources du schéma historique sous les modèles logiques `raw`, `core`, `processing`, `decode` et `materialization`. Il expose des compteurs de ressources attendues/disponibles/créées sans exposer les noms physiques. Une liste générique de catégories dobjets backend expose en plus les compteurs physiques sans noms : PostgreSQL fournit déjà la catégorie `table`; `pre.003` ajoutera la catégorie `index` lors de la reconstruction/introspection du nouveau baseline. Un futur backend pourra fournir dautres catégories sans modifier le contrat du desktop.
La constante `STORE_SCHEMA_CONTRACT_VERSION` identifie explicitement cette étape comme un schéma legacy transitoire ; elle ne constitue pas le snapshot frozen final.
### 32.4 Replay et API publique
Les DTO publics de diagnostic et de replay ne portent plus le préfixe `Postgres`. Le vocabulaire nouveau utilise `top-level` au lieu de `outer`. Les quatre anciens traits publics sans implémentation PostgreSQL réelle et leurs DTO historiques sont supprimés au lieu dêtre figés.
La structure SQL et les requêtes restent encore basées sur les tables `kb_sol_*` de `0.5.2` : leur reconstruction sous `k_sol_*`, lalignement top-level/CPI et le nouveau baseline appartiennent toujours à `pre.003`.
### 32.5 Desktop
Les commandes et payloads nécessaires au desktop ne construisent plus un backend concret. `demo_config` reçoit un résumé store sanitisé ; les diagnostics SQL utilisent désormais des ressources logiques génériques et un descriptor de connexion masqué. Le splash utilise le résumé dinitialisation par modèle et des compteurs, sans afficher de noms physiques de tables/index.
Les noms historiques de fenêtres/commandes `demo_sql_*` sont supprimés dès `pre.002` et remplacés par `demo_store_*`. `pre.005` conserve uniquement la refonte fonctionnelle finale de lexpérience tabulaire, de `demo_config` et du splash autour du nouveau baseline.
### 32.6 Logging et sécurité
Le backend PostgreSQL utilise lunique target canonique de la crate :
```text
target: crate::TRACING_TARGET // "ks-store"
backend = "postgres"
domain = "ks-store.pg"
action = "connection | migration | query | health | replay | ..."
```
Un canari workspace interdit désormais les types/symboles PostgreSQL/SQLx dans les crates consommatrices et vérifie que limplémentation PostgreSQL utilise `target: crate::TRACING_TARGET` avec les champs structurés `backend="postgres"` et `domain="ks-store.pg"`. Des tests sentinelles vérifient également quun backend inconnu, un descriptor masqué et les résumés publics ne recopient pas des options contenant un secret.
Le nettoyage fin de linstrumentation des requêtes (durées, nombres de lignes et suppression systématique de toute valeur bindée des traces normales) reste couplé à la réécriture des repositories de `pre.004`, conformément au plan.
### 32.7 Limites volontaires de la tranche
`pre.002` ne :
- renomme aucune table `kb_sol_*` ;
- ne crée aucun contrat N1/N2/N3 supplémentaire ;
- ne restructure pas encore `migrations/postgres/` ;
- ne change pas `block_time`, `stack_height`, la hiérarchie CPI ou `k_sol_mat_outputs` ;
- ne crée aucune projection N4 metadata/trading ;
- nimplémente pas Anchor.
Ces travaux restent affectés aux tranches `pre.003+` prévues par le présent plan.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_RUST.md --> <!-- file: docs/rules/RULES_RUST.md -->
<!-- version: 8 --> <!-- version: 9 -->
# Règles Rust générales # Règles Rust générales
@@ -101,6 +101,7 @@ implicit_saturating_sub = "allow"
## Visibilité et API de crate ## Visibilité et API de crate
- Aucun `pub mod` n'est autorisé. Les modules restent privés et l'API est constituée exclusivement par des réexports explicites depuis le point dentrée de la crate. - Aucun `pub mod` n'est autorisé. Les modules restent privés et l'API est constituée exclusivement par des réexports explicites depuis le point dentrée de la crate.
- Aucun `pub(in ...)` ni `pub(super)` n'est autorisé : un élément partagé au-delà de son module est `pub(crate)`, réexporté au crate-root, puis consommé via `crate::Item`; sinon il reste strictement privé.
- Un élément `pub` inaccessible depuis le point d'entrée de sa crate est une erreur de conception, pas un simple avertissement. - Un élément `pub` inaccessible depuis le point d'entrée de sa crate est une erreur de conception, pas un simple avertissement.
- Un élément `pub(crate)` utilisé hors de son module est réexporté au niveau du point d'entrée de la crate. - Un élément `pub(crate)` utilisé hors de son module est réexporté au niveau du point d'entrée de la crate.
- Les chemins internes de modules ne font pas partie de l'API stable. - Les chemins internes de modules ne font pas partie de l'API stable.

View File

@@ -1,8 +1,33 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md --> <!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 60 --> <!-- version: 64 -->
# CHANGELOG — kb-app-demo-desktop # CHANGELOG — kb-app-demo-desktop
## `0.5.3-pre.002-delta-fix-004`
- aligne les versions Tauri/Vite de l'application sur `0.5.3` dans `package.json` et `tauri.conf.json` ;
- remplace le groupe visible `SQL` par `Store` dans le menu principal et renomme les entrées backend-spécifiques en `Raw store` / `Core store` ;
- retire les titres de fenêtres `PostgreSQL ...` des surfaces `demo_store_*` et renomme le titre de la page de replay pour rester backend-agnostique ;
- réconcilie les rustdocs, les bindings TS-RS et la documentation d'usage des surfaces desktop afin qu'ils décrivent la façade `Store` et non PostgreSQL/SQL ;
- aligne les messages de préparation Devnet et le texte d'extraction Core sur les ressources/store génériques au lieu de tables ou transactions PostgreSQL.
## `0.5.3-pre.002-delta-fix-003`
- corrige `demo_core_extraction_execute`, qui reçoit déjà `&AppState` et ne doit donc pas appeler `state.inner()` avant de réutiliser le `Store` partagé ;
- rend `store_open_options_from_profile` strictement privé à `demo_store_common` et supprime son réexport crate-root devenu inutilisé.
## `0.5.3-pre.002-delta-fix-002`
- renomme les modules, commandes Tauri, fenêtres, pages HTML, TypeScript et DTO de `demo_sql_*` vers `demo_store_*` afin que la nomenclature ne dépende plus dun backend SQL ;
- conserve la refonte fonctionnelle finale des tableaux/compteurs pour `pre.005`.
## `0.5.3-pre.002`
- conserve un `ks_store::Store` backend-agnostique dans `AppState` via `OnceCell` et réutilise ce handle dans les commandes de backfill, extraction Core, replay et scénarios d'exécution ;
- remplace les payloads SQL/configuration dépendants de PostgreSQL par des descripteurs de connexion sanitisés et des diagnostics de ressources/modèles génériques ;
- adapte le splash à la vérification du store par modèle logique et compteurs de ressources, sans afficher les noms physiques des tables/index ;
- remplace le vocabulaire replay `outer` par `top-level` dans les nouveaux contrats UI, tout en conservant temporairement les noms techniques des fenêtres `demo_store_*` jusqu'à leur refonte finale.
## `0.5.2-pre.006-delta-fix-009` ## `0.5.2-pre.006-delta-fix-009`
- corrige les adaptateurs Tauri Devnet qui transmettaient directement `tauri::State<AppState>` aux helpers wallet/projection attendant `&AppState`; les commandes empruntent désormais explicitement `&state` pour System, SPL et les trois familles Metadata ; - corrige les adaptateurs Tauri Devnet qui transmettaient directement `tauri::State<AppState>` aux helpers wallet/projection attendant `&AppState`; les commandes empruntent désormais explicitement `&state` pour System, SPL et les trois familles Metadata ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/README.md --> <!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 21 --> <!-- version: 23 -->
# kb-app-demo-desktop # kb-app-demo-desktop
@@ -10,7 +10,7 @@
- configuration et diagnostics de logging ; - configuration et diagnostics de logging ;
- fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création dun conteneur persistant via un secret backend-only, sélection session-only du wallet d'exécution Devnet, inspection/import/export des formats keypair supportés sous `data/wallets/`, extraction sûre de pubkey depuis un keypair legacy, inspection explicite dun `.kswallet` externe et exploration publique du solde SOL, des comptes SPL Token / Token-2022 et des transactions récentes sur un profil RPC explicitement sélectionnable ; - fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création dun conteneur persistant via un secret backend-only, sélection session-only du wallet d'exécution Devnet, inspection/import/export des formats keypair supportés sous `data/wallets/`, extraction sûre de pubkey depuis un keypair legacy, inspection explicite dun `.kswallet` externe et exploration publique du solde SOL, des comptes SPL Token / Token-2022 et des transactions récentes sur un profil RPC explicitement sélectionnable ;
- transport HTTP JSON-RPC et WebSocket ; - transport HTTP JSON-RPC et WebSocket ;
- diagnostics PostgreSQL ; - diagnostics de stockage backend-agnostiques ;
- backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ; - backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ;
- démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ; - démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ;
- panneau Metadata avec parcours séparés Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata, préparation du profil et de la base, simulation RPC réelle, soumission explicitement confirmée et affichage des postconditions ; - panneau Metadata avec parcours séparés Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata, préparation du profil et de la base, simulation RPC réelle, soumission explicitement confirmée et affichage des postconditions ;
@@ -20,6 +20,8 @@
Les commandes Tauri sont des adaptateurs minces. La logique réutilisable appartient à `ks-pipeline`, `ks-pipeline-demo-scenarios`, `ks-lib`, `ks-store` et `ks-onchain-transport`. Les commandes Tauri sont des adaptateurs minces. La logique réutilisable appartient à `ks-pipeline`, `ks-pipeline-demo-scenarios`, `ks-lib`, `ks-store` et `ks-onchain-transport`.
Depuis `0.5.3-pre.002`, l'application conserve une façade `ks_store::Store` unique dans `AppState`. Les commandes ne construisent plus de `PostgresStore` et les payloads de configuration/diagnostic ne transportent plus de champs PostgreSQL/SQLite concrets. Les anciennes surfaces `demo_sql_*` sont renommées en `demo_store_*` dès `pre.002`; la refonte fonctionnelle finale de leurs tableaux et compteurs reste planifiée en `pre.005`.
Les campagnes Devnet/Testnet réutilisables appartiennent à `ks-pipeline-demo-scenarios`. Le desktop conserve leurs adaptateurs UI/Tauri, la sélection opérateur, la progression, les payloads TS-RS et la présentation ; `0.5.4` doit retirer les orchestrations réutilisables qui subsistent encore localement. Les campagnes Devnet/Testnet réutilisables appartiennent à `ks-pipeline-demo-scenarios`. Le desktop conserve leurs adaptateurs UI/Tauri, la sélection opérateur, la progression, les payloads TS-RS et la présentation ; `0.5.4` doit retirer les orchestrations réutilisables qui subsistent encore localement.
Depuis `0.5.1-pre.008`, TS-RS appartient à cette frontière applicative : `ks-config` et `ks-lib` ne génèrent plus de bindings TypeScript. Lorsqu'un type généraliste doit être présenté au frontend, le desktop construit un DTO/wrapper dédié. Depuis `0.5.1-pre.008`, TS-RS appartient à cette frontière applicative : `ks-config` et `ks-lib` ne génèrent plus de bindings TypeScript. Lorsqu'un type généraliste doit être présenté au frontend, le desktop construit un DTO/wrapper dédié.

View File

@@ -1,10 +1,11 @@
<!-- file: kb-app-demo-desktop/TODO.md --> <!-- file: kb-app-demo-desktop/TODO.md -->
<!-- version: 20 --> <!-- version: 22 -->
# TODO — kb-app-demo-desktop # TODO — kb-app-demo-desktop
## Évolutions générales ## Évolutions générales
- [ ] `0.5.3` — finaliser en `pre.005` la présentation des surfaces `demo_store_*`, puis aligner `demo_config` et le splash sur les modèles/compteurs du nouveau baseline sans exposer de noms physiques ; le renommage technique est terminé en `pre.002`.
- [ ] `0.5.4` — réduire les panneaux d'exécution à l'adaptation UI/Tauri en déplaçant toute orchestration de campagne encore réutilisable vers `ks-pipeline-demo-scenarios`. - [ ] `0.5.4` — réduire les panneaux d'exécution à l'adaptation UI/Tauri en déplaçant toute orchestration de campagne encore réutilisable vers `ks-pipeline-demo-scenarios`.
- [ ] UX — empêcher les doubles déclenchements pendant une préparation, simulation ou soumission active. - [ ] UX — empêcher les doubles déclenchements pendant une préparation, simulation ou soumission active.
- [ ] Observabilité — afficher explicitement les comptes relus et les projections matérialisées. - [ ] Observabilité — afficher explicitement les comptes relus et les projections matérialisées.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/USAGE.md --> <!-- file: kb-app-demo-desktop/USAGE.md -->
<!-- version: 21 --> <!-- version: 24 -->
# Utilisation de kb-app-demo-desktop # Utilisation de kb-app-demo-desktop
@@ -116,7 +116,7 @@ Les autres fenêtres sont ouvertes par les commandes Tauri dédiées :
- backfill ; - backfill ;
- HTTP ; - HTTP ;
- WebSocket ; - WebSocket ;
- diagnostics SQL ; - diagnostics store ;
- candidats replay ; - candidats replay ;
- configuration ; - configuration ;
- wallets ; - wallets ;
@@ -311,22 +311,24 @@ Linventaire contient cinq variantes `Create -> Mint`, puis collection `Verify
Dans la colonne de résultats, `Préflight et opérations`, `Simulation et confirmation` et `Postconditions et preuves` sont présentés dans un accordéon commun. La carte `Exigences` reste sous cet accordéon et le journal dexécution reste sous la grille, sur toute la largeur. Dans la colonne de résultats, `Préflight et opérations`, `Simulation et confirmation` et `Postconditions et preuves` sont présentés dans un accordéon commun. La carte `Exigences` reste sous cet accordéon et le journal dexécution reste sous la grille, sur toute la largeur.
### Diagnostics PostgreSQL ### Diagnostics du store
```typescript ```typescript
const diagnostics = await invoke( const diagnostics = await invoke(
"load_demo_sql_diag" "load_demo_store_diag"
); );
const rawTables = await invoke( const rawTables = await invoke(
"load_demo_sql_pg_raw" "load_demo_store_raw"
); );
const coreTables = await invoke( const coreTables = await invoke(
"load_demo_sql_pg_core" "load_demo_store_core"
); );
console.log(diagnostics, rawTables, coreTables); console.log(diagnostics, rawTables, coreTables);
``` ```
Les commandes/fenêtres historiques `demo_sql_*` sont renommées en `demo_store_*` dès `pre.002`, et leurs payloads sont backend-agnostiques : `connectionDescriptor`, `resources`, `backend`, `namespace` et compteurs logiques. Le frontend n'accède plus à un DSN PostgreSQL ni à des noms physiques de tables. La tranche consommateurs de `0.5.3` doit finaliser la présentation tabulaire de `demo_store_*`, aligner `demo_config` et exploiter les compteurs tables/index du nouveau baseline dans le splash.
## Capabilities Tauri ## Capabilities Tauri
Les fenêtres autorisées sont listées dans `capabilities/default.json`. La capability active doit inclure au minimum les permissions Tauri Core et tracing nécessaires aux fenêtres déclarées. Les fenêtres autorisées sont listées dans `capabilities/default.json`. La capability active doit inclure au minimum les permissions Tauri Core et tracing nécessaires aux fenêtres déclarées.
@@ -350,7 +352,7 @@ Toute nouvelle fenêtre ou commande sensible doit être auditée avant ajout de
- tests de System Transfer et Memo ; - tests de System Transfer et Memo ;
- tests ATA, SPL Token et Token-2022 ; - tests ATA, SPL Token et Token-2022 ;
- tests de backfill, extraction Core et decode replay ; - tests de backfill, extraction Core et decode replay ;
- tests de diagnostics SQL et exports CSV ; - tests de diagnostics store et exports CSV ;
- tests de journalisation frontend ; - tests de journalisation frontend ;
- test du verrou détat applicatif et de la séquence splash. - test du verrou détat applicatif et de la séquence splash.
@@ -368,7 +370,7 @@ Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metada
- de choisir un scénario synthétique ou un contrat de campagne Devnet ; - de choisir un scénario synthétique ou un contrat de campagne Devnet ;
- de sélectionner le profil Devnet ; - de sélectionner le profil Devnet ;
- de préparer et vérifier la base PostgreSQL du profil ; - de préparer et vérifier le store persistant du profil ;
- dafficher les contrats, contrôles de préflight et preuves attendues dans le JSON viewer commun ; - dafficher les contrats, contrôles de préflight et preuves attendues dans le JSON viewer commun ;
- de restaurer le scénario sélectionné lors de la réouverture. - de restaurer le scénario sélectionné lors de la réouverture.
@@ -378,7 +380,7 @@ Les scénarios synthétiques restent disponibles pour inspecter les contrats san
### Campagnes Metaplex qualifiées ### Campagnes Metaplex qualifiées
Ouvrir **Metaplex Token Metadata -> Campagnes Devnet qualifiées**, sélectionner un profil persistant et préparer PostgreSQL. Le sélecteur présente 11 campagnes : cinq familles `Create -> Mint`, collection, Print/Burn, lifecycle pNFT, escrow, maintenance et Use. Après confirmation opérateur, le desktop appelle le runner correspondant et affiche : Ouvrir **Metaplex Token Metadata -> Campagnes Devnet qualifiées**, sélectionner un profil persistant et préparer le store du profil. Le sélecteur présente 11 campagnes : cinq familles `Create -> Mint`, collection, Print/Burn, lifecycle pNFT, escrow, maintenance et Use. Après confirmation opérateur, le desktop appelle le runner correspondant et affiche :
- la fixture et son setup ; - la fixture et son setup ;
- les exécutions confirmées et les probes indisponibles dans leur ordre réel ; - les exécutions confirmées et les probes indisponibles dans leur ordre réel ;
@@ -389,7 +391,7 @@ La surface générique par opération reste une API avancée et ne doit pas êtr
### Campagne Token-2022 Token Metadata ### Campagne Token-2022 Token Metadata
Ouvrir laccordéon **Token-2022 Token Metadata**, sélectionner un profil Devnet persistant et préparer sa base PostgreSQL. Le panneau affiche le contrat fixe de cinq opérations : Ouvrir laccordéon **Token-2022 Token Metadata**, sélectionner un profil Devnet persistant et préparer son store persistant. Le panneau affiche le contrat fixe de cinq opérations :
```text ```text
Initialize -> UpdateField -> Emit -> RemoveKey -> UpdateAuthority Initialize -> UpdateField -> Emit -> RemoveKey -> UpdateAuthority

View File

@@ -12,10 +12,10 @@
"demo_backfill", "demo_backfill",
"demo_core_extraction", "demo_core_extraction",
"demo_decode_replay", "demo_decode_replay",
"demo_sql_diag", "demo_store_diag",
"demo_sql_pg_raw", "demo_store_raw",
"demo_sql_pg_core", "demo_store_core",
"demo_sql_replay_candidates", "demo_store_replay_candidates",
"demo_execution_solana_core", "demo_execution_solana_core",
"demo_execution_spl", "demo_execution_spl",
"demo_execution_metadata" "demo_execution_metadata"

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_core_extraction.html --> <!-- file: kb-app-demo-desktop/frontend/demo_core_extraction.html -->
<!-- version: 10 --> <!-- version: 11 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -39,7 +39,7 @@
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h1 class="h3 card-title">Extraction transactionnelle canonical vers core</h1> <h1 class="h3 card-title">Extraction transactionnelle canonical vers core</h1>
<p class="text-body-secondary mb-0">Chaque transaction canonique sélectionnée est validée, normalisée dans les tables core et enregistrée dans le ledger de traitement au sein d'une seule transaction PostgreSQL.</p> <p class="text-body-secondary mb-0">Chaque transaction canonique sélectionnée est validée, normalisée dans les tables core et enregistrée dans le ledger de traitement au sein d'une seule transaction de stockage atomique.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_decode_replay.html --> <!-- file: kb-app-demo-desktop/frontend/demo_decode_replay.html -->
<!-- version: 8 --> <!-- version: 9 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -61,7 +61,7 @@
</div> </div>
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<label class="form-label" for="decodeReplaySignaturesTextarea">Signatures explicites, une par ligne</label> <label class="form-label" for="decodeReplaySignaturesTextarea">Signatures explicites, une par ligne</label>
<textarea id="decodeReplaySignaturesTextarea" class="form-control font-monospace" rows="7" placeholder="Copier depuis demo_sql_replay_candidates"></textarea> <textarea id="decodeReplaySignaturesTextarea" class="form-control font-monospace" rows="7" placeholder="Copier depuis demo_store_replay_candidates"></textarea>
</div> </div>
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<label class="form-label" for="decodeReplayPathsTextarea">Instruction paths exacts, un par ligne</label> <label class="form-label" for="decodeReplayPathsTextarea">Instruction paths exacts, un par ligne</label>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_sql_pg_core.html --> <!-- file: kb-app-demo-desktop/frontend/demo_store_core.html -->
<!-- version: 6 --> <!-- version: 8 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -8,7 +8,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="icon" href="imgs/logo.png"> <link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss"> <link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — PostgreSQL core store</title> <title>Khadhroony Bot3 — Core store diagnostics</title>
</head> </head>
<body> <body>
@@ -17,7 +17,7 @@
<div class="container-fluid px-4 my-0"> <div class="container-fluid px-4 my-0">
<div class="navbar-brand d-flex align-items-center"> <div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo"> <img alt="Logo" src="imgs/logo.png" class="app-logo">
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">PostgreSQL core store</span></span> <span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Core store diagnostics</span></span>
</div> </div>
</div> </div>
</nav> </nav>
@@ -36,11 +36,11 @@
</div> </div>
<dl class="row mb-0"> <dl class="row mb-0">
<dt class="col-sm-3">Statut</dt> <dt class="col-sm-3">Statut</dt>
<dd class="col-sm-9"><code id="sqlCoreStatus">Chargement...</code></dd> <dd class="col-sm-9"><code id="storeCoreStatus">Chargement...</code></dd>
<dt class="col-sm-3">Profil</dt> <dt class="col-sm-3">Profil</dt>
<dd class="col-sm-9"><code id="sqlCoreProfile"></code></dd> <dd class="col-sm-9"><code id="storeCoreProfile"></code></dd>
<dt class="col-sm-3">DSN</dt> <dt class="col-sm-3">Connexion</dt>
<dd class="col-sm-9"><code id="sqlCoreDsn"></code></dd> <dd class="col-sm-9"><code id="storeCoreConnection"></code></dd>
</dl> </dl>
</div> </div>
</div> </div>
@@ -49,13 +49,13 @@
<div class="col-12"> <div class="col-12">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Tables core</h2> <h2 class="h5 card-title mb-3">Ressources Core</h2>
<div class="table-responsive app-table-full-width"> <div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0" id="sqlCoreTable"> <table class="table table-sm align-middle mb-0" id="storeCoreTable">
<thead> <thead>
<tr> <tr>
<th>Table</th> <th>Ressource</th>
<th>Domaine</th> <th>Modèle</th>
<th>Rôle</th> <th>Rôle</th>
<th>Existe</th> <th>Existe</th>
<th>Lignes</th> <th>Lignes</th>
@@ -64,7 +64,7 @@
<th>Dernière insertion</th> <th>Dernière insertion</th>
</tr> </tr>
</thead> </thead>
<tbody id="sqlCoreTableBody"></tbody> <tbody id="storeCoreTableBody"></tbody>
</table> </table>
</div> </div>
</div> </div>
@@ -75,7 +75,7 @@
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2> <h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlCoreJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div> <div id="storeCoreJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données du store."}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -91,7 +91,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script type="module" src="ts/demo_sql_pg_core.ts" defer></script> <script type="module" src="ts/demo_store_core.ts" defer></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_sql_diag.html --> <!-- file: kb-app-demo-desktop/frontend/demo_store_diag.html -->
<!-- version: 5 --> <!-- version: 7 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -8,7 +8,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="icon" href="imgs/logo.png"> <link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss"> <link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — SQL diagnostics</title> <title>Khadhroony Bot3 — Store diagnostics</title>
</head> </head>
<body> <body>
@@ -17,7 +17,7 @@
<div class="container-fluid px-4 my-0"> <div class="container-fluid px-4 my-0">
<div class="navbar-brand d-flex align-items-center"> <div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo"> <img alt="Logo" src="imgs/logo.png" class="app-logo">
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">SQL diagnostics</span></span> <span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Store diagnostics</span></span>
</div> </div>
</div> </div>
</nav> </nav>
@@ -31,7 +31,7 @@
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-3"> <div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-3">
<h1 class="h3 card-title mb-0">Diagnostic PostgreSQL</h1> <h1 class="h3 card-title mb-0">Diagnostic du store</h1>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button id="refreshSqlDiagButton" class="btn btn-primary" type="button">Rafraîchir</button> <button id="refreshSqlDiagButton" class="btn btn-primary" type="button">Rafraîchir</button>
<button id="copySqlDiagButton" class="btn btn-outline-secondary" type="button">Copier JSON</button> <button id="copySqlDiagButton" class="btn btn-outline-secondary" type="button">Copier JSON</button>
@@ -39,21 +39,21 @@
</div> </div>
<dl class="row mb-0"> <dl class="row mb-0">
<dt class="col-sm-3">Statut</dt> <dt class="col-sm-3">Statut</dt>
<dd class="col-sm-9"><code id="sqlDiagStatus">Chargement...</code></dd> <dd class="col-sm-9"><code id="storeDiagStatus">Chargement...</code></dd>
<dt class="col-sm-3">Fichier config</dt> <dt class="col-sm-3">Fichier config</dt>
<dd class="col-sm-9"><code id="sqlDiagConfigPath"></code></dd> <dd class="col-sm-9"><code id="storeDiagConfigPath"></code></dd>
<dt class="col-sm-3">Profil</dt> <dt class="col-sm-3">Profil</dt>
<dd class="col-sm-9"><code id="sqlDiagProfile"></code></dd> <dd class="col-sm-9"><code id="storeDiagProfile"></code></dd>
<dt class="col-sm-3">Backend</dt> <dt class="col-sm-3">Backend</dt>
<dd class="col-sm-9"><code id="sqlDiagBackend"></code></dd> <dd class="col-sm-9"><code id="storeDiagBackend"></code></dd>
<dt class="col-sm-3">DSN</dt> <dt class="col-sm-3">Connexion</dt>
<dd class="col-sm-9"><code id="sqlDiagDsn"></code></dd> <dd class="col-sm-9"><code id="storeDiagConnection"></code></dd>
<dt class="col-sm-3">Schema courant</dt> <dt class="col-sm-3">Schema courant</dt>
<dd class="col-sm-9"><code id="sqlDiagSchema"></code></dd> <dd class="col-sm-9"><code id="storeDiagSchema"></code></dd>
<dt class="col-sm-3">Healthcheck</dt> <dt class="col-sm-3">Healthcheck</dt>
<dd class="col-sm-9"><code id="sqlDiagHealth"></code></dd> <dd class="col-sm-9"><code id="storeDiagHealth"></code></dd>
<dt class="col-sm-3">Migrations</dt> <dt class="col-sm-3">Migrations</dt>
<dd class="col-sm-9"><code id="sqlDiagMigration"></code></dd> <dd class="col-sm-9"><code id="storeDiagMigration"></code></dd>
</dl> </dl>
</div> </div>
</div> </div>
@@ -62,13 +62,13 @@
<div class="col-12"> <div class="col-12">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Tables raw/core connues</h2> <h2 class="h5 card-title mb-3">Ressources store connues</h2>
<div class="table-responsive app-table-full-width"> <div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0" id="sqlDiagTable"> <table class="table table-sm align-middle mb-0" id="storeDiagTable">
<thead> <thead>
<tr> <tr>
<th>Table</th> <th>Ressource</th>
<th>Domaine</th> <th>Modèle</th>
<th>Rôle</th> <th>Rôle</th>
<th>Existe</th> <th>Existe</th>
<th>Lignes</th> <th>Lignes</th>
@@ -77,7 +77,7 @@
<th>Dernière insertion</th> <th>Dernière insertion</th>
</tr> </tr>
</thead> </thead>
<tbody id="sqlDiagTableBody"></tbody> <tbody id="storeDiagTableBody"></tbody>
</table> </table>
</div> </div>
</div> </div>
@@ -88,7 +88,7 @@
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2> <h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlDiagJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div> <div id="storeDiagJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données du store."}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -104,7 +104,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script type="module" src="ts/demo_sql_diag.ts" defer></script> <script type="module" src="ts/demo_store_diag.ts" defer></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_sql_pg_raw.html --> <!-- file: kb-app-demo-desktop/frontend/demo_store_raw.html -->
<!-- version: 7 --> <!-- version: 9 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -8,7 +8,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="icon" href="imgs/logo.png"> <link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss"> <link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — PostgreSQL canonical acquisition</title> <title>Khadhroony Bot3 — Raw store diagnostics</title>
</head> </head>
<body> <body>
@@ -17,7 +17,7 @@
<div class="container-fluid px-4 my-0"> <div class="container-fluid px-4 my-0">
<div class="navbar-brand d-flex align-items-center"> <div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo"> <img alt="Logo" src="imgs/logo.png" class="app-logo">
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">PostgreSQL canonical acquisition</span></span> <span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Raw store diagnostics</span></span>
</div> </div>
</div> </div>
</nav> </nav>
@@ -36,11 +36,11 @@
</div> </div>
<dl class="row mb-0"> <dl class="row mb-0">
<dt class="col-sm-3">Statut</dt> <dt class="col-sm-3">Statut</dt>
<dd class="col-sm-9"><code id="sqlRawStatus">Chargement...</code></dd> <dd class="col-sm-9"><code id="storeRawStatus">Chargement...</code></dd>
<dt class="col-sm-3">Profil</dt> <dt class="col-sm-3">Profil</dt>
<dd class="col-sm-9"><code id="sqlRawProfile"></code></dd> <dd class="col-sm-9"><code id="storeRawProfile"></code></dd>
<dt class="col-sm-3">DSN</dt> <dt class="col-sm-3">Connexion</dt>
<dd class="col-sm-9"><code id="sqlRawDsn"></code></dd> <dd class="col-sm-9"><code id="storeRawConnection"></code></dd>
</dl> </dl>
</div> </div>
</div> </div>
@@ -51,11 +51,11 @@
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Transactions canoniques et observations</h2> <h2 class="h5 card-title mb-3">Transactions canoniques et observations</h2>
<div class="table-responsive app-table-full-width"> <div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0" id="sqlRawTable"> <table class="table table-sm align-middle mb-0" id="storeRawTable">
<thead> <thead>
<tr> <tr>
<th>Table</th> <th>Ressource</th>
<th>Domaine</th> <th>Modèle</th>
<th>Rôle</th> <th>Rôle</th>
<th>Existe</th> <th>Existe</th>
<th>Lignes</th> <th>Lignes</th>
@@ -64,7 +64,7 @@
<th>Dernière insertion</th> <th>Dernière insertion</th>
</tr> </tr>
</thead> </thead>
<tbody id="sqlRawTableBody"></tbody> <tbody id="storeRawTableBody"></tbody>
</table> </table>
</div> </div>
</div> </div>
@@ -75,7 +75,7 @@
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-3">Payload JSON</h2> <h2 class="h5 card-title mb-3">Payload JSON</h2>
<div id="sqlRawJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données SQL."}</div> <div id="storeRawJson" class="app-config-box app-json-viewer mb-0">{"status":"loading","message":"Chargement des données du store."}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -91,7 +91,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script type="module" src="ts/demo_sql_pg_raw.ts" defer></script> <script type="module" src="ts/demo_store_raw.ts" defer></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_sql_replay_candidates.html --> <!-- file: kb-app-demo-desktop/frontend/demo_store_replay_candidates.html -->
<!-- version: 11 --> <!-- version: 15 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -8,7 +8,7 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="icon" href="imgs/logo.png"> <link rel="icon" href="imgs/logo.png">
<link rel="stylesheet" href="sass/main.scss"> <link rel="stylesheet" href="sass/main.scss">
<title>Khadhroony Bot3 — Candidats de replay SQL</title> <title>Khadhroony Bot3 — Candidats de replay du store</title>
</head> </head>
<body> <body>
@@ -17,7 +17,7 @@
<div class="container-fluid px-4 my-0"> <div class="container-fluid px-4 my-0">
<div class="navbar-brand d-flex align-items-center"> <div class="navbar-brand d-flex align-items-center">
<img alt="Logo" src="imgs/logo.png" class="app-logo"> <img alt="Logo" src="imgs/logo.png" class="app-logo">
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">SQL replay candidates</span></span> <span class="ps-2 fs-4 fw-bold text-primary font-logo">Bot3 — <span class="badge text-bg-primary">Store replay candidates</span></span>
</div> </div>
<div class="ms-auto d-flex align-items-center gap-2"> <div class="ms-auto d-flex align-items-center gap-2">
<span id="replayCandidateProfileBadge" class="badge text-bg-info">Profil —</span> <span id="replayCandidateProfileBadge" class="badge text-bg-info">Profil —</span>
@@ -36,9 +36,9 @@
<div class="card shadow-sm border-0 mb-4"> <div class="card shadow-sm border-0 mb-4">
<div class="card-body"> <div class="card-body">
<h1 class="h3 card-title">Exploration read-only des candidats</h1> <h1 class="h3 card-title">Exploration read-only des candidats</h1>
<p class="text-body-secondary mb-2">Les requêtes sont paramétrées, bornées et sans SQL libre. Utilisez les cases à cocher pour sélectionner les lignes. La case du header sélectionne ou désélectionne toutes les lignes correspondant au filtre local. Sans sélection explicite, la copie et lexport CSV utilisent toutes les lignes filtrées. Les CSV UTF-8 avec BOM et séparateur point-virgule sont écrits par le backend Tauri dans <code>./data/exports_csv/</code>. LibreOffice Calc peut toujours afficher son dialogue dimport, car le format CSV ne décrit pas formellement son encodage ni son séparateur.</p> <p class="text-body-secondary mb-2">Les requêtes sont paramétrées, bornées et indépendantes du backend concret. Utilisez les cases à cocher pour sélectionner les lignes. La case du header sélectionne ou désélectionne toutes les lignes correspondant au filtre local. Sans sélection explicite, la copie et lexport CSV utilisent toutes les lignes filtrées. Les CSV UTF-8 avec BOM et séparateur point-virgule sont écrits par le backend Tauri dans <code>./data/exports_csv/</code>. LibreOffice Calc peut toujours afficher son dialogue dimport, car le format CSV ne décrit pas formellement son encodage ni son séparateur.</p>
<div class="small text-body-secondary"> <div class="small text-body-secondary">
<span class="fw-semibold">DSN :</span> <span class="fw-semibold">Connexion :</span>
<code id="replayCandidateDsn"></code> <code id="replayCandidateDsn"></code>
<span class="ms-3 fw-semibold">Limite serveur maximale :</span> <span class="ms-3 fw-semibold">Limite serveur maximale :</span>
<code id="replayCandidateMaximumLimit"></code> <code id="replayCandidateMaximumLimit"></code>
@@ -68,7 +68,7 @@
<section class="tab-pane fade show active" id="transactionsTabPane" role="tabpanel" aria-labelledby="transactionsTabButton" tabindex="0"> <section class="tab-pane fade show active" id="transactionsTabPane" role="tabpanel" aria-labelledby="transactionsTabButton" tabindex="0">
<div class="card shadow-sm border-0 mb-4"> <div class="card shadow-sm border-0 mb-4">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title">Filtres PostgreSQL bornés</h2> <h2 class="h5 card-title">Filtres store bornés</h2>
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div class="col-12 col-xl-4"> <div class="col-12 col-xl-4">
<label for="transactionSignatureContainsInput" class="form-label">Signature contient</label> <label for="transactionSignatureContainsInput" class="form-label">Signature contient</label>
@@ -110,8 +110,8 @@
<div class="col-6 col-md-3 col-xl-2"> <div class="col-6 col-md-3 col-xl-2">
<label for="transactionProgramScopeSelect" class="form-label">Portée programme</label> <label for="transactionProgramScopeSelect" class="form-label">Portée programme</label>
<select id="transactionProgramScopeSelect" class="form-select"> <select id="transactionProgramScopeSelect" class="form-select">
<option value="any">outer + inner + logs</option> <option value="any">top-level + inner + logs</option>
<option value="outer">outer uniquement</option> <option value="top_level">top-level uniquement</option>
<option value="inner">inner uniquement</option> <option value="inner">inner uniquement</option>
<option value="logs">logs liés uniquement</option> <option value="logs">logs liés uniquement</option>
</select> </select>
@@ -441,7 +441,7 @@
</div> </div>
</div> </div>
</footer> </footer>
<script type="module" src="ts/demo_sql_replay_candidates.ts" defer></script> <script type="module" src="ts/demo_store_replay_candidates.ts" defer></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/main.html --> <!-- file: kb-app-demo-desktop/frontend/main.html -->
<!-- version: 12 --> <!-- version: 14 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -48,12 +48,12 @@
<hr class="dropdown-divider"> <hr class="dropdown-divider">
</li> </li>
<li> <li>
<h6 class="dropdown-header">SQL</h6> <h6 class="dropdown-header">Store</h6>
</li> </li>
<li><a id="openDemoSqlDiagLink" class="dropdown-item" href="#">SQL diagnostics</a></li> <li><a id="openDemoStoreDiagLink" class="dropdown-item" href="#">Store diagnostics</a></li>
<li><a id="openDemoSqlPgRawLink" class="dropdown-item" href="#">SQL PostgreSQL raw</a></li> <li><a id="openDemoStoreRawLink" class="dropdown-item" href="#">Raw store</a></li>
<li><a id="openDemoSqlPgCoreLink" class="dropdown-item" href="#">SQL PostgreSQL core</a></li> <li><a id="openDemoStoreCoreLink" class="dropdown-item" href="#">Core store</a></li>
<li><a id="openDemoSqlReplayCandidatesLink" class="dropdown-item" href="#">SQL replay candidates</a></li> <li><a id="openDemoStoreReplayCandidatesLink" class="dropdown-item" href="#">Store replay candidates</a></li>
<li> <li>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
</li> </li>

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts // file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
// version: 6 // version: 7
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -127,7 +127,7 @@ async function cancelCoreExtraction(): Promise<void> {
} }
async function openDiagnostics(command: "open_demo_sql_pg_raw_window" | "open_demo_sql_pg_core_window" | "open_demo_sql_replay_candidates_window", label: string): Promise<void> { async function openDiagnostics(command: "open_demo_store_raw_window" | "open_demo_store_core_window" | "open_demo_store_replay_candidates_window", label: string): Promise<void> {
try { try {
await invoke(command); await invoke(command);
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `${label} ouvert.` }); appendLog({ timestamp: new Date().toISOString(), level: "info", message: `${label} ouvert.` });
@@ -166,13 +166,13 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
}); });
element<HTMLButtonElement>("#openRawDiagnosticsButton").addEventListener("click", () => { element<HTMLButtonElement>("#openRawDiagnosticsButton").addEventListener("click", () => {
void openDiagnostics("open_demo_sql_pg_raw_window", "Diagnostic raw"); void openDiagnostics("open_demo_store_raw_window", "Diagnostic raw");
}); });
element<HTMLButtonElement>("#openCoreDiagnosticsButton").addEventListener("click", () => { element<HTMLButtonElement>("#openCoreDiagnosticsButton").addEventListener("click", () => {
void openDiagnostics("open_demo_sql_pg_core_window", "Diagnostic core"); void openDiagnostics("open_demo_store_core_window", "Diagnostic core");
}); });
element<HTMLButtonElement>("#openReplayCandidatesButton").addEventListener("click", () => { element<HTMLButtonElement>("#openReplayCandidatesButton").addEventListener("click", () => {
void openDiagnostics("open_demo_sql_replay_candidates_window", "Sélecteur de candidats replay"); void openDiagnostics("open_demo_store_replay_candidates_window", "Sélecteur de candidats replay");
}); });
element<HTMLButtonElement>("#cancelCoreExtractionButton").addEventListener("click", () => { element<HTMLButtonElement>("#cancelCoreExtractionButton").addEventListener("click", () => {
void cancelCoreExtraction(); void cancelCoreExtraction();

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts // file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
// version: 7 // version: 8
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -204,7 +204,7 @@ async function loadDiagnostics(): Promise<void> {
try { try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke demo_decode_replay_diagnostics"); frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke demo_decode_replay_diagnostics");
const diagnostics = await invoke<DemoDecodeDiagnosticsPayload>("demo_decode_replay_diagnostics"); const diagnostics = await invoke<DemoDecodeDiagnosticsPayload>("demo_decode_replay_diagnostics");
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_diagnostics completed tables=${diagnostics.tables.length} coverage=${diagnostics.coverage.length}`); frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_diagnostics completed resources=${diagnostics.resources.length} coverage=${diagnostics.coverage.length}`);
element<HTMLTextAreaElement>("#decodeReplayDiagnosticsOutput").value = JSON.stringify(diagnostics, null, 2); element<HTMLTextAreaElement>("#decodeReplayDiagnosticsOutput").value = JSON.stringify(diagnostics, null, 2);
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -282,7 +282,7 @@ async function loadAnnotations(): Promise<void> {
} }
} }
async function openWindow(command: "open_demo_sql_pg_core_window" | "open_demo_sql_replay_candidates_window"): Promise<void> { async function openWindow(command: "open_demo_store_core_window" | "open_demo_store_replay_candidates_window"): Promise<void> {
try { try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command}`); frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command}`);
await invoke(command); await invoke(command);
@@ -358,8 +358,8 @@ document.addEventListener("DOMContentLoaded", () => {
element<HTMLButtonElement>("#cancelDecodeReplayButton").addEventListener("click", () => void cancelDecodeReplay()); element<HTMLButtonElement>("#cancelDecodeReplayButton").addEventListener("click", () => void cancelDecodeReplay());
element<HTMLButtonElement>("#loadDecodeDiagnosticsButton").addEventListener("click", () => void loadDiagnostics()); element<HTMLButtonElement>("#loadDecodeDiagnosticsButton").addEventListener("click", () => void loadDiagnostics());
element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void loadAnnotations()); element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void loadAnnotations());
element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_replay_candidates_window")); element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_store_replay_candidates_window"));
element<HTMLButtonElement>("#openCoreDiagnosticsFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_pg_core_window")); element<HTMLButtonElement>("#openCoreDiagnosticsFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_store_core_window"));
void listen<DemoDecodeReplayProgressPayload>("demo-decode-replay-progress", event => appendLog(event.payload)); void listen<DemoDecodeReplayProgressPayload>("demo-decode-replay-progress", event => appendLog(event.payload));
void loadOptions().catch(caughtError => { void loadOptions().catch(caughtError => {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_solana_core.ts // file: kb-app-demo-desktop/frontend/ts/demo_execution_solana_core.ts
// version: 10 // version: 11
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -32,6 +32,6 @@ function displaySummary(summary:DemoExecutionSolanaCoreSummaryPayload):void { el
function displayExecutionFailure(message: string, submit: boolean): void { element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ title: "Résumé", status: "error", message }, null, 2); element<HTMLTextAreaElement>("#executionPlanOutput").value = JSON.stringify({ title: "Plan exact", status: "unavailable", reason: "preflight_failed", message: "Aucun plan exécutable na été produit.", error: message }, null, 2); element<HTMLTextAreaElement>("#executionSimulationOutput").value = JSON.stringify({ title: "Simulation exacte", status: "not_started", reason: "preflight_failed", message: "La simulation na pas été lancée.", error: message }, null, 2); element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = JSON.stringify({ title: "Confirmation et replay", status: "not_started", reason: "execution_failed_before_submission", message: submit ? "La signature, lenvoi, la confirmation et le replay nont pas été exécutés." : "La confirmation et le replay ne sappliquent pas à cette simulation refusée.", error: message }, null, 2); appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "failed", message }); } function displayExecutionFailure(message: string, submit: boolean): void { element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ title: "Résumé", status: "error", message }, null, 2); element<HTMLTextAreaElement>("#executionPlanOutput").value = JSON.stringify({ title: "Plan exact", status: "unavailable", reason: "preflight_failed", message: "Aucun plan exécutable na été produit.", error: message }, null, 2); element<HTMLTextAreaElement>("#executionSimulationOutput").value = JSON.stringify({ title: "Simulation exacte", status: "not_started", reason: "preflight_failed", message: "La simulation na pas été lancée.", error: message }, null, 2); element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = JSON.stringify({ title: "Confirmation et replay", status: "not_started", reason: "execution_failed_before_submission", message: submit ? "La signature, lenvoi, la confirmation et le replay nont pas été exécutés." : "La confirmation et le replay ne sappliquent pas à cette simulation refusée.", error: message }, null, 2); appendLog({ timestamp: new Date().toISOString(), level: "error", stage: "failed", message }); }
async function execute(submit: boolean): Promise<void> { if (running) { return; } try { const request = buildRequest(submit); if (submit && !request.operatorConfirmed) { const message = "La confirmation opérateur explicite est obligatoire avant signature et envoi."; displayExecutionFailure(message, submit); frontendWarn("kb-app-demo-desktop.frontend.demo_execution_solana_core", "Submission blocked: operator confirmation missing"); return; } setRunning(true); const summary = await invoke<DemoExecutionSolanaCoreSummaryPayload>("demo_execution_solana_core_execute", { request }); displaySummary(summary); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); displayExecutionFailure(message, submit); frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core", `Execution failed: ${message}`); } finally { setRunning(false); } } async function execute(submit: boolean): Promise<void> { if (running) { return; } try { const request = buildRequest(submit); if (submit && !request.operatorConfirmed) { const message = "La confirmation opérateur explicite est obligatoire avant signature et envoi."; displayExecutionFailure(message, submit); frontendWarn("kb-app-demo-desktop.frontend.demo_execution_solana_core", "Submission blocked: operator confirmation missing"); return; } setRunning(true); const summary = await invoke<DemoExecutionSolanaCoreSummaryPayload>("demo_execution_solana_core_execute", { request }); displaySummary(summary); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); displayExecutionFailure(message, submit); frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core", `Execution failed: ${message}`); } finally { setRunning(false); } }
async function prepareSelectedProfile():Promise<void>{const profile=selectedProfile();profileStoreReady=false;setRunning(running);if(!profile){return;}appendLog({timestamp:new Date().toISOString(),level:"info",stage:"store_prepare",message:`Vérification PostgreSQL du profil ${profile.name}.`});try{const readiness=await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile",{profileName:profile.name});profileStoreReady=true;appendLog({timestamp:new Date().toISOString(),level:"info",stage:"store_ready",message:`PostgreSQL prêt: ${readiness.existingTablesAfter}/${readiness.expectedTables} tables, créées=${readiness.createdTables}, autoInit=${readiness.autoInitializeSchema}.`});}catch(caughtError){const message=caughtError instanceof Error?caughtError.message:String(caughtError);frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core",`Devnet store preparation failed: ${message}`);}finally{setRunning(running);}} async function prepareSelectedProfile(): Promise<void> { const profile = selectedProfile(); profileStoreReady = false; setRunning(running); if (!profile) { return; } appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_prepare", message: `Vérification du store du profil ${profile.name}.` }); try { const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName: profile.name }); profileStoreReady = true; appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_ready", message: `Store ${readiness.backend} prêt: ${readiness.availableResourcesAfter}/${readiness.expectedResources} ressources, créées=${readiness.createdResources}, autoInit=${readiness.autoInitializeSchema}.` }); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core", `Devnet store preparation failed: ${message}`); } finally { setRunning(running); } }
async function loadOptions(): Promise<void> { const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options"); profileOptions = options.profiles; const select = element<HTMLSelectElement>("#executionProfileSelect"); select.replaceChildren(); for (const profile of profileOptions) { const option = document.createElement("option"); option.value = profile.name; option.textContent = profile.name; option.selected = profile.name === options.defaultProfileName; select.append(option); } element<HTMLInputElement>("#executionLamportsInput").value = String(options.defaultTransferLamports); updateProfileHelp(); setRunning(options.running); await prepareSelectedProfile(); } async function loadOptions(): Promise<void> { const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options"); profileOptions = options.profiles; const select = element<HTMLSelectElement>("#executionProfileSelect"); select.replaceChildren(); for (const profile of profileOptions) { const option = document.createElement("option"); option.value = profile.name; option.textContent = profile.name; option.selected = profile.name === options.defaultProfileName; select.append(option); } element<HTMLInputElement>("#executionLamportsInput").value = String(options.defaultTransferLamports); updateProfileHelp(); setRunning(options.running); await prepareSelectedProfile(); }
document.addEventListener("DOMContentLoaded", async () => { installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_execution_solana_core"); frontendDebug("kb-app-demo-desktop.frontend.demo_execution_solana_core", "Solana Core execution window loaded"); await listen<DemoExecutionSolanaCoreProgressPayload>("demo-execution-solana-core-progress", event => appendLog(event.payload)); element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change", () => { updateProfileHelp(); void prepareSelectedProfile(); }); element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click", async () => { const payload = await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient"); element<HTMLInputElement>("#executionRecipientInput").value = payload.publicKey; }); element<HTMLButtonElement>("#simulateExecutionButton").addEventListener("click", () => void execute(false)); element<HTMLButtonElement>("#submitExecutionButton").addEventListener("click", () => void execute(true)); element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click", () => void invoke("demo_execution_solana_core_cancel")); element<HTMLTextAreaElement>("#executionLogOutput").addEventListener("app-log-clear", () => { logLines.length = 0; }); try { await loadOptions(); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core", `Options loading failed: ${message}`); } }); document.addEventListener("DOMContentLoaded", async () => { installFrontendConsoleBridge("kb-app-demo-desktop.frontend.demo_execution_solana_core"); frontendDebug("kb-app-demo-desktop.frontend.demo_execution_solana_core", "Solana Core execution window loaded"); await listen<DemoExecutionSolanaCoreProgressPayload>("demo-execution-solana-core-progress", event => appendLog(event.payload)); element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change", () => { updateProfileHelp(); void prepareSelectedProfile(); }); element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click", async () => { const payload = await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient"); element<HTMLInputElement>("#executionRecipientInput").value = payload.publicKey; }); element<HTMLButtonElement>("#simulateExecutionButton").addEventListener("click", () => void execute(false)); element<HTMLButtonElement>("#submitExecutionButton").addEventListener("click", () => void execute(true)); element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click", () => void invoke("demo_execution_solana_core_cancel")); element<HTMLTextAreaElement>("#executionLogOutput").addEventListener("app-log-clear", () => { logLines.length = 0; }); try { await loadOptions(); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); frontendError("kb-app-demo-desktop.frontend.demo_execution_solana_core", `Options loading failed: ${message}`); } });

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts // file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
// version: 10 // version: 11
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -896,11 +896,11 @@ async function prepareSelectedProfile(): Promise<void> {
if (!profile) { if (!profile) {
return; return;
} }
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_prepare", message: `Vérification PostgreSQL du profil ${profile.name}.` }); appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_prepare", message: `Vérification du store du profil ${profile.name}.` });
try { try {
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName: profile.name }); const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName: profile.name });
profileStoreReady = true; profileStoreReady = true;
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_ready", message: `PostgreSQL prêt: ${readiness.existingTablesAfter}/${readiness.expectedTables} tables, créées=${readiness.createdTables}, autoInit=${readiness.autoInitializeSchema}.` }); appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_ready", message: `Store ${readiness.backend} prêt: ${readiness.availableResourcesAfter}/${readiness.expectedResources} ressources, créées=${readiness.createdResources}, autoInit=${readiness.autoInitializeSchema}.` });
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Devnet store preparation failed: ${message}`); frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Devnet store preparation failed: ${message}`);

View File

@@ -1,38 +1,38 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_pg_core.ts // file: kb-app-demo-desktop/frontend/ts/demo_store_core.ts
// version: 4 // version: 6
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
import { renderJsonViewer, renderTables, setText } from "./demo_sql_tables"; import { renderJsonViewer, renderTables, setText } from "./demo_store_tables";
import type { DemoSqlPgCorePayload } from "./bindings/kb_app_demo_desktop/demo_sql_pg_core/DemoSqlPgCorePayload"; import type { DemoStoreCorePayload } from "./bindings/kb_app_demo_desktop/demo_store_core/DemoStoreCorePayload";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
async function refreshCore(): Promise<void> { async function refreshCore(): Promise<void> {
setText("#sqlCoreStatus", "Chargement..."); setText("#storeCoreStatus", "Chargement...");
try { try {
const payload = await invoke<DemoSqlPgCorePayload>("load_demo_sql_pg_core"); const payload = await invoke<DemoStoreCorePayload>("load_demo_store_core");
setText("#sqlCoreProfile", payload.activeProfileName); setText("#storeCoreProfile", payload.activeProfileName);
setText("#sqlCoreDsn", payload.maskedDsn); setText("#storeCoreConnection", payload.connectionDescriptor);
setText("#sqlCoreStatus", "OK"); setText("#storeCoreStatus", "OK");
renderTables("#sqlCoreTable", "#sqlCoreTableBody", payload.tables); renderTables("#storeCoreTable", "#storeCoreTableBody", payload.resources);
renderJsonViewer("#sqlCoreJson", payload); renderJsonViewer("#storeCoreJson", payload);
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_core", "PostgreSQL core diagnostics refreshed"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_core", "Core store diagnostics refreshed");
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#sqlCoreStatus", `Erreur : ${message}`); setText("#storeCoreStatus", `Erreur : ${message}`);
renderJsonViewer("#sqlCoreJson", { error: message }); renderJsonViewer("#storeCoreJson", { error: message });
frontendError("kb_app_demo_desktop.frontend.demo_sql_pg_core", `PostgreSQL core diagnostics loading failed: ${message}`); frontendError("kb_app_demo_desktop.frontend.demo_store_core", `Core store diagnostics loading failed: ${message}`);
} }
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_pg_core"); installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_core");
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_core", "PostgreSQL core demo window loaded"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_core", "Core store demo window loaded");
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLButtonElement>("#refreshSqlCoreButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#refreshSqlCoreButton")?.addEventListener("click", () => {

View File

@@ -1,41 +1,41 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_diag.ts // file: kb-app-demo-desktop/frontend/ts/demo_store_diag.ts
// version: 3 // version: 5
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
import { jsonText, renderJsonViewer, renderTables, setText } from "./demo_sql_tables"; import { jsonText, renderJsonViewer, renderTables, setText } from "./demo_store_tables";
import type { DemoSqlDiagPayload } from "./bindings/kb_app_demo_desktop/demo_sql_diag/DemoSqlDiagPayload"; import type { DemoStoreDiagPayload } from "./bindings/kb_app_demo_desktop/demo_store_diag/DemoStoreDiagPayload";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
let latestPayload: DemoSqlDiagPayload | null = null; let latestPayload: DemoStoreDiagPayload | null = null;
async function refreshDiagnostics(): Promise<void> { async function refreshDiagnostics(): Promise<void> {
setText("#sqlDiagStatus", "Chargement..."); setText("#storeDiagStatus", "Chargement...");
try { try {
const payload = await invoke<DemoSqlDiagPayload>("load_demo_sql_diag"); const payload = await invoke<DemoStoreDiagPayload>("load_demo_store_diag");
setText("#sqlDiagConfigPath", payload.configPath); setText("#storeDiagConfigPath", payload.configPath);
setText("#sqlDiagProfile", payload.activeProfileName); setText("#storeDiagProfile", payload.activeProfileName);
setText("#sqlDiagBackend", payload.backend); setText("#storeDiagBackend", payload.backend);
setText("#sqlDiagDsn", payload.maskedDsn); setText("#storeDiagConnection", payload.connectionDescriptor);
setText("#sqlDiagSchema", payload.currentSchema ?? "—"); setText("#storeDiagSchema", payload.namespace ?? "—");
setText("#sqlDiagHealth", payload.healthStatus); setText("#storeDiagHealth", payload.healthStatus);
setText("#sqlDiagMigration", payload.migrationStatus); setText("#storeDiagMigration", payload.migrationStatus);
setText("#sqlDiagStatus", "OK"); setText("#storeDiagStatus", "OK");
renderTables("#sqlDiagTable", "#sqlDiagTableBody", payload.tables); renderTables("#storeDiagTable", "#storeDiagTableBody", payload.resources);
latestPayload = payload; latestPayload = payload;
renderJsonViewer("#sqlDiagJson", payload); renderJsonViewer("#storeDiagJson", payload);
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_diag", "SQL diagnostics refreshed"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_diag", "Store diagnostics refreshed");
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#sqlDiagStatus", `Erreur : ${message}`); setText("#storeDiagStatus", `Erreur : ${message}`);
latestPayload = null; latestPayload = null;
renderJsonViewer("#sqlDiagJson", { error: message }); renderJsonViewer("#storeDiagJson", { error: message });
frontendError("kb_app_demo_desktop.frontend.demo_sql_diag", `SQL diagnostics loading failed: ${message}`); frontendError("kb_app_demo_desktop.frontend.demo_store_diag", `Store diagnostics loading failed: ${message}`);
} }
} }
@@ -46,8 +46,8 @@ async function copyDiagnostics(): Promise<void> {
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_diag"); installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_diag");
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_diag", "SQL diagnostics demo window loaded"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_diag", "Store diagnostics demo window loaded");
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLButtonElement>("#refreshSqlDiagButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#refreshSqlDiagButton")?.addEventListener("click", () => {

View File

@@ -1,38 +1,38 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_pg_raw.ts // file: kb-app-demo-desktop/frontend/ts/demo_store_raw.ts
// version: 4 // version: 6
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
import { renderJsonViewer, renderTables, setText } from "./demo_sql_tables"; import { renderJsonViewer, renderTables, setText } from "./demo_store_tables";
import type { DemoSqlPgRawPayload } from "./bindings/kb_app_demo_desktop/demo_sql_pg_raw/DemoSqlPgRawPayload"; import type { DemoStoreRawPayload } from "./bindings/kb_app_demo_desktop/demo_store_raw/DemoStoreRawPayload";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
async function refreshRaw(): Promise<void> { async function refreshRaw(): Promise<void> {
setText("#sqlRawStatus", "Chargement..."); setText("#storeRawStatus", "Chargement...");
try { try {
const payload = await invoke<DemoSqlPgRawPayload>("load_demo_sql_pg_raw"); const payload = await invoke<DemoStoreRawPayload>("load_demo_store_raw");
setText("#sqlRawProfile", payload.activeProfileName); setText("#storeRawProfile", payload.activeProfileName);
setText("#sqlRawDsn", payload.maskedDsn); setText("#storeRawConnection", payload.connectionDescriptor);
setText("#sqlRawStatus", "OK"); setText("#storeRawStatus", "OK");
renderTables("#sqlRawTable", "#sqlRawTableBody", payload.tables); renderTables("#storeRawTable", "#storeRawTableBody", payload.resources);
renderJsonViewer("#sqlRawJson", payload); renderJsonViewer("#storeRawJson", payload);
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_raw", "PostgreSQL raw diagnostics refreshed"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_raw", "Raw store diagnostics refreshed");
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#sqlRawStatus", `Erreur : ${message}`); setText("#storeRawStatus", `Erreur : ${message}`);
renderJsonViewer("#sqlRawJson", { error: message }); renderJsonViewer("#storeRawJson", { error: message });
frontendError("kb_app_demo_desktop.frontend.demo_sql_pg_raw", `PostgreSQL raw diagnostics loading failed: ${message}`); frontendError("kb_app_demo_desktop.frontend.demo_store_raw", `Raw store diagnostics loading failed: ${message}`);
} }
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_pg_raw"); installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_raw");
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_raw", "PostgreSQL raw demo window loaded"); frontendDebug("kb_app_demo_desktop.frontend.demo_store_raw", "Raw store demo window loaded");
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.querySelector<HTMLButtonElement>("#refreshSqlRawButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#refreshSqlRawButton")?.addEventListener("click", () => {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_replay_candidates.ts // file: kb-app-demo-desktop/frontend/ts/demo_store_replay_candidates.ts
// version: 9 // version: 11
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -10,14 +10,14 @@ import "datatables.net-select-bs5";
import "datatables.net-select-bs5/css/select.bootstrap5.css"; import "datatables.net-select-bs5/css/select.bootstrap5.css";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
import type { DemoSqlReplayEntityRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRequest"; import type { DemoStoreReplayEntityRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRequest";
import type { DemoSqlReplayEntityRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRow"; import type { DemoStoreReplayEntityRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRow";
import type { DemoSqlReplayKnownProgramOption } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayKnownProgramOption"; import type { DemoStoreReplayKnownProgramOption } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayKnownProgramOption";
import type { DemoSqlReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayOptionsPayload"; import type { DemoStoreReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayOptionsPayload";
import type { DemoSqlReplayProgramRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRequest"; import type { DemoStoreReplayProgramRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRequest";
import type { DemoSqlReplayProgramRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRow"; import type { DemoStoreReplayProgramRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRow";
import type { DemoSqlReplayTransactionRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRequest"; import type { DemoStoreReplayTransactionRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRequest";
import type { DemoSqlReplayTransactionRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRow"; import type { DemoStoreReplayTransactionRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRow";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
@@ -40,7 +40,7 @@ type EntityViewConfig = {
fileName: string; fileName: string;
}; };
const tracingTarget = "kb_app_demo_desktop.frontend.demo_sql_replay_candidates"; const tracingTarget = "kb_app_demo_desktop.frontend.demo_store_replay_candidates";
const entityViewConfigs: EntityViewConfig[] = [ const entityViewConfigs: EntityViewConfig[] = [
{ {
kind: "mint", kind: "mint",
@@ -88,11 +88,11 @@ const entityViewConfigs: EntityViewConfig[] = [
fileName: "replay_account_keys.csv", fileName: "replay_account_keys.csv",
}, },
]; ];
let transactionTable: Api<DemoSqlReplayTransactionRow> | null = null; let transactionTable: Api<DemoStoreReplayTransactionRow> | null = null;
let programTable: Api<DemoSqlReplayProgramRow> | null = null; let programTable: Api<DemoStoreReplayProgramRow> | null = null;
let mintTable: Api<DemoSqlReplayEntityRow> | null = null; let mintTable: Api<DemoStoreReplayEntityRow> | null = null;
let ownerTable: Api<DemoSqlReplayEntityRow> | null = null; let ownerTable: Api<DemoStoreReplayEntityRow> | null = null;
let accountTable: Api<DemoSqlReplayEntityRow> | null = null; let accountTable: Api<DemoStoreReplayEntityRow> | null = null;
let busyOperationCount = 0; let busyOperationCount = 0;
let maximumReplayCandidateLimit = 100_000; let maximumReplayCandidateLimit = 100_000;
@@ -199,7 +199,7 @@ function badgeDisplay(value: unknown, type: string): string {
return `<span class="badge ${className}">${escapeHtml(text)}</span>`; return `<span class="badge ${className}">${escapeHtml(text)}</span>`;
} }
function coreStatusText(row: DemoSqlReplayTransactionRow): string { function coreStatusText(row: DemoStoreReplayTransactionRow): string {
if (!row.hasCoreTransaction) { if (!row.hasCoreTransaction) {
return "absent"; return "absent";
} }
@@ -209,7 +209,7 @@ function coreStatusText(row: DemoSqlReplayTransactionRow): string {
return "présent"; return "présent";
} }
function coreDisplay(_value: unknown, type: string, row: DemoSqlReplayTransactionRow): string | number { function coreDisplay(_value: unknown, type: string, row: DemoStoreReplayTransactionRow): string | number {
if (type === "display") { if (type === "display") {
if (!row.hasCoreTransaction) { if (!row.hasCoreTransaction) {
return '<span class="badge text-bg-secondary">absent</span>'; return '<span class="badge text-bg-secondary">absent</span>';
@@ -228,7 +228,7 @@ function coreDisplay(_value: unknown, type: string, row: DemoSqlReplayTransactio
return row.hasCoreTransaction ? 1 : 2; return row.hasCoreTransaction ? 1 : 2;
} }
function occurrenceRatioDisplay(_value: unknown, type: string, row: DemoSqlReplayEntityRow): string | number { function occurrenceRatioDisplay(_value: unknown, type: string, row: DemoStoreReplayEntityRow): string | number {
const ratio = row.transactionCount > 0 ? row.occurrenceCount / row.transactionCount : 0; const ratio = row.transactionCount > 0 ? row.occurrenceCount / row.transactionCount : 0;
if (type === "display" || type === "filter") { if (type === "display" || type === "filter") {
return ratio.toFixed(2); return ratio.toFixed(2);
@@ -236,7 +236,7 @@ function occurrenceRatioDisplay(_value: unknown, type: string, row: DemoSqlRepla
return ratio; return ratio;
} }
function slotSpanDisplay(_value: unknown, type: string, row: DemoSqlReplayEntityRow): string | number { function slotSpanDisplay(_value: unknown, type: string, row: DemoStoreReplayEntityRow): string | number {
const span = Math.max(0, row.maxSlot - row.minSlot); const span = Math.max(0, row.maxSlot - row.minSlot);
if (type === "display" || type === "filter") { if (type === "display" || type === "filter") {
return String(span); return String(span);
@@ -258,8 +258,8 @@ function commonLanguage(emptyTable: string, infoLabel: string, zeroRecords: stri
}; };
} }
function createTransactionTable(): Api<DemoSqlReplayTransactionRow> { function createTransactionTable(): Api<DemoStoreReplayTransactionRow> {
return new DataTable<DemoSqlReplayTransactionRow>("#transactionCandidateTable", { return new DataTable<DemoStoreReplayTransactionRow>("#transactionCandidateTable", {
data: [], data: [],
columns: [ columns: [
{ {
@@ -277,9 +277,9 @@ function createTransactionTable(): Api<DemoSqlReplayTransactionRow> {
{ data: "ledgerStatus", render: badgeDisplay }, { data: "ledgerStatus", render: badgeDisplay },
{ data: "processorVersion", defaultContent: "—", render: codeDisplay }, { data: "processorVersion", defaultContent: "—", render: codeDisplay },
{ data: "attemptCount" }, { data: "attemptCount" },
{ data: "outerInstructionCount" }, { data: "topLevelInstructionCount" },
{ data: "innerInstructionCount" }, { data: "innerInstructionCount" },
{ data: "outerProgramCount" }, { data: "topLevelProgramCount" },
{ data: "innerProgramCount" }, { data: "innerProgramCount" },
{ data: "updatedAt" }, { data: "updatedAt" },
], ],
@@ -296,8 +296,8 @@ function createTransactionTable(): Api<DemoSqlReplayTransactionRow> {
}); });
} }
function createProgramTable(): Api<DemoSqlReplayProgramRow> { function createProgramTable(): Api<DemoStoreReplayProgramRow> {
return new DataTable<DemoSqlReplayProgramRow>("#programCandidateTable", { return new DataTable<DemoStoreReplayProgramRow>("#programCandidateTable", {
data: [], data: [],
columns: [ columns: [
{ {
@@ -311,7 +311,7 @@ function createProgramTable(): Api<DemoSqlReplayProgramRow> {
{ data: "programCode", defaultContent: "—", render: codeDisplay }, { data: "programCode", defaultContent: "—", render: codeDisplay },
{ data: "programId", render: copyableCodeDisplay("le program ID") }, { data: "programId", render: copyableCodeDisplay("le program ID") },
{ data: "transactionCount" }, { data: "transactionCount" },
{ data: "outerInstructionCount" }, { data: "topLevelInstructionCount" },
{ data: "innerInstructionCount" }, { data: "innerInstructionCount" },
{ data: "logCount" }, { data: "logCount" },
{ data: "minSlot" }, { data: "minSlot" },
@@ -330,8 +330,8 @@ function createProgramTable(): Api<DemoSqlReplayProgramRow> {
}); });
} }
function createEntityTable(config: EntityViewConfig): Api<DemoSqlReplayEntityRow> { function createEntityTable(config: EntityViewConfig): Api<DemoStoreReplayEntityRow> {
return new DataTable<DemoSqlReplayEntityRow>(config.tableSelector, { return new DataTable<DemoStoreReplayEntityRow>(config.tableSelector, {
data: [], data: [],
columns: [ columns: [
{ {
@@ -363,7 +363,7 @@ function createEntityTable(config: EntityViewConfig): Api<DemoSqlReplayEntityRow
}); });
} }
function entityTable(kind: EntityKindCode): Api<DemoSqlReplayEntityRow> | null { function entityTable(kind: EntityKindCode): Api<DemoStoreReplayEntityRow> | null {
if (kind === "mint") { if (kind === "mint") {
return mintTable; return mintTable;
} }
@@ -425,7 +425,7 @@ function setBusy(busy: boolean, message: string): void {
}); });
} }
function buildTransactionRequest(): DemoSqlReplayTransactionRequest { function buildTransactionRequest(): DemoStoreReplayTransactionRequest {
const entityKind = optionalInputValue("#transactionEntityKindSelect"); const entityKind = optionalInputValue("#transactionEntityKindSelect");
const entityValue = optionalInputValue("#transactionEntityValueInput"); const entityValue = optionalInputValue("#transactionEntityValueInput");
if ((entityKind === null) !== (entityValue === null)) { if ((entityKind === null) !== (entityValue === null)) {
@@ -484,9 +484,9 @@ async function loadTransactions(): Promise<void> {
try { try {
setBusy(true, "Chargement transactions"); setBusy(true, "Chargement transactions");
const request = buildTransactionRequest(); const request = buildTransactionRequest();
const rows = await invoke<DemoSqlReplayTransactionRow[]>("load_demo_sql_replay_transactions", { request }); const rows = await invoke<DemoStoreReplayTransactionRow[]>("load_demo_store_replay_transactions", { request });
replaceRows(transactionTable, rows); replaceRows(transactionTable, rows);
element<HTMLElement>("#transactionResultSummary").textContent = `${rows.length} transaction(s) chargée(s) depuis PostgreSQL.`; element<HTMLElement>("#transactionResultSummary").textContent = `${rows.length} transaction(s) chargée(s) depuis le store actif.`;
frontendDebug(tracingTarget, `loaded ${rows.length} replay transaction candidates`); frontendDebug(tracingTarget, `loaded ${rows.length} replay transaction candidates`);
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -503,13 +503,13 @@ async function loadPrograms(): Promise<void> {
} }
try { try {
setBusy(true, "Chargement programmes"); setBusy(true, "Chargement programmes");
const request: DemoSqlReplayProgramRequest = { const request: DemoStoreReplayProgramRequest = {
programIdContains: optionalInputValue("#programContainsInput"), programIdContains: optionalInputValue("#programContainsInput"),
limit: boundedPositiveIntegerValue("#programLimitInput"), limit: boundedPositiveIntegerValue("#programLimitInput"),
}; };
const rows = await invoke<DemoSqlReplayProgramRow[]>("load_demo_sql_replay_programs", { request }); const rows = await invoke<DemoStoreReplayProgramRow[]>("load_demo_store_replay_programs", { request });
replaceRows(programTable, rows); replaceRows(programTable, rows);
element<HTMLElement>("#programResultSummary").textContent = `${rows.length} programme(s) chargé(s), avec occurrences outer, inner et logs liés.`; element<HTMLElement>("#programResultSummary").textContent = `${rows.length} programme(s) chargé(s), avec occurrences top-level, inner et logs liés.`;
frontendDebug(tracingTarget, `loaded ${rows.length} replay program summaries`); frontendDebug(tracingTarget, `loaded ${rows.length} replay program summaries`);
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -527,14 +527,14 @@ async function loadEntity(config: EntityViewConfig): Promise<void> {
} }
try { try {
setBusy(true, `Chargement ${config.pluralLabel}`); setBusy(true, `Chargement ${config.pluralLabel}`);
const request: DemoSqlReplayEntityRequest = { const request: DemoStoreReplayEntityRequest = {
entityKind: config.kind, entityKind: config.kind,
entityValueContains: optionalInputValue(config.containsSelector), entityValueContains: optionalInputValue(config.containsSelector),
limit: boundedPositiveIntegerValue(config.limitSelector), limit: boundedPositiveIntegerValue(config.limitSelector),
}; };
const rows = await invoke<DemoSqlReplayEntityRow[]>("load_demo_sql_replay_entities", { request }); const rows = await invoke<DemoStoreReplayEntityRow[]>("load_demo_store_replay_entities", { request });
replaceRows(table, rows); replaceRows(table, rows);
element<HTMLElement>(config.summarySelector).textContent = `${rows.length} ${config.pluralLabel} chargé(s) depuis PostgreSQL.`; element<HTMLElement>(config.summarySelector).textContent = `${rows.length} ${config.pluralLabel} chargé(s) depuis le store actif.`;
frontendDebug(tracingTarget, `loaded ${rows.length} replay ${config.kind} summaries`); frontendDebug(tracingTarget, `loaded ${rows.length} replay ${config.kind} summaries`);
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -567,7 +567,7 @@ async function saveCsv(fileName: string, content: string, rowCount: number): Pro
if (rowCount === 0) { if (rowCount === 0) {
throw new Error("Aucune ligne à exporter."); throw new Error("Aucune ligne à exporter.");
} }
const path = await invoke<string>("export_demo_sql_replay_csv", { fileName, content }); const path = await invoke<string>("export_demo_store_replay_csv", { fileName, content });
const badge = element<HTMLElement>("#replayCandidateStatusBadge"); const badge = element<HTMLElement>("#replayCandidateStatusBadge");
badge.textContent = `${rowCount} ligne(s) exportée(s) : ${path}`; badge.textContent = `${rowCount} ligne(s) exportée(s) : ${path}`;
badge.className = "badge text-bg-success"; badge.className = "badge text-bg-success";
@@ -577,8 +577,8 @@ async function saveCsv(fileName: string, content: string, rowCount: number): Pro
async function exportTransactionsCsv(): Promise<void> { async function exportTransactionsCsv(): Promise<void> {
const rows = transactionRowsForExport(); const rows = transactionRowsForExport();
const content = csvContent( const content = csvContent(
["signature", "slot", "raw_processing_state", "retention_state", "core_status", "ledger_status", "processor_version", "attempt_count", "outer_instruction_count", "inner_instruction_count", "outer_program_count", "inner_program_count", "updated_at"], ["signature", "slot", "raw_processing_state", "retention_state", "core_status", "ledger_status", "processor_version", "attempt_count", "top_level_instruction_count", "inner_instruction_count", "top_level_program_count", "inner_program_count", "updated_at"],
rows.map(row => [row.signature, row.slot, row.rawProcessingState, row.retentionState, coreStatusText(row), row.ledgerStatus, row.processorVersion, row.attemptCount, row.outerInstructionCount, row.innerInstructionCount, row.outerProgramCount, row.innerProgramCount, row.updatedAt]), rows.map(row => [row.signature, row.slot, row.rawProcessingState, row.retentionState, coreStatusText(row), row.ledgerStatus, row.processorVersion, row.attemptCount, row.topLevelInstructionCount, row.innerInstructionCount, row.topLevelProgramCount, row.innerProgramCount, row.updatedAt]),
); );
await saveCsv("replay_transactions.csv", content, rows.length); await saveCsv("replay_transactions.csv", content, rows.length);
} }
@@ -586,8 +586,8 @@ async function exportTransactionsCsv(): Promise<void> {
async function exportProgramsCsv(): Promise<void> { async function exportProgramsCsv(): Promise<void> {
const rows = programRowsForExport(); const rows = programRowsForExport();
const content = csvContent( const content = csvContent(
["program_code", "program_id", "transaction_count", "outer_instruction_count", "inner_instruction_count", "log_count", "min_slot", "max_slot"], ["program_code", "program_id", "transaction_count", "top_level_instruction_count", "inner_instruction_count", "log_count", "min_slot", "max_slot"],
rows.map(row => [row.programCode, row.programId, row.transactionCount, row.outerInstructionCount, row.innerInstructionCount, row.logCount, row.minSlot, row.maxSlot]), rows.map(row => [row.programCode, row.programId, row.transactionCount, row.topLevelInstructionCount, row.innerInstructionCount, row.logCount, row.minSlot, row.maxSlot]),
); );
await saveCsv("replay_programs.csv", content, rows.length); await saveCsv("replay_programs.csv", content, rows.length);
} }
@@ -601,21 +601,21 @@ async function exportEntityCsv(config: EntityViewConfig): Promise<void> {
await saveCsv(config.fileName, content, rows.length); await saveCsv(config.fileName, content, rows.length);
} }
function transactionRowsForExport(): DemoSqlReplayTransactionRow[] { function transactionRowsForExport(): DemoStoreReplayTransactionRow[] {
if (transactionTable === null) { if (transactionTable === null) {
return []; return [];
} }
return selectedOrFilteredRows(transactionTable); return selectedOrFilteredRows(transactionTable);
} }
function programRowsForExport(): DemoSqlReplayProgramRow[] { function programRowsForExport(): DemoStoreReplayProgramRow[] {
if (programTable === null) { if (programTable === null) {
return []; return [];
} }
return selectedOrFilteredRows(programTable); return selectedOrFilteredRows(programTable);
} }
function entityRowsForExport(kind: EntityKindCode): DemoSqlReplayEntityRow[] { function entityRowsForExport(kind: EntityKindCode): DemoStoreReplayEntityRow[] {
const table = entityTable(kind); const table = entityTable(kind);
if (table === null) { if (table === null) {
return []; return [];
@@ -660,13 +660,13 @@ async function openCoreExtraction(): Promise<void> {
} }
async function loadOptions(): Promise<void> { async function loadOptions(): Promise<void> {
const options = await invoke<DemoSqlReplayOptionsPayload>("demo_sql_replay_options"); const options = await invoke<DemoStoreReplayOptionsPayload>("demo_store_replay_options");
if (!Number.isSafeInteger(options.maximumLimit) || options.maximumLimit <= 0) { if (!Number.isSafeInteger(options.maximumLimit) || options.maximumLimit <= 0) {
throw new Error("La limite maximale des candidats replay retournée par le backend est invalide."); throw new Error("La limite maximale des candidats replay retournée par le backend est invalide.");
} }
maximumReplayCandidateLimit = options.maximumLimit; maximumReplayCandidateLimit = options.maximumLimit;
element<HTMLElement>("#replayCandidateProfileBadge").textContent = `Profil ${options.activeProfileName}`; element<HTMLElement>("#replayCandidateProfileBadge").textContent = `Profil ${options.activeProfileName}`;
element<HTMLElement>("#replayCandidateDsn").textContent = options.maskedDsn; element<HTMLElement>("#replayCandidateDsn").textContent = options.connectionDescriptor;
element<HTMLElement>("#replayCandidateMaximumLimit").textContent = String(options.maximumLimit); element<HTMLElement>("#replayCandidateMaximumLimit").textContent = String(options.maximumLimit);
document.querySelectorAll<HTMLInputElement>("input[type=number][max]").forEach(input => { document.querySelectorAll<HTMLInputElement>("input[type=number][max]").forEach(input => {
input.max = String(options.maximumLimit); input.max = String(options.maximumLimit);
@@ -674,7 +674,7 @@ async function loadOptions(): Promise<void> {
populateKnownPrograms(options.knownPrograms); populateKnownPrograms(options.knownPrograms);
} }
function populateKnownPrograms(programs: DemoSqlReplayKnownProgramOption[]): void { function populateKnownPrograms(programs: DemoStoreReplayKnownProgramOption[]): void {
const select = element<HTMLSelectElement>("#knownProgramSelect"); const select = element<HTMLSelectElement>("#knownProgramSelect");
const datalist = element<HTMLDataListElement>("#knownProgramDatalist"); const datalist = element<HTMLDataListElement>("#knownProgramDatalist");
select.replaceChildren(new Option("Tous les programmes connus", "")); select.replaceChildren(new Option("Tous les programmes connus", ""));
@@ -779,7 +779,7 @@ function installHandlers(): void {
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge(tracingTarget); installFrontendConsoleBridge(tracingTarget);
frontendDebug(tracingTarget, "SQL replay candidate browser loaded"); frontendDebug(tracingTarget, "Store replay candidate browser loaded");
transactionTable = createTransactionTable(); transactionTable = createTransactionTable();
programTable = createProgramTable(); programTable = createProgramTable();
mintTable = createEntityTable(entityViewConfigs[0]); mintTable = createEntityTable(entityViewConfigs[0]);

View File

@@ -1,10 +1,10 @@
// file: kb-app-demo-desktop/frontend/ts/demo_sql_tables.ts // file: kb-app-demo-desktop/frontend/ts/demo_store_tables.ts
// version: 4 // version: 6
import DataTable, { type Api } from "datatables.net-bs5"; import DataTable, { type Api } from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css"; import "datatables.net-bs5/css/dataTables.bootstrap5.css";
import { jsonText, renderJsonViewer } from "./json_viewer.ts"; import { jsonText, renderJsonViewer } from "./json_viewer.ts";
import type { DemoSqlTableSnapshot } from "./bindings/kb_app_demo_desktop/demo_sql/DemoSqlTableSnapshot"; import type { DemoStoreResourceSnapshot } from "./bindings/kb_app_demo_desktop/demo_store/DemoStoreResourceSnapshot";
const tableInstances = new Map<string, Api>(); const tableInstances = new Map<string, Api>();
@@ -15,7 +15,7 @@ export function setText(selector: string, value: string): void {
} }
} }
export function renderTables(tableSelector: string, bodySelector: string, tables: DemoSqlTableSnapshot[]): void { export function renderTables(tableSelector: string, bodySelector: string, resources: DemoStoreResourceSnapshot[]): void {
const existing = tableInstances.get(tableSelector); const existing = tableInstances.get(tableSelector);
if (existing) { if (existing) {
existing.destroy(); existing.destroy();
@@ -26,16 +26,16 @@ export function renderTables(tableSelector: string, bodySelector: string, tables
return; return;
} }
tbody.textContent = ""; tbody.textContent = "";
for (const table of tables) { for (const resource of resources) {
const row = document.createElement("tr"); const row = document.createElement("tr");
row.appendChild(cell(table.tableName, true)); row.appendChild(cell(resource.resourceCode, true));
row.appendChild(cell(table.domain, false)); row.appendChild(cell(resource.modelCode, false));
row.appendChild(cell(table.role, false)); row.appendChild(cell(resource.role, false));
row.appendChild(cell(table.exists ? "oui" : "non", false)); row.appendChild(cell(resource.available ? "oui" : "non", false));
row.appendChild(cell(table.rowCount === null ? "—" : String(table.rowCount), false)); row.appendChild(cell(resource.recordCount === null ? "—" : String(resource.recordCount), false));
row.appendChild(cell(table.minSlot === null ? "—" : String(table.minSlot), false)); row.appendChild(cell(resource.minSlot === null ? "—" : String(resource.minSlot), false));
row.appendChild(cell(table.maxSlot === null ? "—" : String(table.maxSlot), false)); row.appendChild(cell(resource.maxSlot === null ? "—" : String(resource.maxSlot), false));
row.appendChild(cell(table.latestCreatedAt ?? "—", false)); row.appendChild(cell(resource.latestCreatedAt ?? "—", false));
tbody.appendChild(row); tbody.appendChild(row);
} }
const tableElement = document.querySelector<HTMLTableElement>(tableSelector); const tableElement = document.querySelector<HTMLTableElement>(tableSelector);
@@ -48,12 +48,12 @@ export function renderTables(tableSelector: string, bodySelector: string, tables
lengthMenu: [10, 25, 50, 100], lengthMenu: [10, 25, 50, 100],
order: [[0, "asc"]], order: [[0, "asc"]],
language: { language: {
emptyTable: "Aucune table disponible", emptyTable: "Aucune ressource disponible",
info: "Tables _START_ à _END_ sur _TOTAL_", info: "Ressources _START_ à _END_ sur _TOTAL_",
infoEmpty: "Aucune table", infoEmpty: "Aucune ressource",
lengthMenu: "Afficher _MENU_ tables", lengthMenu: "Afficher _MENU_ ressources",
search: "Rechercher :", search: "Rechercher :",
zeroRecords: "Aucune table correspondante", zeroRecords: "Aucune ressource correspondante",
}, },
})); }));
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/main.ts // file: kb-app-demo-desktop/frontend/ts/main.ts
// version: 7 // version: 8
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
@@ -97,10 +97,10 @@ for (const [elementId, command] of pipelineDemoLinks) {
} }
const sqlDemoLinks: ReadonlyArray<readonly [string, string]> = [ const sqlDemoLinks: ReadonlyArray<readonly [string, string]> = [
["openDemoSqlDiagLink", "open_demo_sql_diag_window"], ["openDemoStoreDiagLink", "open_demo_store_diag_window"],
["openDemoSqlPgRawLink", "open_demo_sql_pg_raw_window"], ["openDemoStoreRawLink", "open_demo_store_raw_window"],
["openDemoSqlPgCoreLink", "open_demo_sql_pg_core_window"], ["openDemoStoreCoreLink", "open_demo_store_core_window"],
["openDemoSqlReplayCandidatesLink", "open_demo_sql_replay_candidates_window"], ["openDemoStoreReplayCandidatesLink", "open_demo_store_replay_candidates_window"],
]; ];
for (const [elementId, command] of sqlDemoLinks) { for (const [elementId, command] of sqlDemoLinks) {
const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`); const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`);

View File

@@ -1,7 +1,7 @@
{ {
"name": "kb-app-demo-desktop", "name": "kb-app-demo-desktop",
"private": true, "private": true,
"version": "0.5.2", "version": "0.5.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/app_state.rs // file: kb-app-demo-desktop/src/app_state.rs
// version: 17 // version: 20
//! Shared Tauri application state and startup initialization. //! Shared Tauri application state and startup initialization.
@@ -17,6 +17,7 @@ pub(crate) struct AppState {
active_profile: ks_config::ProfileConfig, active_profile: ks_config::ProfileConfig,
logging_guard: std::sync::Mutex<ks_logging::LoggingGuard>, logging_guard: std::sync::Mutex<ks_logging::LoggingGuard>,
http_pool: ks_onchain_transport::HttpEndpointPool, http_pool: ks_onchain_transport::HttpEndpointPool,
store: tokio::sync::OnceCell<ks_store::Store>,
ws_pool: ws_pool:
std::sync::Mutex<std::option::Option<std::sync::Arc<ks_onchain_transport::WsEndpointPool>>>, std::sync::Mutex<std::option::Option<std::sync::Arc<ks_onchain_transport::WsEndpointPool>>>,
demo_ws_session: demo_ws_session:
@@ -106,6 +107,7 @@ impl crate::AppState {
active_profile, active_profile,
logging_guard: std::sync::Mutex::new(logging_guard), logging_guard: std::sync::Mutex::new(logging_guard),
http_pool, http_pool,
store: tokio::sync::OnceCell::new(),
ws_pool: std::sync::Mutex::new(std::option::Option::None), ws_pool: std::sync::Mutex::new(std::option::Option::None),
demo_ws_session: tokio::sync::Mutex::new(std::option::Option::None), demo_ws_session: tokio::sync::Mutex::new(std::option::Option::None),
demo_backfill_running: std::sync::atomic::AtomicBool::new(false), demo_backfill_running: std::sync::atomic::AtomicBool::new(false),
@@ -174,6 +176,25 @@ impl crate::AppState {
return &self.active_profile; return &self.active_profile;
} }
/// Returns the persistent backend-agnostic store opened from the active profile.
pub(crate) async fn store(&self) -> ks_core::Result<&ks_store::Store> {
if let std::option::Option::Some(store) = self.store.get() {
return std::result::Result::Ok(store);
}
let options = match ks_store::StoreOpenOptions::new(
self.active_profile.database.enabled,
self.active_profile.database.backend.clone(),
self.active_profile.database.backend_options.clone(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return self
.store
.get_or_try_init(|| return async move { return ks_store::Store::open(options).await })
.await;
}
/// Returns the session-only execution-wallet alias override for one profile. /// Returns the session-only execution-wallet alias override for one profile.
pub(crate) fn demo_execution_wallet_alias_override( pub(crate) fn demo_execution_wallet_alias_override(
&self, &self,

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_backfill.rs // file: kb-app-demo-desktop/src/demo_backfill.rs
// version: 16 // version: 17
//! Tauri commands and UI payloads for bounded HTTP transaction backfills. //! Tauri commands and UI payloads for bounded HTTP transaction backfills.
@@ -288,23 +288,10 @@ pub(crate) async fn demo_backfill_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let profile = state.active_profile(); let store = match crate::shared_store(state.inner()).await {
let store_options = match ks_store::PostgresStoreOptions::new(
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let observer = crate::DemoBackfillObserver { let observer = crate::DemoBackfillObserver {
app_handle, app_handle,
cancel_requested: state.demo_backfill_cancel_requested(), cancel_requested: state.demo_backfill_cancel_requested(),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_config.rs // file: kb-app-demo-desktop/src/demo_config.rs
// version: 6 // version: 8
//! Configuration demo payloads with explicit public and bounded diagnostic projections. //! Configuration demo payloads with explicit public and bounded diagnostic projections.
@@ -164,7 +164,7 @@ pub(crate) struct DemoConfigEndpointDiagnosticPayload {
pub(crate) role_count: u32, pub(crate) role_count: u32,
} }
/// Store diagnostic projection without DSN or SQLite path content. /// Backend-agnostic store diagnostic projection without backend option values.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[ts( #[ts(
export, export,
@@ -173,23 +173,16 @@ pub(crate) struct DemoConfigEndpointDiagnosticPayload {
pub(crate) struct DemoConfigStoreDiagnosticPayload { pub(crate) struct DemoConfigStoreDiagnosticPayload {
/// Whether persistence is enabled. /// Whether persistence is enabled.
pub(crate) enabled: bool, pub(crate) enabled: bool,
/// Selected store backend. /// Selected store backend code.
pub(crate) backend: std::string::String, pub(crate) backend: std::string::String,
/// Whether a PostgreSQL URL is configured. /// Whether backend-specific configuration validated successfully.
pub(crate) postgres_url_state: std::string::String, pub(crate) configuration_status: std::string::String,
/// PostgreSQL connection pool ceiling. /// Whether a backend connection or location value is configured.
pub(crate) postgres_max_connections: u32, pub(crate) connection_state: std::string::String,
/// PostgreSQL connection timeout in milliseconds. /// Whether automatic store initialization is enabled.
#[ts(type = "number")] pub(crate) auto_initialize_schema: bool,
pub(crate) postgres_connect_timeout_ms: u64, /// Number of opaque backend options supplied after composition.
/// Whether PostgreSQL schema initialization is enabled. pub(crate) backend_option_count: u32,
pub(crate) postgres_auto_initialize_schema: bool,
/// Whether a SQLite path is configured.
pub(crate) sqlite_path_state: std::string::String,
/// SQLite connection pool ceiling.
pub(crate) sqlite_max_connections: u32,
/// Whether SQLite schema initialization is enabled.
pub(crate) sqlite_auto_initialize_schema: bool,
} }
/// Wallet diagnostic projection without paths or secret key material. /// Wallet diagnostic projection without paths or secret key material.
@@ -375,17 +368,7 @@ fn diagnostic_config_payload(state: &crate::AppState) -> DemoConfigDiagnosticPay
source_diagnostic("execution", state.execution_config_path()), source_diagnostic("execution", state.execution_config_path()),
], ],
endpoints, endpoints,
store: DemoConfigStoreDiagnosticPayload { store: store_diagnostic(profile),
enabled: profile.database.enabled,
backend: profile.database.backend.clone(),
postgres_url_state: configured_state(profile.database.postgres.url.as_str()),
postgres_max_connections: profile.database.postgres.max_connections,
postgres_connect_timeout_ms: profile.database.postgres.connect_timeout_ms,
postgres_auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
sqlite_path_state: configured_state(profile.database.sqlite.path.as_str()),
sqlite_max_connections: profile.database.sqlite.max_connections,
sqlite_auto_initialize_schema: profile.database.sqlite.auto_initialize_schema,
},
wallet: DemoConfigWalletDiagnosticPayload { wallet: DemoConfigWalletDiagnosticPayload {
wallet_directory_state: configured_state(profile.wallet.wallet_dir.as_str()), wallet_directory_state: configured_state(profile.wallet.wallet_dir.as_str()),
cluster: profile.wallet.cluster.clone(), cluster: profile.wallet.cluster.clone(),
@@ -398,6 +381,55 @@ fn diagnostic_config_payload(state: &crate::AppState) -> DemoConfigDiagnosticPay
}; };
} }
fn store_diagnostic(profile: &ks_config::ProfileConfig) -> DemoConfigStoreDiagnosticPayload {
let backend_option_count = match profile.database.backend_options.as_object() {
std::option::Option::Some(value) => bounded_len(value.len()),
std::option::Option::None => 0,
};
let options = match ks_store::StoreOpenOptions::new(
profile.database.enabled,
profile.database.backend.clone(),
profile.database.backend_options.clone(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return DemoConfigStoreDiagnosticPayload {
enabled: profile.database.enabled,
backend: profile.database.backend.clone(),
configuration_status: "invalid".to_string(),
connection_state: "unknown".to_string(),
auto_initialize_schema: false,
backend_option_count,
};
},
};
let summary = match options.configuration_summary() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return DemoConfigStoreDiagnosticPayload {
enabled: profile.database.enabled,
backend: profile.database.backend.clone(),
configuration_status: "invalid".to_string(),
connection_state: "unknown".to_string(),
auto_initialize_schema: false,
backend_option_count,
};
},
};
return DemoConfigStoreDiagnosticPayload {
enabled: summary.enabled,
backend: summary.backend_code,
configuration_status: "ready".to_string(),
connection_state: if summary.connection_configured {
"configured".to_string()
} else {
"missing".to_string()
},
auto_initialize_schema: summary.auto_initialize_schema,
backend_option_count: summary.backend_option_count,
};
}
fn source_diagnostic(category: &str, path: &str) -> DemoConfigSourceDiagnosticPayload { fn source_diagnostic(category: &str, path: &str) -> DemoConfigSourceDiagnosticPayload {
return DemoConfigSourceDiagnosticPayload { return DemoConfigSourceDiagnosticPayload {
category: category.to_string(), category: category.to_string(),
@@ -503,9 +535,19 @@ mod tests {
#[test] #[test]
fn public_and_diagnostic_projections_never_serialize_secret_canaries() { fn public_and_diagnostic_projections_never_serialize_secret_canaries() {
let mut profile = active_fixture_profile(); let mut profile = active_fixture_profile();
profile.database.postgres.url = if let std::option::Option::Some(options) = profile.database.backend_options.as_object_mut()
"postgres://operator:POSTGRES-SECRET-CANARY@localhost/solana".to_string(); {
profile.database.sqlite.path = "/private/SQLITE-PATH-CANARY.sqlite".to_string(); options.insert(
"url".to_string(),
serde_json::Value::String(
"postgres://operator:POSTGRES-SECRET-CANARY@localhost/solana".to_string(),
),
);
options.insert(
"private_path".to_string(),
serde_json::Value::String("/private/STORE-PATH-CANARY".to_string()),
);
}
profile.wallet.wallet_dir = "/private/WALLET-PATH-CANARY".to_string(); profile.wallet.wallet_dir = "/private/WALLET-PATH-CANARY".to_string();
if let std::option::Option::Some(endpoint) = profile.solana.http_endpoints.first_mut() { if let std::option::Option::Some(endpoint) = profile.solana.http_endpoints.first_mut() {
endpoint.url = "https://provider.invalid/?api-key=HELIUS-SECRET-CANARY".to_string(); endpoint.url = "https://provider.invalid/?api-key=HELIUS-SECRET-CANARY".to_string();
@@ -536,17 +578,7 @@ mod tests {
}; };
}) })
.collect::<std::vec::Vec<super::DemoConfigEndpointDiagnosticPayload>>(), .collect::<std::vec::Vec<super::DemoConfigEndpointDiagnosticPayload>>(),
store: super::DemoConfigStoreDiagnosticPayload { store: super::store_diagnostic(&profile),
enabled: profile.database.enabled,
backend: profile.database.backend.clone(),
postgres_url_state: super::configured_state(profile.database.postgres.url.as_str()),
postgres_max_connections: profile.database.postgres.max_connections,
postgres_connect_timeout_ms: profile.database.postgres.connect_timeout_ms,
postgres_auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
sqlite_path_state: super::configured_state(profile.database.sqlite.path.as_str()),
sqlite_max_connections: profile.database.sqlite.max_connections,
sqlite_auto_initialize_schema: profile.database.sqlite.auto_initialize_schema,
},
wallet: super::DemoConfigWalletDiagnosticPayload { wallet: super::DemoConfigWalletDiagnosticPayload {
wallet_directory_state: super::configured_state(profile.wallet.wallet_dir.as_str()), wallet_directory_state: super::configured_state(profile.wallet.wallet_dir.as_str()),
cluster: profile.wallet.cluster.clone(), cluster: profile.wallet.cluster.clone(),
@@ -563,7 +595,7 @@ mod tests {
}; };
for secret in [ for secret in [
"POSTGRES-SECRET-CANARY", "POSTGRES-SECRET-CANARY",
"SQLITE-PATH-CANARY", "STORE-PATH-CANARY",
"WALLET-PATH-CANARY", "WALLET-PATH-CANARY",
"HELIUS-SECRET-CANARY", "HELIUS-SECRET-CANARY",
"WS-SECRET-CANARY", "WS-SECRET-CANARY",

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_core_extraction.rs // file: kb-app-demo-desktop/src/demo_core_extraction.rs
// version: 14 // version: 17
//! Tauri commands and UI payloads for canonical transaction to core extraction. //! Tauri commands and UI payloads for canonical transaction to core extraction.
@@ -221,7 +221,7 @@ pub(crate) async fn demo_core_extraction_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs // file: kb-app-demo-desktop/src/demo_decode_replay.rs
// version: 40 // version: 44
//! Tauri commands and UI payloads for contextual instruction decode replay. //! Tauri commands and UI payloads for contextual instruction decode replay.
@@ -255,8 +255,8 @@ pub(crate) struct DemoDecodeCoverageSummaryPayload {
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts"
)] )]
pub(crate) struct DemoDecodeDiagnosticsPayload { pub(crate) struct DemoDecodeDiagnosticsPayload {
/// Decode, materialization and ledger table diagnostics. /// Decode, materialization and ledger resource diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>, pub(crate) resources: std::vec::Vec<crate::DemoStoreResourceSnapshot>,
/// Aggregated declared and observed coverage. /// Aggregated declared and observed coverage.
pub(crate) coverage: std::vec::Vec<crate::DemoDecodeCoverageSummaryPayload>, pub(crate) coverage: std::vec::Vec<crate::DemoDecodeCoverageSummaryPayload>,
} }
@@ -495,7 +495,7 @@ pub(crate) async fn demo_decode_replay_execute(
materialize_after_decode = pipeline_request.materialize_after_decode, materialize_after_decode = pipeline_request.materialize_after_decode,
"start contextual decode replay pipeline" "start contextual decode replay pipeline"
); );
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -563,24 +563,24 @@ pub(crate) async fn demo_decode_replay_diagnostics(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoDecodeDiagnosticsPayload, std::string::String> { ) -> std::result::Result<crate::DemoDecodeDiagnosticsPayload, std::string::String> {
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", coverage_limit = 500_u32, "load contextual decode replay diagnostics"); tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", coverage_limit = 500_u32, "load contextual decode replay diagnostics");
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let table_result = store.known_table_diagnostics().await; let resource_result = store.known_resource_diagnostics().await;
let all_tables = match table_result { let all_resources = match resource_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let selected_tables: std::vec::Vec<crate::DemoSqlTableSnapshot> = all_tables let selected_resources: std::vec::Vec<crate::DemoStoreResourceSnapshot> = all_resources
.iter() .iter()
.filter(|table| { .filter(|resource| {
return table.table_name.starts_with("kb_sol_decode_") return resource.model_code == "processing"
|| table.table_name.starts_with("kb_sol_mat_") || resource.model_code == "decode"
|| table.table_name == "kb_sol_ops_processing_ledger"; || resource.model_code == "materialization";
}) })
.map(crate::table_snapshot_from_pg) .map(crate::store_resource_snapshot)
.collect(); .collect();
let coverage_result = ks_store::DecodePipelineStore::list_decode_coverage_summary( let coverage_result = ks_store::DecodePipelineStore::list_decode_coverage_summary(
&store, &store,
@@ -593,9 +593,9 @@ pub(crate) async fn demo_decode_replay_diagnostics(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", table_count = selected_tables.len(), coverage_count = coverage.len(), "contextual decode replay diagnostics loaded"); tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", resource_count = selected_resources.len(), coverage_count = coverage.len(), "contextual decode replay diagnostics loaded");
return std::result::Result::Ok(crate::DemoDecodeDiagnosticsPayload { return std::result::Result::Ok(crate::DemoDecodeDiagnosticsPayload {
tables: selected_tables, resources: selected_resources,
coverage: coverage.into_iter().map(coverage_payload).collect(), coverage: coverage.into_iter().map(coverage_payload).collect(),
}); });
} }
@@ -616,7 +616,7 @@ pub(crate) async fn demo_decode_replay_annotations(
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", signature_contains = ?filter.signature_contains, limit = filter.limit, "load bounded committed transaction annotation journal"); tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", signature_contains = ?filter.signature_contains, limit = filter.limit, "load bounded committed transaction annotation journal");
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_devnet_common.rs // file: kb-app-demo-desktop/src/demo_devnet_common.rs
// version: 6 // version: 7
//! Shared Devnet demo UI contracts. //! Shared Devnet demo UI contracts.
@@ -7,7 +7,7 @@ use ts_rs::TS; // rust-rules: derive-import
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD"; const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
/// Readiness report for one Devnet profile PostgreSQL store. /// Backend-agnostic readiness report for one Devnet profile store.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
@@ -17,16 +17,18 @@ const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
pub(crate) struct DemoExecutionDevnetStoreReadinessPayload { pub(crate) struct DemoExecutionDevnetStoreReadinessPayload {
/// Selected Devnet profile. /// Selected Devnet profile.
pub(crate) profile_name: std::string::String, pub(crate) profile_name: std::string::String,
/// Selected backend code.
pub(crate) backend: std::string::String,
/// Whether schema creation was allowed by configuration. /// Whether schema creation was allowed by configuration.
pub(crate) auto_initialize_schema: bool, pub(crate) auto_initialize_schema: bool,
/// Number of known tables already present before preparation. /// Number of logical resources already present before preparation.
pub(crate) existing_tables_before: u32, pub(crate) available_resources_before: u32,
/// Number of known tables created during preparation. /// Number of logical resources created during preparation.
pub(crate) created_tables: u32, pub(crate) created_resources: u32,
/// Number of known tables present after preparation. /// Number of logical resources present after preparation.
pub(crate) existing_tables_after: u32, pub(crate) available_resources_after: u32,
/// Total number of known tables expected by the current store. /// Total number of logical resources expected by the current store contract.
pub(crate) expected_tables: u32, pub(crate) expected_resources: u32,
} }
/// Converts the reusable scenario readiness report to the desktop TS-RS payload. /// Converts the reusable scenario readiness report to the desktop TS-RS payload.
@@ -35,11 +37,12 @@ pub(crate) fn devnet_store_readiness_payload(
) -> DemoExecutionDevnetStoreReadinessPayload { ) -> DemoExecutionDevnetStoreReadinessPayload {
return DemoExecutionDevnetStoreReadinessPayload { return DemoExecutionDevnetStoreReadinessPayload {
profile_name: readiness.profile_name, profile_name: readiness.profile_name,
backend: readiness.backend,
auto_initialize_schema: readiness.auto_initialize_schema, auto_initialize_schema: readiness.auto_initialize_schema,
existing_tables_before: bounded_u32(readiness.existing_tables_before), available_resources_before: bounded_u32(readiness.available_resources_before),
created_tables: bounded_u32(readiness.created_tables), created_resources: bounded_u32(readiness.created_resources),
existing_tables_after: bounded_u32(readiness.existing_tables_after), available_resources_after: bounded_u32(readiness.available_resources_after),
expected_tables: bounded_u32(readiness.expected_tables), expected_resources: bounded_u32(readiness.expected_resources),
}; };
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs // file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
// version: 11 // version: 13
//! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows. //! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows.
@@ -190,13 +190,10 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let mut execution_request = match ks_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json( let mut execution_request = match ks_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
request.intent_id, request.intent_id,
request.operation_json.as_str(), request.operation_json.as_str(),
@@ -1044,13 +1041,10 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let workspace_root = crate::workspace_root_dir(); let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs // file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs
// version: 5 // version: 7
//! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign. //! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign.
@@ -88,13 +88,10 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let workspace_root = crate::workspace_root_dir(); let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs // file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 20 // version: 23
//! Tauri adapter for bounded Solana Core execution on Devnet. //! Tauri adapter for bounded Solana Core execution on Devnet.
@@ -394,7 +394,7 @@ pub(crate) fn confirmation_status_code(
}; };
} }
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile. /// Verifies or initializes the configured store selected by one Devnet profile.
pub(crate) async fn demo_execution_devnet_prepare_profile( pub(crate) async fn demo_execution_devnet_prepare_profile(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String, profile_name: std::string::String,
@@ -473,14 +473,10 @@ pub(crate) async fn demo_execution_solana_core_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let initialize_result = store.initialize_store_schema().await;
if let std::result::Result::Err(error) = initialize_result {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = ks_pipeline_demo_scenarios::DevnetSystemTransferRequest::new( let mut pipeline_request = ks_pipeline_demo_scenarios::DevnetSystemTransferRequest::new(
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()), format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
ks_lib::MdPubkey(request.recipient.trim().to_string()), ks_lib::MdPubkey(request.recipient.trim().to_string()),
@@ -561,13 +557,10 @@ pub(crate) async fn demo_execution_spl_memo_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new( let mut pipeline_request = ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new(
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()), format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
request.message, request.message,

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs // file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 16 // version: 18
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads. //! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
@@ -260,13 +260,10 @@ pub(crate) async fn demo_execution_spl_ata_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> = std::vec![ let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> = std::vec![
std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder), std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder),
std::sync::Arc::new(ks_lib::DcSplTokenDecoder), std::sync::Arc::new(ks_lib::DcSplTokenDecoder),
@@ -315,7 +312,7 @@ pub(crate) async fn demo_spl_ata_journal(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs // file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 13 // version: 16
//! Thin Tauri adapter for classic SPL Token execution and materialized journals. //! Thin Tauri adapter for classic SPL Token execution and materialized journals.
@@ -99,7 +99,7 @@ pub(crate) struct DemoExecutionSplTokenSummaryPayload {
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts"
)] )]
pub(crate) struct DemoSplTokenJournalRequest { pub(crate) struct DemoSplTokenJournalRequest {
/// Devnet profile whose PostgreSQL store is queried. /// Devnet profile whose configured store is queried.
pub(crate) profile_name: std::string::String, pub(crate) profile_name: std::string::String,
/// Optional partial signature handled by the bounded store query. /// Optional partial signature handled by the bounded store query.
pub(crate) signature_contains: std::option::Option<std::string::String>, pub(crate) signature_contains: std::option::Option<std::string::String>,
@@ -183,13 +183,10 @@ pub(crate) async fn demo_execution_spl_token_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let amount_raw = request.amount_raw.trim().to_string(); let amount_raw = request.amount_raw.trim().to_string();
let operation = ks_lib::ExSplClassicTokenOperation::Instruction { let operation = ks_lib::ExSplClassicTokenOperation::Instruction {
value: ks_lib::ExSplClassicTokenSingleOperation::TransferChecked { value: ks_lib::ExSplClassicTokenSingleOperation::TransferChecked {
@@ -259,13 +256,10 @@ pub(crate) async fn demo_spl_token_journal(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let needs_payload_filter = let needs_payload_filter =
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some(); validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
let query_limit = if needs_payload_filter { let query_limit = if needs_payload_filter {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs // file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
// version: 18 // version: 20
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios. //! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
@@ -159,13 +159,10 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store = match crate::connect_postgres_store(&profile).await { let store = match crate::open_store(&profile).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let operation = match operation_from_request(&request) { let operation = match operation_from_request(&request) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),

View File

@@ -1,312 +0,0 @@
// file: kb-app-demo-desktop/src/demo_sql_common.rs
// version: 13
//! Shared SQL demo helpers and serializable payloads.
use ts_rs::TS; // rust-rules: derive-import
/// UI-safe table diagnostics shown by SQL demo windows.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql/DemoSqlTableSnapshot.ts"
)]
pub(crate) struct DemoSqlTableSnapshot {
/// Table name.
pub(crate) table_name: std::string::String,
/// Logical Solana domain encoded in the table name.
pub(crate) domain: std::string::String,
/// Human-readable table role.
pub(crate) role: std::string::String,
/// Whether the table currently exists.
pub(crate) exists: bool,
/// Row count when the table exists.
#[ts(type = "number | null")]
pub(crate) row_count: std::option::Option<i64>,
/// Minimum slot when available.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<i64>,
/// Maximum slot when available.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<i64>,
/// Latest insertion timestamp rendered by PostgreSQL when available.
pub(crate) latest_created_at: std::option::Option<std::string::String>,
}
/// Builds PostgreSQL options from the active profile without coupling ks-store to ks-config.
pub(crate) fn postgres_store_options_from_profile(
profile: &ks_config::ProfileConfig,
auto_initialize_schema: bool,
) -> ks_core::Result<ks_store::PostgresStoreOptions> {
return ks_store::PostgresStoreOptions::new(
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
auto_initialize_schema,
);
}
/// Connects to PostgreSQL without reapplying schemas initialized at application startup.
pub(crate) async fn connect_postgres_store(
profile: &ks_config::ProfileConfig,
) -> std::result::Result<ks_store::PostgresStore, std::string::String> {
if !profile.database.enabled {
return std::result::Result::Err(std::string::String::from(
"database configuration is disabled in the active profile",
));
}
let options_result = postgres_store_options_from_profile(profile, false);
let mut options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
options.auto_initialize_schema = false;
let store_result = ks_store::PostgresStore::connect(options).await;
return match store_result {
std::result::Result::Ok(store) => std::result::Result::Ok(store),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Converts store table diagnostics to the UI payload shape.
pub(crate) fn table_snapshot_from_pg(
value: &ks_store::PostgresTableDiagnostics,
) -> crate::DemoSqlTableSnapshot {
let row_count = match &value.statistics {
std::option::Option::Some(statistics) => std::option::Option::Some(statistics.row_count),
std::option::Option::None => std::option::Option::None,
};
let min_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.min_slot,
std::option::Option::None => std::option::Option::None,
};
let max_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.max_slot,
std::option::Option::None => std::option::Option::None,
};
let latest_created_at = match &value.statistics {
std::option::Option::Some(statistics) => statistics.latest_created_at.clone(),
std::option::Option::None => std::option::Option::None,
};
return crate::DemoSqlTableSnapshot {
table_name: value.table_name.clone(),
domain: value.domain.clone(),
role: value.role.clone(),
exists: value.exists,
row_count,
min_slot,
max_slot,
latest_created_at,
};
}
/// Converts many table diagnostics to UI payloads.
pub(crate) fn table_snapshots_from_pg(
values: &[ks_store::PostgresTableDiagnostics],
) -> std::vec::Vec<crate::DemoSqlTableSnapshot> {
let mut output = std::vec::Vec::new();
for value in values {
output.push(crate::table_snapshot_from_pg(value));
}
return output;
}
/// Initializes PostgreSQL raw, core, decode and materialization schemas during Tauri startup.
pub(crate) async fn initialize_postgres_schema_for_startup(
state: &crate::AppState,
splash_window: &tauri::WebviewWindow,
) {
let profile = state.active_profile();
if !profile.database.enabled {
emit_sql_startup_splash(
splash_window,
"SQL store disabled; schema initialization skipped.",
"info",
false,
);
tracing::debug!(target: crate::TRACING_TARGET, "database is disabled; skip SQL schema initialization");
return;
}
if profile.database.backend != "postgres" {
emit_sql_startup_splash(
splash_window,
"SQL backend is not PostgreSQL; schema initialization skipped.",
"info",
false,
);
tracing::debug!(target: crate::TRACING_TARGET, backend = profile.database.backend.as_str(), "database backend is not postgres; skip SQL schema initialization");
return;
}
let options_result = postgres_store_options_from_profile(
profile,
profile.database.postgres.auto_initialize_schema,
);
let mut options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot build PostgreSQL store options");
return;
},
};
let initialize_schema = options.auto_initialize_schema;
let masked_dsn = options.masked_dsn();
options.auto_initialize_schema = false;
emit_sql_startup_splash(
splash_window,
"Connexion PostgreSQL et vérification des tables raw/core/decode...",
"info",
false,
);
if !initialize_schema {
emit_sql_startup_splash(
splash_window,
"Auto-initialisation désactivée : vérification sans création de table.",
"info",
true,
);
}
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "start PostgreSQL schema initialization");
let store_result = ks_store::PostgresStore::connect(options).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL connection failed during startup schema initialization");
return;
},
};
let before_result = store.known_table_diagnostics().await;
let before_tables = match before_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics before schema initialization");
return;
},
};
if initialize_schema {
let initialize_result = store.initialize_store_schema().await;
if let std::result::Result::Err(error) = initialize_result {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
return;
}
}
let after_result = store.known_table_diagnostics().await;
let after_tables = match after_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics after schema initialization");
return;
},
};
emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice());
}
/// Formats debug enums for UI display.
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
return format!("{value:?}");
}
fn emit_sql_startup_table_report(
splash_window: &tauri::WebviewWindow,
before_tables: &[ks_store::PostgresTableDiagnostics],
after_tables: &[ks_store::PostgresTableDiagnostics],
) {
let mut created_count = 0_u32;
let mut existing_count = 0_u32;
let mut missing_count = 0_u32;
for table in after_tables {
let before_exists = table_exists_in(before_tables, table.table_name.as_str());
if table.exists && before_exists == std::option::Option::Some(false) {
created_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Created table {}", table.table_name).as_str(),
"success",
true,
);
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table created");
} else if table.exists {
existing_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Table {} already exists", table.table_name).as_str(),
"info",
true,
);
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table already exists");
} else {
missing_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Table {} is missing after initialization", table.table_name).as_str(),
"warning",
true,
);
tracing::error!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table missing after initialization");
}
}
emit_sql_startup_splash(
splash_window,
format!(
"PostgreSQL schema ready: {created_count} created, {existing_count} already present, {missing_count} missing."
)
.as_str(),
schema_report_status(missing_count),
false,
);
tracing::debug!(target: crate::TRACING_TARGET, created = created_count, existing = existing_count, missing = missing_count, "PostgreSQL schema initialization completed");
}
fn table_exists_in(
tables: &[ks_store::PostgresTableDiagnostics],
table_name: &str,
) -> std::option::Option<bool> {
for table in tables {
if table.table_name.as_str() == table_name {
return std::option::Option::Some(table.exists);
}
}
return std::option::Option::None;
}
fn schema_report_status(missing_count: u32) -> &'static str {
if missing_count == 0 {
return "success";
}
return "warning";
}
fn emit_sql_startup_error(splash_window: &tauri::WebviewWindow, message: &str) {
emit_sql_startup_splash(
splash_window,
format!("PostgreSQL schema initialization error: {message}").as_str(),
"danger",
false,
);
}
fn emit_sql_startup_splash(
splash_window: &tauri::WebviewWindow,
message: &str,
status: &str,
log_only: bool,
) {
let order = if log_only { "add_log" } else { "add_msg" };
crate::emit_splash_order(
splash_window,
order,
std::option::Option::Some(message),
std::option::Option::Some(status),
std::option::Option::None,
);
}

View File

@@ -1,73 +0,0 @@
// file: kb-app-demo-desktop/src/demo_sql_diag.rs
// version: 5
//! SQL diagnostic demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// Complete SQL diagnostic payload shown by `demo_sql_diag`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_diag/DemoSqlDiagPayload.ts"
)]
pub(crate) struct DemoSqlDiagPayload {
/// Configuration file path.
pub(crate) config_path: std::string::String,
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Configured database backend.
pub(crate) backend: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// PostgreSQL current schema.
pub(crate) current_schema: std::option::Option<std::string::String>,
/// PostgreSQL health status.
pub(crate) health_status: std::string::String,
/// PostgreSQL health message.
pub(crate) health_message: std::option::Option<std::string::String>,
/// Migration status.
pub(crate) migration_status: std::string::String,
/// Migration diagnostic message.
pub(crate) migration_message: std::option::Option<std::string::String>,
/// PostgreSQL server version when available.
pub(crate) server_version: std::option::Option<std::string::String>,
/// Known raw/core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}
/// Loads read-only SQL diagnostics from the active profile.
pub(crate) async fn load_demo_sql_diag(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&profile).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let diagnostics_result = store.backend_diagnostics().await;
let diagnostics = match diagnostics_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let tables_result = store.known_table_diagnostics().await;
let tables = match tables_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::DemoSqlDiagPayload {
config_path: state.config_path().to_string(),
active_profile_name: profile.name.clone(),
backend: profile.database.backend.clone(),
masked_dsn: store.options().masked_dsn(),
current_schema: diagnostics.descriptor.current_schema,
health_status: crate::debug_status(diagnostics.health.status),
health_message: diagnostics.health.message,
migration_status: crate::debug_status(diagnostics.migrations.status),
migration_message: diagnostics.migrations.message,
server_version: diagnostics.server_version,
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -1,44 +0,0 @@
// file: kb-app-demo-desktop/src/demo_sql_pg_core.rs
// version: 6
//! PostgreSQL core store demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// PostgreSQL core store payload shown by `demo_sql_pg_core`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_pg_core/DemoSqlPgCorePayload.ts"
)]
pub(crate) struct DemoSqlPgCorePayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}
/// Loads read-only core table diagnostics from PostgreSQL.
pub(crate) async fn load_demo_sql_pg_core(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&profile).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let tables_result = store.core_table_diagnostics().await;
let tables = match tables_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::DemoSqlPgCorePayload {
active_profile_name: profile.name.clone(),
masked_dsn: store.options().masked_dsn(),
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -1,44 +0,0 @@
// file: kb-app-demo-desktop/src/demo_sql_pg_raw.rs
// version: 7
//! PostgreSQL canonical acquisition store demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// PostgreSQL canonical acquisition payload shown by `demo_sql_pg_raw`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_pg_raw/DemoSqlPgRawPayload.ts"
)]
pub(crate) struct DemoSqlPgRawPayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Raw table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}
/// Loads read-only raw table diagnostics from PostgreSQL.
pub(crate) async fn load_demo_sql_pg_raw(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&profile).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let tables_result = store.raw_table_diagnostics().await;
let tables = match tables_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(crate::DemoSqlPgRawPayload {
active_profile_name: profile.name.clone(),
masked_dsn: store.options().masked_dsn(),
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -0,0 +1,264 @@
// file: kb-app-demo-desktop/src/demo_store_common.rs
// version: 19
//! Shared backend-agnostic store demo helpers and serializable payloads.
use ts_rs::TS; // rust-rules: derive-import
/// UI-safe diagnostics for one logical store resource.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store/DemoStoreResourceSnapshot.ts"
)]
pub(crate) struct DemoStoreResourceSnapshot {
/// Stable logical resource code independent of the active backend.
pub(crate) resource_code: std::string::String,
/// Logical store model containing the resource.
pub(crate) model_code: std::string::String,
/// Human-readable resource role.
pub(crate) role: std::string::String,
/// Whether the logical resource is currently available.
pub(crate) available: bool,
/// Record count when the resource is available.
#[ts(type = "number | null")]
pub(crate) record_count: std::option::Option<i64>,
/// Minimum slot when available.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<i64>,
/// Maximum slot when available.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<i64>,
/// Latest persistence timestamp when available.
pub(crate) latest_created_at: std::option::Option<std::string::String>,
}
/// Builds backend-agnostic store-open options from one resolved profile.
fn store_open_options_from_profile(
profile: &ks_config::ProfileConfig,
) -> ks_core::Result<ks_store::StoreOpenOptions> {
return ks_store::StoreOpenOptions::new(
profile.database.enabled,
profile.database.backend.clone(),
profile.database.backend_options.clone(),
);
}
/// Opens a backend-agnostic store from one resolved profile.
///
/// Prefer [`crate::AppState::store`] for desktop commands that already own application state.
pub(crate) async fn open_store(
profile: &ks_config::ProfileConfig,
) -> std::result::Result<ks_store::Store, std::string::String> {
let options = match store_open_options_from_profile(profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return match ks_store::Store::open(options).await {
std::result::Result::Ok(store) => std::result::Result::Ok(store),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Clones the persistent store held by application state.
pub(crate) async fn shared_store(
state: &crate::AppState,
) -> std::result::Result<ks_store::Store, std::string::String> {
let store_result = state.store().await;
return match store_result {
std::result::Result::Ok(store) => std::result::Result::Ok(store.clone()),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Converts one logical store resource diagnostic to the UI payload shape.
pub(crate) fn store_resource_snapshot(
value: &ks_store::StoreResourceDiagnostics,
) -> crate::DemoStoreResourceSnapshot {
let record_count = match &value.statistics {
std::option::Option::Some(statistics) => std::option::Option::Some(statistics.record_count),
std::option::Option::None => std::option::Option::None,
};
let min_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.min_slot,
std::option::Option::None => std::option::Option::None,
};
let max_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.max_slot,
std::option::Option::None => std::option::Option::None,
};
let latest_created_at = match &value.statistics {
std::option::Option::Some(statistics) => statistics.latest_created_at.clone(),
std::option::Option::None => std::option::Option::None,
};
return crate::DemoStoreResourceSnapshot {
resource_code: value.resource_code.clone(),
model_code: value.model_code.clone(),
role: value.role.clone(),
available: value.available,
record_count,
min_slot,
max_slot,
latest_created_at,
};
}
/// Converts many logical store resource diagnostics to UI payloads.
pub(crate) fn store_resource_snapshots(
values: &[ks_store::StoreResourceDiagnostics],
) -> std::vec::Vec<crate::DemoStoreResourceSnapshot> {
let mut output = std::vec::Vec::with_capacity(values.len());
for value in values {
output.push(crate::store_resource_snapshot(value));
}
return output;
}
/// Opens, auto-initializes when configured, and verifies the persistent store during startup.
pub(crate) async fn initialize_store_for_startup(
state: &crate::AppState,
splash_window: &tauri::WebviewWindow,
) {
let store_result = state.store().await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_store_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "store startup failed");
return;
},
};
let configuration = store.configuration_summary();
if !configuration.enabled {
emit_store_startup_splash(
splash_window,
"Store disabled; initialization skipped.",
"info",
false,
);
tracing::debug!(target: crate::TRACING_TARGET, backend = configuration.backend_code.as_str(), "store disabled; startup initialization skipped");
return;
}
emit_store_startup_splash(
splash_window,
format!(
"Store backend {}: vérification des modèles de persistance...",
configuration.backend_code
)
.as_str(),
"info",
false,
);
let initialization = store.initialization_summary();
for model in &initialization.models {
emit_store_startup_splash(
splash_window,
format!(
"Modèle {}: {}/{} ressources disponibles, {} créées.",
model.model_code,
model.available_resource_count,
model.expected_resource_count,
model.created_resource_count
)
.as_str(),
model_status(&model.status),
true,
);
}
for object in &initialization.objects {
emit_store_startup_splash(
splash_window,
format!(
"Objets {}: {}/{} disponibles, {} créés.",
object.object_kind,
object.available_count,
object.expected_count,
object.created_count
)
.as_str(),
"info",
true,
);
}
emit_store_startup_splash(
splash_window,
format!(
"Store {:?}: ressources {}/{}, {} créées.",
initialization.status,
initialization.available_resource_count,
initialization.expected_resource_count,
initialization.created_resource_count
)
.as_str(),
initialization_status(&initialization.status),
false,
);
tracing::debug!(target: crate::TRACING_TARGET, backend = configuration.backend_code.as_str(), available_resources = initialization.available_resource_count, expected_resources = initialization.expected_resource_count, created_resources = initialization.created_resource_count, "store startup verification completed");
}
/// Returns the safe connection descriptor exposed by the active backend.
pub(crate) async fn store_connection_descriptor(
store: &ks_store::Store,
) -> std::result::Result<std::string::String, std::string::String> {
let runtime = match store.runtime_summary().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let backend = match runtime.backend {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok("disabled".to_string()),
};
let descriptor = match backend.descriptor.masked_connection_descriptor {
std::option::Option::Some(value) => value,
std::option::Option::None => "configured".to_string(),
};
return std::result::Result::Ok(descriptor);
}
/// Formats debug enums for UI display.
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
return format!("{value:?}");
}
fn model_status(status: &ks_store::StoreModelVerificationStatus) -> &'static str {
return match status {
ks_store::StoreModelVerificationStatus::Ready => "success",
ks_store::StoreModelVerificationStatus::Incomplete => "warning",
};
}
fn initialization_status(status: &ks_store::StoreInitializationStatus) -> &'static str {
return match status {
ks_store::StoreInitializationStatus::Ready => "success",
ks_store::StoreInitializationStatus::Disabled => "info",
ks_store::StoreInitializationStatus::Partial => "warning",
ks_store::StoreInitializationStatus::Failed => "danger",
};
}
fn emit_store_startup_error(splash_window: &tauri::WebviewWindow, message: &str) {
emit_store_startup_splash(
splash_window,
format!("Store initialization error: {message}").as_str(),
"danger",
false,
);
}
fn emit_store_startup_splash(
splash_window: &tauri::WebviewWindow,
message: &str,
status: &str,
log_only: bool,
) {
let order = if log_only { "add_log" } else { "add_msg" };
crate::emit_splash_order(
splash_window,
order,
std::option::Option::Some(message),
std::option::Option::Some(status),
std::option::Option::None,
);
}

View File

@@ -0,0 +1,45 @@
// file: kb-app-demo-desktop/src/demo_store_core.rs
// version: 9
//! Backend-agnostic Core store demo commands retained by the store window.
use ts_rs::TS; // rust-rules: derive-import
/// Backend-agnostic Core store payload shown by the existing `demo_store_core` window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_core/DemoStoreCorePayload.ts"
)]
pub(crate) struct DemoStoreCorePayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Safe backend connection descriptor.
pub(crate) connection_descriptor: std::string::String,
/// Logical Core-resource diagnostics.
pub(crate) resources: std::vec::Vec<crate::DemoStoreResourceSnapshot>,
}
/// Loads read-only Core resource diagnostics from the active store.
pub(crate) async fn load_demo_store_core(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoStoreCorePayload, std::string::String> {
let store = match crate::shared_store(state.inner()).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let resources = match store.core_resource_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let connection_descriptor = match crate::store_connection_descriptor(&store).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::DemoStoreCorePayload {
active_profile_name: state.active_profile().name.clone(),
connection_descriptor,
resources: crate::store_resource_snapshots(resources.as_slice()),
});
}

View File

@@ -0,0 +1,81 @@
// file: kb-app-demo-desktop/src/demo_store_diag.rs
// version: 9
//! Backend-agnostic store diagnostic demo commands retained by the Store diagnostics window.
use ts_rs::TS; // rust-rules: derive-import
/// Complete backend-neutral store diagnostic payload shown by `demo_store_diag`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_diag/DemoStoreDiagPayload.ts"
)]
pub(crate) struct DemoStoreDiagPayload {
/// Configuration file path.
pub(crate) config_path: std::string::String,
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Active backend code.
pub(crate) backend: std::string::String,
/// Safe backend connection descriptor.
pub(crate) connection_descriptor: std::string::String,
/// Backend namespace, schema, or equivalent logical location.
pub(crate) namespace: std::option::Option<std::string::String>,
/// Store health status.
pub(crate) health_status: std::string::String,
/// Store health message.
pub(crate) health_message: std::option::Option<std::string::String>,
/// Migration status.
pub(crate) migration_status: std::string::String,
/// Migration diagnostic message.
pub(crate) migration_message: std::option::Option<std::string::String>,
/// Backend version when available.
pub(crate) backend_version: std::option::Option<std::string::String>,
/// Known logical store resource diagnostics.
pub(crate) resources: std::vec::Vec<crate::DemoStoreResourceSnapshot>,
}
/// Loads read-only diagnostics from the active store.
pub(crate) async fn load_demo_store_diag(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoStoreDiagPayload, std::string::String> {
let store = match crate::shared_store(state.inner()).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let runtime = match store.runtime_summary().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let resources = match store.known_resource_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let backend = match runtime.backend {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"store backend diagnostics are unavailable".to_string(),
);
},
};
let connection_descriptor = match backend.descriptor.masked_connection_descriptor.clone() {
std::option::Option::Some(value) => value,
std::option::Option::None => "configured".to_string(),
};
return std::result::Result::Ok(crate::DemoStoreDiagPayload {
config_path: state.config_path().to_string(),
active_profile_name: state.active_profile().name.clone(),
backend: backend.descriptor.backend_code,
connection_descriptor,
namespace: backend.descriptor.namespace,
health_status: crate::debug_status(backend.health.status),
health_message: backend.health.message,
migration_status: crate::debug_status(backend.migrations.status),
migration_message: backend.migrations.message,
backend_version: backend.backend_version,
resources: crate::store_resource_snapshots(resources.as_slice()),
});
}

View File

@@ -0,0 +1,45 @@
// file: kb-app-demo-desktop/src/demo_store_raw.rs
// version: 10
//! Backend-agnostic level-1 raw store demo commands retained by the store window.
use ts_rs::TS; // rust-rules: derive-import
/// Backend-agnostic raw store payload shown by the existing `demo_store_raw` window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_raw/DemoStoreRawPayload.ts"
)]
pub(crate) struct DemoStoreRawPayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Safe backend connection descriptor.
pub(crate) connection_descriptor: std::string::String,
/// Logical raw-resource diagnostics.
pub(crate) resources: std::vec::Vec<crate::DemoStoreResourceSnapshot>,
}
/// Loads read-only level-1 raw resource diagnostics from the active store.
pub(crate) async fn load_demo_store_raw(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoStoreRawPayload, std::string::String> {
let store = match crate::shared_store(state.inner()).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let resources = match store.raw_resource_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let connection_descriptor = match crate::store_connection_descriptor(&store).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::DemoStoreRawPayload {
active_profile_name: state.active_profile().name.clone(),
connection_descriptor,
resources: crate::store_resource_snapshots(resources.as_slice()),
});
}

View File

@@ -1,7 +1,7 @@
// file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs // file: kb-app-demo-desktop/src/demo_store_replay_candidates.rs
// version: 13 // version: 16
//! Read-only SQL replay candidate browser commands. //! Backend-agnostic read-only replay candidate browser commands.
use ts_rs::TS; // rust-rules: derive-import use ts_rs::TS; // rust-rules: derive-import
@@ -10,9 +10,9 @@ use ts_rs::TS; // rust-rules: derive-import
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayKnownProgramOption.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayKnownProgramOption.ts"
)] )]
pub(crate) struct DemoSqlReplayKnownProgramOption { pub(crate) struct DemoStoreReplayKnownProgramOption {
/// Stable lower snake case program code. /// Stable lower snake case program code.
pub(crate) code: std::string::String, pub(crate) code: std::string::String,
/// Base58 Solana program identifier. /// Base58 Solana program identifier.
@@ -24,17 +24,17 @@ pub(crate) struct DemoSqlReplayKnownProgramOption {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayOptionsPayload.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayOptionsPayload.ts"
)] )]
pub(crate) struct DemoSqlReplayOptionsPayload { pub(crate) struct DemoStoreReplayOptionsPayload {
/// Active profile name. /// Active profile name.
pub(crate) active_profile_name: std::string::String, pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN. /// Safe backend connection descriptor.
pub(crate) masked_dsn: std::string::String, pub(crate) connection_descriptor: std::string::String,
/// Maximum rows accepted by one query. /// Maximum rows accepted by one query.
pub(crate) maximum_limit: u32, pub(crate) maximum_limit: u32,
/// Program identifiers enumerable from `ks_program_ids`. /// Program identifiers enumerable from `ks_program_ids`.
pub(crate) known_programs: std::vec::Vec<crate::DemoSqlReplayKnownProgramOption>, pub(crate) known_programs: std::vec::Vec<crate::DemoStoreReplayKnownProgramOption>,
} }
/// UI request for bounded transaction replay candidates. /// UI request for bounded transaction replay candidates.
@@ -42,9 +42,9 @@ pub(crate) struct DemoSqlReplayOptionsPayload {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRequest.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRequest.ts"
)] )]
pub(crate) struct DemoSqlReplayTransactionRequest { pub(crate) struct DemoStoreReplayTransactionRequest {
/// Optional partial signature search. /// Optional partial signature search.
pub(crate) signature_contains: std::option::Option<std::string::String>, pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional inclusive minimum slot. /// Optional inclusive minimum slot.
@@ -59,7 +59,7 @@ pub(crate) struct DemoSqlReplayTransactionRequest {
pub(crate) ledger_status: std::option::Option<std::string::String>, pub(crate) ledger_status: std::option::Option<std::string::String>,
/// Optional exact program id. /// Optional exact program id.
pub(crate) program_id: std::option::Option<std::string::String>, pub(crate) program_id: std::option::Option<std::string::String>,
/// Program scope code: any, outer, inner or logs. /// Program scope code: any, top_level, inner or logs.
pub(crate) program_scope: std::string::String, pub(crate) program_scope: std::string::String,
/// Optional entity kind: mint, owner or account_key. /// Optional entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::option::Option<std::string::String>, pub(crate) entity_kind: std::option::Option<std::string::String>,
@@ -76,9 +76,9 @@ pub(crate) struct DemoSqlReplayTransactionRequest {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRow.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRow.ts"
)] )]
pub(crate) struct DemoSqlReplayTransactionRow { pub(crate) struct DemoStoreReplayTransactionRow {
/// Canonical transaction signature. /// Canonical transaction signature.
pub(crate) signature: std::string::String, pub(crate) signature: std::string::String,
/// Transaction slot. /// Transaction slot.
@@ -100,13 +100,13 @@ pub(crate) struct DemoSqlReplayTransactionRow {
pub(crate) attempt_count: i32, pub(crate) attempt_count: i32,
/// Number of top-level instructions. /// Number of top-level instructions.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) outer_instruction_count: i64, pub(crate) top_level_instruction_count: i64,
/// Number of inner instructions. /// Number of inner instructions.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) inner_instruction_count: i64, pub(crate) inner_instruction_count: i64,
/// Number of distinct top-level programs. /// Number of distinct top-level programs.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) outer_program_count: i64, pub(crate) top_level_program_count: i64,
/// Number of distinct inner programs. /// Number of distinct inner programs.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) inner_program_count: i64, pub(crate) inner_program_count: i64,
@@ -119,9 +119,9 @@ pub(crate) struct DemoSqlReplayTransactionRow {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRequest.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRequest.ts"
)] )]
pub(crate) struct DemoSqlReplayProgramRequest { pub(crate) struct DemoStoreReplayProgramRequest {
/// Optional partial program id search. /// Optional partial program id search.
pub(crate) program_id_contains: std::option::Option<std::string::String>, pub(crate) program_id_contains: std::option::Option<std::string::String>,
/// Maximum returned rows. /// Maximum returned rows.
@@ -133,9 +133,9 @@ pub(crate) struct DemoSqlReplayProgramRequest {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRow.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRow.ts"
)] )]
pub(crate) struct DemoSqlReplayProgramRow { pub(crate) struct DemoStoreReplayProgramRow {
/// Optional stable code from `ks_program_ids`. /// Optional stable code from `ks_program_ids`.
pub(crate) program_code: std::option::Option<std::string::String>, pub(crate) program_code: std::option::Option<std::string::String>,
/// Program id. /// Program id.
@@ -145,7 +145,7 @@ pub(crate) struct DemoSqlReplayProgramRow {
pub(crate) transaction_count: i64, pub(crate) transaction_count: i64,
/// Number of top-level instruction occurrences. /// Number of top-level instruction occurrences.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) outer_instruction_count: i64, pub(crate) top_level_instruction_count: i64,
/// Number of inner instruction occurrences. /// Number of inner instruction occurrences.
#[ts(type = "number")] #[ts(type = "number")]
pub(crate) inner_instruction_count: i64, pub(crate) inner_instruction_count: i64,
@@ -165,9 +165,9 @@ pub(crate) struct DemoSqlReplayProgramRow {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRequest.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRequest.ts"
)] )]
pub(crate) struct DemoSqlReplayEntityRequest { pub(crate) struct DemoStoreReplayEntityRequest {
/// Entity kind: mint, owner or account_key. /// Entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::string::String, pub(crate) entity_kind: std::string::String,
/// Optional partial entity value search. /// Optional partial entity value search.
@@ -181,9 +181,9 @@ pub(crate) struct DemoSqlReplayEntityRequest {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts( #[ts(
export, export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRow.ts" export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRow.ts"
)] )]
pub(crate) struct DemoSqlReplayEntityRow { pub(crate) struct DemoStoreReplayEntityRow {
/// Stable entity kind code. /// Stable entity kind code.
pub(crate) entity_kind: std::string::String, pub(crate) entity_kind: std::string::String,
/// Mint, owner or account-key address. /// Mint, owner or account-key address.
@@ -203,7 +203,7 @@ pub(crate) struct DemoSqlReplayEntityRow {
} }
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`. /// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
pub(crate) async fn export_demo_sql_replay_csv( pub(crate) async fn export_demo_store_replay_csv(
file_name: std::string::String, file_name: std::string::String,
content: std::string::String, content: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> { ) -> std::result::Result<std::string::String, std::string::String> {
@@ -240,37 +240,40 @@ pub(crate) async fn export_demo_sql_replay_csv(
return std::result::Result::Ok(display_path); return std::result::Result::Ok(display_path);
} }
/// Loads static browser options from the active PostgreSQL profile. /// Loads static browser options from the active store.
pub(crate) async fn demo_sql_replay_options( pub(crate) async fn demo_store_replay_options(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> { ) -> std::result::Result<crate::DemoStoreReplayOptionsPayload, std::string::String> {
let profile = state.active_profile().clone(); let profile = state.active_profile().clone();
let options_result = crate::postgres_store_options_from_profile(&profile, false); let store = match crate::shared_store(state.inner()).await {
let options = match options_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let connection_descriptor = match crate::store_connection_descriptor(&store).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let mut known_programs = let mut known_programs =
std::vec::Vec::with_capacity(ks_program_ids::registered_program_ids().len()); std::vec::Vec::with_capacity(ks_program_ids::registered_program_ids().len());
for entry in ks_program_ids::registered_program_ids() { for entry in ks_program_ids::registered_program_ids() {
known_programs.push(crate::DemoSqlReplayKnownProgramOption { known_programs.push(crate::DemoStoreReplayKnownProgramOption {
code: entry.code().to_owned(), code: entry.code().to_owned(),
program_id: entry.program_id().to_owned(), program_id: entry.program_id().to_owned(),
}); });
} }
return std::result::Result::Ok(crate::DemoSqlReplayOptionsPayload { return std::result::Result::Ok(crate::DemoStoreReplayOptionsPayload {
active_profile_name: profile.name, active_profile_name: profile.name,
masked_dsn: options.masked_dsn(), connection_descriptor,
maximum_limit: ks_store::MAX_REPLAY_CANDIDATE_ROWS, maximum_limit: ks_store::MAX_REPLAY_CANDIDATE_ROWS,
known_programs, known_programs,
}); });
} }
/// Loads bounded transaction replay candidates from PostgreSQL. /// Loads bounded transaction replay candidates from the active store.
pub(crate) async fn load_demo_sql_replay_transactions( pub(crate) async fn load_demo_store_replay_transactions(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayTransactionRequest, request: crate::DemoStoreReplayTransactionRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayTransactionRow>, std::string::String> {
let program_scope_result = program_scope_from_code(request.program_scope.as_str()); let program_scope_result = program_scope_from_code(request.program_scope.as_str());
let program_scope = match program_scope_result { let program_scope = match program_scope_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -281,7 +284,7 @@ pub(crate) async fn load_demo_sql_replay_transactions(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let filter_result = ks_store::PostgresReplayTransactionFilter::new( let filter_result = ks_store::ReplayTransactionFilter::new(
request.signature_contains, request.signature_contains,
request.min_slot, request.min_slot,
request.max_slot, request.max_slot,
@@ -298,7 +301,7 @@ pub(crate) async fn load_demo_sql_replay_transactions(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -311,23 +314,23 @@ pub(crate) async fn load_demo_sql_replay_transactions(
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay transaction candidates"); tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay transaction candidates");
let mut output = std::vec::Vec::with_capacity(rows.len()); let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows { for row in rows {
output.push(transaction_row_from_pg(row)); output.push(transaction_row_from_store(row));
} }
return std::result::Result::Ok(output); return std::result::Result::Ok(output);
} }
/// Loads bounded program summaries from PostgreSQL. /// Loads bounded program summaries from the active store.
pub(crate) async fn load_demo_sql_replay_programs( pub(crate) async fn load_demo_store_replay_programs(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayProgramRequest, request: crate::DemoStoreReplayProgramRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayProgramRow>, std::string::String> {
let filter_result = let filter_result =
ks_store::PostgresReplayProgramFilter::new(request.program_id_contains, request.limit); ks_store::ReplayProgramFilter::new(request.program_id_contains, request.limit);
let filter = match filter_result { let filter = match filter_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -340,22 +343,22 @@ pub(crate) async fn load_demo_sql_replay_programs(
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay program summaries"); tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay program summaries");
let mut output = std::vec::Vec::with_capacity(rows.len()); let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows { for row in rows {
output.push(program_row_from_pg(row)); output.push(program_row_from_store(row));
} }
return std::result::Result::Ok(output); return std::result::Result::Ok(output);
} }
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables. /// Loads bounded mint, owner or account-key summaries from Core facts.
pub(crate) async fn load_demo_sql_replay_entities( pub(crate) async fn load_demo_store_replay_entities(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayEntityRequest, request: crate::DemoStoreReplayEntityRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayEntityRow>, std::string::String> {
let entity_kind_result = entity_kind_from_code(request.entity_kind.as_str()); let entity_kind_result = entity_kind_from_code(request.entity_kind.as_str());
let entity_kind = match entity_kind_result { let entity_kind = match entity_kind_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let filter_result = ks_store::PostgresReplayEntityFilter::new( let filter_result = ks_store::ReplayEntityFilter::new(
entity_kind, entity_kind,
request.entity_value_contains, request.entity_value_contains,
request.limit, request.limit,
@@ -364,7 +367,7 @@ pub(crate) async fn load_demo_sql_replay_entities(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let store_result = crate::connect_postgres_store(state.active_profile()).await; let store_result = crate::shared_store(state.inner()).await;
let store = match store_result { let store = match store_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -377,15 +380,15 @@ pub(crate) async fn load_demo_sql_replay_entities(
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay entity summaries"); tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay entity summaries");
let mut output = std::vec::Vec::with_capacity(rows.len()); let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows { for row in rows {
output.push(entity_row_from_pg(row)); output.push(entity_row_from_store(row));
} }
return std::result::Result::Ok(output); return std::result::Result::Ok(output);
} }
fn transaction_row_from_pg( fn transaction_row_from_store(
row: ks_store::PostgresReplayTransactionCandidate, row: ks_store::ReplayTransactionCandidate,
) -> crate::DemoSqlReplayTransactionRow { ) -> crate::DemoStoreReplayTransactionRow {
return crate::DemoSqlReplayTransactionRow { return crate::DemoStoreReplayTransactionRow {
signature: row.signature, signature: row.signature,
slot: row.slot, slot: row.slot,
raw_processing_state: row.raw_processing_state, raw_processing_state: row.raw_processing_state,
@@ -395,24 +398,22 @@ fn transaction_row_from_pg(
ledger_status: row.ledger_status, ledger_status: row.ledger_status,
processor_version: row.processor_version, processor_version: row.processor_version,
attempt_count: row.attempt_count, attempt_count: row.attempt_count,
outer_instruction_count: row.outer_instruction_count, top_level_instruction_count: row.top_level_instruction_count,
inner_instruction_count: row.inner_instruction_count, inner_instruction_count: row.inner_instruction_count,
outer_program_count: row.outer_program_count, top_level_program_count: row.top_level_program_count,
inner_program_count: row.inner_program_count, inner_program_count: row.inner_program_count,
updated_at: row.updated_at, updated_at: row.updated_at,
}; };
} }
fn program_row_from_pg( fn program_row_from_store(row: ks_store::ReplayProgramSummary) -> crate::DemoStoreReplayProgramRow {
row: ks_store::PostgresReplayProgramSummary,
) -> crate::DemoSqlReplayProgramRow {
let program_code = ks_program_ids::find_registered_program_id(&row.program_id) let program_code = ks_program_ids::find_registered_program_id(&row.program_id)
.map(|entry| return entry.code().to_owned()); .map(|entry| return entry.code().to_owned());
return crate::DemoSqlReplayProgramRow { return crate::DemoStoreReplayProgramRow {
program_code, program_code,
program_id: row.program_id, program_id: row.program_id,
transaction_count: row.transaction_count, transaction_count: row.transaction_count,
outer_instruction_count: row.outer_instruction_count, top_level_instruction_count: row.top_level_instruction_count,
inner_instruction_count: row.inner_instruction_count, inner_instruction_count: row.inner_instruction_count,
log_count: row.log_count, log_count: row.log_count,
min_slot: row.min_slot, min_slot: row.min_slot,
@@ -420,8 +421,8 @@ fn program_row_from_pg(
}; };
} }
fn entity_row_from_pg(row: ks_store::PostgresReplayEntitySummary) -> crate::DemoSqlReplayEntityRow { fn entity_row_from_store(row: ks_store::ReplayEntitySummary) -> crate::DemoStoreReplayEntityRow {
return crate::DemoSqlReplayEntityRow { return crate::DemoStoreReplayEntityRow {
entity_kind: row.entity_kind, entity_kind: row.entity_kind,
entity_value: row.entity_value, entity_value: row.entity_value,
transaction_count: row.transaction_count, transaction_count: row.transaction_count,
@@ -480,20 +481,19 @@ async fn available_csv_export_path(
fn program_scope_from_code( fn program_scope_from_code(
code: &str, code: &str,
) -> std::result::Result<ks_store::PostgresReplayProgramScope, std::string::String> { ) -> std::result::Result<ks_store::ReplayProgramScope, std::string::String> {
return match code { return match code {
"any" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Any), "any" => std::result::Result::Ok(ks_store::ReplayProgramScope::Any),
"outer" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Outer), "top_level" => std::result::Result::Ok(ks_store::ReplayProgramScope::TopLevel),
"inner" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Inner), "inner" => std::result::Result::Ok(ks_store::ReplayProgramScope::Inner),
"logs" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Logs), "logs" => std::result::Result::Ok(ks_store::ReplayProgramScope::Logs),
_ => std::result::Result::Err(format!("unsupported replay program scope: {code}")), _ => std::result::Result::Err(format!("unsupported replay program scope: {code}")),
}; };
} }
fn optional_entity_kind_from_code( fn optional_entity_kind_from_code(
code: std::option::Option<&str>, code: std::option::Option<&str>,
) -> std::result::Result<std::option::Option<ks_store::PostgresReplayEntityKind>, std::string::String> ) -> std::result::Result<std::option::Option<ks_store::ReplayEntityKind>, std::string::String> {
{
return match code { return match code {
std::option::Option::Some(value) => { std::option::Option::Some(value) => {
let result = entity_kind_from_code(value); let result = entity_kind_from_code(value);
@@ -510,11 +510,11 @@ fn optional_entity_kind_from_code(
fn entity_kind_from_code( fn entity_kind_from_code(
code: &str, code: &str,
) -> std::result::Result<ks_store::PostgresReplayEntityKind, std::string::String> { ) -> std::result::Result<ks_store::ReplayEntityKind, std::string::String> {
return match code { return match code {
"mint" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::Mint), "mint" => std::result::Result::Ok(ks_store::ReplayEntityKind::Mint),
"owner" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::Owner), "owner" => std::result::Result::Ok(ks_store::ReplayEntityKind::Owner),
"account_key" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::AccountKey), "account_key" => std::result::Result::Ok(ks_store::ReplayEntityKind::AccountKey),
_ => std::result::Result::Err(format!("unsupported replay entity kind: {code}")), _ => std::result::Result::Err(format!("unsupported replay entity kind: {code}")),
}; };
} }
@@ -524,7 +524,7 @@ mod tests {
#[test] #[test]
fn program_scope_parser_accepts_logs() { fn program_scope_parser_accepts_logs() {
let result = super::program_scope_from_code("logs"); let result = super::program_scope_from_code("logs");
assert_eq!(result, std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Logs)); assert_eq!(result, std::result::Result::Ok(ks_store::ReplayProgramScope::Logs));
} }
#[test] #[test]
@@ -551,16 +551,16 @@ mod tests {
#[test] #[test]
fn program_row_exposes_registered_code() { fn program_row_exposes_registered_code() {
let row = ks_store::PostgresReplayProgramSummary { let row = ks_store::ReplayProgramSummary {
program_id: ks_program_ids::SYSTEM_PROGRAM_ID.to_owned(), program_id: ks_program_ids::SYSTEM_PROGRAM_ID.to_owned(),
transaction_count: 1, transaction_count: 1,
outer_instruction_count: 1, top_level_instruction_count: 1,
inner_instruction_count: 0, inner_instruction_count: 0,
log_count: 1, log_count: 1,
min_slot: 1, min_slot: 1,
max_slot: 1, max_slot: 1,
}; };
let converted = super::program_row_from_pg(row); let converted = super::program_row_from_store(row);
assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system")); assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system"));
} }
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_ws.rs // file: kb-app-demo-desktop/src/demo_ws.rs
// version: 23 // version: 24
//! Standard Solana WebSocket demo commands backed by `ks_onchain_transport::WsSession`. //! Standard Solana WebSocket demo commands backed by `ks_onchain_transport::WsSession`.
@@ -1031,7 +1031,7 @@ mod tests {
<crate::DemoWsUnsubscribeRequest as TS>::decl(&config), <crate::DemoWsUnsubscribeRequest as TS>::decl(&config),
<crate::DemoBackfillProgressPayload as TS>::decl(&config), <crate::DemoBackfillProgressPayload as TS>::decl(&config),
<crate::DemoBackfillSummaryPayload as TS>::decl(&config), <crate::DemoBackfillSummaryPayload as TS>::decl(&config),
<crate::DemoSqlTableSnapshot as TS>::decl(&config), <crate::DemoStoreResourceSnapshot as TS>::decl(&config),
]; ];
for declaration in declarations { for declaration in declarations {
assert!(!declaration.contains("bigint")); assert!(!declaration.contains("bigint"));

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs // file: kb-app-demo-desktop/src/lib.rs
// version: 43 // version: 47
//! Tauri desktop demo application for `khadhroony-bot3`. //! Tauri desktop demo application for `khadhroony-bot3`.
@@ -24,11 +24,11 @@ mod demo_http;
mod demo_spl_ata; mod demo_spl_ata;
mod demo_spl_token; mod demo_spl_token;
mod demo_spl_token_2022; mod demo_spl_token_2022;
mod demo_sql_common; mod demo_store_common;
mod demo_sql_diag; mod demo_store_core;
mod demo_sql_pg_core; mod demo_store_diag;
mod demo_sql_pg_raw; mod demo_store_raw;
mod demo_sql_replay_candidates; mod demo_store_replay_candidates;
mod demo_transport; mod demo_transport;
mod demo_wallet; mod demo_wallet;
mod demo_ws; mod demo_ws;
@@ -147,7 +147,7 @@ pub(crate) use self::demo_decode_replay::demo_decode_replay_diagnostics;
pub(crate) use self::demo_decode_replay::demo_decode_replay_execute; pub(crate) use self::demo_decode_replay::demo_decode_replay_execute;
/// Returns available decoders and default replay bounds. /// Returns available decoders and default replay bounds.
pub(crate) use self::demo_decode_replay::demo_decode_replay_options; pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
/// Readiness report for one Devnet profile PostgreSQL store. /// Backend-agnostic readiness report for one Devnet profile store.
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload; pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
/// Restores the shared single-execution state flag when a Devnet run finishes. /// Restores the shared single-execution state flag when a Devnet run finishes.
pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard; pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard;
@@ -315,58 +315,60 @@ pub(crate) use self::demo_spl_token_2022::DemoSplToken2022FixturePayload;
pub(crate) use self::demo_spl_token_2022::demo_execution_spl_token_2022_execute; pub(crate) use self::demo_spl_token_2022::demo_execution_spl_token_2022_execute;
/// Loads public Token-2022 fixture values without reading private key bytes. /// Loads public Token-2022 fixture values without reading private key bytes.
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_fixture; pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_fixture;
/// UI-safe SQL table diagnostic snapshot. /// UI-safe logical store resource diagnostic snapshot.
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot; pub(crate) use self::demo_store_common::DemoStoreResourceSnapshot;
/// Connects to the configured PostgreSQL store.
pub(crate) use self::demo_sql_common::connect_postgres_store;
/// Formats diagnostic statuses. /// Formats diagnostic statuses.
pub(crate) use self::demo_sql_common::debug_status; pub(crate) use self::demo_store_common::debug_status;
/// Initializes the PostgreSQL schema during startup. /// Opens and verifies the configured store during startup.
pub(crate) use self::demo_sql_common::initialize_postgres_schema_for_startup; pub(crate) use self::demo_store_common::initialize_store_for_startup;
/// Builds PostgreSQL options from the active profile. /// Opens the configured backend-agnostic store.
pub(crate) use self::demo_sql_common::postgres_store_options_from_profile; pub(crate) use self::demo_store_common::open_store;
/// Converts one PostgreSQL table diagnostic. /// Clones the persistent application store.
pub(crate) use self::demo_sql_common::table_snapshot_from_pg; pub(crate) use self::demo_store_common::shared_store;
/// Converts PostgreSQL table diagnostics. /// Returns the safe connection descriptor exposed by the active store backend.
pub(crate) use self::demo_sql_common::table_snapshots_from_pg; pub(crate) use self::demo_store_common::store_connection_descriptor;
/// SQL diagnostic payload. /// Converts one logical store resource diagnostic.
pub(crate) use self::demo_sql_diag::DemoSqlDiagPayload; pub(crate) use self::demo_store_common::store_resource_snapshot;
/// Loads read-only SQL diagnostics from the active profile. /// Converts logical store resource diagnostics.
pub(crate) use self::demo_sql_diag::load_demo_sql_diag; pub(crate) use self::demo_store_common::store_resource_snapshots;
/// PostgreSQL core-table payload. /// Backend-agnostic Core store diagnostic payload.
pub(crate) use self::demo_sql_pg_core::DemoSqlPgCorePayload; pub(crate) use self::demo_store_core::DemoStoreCorePayload;
/// Loads read-only core table diagnostics from PostgreSQL. /// Loads read-only Core resource diagnostics from the active store.
pub(crate) use self::demo_sql_pg_core::load_demo_sql_pg_core; pub(crate) use self::demo_store_core::load_demo_store_core;
/// PostgreSQL raw-table payload. /// Backend-agnostic store diagnostic payload.
pub(crate) use self::demo_sql_pg_raw::DemoSqlPgRawPayload; pub(crate) use self::demo_store_diag::DemoStoreDiagPayload;
/// Loads read-only raw table diagnostics from PostgreSQL. /// Loads read-only Store diagnostics from the active profile.
pub(crate) use self::demo_sql_pg_raw::load_demo_sql_pg_raw; pub(crate) use self::demo_store_diag::load_demo_store_diag;
/// SQL replay entity request. /// Backend-agnostic raw store diagnostic payload.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRequest; pub(crate) use self::demo_store_raw::DemoStoreRawPayload;
/// SQL replay entity row. /// Loads read-only raw resource diagnostics from the active store.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRow; pub(crate) use self::demo_store_raw::load_demo_store_raw;
/// Store replay entity request.
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayEntityRequest;
/// Store replay entity row.
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayEntityRow;
/// Known program option. /// Known program option.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayKnownProgramOption; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayKnownProgramOption;
/// SQL replay options payload. /// Store replay options payload.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayOptionsPayload; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayOptionsPayload;
/// SQL replay program request. /// Store replay program request.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRequest; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayProgramRequest;
/// SQL replay program row. /// Store replay program row.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRow; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayProgramRow;
/// SQL replay transaction request. /// Store replay transaction request.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayTransactionRequest;
/// SQL replay transaction row. /// Store replay transaction row.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRow; pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayTransactionRow;
/// Loads static browser options from the active PostgreSQL profile. /// Loads static replay browser options from the active store profile.
pub(crate) use self::demo_sql_replay_candidates::demo_sql_replay_options; pub(crate) use self::demo_store_replay_candidates::demo_store_replay_options;
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`. /// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
pub(crate) use self::demo_sql_replay_candidates::export_demo_sql_replay_csv; pub(crate) use self::demo_store_replay_candidates::export_demo_store_replay_csv;
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables. /// Loads bounded mint, owner or account-key summaries from Core store resources.
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_entities; pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_entities;
/// Loads bounded program summaries from PostgreSQL. /// Loads bounded program summaries from the active store.
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_programs; pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_programs;
/// Loads bounded transaction replay candidates from PostgreSQL. /// Loads bounded transaction replay candidates from the active store.
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_transactions; pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_transactions;
/// UI-safe endpoint role shared by HTTP and WebSocket diagnostics. /// UI-safe endpoint role shared by HTTP and WebSocket diagnostics.
pub(crate) use self::demo_transport::DemoEndpointRolePayload; pub(crate) use self::demo_transport::DemoEndpointRolePayload;
/// Exact public SOL balance projected by the wallet demo. /// Exact public SOL balance projected by the wallet demo.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/splash.rs // file: kb-app-demo-desktop/src/splash.rs
// version: 5 // version: 6
//! Splash-window payloads and startup sequencing helpers. //! Splash-window payloads and startup sequencing helpers.
@@ -112,7 +112,7 @@ pub(crate) async fn splash_frontend_ready(
std::option::Option::Some("success"), std::option::Option::Some("success"),
std::option::Option::None, std::option::Option::None,
); );
crate::initialize_postgres_schema_for_startup(state, &window).await; crate::initialize_store_for_startup(state, &window).await;
crate::emit_splash_order( crate::emit_splash_order(
&window, &window,
"add_msg", "add_msg",

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs // file: kb-app-demo-desktop/src/tauri.rs
// version: 44 // version: 46
//! Tauri runtime assembly and private command wrappers. //! Tauri runtime assembly and private command wrappers.
@@ -71,18 +71,18 @@ pub fn run() -> ks_core::Result<()> {
demo_decode_replay_cancel, demo_decode_replay_cancel,
demo_decode_replay_diagnostics, demo_decode_replay_diagnostics,
demo_decode_replay_annotations, demo_decode_replay_annotations,
open_demo_sql_diag_window, open_demo_store_diag_window,
load_demo_sql_diag, load_demo_store_diag,
open_demo_sql_pg_raw_window, open_demo_store_raw_window,
load_demo_sql_pg_raw, load_demo_store_raw,
open_demo_sql_pg_core_window, open_demo_store_core_window,
load_demo_sql_pg_core, load_demo_store_core,
open_demo_sql_replay_candidates_window, open_demo_store_replay_candidates_window,
export_demo_sql_replay_csv, export_demo_store_replay_csv,
demo_sql_replay_options, demo_store_replay_options,
load_demo_sql_replay_transactions, load_demo_store_replay_transactions,
load_demo_sql_replay_programs, load_demo_store_replay_programs,
load_demo_sql_replay_entities, load_demo_store_replay_entities,
open_demo_execution_solana_core_window, open_demo_execution_solana_core_window,
demo_execution_devnet_prepare_profile, demo_execution_devnet_prepare_profile,
demo_execution_solana_core_options, demo_execution_solana_core_options,
@@ -518,111 +518,111 @@ async fn demo_decode_replay_annotations(
} }
#[tauri::command] #[tauri::command]
fn open_demo_sql_diag_window( fn open_demo_store_diag_window(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<(), std::string::String> {
return open_or_focus_demo_window( return open_or_focus_demo_window(
app_handle, app_handle,
"demo_sql_diag", "demo_store_diag",
"demo_sql_diag.html", "demo_store_diag.html",
"SQL diagnostics", "Store diagnostics",
); );
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_diag( async fn load_demo_store_diag(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> { ) -> std::result::Result<crate::DemoStoreDiagPayload, std::string::String> {
return crate::load_demo_sql_diag(state).await; return crate::load_demo_store_diag(state).await;
} }
#[tauri::command] #[tauri::command]
fn open_demo_sql_pg_raw_window( fn open_demo_store_raw_window(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<(), std::string::String> {
return open_or_focus_demo_window( return open_or_focus_demo_window(
app_handle, app_handle,
"demo_sql_pg_raw", "demo_store_raw",
"demo_sql_pg_raw.html", "demo_store_raw.html",
"PostgreSQL canonical acquisition", "Raw store",
); );
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_pg_raw( async fn load_demo_store_raw(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> { ) -> std::result::Result<crate::DemoStoreRawPayload, std::string::String> {
return crate::load_demo_sql_pg_raw(state).await; return crate::load_demo_store_raw(state).await;
} }
#[tauri::command] #[tauri::command]
fn open_demo_sql_pg_core_window( fn open_demo_store_core_window(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<(), std::string::String> {
return open_or_focus_demo_window( return open_or_focus_demo_window(
app_handle, app_handle,
"demo_sql_pg_core", "demo_store_core",
"demo_sql_pg_core.html", "demo_store_core.html",
"PostgreSQL core store", "Core store",
); );
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_pg_core( async fn load_demo_store_core(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> { ) -> std::result::Result<crate::DemoStoreCorePayload, std::string::String> {
return crate::load_demo_sql_pg_core(state).await; return crate::load_demo_store_core(state).await;
} }
#[tauri::command] #[tauri::command]
fn open_demo_sql_replay_candidates_window( fn open_demo_store_replay_candidates_window(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<(), std::string::String> {
return open_or_focus_demo_window( return open_or_focus_demo_window(
app_handle, app_handle,
"demo_sql_replay_candidates", "demo_store_replay_candidates",
"demo_sql_replay_candidates.html", "demo_store_replay_candidates.html",
"SQL replay candidates", "Store replay candidates",
); );
} }
#[tauri::command] #[tauri::command]
async fn export_demo_sql_replay_csv( async fn export_demo_store_replay_csv(
file_name: std::string::String, file_name: std::string::String,
content: std::string::String, content: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> { ) -> std::result::Result<std::string::String, std::string::String> {
return crate::export_demo_sql_replay_csv(file_name, content).await; return crate::export_demo_store_replay_csv(file_name, content).await;
} }
#[tauri::command] #[tauri::command]
async fn demo_sql_replay_options( async fn demo_store_replay_options(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> { ) -> std::result::Result<crate::DemoStoreReplayOptionsPayload, std::string::String> {
return crate::demo_sql_replay_options(state).await; return crate::demo_store_replay_options(state).await;
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_replay_transactions( async fn load_demo_store_replay_transactions(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayTransactionRequest, request: crate::DemoStoreReplayTransactionRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayTransactionRow>, std::string::String> {
return crate::load_demo_sql_replay_transactions(state, request).await; return crate::load_demo_store_replay_transactions(state, request).await;
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_replay_programs( async fn load_demo_store_replay_programs(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayProgramRequest, request: crate::DemoStoreReplayProgramRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayProgramRow>, std::string::String> {
return crate::load_demo_sql_replay_programs(state, request).await; return crate::load_demo_store_replay_programs(state, request).await;
} }
#[tauri::command] #[tauri::command]
async fn load_demo_sql_replay_entities( async fn load_demo_store_replay_entities(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayEntityRequest, request: crate::DemoStoreReplayEntityRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> { ) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayEntityRow>, std::string::String> {
return crate::load_demo_sql_replay_entities(state, request).await; return crate::load_demo_store_replay_entities(state, request).await;
} }
#[tauri::command] #[tauri::command]

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Khadhroony Bot3 Demo Desktop", "productName": "Khadhroony Bot3 Demo Desktop",
"version": "0.5.2", "version": "0.5.3",
"identifier": "com.sasedev.kb-app-demo-desktop", "identifier": "com.sasedev.kb-app-demo-desktop",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/vite.config.ts // file: kb-app-demo-desktop/vite.config.ts
// version: 10 // version: 11
import { defineConfig, normalizePath } from "vite"; import { defineConfig, normalizePath } from "vite";
import { NodePackageImporter } from "sass-embedded"; import { NodePackageImporter } from "sass-embedded";
@@ -30,10 +30,10 @@ export default defineConfig(() => ({
"demo_backfill": normalizePath(resolve(__dirname, 'frontend/demo_backfill.html')), "demo_backfill": normalizePath(resolve(__dirname, 'frontend/demo_backfill.html')),
"demo_core_extraction": normalizePath(resolve(__dirname, 'frontend/demo_core_extraction.html')), "demo_core_extraction": normalizePath(resolve(__dirname, 'frontend/demo_core_extraction.html')),
"demo_decode_replay": normalizePath(resolve(__dirname, 'frontend/demo_decode_replay.html')), "demo_decode_replay": normalizePath(resolve(__dirname, 'frontend/demo_decode_replay.html')),
"demo_sql_diag": normalizePath(resolve(__dirname, 'frontend/demo_sql_diag.html')), "demo_store_diag": normalizePath(resolve(__dirname, 'frontend/demo_store_diag.html')),
"demo_sql_pg_raw": normalizePath(resolve(__dirname, 'frontend/demo_sql_pg_raw.html')), "demo_store_raw": normalizePath(resolve(__dirname, 'frontend/demo_store_raw.html')),
"demo_sql_pg_core": normalizePath(resolve(__dirname, 'frontend/demo_sql_pg_core.html')), "demo_store_core": normalizePath(resolve(__dirname, 'frontend/demo_store_core.html')),
"demo_sql_replay_candidates": normalizePath(resolve(__dirname, 'frontend/demo_sql_replay_candidates.html')), "demo_store_replay_candidates": normalizePath(resolve(__dirname, 'frontend/demo_store_replay_candidates.html')),
"demo_execution_solana_core": normalizePath(resolve(__dirname, 'frontend/demo_execution_solana_core.html')), "demo_execution_solana_core": normalizePath(resolve(__dirname, 'frontend/demo_execution_solana_core.html')),
"demo_execution_spl": normalizePath(resolve(__dirname, 'frontend/demo_execution_spl.html')), "demo_execution_spl": normalizePath(resolve(__dirname, 'frontend/demo_execution_spl.html')),
"demo_execution_metatdata": normalizePath(resolve(__dirname, 'frontend/demo_execution_metadata.html')), "demo_execution_metatdata": normalizePath(resolve(__dirname, 'frontend/demo_execution_metadata.html')),

View File

@@ -1,8 +1,19 @@
<!-- file: ks-config/CHANGELOG.md --> <!-- file: ks-config/CHANGELOG.md -->
<!-- version: 24 --> <!-- version: 26 -->
# CHANGELOG — ks-config # CHANGELOG — ks-config
## `0.5.3-pre.002-delta-fix-001`
- aligne tous les contrats/types publics de `ks-config` sur la règle workspace imposant `crate::Item` depuis leur crate propriétaire, y compris dans leur module de déclaration et dans les tests ;
- aligne les appels aux fonctions publiques réexportées sur le crate-root et ajoute le canari `KH_CFG015` pour empêcher le retour d'une référence locale non qualifiée à un type public de configuration.
## `0.5.3-pre.002`
- remplace les contrats runtime `PostgresConfig`/`SqliteConfig` par `DatabaseConfig { enabled, backend, backend_options }` afin que `ks-config` ne connaisse plus les paramètres d'un moteur non sélectionné ;
- limite la validation store à la structure backend-agnostique et laisse `ks-store` interpréter/valider les options concrètes, sans modifier les conventions `KS_SECRET_*` ni la résolution des placeholders ;
- aligne `store.config.json`, son exemple, le schéma spécialisé, le schéma runtime et les fixtures de configuration sur ce contrat opaque.
## `0.5.2-pre.006-delta-fix-007` ## `0.5.2-pre.006-delta-fix-007`
- corrige le test de séparation des répertoires wallet afin quil résolve dabord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ; - corrige le test de séparation des répertoires wallet afin quil résolve dabord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/README.md --> <!-- file: ks-config/README.md -->
<!-- version: 13 --> <!-- version: 14 -->
# ks-config # ks-config
@@ -59,6 +59,8 @@ Les valeurs globales telles que `logs_directory` et `wallets_directory` ne sont
Depuis `0.5.1`, `ks-config` ne dépend plus de TS-RS et ne produit plus de bindings TypeScript. Ses contrats source/runtime peuvent contenir des secrets résolus : ils ne dérivent donc ni `serde::Serialize` ni `Debug`. Les applications propriétaires construisent leurs DTO publics et diagnostics bornés champ par champ. Depuis `0.5.1`, `ks-config` ne dépend plus de TS-RS et ne produit plus de bindings TypeScript. Ses contrats source/runtime peuvent contenir des secrets résolus : ils ne dérivent donc ni `serde::Serialize` ni `Debug`. Les applications propriétaires construisent leurs DTO publics et diagnostics bornés champ par champ.
Depuis `0.5.3-pre.002`, `DatabaseConfig` ne modélise plus PostgreSQL/SQLite : il conserve uniquement `enabled`, le code `backend` et un objet `backend_options` opaque. `ks-config` charge, compose et résout les placeholders ; la validation des paramètres du moteur et la création de la connexion appartiennent exclusivement à `ks-store`.
La section `application` d'une composition est volontairement opaque pour `ks-config`. Chaque binaire propriétaire définit son schéma et le valide avec `validate_json_value_against_schema`; le desktop utilise `config/schemas/kb-app-demo-desktop.application.config.schema.json`. La section `application` d'une composition est volontairement opaque pour `ks-config`. Chaque binaire propriétaire définit son schéma et le valide avec `validate_json_value_against_schema`; le desktop utilise `config/schemas/kb-app-demo-desktop.application.config.schema.json`.
## Documents ## Documents

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/USAGE.md --> <!-- file: ks-config/USAGE.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Utilisation de ks-config # Utilisation de ks-config
@@ -88,7 +88,7 @@ Les endpoints WebSocket source sélectionnent une classe `ws_endpoint_defaults`;
## Store ## Store
`store.config.json` possède les paramètres de backend et les profils PostgreSQL/SQLite. Les helpers principaux sont `read_store_json_file_with_environment`, `default_store_profile` et `store_profile`. `store.config.json` sélectionne un backend par profil et transporte ses paramètres sous `backend_options`. Ce bloc reste opaque pour `ks-config` : seuls le code backend non vide et la forme objet sont validés ici. Les helpers principaux sont `read_store_json_file_with_environment`, `default_store_profile` et `store_profile`; `ks-store` interprète ensuite les options du backend sélectionné et crée la connexion.
## Wallet ## Wallet

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/composition.rs // file: ks-config/src/composition.rs
// version: 3 // version: 5
//! Binary composition configuration and resolution into the shared runtime profile contract. //! Binary composition configuration and resolution into the shared runtime profile contract.
@@ -18,9 +18,9 @@ pub struct CompositionConfigDocument {
/// Active binary composition profile name. /// Active binary composition profile name.
pub active_profile: std::string::String, pub active_profile: std::string::String,
/// Shared configuration documents referenced by this composition. /// Shared configuration documents referenced by this composition.
pub sources: CompositionConfigSources, pub sources: crate::CompositionConfigSources,
/// Named binary composition profiles. /// Named binary composition profiles.
pub profiles: std::vec::Vec<CompositionProfileConfig>, pub profiles: std::vec::Vec<crate::CompositionProfileConfig>,
} }
/// Paths of shared configuration documents consumed by one binary composition. /// Paths of shared configuration documents consumed by one binary composition.
@@ -65,7 +65,7 @@ pub struct CompositionProfileConfig {
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct ComposedAppConfig { pub struct ComposedAppConfig {
/// Parsed binary composition document. /// Parsed binary composition document.
pub composition: CompositionConfigDocument, pub composition: crate::CompositionConfigDocument,
/// Resolved runtime application configuration. /// Resolved runtime application configuration.
pub app_config: crate::AppConfig, pub app_config: crate::AppConfig,
/// Resolved logging source path. /// Resolved logging source path.
@@ -101,7 +101,7 @@ pub fn composition_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw binary composition JSON against its embedded schema. /// Validates raw binary composition JSON against its embedded schema.
pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> { pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match composition_json_schema_value() { let schema = match crate::composition_json_schema_value() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -133,12 +133,12 @@ pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> {
} }
/// Parses and validates one binary composition document. /// Parses and validates one binary composition document.
pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConfigDocument> { pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<crate::CompositionConfigDocument> {
match validate_composition_json_schema(raw_json) { match crate::validate_composition_json_schema(raw_json) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let document = match serde_json::from_str::<CompositionConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::CompositionConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -147,7 +147,7 @@ pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConf
)); ));
}, },
}; };
return match validate_composition_document(&document) { return match crate::validate_composition_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -156,7 +156,7 @@ pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConf
/// Reads and parses one binary composition document from a filesystem path. /// Reads and parses one binary composition document from a filesystem path.
pub fn read_composition_json_file( pub fn read_composition_json_file(
path: &std::path::Path, path: &std::path::Path,
) -> ks_core::Result<CompositionConfigDocument> { ) -> ks_core::Result<crate::CompositionConfigDocument> {
let raw_json = match std::fs::read_to_string(path) { let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -166,14 +166,14 @@ pub fn read_composition_json_file(
)); ));
}, },
}; };
return parse_composition_json(&raw_json); return crate::parse_composition_json(&raw_json);
} }
/// Loads workspace environment values, resolves placeholders and parses one binary composition document. /// Loads workspace environment values, resolves placeholders and parses one binary composition document.
pub fn read_composition_json_file_with_environment( pub fn read_composition_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<CompositionConfigDocument> { ) -> ks_core::Result<crate::CompositionConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -187,14 +187,14 @@ pub fn read_composition_json_file_with_environment(
)); ));
}, },
}; };
return parse_composition_json(&crate::resolve_environment_placeholders(&raw_json)); return crate::parse_composition_json(&crate::resolve_environment_placeholders(&raw_json));
} }
/// Returns one named binary composition profile. /// Returns one named binary composition profile.
pub fn composition_profile<'a>( pub fn composition_profile<'a>(
document: &'a CompositionConfigDocument, document: &'a crate::CompositionConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a CompositionProfileConfig> { ) -> ks_core::Result<&'a crate::CompositionProfileConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -208,9 +208,9 @@ pub fn composition_profile<'a>(
/// Returns the active binary composition profile. /// Returns the active binary composition profile.
pub fn active_composition_profile( pub fn active_composition_profile(
document: &CompositionConfigDocument, document: &crate::CompositionConfigDocument,
) -> ks_core::Result<&CompositionProfileConfig> { ) -> ks_core::Result<&crate::CompositionProfileConfig> {
return composition_profile(document, document.active_profile.as_str()); return crate::composition_profile(document, document.active_profile.as_str());
} }
/// Resolves a source path relative to the workspace root unless already absolute. /// Resolves a source path relative to the workspace root unless already absolute.
@@ -238,7 +238,7 @@ pub fn resolve_profile_selection<'a>(
/// Resolves all shared source selections into the runtime application contract. /// Resolves all shared source selections into the runtime application contract.
pub fn compose_app_config( pub fn compose_app_config(
document: &CompositionConfigDocument, document: &crate::CompositionConfigDocument,
transport: &crate::TransportConfigDocument, transport: &crate::TransportConfigDocument,
listeners: &crate::ListenersConfigDocument, listeners: &crate::ListenersConfigDocument,
store: &crate::StoreConfigDocument, store: &crate::StoreConfigDocument,
@@ -247,23 +247,23 @@ pub fn compose_app_config(
) -> ks_core::Result<crate::AppConfig> { ) -> ks_core::Result<crate::AppConfig> {
let mut profiles = std::vec::Vec::<crate::ProfileConfig>::new(); let mut profiles = std::vec::Vec::<crate::ProfileConfig>::new();
for profile in &document.profiles { for profile in &document.profiles {
let transport_name = resolve_profile_selection( let transport_name = crate::resolve_profile_selection(
profile.transport_profile.as_deref(), profile.transport_profile.as_deref(),
transport.default_profile.as_str(), transport.default_profile.as_str(),
); );
let listeners_name = resolve_profile_selection( let listeners_name = crate::resolve_profile_selection(
profile.listeners_profile.as_deref(), profile.listeners_profile.as_deref(),
listeners.default_profile.as_str(), listeners.default_profile.as_str(),
); );
let store_name = resolve_profile_selection( let store_name = crate::resolve_profile_selection(
profile.store_profile.as_deref(), profile.store_profile.as_deref(),
store.default_profile.as_str(), store.default_profile.as_str(),
); );
let wallet_name = resolve_profile_selection( let wallet_name = crate::resolve_profile_selection(
profile.wallet_profile.as_deref(), profile.wallet_profile.as_deref(),
wallet.default_profile.as_str(), wallet.default_profile.as_str(),
); );
let execution_name = resolve_profile_selection( let execution_name = crate::resolve_profile_selection(
profile.execution_profile.as_deref(), profile.execution_profile.as_deref(),
execution.default_profile.as_str(), execution.default_profile.as_str(),
); );
@@ -313,24 +313,34 @@ pub fn compose_app_config(
pub fn read_composed_app_config_with_environment( pub fn read_composed_app_config_with_environment(
composition_path: &std::path::Path, composition_path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<ComposedAppConfig> { ) -> ks_core::Result<crate::ComposedAppConfig> {
let composition = let composition = match crate::read_composition_json_file_with_environment(
match read_composition_json_file_with_environment(composition_path, workspace_root) { composition_path,
workspace_root,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let logging_path = let logging_path = crate::resolve_composition_source_path(
resolve_composition_source_path(workspace_root, composition.sources.logging.as_str()); workspace_root,
let transport_path = composition.sources.logging.as_str(),
resolve_composition_source_path(workspace_root, composition.sources.transport.as_str()); );
let listeners_path = let transport_path = crate::resolve_composition_source_path(
resolve_composition_source_path(workspace_root, composition.sources.listeners.as_str()); workspace_root,
composition.sources.transport.as_str(),
);
let listeners_path = crate::resolve_composition_source_path(
workspace_root,
composition.sources.listeners.as_str(),
);
let store_path = let store_path =
resolve_composition_source_path(workspace_root, composition.sources.store.as_str()); crate::resolve_composition_source_path(workspace_root, composition.sources.store.as_str());
let wallet_path = let wallet_path =
resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str()); crate::resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str());
let execution_path = let execution_path = crate::resolve_composition_source_path(
resolve_composition_source_path(workspace_root, composition.sources.execution.as_str()); workspace_root,
composition.sources.execution.as_str(),
);
let transport = let transport =
match crate::read_transport_json_file_with_environment(&transport_path, workspace_root) { match crate::read_transport_json_file_with_environment(&transport_path, workspace_root) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -359,13 +369,18 @@ pub fn read_composed_app_config_with_environment(
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let app_config = let app_config = match crate::compose_app_config(
match compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution) &composition,
{ &transport,
&listeners,
&store,
&wallet,
&execution,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
return std::result::Result::Ok(ComposedAppConfig { return std::result::Result::Ok(crate::ComposedAppConfig {
composition, composition,
app_config, app_config,
logging_path, logging_path,
@@ -421,9 +436,9 @@ pub fn read_default_shared_app_config_with_environment(
} else { } else {
"shared_default".to_string() "shared_default".to_string()
}; };
let composition = CompositionConfigDocument { let composition = crate::CompositionConfigDocument {
active_profile: profile_name.clone(), active_profile: profile_name.clone(),
sources: CompositionConfigSources { sources: crate::CompositionConfigSources {
logging: DEFAULT_LOGGING_CONFIG_PATH.to_string(), logging: DEFAULT_LOGGING_CONFIG_PATH.to_string(),
transport: DEFAULT_TRANSPORT_CONFIG_PATH.to_string(), transport: DEFAULT_TRANSPORT_CONFIG_PATH.to_string(),
listeners: DEFAULT_LISTENERS_CONFIG_PATH.to_string(), listeners: DEFAULT_LISTENERS_CONFIG_PATH.to_string(),
@@ -431,7 +446,7 @@ pub fn read_default_shared_app_config_with_environment(
wallet: DEFAULT_WALLET_CONFIG_PATH.to_string(), wallet: DEFAULT_WALLET_CONFIG_PATH.to_string(),
execution: DEFAULT_EXECUTION_CONFIG_PATH.to_string(), execution: DEFAULT_EXECUTION_CONFIG_PATH.to_string(),
}, },
profiles: vec![CompositionProfileConfig { profiles: vec![crate::CompositionProfileConfig {
name: profile_name.clone(), name: profile_name.clone(),
application: std::option::Option::None, application: std::option::Option::None,
logging_profile: std::option::Option::None, logging_profile: std::option::Option::None,
@@ -442,11 +457,20 @@ pub fn read_default_shared_app_config_with_environment(
execution_profile: std::option::Option::None, execution_profile: std::option::Option::None,
}], }],
}; };
return compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution); return crate::compose_app_config(
&composition,
&transport,
&listeners,
&store,
&wallet,
&execution,
);
} }
/// Validates a typed binary composition document before referenced documents are loaded. /// Validates a typed binary composition document before referenced documents are loaded.
pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks_core::Result<()> { pub fn validate_composition_document(
document: &crate::CompositionConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.active_profile, "composition.active_profile") { match crate::require_non_empty(&document.active_profile, "composition.active_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -498,7 +522,7 @@ pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks
)); ));
} }
} }
return match active_composition_profile(document) { return match crate::active_composition_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -507,7 +531,7 @@ pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks
fn validate_logging_references_with_environment( fn validate_logging_references_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
composition: &CompositionConfigDocument, composition: &crate::CompositionConfigDocument,
) -> ks_core::Result<()> { ) -> ks_core::Result<()> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
@@ -566,7 +590,7 @@ fn validate_logging_references_with_environment(
} }
for profile in &composition.profiles { for profile in &composition.profiles {
let selected = let selected =
resolve_profile_selection(profile.logging_profile.as_deref(), default_profile); crate::resolve_profile_selection(profile.logging_profile.as_deref(), default_profile);
if !names.contains(selected) { if !names.contains(selected) {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
"composition_logging_profile_not_found", "composition_logging_profile_not_found",
@@ -586,17 +610,17 @@ mod tests {
#[test] #[test]
fn default_and_example_compositions_validate() { fn default_and_example_compositions_validate() {
assert!(super::parse_composition_json(DEFAULT_COMPOSITION).is_ok()); assert!(crate::parse_composition_json(DEFAULT_COMPOSITION).is_ok());
assert!(super::parse_composition_json(EXAMPLE_COMPOSITION).is_ok()); assert!(crate::parse_composition_json(EXAMPLE_COMPOSITION).is_ok());
} }
#[test] #[test]
fn local_devnet_composition_uses_shared_defaults() { fn local_devnet_composition_uses_shared_defaults() {
let composition = match super::parse_composition_json(DEFAULT_COMPOSITION) { let composition = match crate::parse_composition_json(DEFAULT_COMPOSITION) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default composition must parse: {error}"), std::result::Result::Err(error) => panic!("default composition must parse: {error}"),
}; };
let profile = match super::composition_profile(&composition, "local_devnet") { let profile = match crate::composition_profile(&composition, "local_devnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
panic!("local Devnet composition must resolve: {error}") panic!("local Devnet composition must resolve: {error}")

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/environment.rs // file: ks-config/src/environment.rs
// version: 6 // version: 8
//! Environment-file loading and configuration placeholder resolution. //! Environment-file loading and configuration placeholder resolution.
@@ -16,7 +16,7 @@ pub struct EnvironmentLoadReport {
/// fallback text declared with `${NAME:-fallback}` placeholders. /// fallback text declared with `${NAME:-fallback}` placeholders.
pub fn load_workspace_environment( pub fn load_workspace_environment(
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<EnvironmentLoadReport> { ) -> ks_core::Result<crate::EnvironmentLoadReport> {
let explicit_path = std::env::var("KS_ENV_FILE").ok(); let explicit_path = std::env::var("KS_ENV_FILE").ok();
let selected_path = match explicit_path { let selected_path = match explicit_path {
std::option::Option::Some(path) if !path.trim().is_empty() => { std::option::Option::Some(path) if !path.trim().is_empty() => {
@@ -26,10 +26,10 @@ pub fn load_workspace_environment(
_ => workspace_root.join(".env"), _ => workspace_root.join(".env"),
}; };
if !selected_path.exists() { if !selected_path.exists() {
return std::result::Result::Ok(EnvironmentLoadReport { loaded_path: None }); return std::result::Result::Ok(crate::EnvironmentLoadReport { loaded_path: None });
} }
return match dotenvy::from_path(&selected_path) { return match dotenvy::from_path(&selected_path) {
std::result::Result::Ok(()) => std::result::Result::Ok(EnvironmentLoadReport { std::result::Result::Ok(()) => std::result::Result::Ok(crate::EnvironmentLoadReport {
loaded_path: std::option::Option::Some(selected_path), loaded_path: std::option::Option::Some(selected_path),
}), }),
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new( std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
@@ -82,14 +82,14 @@ mod tests {
#[test] #[test]
fn fallback_is_used_when_variable_is_absent() { fn fallback_is_used_when_variable_is_absent() {
let resolved = let resolved =
super::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}"); crate::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}");
assert_eq!(resolved, "fallback"); assert_eq!(resolved, "fallback");
} }
#[test] #[test]
fn unresolved_required_placeholder_is_preserved() { fn unresolved_required_placeholder_is_preserved() {
let resolved = let resolved =
super::resolve_environment_placeholders("prefix-${KS_CONFIG_TEST_MISSING}-suffix"); crate::resolve_environment_placeholders("prefix-${KS_CONFIG_TEST_MISSING}-suffix");
assert_eq!(resolved, "prefix-${KS_CONFIG_TEST_MISSING}-suffix"); assert_eq!(resolved, "prefix-${KS_CONFIG_TEST_MISSING}-suffix");
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/execution.rs // file: ks-config/src/execution.rs
// version: 3 // version: 5
//! Independent execution policy configuration document loading and validation. //! Independent execution policy configuration document loading and validation.
@@ -12,7 +12,7 @@ pub struct ExecutionConfigDocument {
/// Default execution profile used when a composition does not override it. /// Default execution profile used when a composition does not override it.
pub default_profile: std::string::String, pub default_profile: std::string::String,
/// Named execution policy profiles. /// Named execution policy profiles.
pub profiles: std::vec::Vec<ExecutionProfileConfig>, pub profiles: std::vec::Vec<crate::ExecutionProfileConfig>,
} }
/// Named execution policy profile. /// Named execution policy profile.
@@ -31,7 +31,7 @@ pub fn execution_json_schema_text() -> &'static str {
} }
/// Parses and validates one execution configuration document. /// Parses and validates one execution configuration document.
pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDocument> { pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<crate::ExecutionConfigDocument> {
let schema = match serde_json::from_str::<serde_json::Value>(EXECUTION_JSON_SCHEMA) { let schema = match serde_json::from_str::<serde_json::Value>(EXECUTION_JSON_SCHEMA) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -65,7 +65,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
"execution configuration does not satisfy its schema", "execution configuration does not satisfy its schema",
)); ));
} }
let document = match serde_json::from_str::<ExecutionConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::ExecutionConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -74,7 +74,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
)); ));
}, },
}; };
return match validate_execution_document(&document) { return match crate::validate_execution_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -84,7 +84,7 @@ pub fn parse_execution_json(raw_json: &str) -> ks_core::Result<ExecutionConfigDo
pub fn read_execution_json_file_with_environment( pub fn read_execution_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<ExecutionConfigDocument> { ) -> ks_core::Result<crate::ExecutionConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -98,14 +98,14 @@ pub fn read_execution_json_file_with_environment(
)); ));
}, },
}; };
return parse_execution_json(&crate::resolve_environment_placeholders(&raw_json)); return crate::parse_execution_json(&crate::resolve_environment_placeholders(&raw_json));
} }
/// Returns one named execution profile. /// Returns one named execution profile.
pub fn execution_profile<'a>( pub fn execution_profile<'a>(
document: &'a ExecutionConfigDocument, document: &'a crate::ExecutionConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a ExecutionProfileConfig> { ) -> ks_core::Result<&'a crate::ExecutionProfileConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -119,13 +119,15 @@ pub fn execution_profile<'a>(
/// Returns the default execution profile. /// Returns the default execution profile.
pub fn default_execution_profile( pub fn default_execution_profile(
document: &ExecutionConfigDocument, document: &crate::ExecutionConfigDocument,
) -> ks_core::Result<&ExecutionProfileConfig> { ) -> ks_core::Result<&crate::ExecutionProfileConfig> {
return execution_profile(document, document.default_profile.as_str()); return crate::execution_profile(document, document.default_profile.as_str());
} }
/// Validates one typed execution configuration document. /// Validates one typed execution configuration document.
pub fn validate_execution_document(document: &ExecutionConfigDocument) -> ks_core::Result<()> { pub fn validate_execution_document(
document: &crate::ExecutionConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "execution.default_profile") { match crate::require_non_empty(&document.default_profile, "execution.default_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -153,7 +155,7 @@ pub fn validate_execution_document(document: &ExecutionConfigDocument) -> ks_cor
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match default_execution_profile(document) { return match crate::default_execution_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -167,7 +169,7 @@ mod tests {
#[test] #[test]
fn default_and_example_execution_configs_validate() { fn default_and_example_execution_configs_validate() {
assert!(super::parse_execution_json(DEFAULT_EXECUTION).is_ok()); assert!(crate::parse_execution_json(DEFAULT_EXECUTION).is_ok());
assert!(super::parse_execution_json(EXAMPLE_EXECUTION).is_ok()); assert!(crate::parse_execution_json(EXAMPLE_EXECUTION).is_ok());
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/lib.rs // file: ks-config/src/lib.rs
// version: 10 // version: 11
//! Khadhroony Solana shared configuration contracts and composition helpers. //! Khadhroony Solana shared configuration contracts and composition helpers.
#![warn(missing_docs)] #![warn(missing_docs)]
@@ -125,16 +125,12 @@ pub use self::settings::HttpEndpointConfig;
pub use self::settings::ListenerConfig; pub use self::settings::ListenerConfig;
/// Exposes the log listener configuration type. /// Exposes the log listener configuration type.
pub use self::settings::LogListenerConfig; pub use self::settings::LogListenerConfig;
/// Exposes the PostgreSQL configuration type.
pub use self::settings::PostgresConfig;
/// Exposes the profile configuration type. /// Exposes the profile configuration type.
pub use self::settings::ProfileConfig; pub use self::settings::ProfileConfig;
/// Exposes the program listener configuration type. /// Exposes the program listener configuration type.
pub use self::settings::ProgramListenerConfig; pub use self::settings::ProgramListenerConfig;
/// Exposes the Solana configuration type. /// Exposes the Solana configuration type.
pub use self::settings::SolanaConfig; pub use self::settings::SolanaConfig;
/// Exposes the SQLite configuration type.
pub use self::settings::SqliteConfig;
/// Exposes the wallet configuration type. /// Exposes the wallet configuration type.
pub use self::settings::WalletConfig; pub use self::settings::WalletConfig;
/// Exposes the WebSocket endpoint configuration type. /// Exposes the WebSocket endpoint configuration type.

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/listeners.rs // file: ks-config/src/listeners.rs
// version: 3 // version: 5
//! Independent Solana listener configuration document loading and validation. //! Independent Solana listener configuration document loading and validation.
@@ -12,7 +12,7 @@ pub struct ListenersConfigDocument {
/// Default listener profile used by consumers that do not provide an explicit selection. /// Default listener profile used by consumers that do not provide an explicit selection.
pub default_profile: std::string::String, pub default_profile: std::string::String,
/// Named listener profiles available in this document. /// Named listener profiles available in this document.
pub profiles: std::vec::Vec<ListenersProfileConfig>, pub profiles: std::vec::Vec<crate::ListenersProfileConfig>,
} }
/// Named listener profile. /// Named listener profile.
@@ -51,7 +51,7 @@ pub fn listeners_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw listener JSON against the embedded listener schema. /// Validates raw listener JSON against the embedded listener schema.
pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> { pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match listeners_json_schema_value() { let schema = match crate::listeners_json_schema_value() {
std::result::Result::Ok(schema) => schema, std::result::Result::Ok(schema) => schema,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -83,12 +83,12 @@ pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> {
} }
/// Parses and validates one independent listener configuration document. /// Parses and validates one independent listener configuration document.
pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDocument> { pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<crate::ListenersConfigDocument> {
match validate_listeners_json_schema(raw_json) { match crate::validate_listeners_json_schema(raw_json) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let document = match serde_json::from_str::<ListenersConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::ListenersConfigDocument>(raw_json) {
std::result::Result::Ok(document) => document, std::result::Result::Ok(document) => document,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -97,7 +97,7 @@ pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDo
)); ));
}, },
}; };
return match validate_listeners_document(&document) { return match crate::validate_listeners_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -106,7 +106,7 @@ pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDo
/// Reads and parses one listener configuration document from a filesystem path. /// Reads and parses one listener configuration document from a filesystem path.
pub fn read_listeners_json_file( pub fn read_listeners_json_file(
path: &std::path::Path, path: &std::path::Path,
) -> ks_core::Result<ListenersConfigDocument> { ) -> ks_core::Result<crate::ListenersConfigDocument> {
let raw_json = match std::fs::read_to_string(path) { let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(content) => content, std::result::Result::Ok(content) => content,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -116,14 +116,14 @@ pub fn read_listeners_json_file(
)); ));
}, },
}; };
return parse_listeners_json(&raw_json); return crate::parse_listeners_json(&raw_json);
} }
/// Loads workspace environment values, resolves placeholders and parses listener configuration. /// Loads workspace environment values, resolves placeholders and parses listener configuration.
pub fn read_listeners_json_file_with_environment( pub fn read_listeners_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<ListenersConfigDocument> { ) -> ks_core::Result<crate::ListenersConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -138,14 +138,14 @@ pub fn read_listeners_json_file_with_environment(
}, },
}; };
let resolved = crate::resolve_environment_placeholders(&raw_json); let resolved = crate::resolve_environment_placeholders(&raw_json);
return parse_listeners_json(&resolved); return crate::parse_listeners_json(&resolved);
} }
/// Returns one named listener profile. /// Returns one named listener profile.
pub fn listeners_profile<'a>( pub fn listeners_profile<'a>(
document: &'a ListenersConfigDocument, document: &'a crate::ListenersConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a ListenersProfileConfig> { ) -> ks_core::Result<&'a crate::ListenersProfileConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -159,13 +159,13 @@ pub fn listeners_profile<'a>(
/// Returns the default listener profile declared by the document. /// Returns the default listener profile declared by the document.
pub fn default_listeners_profile( pub fn default_listeners_profile(
document: &ListenersConfigDocument, document: &crate::ListenersConfigDocument,
) -> ks_core::Result<&ListenersProfileConfig> { ) -> ks_core::Result<&crate::ListenersProfileConfig> {
return listeners_profile(document, document.default_profile.as_str()); return crate::listeners_profile(document, document.default_profile.as_str());
} }
/// Converts one named listener profile into the resolved runtime listener contract. /// Converts one named listener profile into the resolved runtime listener contract.
pub fn resolved_listener_config(profile: &ListenersProfileConfig) -> crate::ListenerConfig { pub fn resolved_listener_config(profile: &crate::ListenersProfileConfig) -> crate::ListenerConfig {
return crate::ListenerConfig { return crate::ListenerConfig {
enabled: profile.enabled, enabled: profile.enabled,
default_commitment: profile.default_commitment.clone(), default_commitment: profile.default_commitment.clone(),
@@ -176,7 +176,9 @@ pub fn resolved_listener_config(profile: &ListenersProfileConfig) -> crate::List
} }
/// Validates a typed listener configuration document. /// Validates a typed listener configuration document.
pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_core::Result<()> { pub fn validate_listeners_document(
document: &crate::ListenersConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "default_profile") { match crate::require_non_empty(&document.default_profile, "default_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -199,13 +201,13 @@ pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_cor
profile.name.clone(), profile.name.clone(),
)); ));
} }
let resolved = resolved_listener_config(profile); let resolved = crate::resolved_listener_config(profile);
match crate::validate_listeners(&resolved) { match crate::validate_listeners(&resolved) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match default_listeners_profile(document) { return match crate::default_listeners_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -219,7 +221,7 @@ mod tests {
#[test] #[test]
fn default_and_example_listener_configs_validate() { fn default_and_example_listener_configs_validate() {
assert!(super::parse_listeners_json(DEFAULT_LISTENERS).is_ok()); assert!(crate::parse_listeners_json(DEFAULT_LISTENERS).is_ok());
assert!(super::parse_listeners_json(EXAMPLE_LISTENERS).is_ok()); assert!(crate::parse_listeners_json(EXAMPLE_LISTENERS).is_ok());
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/schema.rs // file: ks-config/src/schema.rs
// version: 1 // version: 2
//! Generic JSON Schema validation helpers for binary-owned configuration fragments. //! Generic JSON Schema validation helpers for binary-owned configuration fragments.
@@ -42,7 +42,7 @@ mod tests {
fn validation_error_never_echoes_instance_secret() { fn validation_error_never_echoes_instance_secret() {
let schema = r#"{"type":"object","additionalProperties":false,"required":["value"],"properties":{"value":{"type":"integer"}}}"#; let schema = r#"{"type":"object","additionalProperties":false,"required":["value"],"properties":{"value":{"type":"integer"}}}"#;
let instance = serde_json::json!({"value": "SECRET-CANARY"}); let instance = serde_json::json!({"value": "SECRET-CANARY"});
let result = super::validate_json_value_against_schema( let result = crate::validate_json_value_against_schema(
schema, schema,
&instance, &instance,
"test_fragment_validation_failed", "test_fragment_validation_failed",

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/sensitivity.rs // file: ks-config/src/sensitivity.rs
// version: 1 // version: 3
//! Application environment-variable sensitivity classification. //! Application environment-variable sensitivity classification.
@@ -20,7 +20,7 @@ pub enum EnvironmentValueSensitivity {
/// one `KS_SECRET_*` placeholder is classified as secret even when the surrounding URL is public. /// one `KS_SECRET_*` placeholder is classified as secret even when the surrounding URL is public.
pub fn classify_environment_template( pub fn classify_environment_template(
raw: &str, raw: &str,
) -> std::option::Option<EnvironmentValueSensitivity> { ) -> std::option::Option<crate::EnvironmentValueSensitivity> {
let bytes = raw.as_bytes(); let bytes = raw.as_bytes();
let mut sensitivity = std::option::Option::None; let mut sensitivity = std::option::Option::None;
let mut index = 0_usize; let mut index = 0_usize;
@@ -36,7 +36,8 @@ pub fn classify_environment_template(
std::option::Option::Some((name, _)) => name, std::option::Option::Some((name, _)) => name,
std::option::Option::None => expression, std::option::Option::None => expression,
}; };
sensitivity = merge_sensitivity(sensitivity, classify_environment_variable(name)); sensitivity =
merge_sensitivity(sensitivity, crate::classify_environment_variable(name));
index = end + 1; index = end + 1;
continue; continue;
} }
@@ -49,15 +50,15 @@ pub fn classify_environment_template(
/// Classifies a Khadhroony-owned environment variable by its namespace. /// Classifies a Khadhroony-owned environment variable by its namespace.
pub fn classify_environment_variable( pub fn classify_environment_variable(
name: &str, name: &str,
) -> std::option::Option<EnvironmentValueSensitivity> { ) -> std::option::Option<crate::EnvironmentValueSensitivity> {
if name.starts_with("KS_SECRET_") || name.starts_with("KB_SECRET_") { if name.starts_with("KS_SECRET_") || name.starts_with("KB_SECRET_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Secret); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret);
} }
if name.starts_with("KS_PUBLIC_") || name.starts_with("KB_PUBLIC_") { if name.starts_with("KS_PUBLIC_") || name.starts_with("KB_PUBLIC_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Public); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Public);
} }
if name.starts_with("KS_") || name.starts_with("KB_") { if name.starts_with("KS_") || name.starts_with("KB_") {
return std::option::Option::Some(EnvironmentValueSensitivity::Internal); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal);
} }
return std::option::Option::None; return std::option::Option::None;
} }
@@ -67,14 +68,14 @@ pub fn diagnostic_environment_value(
name: &str, name: &str,
value: &str, value: &str,
) -> std::option::Option<std::string::String> { ) -> std::option::Option<std::string::String> {
return match classify_environment_variable(name) { return match crate::classify_environment_variable(name) {
std::option::Option::Some(EnvironmentValueSensitivity::Secret) => { std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret) => {
std::option::Option::Some("<redacted>".to_string()) std::option::Option::Some("<redacted>".to_string())
}, },
std::option::Option::Some(EnvironmentValueSensitivity::Internal) => { std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal) => {
std::option::Option::Some("<configured>".to_string()) std::option::Option::Some("<configured>".to_string())
}, },
std::option::Option::Some(EnvironmentValueSensitivity::Public) => { std::option::Option::Some(crate::EnvironmentValueSensitivity::Public) => {
std::option::Option::Some(value.to_string()) std::option::Option::Some(value.to_string())
}, },
std::option::Option::None => std::option::Option::None, std::option::Option::None => std::option::Option::None,
@@ -82,23 +83,23 @@ pub fn diagnostic_environment_value(
} }
fn merge_sensitivity( fn merge_sensitivity(
left: std::option::Option<EnvironmentValueSensitivity>, left: std::option::Option<crate::EnvironmentValueSensitivity>,
right: std::option::Option<EnvironmentValueSensitivity>, right: std::option::Option<crate::EnvironmentValueSensitivity>,
) -> std::option::Option<EnvironmentValueSensitivity> { ) -> std::option::Option<crate::EnvironmentValueSensitivity> {
if left == std::option::Option::Some(EnvironmentValueSensitivity::Secret) if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Secret) || right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret)
{ {
return std::option::Option::Some(EnvironmentValueSensitivity::Secret); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret);
} }
if left == std::option::Option::Some(EnvironmentValueSensitivity::Internal) if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Internal) || right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal)
{ {
return std::option::Option::Some(EnvironmentValueSensitivity::Internal); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Internal);
} }
if left == std::option::Option::Some(EnvironmentValueSensitivity::Public) if left == std::option::Option::Some(crate::EnvironmentValueSensitivity::Public)
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Public) || right == std::option::Option::Some(crate::EnvironmentValueSensitivity::Public)
{ {
return std::option::Option::Some(EnvironmentValueSensitivity::Public); return std::option::Option::Some(crate::EnvironmentValueSensitivity::Public);
} }
return std::option::Option::None; return std::option::Option::None;
} }
@@ -108,19 +109,19 @@ mod tests {
#[test] #[test]
fn composed_values_inherit_the_strongest_placeholder_sensitivity() { fn composed_values_inherit_the_strongest_placeholder_sensitivity() {
assert_eq!( assert_eq!(
super::classify_environment_template( crate::classify_environment_template(
"https://rpc.invalid/?api-key=${KS_SECRET_HELIUS_API_KEY}&mint=${KS_PUBLIC_DEVNET_TOKEN_2022_MINT}" "https://rpc.invalid/?api-key=${KS_SECRET_HELIUS_API_KEY}&mint=${KS_PUBLIC_DEVNET_TOKEN_2022_MINT}"
), ),
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret) std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
); );
assert_eq!( assert_eq!(
super::classify_environment_template( crate::classify_environment_template(
"${KS_LOGS_DIRECTORY:-logs}/${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}" "${KS_LOGS_DIRECTORY:-logs}/${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"
), ),
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal) std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
); );
assert_eq!( assert_eq!(
super::classify_environment_template("${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"), crate::classify_environment_template("${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Public) std::option::Option::Some(super::EnvironmentValueSensitivity::Public)
); );
} }
@@ -128,15 +129,15 @@ mod tests {
#[test] #[test]
fn generic_diagnostic_never_reveals_secret_or_internal_values() { fn generic_diagnostic_never_reveals_secret_or_internal_values() {
assert_eq!( assert_eq!(
super::diagnostic_environment_value("KS_SECRET_HELIUS_API_KEY", "SECRET-CANARY"), crate::diagnostic_environment_value("KS_SECRET_HELIUS_API_KEY", "SECRET-CANARY"),
std::option::Option::Some("<redacted>".to_string()) std::option::Option::Some("<redacted>".to_string())
); );
assert_eq!( assert_eq!(
super::diagnostic_environment_value("KS_LOGS_DIRECTORY", "/private/logs"), crate::diagnostic_environment_value("KS_LOGS_DIRECTORY", "/private/logs"),
std::option::Option::Some("<configured>".to_string()) std::option::Option::Some("<configured>".to_string())
); );
assert_eq!( assert_eq!(
super::diagnostic_environment_value("KS_PUBLIC_SPL_TOKEN_PROGRAM_ID", "Token111"), crate::diagnostic_environment_value("KS_PUBLIC_SPL_TOKEN_PROGRAM_ID", "Token111"),
std::option::Option::Some("Token111".to_string()) std::option::Option::Some("Token111".to_string())
); );
} }
@@ -144,17 +145,17 @@ mod tests {
#[test] #[test]
fn namespaces_map_to_secret_internal_and_public() { fn namespaces_map_to_secret_internal_and_public() {
assert_eq!( assert_eq!(
super::classify_environment_variable("KS_SECRET_HELIUS_API_KEY"), crate::classify_environment_variable("KS_SECRET_HELIUS_API_KEY"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret) std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
); );
assert_eq!( assert_eq!(
super::classify_environment_variable("KS_LOGS_DIRECTORY"), crate::classify_environment_variable("KS_LOGS_DIRECTORY"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal) std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
); );
assert_eq!( assert_eq!(
super::classify_environment_variable("KB_PUBLIC_DEMO_VALUE"), crate::classify_environment_variable("KB_PUBLIC_DEMO_VALUE"),
std::option::Option::Some(super::EnvironmentValueSensitivity::Public) std::option::Option::Some(super::EnvironmentValueSensitivity::Public)
); );
assert_eq!(super::classify_environment_variable("HOME"), std::option::Option::None); assert_eq!(crate::classify_environment_variable("HOME"), std::option::Option::None);
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/settings.rs // file: ks-config/src/settings.rs
// version: 25 // version: 29
//! Resolved runtime configuration models retained during the `0.5.1` source-document split. //! Resolved runtime configuration models retained during the `0.5.1` source-document split.
@@ -12,7 +12,7 @@ pub struct AppConfig {
/// Active profile name. /// Active profile name.
pub active_profile: std::string::String, pub active_profile: std::string::String,
/// Named profiles reconstructed from the active binary composition and shared documents. /// Named profiles reconstructed from the active binary composition and shared documents.
pub profiles: std::vec::Vec<ProfileConfig>, pub profiles: std::vec::Vec<crate::ProfileConfig>,
} }
/// Resolved runtime profile selected by the root active profile name. /// Resolved runtime profile selected by the root active profile name.
@@ -21,67 +21,35 @@ pub struct ProfileConfig {
/// Profile code. /// Profile code.
pub name: std::string::String, pub name: std::string::String,
/// Database configuration. /// Database configuration.
pub database: DatabaseConfig, pub database: crate::DatabaseConfig,
/// Solana endpoint and listener configuration. /// Solana endpoint and listener configuration.
pub solana: SolanaConfig, pub solana: crate::SolanaConfig,
/// Wallet configuration. /// Wallet configuration.
pub wallet: WalletConfig, pub wallet: crate::WalletConfig,
/// Execution safety configuration. /// Execution safety configuration.
pub execution: ExecutionConfig, pub execution: crate::ExecutionConfig,
} }
/// Database backend configuration. /// Backend-agnostic store configuration resolved by `ks-config`.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)] #[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct DatabaseConfig { pub struct DatabaseConfig {
/// Enables database storage. /// Enables persistent storage for this profile.
pub enabled: bool, pub enabled: bool,
/// Selected backend code. /// Selected backend code interpreted by `ks-store`.
pub backend: std::string::String, pub backend: std::string::String,
/// PostgreSQL configuration. /// Backend-specific options retained as an opaque JSON object until `ks-store` opens the store.
pub postgres: PostgresConfig, pub backend_options: serde_json::Value,
/// SQLite configuration for tests or imports.
pub sqlite: SqliteConfig,
}
/// PostgreSQL backend configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct PostgresConfig {
/// PostgreSQL URL or local placeholder.
pub url: std::string::String,
/// Maximum connection count.
pub max_connections: u32,
/// Connection timeout in milliseconds.
pub connect_timeout_ms: u64,
/// Enables schema initialization at startup.
pub auto_initialize_schema: bool,
}
/// SQLite backend configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct SqliteConfig {
/// SQLite database path.
pub path: std::string::String,
/// Creates the file when missing.
pub create_if_missing: bool,
/// Busy timeout in milliseconds.
pub busy_timeout_ms: u64,
/// Maximum connection count.
pub max_connections: u32,
/// Enables schema initialization at startup.
pub auto_initialize_schema: bool,
/// Enables WAL mode.
pub use_wal: bool,
} }
/// Solana endpoints and listener configuration. /// Solana endpoints and listener configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)] #[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct SolanaConfig { pub struct SolanaConfig {
/// HTTP JSON-RPC endpoints. /// HTTP JSON-RPC endpoints.
pub http_endpoints: std::vec::Vec<HttpEndpointConfig>, pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
/// Standard Solana WebSocket endpoints. /// Standard Solana WebSocket endpoints.
pub ws_endpoints: std::vec::Vec<WsEndpointConfig>, pub ws_endpoints: std::vec::Vec<crate::WsEndpointConfig>,
/// Listener declarations. /// Listener declarations.
pub listeners: ListenerConfig, pub listeners: crate::ListenerConfig,
} }
/// HTTP JSON-RPC endpoint configuration. /// HTTP JSON-RPC endpoint configuration.
@@ -104,7 +72,7 @@ pub struct HttpEndpointConfig {
/// Maximum idle connections per host. /// Maximum idle connections per host.
pub max_idle_connections_per_host: u32, pub max_idle_connections_per_host: u32,
/// Role-specific limits for this URL. /// Role-specific limits for this URL.
pub roles: std::vec::Vec<EndpointRoleConfig>, pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
} }
/// Standard Solana WebSocket endpoint configuration. /// Standard Solana WebSocket endpoint configuration.
@@ -133,7 +101,7 @@ pub struct WsEndpointConfig {
/// Enables automatic reconnect for this endpoint. /// Enables automatic reconnect for this endpoint.
pub auto_reconnect: bool, pub auto_reconnect: bool,
/// Role-specific limits for this URL. /// Role-specific limits for this URL.
pub roles: std::vec::Vec<EndpointRoleConfig>, pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
} }
/// Role-specific endpoint limits. /// Role-specific endpoint limits.
@@ -167,11 +135,11 @@ pub struct ListenerConfig {
/// Default commitment used by listener subscriptions. /// Default commitment used by listener subscriptions.
pub default_commitment: std::string::String, pub default_commitment: std::string::String,
/// Log listeners filtered by program mentions. /// Log listeners filtered by program mentions.
pub log_listeners: std::vec::Vec<LogListenerConfig>, pub log_listeners: std::vec::Vec<crate::LogListenerConfig>,
/// Program account listeners. /// Program account listeners.
pub program_listeners: std::vec::Vec<ProgramListenerConfig>, pub program_listeners: std::vec::Vec<crate::ProgramListenerConfig>,
/// Account listeners. /// Account listeners.
pub account_listeners: std::vec::Vec<AccountListenerConfig>, pub account_listeners: std::vec::Vec<crate::AccountListenerConfig>,
} }
/// Log listener filtered by a program identifier mention. /// Log listener filtered by a program identifier mention.
@@ -298,7 +266,7 @@ pub fn config_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates a raw resolved runtime JSON string against the embedded compatibility schema. /// Validates a raw resolved runtime JSON string against the embedded compatibility schema.
pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> { pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match config_json_schema_value() { let schema = match crate::config_json_schema_value() {
std::result::Result::Ok(schema) => schema, std::result::Result::Ok(schema) => schema,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -331,12 +299,12 @@ pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> {
} }
/// Parses the resolved runtime application contract from a JSON string and validates it. /// Parses the resolved runtime application contract from a JSON string and validates it.
pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> { pub fn parse_config_json(raw_json: &str) -> ks_core::Result<crate::AppConfig> {
match validate_config_json_schema(raw_json) { match crate::validate_config_json_schema(raw_json) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let config = match serde_json::from_str::<AppConfig>(raw_json) { let config = match serde_json::from_str::<crate::AppConfig>(raw_json) {
std::result::Result::Ok(config) => config, std::result::Result::Ok(config) => config,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -345,14 +313,14 @@ pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> {
)); ));
}, },
}; };
return match validate_config(&config) { return match crate::validate_config(&config) {
std::result::Result::Ok(()) => std::result::Result::Ok(config), std::result::Result::Ok(()) => std::result::Result::Ok(config),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
} }
/// Reads and parses a resolved runtime application snapshot from a filesystem path. /// Reads and parses a resolved runtime application snapshot from a filesystem path.
pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<AppConfig> { pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<crate::AppConfig> {
let raw_json = match std::fs::read_to_string(path) { let raw_json = match std::fs::read_to_string(path) {
std::result::Result::Ok(content) => content, std::result::Result::Ok(content) => content,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -362,14 +330,14 @@ pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result<AppConfi
)); ));
}, },
}; };
return parse_config_json(&raw_json); return crate::parse_config_json(&raw_json);
} }
/// Loads the workspace environment, resolves placeholders and parses one resolved runtime snapshot. /// Loads the workspace environment, resolves placeholders and parses one resolved runtime snapshot.
pub fn read_config_json_file_with_environment( pub fn read_config_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<AppConfig> { ) -> ks_core::Result<crate::AppConfig> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -384,11 +352,11 @@ pub fn read_config_json_file_with_environment(
}, },
}; };
let resolved = crate::resolve_environment_placeholders(&raw_json); let resolved = crate::resolve_environment_placeholders(&raw_json);
return parse_config_json(&resolved); return crate::parse_config_json(&resolved);
} }
/// Returns the active profile declared by the root configuration. /// Returns the active profile declared by the root configuration.
pub fn active_profile(config: &AppConfig) -> ks_core::Result<&ProfileConfig> { pub fn active_profile(config: &crate::AppConfig) -> ks_core::Result<&crate::ProfileConfig> {
for profile in &config.profiles { for profile in &config.profiles {
if profile.name == config.active_profile { if profile.name == config.active_profile {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -401,7 +369,7 @@ pub fn active_profile(config: &AppConfig) -> ks_core::Result<&ProfileConfig> {
} }
/// Validates a typed configuration after JSON Schema validation and deserialization. /// Validates a typed configuration after JSON Schema validation and deserialization.
pub fn validate_config(config: &AppConfig) -> ks_core::Result<()> { pub fn validate_config(config: &crate::AppConfig) -> ks_core::Result<()> {
match validate_root_config(config) { match validate_root_config(config) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -412,14 +380,14 @@ pub fn validate_config(config: &AppConfig) -> ks_core::Result<()> {
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match active_profile(config) { return match crate::active_profile(config) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
} }
fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> { fn validate_root_config(config: &crate::AppConfig) -> ks_core::Result<()> {
match require_non_empty(&config.active_profile, "active_profile") { match crate::require_non_empty(&config.active_profile, "active_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -432,7 +400,7 @@ fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
let mut names = std::collections::BTreeSet::<std::string::String>::new(); let mut names = std::collections::BTreeSet::<std::string::String>::new();
let mut active_count = 0_u32; let mut active_count = 0_u32;
for profile in &config.profiles { for profile in &config.profiles {
match require_non_empty(&profile.name, "profile.name") { match crate::require_non_empty(&profile.name, "profile.name") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -455,8 +423,8 @@ fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> { fn validate_profile(profile: &crate::ProfileConfig) -> ks_core::Result<()> {
match validate_database(&profile.database) { match crate::validate_database(&profile.database) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -464,44 +432,32 @@ fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match validate_wallet(&profile.wallet) { match crate::validate_wallet(&profile.wallet) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match validate_execution(&profile.execution) { match crate::validate_execution(&profile.execution) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
return validate_wallet_execution_pair(&profile.wallet, &profile.execution); return validate_wallet_execution_pair(&profile.wallet, &profile.execution);
} }
pub(crate) fn validate_database(config: &DatabaseConfig) -> ks_core::Result<()> { pub(crate) fn validate_database(config: &crate::DatabaseConfig) -> ks_core::Result<()> {
if config.backend != "postgres" && config.backend != "sqlite" { match crate::require_non_empty(&config.backend, "database.backend") {
return std::result::Result::Err(ks_core::Error::new(
"database_backend_invalid",
config.backend.clone(),
));
}
match require_non_empty(&config.postgres.url, "database.postgres.url") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
if config.postgres.max_connections == 0 || config.sqlite.max_connections == 0 { if !config.backend_options.is_object() {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
"database_max_connections_zero", "database_backend_options_invalid",
"database connection limits must be greater than zero", "database.backend_options must be a JSON object",
)); ));
} }
if config.postgres.connect_timeout_ms == 0 || config.sqlite.busy_timeout_ms == 0 { return std::result::Result::Ok(());
return std::result::Result::Err(ks_core::Error::new(
"database_timeout_zero",
"database timeouts must be greater than zero",
));
}
return require_non_empty(&config.sqlite.path, "database.sqlite.path");
} }
fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> { fn validate_solana(config: &crate::SolanaConfig) -> ks_core::Result<()> {
if config.http_endpoints.is_empty() { if config.http_endpoints.is_empty() {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
"http_endpoints_empty", "http_endpoints_empty",
@@ -522,7 +478,7 @@ fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
endpoint.name.clone(), endpoint.name.clone(),
)); ));
} }
match validate_http_endpoint(endpoint) { match crate::validate_http_endpoint(endpoint) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -535,15 +491,15 @@ fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
endpoint.name.clone(), endpoint.name.clone(),
)); ));
} }
match validate_ws_endpoint(endpoint) { match crate::validate_ws_endpoint(endpoint) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return validate_listeners(&config.listeners); return crate::validate_listeners(&config.listeners);
} }
pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Result<()> { pub(crate) fn validate_http_endpoint(config: &crate::HttpEndpointConfig) -> ks_core::Result<()> {
match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) { match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -574,7 +530,7 @@ pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Re
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
pub(crate) fn validate_ws_endpoint(config: &WsEndpointConfig) -> ks_core::Result<()> { pub(crate) fn validate_ws_endpoint(config: &crate::WsEndpointConfig) -> ks_core::Result<()> {
match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) { match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -603,17 +559,17 @@ fn validate_endpoint_common(
name: &str, name: &str,
provider: &str, provider: &str,
cluster: &str, cluster: &str,
roles: &[EndpointRoleConfig], roles: &[crate::EndpointRoleConfig],
) -> ks_core::Result<()> { ) -> ks_core::Result<()> {
match require_non_empty(name, "endpoint.name") { match crate::require_non_empty(name, "endpoint.name") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(provider, "endpoint.provider") { match crate::require_non_empty(provider, "endpoint.provider") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(cluster, "endpoint.cluster") { match crate::require_non_empty(cluster, "endpoint.cluster") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -639,8 +595,8 @@ fn validate_endpoint_common(
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> { fn validate_endpoint_role(config: &crate::EndpointRoleConfig) -> ks_core::Result<()> {
match require_non_empty(&config.role, "endpoint.roles.role") { match crate::require_non_empty(&config.role, "endpoint.roles.role") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -651,7 +607,7 @@ fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
)); ));
} }
for request_kind in &config.request_kinds { for request_kind in &config.request_kinds {
match require_non_empty(request_kind, "endpoint.roles.request_kinds") { match crate::require_non_empty(request_kind, "endpoint.roles.request_kinds") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -669,8 +625,11 @@ fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()> { pub(crate) fn validate_listeners(config: &crate::ListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.default_commitment, "solana.listeners.default_commitment") { match crate::require_non_empty(
&config.default_commitment,
"solana.listeners.default_commitment",
) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -695,62 +654,65 @@ pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()>
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
fn validate_log_listener(config: &LogListenerConfig) -> ks_core::Result<()> { fn validate_log_listener(config: &crate::LogListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "log_listener.name") { match crate::require_non_empty(&config.name, "log_listener.name") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.endpoint_role, "log_listener.endpoint_role") { match crate::require_non_empty(&config.endpoint_role, "log_listener.endpoint_role") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
return require_non_empty(&config.program_id, "log_listener.program_id"); return crate::require_non_empty(&config.program_id, "log_listener.program_id");
} }
fn validate_program_listener(config: &ProgramListenerConfig) -> ks_core::Result<()> { fn validate_program_listener(config: &crate::ProgramListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "program_listener.name") { match crate::require_non_empty(&config.name, "program_listener.name") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.endpoint_role, "program_listener.endpoint_role") { match crate::require_non_empty(&config.endpoint_role, "program_listener.endpoint_role") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
return require_non_empty(&config.program_id, "program_listener.program_id"); return crate::require_non_empty(&config.program_id, "program_listener.program_id");
} }
fn validate_account_listener(config: &AccountListenerConfig) -> ks_core::Result<()> { fn validate_account_listener(config: &crate::AccountListenerConfig) -> ks_core::Result<()> {
match require_non_empty(&config.name, "account_listener.name") { match crate::require_non_empty(&config.name, "account_listener.name") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.endpoint_role, "account_listener.endpoint_role") { match crate::require_non_empty(&config.endpoint_role, "account_listener.endpoint_role") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
return require_non_empty(&config.account_pubkey, "account_listener.account_pubkey"); return crate::require_non_empty(&config.account_pubkey, "account_listener.account_pubkey");
} }
pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> { pub(crate) fn validate_wallet(config: &crate::WalletConfig) -> ks_core::Result<()> {
match require_non_empty(&config.wallet_dir, "wallet.wallet_dir") { match crate::require_non_empty(&config.wallet_dir, "wallet.wallet_dir") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.temporary_wallet_dir, "wallet.temporary_wallet_dir") { match crate::require_non_empty(&config.temporary_wallet_dir, "wallet.temporary_wallet_dir") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.cluster, "wallet.cluster") { match crate::require_non_empty(&config.cluster, "wallet.cluster") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
if let std::option::Option::Some(alias) = config.wallet_alias.as_deref() { if let std::option::Option::Some(alias) = config.wallet_alias.as_deref() {
match validate_wallet_alias(alias, "wallet.wallet_alias") { match crate::validate_wallet_alias(alias, "wallet.wallet_alias") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
match validate_wallet_alias(&config.temporary_wallet_alias, "wallet.temporary_wallet_alias") { match crate::validate_wallet_alias(
&config.temporary_wallet_alias,
"wallet.temporary_wallet_alias",
) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
@@ -787,7 +749,7 @@ pub(crate) fn validate_wallet_alias(value: &str, field: &str) -> ks_core::Result
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
pub(crate) fn validate_execution(config: &ExecutionConfig) -> ks_core::Result<()> { pub(crate) fn validate_execution(config: &crate::ExecutionConfig) -> ks_core::Result<()> {
let configured_spend = config let configured_spend = config
.localnet_max_spend_lamports .localnet_max_spend_lamports
.saturating_add(config.devnet_max_spend_lamports) .saturating_add(config.devnet_max_spend_lamports)
@@ -833,8 +795,8 @@ pub(crate) fn validate_execution(config: &ExecutionConfig) -> ks_core::Result<()
} }
fn validate_wallet_execution_pair( fn validate_wallet_execution_pair(
wallet: &WalletConfig, wallet: &crate::WalletConfig,
execution: &ExecutionConfig, execution: &crate::ExecutionConfig,
) -> ks_core::Result<()> { ) -> ks_core::Result<()> {
for (enabled, limit, cluster) in [ for (enabled, limit, cluster) in [
( (
@@ -909,25 +871,25 @@ mod tests {
#[test] #[test]
fn schema_text_is_valid_json() { fn schema_text_is_valid_json() {
let result = super::config_json_schema_value(); let result = crate::config_json_schema_value();
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn default_and_example_app_configs_validate_against_schema() { fn default_and_example_app_configs_validate_against_schema() {
assert!(super::validate_config_json_schema(DEFAULT_CONFIG).is_ok()); assert!(crate::validate_config_json_schema(DEFAULT_CONFIG).is_ok());
assert!(super::validate_config_json_schema(EXAMPLE_CONFIG).is_ok()); assert!(crate::validate_config_json_schema(EXAMPLE_CONFIG).is_ok());
} }
#[test] #[test]
fn default_app_config_parses_and_resolves_active_profile() { fn default_app_config_parses_and_resolves_active_profile() {
let config_result = super::parse_config_json(DEFAULT_CONFIG); let config_result = crate::parse_config_json(DEFAULT_CONFIG);
assert!(config_result.is_ok()); assert!(config_result.is_ok());
let config = match config_result { let config = match config_result {
std::result::Result::Ok(config) => config, std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => panic!("default app config must parse: {error}"), std::result::Result::Err(error) => panic!("default app config must parse: {error}"),
}; };
let active_result = super::active_profile(&config); let active_result = crate::active_profile(&config);
assert!(active_result.is_ok()); assert!(active_result.is_ok());
//let active = //let active =
match active_result { match active_result {
@@ -942,33 +904,62 @@ mod tests {
let mut value = parse_default_value(); let mut value = parse_default_value();
value["active_profile"] = serde_json::Value::String("missing_profile".to_string()); value["active_profile"] = serde_json::Value::String("missing_profile".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn default_app_config_separates_runtime_postgres_profiles() { fn default_app_config_separates_selected_store_backend_options() {
let value = parse_default_value(); let value = parse_default_value();
assert_eq!( assert_eq!(
value["profiles"][0]["database"]["postgres"]["url"], value["profiles"][0]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_DEVNET_URL}".to_string()) serde_json::Value::String("${KS_SECRET_POSTGRES_DEVNET_URL}".to_string())
); );
assert_eq!( assert_eq!(
value["profiles"][1]["database"]["postgres"]["url"], value["profiles"][1]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string()) serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string())
); );
assert_eq!( assert_eq!(
value["profiles"][2]["database"]["postgres"]["url"], value["profiles"][2]["database"]["backend_options"]["url"],
serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string()) serde_json::Value::String("${KS_SECRET_POSTGRES_MAINNET_URL}".to_string())
); );
} }
#[test]
fn parser_keeps_unknown_store_backend_options_opaque() {
let mut value = parse_default_value();
value["profiles"][0]["database"]["backend"] =
serde_json::Value::String("future-backend".to_string());
value["profiles"][0]["database"]["backend_options"] =
serde_json::json!({"secret": "STORE-SECRET-CANARY"});
let raw_json = value_to_json(&value);
let config = match crate::parse_config_json(&raw_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("opaque backend must parse: {error}"),
};
assert_eq!(config.profiles[0].database.backend, "future-backend");
assert_eq!(
config.profiles[0].database.backend_options["secret"],
serde_json::Value::String("STORE-SECRET-CANARY".to_string())
);
}
#[test]
fn schema_rejects_non_object_store_backend_options() {
let mut value = parse_default_value();
value["profiles"][0]["database"]["backend_options"] =
serde_json::Value::String("invalid".to_string());
let raw_json = value_to_json(&value);
let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err());
}
#[test] #[test]
fn schema_rejects_profile_enabled_field() { fn schema_rejects_profile_enabled_field() {
let mut value = parse_default_value(); let mut value = parse_default_value();
value["profiles"][0]["enabled"] = serde_json::Value::Bool(true); value["profiles"][0]["enabled"] = serde_json::Value::Bool(true);
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::validate_config_json_schema(&raw_json); let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -977,7 +968,7 @@ mod tests {
let mut value = parse_default_value(); let mut value = parse_default_value();
value["profiles"][1]["name"] = serde_json::Value::String("local_devnet".to_string()); value["profiles"][1]["name"] = serde_json::Value::String("local_devnet".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -987,7 +978,7 @@ mod tests {
value["profiles"][0]["solana"]["http_endpoints"][0]["secret_env_field"] = value["profiles"][0]["solana"]["http_endpoints"][0]["secret_env_field"] =
serde_json::Value::String("KS_SECRET_HELIUS_API_KEY".to_string()); serde_json::Value::String("KS_SECRET_HELIUS_API_KEY".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::validate_config_json_schema(&raw_json); let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -997,7 +988,7 @@ mod tests {
value["profiles"][0]["data"]["idls_directory"] = value["profiles"][0]["data"]["idls_directory"] =
serde_json::Value::String("idls".to_string()); serde_json::Value::String("idls".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::validate_config_json_schema(&raw_json); let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1006,7 +997,7 @@ mod tests {
let mut value = parse_default_value(); let mut value = parse_default_value();
value["profiles"][0]["solana"]["advanced_streams"] = serde_json::json!([]); value["profiles"][0]["solana"]["advanced_streams"] = serde_json::json!([]);
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::validate_config_json_schema(&raw_json); let result = crate::validate_config_json_schema(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1016,7 +1007,7 @@ mod tests {
value["profiles"][0]["solana"]["http_endpoints"][0]["url"] = value["profiles"][0]["solana"]["http_endpoints"][0]["url"] =
serde_json::Value::String("wss://api.devnet.solana.com".to_string()); serde_json::Value::String("wss://api.devnet.solana.com".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1026,7 +1017,7 @@ mod tests {
value["profiles"][0]["solana"]["ws_endpoints"][0]["url"] = value["profiles"][0]["solana"]["ws_endpoints"][0]["url"] =
serde_json::Value::String("https://api.devnet.solana.com".to_string()); serde_json::Value::String("https://api.devnet.solana.com".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1036,7 +1027,7 @@ mod tests {
value["profiles"][0]["wallet"]["wallet_alias"] = value["profiles"][0]["wallet"]["wallet_alias"] =
serde_json::Value::String("../wallet".to_string()); serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1046,7 +1037,7 @@ mod tests {
value["profiles"][0]["wallet"]["temporary_wallet_alias"] = value["profiles"][0]["wallet"]["temporary_wallet_alias"] =
serde_json::Value::String("../wallet".to_string()); serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1055,7 +1046,7 @@ mod tests {
let mut value = parse_default_value(); let mut value = parse_default_value();
value["profiles"][0]["wallet"]["temporary_wallet_enabled"] = serde_json::Value::Bool(false); value["profiles"][0]["wallet"]["temporary_wallet_enabled"] = serde_json::Value::Bool(false);
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1065,7 +1056,7 @@ mod tests {
value["profiles"][0]["execution"]["devnet_max_spend_lamports"] = value["profiles"][0]["execution"]["devnet_max_spend_lamports"] =
serde_json::Value::Number(serde_json::Number::from(0)); serde_json::Value::Number(serde_json::Number::from(0));
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -1081,7 +1072,7 @@ mod tests {
value["profiles"][0]["execution"][field] = value["profiles"][0]["execution"][field] =
serde_json::Value::Number(serde_json::Number::from(0)); serde_json::Value::Number(serde_json::Number::from(0));
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
} }
@@ -1092,7 +1083,7 @@ mod tests {
value["profiles"][0]["solana"]["http_endpoints"][0]["roles"][0]["max_subscriptions"] = value["profiles"][0]["solana"]["http_endpoints"][0]["roles"][0]["max_subscriptions"] =
serde_json::Value::Number(serde_json::Number::from(1)); serde_json::Value::Number(serde_json::Number::from(1));
let raw_json = value_to_json(&value); let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json); let result = crate::parse_config_json(&raw_json);
assert!(result.is_err()); assert!(result.is_err());
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/store.rs // file: ks-config/src/store.rs
// version: 2 // version: 4
//! Independent store configuration document loading and validation. //! Independent store configuration document loading and validation.
@@ -11,7 +11,7 @@ pub struct StoreConfigDocument {
/// Default store profile used when a composition does not override it. /// Default store profile used when a composition does not override it.
pub default_profile: std::string::String, pub default_profile: std::string::String,
/// Named store profiles. /// Named store profiles.
pub profiles: std::vec::Vec<StoreProfileConfig>, pub profiles: std::vec::Vec<crate::StoreProfileConfig>,
} }
/// Named store profile. /// Named store profile.
@@ -42,7 +42,7 @@ pub fn store_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw store JSON against the embedded schema. /// Validates raw store JSON against the embedded schema.
pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> { pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match store_json_schema_value() { let schema = match crate::store_json_schema_value() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -74,12 +74,12 @@ pub fn validate_store_json_schema(raw_json: &str) -> ks_core::Result<()> {
} }
/// Parses and validates one store configuration document. /// Parses and validates one store configuration document.
pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument> { pub fn parse_store_json(raw_json: &str) -> ks_core::Result<crate::StoreConfigDocument> {
match validate_store_json_schema(raw_json) { match crate::validate_store_json_schema(raw_json) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let document = match serde_json::from_str::<StoreConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::StoreConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -88,7 +88,7 @@ pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument>
)); ));
}, },
}; };
return match validate_store_document(&document) { return match crate::validate_store_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -98,7 +98,7 @@ pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument>
pub fn read_store_json_file_with_environment( pub fn read_store_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<StoreConfigDocument> { ) -> ks_core::Result<crate::StoreConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -112,14 +112,14 @@ pub fn read_store_json_file_with_environment(
)); ));
}, },
}; };
return parse_store_json(&crate::resolve_environment_placeholders(&raw_json)); return crate::parse_store_json(&crate::resolve_environment_placeholders(&raw_json));
} }
/// Returns one named store profile. /// Returns one named store profile.
pub fn store_profile<'a>( pub fn store_profile<'a>(
document: &'a StoreConfigDocument, document: &'a crate::StoreConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a StoreProfileConfig> { ) -> ks_core::Result<&'a crate::StoreProfileConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -133,13 +133,13 @@ pub fn store_profile<'a>(
/// Returns the default store profile. /// Returns the default store profile.
pub fn default_store_profile( pub fn default_store_profile(
document: &StoreConfigDocument, document: &crate::StoreConfigDocument,
) -> ks_core::Result<&StoreProfileConfig> { ) -> ks_core::Result<&crate::StoreProfileConfig> {
return store_profile(document, document.default_profile.as_str()); return crate::store_profile(document, document.default_profile.as_str());
} }
/// Validates one typed store configuration document. /// Validates one typed store configuration document.
pub fn validate_store_document(document: &StoreConfigDocument) -> ks_core::Result<()> { pub fn validate_store_document(document: &crate::StoreConfigDocument) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "store.default_profile") { match crate::require_non_empty(&document.default_profile, "store.default_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -167,7 +167,7 @@ pub fn validate_store_document(document: &StoreConfigDocument) -> ks_core::Resul
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match default_store_profile(document) { return match crate::default_store_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -180,7 +180,7 @@ mod tests {
#[test] #[test]
fn default_and_example_store_configs_validate() { fn default_and_example_store_configs_validate() {
assert!(super::parse_store_json(DEFAULT_STORE).is_ok()); assert!(crate::parse_store_json(DEFAULT_STORE).is_ok());
assert!(super::parse_store_json(EXAMPLE_STORE).is_ok()); assert!(crate::parse_store_json(EXAMPLE_STORE).is_ok());
} }
} }

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/transport.rs // file: ks-config/src/transport.rs
// version: 3 // version: 5
//! Independent Solana transport configuration document loading and validation. //! Independent Solana transport configuration document loading and validation.
@@ -12,9 +12,9 @@ pub struct TransportConfigDocument {
/// Default transport profile used when a composition does not override it. /// Default transport profile used when a composition does not override it.
pub default_profile: std::string::String, pub default_profile: std::string::String,
/// Named WebSocket endpoint defaults resolved before endpoint-specific overrides. /// Named WebSocket endpoint defaults resolved before endpoint-specific overrides.
pub ws_endpoint_defaults: std::vec::Vec<WsEndpointDefaultsConfig>, pub ws_endpoint_defaults: std::vec::Vec<crate::WsEndpointDefaultsConfig>,
/// Named transport profiles available in this document. /// Named transport profiles available in this document.
pub profiles: std::vec::Vec<TransportProfileSourceConfig>, pub profiles: std::vec::Vec<crate::TransportProfileSourceConfig>,
} }
/// Default values shared by one class of WebSocket endpoints. /// Default values shared by one class of WebSocket endpoints.
@@ -70,7 +70,7 @@ pub struct WsEndpointSourceConfig {
pub url: std::string::String, pub url: std::string::String,
/// Optional endpoint-specific overrides. /// Optional endpoint-specific overrides.
#[serde(default)] #[serde(default)]
pub overrides: WsEndpointOverridesConfig, pub overrides: crate::WsEndpointOverridesConfig,
/// Role-specific limits for this URL. /// Role-specific limits for this URL.
pub roles: std::vec::Vec<crate::EndpointRoleConfig>, pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
} }
@@ -83,7 +83,7 @@ pub struct TransportProfileSourceConfig {
/// HTTP JSON-RPC endpoints. /// HTTP JSON-RPC endpoints.
pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>, pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
/// Standard Solana WebSocket endpoints before shared defaults are resolved. /// Standard Solana WebSocket endpoints before shared defaults are resolved.
pub ws_endpoints: std::vec::Vec<WsEndpointSourceConfig>, pub ws_endpoints: std::vec::Vec<crate::WsEndpointSourceConfig>,
} }
/// Fully resolved transport profile consumed by transport clients. /// Fully resolved transport profile consumed by transport clients.
@@ -116,7 +116,7 @@ pub fn transport_json_schema_value() -> ks_core::Result<serde_json::Value> {
/// Validates raw transport JSON against the embedded transport schema. /// Validates raw transport JSON against the embedded transport schema.
pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> { pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> {
let schema = match transport_json_schema_value() { let schema = match crate::transport_json_schema_value() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -148,12 +148,12 @@ pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> {
} }
/// Parses and validates one independent transport configuration document. /// Parses and validates one independent transport configuration document.
pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDocument> { pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<crate::TransportConfigDocument> {
match validate_transport_json_schema(raw_json) { match crate::validate_transport_json_schema(raw_json) {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
let document = match serde_json::from_str::<TransportConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::TransportConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -162,7 +162,7 @@ pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDo
)); ));
}, },
}; };
return match validate_transport_document(&document) { return match crate::validate_transport_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -172,7 +172,7 @@ pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDo
pub fn read_transport_json_file_with_environment( pub fn read_transport_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<TransportConfigDocument> { ) -> ks_core::Result<crate::TransportConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -186,14 +186,14 @@ pub fn read_transport_json_file_with_environment(
)); ));
}, },
}; };
return parse_transport_json(&crate::resolve_environment_placeholders(&raw_json)); return crate::parse_transport_json(&crate::resolve_environment_placeholders(&raw_json));
} }
/// Resolves one named transport profile including WebSocket defaults and overrides. /// Resolves one named transport profile including WebSocket defaults and overrides.
pub fn transport_profile( pub fn transport_profile(
document: &TransportConfigDocument, document: &crate::TransportConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<TransportProfileConfig> { ) -> ks_core::Result<crate::TransportProfileConfig> {
let source = match source_transport_profile(document, profile_name) { let source = match source_transport_profile(document, profile_name) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -259,7 +259,7 @@ pub fn transport_profile(
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return std::result::Result::Ok(TransportProfileConfig { return std::result::Result::Ok(crate::TransportProfileConfig {
name: source.name.clone(), name: source.name.clone(),
http_endpoints: source.http_endpoints.clone(), http_endpoints: source.http_endpoints.clone(),
ws_endpoints, ws_endpoints,
@@ -268,15 +268,15 @@ pub fn transport_profile(
/// Returns the default resolved transport profile. /// Returns the default resolved transport profile.
pub fn default_transport_profile( pub fn default_transport_profile(
document: &TransportConfigDocument, document: &crate::TransportConfigDocument,
) -> ks_core::Result<TransportProfileConfig> { ) -> ks_core::Result<crate::TransportProfileConfig> {
return transport_profile(document, document.default_profile.as_str()); return crate::transport_profile(document, document.default_profile.as_str());
} }
fn source_transport_profile<'a>( fn source_transport_profile<'a>(
document: &'a TransportConfigDocument, document: &'a crate::TransportConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a TransportProfileSourceConfig> { ) -> ks_core::Result<&'a crate::TransportProfileSourceConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -289,9 +289,9 @@ fn source_transport_profile<'a>(
} }
fn ws_endpoint_defaults<'a>( fn ws_endpoint_defaults<'a>(
document: &'a TransportConfigDocument, document: &'a crate::TransportConfigDocument,
defaults_name: &str, defaults_name: &str,
) -> ks_core::Result<&'a WsEndpointDefaultsConfig> { ) -> ks_core::Result<&'a crate::WsEndpointDefaultsConfig> {
for defaults in &document.ws_endpoint_defaults { for defaults in &document.ws_endpoint_defaults {
if defaults.name == defaults_name { if defaults.name == defaults_name {
return std::result::Result::Ok(defaults); return std::result::Result::Ok(defaults);
@@ -304,7 +304,9 @@ fn ws_endpoint_defaults<'a>(
} }
/// Validates a typed transport configuration document. /// Validates a typed transport configuration document.
pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_core::Result<()> { pub fn validate_transport_document(
document: &crate::TransportConfigDocument,
) -> ks_core::Result<()> {
match crate::require_non_empty(&document.default_profile, "transport.default_profile") { match crate::require_non_empty(&document.default_profile, "transport.default_profile") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -379,12 +381,12 @@ pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_cor
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
match transport_profile(document, profile.name.as_str()) { match crate::transport_profile(document, profile.name.as_str()) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match default_transport_profile(document) { return match crate::default_transport_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -398,17 +400,17 @@ mod tests {
#[test] #[test]
fn default_and_example_transport_configs_validate() { fn default_and_example_transport_configs_validate() {
assert!(super::parse_transport_json(DEFAULT_TRANSPORT).is_ok()); assert!(crate::parse_transport_json(DEFAULT_TRANSPORT).is_ok());
assert!(super::parse_transport_json(EXAMPLE_TRANSPORT).is_ok()); assert!(crate::parse_transport_json(EXAMPLE_TRANSPORT).is_ok());
} }
#[test] #[test]
fn websocket_defaults_are_resolved_before_endpoint_overrides() { fn websocket_defaults_are_resolved_before_endpoint_overrides() {
let document = match super::parse_transport_json(DEFAULT_TRANSPORT) { let document = match crate::parse_transport_json(DEFAULT_TRANSPORT) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default transport must parse: {error}"), std::result::Result::Err(error) => panic!("default transport must parse: {error}"),
}; };
let profile = match super::transport_profile(&document, "mainnet_research") { let profile = match crate::transport_profile(&document, "mainnet_research") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
panic!("mainnet research transport must resolve: {error}") panic!("mainnet research transport must resolve: {error}")

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/wallet.rs // file: ks-config/src/wallet.rs
// version: 6 // version: 8
//! Independent wallet configuration document loading and validation. //! Independent wallet configuration document loading and validation.
@@ -13,7 +13,7 @@ pub struct WalletConfigDocument {
/// Default wallet profile used when a composition does not override it. /// Default wallet profile used when a composition does not override it.
pub default_profile: std::string::String, pub default_profile: std::string::String,
/// Named wallet profiles. /// Named wallet profiles.
pub profiles: std::vec::Vec<WalletProfileConfig>, pub profiles: std::vec::Vec<crate::WalletProfileConfig>,
} }
/// Named wallet profile whose directory is relative to the global wallet root unless absolute. /// Named wallet profile whose directory is relative to the global wallet root unless absolute.
@@ -41,7 +41,7 @@ pub fn wallet_json_schema_text() -> &'static str {
} }
/// Parses and validates one wallet configuration document. /// Parses and validates one wallet configuration document.
pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument> { pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<crate::WalletConfigDocument> {
let schema = match serde_json::from_str::<serde_json::Value>(WALLET_JSON_SCHEMA) { let schema = match serde_json::from_str::<serde_json::Value>(WALLET_JSON_SCHEMA) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => { std::result::Result::Err(error) => {
@@ -75,7 +75,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
"wallet configuration does not satisfy its schema", "wallet configuration does not satisfy its schema",
)); ));
} }
let document = match serde_json::from_str::<WalletConfigDocument>(raw_json) { let document = match serde_json::from_str::<crate::WalletConfigDocument>(raw_json) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => { std::result::Result::Err(_) => {
return std::result::Result::Err(ks_core::Error::new( return std::result::Result::Err(ks_core::Error::new(
@@ -84,7 +84,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
)); ));
}, },
}; };
return match validate_wallet_document(&document) { return match crate::validate_wallet_document(&document) {
std::result::Result::Ok(()) => std::result::Result::Ok(document), std::result::Result::Ok(()) => std::result::Result::Ok(document),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -94,7 +94,7 @@ pub fn parse_wallet_json(raw_json: &str) -> ks_core::Result<WalletConfigDocument
pub fn read_wallet_json_file_with_environment( pub fn read_wallet_json_file_with_environment(
path: &std::path::Path, path: &std::path::Path,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
) -> ks_core::Result<WalletConfigDocument> { ) -> ks_core::Result<crate::WalletConfigDocument> {
match crate::load_workspace_environment(workspace_root) { match crate::load_workspace_environment(workspace_root) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -108,14 +108,14 @@ pub fn read_wallet_json_file_with_environment(
)); ));
}, },
}; };
return parse_wallet_json(&crate::resolve_environment_placeholders(&raw_json)); return crate::parse_wallet_json(&crate::resolve_environment_placeholders(&raw_json));
} }
/// Returns one named wallet source profile. /// Returns one named wallet source profile.
pub fn wallet_profile<'a>( pub fn wallet_profile<'a>(
document: &'a WalletConfigDocument, document: &'a crate::WalletConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<&'a WalletProfileConfig> { ) -> ks_core::Result<&'a crate::WalletProfileConfig> {
for profile in &document.profiles { for profile in &document.profiles {
if profile.name == profile_name { if profile.name == profile_name {
return std::result::Result::Ok(profile); return std::result::Result::Ok(profile);
@@ -129,17 +129,17 @@ pub fn wallet_profile<'a>(
/// Returns the default wallet source profile. /// Returns the default wallet source profile.
pub fn default_wallet_profile( pub fn default_wallet_profile(
document: &WalletConfigDocument, document: &crate::WalletConfigDocument,
) -> ks_core::Result<&WalletProfileConfig> { ) -> ks_core::Result<&crate::WalletProfileConfig> {
return wallet_profile(document, document.default_profile.as_str()); return crate::wallet_profile(document, document.default_profile.as_str());
} }
/// Resolves one source wallet profile into the runtime wallet contract. /// Resolves one source wallet profile into the runtime wallet contract.
pub fn resolved_wallet_profile( pub fn resolved_wallet_profile(
document: &WalletConfigDocument, document: &crate::WalletConfigDocument,
profile_name: &str, profile_name: &str,
) -> ks_core::Result<crate::WalletConfig> { ) -> ks_core::Result<crate::WalletConfig> {
let profile = match wallet_profile(document, profile_name) { let profile = match crate::wallet_profile(document, profile_name) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -166,7 +166,7 @@ pub fn resolved_wallet_profile(
} }
/// Validates one typed wallet configuration document. /// Validates one typed wallet configuration document.
pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Result<()> { pub fn validate_wallet_document(document: &crate::WalletConfigDocument) -> ks_core::Result<()> {
match crate::require_non_empty(&document.wallets_directory, "wallet.wallets_directory") { match crate::require_non_empty(&document.wallets_directory, "wallet.wallets_directory") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -203,12 +203,12 @@ pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Res
profile.name.clone(), profile.name.clone(),
)); ));
} }
match resolved_wallet_profile(document, profile.name.as_str()) { match crate::resolved_wallet_profile(document, profile.name.as_str()) {
std::result::Result::Ok(_) => (), std::result::Result::Ok(_) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
} }
return match default_wallet_profile(document) { return match crate::default_wallet_profile(document) {
std::result::Result::Ok(_) => std::result::Result::Ok(()), std::result::Result::Ok(_) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error), std::result::Result::Err(error) => std::result::Result::Err(error),
}; };
@@ -221,9 +221,9 @@ mod tests {
#[test] #[test]
fn default_and_example_wallet_configs_validate() { fn default_and_example_wallet_configs_validate() {
let default = super::parse_wallet_json(DEFAULT_WALLET); let default = crate::parse_wallet_json(DEFAULT_WALLET);
assert!(default.is_ok()); assert!(default.is_ok());
let example = super::parse_wallet_json(EXAMPLE_WALLET); let example = crate::parse_wallet_json(EXAMPLE_WALLET);
assert!(example.is_ok()); assert!(example.is_ok());
let example = match example { let example = match example {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -238,11 +238,11 @@ mod tests {
#[test] #[test]
fn resolved_profile_separates_persistent_and_temporary_wallet_directories() { fn resolved_profile_separates_persistent_and_temporary_wallet_directories() {
let resolved_raw = crate::resolve_environment_placeholders(DEFAULT_WALLET); let resolved_raw = crate::resolve_environment_placeholders(DEFAULT_WALLET);
let document = match super::parse_wallet_json(resolved_raw.as_str()) { let document = match crate::parse_wallet_json(resolved_raw.as_str()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("resolved wallet config failed: {error}"), std::result::Result::Err(error) => panic!("resolved wallet config failed: {error}"),
}; };
let resolved = match super::resolved_wallet_profile(&document, "local_devnet") { let resolved = match crate::resolved_wallet_profile(&document, "local_devnet") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet profile resolution failed: {error}"), std::result::Result::Err(error) => panic!("wallet profile resolution failed: {error}"),
}; };
@@ -269,7 +269,7 @@ mod tests {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"), std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
}; };
let parsed = super::parse_wallet_json(raw.as_str()); let parsed = crate::parse_wallet_json(raw.as_str());
assert!(parsed.is_ok()); assert!(parsed.is_ok());
let parsed = match parsed { let parsed = match parsed {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -289,6 +289,6 @@ mod tests {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"), std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
}; };
assert!(super::parse_wallet_json(raw.as_str()).is_err()); assert!(crate::parse_wallet_json(raw.as_str()).is_err());
} }
} }

View File

@@ -1,8 +1,14 @@
<!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md --> <!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md -->
<!-- version: 81 --> <!-- version: 82 -->
# CHANGELOG — ks-pipeline-demo-scenarios # CHANGELOG — ks-pipeline-demo-scenarios
## `0.5.3-pre.002`
- remplace les constructions `PostgresStore`/`PostgresStoreOptions` des scénarios par `ks_store::Store` et `StoreOpenOptions` ;
- conserve les overrides PostgreSQL des tests opt-in sous forme de modification du bloc opaque `backend_options`, sans exposer de type backend dans les signatures de scénarios ;
- adapte les campagnes Solana/SPL/Metadata afin qu'elles réutilisent la même façade de store que `ks-pipeline` et le desktop.
## `0.5.2-pre.006-delta-fix-006` ## `0.5.2-pre.006-delta-fix-006`
- corrige le wrapper pNFT legacy : il reprend son paramètre `workspace_root` historique et construit le wallet temporaire uniquement dans ce wrapper, tandis que la variante `_with_wallet` conserve le signer explicite ; - corrige le wrapper pNFT legacy : il reprend son paramètre `workspace_root` historique et construit le wallet temporaire uniquement dans ce wrapper, tandis que la variante `_with_wallet` conserve le signer explicite ;

View File

@@ -1,10 +1,12 @@
<!-- file: ks-pipeline-demo-scenarios/README.md --> <!-- file: ks-pipeline-demo-scenarios/README.md -->
<!-- version: 31 --> <!-- version: 32 -->
# ks-pipeline-demo-scenarios # ks-pipeline-demo-scenarios
`ks-pipeline-demo-scenarios` fournit des scénarios synthétiques et des campagnes Devnet/Testnet automatisables au-dessus de `ks-pipeline`. `ks-pipeline-demo-scenarios` fournit des scénarios synthétiques et des campagnes Devnet/Testnet automatisables au-dessus de `ks-pipeline`.
La crate ouvre désormais le stockage de ses profils via la façade backend-agnostique `ks_store::Store`. Elle ne construit ni `PostgresStore`, ni pool SQLx ; les paramètres du moteur restent interprétés par `ks-store`.
La crate contient : La crate contient :
- la bibliothèque Rust `ks_pipeline_demo_scenarios` ; - la bibliothèque Rust `ks_pipeline_demo_scenarios` ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-pipeline-demo-scenarios/USAGE.md --> <!-- file: ks-pipeline-demo-scenarios/USAGE.md -->
<!-- version: 33 --> <!-- version: 34 -->
# Utilisation de ks-pipeline-demo-scenarios # Utilisation de ks-pipeline-demo-scenarios
@@ -21,7 +21,7 @@ fn initialize_environment() -> ks_core::Result<()> {
} }
``` ```
`resolve_demo_devnet_profile` sélectionne un profil Devnet configuré et `prepare_demo_devnet_profile_store` vérifie ou initialise son stockage PostgreSQL. `resolve_demo_devnet_profile` sélectionne un profil Devnet configuré et `prepare_demo_devnet_profile_store` ouvre/vérifie son `ks_store::Store`. Le scénario ne connaît pas le backend concret ; PostgreSQL reste actuellement sélectionné par la configuration mais sa connexion appartient à `ks-store`.
## Observer une exécution ## Observer une exécution
@@ -63,13 +63,12 @@ fn memo_request() -> ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest {
``` ```
```rust ```rust
async fn run_memo<S, O>( async fn run_memo<O>(
store: &S, store: &ks_store::Store,
observer: &O, observer: &O,
request: ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest, request: ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest,
) -> ks_core::Result<ks_pipeline_demo_scenarios::DevnetMemoExecutionSummary> ) -> ks_core::Result<ks_pipeline_demo_scenarios::DevnetMemoExecutionSummary>
where where
S: ks_store::Store + Sync,
O: ks_pipeline_demo_scenarios::SolanaExecutionObserver, O: ks_pipeline_demo_scenarios::SolanaExecutionObserver,
{ {
let result = ks_pipeline_demo_scenarios::execute_devnet_memo( let result = ks_pipeline_demo_scenarios::execute_devnet_memo(
@@ -78,14 +77,12 @@ where
request, request,
) )
.await; .await;
match result { match result {
Ok(summary) => Ok(summary), Ok(summary) => Ok(summary),
Err(error) => Err(error), Err(error) => Err(error),
} }
} }
``` ```
## Simuler Token-2022 ## Simuler Token-2022
```rust ```rust

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/environment.rs // file: ks-pipeline-demo-scenarios/src/environment.rs
// version: 7 // version: 9
//! Environment initialization for opt-in demonstration scenarios. //! Environment initialization for opt-in demonstration scenarios.
@@ -83,28 +83,53 @@ pub fn resolve_demo_devnet_profile(
}; };
} }
/// Readiness report for one Devnet profile PostgreSQL store. /// Backend-agnostic readiness report for one Devnet profile store.
#[derive(Clone, Debug, serde::Serialize)] #[derive(Clone, Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DevnetProfileStoreReadiness { pub struct DevnetProfileStoreReadiness {
/// Selected Devnet profile. /// Selected Devnet profile.
pub profile_name: std::string::String, pub profile_name: std::string::String,
/// Selected backend code.
pub backend: std::string::String,
/// Whether schema creation was allowed by configuration. /// Whether schema creation was allowed by configuration.
pub auto_initialize_schema: bool, pub auto_initialize_schema: bool,
/// Number of known tables already present before preparation. /// Number of known logical resources already present before preparation.
pub existing_tables_before: usize, pub available_resources_before: usize,
/// Number of known tables created during preparation. /// Number of known logical resources created during preparation.
pub created_tables: usize, pub created_resources: usize,
/// Number of known tables present after preparation. /// Number of known logical resources present after preparation.
pub existing_tables_after: usize, pub available_resources_after: usize,
/// Total number of known tables expected by the current store. /// Total number of logical resources expected by the current store contract.
pub expected_tables: usize, pub expected_resources: usize,
} }
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile. /// Builds backend-agnostic store-open options from one resolved profile.
fn store_open_options_from_profile(
profile: &ks_config::ProfileConfig,
) -> ks_core::Result<ks_store::StoreOpenOptions> {
return ks_store::StoreOpenOptions::new(
profile.database.enabled,
profile.database.backend.clone(),
profile.database.backend_options.clone(),
);
}
/// Opens the backend-agnostic store selected by one resolved profile.
#[cfg(test)]
pub(crate) async fn open_profile_store(
profile: &ks_config::ProfileConfig,
) -> ks_core::Result<ks_store::Store> {
let options = match store_open_options_from_profile(profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return ks_store::Store::open(options).await;
}
/// Verifies or initializes the store selected by one Devnet profile.
pub async fn prepare_demo_devnet_profile_store( pub async fn prepare_demo_devnet_profile_store(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
) -> ks_core::Result<DevnetProfileStoreReadiness> { ) -> ks_core::Result<crate::DevnetProfileStoreReadiness> {
if profile.wallet.cluster != "devnet" { if profile.wallet.cluster != "devnet" {
return std::result::Result::Err(ks_core::Error::config(format!( return std::result::Result::Err(ks_core::Error::config(format!(
"profile '{}' is not configured for Devnet", "profile '{}' is not configured for Devnet",
@@ -117,65 +142,78 @@ pub async fn prepare_demo_devnet_profile_store(
profile.name profile.name
))); )));
} }
if profile.database.backend != "postgres" { let options = match store_open_options_from_profile(profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let configuration = match options.configuration_summary() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match ks_store::Store::open(options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let initialization = store.initialization_summary();
if initialization.status != ks_store::StoreInitializationStatus::Ready {
return std::result::Result::Err(ks_core::Error::config(format!( return std::result::Result::Err(ks_core::Error::config(format!(
"Devnet profile '{}' must use the PostgreSQL backend", "Devnet profile '{}' store model is incomplete and automatic initialization is {}",
profile.name profile.name,
if configuration.auto_initialize_schema { "enabled" } else { "disabled" }
))); )));
} }
let options = match ks_store::PostgresStoreOptions::new( return std::result::Result::Ok(crate::DevnetProfileStoreReadiness {
profile.database.postgres.url.clone(), profile_name: profile.name.clone(),
profile.database.postgres.max_connections, backend: configuration.backend_code,
profile.database.postgres.connect_timeout_ms, auto_initialize_schema: configuration.auto_initialize_schema,
false, available_resources_before: initialization
.available_resource_count
.saturating_sub(initialization.created_resource_count)
as usize,
created_resources: initialization.created_resource_count as usize,
available_resources_after: initialization.available_resource_count as usize,
expected_resources: initialization.expected_resource_count as usize,
});
}
#[cfg(test)]
pub(crate) fn override_profile_store_url_for_test(
profile: &mut ks_config::ProfileConfig,
database_url: std::string::String,
) -> ks_core::Result<()> {
profile.database.enabled = true;
profile.database.backend = "postgres".to_string();
let backend_options = match profile.database.backend_options.as_object_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::config(
"test store backend options must be an object",
));
},
};
backend_options.insert("url".to_string(), serde_json::Value::String(database_url));
backend_options.insert("auto_initialize_schema".to_string(), serde_json::Value::Bool(true));
return std::result::Result::Ok(());
}
#[cfg(test)]
pub(crate) async fn open_test_store(
database_url: std::string::String,
) -> ks_core::Result<ks_store::Store> {
let options = match ks_store::StoreOpenOptions::new(
true,
"postgres",
serde_json::json!({
"url": database_url,
"max_connections": 5,
"connect_timeout_ms": 5_000,
"auto_initialize_schema": true
}),
) { ) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store = match ks_store::PostgresStore::connect(options).await { return ks_store::Store::open(options).await;
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let before = match store.known_table_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let existing_tables_before = before.iter().filter(|table| return table.exists).count();
if profile.database.postgres.auto_initialize_schema
&& let std::result::Result::Err(error) = store.initialize_store_schema().await
{
return std::result::Result::Err(error);
}
let after = match store.known_table_diagnostics().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let existing_tables_after = after.iter().filter(|table| return table.exists).count();
if existing_tables_after != after.len() {
let missing = after
.iter()
.filter(|table| return !table.exists)
.map(|table| return table.table_name.clone())
.collect::<std::vec::Vec<_>>();
return std::result::Result::Err(ks_core::Error::config(format!(
"Devnet profile '{}' PostgreSQL schema is incomplete and auto initialization is {}: missing {}",
profile.name,
if profile.database.postgres.auto_initialize_schema {
"enabled"
} else {
"disabled"
},
missing.join(", ")
)));
}
return std::result::Result::Ok(DevnetProfileStoreReadiness {
profile_name: profile.name.clone(),
auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
existing_tables_before,
created_tables: existing_tables_after.saturating_sub(existing_tables_before),
existing_tables_after,
expected_tables: after.len(),
});
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/lib.rs // file: ks-pipeline-demo-scenarios/src/lib.rs
// version: 31 // version: 34
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -14,17 +14,27 @@ mod solana;
mod spl; mod spl;
mod wallet; mod wallet;
/// Readiness report for one Devnet profile PostgreSQL store. /// Backend-agnostic readiness report for one Devnet profile store.
pub use self::environment::DevnetProfileStoreReadiness; pub use self::environment::DevnetProfileStoreReadiness;
/// Loads the workspace environment for opt-in demo and Devnet scenarios. /// Loads the workspace environment for opt-in demo and Devnet scenarios.
pub use self::environment::initialize_demo_scenario_environment; pub use self::environment::initialize_demo_scenario_environment;
/// Loads the shared default configuration or an explicit scenario composition override. /// Loads the shared default configuration or an explicit scenario composition override.
pub use self::environment::load_demo_scenario_config; pub use self::environment::load_demo_scenario_config;
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile. /// Verifies or initializes the store selected by one Devnet profile.
pub use self::environment::prepare_demo_devnet_profile_store; pub use self::environment::prepare_demo_devnet_profile_store;
/// Resolves one configured Devnet profile for UI, CLI and test scenarios. /// Resolves one configured Devnet profile for UI, CLI and test scenarios.
pub use self::environment::resolve_demo_devnet_profile; pub use self::environment::resolve_demo_devnet_profile;
/// Complete evidence produced by one collection parent/member verification campaign.
#[cfg(test)]
/// Opens one backend-agnostic store for internal scenarios.
pub(crate) use self::environment::open_profile_store;
#[cfg(test)]
/// Opens one backend-agnostic store from a direct test URL.
pub(crate) use self::environment::open_test_store;
#[cfg(test)]
/// Overrides one profile store URL for PostgreSQL integration tests without exposing backend types.
pub(crate) use self::environment::override_profile_store_url_for_test;
/// Complete evidence retained by one Metaplex collection verification campaign.
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyCampaignSummary; pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyCampaignSummary;
/// Collection relation and sized-collection state observed across verification. /// Collection relation and sized-collection state observed across verification.
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyState; pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyState;
@@ -88,7 +98,7 @@ pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture
pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_account_state; pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_account_state;
/// Reads the validated classic SPL token-account amount for an internal Metaplex campaign. /// Reads the validated classic SPL token-account amount for an internal Metaplex campaign.
pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_amount; pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_amount;
/// Creates or reuses a classic SPL mint and derives its Metaplex PDAs. /// Creates the default classic SPL fixture required by one Metaplex Create campaign.
pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture; pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture;
/// Creates a classic SPL mint fixture using an explicitly resolved operator wallet. /// Creates a classic SPL mint fixture using an explicitly resolved operator wallet.
pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture_with_wallet; pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture_with_wallet;
@@ -100,7 +110,7 @@ pub(crate) use self::metadata::metaplex_token_metadata::fixture::read_token_acco
pub(crate) use self::metadata::metaplex_token_metadata::fixture::validate_classic_fixture_mint_state; pub(crate) use self::metadata::metaplex_token_metadata::fixture::validate_classic_fixture_mint_state;
/// Validates one classic SPL token-account fixture against an exact expected amount. /// Validates one classic SPL token-account fixture against an exact expected amount.
pub(crate) use self::metadata::metaplex_token_metadata::fixture::validate_classic_fixture_token_account_state; pub(crate) use self::metadata::metaplex_token_metadata::fixture::validate_classic_fixture_token_account_state;
/// Complete evidence produced by the final Metaplex maintenance campaign. /// Complete evidence retained by one bounded Metaplex maintenance campaign.
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::DevnetMetaplexMaintenanceCampaignSummary; pub use self::metadata::metaplex_token_metadata::maintenance_campaign::DevnetMetaplexMaintenanceCampaignSummary;
/// Executes the final bounded current maintenance campaign on Devnet. /// Executes the final bounded current maintenance campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::execute_devnet_metaplex_maintenance_campaign; pub use self::metadata::metaplex_token_metadata::maintenance_campaign::execute_devnet_metaplex_maintenance_campaign;
@@ -256,7 +266,7 @@ pub use self::solana::SolanaExecutionObserver;
pub use self::solana::SolanaExecutionProgressEvent; pub use self::solana::SolanaExecutionProgressEvent;
/// Severity of one Solana execution progress event. /// Severity of one Solana execution progress event.
pub use self::solana::SolanaExecutionProgressLevel; pub use self::solana::SolanaExecutionProgressLevel;
/// Emits one execution progress event for specialized pipeline orchestrators. /// Emits one execution progress event through the optional internal observer.
pub(crate) use self::solana::emit; pub(crate) use self::solana::emit;
/// Rejects one execution stage when the observer reports cancellation. /// Rejects one execution stage when the observer reports cancellation.
pub(crate) use self::solana::ensure_not_cancelled; pub(crate) use self::solana::ensure_not_cancelled;
@@ -266,7 +276,7 @@ pub use self::solana::execute_devnet_system_transfer;
pub use self::solana::execute_devnet_system_transfer_with_wallet; pub use self::solana::execute_devnet_system_transfer_with_wallet;
/// Inspects the persistent native wallet alias selected by one execution profile. /// Inspects the persistent native wallet alias selected by one execution profile.
pub use self::solana::inspect_selected_profile_wallet; pub use self::solana::inspect_selected_profile_wallet;
/// Loads the managed temporary wallet used by legacy Devnet scenarios. /// Loads the temporary wallet fixture selected by one execution profile.
pub(crate) use self::solana::load_profile_temporary_wallet; pub(crate) use self::solana::load_profile_temporary_wallet;
/// Formats one failed simulation without discarding runtime diagnostics. /// Formats one failed simulation without discarding runtime diagnostics.
pub(crate) use self::solana::simulation_failure_message; pub(crate) use self::solana::simulation_failure_message;
@@ -296,7 +306,7 @@ pub use self::spl::memo::execute_devnet_memo_with_wallet;
pub use self::spl::token::execution::DevnetSplTokenExecutionRequest; pub use self::spl::token::execution::DevnetSplTokenExecutionRequest;
/// Complete result of one Devnet classic SPL Token execution. /// Complete result of one Devnet classic SPL Token execution.
pub use self::spl::token::execution::DevnetSplTokenExecutionSummary; pub use self::spl::token::execution::DevnetSplTokenExecutionSummary;
/// Canonical hydration availability helper shared by Token lifecycle orchestration. /// Reports whether canonical transaction data is available for one signature.
pub(crate) use self::spl::token::execution::canonical_available; pub(crate) use self::spl::token::execution::canonical_available;
/// Decode completion helper shared by Token lifecycle orchestration. /// Decode completion helper shared by Token lifecycle orchestration.
pub(crate) use self::spl::token::execution::decode_completed; pub(crate) use self::spl::token::execution::decode_completed;
@@ -304,7 +314,7 @@ pub(crate) use self::spl::token::execution::decode_completed;
pub use self::spl::token::execution::execute_devnet_spl_token; pub use self::spl::token::execution::execute_devnet_spl_token;
/// Executes one SPL Token operation with an explicitly resolved wallet capability. /// Executes one SPL Token operation with an explicitly resolved wallet capability.
pub use self::spl::token::execution::execute_devnet_spl_token_with_wallet; pub use self::spl::token::execution::execute_devnet_spl_token_with_wallet;
/// Canonical signature hydration helper shared by Token lifecycle orchestration. /// Hydrates one submitted signature into the canonical raw/Core pipeline.
pub(crate) use self::spl::token::execution::hydrate_signature; pub(crate) use self::spl::token::execution::hydrate_signature;
/// Targeted program replay helper shared by Token lifecycle orchestration. /// Targeted program replay helper shared by Token lifecycle orchestration.
pub(crate) use self::spl::token::execution::replay_program; pub(crate) use self::spl::token::execution::replay_program;

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs
// version: 7 // version: 9
//! Confirmed Devnet collection parent/member `Verify -> Unverify` campaign. //! Confirmed Devnet collection parent/member `Verify -> Unverify` campaign.
@@ -678,23 +678,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs
// version: 15 // version: 17
//! Confirmed Devnet `Create -> Mint` campaign for one Metaplex asset family. //! Confirmed Devnet `Create -> Mint` campaign for one Metaplex asset family.
@@ -844,23 +844,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs
// version: 24 // version: 26
//! Real Devnet simulation and submission for current Metaplex Token Metadata operations. //! Real Devnet simulation and submission for current Metaplex Token Metadata operations.
@@ -1516,18 +1516,10 @@ mod tests {
panic!("KS_SECRET_POSTGRES_TEST_URL is required for Metaplex execution: {error}") panic!("KS_SECRET_POSTGRES_TEST_URL is required for Metaplex execution: {error}")
}, },
}; };
let store_options = match ks_store::PostgresStoreOptions::new(database_url, 5, 5_000, false) let store = match crate::open_test_store(database_url).await {
{
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Postgres options failed: {error}"), std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Postgres connection failed: {error}"),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("Postgres schema initialization failed: {error}");
}
let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() { let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() {
std::option::Option::Some(value) => value, std::option::Option::Some(value) => value,
std::option::Option::None => panic!("workspace root cannot be resolved"), std::option::Option::None => panic!("workspace root cannot be resolved"),

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs
// version: 7 // version: 9
//! Confirmed Devnet Token Owned Escrow campaign for current Metaplex escrow operations. //! Confirmed Devnet Token Owned Escrow campaign for current Metaplex escrow operations.
@@ -1004,23 +1004,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs
// version: 9 // version: 11
//! Final bounded Devnet qualification campaign for current Metaplex maintenance operations. //! Final bounded Devnet qualification campaign for current Metaplex maintenance operations.
@@ -598,23 +598,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs
// version: 10 // version: 12
//! Confirmed Devnet pNFT delegate, lock, unlock, revoke and transfer campaign. //! Confirmed Devnet pNFT delegate, lock, unlock, revoke and transfer campaign.
@@ -1096,23 +1096,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs
// version: 10 // version: 12
//! Devnet printable master NFT `Print -> Burn` campaign. //! Devnet printable master NFT `Print -> Burn` campaign.
@@ -1210,23 +1210,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs
// version: 7 // version: 9
//! Bounded Devnet availability probe for the current Metaplex `Use` surface. //! Bounded Devnet availability probe for the current Metaplex `Use` surface.
@@ -455,23 +455,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/solana.rs // file: ks-pipeline-demo-scenarios/src/solana.rs
// version: 18 // version: 20
//! Devnet Solana execution orchestration with canonical post-validation. //! Devnet Solana execution orchestration with canonical post-validation.
@@ -1544,8 +1544,8 @@ mod tests {
}, },
}; };
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.temporary_wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
@@ -1553,22 +1553,10 @@ mod tests {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"), std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
}; };
let store_options = match ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL options failed: {error}"), std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL connection failed: {error}"),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let recipient_alias = match ks_wallet::WalletAlias::parse("devnet-recipient") { let recipient_alias = match ks_wallet::WalletAlias::parse("devnet-recipient") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("recipient alias failed: {error}"), std::result::Result::Err(error) => panic!("recipient alias failed: {error}"),

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs // file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs
// version: 17 // version: 19
//! Devnet ATA execution with stateful and canonical post-validation. //! Devnet ATA execution with stateful and canonical post-validation.
@@ -1162,20 +1162,10 @@ mod tests {
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
store
.initialize_store_schema()
.await
.unwrap_or_else(|error| panic!("schema initialization failed: {error}"));
let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> = std::vec![ let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> = std::vec![
std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder), std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder),
std::sync::Arc::new(ks_lib::DcSplTokenDecoder), std::sync::Arc::new(ks_lib::DcSplTokenDecoder),

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/memo.rs // file: ks-pipeline-demo-scenarios/src/spl/memo.rs
// version: 13 // version: 15
//! Devnet SPL Memo v4 execution with canonical post-validation. //! Devnet SPL Memo v4 execution with canonical post-validation.
@@ -857,8 +857,8 @@ mod tests {
}, },
}; };
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.temporary_wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
@@ -866,22 +866,10 @@ mod tests {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"), std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
}; };
let store_options = match ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL options failed: {error}"), std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL connection failed: {error}"),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let mut request = crate::DevnetMemoExecutionRequest::new( let mut request = crate::DevnetMemoExecutionRequest::new(
format!("devnet-memo-test-{}", uuid::Uuid::new_v4()), format!("devnet-memo-test-{}", uuid::Uuid::new_v4()),
format!("khadhroony-bot3 memo validation {}", uuid::Uuid::new_v4()), format!("khadhroony-bot3 memo validation {}", uuid::Uuid::new_v4()),

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs // file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs
// version: 17 // version: 19
//! Devnet classic SPL Token execution with stateful and canonical post-validation. //! Devnet classic SPL Token execution with stateful and canonical post-validation.
@@ -1189,24 +1189,12 @@ mod tests {
panic!("KS_SECRET_POSTGRES_TEST_URL is required for submission: {error}"); panic!("KS_SECRET_POSTGRES_TEST_URL is required for submission: {error}");
}, },
}; };
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let store_options = match ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL options failed: {error}"), std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL connection failed: {error}"),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> = let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(ks_lib::DcSplTokenDecoder)]; std::vec![std::sync::Arc::new(ks_lib::DcSplTokenDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>> = let materializers: std::vec::Vec<std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>> =

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs // file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs
// version: 16 // version: 18
//! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts. //! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts.
@@ -1520,28 +1520,16 @@ mod tests {
panic!("KS_SECRET_POSTGRES_TEST_URL is required for lifecycle submission: {error}"); panic!("KS_SECRET_POSTGRES_TEST_URL is required for lifecycle submission: {error}");
}, },
}; };
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"), std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
}; };
let store_options = match ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL options failed: {error}"), std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
}; };
let store = match ks_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("PostgreSQL connection failed: {error}"),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() { let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() {
std::option::Option::Some(value) => value, std::option::Option::Some(value) => value,
std::option::Option::None => panic!("workspace root cannot be resolved"), std::option::Option::None => panic!("workspace root cannot be resolved"),

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs // file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs
// version: 10 // version: 12
//! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions. //! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions.
@@ -529,23 +529,14 @@ mod tests {
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL") let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}")); .unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
profile.database.backend = "postgres".to_string(); crate::override_profile_store_url_for_test(&mut profile, database_url)
profile.database.postgres.url = database_url; .unwrap_or_else(|error| panic!("Store test override failed: {error}"));
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile) let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}")); .unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
let store_options = ks_store::PostgresStoreOptions::new( let store = match crate::open_profile_store(&profile).await {
profile.database.postgres.url.clone(), std::result::Result::Ok(value) => value,
profile.database.postgres.max_connections, std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
profile.database.postgres.connect_timeout_ms, };
false,
)
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
let store = ks_store::PostgresStore::connect(store_options)
.await
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}");
}
let configured_wallet_dir = let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str()); std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {

View File

@@ -1,8 +1,48 @@
<!-- file: ks-store/CHANGELOG.md --> <!-- file: ks-store/CHANGELOG.md -->
<!-- version: 19 --> <!-- version: 23 -->
# CHANGELOG — ks-store # CHANGELOG — ks-store
## `0.5.3-pre.002-delta-fix-003`
- corrige deux appels de helpers strictement privés PostgreSQL qui avaient été qualifiés à tort via `crate::`; ils restent locaux à leur module et ne sont pas réexportés au crate-root ;
- ne modifie aucun contrat public ni schéma SQL.
## `0.5.3-pre.002-delta-fix-002`
- remplace les visibilités `pub(in crate::postgres)` par la convention workspace : privé ou `pub(crate)` + réexport crate-root et consommation via `crate::Item` ;
- ajoute le canari Rust interdisant `pub(in ...)`/`pub(super)` et corrige les reexports `#[cfg(test)]` de `ks-pipeline-demo-scenarios` ;
- supprime les appels obsolètes à `Store::initialize_store_schema()` : linitialisation reste possédée par `Store::open()` via la configuration backend ;
- renomme structurellement les modules, fenêtres, commandes, HTML/TypeScript et DTO `demo_sql_*` en `demo_store_*`, sans attendre la refonte fonctionnelle finale de `pre.005` ;
- réconcilie le tracing PostgreSQL avec la règle dun target unique : `target: crate::TRACING_TARGET`, `backend="postgres"`, `domain="ks-store.pg"`, `action=...` ;
- supprime les helpers/constantes transitoires morts signalés par `cargo check`/Clippy et lattente Clippy devenue inutile.
## `0.5.3-pre.002-delta-fix-001`
- corrige les helpers PostgreSQL de validation de noms de tables rendus inaccessibles par la nouvelle frontière privée et aligne leurs appels/tests sur la façade interne `crate::postgres::*` ;
- supprime les réexports PostgreSQL internes devenus inutiles qui produisaient des warnings et conserve le target générique `ks-store` en l'utilisant réellement à l'ouverture de la façade `Store` ;
- aligne les nouveaux types publics/crate-publics introduits par `pre.002` sur la règle de qualification `crate::Item` dans leur crate propriétaire.
## `0.5.3-pre.002`
### Façade et configuration
- introduit `Store` et `StoreOpenOptions` comme frontière publique backend-agnostique et confine `PostgresStore`, `PostgresStoreOptions`, `PgPool`, les constantes physiques et les requêtes SQL à l'implémentation privée ;
- remplace les sections runtime PostgreSQL/SQLite imposées par un code `backend` et un objet `backend_options` opaque jusqu'à son interprétation par `ks-store`, sans créer de dépendance `ks-store -> ks-config` ;
- retire les quatre anciens traits/DTO historiques sans implémentation réelle et renomme les contrats publics de replay/diagnostic sans préfixe PostgreSQL ;
- conserve `Store` dans l'état applicatif desktop via une initialisation unique et réutilisable par les commandes/scénarios.
### Diagnostics et sécurité
- ajoute les résumés génériques de configuration, backend, initialisation et vérification par modèle, sans exposition des noms physiques de tables/index ni des options backend brutes ;
- masque le descripteur PostgreSQL et sanitise l'erreur de connexion afin qu'aucun DSN/password ne traverse les diagnostics normaux ;
- introduit le domaine structuré PostgreSQL `ks-store.pg` sous lunique `target: crate::TRACING_TARGET`, ainsi qu'un canari workspace empêchant les types PostgreSQL/SQLx de sortir de `ks-store` ;
- adapte les consommateurs Rust et les payloads desktop aux ressources/diagnostics génériques ; le renommage structurel `demo_sql_* -> demo_store_*` est finalisé par `delta-fix-002`, tandis que la refonte fonctionnelle finale reste à la tranche consommateurs.
### Portée
- ne modifie aucune table ni migration SQL de production : le namespace `k_sol_*`, `block_time`, la symétrie top-level/CPI et les ressources SQL atomiques restent réservés à `pre.003`.
## `0.5.3-pre.001` ## `0.5.3-pre.001`
### Audit / plan ### Audit / plan
@@ -16,7 +56,7 @@
- normalise le vocabulaire d'instruction vers `top-level`, conserve les CPI séparées et exige `stack_height`/parent CPI immédiat lorsque reconstructible ; - normalise le vocabulaire d'instruction vers `top-level`, conserve les CPI séparées et exige `stack_height`/parent CPI immédiat lorsque reconstructible ;
- ajoute un canari de gel future-decoder/Anchor-readiness : N1/N2/N3 doivent conserver les données Solana génériques nécessaires aux instructions/CPI, événements/logs, `returnData`, états/observations de comptes et provenance de schéma, sans ajouter de contrat `anchor_*` ; les 13 tables existantes restent une baseline et non un quota final si un manque générique est découvert avant gel ; - ajoute un canari de gel future-decoder/Anchor-readiness : N1/N2/N3 doivent conserver les données Solana génériques nécessaires aux instructions/CPI, événements/logs, `returnData`, états/observations de comptes et provenance de schéma, sans ajouter de contrat `anchor_*` ; les 13 tables existantes restent une baseline et non un quota final si un manque générique est découvert avant gel ;
- formalise la reconstruction `kb_sol_* -> k_sol_*`, l'arborescence `migrations/postgres/`, une instruction SQL par fichier exécutée via `include_str!`, l'audit des index/paginations/BIGINT et les règles de replay/invalidation ; - formalise la reconstruction `kb_sol_* -> k_sol_*`, l'arborescence `migrations/postgres/`, une instruction SQL par fichier exécutée via `include_str!`, l'audit des index/paginations/BIGINT et les règles de replay/invalidation ;
- définit la façade de stockage backend-agnostique, l'encapsulation de PostgreSQL, la stratégie de configuration backend opaque jusqu'à `ks-store`, le résumé runtime/init sans secrets, la future réutilisation d'un `Store` persistant dans `AppState`, la refonte backend-agnostique de `demo_sql`/`demo_config`/splash et les targets `ks-store.pg.*` ; - définit la façade de stockage backend-agnostique, l'encapsulation de PostgreSQL, la stratégie de configuration backend opaque jusqu'à `ks-store`, le résumé runtime/init sans secrets, la future réutilisation d'un `Store` persistant dans `AppState`, le renommage structurel `demo_sql_* -> demo_store_*`, la refonte backend-agnostique de `demo_config`/splash et le domaine de tracing PostgreSQL `ks-store.pg` sous lunique target `ks-store` ;
- ajoute [`docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md`](../docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md) ; - ajoute [`docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md`](../docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md) ;
- ne crée aucune table DEX/trading ni projection metadata N4 dans `0.5.3` ; - ne crée aucune table DEX/trading ni projection metadata N4 dans `0.5.3` ;
- considère `pre.007` comme premier candidat de clôture et autorise explicitement son décalage si des manques apparaissent ; - considère `pre.007` comme premier candidat de clôture et autorise explicitement son décalage si des manques apparaissent ;

View File

@@ -1,48 +1,56 @@
<!-- file: ks-store/README.md --> <!-- file: ks-store/README.md -->
<!-- version: 4 --> <!-- version: 6 -->
# ks-store # ks-store
`ks-store` consolide les contrats de stockage et limplémentation PostgreSQL de `khadhroony-bot3`. `ks-store` porte la frontière de persistance généraliste de Khadhroony Solana. Les consommateurs utilisent des contrats fonctionnels et une façade `Store` indépendante du moteur concret ; PostgreSQL reste l'implémentation opérationnelle de `0.5.3`, confinée à l'intérieur de la crate.
## Périmètre ## Périmètre
La crate expose : La crate expose :
- DTO et entités raw, observations, Core, décodage, matérialisation et ledger ; - `Store` et `StoreOpenOptions` comme façade d'ouverture et handle persistant backend-agnostique ;
- traits async de repositories indépendants du backend ; - les DTO et entités raw, observations, Core, décodage, matérialisation et ledger ;
- pagination et filtres bornés ; - les traits async `StoreHealthStore`, `RawTransactionStore`, `CoreTransactionStore`, `CoreExtractionStore` et `DecodePipelineStore` ;
- `PostgresStore` et ses options de connexion ; - les filtres et résultats de replay sans préfixe backend ;
- initialisation idempotente du schéma et migrations SQL ; - des diagnostics et résumés runtime/init sanitisés ne révélant ni secret ni nom physique d'objet ;
- diagnostics read-only, healthchecks et résumés de replay ; - des transactions atomiques pour extraction, décodage et matérialisation.
- transactions atomiques pour extraction, décodage et matérialisation.
## Frontière backend
`PostgresStore`, `PostgresStoreOptions`, `sqlx::PgPool`, les requêtes SQL et les noms physiques de tables sont privés à `ks-store`. `ks-pipeline`, les scénarios et les applications ne doivent connaître que `Store`, les repositories et les DTO fonctionnels.
`ks-store` ne dépend pas de `ks-config`. Un consommateur lui fournit un backend sélectionné et un objet `backend_options` opaque ; seul `ks-store` interprète et valide les paramètres PostgreSQL puis crée la connexion.
## Configuration et sécurité
`StoreOpenOptions` ne dérive pas `Debug` et ne rend pas les options backend accessibles après construction. Les résumés publics exposent uniquement des informations explicitement sanitisées : code backend, présence d'une connexion, état d'initialisation, health, modèle logique et compteurs.
Le backend PostgreSQL masque le descripteur de connexion et utilise lunique target de crate `ks-store`, complété par `backend="postgres"`, `domain="ks-store.pg"` et un champ `action` structuré. Aucun DSN, password ou valeur bindée n'est destiné aux diagnostics publics.
## Schéma pendant `0.5.3`
`0.5.3-pre.002` ne modifie pas encore les tables héritées `kb_sol_*`. Leur reconstruction sous `k_sol_*`, les ressources SQL atomiques et le gel N1/N2/N3 commencent en `pre.003` conformément au plan actif.
## Responsabilités ## Responsabilités
- garantir les invariants des données persistées ; - garantir les invariants des données persistées ;
- séparer contrats et implémentation PostgreSQL à lintérieur de la crate consolidée ; - posséder la connexion et l'initialisation du backend actif ;
- maintenir les tables canoniques `kb_sol_*` ; - fournir des frontières async typées au pipeline ;
- fournir au pipeline des frontières async typées ;
- protéger les opérations multi-tables par transactions ; - protéger les opérations multi-tables par transactions ;
- borner toutes les requêtes de diagnostic et de replay exposées. - borner les diagnostics et replay exposés ;
- permettre le remplacement futur de PostgreSQL sans réécriture des consommateurs.
## Hors périmètre ## Hors périmètre
La crate ne décode pas les instructions, nacquiert pas les transactions et ne décide pas quelle matérialisation exécuter. Elle persiste les résultats produits par `ks-lib` et orchestrés par `ks-pipeline`. La crate ne décode pas les instructions, n'acquiert pas les transactions et ne décide pas quelle matérialisation ou projection N4 exécuter. Elle persiste les faits produits par `ks-lib` et orchestrés par `ks-pipeline`.
## API publique
Les consommateurs utilisent principalement `PostgresStore`, `PostgresStoreOptions`, les traits `*Store`, les DTO `*Insert`/`*Row`, les filtres de replay et les diagnostics. Les constantes de tables et fonctions de validation sont publiques pour les audits et outils dadministration.
## Relations ## Relations
- dépend de `ks-core` pour les erreurs ; - dépend de `ks-core` pour les erreurs ;
- utilise les contrats de replay de `ks-lib` ; - utilise les contrats de replay de `ks-lib` ;
- est consommée principalement par `ks-pipeline`, les scénarios de démonstration et le desktop. - est consommée principalement par `ks-pipeline`, `ks-pipeline-demo-scenarios` et `kb-app-demo-desktop` ;
- ne dépend pas de `ks-config`.
## Statut
Le stockage consolidé dispose de tests unitaires étendus et de tests PostgreSQL optionnels pilotés par environnement. Les opérations réelles exigent un serveur PostgreSQL et lapplication préalable des migrations.
## Documents ## Documents
@@ -50,3 +58,4 @@ Le stockage consolidé dispose de tests unitaires étendus et de tests PostgreSQ
- [Travaux restants](TODO.md) - [Travaux restants](TODO.md)
- [Historique](CHANGELOG.md) - [Historique](CHANGELOG.md)
- [Architecture du stockage](../docs/architecture/STORAGE_ARCHITECTURE.md) - [Architecture du stockage](../docs/architecture/STORAGE_ARCHITECTURE.md)
- [Plan actif `0.5.3`](../docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md)

View File

@@ -1,18 +1,16 @@
<!-- file: ks-store/TODO.md --> <!-- file: ks-store/TODO.md -->
<!-- version: 11 --> <!-- version: 13 -->
# TODO — ks-store # TODO — ks-store
## Série `0.5.x` ## Série `0.5.x`
- [ ] `0.5.3` — introduire une façade `Store` backend-agnostique, rendre PostgreSQL/SQLx privés et supprimer les contrats publics historiques non implémentés.
- [ ] `0.5.3` — remodeler la configuration store afin que le backend sélectionné et ses options restent opaques jusqu'à leur validation/interprétation par `ks-store`.
- [ ] `0.5.3` — reconstruire `kb_sol_* -> k_sol_*`, ajouter `block_time` aux ancres raw/Core, conserver top-level/CPI dans deux tables Core avec lifecycle/replay équivalents, `stack_height`/parent CPI audités et renommer le journal N3 en `k_sol_mat_outputs`. - [ ] `0.5.3` — reconstruire `kb_sol_* -> k_sol_*`, ajouter `block_time` aux ancres raw/Core, conserver top-level/CPI dans deux tables Core avec lifecycle/replay équivalents, `stack_height`/parent CPI audités et renommer le journal N3 en `k_sol_mat_outputs`.
- [ ] `0.5.3` — déplacer les ressources sous `migrations/postgres/`, imposer une instruction SQL par fichier avec exécution privée via `include_str!`, séparer tables/keys/index/drop/truncate et supprimer le DDL dupliqué en Rust. - [ ] `0.5.3` — déplacer les ressources sous `migrations/postgres/`, imposer une instruction SQL par fichier avec exécution privée via `include_str!`, séparer tables/keys/index/drop/truncate et supprimer le DDL dupliqué en Rust.
- [ ] `0.5.3` — corriger l'invalidation descendante, formaliser les replays N1 -> N2 / N2 -> N3 / frontière N3 -> N4, les paginations/limites, les conversions BIGINT et les index justifiés par les requêtes réelles. - [ ] `0.5.3` — corriger l'invalidation descendante, formaliser les replays N1 -> N2 / N2 -> N3 / frontière N3 -> N4, les paginations/limites, les conversions BIGINT et les index justifiés par les requêtes réelles.
- [ ] `0.5.3`migrer `ks-pipeline-demo-scenarios` et `kb-app-demo-desktop` vers les API génériques sans symboles `Postgres*`, conserver un `Store` générique dans létat applicatif, fournir un résumé runtime/init sans secrets et réconcilier `demo_sql`/`demo_config`/splash sans noms d'objets physiques. - [ ] `0.5.3`finaliser en `pre.005` la présentation fonctionnelle des surfaces `demo_store_*`, `demo_config` et le splash autour des modèles/compteurs du nouveau baseline sans noms d'objets physiques ; le renommage technique `demo_sql_* -> demo_store_*` est réalisé en `pre.002`.
- [ ] `0.5.3` — exécuter avant gel le canari future-decoder/Anchor-readiness sur N1/N2/N3 : bytes d'instruction, comptes ordonnés/flags, hiérarchie CPI, logs, `returnData`, observations/états de comptes et provenance decoder/schema ; ajouter seulement les contrats Solana génériques manquants, sans table `anchor_*`. - [ ] `0.5.3` — exécuter avant gel le canari future-decoder/Anchor-readiness sur N1/N2/N3 : bytes d'instruction, comptes ordonnés/flags, hiérarchie CPI, logs, `returnData`, observations/états de comptes et provenance decoder/schema ; ajouter seulement les contrats Solana génériques manquants, sans table `anchor_*`.
- [ ] `0.5.3` — produire le snapshot machine-readable des tables N1/N2/N3 réellement stabilisées après ce canari (13 tables existantes de baseline, plus éventuels contrats génériques nécessaires), documenter la politique N4 plus souple et les futures projections metadata asset/token + program metadata, puis ajouter les canaris anti-`kb_sol_*`, anti-backend leak, anti-secret et logging `ks-store.pg.*`. - [ ] `0.5.3` — produire le snapshot machine-readable des tables N1/N2/N3 réellement stabilisées après ce canari (13 tables existantes de baseline, plus éventuels contrats génériques nécessaires), documenter la politique N4 plus souple et les futures projections metadata asset/token + program metadata, puis ajouter les canaris anti-`kb_sol_*`, anti-backend leak, anti-secret et logging sous le target canonique `ks-store` avec domaine structuré `ks-store.pg`.
- [ ] PostgreSQL - auditer le dimensionnement du pool selon les charges réelles. - [ ] PostgreSQL - auditer le dimensionnement du pool selon les charges réelles.
- [ ] Résilience - décider et implémenter, si justifié, un retry borné pour les timeouts transitoires. - [ ] Résilience - décider et implémenter, si justifié, un retry borné pour les timeouts transitoires.
- [ ] Administration - définir les outils supplémentaires prévus par le ROADMAP avant leur implémentation. - [ ] Administration - définir les outils supplémentaires prévus par le ROADMAP avant leur implémentation.

View File

@@ -1,90 +1,79 @@
<!-- file: ks-store/USAGE.md --> <!-- file: ks-store/USAGE.md -->
<!-- version: 4 --> <!-- version: 5 -->
# Utilisation de ks-store # Utilisation de ks-store
## Connexion PostgreSQL ## 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 ```rust
let options = match ks_store::PostgresStoreOptions::new( let options = match ks_store::StoreOpenOptions::new(
database_url,
10,
10_000,
true, true,
"postgres",
serde_json::json!({
"url": database_url,
"max_connections": 10,
"connect_timeout_ms": 10_000,
"auto_initialize_schema": true
}),
) { ) {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
let store = match ks_store::Store::open(options).await {
println!("postgres endpoint: {}", options.masked_dsn());
let store = match ks_store::PostgresStore::connect(options).await {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
``` ```
`PostgresStoreOptions::new` valide le DSN, le nombre de connexions et le timeout. Lorsque `auto_initialize_schema` vaut `true`, la connexion applique les schémas idempotents. Utiliser `masked_dsn` ou `mask_postgres_dsn` dans les logs ; ne jamais journaliser le DSN brut. `Store::open` interprète les options du backend actif, crée la connexion, exécute l'auto-initialisation lorsqu'elle est configurée et capture un résumé d'initialisation sanitisé. PostgreSQL reste le seul backend opérationnel exigé en `0.5.3-pre.002`; un code backend inconnu est refusé proprement.
## Diagnostics ## Résumés sûrs
```rust ```rust
let health = store.health_snapshot().await; let configuration = store.configuration_summary();
let migrations = store.migration_snapshot().await; let initialization = store.initialization_summary();
let backend = store.backend_diagnostics().await; let runtime = match store.runtime_summary().await {
println!("health={:?}", health.status);
println!("migration={:?}", migrations.status);
println!("backend={:?}", backend.descriptor.backend);
```
Les diagnostics de tables raw, Core et decode/materialization sont également disponibles par les méthodes `*_table_diagnostics`.
```rust
let raw_tables = match store.raw_table_diagnostics().await {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
```
for table in raw_tables { 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. Il expose aussi `objects`, une liste de catégories physiques sans noms d'objets : PostgreSQL publie déjà le total `table`, tandis que `pre.003` ajoutera le total `index` avec le nouveau baseline. Les noms physiques de tables, index et contraintes restent internes au backend.
## 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!( println!(
"table={} domain={} exists={}", "resource={} model={} available={}",
table.table_name, resource.resource_code,
table.domain, resource.model_code,
table.exists resource.available
); );
} }
``` ```
## Pagination bornée Les méthodes `core_resource_diagnostics`, `decode_resource_diagnostics` et `known_resource_diagnostics` suivent le même contrat backend-agnostique.
```rust
let first_page = ks_store::PageRequest::first_page();
let next_page = match ks_store::PageRequest::new(250, 250) {
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);
```
## Contrats de repositories ## Contrats de repositories
Les traits publics principaux sont : Les traits publics effectivement implémentés sont :
- `StoreHealthStore` ; - `StoreHealthStore` ;
- `RawTransactionStore` ; - `RawTransactionStore` ;
- `CoreTransactionStore` ; - `CoreTransactionStore` ;
- `CoreExtractionStore` ; - `CoreExtractionStore` ;
- `DecodePipelineStore` ; - `DecodePipelineStore`.
- `ProgramObservationStore` ;
- `DecodedEventStore` ;
- `MaterializedEventStore` ;
- `ProcessingLedgerStore`.
Ils permettent au pipeline de dépendre dun contrat async plutôt que dune requête SQL directe. `Store` implémente ces traits et délègue au backend privé. Les anciens traits historiques sans implémentation réelle ont été retirés avant gel de l'API.
## Replay et requêtes bornées ## Replay et requêtes bornées
@@ -93,71 +82,43 @@ let candidates = match store.replay_transaction_candidates(filter).await {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
for candidate in candidates {
println!(
"signature={} slot={}",
candidate.signature,
candidate.slot
);
}
```
```rust
let programs = match store.replay_program_summaries(program_filter).await { let programs = match store.replay_program_summaries(program_filter).await {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
let entities = match store.replay_entity_summaries(entity_filter).await { let entities = match store.replay_entity_summaries(entity_filter).await {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
println!("programs={}, entities={}", programs.len(), entities.len());
``` ```
Les filtres refusent les limites nulles ou supérieures aux bornes publiques. `PageRequest` impose également `DEFAULT_PAGE_SIZE` et `MAX_PAGE_SIZE`. Les contrats publics utilisent `ReplayTransaction*`, `ReplayProgram*` et `ReplayEntity*`. Le scope d'instruction utilise désormais `TopLevel`, `Inner` et `Logs`; le terme historique `outer` n'est pas introduit dans la nouvelle API.
## Pagination bornée
```rust
let first_page = ks_store::PageRequest::first_page();
let next_page = match ks_store::PageRequest::new(250, 250) {
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);
```
La refonte cursorisée prévue par le plan `0.5.3` reste dans la tranche repositories/replay ultérieure.
## Transactions atomiques ## Transactions atomiques
Les bundles `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 isolées. Les bundles `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.
## Validation des noms de tables ## Tests PostgreSQL réels
```rust 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.
if let Err(error) = ks_store::validate_raw_store_table_names() {
return Err(error);
}
if let Err(error) = ks_store::validate_core_store_table_names() {
return Err(error);
}
if let Err(error) = ks_store::validate_decode_store_table_names() {
return Err(error);
}
assert!(ks_store::is_valid_solana_table_name( ## Limites de `pre.002`
ks_store::CORE_TRANSACTIONS_TABLE_NAME
));
```
## Schéma et tables - les tables restent temporairement sous leur namespace historique jusqu'à `pre.003` ;
- les migrations PostgreSQL ne sont pas encore déplacées sous `migrations/postgres/` ;
Les constantes `RAW_STORE_TABLE_NAMES`, `CORE_STORE_TABLE_NAMES` et `DECODE_STORE_TABLE_NAMES` exposent les noms canoniques. Les fonctions `validate_*_table_names` et `is_valid_solana_table_name` servent aux audits et outils dadministration. - la pagination cursorisée et l'invalidation descendante sont traitées dans la tranche repositories/replay ;
- la refonte complète des fenêtres desktop `demo_store_*` est prévue dans la tranche consommateurs.
## Erreurs et invariants
Toutes les APIs utilisent `ks_core::Result`. Les DTO valident notamment les signatures, chemins, Program IDs, clés, montants, états de traitement et identités de ledger. Les erreurs de contrat peuvent être construites avec `storage_contract_error`.
## Tests instructifs
- les tests `*_rejects_*` documentent les invariants des DTO et filtres ;
- `decoded_events_and_ledger_roll_back_together` vérifie latomicité ;
- `optional_postgres_*_from_env` couvre les parcours réels lorsque lenvironnement PostgreSQL est configuré ;
- les tests de schéma vérifient les noms, index, contraintes et migrations.
## Limites
- backend actif : PostgreSQL ;
- les tests réels sont optionnels sans DSN de test ;
- la crate ne prend aucune décision de décodage ou de matérialisation.

View File

@@ -1,7 +1,9 @@
// file: ks-store/src/constants.rs // file: ks-store/src/constants.rs
// version: 2 // version: 5
//! Local constants for the `ks-store` crate. //! Local constants for the `ks-store` crate.
/// Canonical tracing target for this crate. /// Canonical tracing target for backend-independent storage operations.
pub(crate) const TRACING_TARGET: &str = "ks-store"; pub(crate) const TRACING_TARGET: &str = "ks-store";
/// Transitional schema contract identifier used until the `pre.003` baseline rebuild.
pub const STORE_SCHEMA_CONTRACT_VERSION: &str = "0.5.3-pre.2-legacy-schema";

View File

@@ -1,5 +1,5 @@
// file: ks-store/src/contracts.rs // file: ks-store/src/contracts.rs
// version: 3 // version: 6
//! Backend-neutral storage contracts used by pipeline crates. //! Backend-neutral storage contracts used by pipeline crates.
@@ -8,6 +8,7 @@ mod entity;
mod error; mod error;
mod health; mod health;
mod pagination; mod pagination;
mod replay;
mod repository; mod repository;
/// Core account key insert contract. /// Core account key insert contract.
@@ -52,8 +53,6 @@ pub use self::dto::DecodeObservationInsert;
pub use self::dto::DecodePersistenceBundle; pub use self::dto::DecodePersistenceBundle;
/// Bounded contextual instruction selection filter for decode campaigns. /// Bounded contextual instruction selection filter for decode campaigns.
pub use self::dto::DecodeSelectionFilter; pub use self::dto::DecodeSelectionFilter;
/// Decoded event insert contract.
pub use self::dto::DecodedEventInsert;
/// Insert or upsert result contract returned by repositories. /// Insert or upsert result contract returned by repositories.
pub use self::dto::InsertOutcome; pub use self::dto::InsertOutcome;
/// Maximum number of materialized rows returned by one bounded query. /// Maximum number of materialized rows returned by one bounded query.
@@ -62,16 +61,12 @@ pub use self::dto::MAX_MATERIALIZED_EVENT_QUERY_ROWS;
pub use self::dto::MaterializationPersistenceBundle; pub use self::dto::MaterializationPersistenceBundle;
/// Bounded read-only materialized event selection. /// Bounded read-only materialized event selection.
pub use self::dto::MaterializedEventFilter; pub use self::dto::MaterializedEventFilter;
/// Materialized event insert contract.
pub use self::dto::MaterializedEventInsert;
/// One materialized output returned by a bounded query. /// One materialized output returned by a bounded query.
pub use self::dto::MaterializedEventQueryRow; pub use self::dto::MaterializedEventQueryRow;
/// One processor-owned materialized output row. /// One processor-owned materialized output row.
pub use self::dto::MaterializedOutputInsert; pub use self::dto::MaterializedOutputInsert;
/// Stable processing ledger identity. /// Stable processing ledger identity.
pub use self::dto::ProcessingLedgerIdentity; pub use self::dto::ProcessingLedgerIdentity;
/// Processing ledger mark request contract.
pub use self::dto::ProcessingLedgerMark;
/// Stable processing ledger status. /// Stable processing ledger status.
pub use self::dto::ProcessingLedgerStatus; pub use self::dto::ProcessingLedgerStatus;
/// Raw payload lifecycle mark request. /// Raw payload lifecycle mark request.
@@ -84,12 +79,30 @@ pub use self::dto::RawPayloadRetentionState;
pub use self::dto::RawTransactionInsert; pub use self::dto::RawTransactionInsert;
/// Store backend diagnostic contract. /// Store backend diagnostic contract.
pub use self::dto::StoreBackendDescriptor; pub use self::dto::StoreBackendDescriptor;
/// Store backend kind contract. /// Backend-neutral diagnostic snapshot.
pub use self::dto::StoreBackendKind; pub use self::dto::StoreBackendDiagnostics;
/// Store configuration summary safe for UI and logs.
pub use self::dto::StoreConfigurationSummary;
/// Store initialization status.
pub use self::dto::StoreInitializationStatus;
/// Initialization and model-verification summary captured while opening a store.
pub use self::dto::StoreInitializationSummary;
/// Store migration diagnostic snapshot contract. /// Store migration diagnostic snapshot contract.
pub use self::dto::StoreMigrationSnapshot; pub use self::dto::StoreMigrationSnapshot;
/// Store migration status contract. /// Store migration status contract.
pub use self::dto::StoreMigrationStatus; pub use self::dto::StoreMigrationStatus;
/// Store model verification status.
pub use self::dto::StoreModelVerificationStatus;
/// Verification summary for one logical store model.
pub use self::dto::StoreModelVerificationSummary;
/// Count summary for one backend object category without exposing physical names.
pub use self::dto::StoreObjectVerificationSummary;
/// Read-only diagnostics for one logical store resource.
pub use self::dto::StoreResourceDiagnostics;
/// Read-only statistics for one logical store resource.
pub use self::dto::StoreResourceStatistics;
/// Complete runtime summary safe for application diagnostics.
pub use self::dto::StoreRuntimeSummary;
/// Transaction acquisition observation insert contract. /// Transaction acquisition observation insert contract.
pub use self::dto::TransactionObservationInsert; pub use self::dto::TransactionObservationInsert;
/// Transaction acquisition observation origin. /// Transaction acquisition observation origin.
@@ -108,12 +121,6 @@ pub use self::entity::CoreInstructionRow;
pub use self::entity::CoreLogRow; pub use self::entity::CoreLogRow;
/// Core transaction SQL-like row contract. /// Core transaction SQL-like row contract.
pub use self::entity::CoreTransactionRow; pub use self::entity::CoreTransactionRow;
/// Decoded event SQL-like row contract.
pub use self::entity::DecodedEventRow;
/// Materialized event SQL-like row contract.
pub use self::entity::MaterializedEventRow;
/// Processing ledger SQL-like row contract.
pub use self::entity::ProcessingLedgerRow;
/// Canonical raw Solana transaction SQL-like row contract. /// Canonical raw Solana transaction SQL-like row contract.
pub use self::entity::RawTransactionRow; pub use self::entity::RawTransactionRow;
/// Transaction acquisition observation SQL-like row contract. /// Transaction acquisition observation SQL-like row contract.
@@ -132,20 +139,30 @@ pub use self::pagination::MAX_PAGE_SIZE;
pub use self::pagination::PageRequest; pub use self::pagination::PageRequest;
/// Sort direction for repository list operations. /// Sort direction for repository list operations.
pub use self::pagination::SortDirection; pub use self::pagination::SortDirection;
/// Maximum number of rows returned by one replay candidate query.
pub use self::replay::MAX_REPLAY_CANDIDATE_ROWS;
/// Bounded read-only filter for core entity summaries.
pub use self::replay::ReplayEntityFilter;
/// Core entity kind used while filtering replay candidates.
pub use self::replay::ReplayEntityKind;
/// Aggregated mint, owner or account-key occurrences from Core facts.
pub use self::replay::ReplayEntitySummary;
/// Bounded read-only filter for program summaries.
pub use self::replay::ReplayProgramFilter;
/// Program occurrence scope used while filtering replay candidates.
pub use self::replay::ReplayProgramScope;
/// Aggregated program occurrences across top-level instructions, inner instructions and linked logs.
pub use self::replay::ReplayProgramSummary;
/// One raw transaction candidate enriched with Core and processing-ledger diagnostics.
pub use self::replay::ReplayTransactionCandidate;
/// Bounded read-only filter for transaction replay candidates.
pub use self::replay::ReplayTransactionFilter;
/// Canonical transaction to core extraction storage behavior. /// Canonical transaction to core extraction storage behavior.
pub use self::repository::CoreExtractionStore; pub use self::repository::CoreExtractionStore;
/// Core Solana storage behavior. /// Core Solana storage behavior.
pub use self::repository::CoreTransactionStore; pub use self::repository::CoreTransactionStore;
/// Contextual instruction decode and materialization storage behavior. /// Contextual instruction decode and materialization storage behavior.
pub use self::repository::DecodePipelineStore; pub use self::repository::DecodePipelineStore;
/// Decoded event storage behavior.
pub use self::repository::DecodedEventStore;
/// Materialized event storage behavior.
pub use self::repository::MaterializedEventStore;
/// Processing ledger storage behavior.
pub use self::repository::ProcessingLedgerStore;
/// Program observation storage behavior.
pub use self::repository::ProgramObservationStore;
/// Raw transaction storage behavior. /// Raw transaction storage behavior.
pub use self::repository::RawTransactionStore; pub use self::repository::RawTransactionStore;
/// Store health storage behavior. /// Store health storage behavior.

Some files were not shown because too many files have changed in this diff Show More