v0.5.3-pre.002
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: Cargo.toml
|
||||
# version: 68
|
||||
# version: 69
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.3-pre.1"
|
||||
version = "0.5.3-pre.2"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: config/README.md -->
|
||||
<!-- version: 26 -->
|
||||
<!-- version: 27 -->
|
||||
|
||||
# Configuration locale
|
||||
|
||||
@@ -111,7 +111,7 @@ Les defaults portent les timeouts, capacités de channels et `auto_reconnect`. U
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -6,19 +6,11 @@
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"backend_options": {
|
||||
"url": "${KS_SECRET_POSTGRES_DEVNET_URL}",
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 5000,
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,85 +59,17 @@
|
||||
"required": [
|
||||
"enabled",
|
||||
"backend",
|
||||
"postgres",
|
||||
"sqlite"
|
||||
"backend_options"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"max_connections": {
|
||||
"type": "integer",
|
||||
"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"
|
||||
"backend_options": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,85 +31,17 @@
|
||||
"required": [
|
||||
"enabled",
|
||||
"backend",
|
||||
"postgres",
|
||||
"sqlite"
|
||||
"backend_options"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"max_connections": {
|
||||
"type": "integer",
|
||||
"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"
|
||||
"backend_options": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,19 +6,11 @@
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"backend_options": {
|
||||
"url": "${KS_SECRET_POSTGRES_DEVNET_URL}",
|
||||
"max_connections": 8,
|
||||
"connect_timeout_ms": 5000,
|
||||
"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": {
|
||||
"enabled": true,
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"backend_options": {
|
||||
"url": "${KS_SECRET_POSTGRES_MAINNET_URL}",
|
||||
"max_connections": 16,
|
||||
"connect_timeout_ms": 5000,
|
||||
"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": {
|
||||
"enabled": true,
|
||||
"backend": "postgres",
|
||||
"postgres": {
|
||||
"backend_options": {
|
||||
"url": "${KS_SECRET_POSTGRES_MAINNET_URL}",
|
||||
"max_connections": 16,
|
||||
"connect_timeout_ms": 5000,
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/DEVNET_EXECUTION_GUIDE.md -->
|
||||
<!-- version: 27 -->
|
||||
<!-- version: 28 -->
|
||||
|
||||
# Guide d’exécution Devnet
|
||||
|
||||
@@ -30,7 +30,7 @@ Les commandes réutilisables sont centralisées en section 4. Chaque scénario r
|
||||
- le wallet temporaire persistant n’est jamais copié dans les logs ou les preuves ;
|
||||
- le financement est réalisé par un faucet Web Devnet, pas par `solana airdrop`.
|
||||
|
||||
Lorsque `database.postgres.auto_initialize_schema` vaut `true`, l’ouverture d’une fenêtre Devnet crée les tables manquantes. Lorsqu’il vaut `false`, le schéma doit être préparé avant l’ouverture.
|
||||
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 l’option backend `auto_initialize_schema` vaut `true`, l’ouverture persistante du `Store` peut initialiser le schéma du backend actif. Lorsqu’elle vaut `false`, le modèle de stockage doit déjà être disponible.
|
||||
|
||||
## 3. Organisation des terminaux
|
||||
|
||||
@@ -81,7 +81,7 @@ Attendre avant tout scénario :
|
||||
- le démarrage de Vite ;
|
||||
- le lancement du binaire `kb-app-demo-desktop` ;
|
||||
- l’ouverture 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`.
|
||||
|
||||
Ne pas exécuter les commandes `C06` ou `C07` dans ce terminal.
|
||||
@@ -330,7 +330,7 @@ Un scénario n’est validé que si le replay ne produit ni échec fonctionnel n
|
||||
1. Dans le terminal B, exécuter `C01`, `C02`, `C03` et `C04`.
|
||||
2. Dans le terminal B, exécuter `C05` pour repartir d’une base propre.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/STORAGE_ARCHITECTURE.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Architecture du stockage
|
||||
|
||||
@@ -7,30 +7,38 @@
|
||||
|
||||
`ks-store` réunit :
|
||||
|
||||
- la façade publique backend-agnostique `Store` et ses options d'ouverture ;
|
||||
- les contrats store-neutral ;
|
||||
- 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 ;
|
||||
- l’implémentation PostgreSQL ;
|
||||
- les migrations, requêtes et mécanismes de replay associés.
|
||||
- les résumés runtime/init sanitisés ;
|
||||
- l’implémentation PostgreSQL privée ;
|
||||
- les migrations et requêtes appartenant au backend actif.
|
||||
|
||||
La consolidation remplace l’ancien 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.1 Contrats store-neutral
|
||||
|
||||
Les contrats ne doivent pas dépendre des détails SQL lorsqu’une 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 lorsqu’une 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
|
||||
|
||||
Le module PostgreSQL possède :
|
||||
|
||||
- la validation de ses options ;
|
||||
- la connexion et l’initialisation ;
|
||||
- l’application idempotente des migrations ;
|
||||
- les requêtes typées ;
|
||||
- les diagnostics ;
|
||||
- les opérations de replay et de sélection de candidats.
|
||||
- les diagnostics physiques ;
|
||||
- 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
|
||||
|
||||
@@ -38,14 +46,14 @@ Les modèles métier communs restent dans `ks-lib` lorsqu’ils dépassent la se
|
||||
|
||||
## 3. Catégories de données
|
||||
|
||||
Le stockage couvre plusieurs niveaux :
|
||||
Le stockage distingue quatre niveaux durables :
|
||||
|
||||
- données brutes acquises ;
|
||||
- transactions et instructions canoniques ;
|
||||
- événements de décodage et diagnostics ;
|
||||
- matérialisations ;
|
||||
- états de campagne et candidats de replay ;
|
||||
- informations opérationnelles et de santé.
|
||||
- **N1** — acquisition et raw canonique ;
|
||||
- **N2** — Core Solana canonique ;
|
||||
- **N3** — decode/materialization générique et journaux versionnés ;
|
||||
- **N4** — projections spécialisées/queryables additives.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
<!-- file: docs/guides/POSTGRES_STORAGE.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# Guide PostgreSQL et contrats de stockage
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
let options = match ks_store::PostgresStoreOptions::new(
|
||||
database_url,
|
||||
10,
|
||||
10_000,
|
||||
let options = match ks_store::StoreOpenOptions::new(
|
||||
true,
|
||||
"postgres",
|
||||
serde_json::json!({
|
||||
"url": database_url,
|
||||
"max_connections": 10,
|
||||
"connect_timeout_ms": 10_000,
|
||||
"auto_initialize_schema": true
|
||||
}),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let store = match ks_store::PostgresStore::connect(options).await {
|
||||
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),
|
||||
};
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -37,7 +42,9 @@ Toujours utiliser `masked_dsn()` dans les diagnostics.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -47,15 +54,20 @@ Aucune migration SQL automatique de ces valeurs n'est ajoutée en `0.5.1` : avan
|
||||
|
||||
## Repositories
|
||||
|
||||
Les traits publics séparent le contrat de l’implé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 à l’inté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
|
||||
|
||||
La surface publique expose :
|
||||
|
||||
- health snapshot ;
|
||||
- migration snapshot ;
|
||||
- backend diagnostics ;
|
||||
- diagnostics des tables raw, Core et decode ;
|
||||
- validation des noms de tables.
|
||||
- backend descriptor masqué ;
|
||||
- résumé de configuration sanitisé ;
|
||||
- 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 l’unique `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
|
||||
|
||||
@@ -63,7 +75,7 @@ Les traits publics séparent le contrat de l’implémentation PostgreSQL. Les o
|
||||
- les écritures rejouables doivent être idempotentes ;
|
||||
- la progression de campagne doit rester cohérente avec les lignes effectivement traitées ;
|
||||
- les requêtes dynamiques n’acceptent 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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- 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`
|
||||
|
||||
@@ -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` ;
|
||||
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 ;
|
||||
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 ;
|
||||
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 ;
|
||||
@@ -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 ;
|
||||
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 ;
|
||||
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 ;
|
||||
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.
|
||||
@@ -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 ;
|
||||
- é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
|
||||
|
||||
Le domaine racine retenu est :
|
||||
|
||||
```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
|
||||
```
|
||||
Le target Rust reste l’unique `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 = ...`.
|
||||
|
||||
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 ;
|
||||
- le texte SQL brut peut être journalisé à `trace` pour le diagnostic ;
|
||||
- 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 ;
|
||||
- remodeler le bloc store config en backend + options sélectionnées/opaques ;
|
||||
- déplacer toute validation/interprétation PostgreSQL dans `ks-store` ;
|
||||
- introduire les targets `ks-store.pg.*` et les canaris backend/secret ;
|
||||
- introduire l’instrumentation 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 ;
|
||||
- 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 ;
|
||||
- couvrir les bornes BIGINT ;
|
||||
- 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` ;
|
||||
- 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 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 ;
|
||||
@@ -1346,7 +1332,7 @@ La version ne peut être clôturée que si :
|
||||
- les index ajoutés correspondent à des requêtes réelles ;
|
||||
- aucune API publique de `ks-store` n'expose PostgreSQL ou SQLx ;
|
||||
- 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 ;
|
||||
- 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 ;
|
||||
@@ -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 ;
|
||||
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` ;
|
||||
11. résumé runtime/init sans secrets exposé à `kb-app-demo-desktop` et réconciliation future de `demo_sql` ;
|
||||
12. logging PostgreSQL sous `ks-store.pg.*`, sans secrets ni valeurs bindées par défaut ;
|
||||
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 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 ;
|
||||
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 ;
|
||||
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 ;
|
||||
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 ;
|
||||
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 ;
|
||||
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`.
|
||||
|
||||
|
||||
## 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 d’ouverture et d’utilisation 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 l’auto-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é d’initialisation ;
|
||||
- résumé runtime complet.
|
||||
|
||||
Le résumé d’initialisation 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 d’objets 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 d’autres 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_*`, l’alignement 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é d’initialisation 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 l’expérience tabulaire, de `demo_config` et du splash autour du nouveau baseline.
|
||||
|
||||
### 32.6 Logging et sécurité
|
||||
|
||||
Le backend PostgreSQL utilise l’unique 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 l’implé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 qu’un backend inconnu, un descriptor masqué et les résumés publics ne recopient pas des options contenant un secret.
|
||||
|
||||
Le nettoyage fin de l’instrumentation 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 ;
|
||||
- n’implémente pas Anchor.
|
||||
|
||||
Ces travaux restent affectés aux tranches `pre.003+` prévues par le présent plan.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_RUST.md -->
|
||||
<!-- version: 8 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# Règles Rust générales
|
||||
|
||||
@@ -101,6 +101,7 @@ implicit_saturating_sub = "allow"
|
||||
## 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 d’entré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(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.
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
|
||||
<!-- version: 60 -->
|
||||
<!-- version: 64 -->
|
||||
|
||||
# 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 d’un 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`
|
||||
|
||||
- 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 ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/README.md -->
|
||||
<!-- version: 21 -->
|
||||
<!-- version: 23 -->
|
||||
|
||||
# kb-app-demo-desktop
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- configuration et diagnostics de logging ;
|
||||
- fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création d’un 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 d’un `.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 ;
|
||||
- diagnostics PostgreSQL ;
|
||||
- diagnostics de stockage backend-agnostiques ;
|
||||
- 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 ;
|
||||
- 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`.
|
||||
|
||||
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.
|
||||
|
||||
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é.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<!-- file: kb-app-demo-desktop/TODO.md -->
|
||||
<!-- version: 20 -->
|
||||
<!-- version: 22 -->
|
||||
|
||||
# TODO — kb-app-demo-desktop
|
||||
|
||||
## É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`.
|
||||
- [ ] 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/USAGE.md -->
|
||||
<!-- version: 21 -->
|
||||
<!-- version: 24 -->
|
||||
|
||||
# Utilisation de kb-app-demo-desktop
|
||||
|
||||
@@ -116,7 +116,7 @@ Les autres fenêtres sont ouvertes par les commandes Tauri dédiées :
|
||||
- backfill ;
|
||||
- HTTP ;
|
||||
- WebSocket ;
|
||||
- diagnostics SQL ;
|
||||
- diagnostics store ;
|
||||
- candidats replay ;
|
||||
- configuration ;
|
||||
- wallets ;
|
||||
@@ -311,22 +311,24 @@ L’inventaire 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 d’exécution reste sous la grille, sur toute la largeur.
|
||||
|
||||
### Diagnostics PostgreSQL
|
||||
### Diagnostics du store
|
||||
|
||||
```typescript
|
||||
const diagnostics = await invoke(
|
||||
"load_demo_sql_diag"
|
||||
"load_demo_store_diag"
|
||||
);
|
||||
const rawTables = await invoke(
|
||||
"load_demo_sql_pg_raw"
|
||||
"load_demo_store_raw"
|
||||
);
|
||||
const coreTables = await invoke(
|
||||
"load_demo_sql_pg_core"
|
||||
"load_demo_store_core"
|
||||
);
|
||||
|
||||
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
|
||||
|
||||
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 ATA, SPL Token et Token-2022 ;
|
||||
- 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 ;
|
||||
- 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 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 ;
|
||||
- d’afficher 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.
|
||||
|
||||
@@ -378,7 +380,7 @@ Les scénarios synthétiques restent disponibles pour inspecter les contrats san
|
||||
|
||||
### 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 ;
|
||||
- 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
|
||||
|
||||
Ouvrir l’accordé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 l’accordé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
|
||||
Initialize -> UpdateField -> Emit -> RemoveKey -> UpdateAuthority
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
"demo_backfill",
|
||||
"demo_core_extraction",
|
||||
"demo_decode_replay",
|
||||
"demo_sql_diag",
|
||||
"demo_sql_pg_raw",
|
||||
"demo_sql_pg_core",
|
||||
"demo_sql_replay_candidates",
|
||||
"demo_store_diag",
|
||||
"demo_store_raw",
|
||||
"demo_store_core",
|
||||
"demo_store_replay_candidates",
|
||||
"demo_execution_solana_core",
|
||||
"demo_execution_spl",
|
||||
"demo_execution_metadata"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_core_extraction.html -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
@@ -163,4 +163,4 @@
|
||||
<script defer="" src="ts/demo_core_extraction.ts" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_decode_replay.html -->
|
||||
<!-- version: 8 -->
|
||||
<!-- version: 9 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<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 class="col-12 col-lg-6">
|
||||
<label class="form-label" for="decodeReplayPathsTextarea">Instruction paths exacts, un par ligne</label>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_sql_pg_core.html -->
|
||||
<!-- version: 6 -->
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_store_core.html -->
|
||||
<!-- version: 8 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" href="imgs/logo.png">
|
||||
<link rel="stylesheet" href="sass/main.scss">
|
||||
<title>Khadhroony Bot3 — PostgreSQL core store</title>
|
||||
<title>Khadhroony Bot3 — Core store diagnostics</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="container-fluid px-4 my-0">
|
||||
<div class="navbar-brand d-flex align-items-center">
|
||||
<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>
|
||||
</nav>
|
||||
@@ -36,11 +36,11 @@
|
||||
</div>
|
||||
<dl class="row mb-0">
|
||||
<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>
|
||||
<dd class="col-sm-9"><code id="sqlCoreProfile">—</code></dd>
|
||||
<dt class="col-sm-3">DSN</dt>
|
||||
<dd class="col-sm-9"><code id="sqlCoreDsn">—</code></dd>
|
||||
<dd class="col-sm-9"><code id="storeCoreProfile">—</code></dd>
|
||||
<dt class="col-sm-3">Connexion</dt>
|
||||
<dd class="col-sm-9"><code id="storeCoreConnection">—</code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,13 +49,13 @@
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<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">
|
||||
<table class="table table-sm align-middle mb-0" id="sqlCoreTable">
|
||||
<table class="table table-sm align-middle mb-0" id="storeCoreTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
<th>Domaine</th>
|
||||
<th>Ressource</th>
|
||||
<th>Modèle</th>
|
||||
<th>Rôle</th>
|
||||
<th>Existe</th>
|
||||
<th>Lignes</th>
|
||||
@@ -64,7 +64,7 @@
|
||||
<th>Dernière insertion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sqlCoreTableBody"></tbody>
|
||||
<tbody id="storeCoreTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_sql_diag.html -->
|
||||
<!-- version: 5 -->
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_store_diag.html -->
|
||||
<!-- version: 7 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" href="imgs/logo.png">
|
||||
<link rel="stylesheet" href="sass/main.scss">
|
||||
<title>Khadhroony Bot3 — SQL diagnostics</title>
|
||||
<title>Khadhroony Bot3 — Store diagnostics</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="container-fluid px-4 my-0">
|
||||
<div class="navbar-brand d-flex align-items-center">
|
||||
<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>
|
||||
</nav>
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<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">
|
||||
<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>
|
||||
@@ -39,21 +39,21 @@
|
||||
</div>
|
||||
<dl class="row mb-0">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<dd class="col-sm-9"><code id="sqlDiagBackend">—</code></dd>
|
||||
<dt class="col-sm-3">DSN</dt>
|
||||
<dd class="col-sm-9"><code id="sqlDiagDsn">—</code></dd>
|
||||
<dd class="col-sm-9"><code id="storeDiagBackend">—</code></dd>
|
||||
<dt class="col-sm-3">Connexion</dt>
|
||||
<dd class="col-sm-9"><code id="storeDiagConnection">—</code></dd>
|
||||
<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>
|
||||
<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>
|
||||
<dd class="col-sm-9"><code id="sqlDiagMigration">—</code></dd>
|
||||
<dd class="col-sm-9"><code id="storeDiagMigration">—</code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,13 +62,13 @@
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<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">
|
||||
<table class="table table-sm align-middle mb-0" id="sqlDiagTable">
|
||||
<table class="table table-sm align-middle mb-0" id="storeDiagTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
<th>Domaine</th>
|
||||
<th>Ressource</th>
|
||||
<th>Modèle</th>
|
||||
<th>Rôle</th>
|
||||
<th>Existe</th>
|
||||
<th>Lignes</th>
|
||||
@@ -77,7 +77,7 @@
|
||||
<th>Dernière insertion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sqlDiagTableBody"></tbody>
|
||||
<tbody id="storeDiagTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
@@ -104,7 +104,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script type="module" src="ts/demo_sql_diag.ts" defer></script>
|
||||
<script type="module" src="ts/demo_store_diag.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_sql_pg_raw.html -->
|
||||
<!-- version: 7 -->
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_store_raw.html -->
|
||||
<!-- version: 9 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" href="imgs/logo.png">
|
||||
<link rel="stylesheet" href="sass/main.scss">
|
||||
<title>Khadhroony Bot3 — PostgreSQL canonical acquisition</title>
|
||||
<title>Khadhroony Bot3 — Raw store diagnostics</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="container-fluid px-4 my-0">
|
||||
<div class="navbar-brand d-flex align-items-center">
|
||||
<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>
|
||||
</nav>
|
||||
@@ -36,11 +36,11 @@
|
||||
</div>
|
||||
<dl class="row mb-0">
|
||||
<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>
|
||||
<dd class="col-sm-9"><code id="sqlRawProfile">—</code></dd>
|
||||
<dt class="col-sm-3">DSN</dt>
|
||||
<dd class="col-sm-9"><code id="sqlRawDsn">—</code></dd>
|
||||
<dd class="col-sm-9"><code id="storeRawProfile">—</code></dd>
|
||||
<dt class="col-sm-3">Connexion</dt>
|
||||
<dd class="col-sm-9"><code id="storeRawConnection">—</code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,11 +51,11 @@
|
||||
<div class="card-body">
|
||||
<h2 class="h5 card-title mb-3">Transactions canoniques et observations</h2>
|
||||
<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>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
<th>Domaine</th>
|
||||
<th>Ressource</th>
|
||||
<th>Modèle</th>
|
||||
<th>Rôle</th>
|
||||
<th>Existe</th>
|
||||
<th>Lignes</th>
|
||||
@@ -64,7 +64,7 @@
|
||||
<th>Dernière insertion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sqlRawTableBody"></tbody>
|
||||
<tbody id="storeRawTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_sql_replay_candidates.html -->
|
||||
<!-- version: 11 -->
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_store_replay_candidates.html -->
|
||||
<!-- version: 15 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" href="imgs/logo.png">
|
||||
<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>
|
||||
|
||||
<body>
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="container-fluid px-4 my-0">
|
||||
<div class="navbar-brand d-flex align-items-center">
|
||||
<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 class="ms-auto d-flex align-items-center gap-2">
|
||||
<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-body">
|
||||
<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 l’export 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 d’import, 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 l’export 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 d’import, car le format CSV ne décrit pas formellement son encodage ni son séparateur.</p>
|
||||
<div class="small text-body-secondary">
|
||||
<span class="fw-semibold">DSN :</span>
|
||||
<span class="fw-semibold">Connexion :</span>
|
||||
<code id="replayCandidateDsn">—</code>
|
||||
<span class="ms-3 fw-semibold">Limite serveur maximale :</span>
|
||||
<code id="replayCandidateMaximumLimit">—</code>
|
||||
@@ -68,7 +68,7 @@
|
||||
<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-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="col-12 col-xl-4">
|
||||
<label for="transactionSignatureContainsInput" class="form-label">Signature contient</label>
|
||||
@@ -110,8 +110,8 @@
|
||||
<div class="col-6 col-md-3 col-xl-2">
|
||||
<label for="transactionProgramScopeSelect" class="form-label">Portée programme</label>
|
||||
<select id="transactionProgramScopeSelect" class="form-select">
|
||||
<option value="any">outer + inner + logs</option>
|
||||
<option value="outer">outer uniquement</option>
|
||||
<option value="any">top-level + inner + logs</option>
|
||||
<option value="top_level">top-level uniquement</option>
|
||||
<option value="inner">inner uniquement</option>
|
||||
<option value="logs">logs liés uniquement</option>
|
||||
</select>
|
||||
@@ -441,7 +441,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/main.html -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 14 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -48,12 +48,12 @@
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<h6 class="dropdown-header">SQL</h6>
|
||||
<h6 class="dropdown-header">Store</h6>
|
||||
</li>
|
||||
<li><a id="openDemoSqlDiagLink" class="dropdown-item" href="#">SQL diagnostics</a></li>
|
||||
<li><a id="openDemoSqlPgRawLink" class="dropdown-item" href="#">SQL PostgreSQL raw</a></li>
|
||||
<li><a id="openDemoSqlPgCoreLink" class="dropdown-item" href="#">SQL PostgreSQL core</a></li>
|
||||
<li><a id="openDemoSqlReplayCandidatesLink" class="dropdown-item" href="#">SQL replay candidates</a></li>
|
||||
<li><a id="openDemoStoreDiagLink" class="dropdown-item" href="#">Store diagnostics</a></li>
|
||||
<li><a id="openDemoStoreRawLink" class="dropdown-item" href="#">Raw store</a></li>
|
||||
<li><a id="openDemoStoreCoreLink" class="dropdown-item" href="#">Core store</a></li>
|
||||
<li><a id="openDemoStoreReplayCandidatesLink" class="dropdown-item" href="#">Store replay candidates</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
@@ -100,4 +100,4 @@
|
||||
<script type="module" src="ts/main.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
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 {
|
||||
await invoke(command);
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `${label} ouvert.` });
|
||||
@@ -166,13 +166,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
});
|
||||
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", () => {
|
||||
void openDiagnostics("open_demo_sql_pg_core_window", "Diagnostic core");
|
||||
void openDiagnostics("open_demo_store_core_window", "Diagnostic core");
|
||||
});
|
||||
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", () => {
|
||||
void cancelCoreExtraction();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -204,7 +204,7 @@ async function loadDiagnostics(): Promise<void> {
|
||||
try {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke 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);
|
||||
} catch (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 {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command}`);
|
||||
await invoke(command);
|
||||
@@ -358,8 +358,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
element<HTMLButtonElement>("#cancelDecodeReplayButton").addEventListener("click", () => void cancelDecodeReplay());
|
||||
element<HTMLButtonElement>("#loadDecodeDiagnosticsButton").addEventListener("click", () => void loadDiagnostics());
|
||||
element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void loadAnnotations());
|
||||
element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_replay_candidates_window"));
|
||||
element<HTMLButtonElement>("#openCoreDiagnosticsFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_pg_core_window"));
|
||||
element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_store_replay_candidates_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 loadOptions().catch(caughtError => {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_execution_solana_core.ts
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -20,18 +20,18 @@ const logLines: string[] = [];
|
||||
let running = false;
|
||||
let profileStoreReady = false;
|
||||
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
|
||||
function element<T extends HTMLElement>(selector: string): T { const value=document.querySelector<T>(selector); if(!value){throw new Error(`Missing UI element: ${selector}`);} return value; }
|
||||
function integerValue(selector: string): number { const value=Number.parseInt(element<HTMLInputElement>(selector).value,10); if(!Number.isFinite(value)||value<0){throw new Error(`Valeur numérique invalide pour ${selector}`);} return value; }
|
||||
function selectedProfile(): DemoExecutionSolanaCoreProfileOption|null { const name=element<HTMLSelectElement>("#executionProfileSelect").value; return profileOptions.find(profile=>profile.name===name)??null; }
|
||||
function appendLog(payload: DemoExecutionSolanaCoreProgressPayload|{timestamp:string;level:string;stage:string;message:string;signature?:string|null}):void { const signature=payload.signature?` signature=${payload.signature}`:""; logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`); const output=element<HTMLTextAreaElement>("#executionLogOutput"); output.value=logLines.join("\n"); output.scrollTop=output.scrollHeight; }
|
||||
function setRunning(value:boolean):void { running=value; const profile=selectedProfile(); element<HTMLButtonElement>("#simulateExecutionButton").disabled=value||!profile||!profileStoreReady; element<HTMLButtonElement>("#submitExecutionButton").disabled=value||!profile||!profileStoreReady||!profile.sendEnabled; element<HTMLButtonElement>("#generateRecipientButton").disabled=value; element<HTMLButtonElement>("#cancelExecutionButton").disabled=!value; const badge=element<HTMLElement>("#executionStatusBadge"); badge.textContent=value?"Exécution en cours":"Prêt"; badge.className=value?"badge text-bg-warning":"badge text-bg-success"; }
|
||||
function updateProfileHelp():void { const profile=selectedProfile(); const help=element<HTMLElement>("#executionProfileHelp"); if(!profile){help.textContent="Aucun profil Devnet compatible.";return;} help.textContent=`wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled?"enabled":"disabled"}`; element<HTMLInputElement>("#executionAirdropInput").max=String(profile.maxAirdropLamports); element<HTMLInputElement>("#executionLamportsInput").max=String(profile.maxSpendLamports); setRunning(running); }
|
||||
function buildRequest(submit:boolean):DemoExecutionSolanaCoreRequest { const recipient=element<HTMLInputElement>("#executionRecipientInput").value.trim(); if(recipient.length===0){throw new Error("Le destinataire est obligatoire.");} return {profileName:element<HTMLSelectElement>("#executionProfileSelect").value,recipient,lamports:integerValue("#executionLamportsInput"),airdropLamports:integerValue("#executionAirdropInput"),submit,operatorConfirmed:element<HTMLInputElement>("#executionOperatorConfirmedInput").checked,forcePostValidationReplay:element<HTMLInputElement>("#executionForceReplayInput").checked,materializeAfterDecode:element<HTMLInputElement>("#executionMaterializeInput").checked}; }
|
||||
function titledJson(title:string,value:string):string { try { const parsed:unknown=JSON.parse(value); if(parsed!==null&&typeof parsed==="object"&&!Array.isArray(parsed)){return JSON.stringify({title,...parsed},null,2);} return JSON.stringify({title,value:parsed},null,2); } catch { return JSON.stringify({title,status:"invalid_json",raw:value},null,2); } }
|
||||
function displaySummary(summary:DemoExecutionSolanaCoreSummaryPayload):void { element<HTMLTextAreaElement>("#executionSummaryOutput").value=JSON.stringify({title:"Résumé",...summary},null,2); element<HTMLTextAreaElement>("#executionPlanOutput").value=titledJson("Plan exact",summary.planJson); element<HTMLTextAreaElement>("#executionSimulationOutput").value=titledJson("Simulation exacte",summary.simulationJson); element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value=titledJson("Confirmation et replay",summary.diagnosticsJson); }
|
||||
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 n’a été produit.",error:message},null,2); element<HTMLTextAreaElement>("#executionSimulationOutput").value=JSON.stringify({title:"Simulation exacte",status:"not_started",reason:"preflight_failed",message:"La simulation n’a 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, l’envoi, la confirmation et le replay n’ont pas été exécutés.":"La confirmation et le replay ne s’appliquent 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);}}
|
||||
function element<T extends HTMLElement>(selector: string): T { const value = document.querySelector<T>(selector); if (!value) { throw new Error(`Missing UI element: ${selector}`); } return value; }
|
||||
function integerValue(selector: string): number { const value = Number.parseInt(element<HTMLInputElement>(selector).value, 10); if (!Number.isFinite(value) || value < 0) { throw new Error(`Valeur numérique invalide pour ${selector}`); } return value; }
|
||||
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null { const name = element<HTMLSelectElement>("#executionProfileSelect").value; return profileOptions.find(profile => profile.name === name) ?? null; }
|
||||
function appendLog(payload: DemoExecutionSolanaCoreProgressPayload | { timestamp: string; level: string; stage: string; message: string; signature?: string | null }): void { const signature = payload.signature ? ` signature=${payload.signature}` : ""; logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`); const output = element<HTMLTextAreaElement>("#executionLogOutput"); output.value = logLines.join("\n"); output.scrollTop = output.scrollHeight; }
|
||||
function setRunning(value: boolean): void { running = value; const profile = selectedProfile(); element<HTMLButtonElement>("#simulateExecutionButton").disabled = value || !profile || !profileStoreReady; element<HTMLButtonElement>("#submitExecutionButton").disabled = value || !profile || !profileStoreReady || !profile.sendEnabled; element<HTMLButtonElement>("#generateRecipientButton").disabled = value; element<HTMLButtonElement>("#cancelExecutionButton").disabled = !value; const badge = element<HTMLElement>("#executionStatusBadge"); badge.textContent = value ? "Exécution en cours" : "Prêt"; badge.className = value ? "badge text-bg-warning" : "badge text-bg-success"; }
|
||||
function updateProfileHelp(): void { const profile = selectedProfile(); const help = element<HTMLElement>("#executionProfileHelp"); if (!profile) { help.textContent = "Aucun profil Devnet compatible."; return; } help.textContent = `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`; element<HTMLInputElement>("#executionAirdropInput").max = String(profile.maxAirdropLamports); element<HTMLInputElement>("#executionLamportsInput").max = String(profile.maxSpendLamports); setRunning(running); }
|
||||
function buildRequest(submit: boolean): DemoExecutionSolanaCoreRequest { const recipient = element<HTMLInputElement>("#executionRecipientInput").value.trim(); if (recipient.length === 0) { throw new Error("Le destinataire est obligatoire."); } return { profileName: element<HTMLSelectElement>("#executionProfileSelect").value, recipient, lamports: integerValue("#executionLamportsInput"), airdropLamports: integerValue("#executionAirdropInput"), submit, operatorConfirmed: element<HTMLInputElement>("#executionOperatorConfirmedInput").checked, forcePostValidationReplay: element<HTMLInputElement>("#executionForceReplayInput").checked, materializeAfterDecode: element<HTMLInputElement>("#executionMaterializeInput").checked }; }
|
||||
function titledJson(title: string, value: string): string { try { const parsed: unknown = JSON.parse(value); if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) { return JSON.stringify({ title, ...parsed }, null, 2); } return JSON.stringify({ title, value: parsed }, null, 2); } catch { return JSON.stringify({ title, status: "invalid_json", raw: value }, null, 2); } }
|
||||
function displaySummary(summary: DemoExecutionSolanaCoreSummaryPayload): void { element<HTMLTextAreaElement>("#executionSummaryOutput").value = JSON.stringify({ title: "Résumé", ...summary }, null, 2); element<HTMLTextAreaElement>("#executionPlanOutput").value = titledJson("Plan exact", summary.planJson); element<HTMLTextAreaElement>("#executionSimulationOutput").value = titledJson("Simulation exacte", summary.simulationJson); element<HTMLTextAreaElement>("#executionDiagnosticsOutput").value = titledJson("Confirmation et replay", summary.diagnosticsJson); }
|
||||
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 n’a été produit.", error: message }, null, 2); element<HTMLTextAreaElement>("#executionSimulationOutput").value = JSON.stringify({ title: "Simulation exacte", status: "not_started", reason: "preflight_failed", message: "La simulation n’a 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, l’envoi, la confirmation et le replay n’ont pas été exécutés." : "La confirmation et le replay ne s’appliquent 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 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 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}`);}});
|
||||
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(); }
|
||||
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}`); } });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -896,11 +896,11 @@ async function prepareSelectedProfile(): Promise<void> {
|
||||
if (!profile) {
|
||||
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 {
|
||||
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}.` });
|
||||
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_spl", `Devnet store preparation failed: ${message}`);
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_sql_pg_core.ts
|
||||
// version: 4
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_core.ts
|
||||
// version: 6
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { renderJsonViewer, renderTables, setText } from "./demo_sql_tables";
|
||||
import type { DemoSqlPgCorePayload } from "./bindings/kb_app_demo_desktop/demo_sql_pg_core/DemoSqlPgCorePayload";
|
||||
import { renderJsonViewer, renderTables, setText } from "./demo_store_tables";
|
||||
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 & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
async function refreshCore(): Promise<void> {
|
||||
setText("#sqlCoreStatus", "Chargement...");
|
||||
setText("#storeCoreStatus", "Chargement...");
|
||||
try {
|
||||
const payload = await invoke<DemoSqlPgCorePayload>("load_demo_sql_pg_core");
|
||||
setText("#sqlCoreProfile", payload.activeProfileName);
|
||||
setText("#sqlCoreDsn", payload.maskedDsn);
|
||||
setText("#sqlCoreStatus", "OK");
|
||||
renderTables("#sqlCoreTable", "#sqlCoreTableBody", payload.tables);
|
||||
renderJsonViewer("#sqlCoreJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_core", "PostgreSQL core diagnostics refreshed");
|
||||
const payload = await invoke<DemoStoreCorePayload>("load_demo_store_core");
|
||||
setText("#storeCoreProfile", payload.activeProfileName);
|
||||
setText("#storeCoreConnection", payload.connectionDescriptor);
|
||||
setText("#storeCoreStatus", "OK");
|
||||
renderTables("#storeCoreTable", "#storeCoreTableBody", payload.resources);
|
||||
renderJsonViewer("#storeCoreJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_core", "Core store diagnostics refreshed");
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#sqlCoreStatus", `Erreur : ${message}`);
|
||||
renderJsonViewer("#sqlCoreJson", { error: message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_sql_pg_core", `PostgreSQL core diagnostics loading failed: ${message}`);
|
||||
setText("#storeCoreStatus", `Erreur : ${message}`);
|
||||
renderJsonViewer("#storeCoreJson", { error: message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_store_core", `Core store diagnostics loading failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_pg_core");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_core", "PostgreSQL core demo window loaded");
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_core");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_core", "Core store demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlCoreButton")?.addEventListener("click", () => {
|
||||
@@ -1,41 +1,41 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_sql_diag.ts
|
||||
// version: 3
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_diag.ts
|
||||
// version: 5
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { jsonText, renderJsonViewer, renderTables, setText } from "./demo_sql_tables";
|
||||
import type { DemoSqlDiagPayload } from "./bindings/kb_app_demo_desktop/demo_sql_diag/DemoSqlDiagPayload";
|
||||
import { jsonText, renderJsonViewer, renderTables, setText } from "./demo_store_tables";
|
||||
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 & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
let latestPayload: DemoSqlDiagPayload | null = null;
|
||||
let latestPayload: DemoStoreDiagPayload | null = null;
|
||||
|
||||
async function refreshDiagnostics(): Promise<void> {
|
||||
setText("#sqlDiagStatus", "Chargement...");
|
||||
setText("#storeDiagStatus", "Chargement...");
|
||||
try {
|
||||
const payload = await invoke<DemoSqlDiagPayload>("load_demo_sql_diag");
|
||||
setText("#sqlDiagConfigPath", payload.configPath);
|
||||
setText("#sqlDiagProfile", payload.activeProfileName);
|
||||
setText("#sqlDiagBackend", payload.backend);
|
||||
setText("#sqlDiagDsn", payload.maskedDsn);
|
||||
setText("#sqlDiagSchema", payload.currentSchema ?? "—");
|
||||
setText("#sqlDiagHealth", payload.healthStatus);
|
||||
setText("#sqlDiagMigration", payload.migrationStatus);
|
||||
setText("#sqlDiagStatus", "OK");
|
||||
renderTables("#sqlDiagTable", "#sqlDiagTableBody", payload.tables);
|
||||
const payload = await invoke<DemoStoreDiagPayload>("load_demo_store_diag");
|
||||
setText("#storeDiagConfigPath", payload.configPath);
|
||||
setText("#storeDiagProfile", payload.activeProfileName);
|
||||
setText("#storeDiagBackend", payload.backend);
|
||||
setText("#storeDiagConnection", payload.connectionDescriptor);
|
||||
setText("#storeDiagSchema", payload.namespace ?? "—");
|
||||
setText("#storeDiagHealth", payload.healthStatus);
|
||||
setText("#storeDiagMigration", payload.migrationStatus);
|
||||
setText("#storeDiagStatus", "OK");
|
||||
renderTables("#storeDiagTable", "#storeDiagTableBody", payload.resources);
|
||||
latestPayload = payload;
|
||||
renderJsonViewer("#sqlDiagJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_diag", "SQL diagnostics refreshed");
|
||||
renderJsonViewer("#storeDiagJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_diag", "Store diagnostics refreshed");
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#sqlDiagStatus", `Erreur : ${message}`);
|
||||
setText("#storeDiagStatus", `Erreur : ${message}`);
|
||||
latestPayload = null;
|
||||
renderJsonViewer("#sqlDiagJson", { error: message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_sql_diag", `SQL diagnostics loading failed: ${message}`);
|
||||
renderJsonViewer("#storeDiagJson", { error: 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", () => {
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_diag");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_diag", "SQL diagnostics demo window loaded");
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_diag");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_diag", "Store diagnostics demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlDiagButton")?.addEventListener("click", () => {
|
||||
@@ -1,38 +1,38 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_sql_pg_raw.ts
|
||||
// version: 4
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_raw.ts
|
||||
// version: 6
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { renderJsonViewer, renderTables, setText } from "./demo_sql_tables";
|
||||
import type { DemoSqlPgRawPayload } from "./bindings/kb_app_demo_desktop/demo_sql_pg_raw/DemoSqlPgRawPayload";
|
||||
import { renderJsonViewer, renderTables, setText } from "./demo_store_tables";
|
||||
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 & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
async function refreshRaw(): Promise<void> {
|
||||
setText("#sqlRawStatus", "Chargement...");
|
||||
setText("#storeRawStatus", "Chargement...");
|
||||
try {
|
||||
const payload = await invoke<DemoSqlPgRawPayload>("load_demo_sql_pg_raw");
|
||||
setText("#sqlRawProfile", payload.activeProfileName);
|
||||
setText("#sqlRawDsn", payload.maskedDsn);
|
||||
setText("#sqlRawStatus", "OK");
|
||||
renderTables("#sqlRawTable", "#sqlRawTableBody", payload.tables);
|
||||
renderJsonViewer("#sqlRawJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_raw", "PostgreSQL raw diagnostics refreshed");
|
||||
const payload = await invoke<DemoStoreRawPayload>("load_demo_store_raw");
|
||||
setText("#storeRawProfile", payload.activeProfileName);
|
||||
setText("#storeRawConnection", payload.connectionDescriptor);
|
||||
setText("#storeRawStatus", "OK");
|
||||
renderTables("#storeRawTable", "#storeRawTableBody", payload.resources);
|
||||
renderJsonViewer("#storeRawJson", payload);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_raw", "Raw store diagnostics refreshed");
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#sqlRawStatus", `Erreur : ${message}`);
|
||||
renderJsonViewer("#sqlRawJson", { error: message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_sql_pg_raw", `PostgreSQL raw diagnostics loading failed: ${message}`);
|
||||
setText("#storeRawStatus", `Erreur : ${message}`);
|
||||
renderJsonViewer("#storeRawJson", { error: message });
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_store_raw", `Raw store diagnostics loading failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_sql_pg_raw");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_sql_pg_raw", "PostgreSQL raw demo window loaded");
|
||||
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_store_raw");
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_raw", "Raw store demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlRawButton")?.addEventListener("click", () => {
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_sql_replay_candidates.ts
|
||||
// version: 9
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_replay_candidates.ts
|
||||
// version: 11
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -10,14 +10,14 @@ import "datatables.net-select-bs5";
|
||||
import "datatables.net-select-bs5/css/select.bootstrap5.css";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import type { DemoSqlReplayEntityRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRequest";
|
||||
import type { DemoSqlReplayEntityRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayEntityRow";
|
||||
import type { DemoSqlReplayKnownProgramOption } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayKnownProgramOption";
|
||||
import type { DemoSqlReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayOptionsPayload";
|
||||
import type { DemoSqlReplayProgramRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRequest";
|
||||
import type { DemoSqlReplayProgramRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayProgramRow";
|
||||
import type { DemoSqlReplayTransactionRequest } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRequest";
|
||||
import type { DemoSqlReplayTransactionRow } from "./bindings/kb_app_demo_desktop/demo_sql_replay_candidates/DemoSqlReplayTransactionRow";
|
||||
import type { DemoStoreReplayEntityRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRequest";
|
||||
import type { DemoStoreReplayEntityRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRow";
|
||||
import type { DemoStoreReplayKnownProgramOption } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayKnownProgramOption";
|
||||
import type { DemoStoreReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayOptionsPayload";
|
||||
import type { DemoStoreReplayProgramRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRequest";
|
||||
import type { DemoStoreReplayProgramRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRow";
|
||||
import type { DemoStoreReplayTransactionRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRequest";
|
||||
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 & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
@@ -40,7 +40,7 @@ type EntityViewConfig = {
|
||||
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[] = [
|
||||
{
|
||||
kind: "mint",
|
||||
@@ -88,11 +88,11 @@ const entityViewConfigs: EntityViewConfig[] = [
|
||||
fileName: "replay_account_keys.csv",
|
||||
},
|
||||
];
|
||||
let transactionTable: Api<DemoSqlReplayTransactionRow> | null = null;
|
||||
let programTable: Api<DemoSqlReplayProgramRow> | null = null;
|
||||
let mintTable: Api<DemoSqlReplayEntityRow> | null = null;
|
||||
let ownerTable: Api<DemoSqlReplayEntityRow> | null = null;
|
||||
let accountTable: Api<DemoSqlReplayEntityRow> | null = null;
|
||||
let transactionTable: Api<DemoStoreReplayTransactionRow> | null = null;
|
||||
let programTable: Api<DemoStoreReplayProgramRow> | null = null;
|
||||
let mintTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let ownerTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let accountTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let busyOperationCount = 0;
|
||||
let maximumReplayCandidateLimit = 100_000;
|
||||
|
||||
@@ -199,7 +199,7 @@ function badgeDisplay(value: unknown, type: string): string {
|
||||
return `<span class="badge ${className}">${escapeHtml(text)}</span>`;
|
||||
}
|
||||
|
||||
function coreStatusText(row: DemoSqlReplayTransactionRow): string {
|
||||
function coreStatusText(row: DemoStoreReplayTransactionRow): string {
|
||||
if (!row.hasCoreTransaction) {
|
||||
return "absent";
|
||||
}
|
||||
@@ -209,7 +209,7 @@ function coreStatusText(row: DemoSqlReplayTransactionRow): string {
|
||||
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 (!row.hasCoreTransaction) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
if (type === "display" || type === "filter") {
|
||||
return ratio.toFixed(2);
|
||||
@@ -236,7 +236,7 @@ function occurrenceRatioDisplay(_value: unknown, type: string, row: DemoSqlRepla
|
||||
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);
|
||||
if (type === "display" || type === "filter") {
|
||||
return String(span);
|
||||
@@ -258,8 +258,8 @@ function commonLanguage(emptyTable: string, infoLabel: string, zeroRecords: stri
|
||||
};
|
||||
}
|
||||
|
||||
function createTransactionTable(): Api<DemoSqlReplayTransactionRow> {
|
||||
return new DataTable<DemoSqlReplayTransactionRow>("#transactionCandidateTable", {
|
||||
function createTransactionTable(): Api<DemoStoreReplayTransactionRow> {
|
||||
return new DataTable<DemoStoreReplayTransactionRow>("#transactionCandidateTable", {
|
||||
data: [],
|
||||
columns: [
|
||||
{
|
||||
@@ -277,9 +277,9 @@ function createTransactionTable(): Api<DemoSqlReplayTransactionRow> {
|
||||
{ data: "ledgerStatus", render: badgeDisplay },
|
||||
{ data: "processorVersion", defaultContent: "—", render: codeDisplay },
|
||||
{ data: "attemptCount" },
|
||||
{ data: "outerInstructionCount" },
|
||||
{ data: "topLevelInstructionCount" },
|
||||
{ data: "innerInstructionCount" },
|
||||
{ data: "outerProgramCount" },
|
||||
{ data: "topLevelProgramCount" },
|
||||
{ data: "innerProgramCount" },
|
||||
{ data: "updatedAt" },
|
||||
],
|
||||
@@ -296,8 +296,8 @@ function createTransactionTable(): Api<DemoSqlReplayTransactionRow> {
|
||||
});
|
||||
}
|
||||
|
||||
function createProgramTable(): Api<DemoSqlReplayProgramRow> {
|
||||
return new DataTable<DemoSqlReplayProgramRow>("#programCandidateTable", {
|
||||
function createProgramTable(): Api<DemoStoreReplayProgramRow> {
|
||||
return new DataTable<DemoStoreReplayProgramRow>("#programCandidateTable", {
|
||||
data: [],
|
||||
columns: [
|
||||
{
|
||||
@@ -311,7 +311,7 @@ function createProgramTable(): Api<DemoSqlReplayProgramRow> {
|
||||
{ data: "programCode", defaultContent: "—", render: codeDisplay },
|
||||
{ data: "programId", render: copyableCodeDisplay("le program ID") },
|
||||
{ data: "transactionCount" },
|
||||
{ data: "outerInstructionCount" },
|
||||
{ data: "topLevelInstructionCount" },
|
||||
{ data: "innerInstructionCount" },
|
||||
{ data: "logCount" },
|
||||
{ data: "minSlot" },
|
||||
@@ -330,8 +330,8 @@ function createProgramTable(): Api<DemoSqlReplayProgramRow> {
|
||||
});
|
||||
}
|
||||
|
||||
function createEntityTable(config: EntityViewConfig): Api<DemoSqlReplayEntityRow> {
|
||||
return new DataTable<DemoSqlReplayEntityRow>(config.tableSelector, {
|
||||
function createEntityTable(config: EntityViewConfig): Api<DemoStoreReplayEntityRow> {
|
||||
return new DataTable<DemoStoreReplayEntityRow>(config.tableSelector, {
|
||||
data: [],
|
||||
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") {
|
||||
return mintTable;
|
||||
}
|
||||
@@ -425,7 +425,7 @@ function setBusy(busy: boolean, message: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
function buildTransactionRequest(): DemoSqlReplayTransactionRequest {
|
||||
function buildTransactionRequest(): DemoStoreReplayTransactionRequest {
|
||||
const entityKind = optionalInputValue("#transactionEntityKindSelect");
|
||||
const entityValue = optionalInputValue("#transactionEntityValueInput");
|
||||
if ((entityKind === null) !== (entityValue === null)) {
|
||||
@@ -484,9 +484,9 @@ async function loadTransactions(): Promise<void> {
|
||||
try {
|
||||
setBusy(true, "Chargement transactions");
|
||||
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);
|
||||
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`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -503,13 +503,13 @@ async function loadPrograms(): Promise<void> {
|
||||
}
|
||||
try {
|
||||
setBusy(true, "Chargement programmes");
|
||||
const request: DemoSqlReplayProgramRequest = {
|
||||
const request: DemoStoreReplayProgramRequest = {
|
||||
programIdContains: optionalInputValue("#programContainsInput"),
|
||||
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);
|
||||
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`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -527,14 +527,14 @@ async function loadEntity(config: EntityViewConfig): Promise<void> {
|
||||
}
|
||||
try {
|
||||
setBusy(true, `Chargement ${config.pluralLabel}`);
|
||||
const request: DemoSqlReplayEntityRequest = {
|
||||
const request: DemoStoreReplayEntityRequest = {
|
||||
entityKind: config.kind,
|
||||
entityValueContains: optionalInputValue(config.containsSelector),
|
||||
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);
|
||||
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`);
|
||||
} catch (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) {
|
||||
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");
|
||||
badge.textContent = `${rowCount} ligne(s) exportée(s) : ${path}`;
|
||||
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> {
|
||||
const rows = transactionRowsForExport();
|
||||
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"],
|
||||
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]),
|
||||
["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.topLevelInstructionCount, row.innerInstructionCount, row.topLevelProgramCount, row.innerProgramCount, row.updatedAt]),
|
||||
);
|
||||
await saveCsv("replay_transactions.csv", content, rows.length);
|
||||
}
|
||||
@@ -586,8 +586,8 @@ async function exportTransactionsCsv(): Promise<void> {
|
||||
async function exportProgramsCsv(): Promise<void> {
|
||||
const rows = programRowsForExport();
|
||||
const content = csvContent(
|
||||
["program_code", "program_id", "transaction_count", "outer_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]),
|
||||
["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.topLevelInstructionCount, row.innerInstructionCount, row.logCount, row.minSlot, row.maxSlot]),
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
function transactionRowsForExport(): DemoSqlReplayTransactionRow[] {
|
||||
function transactionRowsForExport(): DemoStoreReplayTransactionRow[] {
|
||||
if (transactionTable === null) {
|
||||
return [];
|
||||
}
|
||||
return selectedOrFilteredRows(transactionTable);
|
||||
}
|
||||
|
||||
function programRowsForExport(): DemoSqlReplayProgramRow[] {
|
||||
function programRowsForExport(): DemoStoreReplayProgramRow[] {
|
||||
if (programTable === null) {
|
||||
return [];
|
||||
}
|
||||
return selectedOrFilteredRows(programTable);
|
||||
}
|
||||
|
||||
function entityRowsForExport(kind: EntityKindCode): DemoSqlReplayEntityRow[] {
|
||||
function entityRowsForExport(kind: EntityKindCode): DemoStoreReplayEntityRow[] {
|
||||
const table = entityTable(kind);
|
||||
if (table === null) {
|
||||
return [];
|
||||
@@ -660,13 +660,13 @@ async function openCoreExtraction(): 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) {
|
||||
throw new Error("La limite maximale des candidats replay retournée par le backend est invalide.");
|
||||
}
|
||||
maximumReplayCandidateLimit = options.maximumLimit;
|
||||
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);
|
||||
document.querySelectorAll<HTMLInputElement>("input[type=number][max]").forEach(input => {
|
||||
input.max = String(options.maximumLimit);
|
||||
@@ -674,7 +674,7 @@ async function loadOptions(): Promise<void> {
|
||||
populateKnownPrograms(options.knownPrograms);
|
||||
}
|
||||
|
||||
function populateKnownPrograms(programs: DemoSqlReplayKnownProgramOption[]): void {
|
||||
function populateKnownPrograms(programs: DemoStoreReplayKnownProgramOption[]): void {
|
||||
const select = element<HTMLSelectElement>("#knownProgramSelect");
|
||||
const datalist = element<HTMLDataListElement>("#knownProgramDatalist");
|
||||
select.replaceChildren(new Option("Tous les programmes connus", ""));
|
||||
@@ -779,7 +779,7 @@ function installHandlers(): void {
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge(tracingTarget);
|
||||
frontendDebug(tracingTarget, "SQL replay candidate browser loaded");
|
||||
frontendDebug(tracingTarget, "Store replay candidate browser loaded");
|
||||
transactionTable = createTransactionTable();
|
||||
programTable = createProgramTable();
|
||||
mintTable = createEntityTable(entityViewConfigs[0]);
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_sql_tables.ts
|
||||
// version: 4
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_tables.ts
|
||||
// version: 6
|
||||
|
||||
import DataTable, { type Api } from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
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>();
|
||||
|
||||
@@ -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);
|
||||
if (existing) {
|
||||
existing.destroy();
|
||||
@@ -26,16 +26,16 @@ export function renderTables(tableSelector: string, bodySelector: string, tables
|
||||
return;
|
||||
}
|
||||
tbody.textContent = "";
|
||||
for (const table of tables) {
|
||||
for (const resource of resources) {
|
||||
const row = document.createElement("tr");
|
||||
row.appendChild(cell(table.tableName, true));
|
||||
row.appendChild(cell(table.domain, false));
|
||||
row.appendChild(cell(table.role, false));
|
||||
row.appendChild(cell(table.exists ? "oui" : "non", false));
|
||||
row.appendChild(cell(table.rowCount === null ? "—" : String(table.rowCount), false));
|
||||
row.appendChild(cell(table.minSlot === null ? "—" : String(table.minSlot), false));
|
||||
row.appendChild(cell(table.maxSlot === null ? "—" : String(table.maxSlot), false));
|
||||
row.appendChild(cell(table.latestCreatedAt ?? "—", false));
|
||||
row.appendChild(cell(resource.resourceCode, true));
|
||||
row.appendChild(cell(resource.modelCode, false));
|
||||
row.appendChild(cell(resource.role, false));
|
||||
row.appendChild(cell(resource.available ? "oui" : "non", false));
|
||||
row.appendChild(cell(resource.recordCount === null ? "—" : String(resource.recordCount), false));
|
||||
row.appendChild(cell(resource.minSlot === null ? "—" : String(resource.minSlot), false));
|
||||
row.appendChild(cell(resource.maxSlot === null ? "—" : String(resource.maxSlot), false));
|
||||
row.appendChild(cell(resource.latestCreatedAt ?? "—", false));
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
const tableElement = document.querySelector<HTMLTableElement>(tableSelector);
|
||||
@@ -48,12 +48,12 @@ export function renderTables(tableSelector: string, bodySelector: string, tables
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
order: [[0, "asc"]],
|
||||
language: {
|
||||
emptyTable: "Aucune table disponible",
|
||||
info: "Tables _START_ à _END_ sur _TOTAL_",
|
||||
infoEmpty: "Aucune table",
|
||||
lengthMenu: "Afficher _MENU_ tables",
|
||||
emptyTable: "Aucune ressource disponible",
|
||||
info: "Ressources _START_ à _END_ sur _TOTAL_",
|
||||
infoEmpty: "Aucune ressource",
|
||||
lengthMenu: "Afficher _MENU_ ressources",
|
||||
search: "Rechercher :",
|
||||
zeroRecords: "Aucune table correspondante",
|
||||
zeroRecords: "Aucune ressource correspondante",
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/main.ts
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import MarkdownIt from "markdown-it";
|
||||
@@ -97,10 +97,10 @@ for (const [elementId, command] of pipelineDemoLinks) {
|
||||
}
|
||||
|
||||
const sqlDemoLinks: ReadonlyArray<readonly [string, string]> = [
|
||||
["openDemoSqlDiagLink", "open_demo_sql_diag_window"],
|
||||
["openDemoSqlPgRawLink", "open_demo_sql_pg_raw_window"],
|
||||
["openDemoSqlPgCoreLink", "open_demo_sql_pg_core_window"],
|
||||
["openDemoSqlReplayCandidatesLink", "open_demo_sql_replay_candidates_window"],
|
||||
["openDemoStoreDiagLink", "open_demo_store_diag_window"],
|
||||
["openDemoStoreRawLink", "open_demo_store_raw_window"],
|
||||
["openDemoStoreCoreLink", "open_demo_store_core_window"],
|
||||
["openDemoStoreReplayCandidatesLink", "open_demo_store_replay_candidates_window"],
|
||||
];
|
||||
for (const [elementId, command] of sqlDemoLinks) {
|
||||
const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kb-app-demo-desktop",
|
||||
"private": true,
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 17
|
||||
// version: 20
|
||||
|
||||
//! Shared Tauri application state and startup initialization.
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(crate) struct AppState {
|
||||
active_profile: ks_config::ProfileConfig,
|
||||
logging_guard: std::sync::Mutex<ks_logging::LoggingGuard>,
|
||||
http_pool: ks_onchain_transport::HttpEndpointPool,
|
||||
store: tokio::sync::OnceCell<ks_store::Store>,
|
||||
ws_pool:
|
||||
std::sync::Mutex<std::option::Option<std::sync::Arc<ks_onchain_transport::WsEndpointPool>>>,
|
||||
demo_ws_session:
|
||||
@@ -106,6 +107,7 @@ impl crate::AppState {
|
||||
active_profile,
|
||||
logging_guard: std::sync::Mutex::new(logging_guard),
|
||||
http_pool,
|
||||
store: tokio::sync::OnceCell::new(),
|
||||
ws_pool: std::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),
|
||||
@@ -174,6 +176,25 @@ impl crate::AppState {
|
||||
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.
|
||||
pub(crate) fn demo_execution_wallet_alias_override(
|
||||
&self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_backfill.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
|
||||
//! 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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile = state.active_profile();
|
||||
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,
|
||||
) {
|
||||
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.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 {
|
||||
app_handle,
|
||||
cancel_requested: state.demo_backfill_cancel_requested(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_config.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! Configuration demo payloads with explicit public and bounded diagnostic projections.
|
||||
|
||||
@@ -164,7 +164,7 @@ pub(crate) struct DemoConfigEndpointDiagnosticPayload {
|
||||
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)]
|
||||
#[ts(
|
||||
export,
|
||||
@@ -173,23 +173,16 @@ pub(crate) struct DemoConfigEndpointDiagnosticPayload {
|
||||
pub(crate) struct DemoConfigStoreDiagnosticPayload {
|
||||
/// Whether persistence is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Selected store backend.
|
||||
/// Selected store backend code.
|
||||
pub(crate) backend: std::string::String,
|
||||
/// Whether a PostgreSQL URL is configured.
|
||||
pub(crate) postgres_url_state: std::string::String,
|
||||
/// PostgreSQL connection pool ceiling.
|
||||
pub(crate) postgres_max_connections: u32,
|
||||
/// PostgreSQL connection timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) postgres_connect_timeout_ms: u64,
|
||||
/// Whether PostgreSQL schema initialization is enabled.
|
||||
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,
|
||||
/// Whether backend-specific configuration validated successfully.
|
||||
pub(crate) configuration_status: std::string::String,
|
||||
/// Whether a backend connection or location value is configured.
|
||||
pub(crate) connection_state: std::string::String,
|
||||
/// Whether automatic store initialization is enabled.
|
||||
pub(crate) auto_initialize_schema: bool,
|
||||
/// Number of opaque backend options supplied after composition.
|
||||
pub(crate) backend_option_count: u32,
|
||||
}
|
||||
|
||||
/// 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()),
|
||||
],
|
||||
endpoints,
|
||||
store: DemoConfigStoreDiagnosticPayload {
|
||||
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,
|
||||
},
|
||||
store: store_diagnostic(profile),
|
||||
wallet: DemoConfigWalletDiagnosticPayload {
|
||||
wallet_directory_state: configured_state(profile.wallet.wallet_dir.as_str()),
|
||||
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 {
|
||||
return DemoConfigSourceDiagnosticPayload {
|
||||
category: category.to_string(),
|
||||
@@ -503,9 +535,19 @@ mod tests {
|
||||
#[test]
|
||||
fn public_and_diagnostic_projections_never_serialize_secret_canaries() {
|
||||
let mut profile = active_fixture_profile();
|
||||
profile.database.postgres.url =
|
||||
"postgres://operator:POSTGRES-SECRET-CANARY@localhost/solana".to_string();
|
||||
profile.database.sqlite.path = "/private/SQLITE-PATH-CANARY.sqlite".to_string();
|
||||
if let std::option::Option::Some(options) = profile.database.backend_options.as_object_mut()
|
||||
{
|
||||
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();
|
||||
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();
|
||||
@@ -536,17 +578,7 @@ mod tests {
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<super::DemoConfigEndpointDiagnosticPayload>>(),
|
||||
store: super::DemoConfigStoreDiagnosticPayload {
|
||||
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,
|
||||
},
|
||||
store: super::store_diagnostic(&profile),
|
||||
wallet: super::DemoConfigWalletDiagnosticPayload {
|
||||
wallet_directory_state: super::configured_state(profile.wallet.wallet_dir.as_str()),
|
||||
cluster: profile.wallet.cluster.clone(),
|
||||
@@ -563,7 +595,7 @@ mod tests {
|
||||
};
|
||||
for secret in [
|
||||
"POSTGRES-SECRET-CANARY",
|
||||
"SQLITE-PATH-CANARY",
|
||||
"STORE-PATH-CANARY",
|
||||
"WALLET-PATH-CANARY",
|
||||
"HELIUS-SECRET-CANARY",
|
||||
"WS-SECRET-CANARY",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -221,7 +221,7 @@ pub(crate) async fn demo_core_extraction_execute(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
|
||||
// version: 40
|
||||
// version: 44
|
||||
|
||||
//! 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"
|
||||
)]
|
||||
pub(crate) struct DemoDecodeDiagnosticsPayload {
|
||||
/// Decode, materialization and ledger table diagnostics.
|
||||
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
|
||||
/// Decode, materialization and ledger resource diagnostics.
|
||||
pub(crate) resources: std::vec::Vec<crate::DemoStoreResourceSnapshot>,
|
||||
/// Aggregated declared and observed coverage.
|
||||
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,
|
||||
"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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -563,24 +563,24 @@ pub(crate) async fn demo_decode_replay_diagnostics(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> 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");
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let table_result = store.known_table_diagnostics().await;
|
||||
let all_tables = match table_result {
|
||||
let resource_result = store.known_resource_diagnostics().await;
|
||||
let all_resources = match resource_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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()
|
||||
.filter(|table| {
|
||||
return table.table_name.starts_with("kb_sol_decode_")
|
||||
|| table.table_name.starts_with("kb_sol_mat_")
|
||||
|| table.table_name == "kb_sol_ops_processing_ledger";
|
||||
.filter(|resource| {
|
||||
return resource.model_code == "processing"
|
||||
|| resource.model_code == "decode"
|
||||
|| resource.model_code == "materialization";
|
||||
})
|
||||
.map(crate::table_snapshot_from_pg)
|
||||
.map(crate::store_resource_snapshot)
|
||||
.collect();
|
||||
let coverage_result = ks_store::DecodePipelineStore::list_decode_coverage_summary(
|
||||
&store,
|
||||
@@ -593,9 +593,9 @@ pub(crate) async fn demo_decode_replay_diagnostics(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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 {
|
||||
tables: selected_tables,
|
||||
resources: selected_resources,
|
||||
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()),
|
||||
};
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_devnet_common.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! 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";
|
||||
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
/// Backend-agnostic readiness report for one Devnet profile store.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
@@ -17,16 +17,18 @@ const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
|
||||
pub(crate) struct DemoExecutionDevnetStoreReadinessPayload {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Selected backend code.
|
||||
pub(crate) backend: std::string::String,
|
||||
/// Whether schema creation was allowed by configuration.
|
||||
pub(crate) auto_initialize_schema: bool,
|
||||
/// Number of known tables already present before preparation.
|
||||
pub(crate) existing_tables_before: u32,
|
||||
/// Number of known tables created during preparation.
|
||||
pub(crate) created_tables: u32,
|
||||
/// Number of known tables present after preparation.
|
||||
pub(crate) existing_tables_after: u32,
|
||||
/// Total number of known tables expected by the current store.
|
||||
pub(crate) expected_tables: u32,
|
||||
/// Number of logical resources already present before preparation.
|
||||
pub(crate) available_resources_before: u32,
|
||||
/// Number of logical resources created during preparation.
|
||||
pub(crate) created_resources: u32,
|
||||
/// Number of logical resources present after preparation.
|
||||
pub(crate) available_resources_after: u32,
|
||||
/// Total number of logical resources expected by the current store contract.
|
||||
pub(crate) expected_resources: u32,
|
||||
}
|
||||
|
||||
/// Converts the reusable scenario readiness report to the desktop TS-RS payload.
|
||||
@@ -35,11 +37,12 @@ pub(crate) fn devnet_store_readiness_payload(
|
||||
) -> DemoExecutionDevnetStoreReadinessPayload {
|
||||
return DemoExecutionDevnetStoreReadinessPayload {
|
||||
profile_name: readiness.profile_name,
|
||||
backend: readiness.backend,
|
||||
auto_initialize_schema: readiness.auto_initialize_schema,
|
||||
existing_tables_before: bounded_u32(readiness.existing_tables_before),
|
||||
created_tables: bounded_u32(readiness.created_tables),
|
||||
existing_tables_after: bounded_u32(readiness.existing_tables_after),
|
||||
expected_tables: bounded_u32(readiness.expected_tables),
|
||||
available_resources_before: bounded_u32(readiness.available_resources_before),
|
||||
created_resources: bounded_u32(readiness.created_resources),
|
||||
available_resources_after: bounded_u32(readiness.available_resources_after),
|
||||
expected_resources: bounded_u32(readiness.expected_resources),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -190,13 +190,10 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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(
|
||||
request.intent_id,
|
||||
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::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::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 configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -88,13 +88,10 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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 configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
|
||||
// version: 20
|
||||
// version: 23
|
||||
|
||||
//! 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(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
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::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::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(
|
||||
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
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::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::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(
|
||||
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
||||
request.message,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -260,13 +260,10 @@ pub(crate) async fn demo_execution_spl_ata_execute(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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![
|
||||
std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder),
|
||||
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::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::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) struct DemoExecutionSplTokenSummaryPayload {
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts"
|
||||
)]
|
||||
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,
|
||||
/// Optional partial signature handled by the bounded store query.
|
||||
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::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::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 operation = ks_lib::ExSplClassicTokenOperation::Instruction {
|
||||
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::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::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 =
|
||||
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
|
||||
let query_limit = if needs_payload_filter {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -159,13 +159,10 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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()),
|
||||
});
|
||||
}
|
||||
@@ -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()),
|
||||
});
|
||||
}
|
||||
@@ -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()),
|
||||
});
|
||||
}
|
||||
264
kb-app-demo-desktop/src/demo_store_common.rs
Normal file
264
kb-app-demo-desktop/src/demo_store_common.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
45
kb-app-demo-desktop/src/demo_store_core.rs
Normal file
45
kb-app-demo-desktop/src/demo_store_core.rs
Normal 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()),
|
||||
});
|
||||
}
|
||||
81
kb-app-demo-desktop/src/demo_store_diag.rs
Normal file
81
kb-app-demo-desktop/src/demo_store_diag.rs
Normal 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()),
|
||||
});
|
||||
}
|
||||
45
kb-app-demo-desktop/src/demo_store_raw.rs
Normal file
45
kb-app-demo-desktop/src/demo_store_raw.rs
Normal 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()),
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs
|
||||
// version: 13
|
||||
// file: kb-app-demo-desktop/src/demo_store_replay_candidates.rs
|
||||
// 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
|
||||
|
||||
@@ -10,9 +10,9 @@ use ts_rs::TS; // rust-rules: derive-import
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) code: std::string::String,
|
||||
/// Base58 Solana program identifier.
|
||||
@@ -24,17 +24,17 @@ pub(crate) struct DemoSqlReplayKnownProgramOption {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) active_profile_name: std::string::String,
|
||||
/// Masked PostgreSQL DSN.
|
||||
pub(crate) masked_dsn: std::string::String,
|
||||
/// Safe backend connection descriptor.
|
||||
pub(crate) connection_descriptor: std::string::String,
|
||||
/// Maximum rows accepted by one query.
|
||||
pub(crate) maximum_limit: u32,
|
||||
/// 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.
|
||||
@@ -42,9 +42,9 @@ pub(crate) struct DemoSqlReplayOptionsPayload {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) signature_contains: std::option::Option<std::string::String>,
|
||||
/// Optional inclusive minimum slot.
|
||||
@@ -59,7 +59,7 @@ pub(crate) struct DemoSqlReplayTransactionRequest {
|
||||
pub(crate) ledger_status: std::option::Option<std::string::String>,
|
||||
/// Optional exact program id.
|
||||
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,
|
||||
/// Optional entity kind: mint, owner or account_key.
|
||||
pub(crate) entity_kind: std::option::Option<std::string::String>,
|
||||
@@ -76,9 +76,9 @@ pub(crate) struct DemoSqlReplayTransactionRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) signature: std::string::String,
|
||||
/// Transaction slot.
|
||||
@@ -100,13 +100,13 @@ pub(crate) struct DemoSqlReplayTransactionRow {
|
||||
pub(crate) attempt_count: i32,
|
||||
/// Number of top-level instructions.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) outer_instruction_count: i64,
|
||||
pub(crate) top_level_instruction_count: i64,
|
||||
/// Number of inner instructions.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) inner_instruction_count: i64,
|
||||
/// Number of distinct top-level programs.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) outer_program_count: i64,
|
||||
pub(crate) top_level_program_count: i64,
|
||||
/// Number of distinct inner programs.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) inner_program_count: i64,
|
||||
@@ -119,9 +119,9 @@ pub(crate) struct DemoSqlReplayTransactionRow {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) program_id_contains: std::option::Option<std::string::String>,
|
||||
/// Maximum returned rows.
|
||||
@@ -133,9 +133,9 @@ pub(crate) struct DemoSqlReplayProgramRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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`.
|
||||
pub(crate) program_code: std::option::Option<std::string::String>,
|
||||
/// Program id.
|
||||
@@ -145,7 +145,7 @@ pub(crate) struct DemoSqlReplayProgramRow {
|
||||
pub(crate) transaction_count: i64,
|
||||
/// Number of top-level instruction occurrences.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) outer_instruction_count: i64,
|
||||
pub(crate) top_level_instruction_count: i64,
|
||||
/// Number of inner instruction occurrences.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) inner_instruction_count: i64,
|
||||
@@ -165,9 +165,9 @@ pub(crate) struct DemoSqlReplayProgramRow {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) entity_kind: std::string::String,
|
||||
/// Optional partial entity value search.
|
||||
@@ -181,9 +181,9 @@ pub(crate) struct DemoSqlReplayEntityRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
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.
|
||||
pub(crate) entity_kind: std::string::String,
|
||||
/// 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/`.
|
||||
pub(crate) async fn export_demo_sql_replay_csv(
|
||||
pub(crate) async fn export_demo_store_replay_csv(
|
||||
file_name: std::string::String,
|
||||
content: 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);
|
||||
}
|
||||
|
||||
/// Loads static browser options from the active PostgreSQL profile.
|
||||
pub(crate) async fn demo_sql_replay_options(
|
||||
/// Loads static browser options from the active store.
|
||||
pub(crate) async fn demo_store_replay_options(
|
||||
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 options_result = crate::postgres_store_options_from_profile(&profile, false);
|
||||
let options = match options_result {
|
||||
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.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 =
|
||||
std::vec::Vec::with_capacity(ks_program_ids::registered_program_ids().len());
|
||||
for entry in ks_program_ids::registered_program_ids() {
|
||||
known_programs.push(crate::DemoSqlReplayKnownProgramOption {
|
||||
known_programs.push(crate::DemoStoreReplayKnownProgramOption {
|
||||
code: entry.code().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,
|
||||
masked_dsn: options.masked_dsn(),
|
||||
connection_descriptor,
|
||||
maximum_limit: ks_store::MAX_REPLAY_CANDIDATE_ROWS,
|
||||
known_programs,
|
||||
});
|
||||
}
|
||||
|
||||
/// Loads bounded transaction replay candidates from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_replay_transactions(
|
||||
/// Loads bounded transaction replay candidates from the active store.
|
||||
pub(crate) async fn load_demo_store_replay_transactions(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayTransactionRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> {
|
||||
request: crate::DemoStoreReplayTransactionRequest,
|
||||
) -> 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 = match program_scope_result {
|
||||
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::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.min_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::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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(transaction_row_from_pg(row));
|
||||
output.push(transaction_row_from_store(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
/// Loads bounded program summaries from PostgreSQL.
|
||||
pub(crate) async fn load_demo_sql_replay_programs(
|
||||
/// Loads bounded program summaries from the active store.
|
||||
pub(crate) async fn load_demo_store_replay_programs(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayProgramRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> {
|
||||
request: crate::DemoStoreReplayProgramRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayProgramRow>, std::string::String> {
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(program_row_from_pg(row));
|
||||
output.push(program_row_from_store(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
|
||||
pub(crate) async fn load_demo_sql_replay_entities(
|
||||
/// Loads bounded mint, owner or account-key summaries from Core facts.
|
||||
pub(crate) async fn load_demo_store_replay_entities(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayEntityRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> {
|
||||
request: crate::DemoStoreReplayEntityRequest,
|
||||
) -> 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 = match entity_kind_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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,
|
||||
request.entity_value_contains,
|
||||
request.limit,
|
||||
@@ -364,7 +367,7 @@ pub(crate) async fn load_demo_sql_replay_entities(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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");
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
output.push(entity_row_from_pg(row));
|
||||
output.push(entity_row_from_store(row));
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn transaction_row_from_pg(
|
||||
row: ks_store::PostgresReplayTransactionCandidate,
|
||||
) -> crate::DemoSqlReplayTransactionRow {
|
||||
return crate::DemoSqlReplayTransactionRow {
|
||||
fn transaction_row_from_store(
|
||||
row: ks_store::ReplayTransactionCandidate,
|
||||
) -> crate::DemoStoreReplayTransactionRow {
|
||||
return crate::DemoStoreReplayTransactionRow {
|
||||
signature: row.signature,
|
||||
slot: row.slot,
|
||||
raw_processing_state: row.raw_processing_state,
|
||||
@@ -395,24 +398,22 @@ fn transaction_row_from_pg(
|
||||
ledger_status: row.ledger_status,
|
||||
processor_version: row.processor_version,
|
||||
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,
|
||||
outer_program_count: row.outer_program_count,
|
||||
top_level_program_count: row.top_level_program_count,
|
||||
inner_program_count: row.inner_program_count,
|
||||
updated_at: row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
fn program_row_from_pg(
|
||||
row: ks_store::PostgresReplayProgramSummary,
|
||||
) -> crate::DemoSqlReplayProgramRow {
|
||||
fn program_row_from_store(row: ks_store::ReplayProgramSummary) -> crate::DemoStoreReplayProgramRow {
|
||||
let program_code = ks_program_ids::find_registered_program_id(&row.program_id)
|
||||
.map(|entry| return entry.code().to_owned());
|
||||
return crate::DemoSqlReplayProgramRow {
|
||||
return crate::DemoStoreReplayProgramRow {
|
||||
program_code,
|
||||
program_id: row.program_id,
|
||||
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,
|
||||
log_count: row.log_count,
|
||||
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 {
|
||||
return crate::DemoSqlReplayEntityRow {
|
||||
fn entity_row_from_store(row: ks_store::ReplayEntitySummary) -> crate::DemoStoreReplayEntityRow {
|
||||
return crate::DemoStoreReplayEntityRow {
|
||||
entity_kind: row.entity_kind,
|
||||
entity_value: row.entity_value,
|
||||
transaction_count: row.transaction_count,
|
||||
@@ -480,20 +481,19 @@ async fn available_csv_export_path(
|
||||
|
||||
fn program_scope_from_code(
|
||||
code: &str,
|
||||
) -> std::result::Result<ks_store::PostgresReplayProgramScope, std::string::String> {
|
||||
) -> std::result::Result<ks_store::ReplayProgramScope, std::string::String> {
|
||||
return match code {
|
||||
"any" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Any),
|
||||
"outer" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Outer),
|
||||
"inner" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Inner),
|
||||
"logs" => std::result::Result::Ok(ks_store::PostgresReplayProgramScope::Logs),
|
||||
"any" => std::result::Result::Ok(ks_store::ReplayProgramScope::Any),
|
||||
"top_level" => std::result::Result::Ok(ks_store::ReplayProgramScope::TopLevel),
|
||||
"inner" => std::result::Result::Ok(ks_store::ReplayProgramScope::Inner),
|
||||
"logs" => std::result::Result::Ok(ks_store::ReplayProgramScope::Logs),
|
||||
_ => std::result::Result::Err(format!("unsupported replay program scope: {code}")),
|
||||
};
|
||||
}
|
||||
|
||||
fn optional_entity_kind_from_code(
|
||||
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 {
|
||||
std::option::Option::Some(value) => {
|
||||
let result = entity_kind_from_code(value);
|
||||
@@ -510,11 +510,11 @@ fn optional_entity_kind_from_code(
|
||||
|
||||
fn entity_kind_from_code(
|
||||
code: &str,
|
||||
) -> std::result::Result<ks_store::PostgresReplayEntityKind, std::string::String> {
|
||||
) -> std::result::Result<ks_store::ReplayEntityKind, std::string::String> {
|
||||
return match code {
|
||||
"mint" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::Mint),
|
||||
"owner" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::Owner),
|
||||
"account_key" => std::result::Result::Ok(ks_store::PostgresReplayEntityKind::AccountKey),
|
||||
"mint" => std::result::Result::Ok(ks_store::ReplayEntityKind::Mint),
|
||||
"owner" => std::result::Result::Ok(ks_store::ReplayEntityKind::Owner),
|
||||
"account_key" => std::result::Result::Ok(ks_store::ReplayEntityKind::AccountKey),
|
||||
_ => std::result::Result::Err(format!("unsupported replay entity kind: {code}")),
|
||||
};
|
||||
}
|
||||
@@ -524,7 +524,7 @@ mod tests {
|
||||
#[test]
|
||||
fn program_scope_parser_accepts_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]
|
||||
@@ -551,16 +551,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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(),
|
||||
transaction_count: 1,
|
||||
outer_instruction_count: 1,
|
||||
top_level_instruction_count: 1,
|
||||
inner_instruction_count: 0,
|
||||
log_count: 1,
|
||||
min_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"));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_ws.rs
|
||||
// version: 23
|
||||
// version: 24
|
||||
|
||||
//! Standard Solana WebSocket demo commands backed by `ks_onchain_transport::WsSession`.
|
||||
|
||||
@@ -1031,7 +1031,7 @@ mod tests {
|
||||
<crate::DemoWsUnsubscribeRequest as TS>::decl(&config),
|
||||
<crate::DemoBackfillProgressPayload 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 {
|
||||
assert!(!declaration.contains("bigint"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 43
|
||||
// version: 47
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -24,11 +24,11 @@ mod demo_http;
|
||||
mod demo_spl_ata;
|
||||
mod demo_spl_token;
|
||||
mod demo_spl_token_2022;
|
||||
mod demo_sql_common;
|
||||
mod demo_sql_diag;
|
||||
mod demo_sql_pg_core;
|
||||
mod demo_sql_pg_raw;
|
||||
mod demo_sql_replay_candidates;
|
||||
mod demo_store_common;
|
||||
mod demo_store_core;
|
||||
mod demo_store_diag;
|
||||
mod demo_store_raw;
|
||||
mod demo_store_replay_candidates;
|
||||
mod demo_transport;
|
||||
mod demo_wallet;
|
||||
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;
|
||||
/// Returns available decoders and default replay bounds.
|
||||
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;
|
||||
/// Restores the shared single-execution state flag when a Devnet run finishes.
|
||||
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;
|
||||
/// Loads public Token-2022 fixture values without reading private key bytes.
|
||||
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_fixture;
|
||||
/// UI-safe SQL table diagnostic snapshot.
|
||||
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
|
||||
/// Connects to the configured PostgreSQL store.
|
||||
pub(crate) use self::demo_sql_common::connect_postgres_store;
|
||||
/// UI-safe logical store resource diagnostic snapshot.
|
||||
pub(crate) use self::demo_store_common::DemoStoreResourceSnapshot;
|
||||
/// Formats diagnostic statuses.
|
||||
pub(crate) use self::demo_sql_common::debug_status;
|
||||
/// Initializes the PostgreSQL schema during startup.
|
||||
pub(crate) use self::demo_sql_common::initialize_postgres_schema_for_startup;
|
||||
/// Builds PostgreSQL options from the active profile.
|
||||
pub(crate) use self::demo_sql_common::postgres_store_options_from_profile;
|
||||
/// Converts one PostgreSQL table diagnostic.
|
||||
pub(crate) use self::demo_sql_common::table_snapshot_from_pg;
|
||||
/// Converts PostgreSQL table diagnostics.
|
||||
pub(crate) use self::demo_sql_common::table_snapshots_from_pg;
|
||||
/// SQL diagnostic payload.
|
||||
pub(crate) use self::demo_sql_diag::DemoSqlDiagPayload;
|
||||
/// Loads read-only SQL diagnostics from the active profile.
|
||||
pub(crate) use self::demo_sql_diag::load_demo_sql_diag;
|
||||
/// PostgreSQL core-table payload.
|
||||
pub(crate) use self::demo_sql_pg_core::DemoSqlPgCorePayload;
|
||||
/// Loads read-only core table diagnostics from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_pg_core::load_demo_sql_pg_core;
|
||||
/// PostgreSQL raw-table payload.
|
||||
pub(crate) use self::demo_sql_pg_raw::DemoSqlPgRawPayload;
|
||||
/// Loads read-only raw table diagnostics from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_pg_raw::load_demo_sql_pg_raw;
|
||||
/// SQL replay entity request.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRequest;
|
||||
/// SQL replay entity row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRow;
|
||||
pub(crate) use self::demo_store_common::debug_status;
|
||||
/// Opens and verifies the configured store during startup.
|
||||
pub(crate) use self::demo_store_common::initialize_store_for_startup;
|
||||
/// Opens the configured backend-agnostic store.
|
||||
pub(crate) use self::demo_store_common::open_store;
|
||||
/// Clones the persistent application store.
|
||||
pub(crate) use self::demo_store_common::shared_store;
|
||||
/// Returns the safe connection descriptor exposed by the active store backend.
|
||||
pub(crate) use self::demo_store_common::store_connection_descriptor;
|
||||
/// Converts one logical store resource diagnostic.
|
||||
pub(crate) use self::demo_store_common::store_resource_snapshot;
|
||||
/// Converts logical store resource diagnostics.
|
||||
pub(crate) use self::demo_store_common::store_resource_snapshots;
|
||||
/// Backend-agnostic Core store diagnostic payload.
|
||||
pub(crate) use self::demo_store_core::DemoStoreCorePayload;
|
||||
/// Loads read-only Core resource diagnostics from the active store.
|
||||
pub(crate) use self::demo_store_core::load_demo_store_core;
|
||||
/// Backend-agnostic store diagnostic payload.
|
||||
pub(crate) use self::demo_store_diag::DemoStoreDiagPayload;
|
||||
/// Loads read-only Store diagnostics from the active profile.
|
||||
pub(crate) use self::demo_store_diag::load_demo_store_diag;
|
||||
/// Backend-agnostic raw store diagnostic payload.
|
||||
pub(crate) use self::demo_store_raw::DemoStoreRawPayload;
|
||||
/// Loads read-only raw resource diagnostics from the active store.
|
||||
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.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayKnownProgramOption;
|
||||
/// SQL replay options payload.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayOptionsPayload;
|
||||
/// SQL replay program request.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRequest;
|
||||
/// SQL replay program row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRow;
|
||||
/// SQL replay transaction request.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest;
|
||||
/// SQL replay transaction row.
|
||||
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRow;
|
||||
/// Loads static browser options from the active PostgreSQL profile.
|
||||
pub(crate) use self::demo_sql_replay_candidates::demo_sql_replay_options;
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayKnownProgramOption;
|
||||
/// Store replay options payload.
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayOptionsPayload;
|
||||
/// Store replay program request.
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayProgramRequest;
|
||||
/// Store replay program row.
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayProgramRow;
|
||||
/// Store replay transaction request.
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayTransactionRequest;
|
||||
/// Store replay transaction row.
|
||||
pub(crate) use self::demo_store_replay_candidates::DemoStoreReplayTransactionRow;
|
||||
/// Loads static replay browser options from the active store profile.
|
||||
pub(crate) use self::demo_store_replay_candidates::demo_store_replay_options;
|
||||
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
|
||||
pub(crate) use self::demo_sql_replay_candidates::export_demo_sql_replay_csv;
|
||||
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_entities;
|
||||
/// Loads bounded program summaries from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_programs;
|
||||
/// Loads bounded transaction replay candidates from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_transactions;
|
||||
pub(crate) use self::demo_store_replay_candidates::export_demo_store_replay_csv;
|
||||
/// Loads bounded mint, owner or account-key summaries from Core store resources.
|
||||
pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_entities;
|
||||
/// Loads bounded program summaries from the active store.
|
||||
pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_programs;
|
||||
/// Loads bounded transaction replay candidates from the active store.
|
||||
pub(crate) use self::demo_store_replay_candidates::load_demo_store_replay_transactions;
|
||||
/// UI-safe endpoint role shared by HTTP and WebSocket diagnostics.
|
||||
pub(crate) use self::demo_transport::DemoEndpointRolePayload;
|
||||
/// Exact public SOL balance projected by the wallet demo.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/splash.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! 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::None,
|
||||
);
|
||||
crate::initialize_postgres_schema_for_startup(state, &window).await;
|
||||
crate::initialize_store_for_startup(state, &window).await;
|
||||
crate::emit_splash_order(
|
||||
&window,
|
||||
"add_msg",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 44
|
||||
// version: 46
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -71,18 +71,18 @@ pub fn run() -> ks_core::Result<()> {
|
||||
demo_decode_replay_cancel,
|
||||
demo_decode_replay_diagnostics,
|
||||
demo_decode_replay_annotations,
|
||||
open_demo_sql_diag_window,
|
||||
load_demo_sql_diag,
|
||||
open_demo_sql_pg_raw_window,
|
||||
load_demo_sql_pg_raw,
|
||||
open_demo_sql_pg_core_window,
|
||||
load_demo_sql_pg_core,
|
||||
open_demo_sql_replay_candidates_window,
|
||||
export_demo_sql_replay_csv,
|
||||
demo_sql_replay_options,
|
||||
load_demo_sql_replay_transactions,
|
||||
load_demo_sql_replay_programs,
|
||||
load_demo_sql_replay_entities,
|
||||
open_demo_store_diag_window,
|
||||
load_demo_store_diag,
|
||||
open_demo_store_raw_window,
|
||||
load_demo_store_raw,
|
||||
open_demo_store_core_window,
|
||||
load_demo_store_core,
|
||||
open_demo_store_replay_candidates_window,
|
||||
export_demo_store_replay_csv,
|
||||
demo_store_replay_options,
|
||||
load_demo_store_replay_transactions,
|
||||
load_demo_store_replay_programs,
|
||||
load_demo_store_replay_entities,
|
||||
open_demo_execution_solana_core_window,
|
||||
demo_execution_devnet_prepare_profile,
|
||||
demo_execution_solana_core_options,
|
||||
@@ -518,111 +518,111 @@ async fn demo_decode_replay_annotations(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_demo_sql_diag_window(
|
||||
fn open_demo_store_diag_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return open_or_focus_demo_window(
|
||||
app_handle,
|
||||
"demo_sql_diag",
|
||||
"demo_sql_diag.html",
|
||||
"SQL diagnostics",
|
||||
"demo_store_diag",
|
||||
"demo_store_diag.html",
|
||||
"Store diagnostics",
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_diag(
|
||||
async fn load_demo_store_diag(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> {
|
||||
return crate::load_demo_sql_diag(state).await;
|
||||
) -> std::result::Result<crate::DemoStoreDiagPayload, std::string::String> {
|
||||
return crate::load_demo_store_diag(state).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_demo_sql_pg_raw_window(
|
||||
fn open_demo_store_raw_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return open_or_focus_demo_window(
|
||||
app_handle,
|
||||
"demo_sql_pg_raw",
|
||||
"demo_sql_pg_raw.html",
|
||||
"PostgreSQL canonical acquisition",
|
||||
"demo_store_raw",
|
||||
"demo_store_raw.html",
|
||||
"Raw store",
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_pg_raw(
|
||||
async fn load_demo_store_raw(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> {
|
||||
return crate::load_demo_sql_pg_raw(state).await;
|
||||
) -> std::result::Result<crate::DemoStoreRawPayload, std::string::String> {
|
||||
return crate::load_demo_store_raw(state).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_demo_sql_pg_core_window(
|
||||
fn open_demo_store_core_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return open_or_focus_demo_window(
|
||||
app_handle,
|
||||
"demo_sql_pg_core",
|
||||
"demo_sql_pg_core.html",
|
||||
"PostgreSQL core store",
|
||||
"demo_store_core",
|
||||
"demo_store_core.html",
|
||||
"Core store",
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_pg_core(
|
||||
async fn load_demo_store_core(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> {
|
||||
return crate::load_demo_sql_pg_core(state).await;
|
||||
) -> std::result::Result<crate::DemoStoreCorePayload, std::string::String> {
|
||||
return crate::load_demo_store_core(state).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_demo_sql_replay_candidates_window(
|
||||
fn open_demo_store_replay_candidates_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
return open_or_focus_demo_window(
|
||||
app_handle,
|
||||
"demo_sql_replay_candidates",
|
||||
"demo_sql_replay_candidates.html",
|
||||
"SQL replay candidates",
|
||||
"demo_store_replay_candidates",
|
||||
"demo_store_replay_candidates.html",
|
||||
"Store replay candidates",
|
||||
);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn export_demo_sql_replay_csv(
|
||||
async fn export_demo_store_replay_csv(
|
||||
file_name: std::string::String,
|
||||
content: 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]
|
||||
async fn demo_sql_replay_options(
|
||||
async fn demo_store_replay_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> {
|
||||
return crate::demo_sql_replay_options(state).await;
|
||||
) -> std::result::Result<crate::DemoStoreReplayOptionsPayload, std::string::String> {
|
||||
return crate::demo_store_replay_options(state).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_replay_transactions(
|
||||
async fn load_demo_store_replay_transactions(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayTransactionRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> {
|
||||
return crate::load_demo_sql_replay_transactions(state, request).await;
|
||||
request: crate::DemoStoreReplayTransactionRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayTransactionRow>, std::string::String> {
|
||||
return crate::load_demo_store_replay_transactions(state, request).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_replay_programs(
|
||||
async fn load_demo_store_replay_programs(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayProgramRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> {
|
||||
return crate::load_demo_sql_replay_programs(state, request).await;
|
||||
request: crate::DemoStoreReplayProgramRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayProgramRow>, std::string::String> {
|
||||
return crate::load_demo_store_replay_programs(state, request).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_demo_sql_replay_entities(
|
||||
async fn load_demo_store_replay_entities(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoSqlReplayEntityRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> {
|
||||
return crate::load_demo_sql_replay_entities(state, request).await;
|
||||
request: crate::DemoStoreReplayEntityRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoStoreReplayEntityRow>, std::string::String> {
|
||||
return crate::load_demo_store_replay_entities(state, request).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Khadhroony Bot3 Demo Desktop",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"identifier": "com.sasedev.kb-app-demo-desktop",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/vite.config.ts
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
import { defineConfig, normalizePath } from "vite";
|
||||
import { NodePackageImporter } from "sass-embedded";
|
||||
@@ -30,10 +30,10 @@ export default defineConfig(() => ({
|
||||
"demo_backfill": normalizePath(resolve(__dirname, 'frontend/demo_backfill.html')),
|
||||
"demo_core_extraction": normalizePath(resolve(__dirname, 'frontend/demo_core_extraction.html')),
|
||||
"demo_decode_replay": normalizePath(resolve(__dirname, 'frontend/demo_decode_replay.html')),
|
||||
"demo_sql_diag": normalizePath(resolve(__dirname, 'frontend/demo_sql_diag.html')),
|
||||
"demo_sql_pg_raw": normalizePath(resolve(__dirname, 'frontend/demo_sql_pg_raw.html')),
|
||||
"demo_sql_pg_core": normalizePath(resolve(__dirname, 'frontend/demo_sql_pg_core.html')),
|
||||
"demo_sql_replay_candidates": normalizePath(resolve(__dirname, 'frontend/demo_sql_replay_candidates.html')),
|
||||
"demo_store_diag": normalizePath(resolve(__dirname, 'frontend/demo_store_diag.html')),
|
||||
"demo_store_raw": normalizePath(resolve(__dirname, 'frontend/demo_store_raw.html')),
|
||||
"demo_store_core": normalizePath(resolve(__dirname, 'frontend/demo_store_core.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_spl": normalizePath(resolve(__dirname, 'frontend/demo_execution_spl.html')),
|
||||
"demo_execution_metatdata": normalizePath(resolve(__dirname, 'frontend/demo_execution_metadata.html')),
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
<!-- file: ks-config/CHANGELOG.md -->
|
||||
<!-- version: 24 -->
|
||||
<!-- version: 26 -->
|
||||
|
||||
# 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`
|
||||
|
||||
- corrige le test de séparation des répertoires wallet afin qu’il résolve d’abord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-config/README.md -->
|
||||
<!-- version: 13 -->
|
||||
<!-- version: 14 -->
|
||||
|
||||
# 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.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`.
|
||||
|
||||
## Documents
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-config/USAGE.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# Utilisation de ks-config
|
||||
|
||||
@@ -88,7 +88,7 @@ Les endpoints WebSocket source sélectionnent une classe `ws_endpoint_defaults`;
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/composition.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Binary composition configuration and resolution into the shared runtime profile contract.
|
||||
|
||||
@@ -18,9 +18,9 @@ pub struct CompositionConfigDocument {
|
||||
/// Active binary composition profile name.
|
||||
pub active_profile: std::string::String,
|
||||
/// Shared configuration documents referenced by this composition.
|
||||
pub sources: CompositionConfigSources,
|
||||
pub sources: crate::CompositionConfigSources,
|
||||
/// 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.
|
||||
@@ -65,7 +65,7 @@ pub struct CompositionProfileConfig {
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct ComposedAppConfig {
|
||||
/// Parsed binary composition document.
|
||||
pub composition: CompositionConfigDocument,
|
||||
pub composition: crate::CompositionConfigDocument,
|
||||
/// Resolved runtime application configuration.
|
||||
pub app_config: crate::AppConfig,
|
||||
/// 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.
|
||||
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::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.
|
||||
pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<CompositionConfigDocument> {
|
||||
match validate_composition_json_schema(raw_json) {
|
||||
pub fn parse_composition_json(raw_json: &str) -> ks_core::Result<crate::CompositionConfigDocument> {
|
||||
match crate::validate_composition_json_schema(raw_json) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(_) => {
|
||||
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::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.
|
||||
pub fn read_composition_json_file(
|
||||
path: &std::path::Path,
|
||||
) -> ks_core::Result<CompositionConfigDocument> {
|
||||
) -> ks_core::Result<crate::CompositionConfigDocument> {
|
||||
let raw_json = match std::fs::read_to_string(path) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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.
|
||||
pub fn read_composition_json_file_with_environment(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<CompositionConfigDocument> {
|
||||
) -> ks_core::Result<crate::CompositionConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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.
|
||||
pub fn composition_profile<'a>(
|
||||
document: &'a CompositionConfigDocument,
|
||||
document: &'a crate::CompositionConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a CompositionProfileConfig> {
|
||||
) -> ks_core::Result<&'a crate::CompositionProfileConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -208,9 +208,9 @@ pub fn composition_profile<'a>(
|
||||
|
||||
/// Returns the active binary composition profile.
|
||||
pub fn active_composition_profile(
|
||||
document: &CompositionConfigDocument,
|
||||
) -> ks_core::Result<&CompositionProfileConfig> {
|
||||
return composition_profile(document, document.active_profile.as_str());
|
||||
document: &crate::CompositionConfigDocument,
|
||||
) -> ks_core::Result<&crate::CompositionProfileConfig> {
|
||||
return crate::composition_profile(document, document.active_profile.as_str());
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn compose_app_config(
|
||||
document: &CompositionConfigDocument,
|
||||
document: &crate::CompositionConfigDocument,
|
||||
transport: &crate::TransportConfigDocument,
|
||||
listeners: &crate::ListenersConfigDocument,
|
||||
store: &crate::StoreConfigDocument,
|
||||
@@ -247,23 +247,23 @@ pub fn compose_app_config(
|
||||
) -> ks_core::Result<crate::AppConfig> {
|
||||
let mut profiles = std::vec::Vec::<crate::ProfileConfig>::new();
|
||||
for profile in &document.profiles {
|
||||
let transport_name = resolve_profile_selection(
|
||||
let transport_name = crate::resolve_profile_selection(
|
||||
profile.transport_profile.as_deref(),
|
||||
transport.default_profile.as_str(),
|
||||
);
|
||||
let listeners_name = resolve_profile_selection(
|
||||
let listeners_name = crate::resolve_profile_selection(
|
||||
profile.listeners_profile.as_deref(),
|
||||
listeners.default_profile.as_str(),
|
||||
);
|
||||
let store_name = resolve_profile_selection(
|
||||
let store_name = crate::resolve_profile_selection(
|
||||
profile.store_profile.as_deref(),
|
||||
store.default_profile.as_str(),
|
||||
);
|
||||
let wallet_name = resolve_profile_selection(
|
||||
let wallet_name = crate::resolve_profile_selection(
|
||||
profile.wallet_profile.as_deref(),
|
||||
wallet.default_profile.as_str(),
|
||||
);
|
||||
let execution_name = resolve_profile_selection(
|
||||
let execution_name = crate::resolve_profile_selection(
|
||||
profile.execution_profile.as_deref(),
|
||||
execution.default_profile.as_str(),
|
||||
);
|
||||
@@ -313,24 +313,34 @@ pub fn compose_app_config(
|
||||
pub fn read_composed_app_config_with_environment(
|
||||
composition_path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<ComposedAppConfig> {
|
||||
let composition =
|
||||
match read_composition_json_file_with_environment(composition_path, workspace_root) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_path =
|
||||
resolve_composition_source_path(workspace_root, composition.sources.logging.as_str());
|
||||
let transport_path =
|
||||
resolve_composition_source_path(workspace_root, composition.sources.transport.as_str());
|
||||
let listeners_path =
|
||||
resolve_composition_source_path(workspace_root, composition.sources.listeners.as_str());
|
||||
) -> ks_core::Result<crate::ComposedAppConfig> {
|
||||
let composition = match crate::read_composition_json_file_with_environment(
|
||||
composition_path,
|
||||
workspace_root,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_path = crate::resolve_composition_source_path(
|
||||
workspace_root,
|
||||
composition.sources.logging.as_str(),
|
||||
);
|
||||
let transport_path = crate::resolve_composition_source_path(
|
||||
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 =
|
||||
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 =
|
||||
resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str());
|
||||
let execution_path =
|
||||
resolve_composition_source_path(workspace_root, composition.sources.execution.as_str());
|
||||
crate::resolve_composition_source_path(workspace_root, composition.sources.wallet.as_str());
|
||||
let execution_path = crate::resolve_composition_source_path(
|
||||
workspace_root,
|
||||
composition.sources.execution.as_str(),
|
||||
);
|
||||
let transport =
|
||||
match crate::read_transport_json_file_with_environment(&transport_path, workspace_root) {
|
||||
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::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
let app_config =
|
||||
match compose_app_config(&composition, &transport, &listeners, &store, &wallet, &execution)
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(ComposedAppConfig {
|
||||
let app_config = match crate::compose_app_config(
|
||||
&composition,
|
||||
&transport,
|
||||
&listeners,
|
||||
&store,
|
||||
&wallet,
|
||||
&execution,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ComposedAppConfig {
|
||||
composition,
|
||||
app_config,
|
||||
logging_path,
|
||||
@@ -421,9 +436,9 @@ pub fn read_default_shared_app_config_with_environment(
|
||||
} else {
|
||||
"shared_default".to_string()
|
||||
};
|
||||
let composition = CompositionConfigDocument {
|
||||
let composition = crate::CompositionConfigDocument {
|
||||
active_profile: profile_name.clone(),
|
||||
sources: CompositionConfigSources {
|
||||
sources: crate::CompositionConfigSources {
|
||||
logging: DEFAULT_LOGGING_CONFIG_PATH.to_string(),
|
||||
transport: DEFAULT_TRANSPORT_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(),
|
||||
execution: DEFAULT_EXECUTION_CONFIG_PATH.to_string(),
|
||||
},
|
||||
profiles: vec![CompositionProfileConfig {
|
||||
profiles: vec![crate::CompositionProfileConfig {
|
||||
name: profile_name.clone(),
|
||||
application: 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,
|
||||
}],
|
||||
};
|
||||
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.
|
||||
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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
composition: &CompositionConfigDocument,
|
||||
composition: &crate::CompositionConfigDocument,
|
||||
) -> ks_core::Result<()> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
@@ -566,7 +590,7 @@ fn validate_logging_references_with_environment(
|
||||
}
|
||||
for profile in &composition.profiles {
|
||||
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) {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"composition_logging_profile_not_found",
|
||||
@@ -586,17 +610,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_and_example_compositions_validate() {
|
||||
assert!(super::parse_composition_json(DEFAULT_COMPOSITION).is_ok());
|
||||
assert!(super::parse_composition_json(EXAMPLE_COMPOSITION).is_ok());
|
||||
assert!(crate::parse_composition_json(DEFAULT_COMPOSITION).is_ok());
|
||||
assert!(crate::parse_composition_json(EXAMPLE_COMPOSITION).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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::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::Err(error) => {
|
||||
panic!("local Devnet composition must resolve: {error}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/environment.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! Environment-file loading and configuration placeholder resolution.
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct EnvironmentLoadReport {
|
||||
/// fallback text declared with `${NAME:-fallback}` placeholders.
|
||||
pub fn load_workspace_environment(
|
||||
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 selected_path = match explicit_path {
|
||||
std::option::Option::Some(path) if !path.trim().is_empty() => {
|
||||
@@ -26,10 +26,10 @@ pub fn load_workspace_environment(
|
||||
_ => workspace_root.join(".env"),
|
||||
};
|
||||
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) {
|
||||
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),
|
||||
}),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
@@ -82,14 +82,14 @@ mod tests {
|
||||
#[test]
|
||||
fn fallback_is_used_when_variable_is_absent() {
|
||||
let resolved =
|
||||
super::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}");
|
||||
crate::resolve_environment_placeholders("${KS_CONFIG_TEST_MISSING:-fallback}");
|
||||
assert_eq!(resolved, "fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unresolved_required_placeholder_is_preserved() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/execution.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! 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.
|
||||
pub default_profile: std::string::String,
|
||||
/// Named execution policy profiles.
|
||||
pub profiles: std::vec::Vec<ExecutionProfileConfig>,
|
||||
pub profiles: std::vec::Vec<crate::ExecutionProfileConfig>,
|
||||
}
|
||||
|
||||
/// Named execution policy profile.
|
||||
@@ -31,7 +31,7 @@ pub fn execution_json_schema_text() -> &'static str {
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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",
|
||||
));
|
||||
}
|
||||
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::Err(_) => {
|
||||
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::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(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<ExecutionConfigDocument> {
|
||||
) -> ks_core::Result<crate::ExecutionConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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.
|
||||
pub fn execution_profile<'a>(
|
||||
document: &'a ExecutionConfigDocument,
|
||||
document: &'a crate::ExecutionConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a ExecutionProfileConfig> {
|
||||
) -> ks_core::Result<&'a crate::ExecutionProfileConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -119,13 +119,15 @@ pub fn execution_profile<'a>(
|
||||
|
||||
/// Returns the default execution profile.
|
||||
pub fn default_execution_profile(
|
||||
document: &ExecutionConfigDocument,
|
||||
) -> ks_core::Result<&ExecutionProfileConfig> {
|
||||
return execution_profile(document, document.default_profile.as_str());
|
||||
document: &crate::ExecutionConfigDocument,
|
||||
) -> ks_core::Result<&crate::ExecutionProfileConfig> {
|
||||
return crate::execution_profile(document, document.default_profile.as_str());
|
||||
}
|
||||
|
||||
/// 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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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),
|
||||
}
|
||||
}
|
||||
return match default_execution_profile(document) {
|
||||
return match crate::default_execution_profile(document) {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
@@ -167,7 +169,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_and_example_execution_configs_validate() {
|
||||
assert!(super::parse_execution_json(DEFAULT_EXECUTION).is_ok());
|
||||
assert!(super::parse_execution_json(EXAMPLE_EXECUTION).is_ok());
|
||||
assert!(crate::parse_execution_json(DEFAULT_EXECUTION).is_ok());
|
||||
assert!(crate::parse_execution_json(EXAMPLE_EXECUTION).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/lib.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
//! Khadhroony Solana shared configuration contracts and composition helpers.
|
||||
#![warn(missing_docs)]
|
||||
@@ -125,16 +125,12 @@ pub use self::settings::HttpEndpointConfig;
|
||||
pub use self::settings::ListenerConfig;
|
||||
/// Exposes the log listener configuration type.
|
||||
pub use self::settings::LogListenerConfig;
|
||||
/// Exposes the PostgreSQL configuration type.
|
||||
pub use self::settings::PostgresConfig;
|
||||
/// Exposes the profile configuration type.
|
||||
pub use self::settings::ProfileConfig;
|
||||
/// Exposes the program listener configuration type.
|
||||
pub use self::settings::ProgramListenerConfig;
|
||||
/// Exposes the Solana configuration type.
|
||||
pub use self::settings::SolanaConfig;
|
||||
/// Exposes the SQLite configuration type.
|
||||
pub use self::settings::SqliteConfig;
|
||||
/// Exposes the wallet configuration type.
|
||||
pub use self::settings::WalletConfig;
|
||||
/// Exposes the WebSocket endpoint configuration type.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/listeners.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! 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.
|
||||
pub default_profile: std::string::String,
|
||||
/// Named listener profiles available in this document.
|
||||
pub profiles: std::vec::Vec<ListenersProfileConfig>,
|
||||
pub profiles: std::vec::Vec<crate::ListenersProfileConfig>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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::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.
|
||||
pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<ListenersConfigDocument> {
|
||||
match validate_listeners_json_schema(raw_json) {
|
||||
pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result<crate::ListenersConfigDocument> {
|
||||
match crate::validate_listeners_json_schema(raw_json) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(_) => {
|
||||
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::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.
|
||||
pub fn read_listeners_json_file(
|
||||
path: &std::path::Path,
|
||||
) -> ks_core::Result<ListenersConfigDocument> {
|
||||
) -> ks_core::Result<crate::ListenersConfigDocument> {
|
||||
let raw_json = match std::fs::read_to_string(path) {
|
||||
std::result::Result::Ok(content) => content,
|
||||
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.
|
||||
pub fn read_listeners_json_file_with_environment(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<ListenersConfigDocument> {
|
||||
) -> ks_core::Result<crate::ListenersConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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);
|
||||
return parse_listeners_json(&resolved);
|
||||
return crate::parse_listeners_json(&resolved);
|
||||
}
|
||||
|
||||
/// Returns one named listener profile.
|
||||
pub fn listeners_profile<'a>(
|
||||
document: &'a ListenersConfigDocument,
|
||||
document: &'a crate::ListenersConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a ListenersProfileConfig> {
|
||||
) -> ks_core::Result<&'a crate::ListenersProfileConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -159,13 +159,13 @@ pub fn listeners_profile<'a>(
|
||||
|
||||
/// Returns the default listener profile declared by the document.
|
||||
pub fn default_listeners_profile(
|
||||
document: &ListenersConfigDocument,
|
||||
) -> ks_core::Result<&ListenersProfileConfig> {
|
||||
return listeners_profile(document, document.default_profile.as_str());
|
||||
document: &crate::ListenersConfigDocument,
|
||||
) -> ks_core::Result<&crate::ListenersProfileConfig> {
|
||||
return crate::listeners_profile(document, document.default_profile.as_str());
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
enabled: profile.enabled,
|
||||
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.
|
||||
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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(),
|
||||
));
|
||||
}
|
||||
let resolved = resolved_listener_config(profile);
|
||||
let resolved = crate::resolved_listener_config(profile);
|
||||
match crate::validate_listeners(&resolved) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
@@ -219,7 +221,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_and_example_listener_configs_validate() {
|
||||
assert!(super::parse_listeners_json(DEFAULT_LISTENERS).is_ok());
|
||||
assert!(super::parse_listeners_json(EXAMPLE_LISTENERS).is_ok());
|
||||
assert!(crate::parse_listeners_json(DEFAULT_LISTENERS).is_ok());
|
||||
assert!(crate::parse_listeners_json(EXAMPLE_LISTENERS).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/schema.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Generic JSON Schema validation helpers for binary-owned configuration fragments.
|
||||
|
||||
@@ -42,7 +42,7 @@ mod tests {
|
||||
fn validation_error_never_echoes_instance_secret() {
|
||||
let schema = r#"{"type":"object","additionalProperties":false,"required":["value"],"properties":{"value":{"type":"integer"}}}"#;
|
||||
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,
|
||||
&instance,
|
||||
"test_fragment_validation_failed",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/sensitivity.rs
|
||||
// version: 1
|
||||
// version: 3
|
||||
|
||||
//! 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.
|
||||
pub fn classify_environment_template(
|
||||
raw: &str,
|
||||
) -> std::option::Option<EnvironmentValueSensitivity> {
|
||||
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
|
||||
let bytes = raw.as_bytes();
|
||||
let mut sensitivity = std::option::Option::None;
|
||||
let mut index = 0_usize;
|
||||
@@ -36,7 +36,8 @@ pub fn classify_environment_template(
|
||||
std::option::Option::Some((name, _)) => name,
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
@@ -49,15 +50,15 @@ pub fn classify_environment_template(
|
||||
/// Classifies a Khadhroony-owned environment variable by its namespace.
|
||||
pub fn classify_environment_variable(
|
||||
name: &str,
|
||||
) -> std::option::Option<EnvironmentValueSensitivity> {
|
||||
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
|
||||
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_") {
|
||||
return std::option::Option::Some(EnvironmentValueSensitivity::Public);
|
||||
return std::option::Option::Some(crate::EnvironmentValueSensitivity::Public);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -67,14 +68,14 @@ pub fn diagnostic_environment_value(
|
||||
name: &str,
|
||||
value: &str,
|
||||
) -> std::option::Option<std::string::String> {
|
||||
return match classify_environment_variable(name) {
|
||||
std::option::Option::Some(EnvironmentValueSensitivity::Secret) => {
|
||||
return match crate::classify_environment_variable(name) {
|
||||
std::option::Option::Some(crate::EnvironmentValueSensitivity::Secret) => {
|
||||
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(EnvironmentValueSensitivity::Public) => {
|
||||
std::option::Option::Some(crate::EnvironmentValueSensitivity::Public) => {
|
||||
std::option::Option::Some(value.to_string())
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
@@ -82,23 +83,23 @@ pub fn diagnostic_environment_value(
|
||||
}
|
||||
|
||||
fn merge_sensitivity(
|
||||
left: std::option::Option<EnvironmentValueSensitivity>,
|
||||
right: std::option::Option<EnvironmentValueSensitivity>,
|
||||
) -> std::option::Option<EnvironmentValueSensitivity> {
|
||||
if left == std::option::Option::Some(EnvironmentValueSensitivity::Secret)
|
||||
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Secret)
|
||||
left: std::option::Option<crate::EnvironmentValueSensitivity>,
|
||||
right: std::option::Option<crate::EnvironmentValueSensitivity>,
|
||||
) -> std::option::Option<crate::EnvironmentValueSensitivity> {
|
||||
if left == std::option::Option::Some(crate::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)
|
||||
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Internal)
|
||||
if left == std::option::Option::Some(crate::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)
|
||||
|| right == std::option::Option::Some(EnvironmentValueSensitivity::Public)
|
||||
if left == std::option::Option::Some(crate::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;
|
||||
}
|
||||
@@ -108,19 +109,19 @@ mod tests {
|
||||
#[test]
|
||||
fn composed_values_inherit_the_strongest_placeholder_sensitivity() {
|
||||
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}"
|
||||
),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Secret)
|
||||
);
|
||||
assert_eq!(
|
||||
super::classify_environment_template(
|
||||
crate::classify_environment_template(
|
||||
"${KS_LOGS_DIRECTORY:-logs}/${KS_PUBLIC_SPL_TOKEN_PROGRAM_ID}"
|
||||
),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
|
||||
);
|
||||
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)
|
||||
);
|
||||
}
|
||||
@@ -128,15 +129,15 @@ mod tests {
|
||||
#[test]
|
||||
fn generic_diagnostic_never_reveals_secret_or_internal_values() {
|
||||
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())
|
||||
);
|
||||
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())
|
||||
);
|
||||
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())
|
||||
);
|
||||
}
|
||||
@@ -144,17 +145,17 @@ mod tests {
|
||||
#[test]
|
||||
fn namespaces_map_to_secret_internal_and_public() {
|
||||
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)
|
||||
);
|
||||
assert_eq!(
|
||||
super::classify_environment_variable("KS_LOGS_DIRECTORY"),
|
||||
crate::classify_environment_variable("KS_LOGS_DIRECTORY"),
|
||||
std::option::Option::Some(super::EnvironmentValueSensitivity::Internal)
|
||||
);
|
||||
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)
|
||||
);
|
||||
assert_eq!(super::classify_environment_variable("HOME"), std::option::Option::None);
|
||||
assert_eq!(crate::classify_environment_variable("HOME"), std::option::Option::None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/settings.rs
|
||||
// version: 25
|
||||
// version: 29
|
||||
|
||||
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
|
||||
|
||||
@@ -12,7 +12,7 @@ pub struct AppConfig {
|
||||
/// Active profile name.
|
||||
pub active_profile: std::string::String,
|
||||
/// 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.
|
||||
@@ -21,67 +21,35 @@ pub struct ProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
/// Database configuration.
|
||||
pub database: DatabaseConfig,
|
||||
pub database: crate::DatabaseConfig,
|
||||
/// Solana endpoint and listener configuration.
|
||||
pub solana: SolanaConfig,
|
||||
pub solana: crate::SolanaConfig,
|
||||
/// Wallet configuration.
|
||||
pub wallet: WalletConfig,
|
||||
pub wallet: crate::WalletConfig,
|
||||
/// 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)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Enables database storage.
|
||||
/// Enables persistent storage for this profile.
|
||||
pub enabled: bool,
|
||||
/// Selected backend code.
|
||||
/// Selected backend code interpreted by `ks-store`.
|
||||
pub backend: std::string::String,
|
||||
/// PostgreSQL configuration.
|
||||
pub postgres: PostgresConfig,
|
||||
/// 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,
|
||||
/// Backend-specific options retained as an opaque JSON object until `ks-store` opens the store.
|
||||
pub backend_options: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Solana endpoints and listener configuration.
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct SolanaConfig {
|
||||
/// HTTP JSON-RPC endpoints.
|
||||
pub http_endpoints: std::vec::Vec<HttpEndpointConfig>,
|
||||
pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
|
||||
/// Standard Solana WebSocket endpoints.
|
||||
pub ws_endpoints: std::vec::Vec<WsEndpointConfig>,
|
||||
pub ws_endpoints: std::vec::Vec<crate::WsEndpointConfig>,
|
||||
/// Listener declarations.
|
||||
pub listeners: ListenerConfig,
|
||||
pub listeners: crate::ListenerConfig,
|
||||
}
|
||||
|
||||
/// HTTP JSON-RPC endpoint configuration.
|
||||
@@ -104,7 +72,7 @@ pub struct HttpEndpointConfig {
|
||||
/// Maximum idle connections per host.
|
||||
pub max_idle_connections_per_host: u32,
|
||||
/// Role-specific limits for this URL.
|
||||
pub roles: std::vec::Vec<EndpointRoleConfig>,
|
||||
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
|
||||
}
|
||||
|
||||
/// Standard Solana WebSocket endpoint configuration.
|
||||
@@ -133,7 +101,7 @@ pub struct WsEndpointConfig {
|
||||
/// Enables automatic reconnect for this endpoint.
|
||||
pub auto_reconnect: bool,
|
||||
/// Role-specific limits for this URL.
|
||||
pub roles: std::vec::Vec<EndpointRoleConfig>,
|
||||
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
|
||||
}
|
||||
|
||||
/// Role-specific endpoint limits.
|
||||
@@ -167,11 +135,11 @@ pub struct ListenerConfig {
|
||||
/// Default commitment used by listener subscriptions.
|
||||
pub default_commitment: std::string::String,
|
||||
/// Log listeners filtered by program mentions.
|
||||
pub log_listeners: std::vec::Vec<LogListenerConfig>,
|
||||
pub log_listeners: std::vec::Vec<crate::LogListenerConfig>,
|
||||
/// Program account listeners.
|
||||
pub program_listeners: std::vec::Vec<ProgramListenerConfig>,
|
||||
pub program_listeners: std::vec::Vec<crate::ProgramListenerConfig>,
|
||||
/// 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.
|
||||
@@ -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.
|
||||
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::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.
|
||||
pub fn parse_config_json(raw_json: &str) -> ks_core::Result<AppConfig> {
|
||||
match validate_config_json_schema(raw_json) {
|
||||
pub fn parse_config_json(raw_json: &str) -> ks_core::Result<crate::AppConfig> {
|
||||
match crate::validate_config_json_schema(raw_json) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(_) => {
|
||||
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::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
std::result::Result::Ok(content) => content,
|
||||
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.
|
||||
pub fn read_config_json_file_with_environment(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<AppConfig> {
|
||||
) -> ks_core::Result<crate::AppConfig> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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);
|
||||
return parse_config_json(&resolved);
|
||||
return crate::parse_config_json(&resolved);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if profile.name == config.active_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.
|
||||
pub fn validate_config(config: &AppConfig) -> ks_core::Result<()> {
|
||||
pub fn validate_config(config: &crate::AppConfig) -> ks_core::Result<()> {
|
||||
match validate_root_config(config) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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),
|
||||
}
|
||||
}
|
||||
return match active_profile(config) {
|
||||
return match crate::active_profile(config) {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_root_config(config: &AppConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.active_profile, "active_profile") {
|
||||
fn validate_root_config(config: &crate::AppConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.active_profile, "active_profile") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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 active_count = 0_u32;
|
||||
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::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(());
|
||||
}
|
||||
|
||||
fn validate_profile(profile: &ProfileConfig) -> ks_core::Result<()> {
|
||||
match validate_database(&profile.database) {
|
||||
fn validate_profile(profile: &crate::ProfileConfig) -> ks_core::Result<()> {
|
||||
match crate::validate_database(&profile.database) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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::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::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return validate_wallet_execution_pair(&profile.wallet, &profile.execution);
|
||||
}
|
||||
|
||||
pub(crate) fn validate_database(config: &DatabaseConfig) -> ks_core::Result<()> {
|
||||
if config.backend != "postgres" && config.backend != "sqlite" {
|
||||
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") {
|
||||
pub(crate) fn validate_database(config: &crate::DatabaseConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.backend, "database.backend") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(
|
||||
"database_max_connections_zero",
|
||||
"database connection limits must be greater than zero",
|
||||
"database_backend_options_invalid",
|
||||
"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::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");
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
|
||||
fn validate_solana(config: &crate::SolanaConfig) -> ks_core::Result<()> {
|
||||
if config.http_endpoints.is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"http_endpoints_empty",
|
||||
@@ -522,7 +478,7 @@ fn validate_solana(config: &SolanaConfig) -> ks_core::Result<()> {
|
||||
endpoint.name.clone(),
|
||||
));
|
||||
}
|
||||
match validate_http_endpoint(endpoint) {
|
||||
match crate::validate_http_endpoint(endpoint) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(),
|
||||
));
|
||||
}
|
||||
match validate_ws_endpoint(endpoint) {
|
||||
match crate::validate_ws_endpoint(endpoint) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(());
|
||||
}
|
||||
|
||||
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) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -603,17 +559,17 @@ fn validate_endpoint_common(
|
||||
name: &str,
|
||||
provider: &str,
|
||||
cluster: &str,
|
||||
roles: &[EndpointRoleConfig],
|
||||
roles: &[crate::EndpointRoleConfig],
|
||||
) -> 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::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::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::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
@@ -639,8 +595,8 @@ fn validate_endpoint_common(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.role, "endpoint.roles.role") {
|
||||
fn validate_endpoint_role(config: &crate::EndpointRoleConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.role, "endpoint.roles.role") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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 {
|
||||
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::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(());
|
||||
}
|
||||
|
||||
pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.default_commitment, "solana.listeners.default_commitment") {
|
||||
pub(crate) fn validate_listeners(config: &crate::ListenerConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(
|
||||
&config.default_commitment,
|
||||
"solana.listeners.default_commitment",
|
||||
) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(());
|
||||
}
|
||||
|
||||
fn validate_log_listener(config: &LogListenerConfig) -> ks_core::Result<()> {
|
||||
match require_non_empty(&config.name, "log_listener.name") {
|
||||
fn validate_log_listener(config: &crate::LogListenerConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.name, "log_listener.name") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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<()> {
|
||||
match require_non_empty(&config.name, "program_listener.name") {
|
||||
fn validate_program_listener(config: &crate::ProgramListenerConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.name, "program_listener.name") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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<()> {
|
||||
match require_non_empty(&config.name, "account_listener.name") {
|
||||
fn validate_account_listener(config: &crate::AccountListenerConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.name, "account_listener.name") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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<()> {
|
||||
match require_non_empty(&config.wallet_dir, "wallet.wallet_dir") {
|
||||
pub(crate) fn validate_wallet(config: &crate::WalletConfig) -> ks_core::Result<()> {
|
||||
match crate::require_non_empty(&config.wallet_dir, "wallet.wallet_dir") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::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::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
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::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::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(());
|
||||
}
|
||||
|
||||
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
|
||||
.localnet_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(
|
||||
wallet: &WalletConfig,
|
||||
execution: &ExecutionConfig,
|
||||
wallet: &crate::WalletConfig,
|
||||
execution: &crate::ExecutionConfig,
|
||||
) -> ks_core::Result<()> {
|
||||
for (enabled, limit, cluster) in [
|
||||
(
|
||||
@@ -909,25 +871,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn schema_text_is_valid_json() {
|
||||
let result = super::config_json_schema_value();
|
||||
let result = crate::config_json_schema_value();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_and_example_app_configs_validate_against_schema() {
|
||||
assert!(super::validate_config_json_schema(DEFAULT_CONFIG).is_ok());
|
||||
assert!(super::validate_config_json_schema(EXAMPLE_CONFIG).is_ok());
|
||||
assert!(crate::validate_config_json_schema(DEFAULT_CONFIG).is_ok());
|
||||
assert!(crate::validate_config_json_schema(EXAMPLE_CONFIG).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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());
|
||||
let config = match config_result {
|
||||
std::result::Result::Ok(config) => config,
|
||||
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());
|
||||
//let active =
|
||||
match active_result {
|
||||
@@ -942,33 +904,62 @@ mod tests {
|
||||
let mut value = parse_default_value();
|
||||
value["active_profile"] = serde_json::Value::String("missing_profile".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_app_config_separates_runtime_postgres_profiles() {
|
||||
fn default_app_config_separates_selected_store_backend_options() {
|
||||
let value = parse_default_value();
|
||||
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())
|
||||
);
|
||||
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())
|
||||
);
|
||||
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())
|
||||
);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn schema_rejects_profile_enabled_field() {
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["enabled"] = serde_json::Value::Bool(true);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -977,7 +968,7 @@ mod tests {
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][1]["name"] = serde_json::Value::String("local_devnet".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -987,7 +978,7 @@ mod tests {
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["secret_env_field"] =
|
||||
serde_json::Value::String("KS_SECRET_HELIUS_API_KEY".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -997,7 +988,7 @@ mod tests {
|
||||
value["profiles"][0]["data"]["idls_directory"] =
|
||||
serde_json::Value::String("idls".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1006,7 +997,7 @@ mod tests {
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["solana"]["advanced_streams"] = serde_json::json!([]);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1016,7 +1007,7 @@ mod tests {
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["url"] =
|
||||
serde_json::Value::String("wss://api.devnet.solana.com".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1026,7 +1017,7 @@ mod tests {
|
||||
value["profiles"][0]["solana"]["ws_endpoints"][0]["url"] =
|
||||
serde_json::Value::String("https://api.devnet.solana.com".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1036,7 +1027,7 @@ mod tests {
|
||||
value["profiles"][0]["wallet"]["wallet_alias"] =
|
||||
serde_json::Value::String("../wallet".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1046,7 +1037,7 @@ mod tests {
|
||||
value["profiles"][0]["wallet"]["temporary_wallet_alias"] =
|
||||
serde_json::Value::String("../wallet".to_string());
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1055,7 +1046,7 @@ mod tests {
|
||||
let mut value = parse_default_value();
|
||||
value["profiles"][0]["wallet"]["temporary_wallet_enabled"] = serde_json::Value::Bool(false);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1065,7 +1056,7 @@ mod tests {
|
||||
value["profiles"][0]["execution"]["devnet_max_spend_lamports"] =
|
||||
serde_json::Value::Number(serde_json::Number::from(0));
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1081,7 +1072,7 @@ mod tests {
|
||||
value["profiles"][0]["execution"][field] =
|
||||
serde_json::Value::Number(serde_json::Number::from(0));
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1092,7 +1083,7 @@ mod tests {
|
||||
value["profiles"][0]["solana"]["http_endpoints"][0]["roles"][0]["max_subscriptions"] =
|
||||
serde_json::Value::Number(serde_json::Number::from(1));
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/store.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! 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.
|
||||
pub default_profile: std::string::String,
|
||||
/// Named store profiles.
|
||||
pub profiles: std::vec::Vec<StoreProfileConfig>,
|
||||
pub profiles: std::vec::Vec<crate::StoreProfileConfig>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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::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.
|
||||
pub fn parse_store_json(raw_json: &str) -> ks_core::Result<StoreConfigDocument> {
|
||||
match validate_store_json_schema(raw_json) {
|
||||
pub fn parse_store_json(raw_json: &str) -> ks_core::Result<crate::StoreConfigDocument> {
|
||||
match crate::validate_store_json_schema(raw_json) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(_) => {
|
||||
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::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(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<StoreConfigDocument> {
|
||||
) -> ks_core::Result<crate::StoreConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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.
|
||||
pub fn store_profile<'a>(
|
||||
document: &'a StoreConfigDocument,
|
||||
document: &'a crate::StoreConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a StoreProfileConfig> {
|
||||
) -> ks_core::Result<&'a crate::StoreProfileConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -133,13 +133,13 @@ pub fn store_profile<'a>(
|
||||
|
||||
/// Returns the default store profile.
|
||||
pub fn default_store_profile(
|
||||
document: &StoreConfigDocument,
|
||||
) -> ks_core::Result<&StoreProfileConfig> {
|
||||
return store_profile(document, document.default_profile.as_str());
|
||||
document: &crate::StoreConfigDocument,
|
||||
) -> ks_core::Result<&crate::StoreProfileConfig> {
|
||||
return crate::store_profile(document, document.default_profile.as_str());
|
||||
}
|
||||
|
||||
/// 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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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),
|
||||
}
|
||||
}
|
||||
return match default_store_profile(document) {
|
||||
return match crate::default_store_profile(document) {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
@@ -180,7 +180,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_and_example_store_configs_validate() {
|
||||
assert!(super::parse_store_json(DEFAULT_STORE).is_ok());
|
||||
assert!(super::parse_store_json(EXAMPLE_STORE).is_ok());
|
||||
assert!(crate::parse_store_json(DEFAULT_STORE).is_ok());
|
||||
assert!(crate::parse_store_json(EXAMPLE_STORE).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/transport.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! 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.
|
||||
pub default_profile: std::string::String,
|
||||
/// 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.
|
||||
pub profiles: std::vec::Vec<TransportProfileSourceConfig>,
|
||||
pub profiles: std::vec::Vec<crate::TransportProfileSourceConfig>,
|
||||
}
|
||||
|
||||
/// Default values shared by one class of WebSocket endpoints.
|
||||
@@ -70,7 +70,7 @@ pub struct WsEndpointSourceConfig {
|
||||
pub url: std::string::String,
|
||||
/// Optional endpoint-specific overrides.
|
||||
#[serde(default)]
|
||||
pub overrides: WsEndpointOverridesConfig,
|
||||
pub overrides: crate::WsEndpointOverridesConfig,
|
||||
/// Role-specific limits for this URL.
|
||||
pub roles: std::vec::Vec<crate::EndpointRoleConfig>,
|
||||
}
|
||||
@@ -83,7 +83,7 @@ pub struct TransportProfileSourceConfig {
|
||||
/// HTTP JSON-RPC endpoints.
|
||||
pub http_endpoints: std::vec::Vec<crate::HttpEndpointConfig>,
|
||||
/// 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.
|
||||
@@ -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.
|
||||
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::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.
|
||||
pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<TransportConfigDocument> {
|
||||
match validate_transport_json_schema(raw_json) {
|
||||
pub fn parse_transport_json(raw_json: &str) -> ks_core::Result<crate::TransportConfigDocument> {
|
||||
match crate::validate_transport_json_schema(raw_json) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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::Err(_) => {
|
||||
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::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(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<TransportConfigDocument> {
|
||||
) -> ks_core::Result<crate::TransportConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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.
|
||||
pub fn transport_profile(
|
||||
document: &TransportConfigDocument,
|
||||
document: &crate::TransportConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<TransportProfileConfig> {
|
||||
) -> ks_core::Result<crate::TransportProfileConfig> {
|
||||
let source = match source_transport_profile(document, profile_name) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(TransportProfileConfig {
|
||||
return std::result::Result::Ok(crate::TransportProfileConfig {
|
||||
name: source.name.clone(),
|
||||
http_endpoints: source.http_endpoints.clone(),
|
||||
ws_endpoints,
|
||||
@@ -268,15 +268,15 @@ pub fn transport_profile(
|
||||
|
||||
/// Returns the default resolved transport profile.
|
||||
pub fn default_transport_profile(
|
||||
document: &TransportConfigDocument,
|
||||
) -> ks_core::Result<TransportProfileConfig> {
|
||||
return transport_profile(document, document.default_profile.as_str());
|
||||
document: &crate::TransportConfigDocument,
|
||||
) -> ks_core::Result<crate::TransportProfileConfig> {
|
||||
return crate::transport_profile(document, document.default_profile.as_str());
|
||||
}
|
||||
|
||||
fn source_transport_profile<'a>(
|
||||
document: &'a TransportConfigDocument,
|
||||
document: &'a crate::TransportConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a TransportProfileSourceConfig> {
|
||||
) -> ks_core::Result<&'a crate::TransportProfileSourceConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -289,9 +289,9 @@ fn source_transport_profile<'a>(
|
||||
}
|
||||
|
||||
fn ws_endpoint_defaults<'a>(
|
||||
document: &'a TransportConfigDocument,
|
||||
document: &'a crate::TransportConfigDocument,
|
||||
defaults_name: &str,
|
||||
) -> ks_core::Result<&'a WsEndpointDefaultsConfig> {
|
||||
) -> ks_core::Result<&'a crate::WsEndpointDefaultsConfig> {
|
||||
for defaults in &document.ws_endpoint_defaults {
|
||||
if defaults.name == defaults_name {
|
||||
return std::result::Result::Ok(defaults);
|
||||
@@ -304,7 +304,9 @@ fn ws_endpoint_defaults<'a>(
|
||||
}
|
||||
|
||||
/// 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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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),
|
||||
}
|
||||
}
|
||||
match transport_profile(document, profile.name.as_str()) {
|
||||
match crate::transport_profile(document, profile.name.as_str()) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
@@ -398,17 +400,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_and_example_transport_configs_validate() {
|
||||
assert!(super::parse_transport_json(DEFAULT_TRANSPORT).is_ok());
|
||||
assert!(super::parse_transport_json(EXAMPLE_TRANSPORT).is_ok());
|
||||
assert!(crate::parse_transport_json(DEFAULT_TRANSPORT).is_ok());
|
||||
assert!(crate::parse_transport_json(EXAMPLE_TRANSPORT).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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::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::Err(error) => {
|
||||
panic!("mainnet research transport must resolve: {error}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/wallet.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! 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.
|
||||
pub default_profile: std::string::String,
|
||||
/// 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.
|
||||
@@ -41,7 +41,7 @@ pub fn wallet_json_schema_text() -> &'static str {
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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",
|
||||
));
|
||||
}
|
||||
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::Err(_) => {
|
||||
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::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(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<WalletConfigDocument> {
|
||||
) -> ks_core::Result<crate::WalletConfigDocument> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
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.
|
||||
pub fn wallet_profile<'a>(
|
||||
document: &'a WalletConfigDocument,
|
||||
document: &'a crate::WalletConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> ks_core::Result<&'a WalletProfileConfig> {
|
||||
) -> ks_core::Result<&'a crate::WalletProfileConfig> {
|
||||
for profile in &document.profiles {
|
||||
if profile.name == profile_name {
|
||||
return std::result::Result::Ok(profile);
|
||||
@@ -129,17 +129,17 @@ pub fn wallet_profile<'a>(
|
||||
|
||||
/// Returns the default wallet source profile.
|
||||
pub fn default_wallet_profile(
|
||||
document: &WalletConfigDocument,
|
||||
) -> ks_core::Result<&WalletProfileConfig> {
|
||||
return wallet_profile(document, document.default_profile.as_str());
|
||||
document: &crate::WalletConfigDocument,
|
||||
) -> ks_core::Result<&crate::WalletProfileConfig> {
|
||||
return crate::wallet_profile(document, document.default_profile.as_str());
|
||||
}
|
||||
|
||||
/// Resolves one source wallet profile into the runtime wallet contract.
|
||||
pub fn resolved_wallet_profile(
|
||||
document: &WalletConfigDocument,
|
||||
document: &crate::WalletConfigDocument,
|
||||
profile_name: &str,
|
||||
) -> 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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -166,7 +166,7 @@ pub fn resolved_wallet_profile(
|
||||
}
|
||||
|
||||
/// 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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
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(),
|
||||
));
|
||||
}
|
||||
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::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::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
@@ -221,9 +221,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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());
|
||||
let example = super::parse_wallet_json(EXAMPLE_WALLET);
|
||||
let example = crate::parse_wallet_json(EXAMPLE_WALLET);
|
||||
assert!(example.is_ok());
|
||||
let example = match example {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -238,11 +238,11 @@ mod tests {
|
||||
#[test]
|
||||
fn resolved_profile_separates_persistent_and_temporary_wallet_directories() {
|
||||
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::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::Err(error) => panic!("wallet profile resolution failed: {error}"),
|
||||
};
|
||||
@@ -269,7 +269,7 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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());
|
||||
let parsed = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -289,6 +289,6 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md -->
|
||||
<!-- version: 81 -->
|
||||
<!-- version: 82 -->
|
||||
|
||||
# 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`
|
||||
|
||||
- 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 ;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/README.md -->
|
||||
<!-- version: 31 -->
|
||||
<!-- version: 32 -->
|
||||
|
||||
# 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`.
|
||||
|
||||
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 bibliothèque Rust `ks_pipeline_demo_scenarios` ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/USAGE.md -->
|
||||
<!-- version: 33 -->
|
||||
<!-- version: 34 -->
|
||||
|
||||
# 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
|
||||
|
||||
@@ -63,13 +63,12 @@ fn memo_request() -> ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest {
|
||||
```
|
||||
|
||||
```rust
|
||||
async fn run_memo<S, O>(
|
||||
store: &S,
|
||||
async fn run_memo<O>(
|
||||
store: &ks_store::Store,
|
||||
observer: &O,
|
||||
request: ks_pipeline_demo_scenarios::DevnetMemoExecutionRequest,
|
||||
) -> ks_core::Result<ks_pipeline_demo_scenarios::DevnetMemoExecutionSummary>
|
||||
where
|
||||
S: ks_store::Store + Sync,
|
||||
O: ks_pipeline_demo_scenarios::SolanaExecutionObserver,
|
||||
{
|
||||
let result = ks_pipeline_demo_scenarios::execute_devnet_memo(
|
||||
@@ -78,14 +77,12 @@ where
|
||||
request,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(summary) => Ok(summary),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Simuler Token-2022
|
||||
|
||||
```rust
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/environment.rs
|
||||
// version: 7
|
||||
// version: 9
|
||||
|
||||
//! 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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DevnetProfileStoreReadiness {
|
||||
/// Selected Devnet profile.
|
||||
pub profile_name: std::string::String,
|
||||
/// Selected backend code.
|
||||
pub backend: std::string::String,
|
||||
/// Whether schema creation was allowed by configuration.
|
||||
pub auto_initialize_schema: bool,
|
||||
/// Number of known tables already present before preparation.
|
||||
pub existing_tables_before: usize,
|
||||
/// Number of known tables created during preparation.
|
||||
pub created_tables: usize,
|
||||
/// Number of known tables present after preparation.
|
||||
pub existing_tables_after: usize,
|
||||
/// Total number of known tables expected by the current store.
|
||||
pub expected_tables: usize,
|
||||
/// Number of known logical resources already present before preparation.
|
||||
pub available_resources_before: usize,
|
||||
/// Number of known logical resources created during preparation.
|
||||
pub created_resources: usize,
|
||||
/// Number of known logical resources present after preparation.
|
||||
pub available_resources_after: usize,
|
||||
/// Total number of logical resources expected by the current store contract.
|
||||
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(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
) -> ks_core::Result<DevnetProfileStoreReadiness> {
|
||||
) -> ks_core::Result<crate::DevnetProfileStoreReadiness> {
|
||||
if profile.wallet.cluster != "devnet" {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"profile '{}' is not configured for Devnet",
|
||||
@@ -117,65 +142,78 @@ pub async fn prepare_demo_devnet_profile_store(
|
||||
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!(
|
||||
"Devnet profile '{}' must use the PostgreSQL backend",
|
||||
profile.name
|
||||
"Devnet profile '{}' store model is incomplete and automatic initialization is {}",
|
||||
profile.name,
|
||||
if configuration.auto_initialize_schema { "enabled" } else { "disabled" }
|
||||
)));
|
||||
}
|
||||
let options = match ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
profile.database.postgres.connect_timeout_ms,
|
||||
false,
|
||||
return std::result::Result::Ok(crate::DevnetProfileStoreReadiness {
|
||||
profile_name: profile.name.clone(),
|
||||
backend: configuration.backend_code,
|
||||
auto_initialize_schema: configuration.auto_initialize_schema,
|
||||
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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let store = match ks_store::PostgresStore::connect(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(),
|
||||
});
|
||||
return ks_store::Store::open(options).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/lib.rs
|
||||
// version: 31
|
||||
// version: 34
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -14,17 +14,27 @@ mod solana;
|
||||
mod spl;
|
||||
mod wallet;
|
||||
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
/// Backend-agnostic readiness report for one Devnet profile store.
|
||||
pub use self::environment::DevnetProfileStoreReadiness;
|
||||
/// Loads the workspace environment for opt-in demo and Devnet scenarios.
|
||||
pub use self::environment::initialize_demo_scenario_environment;
|
||||
/// Loads the shared default configuration or an explicit scenario composition override.
|
||||
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;
|
||||
/// Resolves one configured Devnet profile for UI, CLI and test scenarios.
|
||||
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;
|
||||
/// Collection relation and sized-collection state observed across verification.
|
||||
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;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// 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;
|
||||
@@ -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;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// Executes the final bounded current maintenance campaign on Devnet.
|
||||
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;
|
||||
/// Severity of one Solana execution progress event.
|
||||
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;
|
||||
/// Rejects one execution stage when the observer reports cancellation.
|
||||
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;
|
||||
/// Inspects the persistent native wallet alias selected by one execution profile.
|
||||
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;
|
||||
/// Formats one failed simulation without discarding runtime diagnostics.
|
||||
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;
|
||||
/// Complete result of one Devnet classic SPL Token execution.
|
||||
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;
|
||||
/// Decode completion helper shared by Token lifecycle orchestration.
|
||||
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;
|
||||
/// Executes one SPL Token operation with an explicitly resolved wallet capability.
|
||||
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;
|
||||
/// Targeted program replay helper shared by Token lifecycle orchestration.
|
||||
pub(crate) use self::spl::token::execution::replay_program;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -678,23 +678,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -844,23 +844,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1516,18 +1516,10 @@ mod tests {
|
||||
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::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() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("workspace root cannot be resolved"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1004,23 +1004,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -598,23 +598,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1096,23 +1096,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1210,23 +1210,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -455,23 +455,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/solana.rs
|
||||
// version: 18
|
||||
// version: 20
|
||||
|
||||
//! Devnet Solana execution orchestration with canonical post-validation.
|
||||
|
||||
@@ -1544,8 +1544,8 @@ mod tests {
|
||||
},
|
||||
};
|
||||
let mut profile = example_devnet_profile();
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, 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") {
|
||||
profile.wallet.temporary_wallet_dir = directory;
|
||||
}
|
||||
@@ -1553,22 +1553,10 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
|
||||
};
|
||||
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,
|
||||
) {
|
||||
let store = match crate::open_profile_store(&profile).await {
|
||||
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") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("recipient alias failed: {error}"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs
|
||||
// version: 17
|
||||
// version: 19
|
||||
|
||||
//! Devnet ATA execution with stateful and canonical post-validation.
|
||||
|
||||
@@ -1162,20 +1162,10 @@ mod tests {
|
||||
request.post_validation_max_retries = 20;
|
||||
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
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::DcSplTokenDecoder),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/memo.rs
|
||||
// version: 13
|
||||
// version: 15
|
||||
|
||||
//! Devnet SPL Memo v4 execution with canonical post-validation.
|
||||
|
||||
@@ -857,8 +857,8 @@ mod tests {
|
||||
},
|
||||
};
|
||||
let mut profile = example_devnet_profile();
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, 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") {
|
||||
profile.wallet.temporary_wallet_dir = directory;
|
||||
}
|
||||
@@ -866,22 +866,10 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
|
||||
};
|
||||
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,
|
||||
) {
|
||||
let store = match crate::open_profile_store(&profile).await {
|
||||
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(
|
||||
format!("devnet-memo-test-{}", uuid::Uuid::new_v4()),
|
||||
format!("khadhroony-bot3 memo validation {}", uuid::Uuid::new_v4()),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -1189,24 +1189,12 @@ mod tests {
|
||||
panic!("KS_SECRET_POSTGRES_TEST_URL is required for submission: {error}");
|
||||
},
|
||||
};
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
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,
|
||||
) {
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let store = match crate::open_profile_store(&profile).await {
|
||||
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>> =
|
||||
std::vec![std::sync::Arc::new(ks_lib::DcSplTokenDecoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>> =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs
|
||||
// version: 16
|
||||
// version: 18
|
||||
|
||||
//! 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}");
|
||||
},
|
||||
};
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
|
||||
};
|
||||
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,
|
||||
) {
|
||||
let store = match crate::open_profile_store(&profile).await {
|
||||
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() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("workspace root cannot be resolved"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -529,23 +529,14 @@ mod tests {
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KS_SECRET_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KS_SECRET_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
crate::override_profile_store_url_for_test(&mut profile, database_url)
|
||||
.unwrap_or_else(|error| panic!("Store test override failed: {error}"));
|
||||
let pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
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 store = match crate::open_profile_store(&profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Store connection failed: {error}"),
|
||||
};
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
<!-- file: ks-store/CHANGELOG.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 23 -->
|
||||
|
||||
# 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()` : l’initialisation 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 d’un 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 l’attente 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 l’unique `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`
|
||||
|
||||
### 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 ;
|
||||
- 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 ;
|
||||
- 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 l’unique target `ks-store` ;
|
||||
- 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` ;
|
||||
- considère `pre.007` comme premier candidat de clôture et autorise explicitement son décalage si des manques apparaissent ;
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
<!-- file: ks-store/README.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# ks-store
|
||||
|
||||
`ks-store` consolide les contrats de stockage et l’implé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
|
||||
|
||||
La crate expose :
|
||||
|
||||
- DTO et entités raw, observations, Core, décodage, matérialisation et ledger ;
|
||||
- traits async de repositories indépendants du backend ;
|
||||
- pagination et filtres bornés ;
|
||||
- `PostgresStore` et ses options de connexion ;
|
||||
- initialisation idempotente du schéma et migrations SQL ;
|
||||
- diagnostics read-only, healthchecks et résumés de replay ;
|
||||
- transactions atomiques pour extraction, décodage et matérialisation.
|
||||
- `Store` et `StoreOpenOptions` comme façade d'ouverture et handle persistant backend-agnostique ;
|
||||
- les DTO et entités raw, observations, Core, décodage, matérialisation et ledger ;
|
||||
- les traits async `StoreHealthStore`, `RawTransactionStore`, `CoreTransactionStore`, `CoreExtractionStore` et `DecodePipelineStore` ;
|
||||
- les filtres et résultats de replay sans préfixe backend ;
|
||||
- des diagnostics et résumés runtime/init sanitisés ne révélant ni secret ni nom physique d'objet ;
|
||||
- des 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 l’unique 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
|
||||
|
||||
- garantir les invariants des données persistées ;
|
||||
- séparer contrats et implémentation PostgreSQL à l’intérieur de la crate consolidée ;
|
||||
- maintenir les tables canoniques `kb_sol_*` ;
|
||||
- fournir au pipeline des frontières async typées ;
|
||||
- posséder la connexion et l'initialisation du backend actif ;
|
||||
- fournir des frontières async typées au pipeline ;
|
||||
- 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
|
||||
|
||||
La crate ne décode pas les instructions, n’acquiert 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`.
|
||||
|
||||
## 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 d’administration.
|
||||
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`.
|
||||
|
||||
## Relations
|
||||
|
||||
- dépend de `ks-core` pour les erreurs ;
|
||||
- utilise les contrats de replay de `ks-lib` ;
|
||||
- est consommée principalement par `ks-pipeline`, les scénarios de démonstration et le desktop.
|
||||
|
||||
## 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 l’application préalable des migrations.
|
||||
- est consommée principalement par `ks-pipeline`, `ks-pipeline-demo-scenarios` et `kb-app-demo-desktop` ;
|
||||
- ne dépend pas de `ks-config`.
|
||||
|
||||
## Documents
|
||||
|
||||
@@ -50,3 +58,4 @@ Le stockage consolidé dispose de tests unitaires étendus et de tests PostgreSQ
|
||||
- [Travaux restants](TODO.md)
|
||||
- [Historique](CHANGELOG.md)
|
||||
- [Architecture du stockage](../docs/architecture/STORAGE_ARCHITECTURE.md)
|
||||
- [Plan actif `0.5.3`](../docs/plans/V0_5_3_KS_STORE_NORMALIZATION_PLAN.md)
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<!-- file: ks-store/TODO.md -->
|
||||
<!-- version: 11 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# TODO — ks-store
|
||||
|
||||
## 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` — 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` — 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` — 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.
|
||||
- [ ] 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.
|
||||
|
||||
@@ -1,90 +1,79 @@
|
||||
<!-- file: ks-store/USAGE.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# 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
|
||||
let options = match ks_store::PostgresStoreOptions::new(
|
||||
database_url,
|
||||
10,
|
||||
10_000,
|
||||
let options = match ks_store::StoreOpenOptions::new(
|
||||
true,
|
||||
"postgres",
|
||||
serde_json::json!({
|
||||
"url": database_url,
|
||||
"max_connections": 10,
|
||||
"connect_timeout_ms": 10_000,
|
||||
"auto_initialize_schema": true
|
||||
}),
|
||||
) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
|
||||
println!("postgres endpoint: {}", options.masked_dsn());
|
||||
|
||||
let store = match ks_store::PostgresStore::connect(options).await {
|
||||
let store = match ks_store::Store::open(options).await {
|
||||
Ok(value) => value,
|
||||
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
|
||||
let health = store.health_snapshot().await;
|
||||
let migrations = store.migration_snapshot().await;
|
||||
let backend = store.backend_diagnostics().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 {
|
||||
let configuration = store.configuration_summary();
|
||||
let initialization = store.initialization_summary();
|
||||
let runtime = match store.runtime_summary().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
```
|
||||
|
||||
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!(
|
||||
"table={} domain={} exists={}",
|
||||
table.table_name,
|
||||
table.domain,
|
||||
table.exists
|
||||
"resource={} model={} available={}",
|
||||
resource.resource_code,
|
||||
resource.model_code,
|
||||
resource.available
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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);
|
||||
```
|
||||
Les méthodes `core_resource_diagnostics`, `decode_resource_diagnostics` et `known_resource_diagnostics` suivent le même contrat backend-agnostique.
|
||||
|
||||
## Contrats de repositories
|
||||
|
||||
Les traits publics principaux sont :
|
||||
Les traits publics effectivement implémentés sont :
|
||||
|
||||
- `StoreHealthStore` ;
|
||||
- `RawTransactionStore` ;
|
||||
- `CoreTransactionStore` ;
|
||||
- `CoreExtractionStore` ;
|
||||
- `DecodePipelineStore` ;
|
||||
- `ProgramObservationStore` ;
|
||||
- `DecodedEventStore` ;
|
||||
- `MaterializedEventStore` ;
|
||||
- `ProcessingLedgerStore`.
|
||||
- `DecodePipelineStore`.
|
||||
|
||||
Ils permettent au pipeline de dépendre d’un contrat async plutôt que d’une 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
|
||||
|
||||
@@ -93,71 +82,43 @@ let candidates = match store.replay_transaction_candidates(filter).await {
|
||||
Ok(value) => value,
|
||||
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 {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
|
||||
let entities = match store.replay_entity_summaries(entity_filter).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
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.
|
||||
|
||||
assert!(ks_store::is_valid_solana_table_name(
|
||||
ks_store::CORE_TRANSACTIONS_TABLE_NAME
|
||||
));
|
||||
```
|
||||
## Limites de `pre.002`
|
||||
|
||||
## Schéma et tables
|
||||
|
||||
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 d’administration.
|
||||
|
||||
## 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 l’atomicité ;
|
||||
- `optional_postgres_*_from_env` couvre les parcours réels lorsque l’environnement 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.
|
||||
- les tables restent temporairement sous leur namespace historique jusqu'à `pre.003` ;
|
||||
- les migrations PostgreSQL ne sont pas encore déplacées sous `migrations/postgres/` ;
|
||||
- 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.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// file: ks-store/src/constants.rs
|
||||
// version: 2
|
||||
// version: 5
|
||||
|
||||
//! 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";
|
||||
/// 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";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-store/src/contracts.rs
|
||||
// version: 3
|
||||
// version: 6
|
||||
|
||||
//! Backend-neutral storage contracts used by pipeline crates.
|
||||
|
||||
@@ -8,6 +8,7 @@ mod entity;
|
||||
mod error;
|
||||
mod health;
|
||||
mod pagination;
|
||||
mod replay;
|
||||
mod repository;
|
||||
|
||||
/// Core account key insert contract.
|
||||
@@ -52,8 +53,6 @@ pub use self::dto::DecodeObservationInsert;
|
||||
pub use self::dto::DecodePersistenceBundle;
|
||||
/// Bounded contextual instruction selection filter for decode campaigns.
|
||||
pub use self::dto::DecodeSelectionFilter;
|
||||
/// Decoded event insert contract.
|
||||
pub use self::dto::DecodedEventInsert;
|
||||
/// Insert or upsert result contract returned by repositories.
|
||||
pub use self::dto::InsertOutcome;
|
||||
/// 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;
|
||||
/// Bounded read-only materialized event selection.
|
||||
pub use self::dto::MaterializedEventFilter;
|
||||
/// Materialized event insert contract.
|
||||
pub use self::dto::MaterializedEventInsert;
|
||||
/// One materialized output returned by a bounded query.
|
||||
pub use self::dto::MaterializedEventQueryRow;
|
||||
/// One processor-owned materialized output row.
|
||||
pub use self::dto::MaterializedOutputInsert;
|
||||
/// Stable processing ledger identity.
|
||||
pub use self::dto::ProcessingLedgerIdentity;
|
||||
/// Processing ledger mark request contract.
|
||||
pub use self::dto::ProcessingLedgerMark;
|
||||
/// Stable processing ledger status.
|
||||
pub use self::dto::ProcessingLedgerStatus;
|
||||
/// Raw payload lifecycle mark request.
|
||||
@@ -84,12 +79,30 @@ pub use self::dto::RawPayloadRetentionState;
|
||||
pub use self::dto::RawTransactionInsert;
|
||||
/// Store backend diagnostic contract.
|
||||
pub use self::dto::StoreBackendDescriptor;
|
||||
/// Store backend kind contract.
|
||||
pub use self::dto::StoreBackendKind;
|
||||
/// Backend-neutral diagnostic snapshot.
|
||||
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.
|
||||
pub use self::dto::StoreMigrationSnapshot;
|
||||
/// Store migration status contract.
|
||||
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.
|
||||
pub use self::dto::TransactionObservationInsert;
|
||||
/// Transaction acquisition observation origin.
|
||||
@@ -108,12 +121,6 @@ pub use self::entity::CoreInstructionRow;
|
||||
pub use self::entity::CoreLogRow;
|
||||
/// Core transaction SQL-like row contract.
|
||||
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.
|
||||
pub use self::entity::RawTransactionRow;
|
||||
/// Transaction acquisition observation SQL-like row contract.
|
||||
@@ -132,20 +139,30 @@ pub use self::pagination::MAX_PAGE_SIZE;
|
||||
pub use self::pagination::PageRequest;
|
||||
/// Sort direction for repository list operations.
|
||||
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.
|
||||
pub use self::repository::CoreExtractionStore;
|
||||
/// Core Solana storage behavior.
|
||||
pub use self::repository::CoreTransactionStore;
|
||||
/// Contextual instruction decode and materialization storage behavior.
|
||||
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.
|
||||
pub use self::repository::RawTransactionStore;
|
||||
/// Store health storage behavior.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user