diff --git a/ROADMAP.md b/ROADMAP.md index bac860a..1a3f3d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,5 @@ - + # ROADMAP — khadhroony-bot3 @@ -204,3 +204,39 @@ - [x] Réduire les frontières réservées à 102 et adapter l’audit. - [ ] Valider les tests portés, Clippy et le workspace avec Cargo sur la machine de développement. - [ ] Porter ensuite SPL Token classique et SPL Associated Token Account. + +### 0.1.0-pre.017 — Exécuteur SPL Associated Token Account + +- [x] Remplacer le squelette ATA par l’exécuteur typé validé de bot2. +- [x] Conserver `Create`, `CreateIdempotent`, `RecoverNested`, les builders officiels et les validations post-exécution. +- [x] Porter le target vers `kb-lib.executor.spl.associated_token_account`. +- [x] Réduire les frontières réservées à 101. +- [x] Valider les 548 tests unitaires, les tests d’intégration, Clippy et le workspace sur la machine de développement. + +### 0.1.0-pre.018 — Exécuteur SPL Token-2022 + +- [x] Remplacer le squelette Token-2022 par les cinq modules métier complets de bot2. +- [x] Conserver les 33 tests, les extensions, Confidential Transfer et Confidential Mint/Burn. +- [x] Porter le target vers `kb-lib.executor.spl.token2022` et la nomenclature interne vers `TOKEN2022`. +- [x] Réduire les frontières réservées à 100. +- [x] Valider les 592 tests unitaires, les tests d’intégration, Clippy et le workspace sur la machine de développement. + +### 0.1.0-pre.019 — Exécuteur SPL ElGamal Registry + +- [x] Remplacer le squelette ElGamal Registry par les opérations `CreateRegistry` et `UpdateRegistry` validées de bot2. +- [x] Conserver les preuves inline/contexte, la simulation obligatoire, les plafonds de frais et les validations post-exécution. +- [x] Déplacer `spl-elgamal-registry-interface` dans les dépendances normales de `kb-lib`. +- [x] Porter le target vers `kb-lib.executor.spl.elgamal_registry`. +- [x] Réduire les frontières réservées à 99. +- [x] Valider les 598 tests unitaires, les tests d’intégration, Clippy et le workspace sur la machine de développement. + +### 0.1.0-pre.020 — Configuration et logging + +- [x] Remplacer le scaffold `kb-config` par les modèles, parseurs, sérialiseurs, validations JSON Schema et 20 tests de bot2. +- [x] Remplacer le scaffold `kb-logging` par la configuration des routes, le runtime tracing multi-writers et les 17 tests de bot2. +- [x] Préserver `kb_logging::tracing_target()` pour la compatibilité temporaire de `kb-app-demo`. +- [x] Corriger les targets et chemins de logs de Solana Core et SPL Memo dans les trois profils. +- [x] Vérifier ATA, Token-2022 et ElGamal Registry, déjà alignés sur leurs targets bot3. +- [x] Laisser SPL Token classique sous son identité réservée tant que son exécuteur n’est pas migré. +- [ ] Valider `kb-config`, `kb-logging`, Clippy et le workspace avec Cargo sur la machine de développement. +- [ ] Décider puis appliquer le renommage de `kb-rpc` avant sa migration fonctionnelle. diff --git a/RULES.md b/RULES.md index 450842a..ecc3523 100644 --- a/RULES.md +++ b/RULES.md @@ -143,7 +143,7 @@ Aucun renommage massif de modules n'est autorisé sans étape de contrôle dédi - Une crate ou un composant de `kb-lib` est opérationnel lorsqu’il effectue des I/O, orchestre un pipeline, décode, matérialise, exécute, applique une politique runtime ou prend une décision mutable observable. - Toute crate opérationnelle ajoutée ou modifiée doit dépendre de `tracing` depuis le workspace et déclarer exactement un `pub(crate) const TRACING_TARGET` dans `src/constants.rs`. -- Hors `kb-lib`, la valeur canonique de `DC_METADATA_MTM_TRACING_TARGET` est le nom exact du package Cargo. +- Hors `kb-lib`, la valeur canonique de `TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA` est le nom exact du package Cargo. - Dans `kb-lib`, chaque composant opérationnel possède son propre `constants.rs` et un target hiérarchique stable fondé sur son identifiant de décodeur, matérialiseur ou exécuteur ; un target unique `kb-lib` ne doit pas effacer l'identité du composant. - Les targets historiques `khbot.*` et les targets de fenêtre sont interdits dans les nouvelles modifications. - Les macros `tracing` doivent utiliser `target: crate::TRACING_TARGET` et des champs structurés stables. La granularité interne passe par `action`, `stage`, `window`, `campaign_id`, `signature`, `instruction_path`, `program_id`, `processor_name`, `processor_version`, `status` et `error_code`. @@ -157,7 +157,7 @@ Aucun renommage massif de modules n'est autorisé sans étape de contrôle dédi - Chaque profil doit router les événements vers les sorties globales `debug.log`, `info.log`, `error.jsonl` et `app.log`, puis vers `debug.log`, `info.log` et `error.jsonl` dans un répertoire propre à chaque crate utilisant `tracing`. - L’ajout ou la suppression de `tracing.workspace = true` dans une crate impose la mise à jour simultanée de la matrice de routes, de ses tests et de `docs/TRACING_CONTRACT.md`. - Le contrat détaillé est défini dans `docs/TRACING_CONTRACT.md`. -- L’audit mécanique spécifique au projet est exécuté par `python3 scripts/audit_khadhroony_workspace_rules.py`. Il couvre notamment `DC_METADATA_MTM_TRACING_TARGET` et l’usage obligatoire de `solana_pubkey::Pubkey` à la place de `solana_address::Address`. +- L’audit mécanique spécifique au projet est exécuté par `python3 scripts/audit_khadhroony_workspace_rules.py`. Il couvre notamment `TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA` et l’usage obligatoire de `solana_pubkey::Pubkey` à la place de `solana_address::Address`. ## Règles de réutilisation des interfaces Solana et SPL diff --git a/config/example.config.json b/config/example.config.json index b66862f..88c927e 100644 --- a/config/example.config.json +++ b/config/example.config.json @@ -107,6 +107,45 @@ "kb_app_demo" ] }, + { + "name": "file_kb_lib_local_devnet_debug", + "enabled": true, + "sink": "file", + "level": "debug", + "path": "logs/devnet/kb-lib/debug.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_local_devnet_info", + "enabled": true, + "sink": "file", + "level": "info", + "path": "logs/devnet/kb-lib/info.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_local_devnet_error", + "enabled": true, + "sink": "file", + "level": "error", + "path": "logs/devnet/kb-lib/error.jsonl", + "rotation": "daily", + "format": "json", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, { "name": "file_kb_app_demo_debug", "enabled": true, @@ -1709,6 +1748,45 @@ "kb_app_demo" ] }, + { + "name": "file_kb_lib_mainnet_research_debug", + "enabled": true, + "sink": "file", + "level": "debug", + "path": "logs/mainnet_research/kb-lib/debug.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_mainnet_research_info", + "enabled": true, + "sink": "file", + "level": "info", + "path": "logs/mainnet_research/kb-lib/info.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_mainnet_research_error", + "enabled": true, + "sink": "file", + "level": "error", + "path": "logs/mainnet_research/kb-lib/error.jsonl", + "rotation": "daily", + "format": "json", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, { "name": "file_kb_app_demo_debug", "enabled": true, @@ -3511,6 +3589,45 @@ "kb_app_demo" ] }, + { + "name": "file_kb_lib_mainnet_debug", + "enabled": true, + "sink": "file", + "level": "debug", + "path": "logs/mainnet/kb-lib/debug.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_mainnet_info", + "enabled": true, + "sink": "file", + "level": "info", + "path": "logs/mainnet/kb-lib/info.log", + "rotation": "daily", + "format": "human", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, + { + "name": "file_kb_lib_mainnet_error", + "enabled": true, + "sink": "file", + "level": "error", + "path": "logs/mainnet/kb-lib/error.jsonl", + "rotation": "daily", + "format": "json", + "ansi": false, + "targets": [ + "kb-lib" + ] + }, { "name": "file_kb_app_demo_debug", "enabled": true, diff --git a/docs/PROGRAM_REGISTRY_CONTROL.md b/docs/PROGRAM_REGISTRY_CONTROL.md index 5688595..3f8be81 100644 --- a/docs/PROGRAM_REGISTRY_CONTROL.md +++ b/docs/PROGRAM_REGISTRY_CONTROL.md @@ -245,15 +245,15 @@ Ces identifiants sont détaillés dans `docs/CORE_PROGRAM_IDS.md`. Ils sont couv ## NFT, metadata, admin, governance et locks -| Canonique | Program ID | Source | Normalisé | Crate actuelle | Crate cible | Statut | -|------------------------------------|------------------------------------------------|---------------------------|---------------------------|------------------------|------------------------------------------------------|--------------------| -| `lock_raydium_lp` | `LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE` | `raydium_lock_lp` | `raydium_lock_lp` | `-` | `kb_decoder_lock_raydium_lp` | `reserved_missing` | -| `admin_jupiter_lock` | `LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn` | `jupiter_lock` | `jupiter_lock` | `-` | `kb_decoder_admin_jupiter_lock` | `reserved_missing` | -| `admin_pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | `pump_fees` | `pump_fees` | `kb_decoder_pump_fees` | `kb_decoder_admin_pump_fees` | `current_alias` | +| Canonique | Program ID | Source | Normalisé | Crate actuelle | Crate cible | Statut | +|------------------------------------|------------------------------------------------|---------------------------|---------------------------|------------------------|--------------------------------------------------|--------------------| +| `lock_raydium_lp` | `LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE` | `raydium_lock_lp` | `raydium_lock_lp` | `-` | `kb_decoder_lock_raydium_lp` | `reserved_missing` | +| `admin_jupiter_lock` | `LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn` | `jupiter_lock` | `jupiter_lock` | `-` | `kb_decoder_admin_jupiter_lock` | `reserved_missing` | +| `admin_pump_fees` | `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` | `pump_fees` | `pump_fees` | `kb_decoder_pump_fees` | `kb_decoder_admin_pump_fees` | `current_alias` | | `metadata_metaplex_token_metadata` | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | `metaplex_token_metadata` | `metaplex_token_metadata` | `-` | `kb_lib::DcMetadataMetaplexTokenMetadataDecoder` | `current_migrated` | -| `metadata_name_service` | `namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX` | `name_service` | `name_service` | `-` | `kb_decoder_metadata_spl_name_service` | `reserved_missing` | -| `nft_metaplex_bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | `bubblegum` | `bubblegum` | `-` | `kb_decoder_nft_metaplex_bubblegum` | `reserved_missing` | -| `nft_metaplex_mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | `mpl_core` | `mpl_core` | `-` | `kb_decoder_nft_metaplex_mpl_core` | `reserved_missing` | +| `metadata_name_service` | `namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX` | `name_service` | `name_service` | `-` | `kb_decoder_metadata_spl_name_service` | `reserved_missing` | +| `nft_metaplex_bubblegum` | `BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY` | `bubblegum` | `bubblegum` | `-` | `kb_decoder_nft_metaplex_bubblegum` | `reserved_missing` | +| `nft_metaplex_mpl_core` | `CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d` | `mpl_core` | `mpl_core` | `-` | `kb_decoder_nft_metaplex_mpl_core` | `reserved_missing` | ## Surfaces à classifier diff --git a/docs/RUST_WORKSPACE_RULE_AUDIT.md b/docs/RUST_WORKSPACE_RULE_AUDIT.md index ffcb7a4..9b6f981 100644 --- a/docs/RUST_WORKSPACE_RULE_AUDIT.md +++ b/docs/RUST_WORKSPACE_RULE_AUDIT.md @@ -57,7 +57,7 @@ pub(crate) use self::constants::TRACING_TARGET; ``` Un composant opérationnel de `kb-lib` utilise un nom préfixé, par exemple -`DC_SPL_TOKEN2022_TRACING_TARGET` ou `MT_TOKEN_ACCOUNTS_TRACING_TARGET`, réexporté +`TRACING_TARGET_DECODER_SPL_TOKEN2022` ou `TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS`, réexporté transitivement jusqu’à `kb-lib/src/lib.rs`. Sa valeur suit exactement le chemin hiérarchique du composant, par exemple `kb-lib.decoder.spl.token2022`. diff --git a/docs/legacy-khadhroony-bot2/RULES.md b/docs/legacy-khadhroony-bot2/RULES.md index 245e53a..6bb8fe3 100644 --- a/docs/legacy-khadhroony-bot2/RULES.md +++ b/docs/legacy-khadhroony-bot2/RULES.md @@ -138,7 +138,7 @@ Aucun renommage massif de crates n'est autorisé sans étape de contrôle dédi - Une crate est opérationnelle lorsqu’elle effectue des I/O, orchestre un pipeline, décode, matérialise, exécute, applique une politique runtime ou prend une décision mutable observable. - Toute crate opérationnelle ajoutée ou modifiée doit dépendre de `tracing` depuis le workspace et déclarer exactement un `pub(crate) const TRACING_TARGET` dans `src/constants.rs`. -- La valeur canonique de `DC_METADATA_MTM_TRACING_TARGET` est le nom exact du package Cargo. Les targets historiques `khbot.*`, les suffixes de module et les targets de fenêtre sont interdits dans les nouvelles modifications. +- La valeur canonique de `TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA` est le nom exact du package Cargo. Les targets historiques `khbot.*`, les suffixes de module et les targets de fenêtre sont interdits dans les nouvelles modifications. - Les macros `tracing` doivent utiliser `target: crate::TRACING_TARGET` et des champs structurés stables. La granularité interne passe par `action`, `stage`, `window`, `campaign_id`, `signature`, `instruction_path`, `program_id`, `processor_name`, `processor_version`, `status` et `error_code`. - Les crates passives de types, contrats, DTO, API sans exécution, registres ou constantes restent sans dépendance `tracing`. `kb_config` reste une exception de bootstrap tant que sa validation précède l’installation du subscriber. - Il est interdit d’ajouter `tracing` sans événement réel ou de conserver un faux target uniquement consommé par `let _target`. @@ -150,7 +150,7 @@ Aucun renommage massif de crates n'est autorisé sans étape de contrôle dédi - Chaque profil doit router les événements vers les sorties globales `debug.log`, `info.log`, `error.jsonl` et `app.log`, puis vers `debug.log`, `info.log` et `error.jsonl` dans un répertoire propre à chaque crate utilisant `tracing`. - L’ajout ou la suppression de `tracing.workspace = true` dans une crate impose la mise à jour simultanée de la matrice de routes, de ses tests et de `docs/TRACING_CONTRACT.md`. - Le contrat détaillé est défini dans `docs/TRACING_CONTRACT.md`. -- L’audit mécanique spécifique au projet est exécuté par `python3 scripts/audit_khadhroony_workspace_rules.py`. Il couvre notamment `DC_METADATA_MTM_TRACING_TARGET` et l’usage obligatoire de `solana_pubkey::Pubkey` à la place de `solana_address::Address`. +- L’audit mécanique spécifique au projet est exécuté par `python3 scripts/audit_khadhroony_workspace_rules.py`. Il couvre notamment `TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA` et l’usage obligatoire de `solana_pubkey::Pubkey` à la place de `solana_address::Address`. ## Règles de réutilisation des interfaces Solana et SPL diff --git a/kb-config/Cargo.toml b/kb-config/Cargo.toml index 79c2377..65a6593 100644 --- a/kb-config/Cargo.toml +++ b/kb-config/Cargo.toml @@ -1,5 +1,5 @@ # file: kb-config/Cargo.toml -# version: 1 +# version: 2 [package] name = "kb-config" @@ -10,6 +10,10 @@ publish.workspace = true [dependencies] kb-core = { path = "../kb-core" } +jsonschema.workspace = true +serde.workspace = true +serde_json.workspace = true +ts-rs.workspace = true [lints] workspace = true diff --git a/kb-config/README.md b/kb-config/README.md new file mode 100644 index 0000000..a20e382 --- /dev/null +++ b/kb-config/README.md @@ -0,0 +1,68 @@ + + + +# kb-config + +Ce crate définit le contrat de configuration commun du workspace. + +## Rôle dans l'écosystème + +`kb-config` charge, valide et sérialise un fichier JSON contenant plusieurs profils. Un seul profil est actif à l'exécution grâce au champ `active_profile`. + +Le crate fournit les structures typées runtime pour : + +- l'application et l'environnement ; +- les sorties de logs console, fichier humain, fichier JSON et fichier erreurs ; +- les filtres de targets `tracing` ; +- les stores PostgreSQL et SQLite ; +- les endpoints HTTP JSON-RPC et WebSocket Solana ; +- les rôles et limites par endpoint ; +- les listeners Solana standards ; +- le wallet temporaire géré par profil ; +- les permissions d'envoi localnet/devnet/testnet/mainnet ; +- les plafonds de dépense, frais, priorité et fraîcheur du blockhash ; +- les pages de démonstration Tauri. + +## Wallet temporaire + +Le profil `local_devnet` active un wallet persistant de laboratoire sous : + +```text +wallets/temporary/local_devnet/local-devnet-operator.json +``` + +Les profils mainnet conservent `temporary_wallet_enabled = false`. La validation typée refuse un wallet temporaire actif sur `mainnet-beta`, une persistance sans activation et les alias pouvant produire un chemin arbitraire. + +## Exécution + +La configuration sépare : + +- le dry-run par défaut ; +- l'obligation de simulation ; +- la confirmation opérateur ; +- les plafonds de dépense localnet, devnet, testnet et mainnet ; +- le plafond de frais ; +- le plafond de prix par compute unit ; +- l'âge maximal du recent blockhash. + +Un cluster dont l'envoi est activé doit disposer d'un plafond de dépense positif. Toute dépense configurée exige la simulation et toute dépense mainnet exige la confirmation opérateur. Les paramètres de confirmation sont bornés et le plafond d’airdrop Devnet est interdit dans les profils d’un autre cluster. + +## Validation + +La validation se fait en deux étapes : + +1. validation JSON brute via `config/schema.config.json` et `jsonschema` ; +2. validation typée après désérialisation Rust. + +## Secrets et placeholders + +Le fichier versionné doit rester sans secret. Les placeholders restent directement dans les URLs. Les keypairs générés par `kb_wallet` vivent hors du fichier de configuration et sous un répertoire ignoré par Git. + +## Exports TS-rs + +Les structures exposées à Tauri utilisent `TS` avec un chemin `export_to` explicite. Les fichiers générés servent uniquement au partage des types Rust/TypeScript et ne remplacent pas le schéma JSON runtime. + +```bash +cargo test export_bindings -p kb-config +cargo test -p kb-config settings::tests:: +``` diff --git a/kb-config/src/lib.rs b/kb-config/src/lib.rs index 3a838fd..9511e77 100644 --- a/kb-config/src/lib.rs +++ b/kb-config/src/lib.rs @@ -1,23 +1,70 @@ // file: kb-config/src/lib.rs -// version: 1 +// version: 3 -#![forbid(unsafe_code)] -#![deny(unreachable_pub)] +//! Khadhroony Bot3 workspace configuration contract and loading helpers. #![warn(missing_docs)] +#![deny(unreachable_pub)] +#![forbid(unsafe_code)] -//! Configuration contracts for Khadhroony Bot3. +mod settings; -/// Minimal application configuration used during the migration. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AppConfig { - /// Active profile name. - pub active_profile: std::string::String, -} - -impl std::default::Default for AppConfig { - fn default() -> Self { - return Self { - active_profile: std::string::String::from("development"), - }; - } -} +/// Exposes the account listener configuration type. +pub use self::settings::AccountListenerConfig; +/// Exposes the root application configuration type. +pub use self::settings::AppConfig; +/// Exposes the application section configuration type. +pub use self::settings::AppSectionConfig; +/// Exposes the local data configuration type. +pub use self::settings::DataConfig; +/// Exposes the database configuration type. +pub use self::settings::DatabaseConfig; +/// Exposes the demo application configuration type. +pub use self::settings::DemoConfig; +/// Exposes the endpoint role configuration type. +pub use self::settings::EndpointRoleConfig; +/// Exposes the execution configuration type. +pub use self::settings::ExecutionConfig; +/// Exposes the HTTP endpoint configuration type. +pub use self::settings::HttpEndpointConfig; +/// Exposes the listener configuration type. +pub use self::settings::ListenerConfig; +/// Exposes the log listener configuration type. +pub use self::settings::LogListenerConfig; +/// Exposes the logging target configuration type. +pub use self::settings::LogTargetConfig; +/// Exposes the logging target filter configuration type. +pub use self::settings::LogTargetFilterConfig; +/// Exposes the logging configuration type. +pub use self::settings::LoggingConfig; +/// 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. +pub use self::settings::WsEndpointConfig; +/// Exposes the active profile resolver. +pub use self::settings::active_profile; +/// Exposes the embedded JSON Schema text. +pub use self::settings::config_json_schema_text; +/// Exposes the embedded JSON Schema value parser. +pub use self::settings::config_json_schema_value; +/// Exposes the configuration parser from a JSON string. +pub use self::settings::parse_config_json; +/// Exposes the configuration loader from a filesystem path. +pub use self::settings::read_config_json_file; +/// Exposes the compact JSON serializer for configuration values. +pub use self::settings::serialize_config_json; +/// Exposes the pretty JSON serializer for configuration values. +pub use self::settings::serialize_config_json_pretty; +/// Exposes the typed configuration validator. +pub use self::settings::validate_config; +/// Exposes the JSON Schema validator for raw JSON configuration. +pub use self::settings::validate_config_json_schema; diff --git a/kb-config/src/settings.rs b/kb-config/src/settings.rs new file mode 100644 index 0000000..cdec64e --- /dev/null +++ b/kb-config/src/settings.rs @@ -0,0 +1,1530 @@ +// file: kb-config/src/settings.rs +// version: 14 + +//! Typed configuration models shared by applications and workers. + +use ts_rs::TS; // rust-rules: derive-import + +const CONFIG_JSON_SCHEMA: &str = include_str!("../../config/schema.config.json"); + +/// Root configuration containing every named profile. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/AppConfig.ts")] +pub struct AppConfig { + /// Active profile name. + pub active_profile: std::string::String, + /// Named profiles available in this configuration file. + pub profiles: std::vec::Vec, +} + +/// Configuration profile selected by the root active profile name. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/ProfileConfig.ts" +)] +pub struct ProfileConfig { + /// Profile code. + pub name: std::string::String, + /// Application metadata. + pub app: AppSectionConfig, + /// Logging configuration. + pub logging: LoggingConfig, + /// Database configuration. + pub database: DatabaseConfig, + /// Local data directories. + pub data: DataConfig, + /// Solana endpoint and listener configuration. + pub solana: SolanaConfig, + /// Wallet configuration. + pub wallet: WalletConfig, + /// Execution safety configuration. + pub execution: ExecutionConfig, + /// Demo application configuration. + pub demo: DemoConfig, +} + +/// Application metadata for a profile. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/AppSectionConfig.ts" +)] +pub struct AppSectionConfig { + /// Application name. + pub name: std::string::String, + /// Environment name. + pub environment: std::string::String, + /// Default auto reconnect flag used when a transport does not override it. + pub auto_reconnect_default: bool, +} + +/// Logging configuration shared by apps and worker processes. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/LoggingConfig.ts" +)] +pub struct LoggingConfig { + /// Default log level. + pub default_level: std::string::String, + /// Output targets. + pub targets: std::vec::Vec, + /// Target-specific filters. + pub target_filters: std::vec::Vec, +} + +/// Single logging output target. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/LogTargetConfig.ts" +)] +pub struct LogTargetConfig { + /// Output target name. + pub name: std::string::String, + /// Enables this target. + pub enabled: bool, + /// Sink kind, such as console or file. + pub sink: std::string::String, + /// Minimum level for this target. + pub level: std::string::String, + /// File path for file sinks or an empty string for console sinks. + pub path: std::string::String, + /// Rotation mode for file sinks. + pub rotation: std::string::String, + /// Message format, such as human or json. + pub format: std::string::String, + /// Enables ANSI escape sequences for this target. + pub ansi: bool, + /// Included tracing targets or globs. + pub targets: std::vec::Vec, +} + +/// Per-target logging filter. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/LogTargetFilterConfig.ts" +)] +pub struct LogTargetFilterConfig { + /// Tracing target or crate prefix. + pub target: std::string::String, + /// Level assigned to the target. + pub level: std::string::String, +} + +/// Database backend configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/DatabaseConfig.ts" +)] +pub struct DatabaseConfig { + /// Enables database storage. + pub enabled: bool, + /// Selected backend code. + pub backend: std::string::String, + /// PostgreSQL configuration. + pub postgres: PostgresConfig, + /// SQLite configuration for tests or imports. + pub sqlite: SqliteConfig, +} + +/// PostgreSQL backend configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/PostgresConfig.ts" +)] +pub struct PostgresConfig { + /// PostgreSQL URL or local placeholder. + pub url: std::string::String, + /// Maximum connection count. + pub max_connections: u32, + /// Connection timeout in milliseconds. + #[ts(type = "number")] + pub connect_timeout_ms: u64, + /// Enables schema initialization at startup. + pub auto_initialize_schema: bool, +} + +/// SQLite backend configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/SqliteConfig.ts")] +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. + #[ts(type = "number")] + 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, +} + +/// Local data directory configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/DataConfig.ts")] +pub struct DataConfig { + /// Wallet directory path. + pub wallets_directory: std::string::String, + /// Logs directory path. + pub logs_directory: std::string::String, +} + +/// Solana endpoints and listener configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/SolanaConfig.ts")] +pub struct SolanaConfig { + /// HTTP JSON-RPC endpoints. + pub http_endpoints: std::vec::Vec, + /// Standard Solana WebSocket endpoints. + pub ws_endpoints: std::vec::Vec, + /// Listener declarations. + pub listeners: ListenerConfig, +} + +/// HTTP JSON-RPC endpoint configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/HttpEndpointConfig.ts" +)] +pub struct HttpEndpointConfig { + /// Endpoint name. + pub name: std::string::String, + /// Enables this endpoint. + pub enabled: bool, + /// Provider code. + pub provider: std::string::String, + /// Cluster code. + pub cluster: std::string::String, + /// Full endpoint URL. + pub url: std::string::String, + /// Connection timeout in milliseconds. + #[ts(type = "number")] + pub connect_timeout_ms: u64, + /// Request timeout in milliseconds. + #[ts(type = "number")] + pub request_timeout_ms: u64, + /// Maximum idle connections per host. + pub max_idle_connections_per_host: u32, + /// Role-specific limits for this URL. + pub roles: std::vec::Vec, +} + +/// Standard Solana WebSocket endpoint configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/WsEndpointConfig.ts" +)] +pub struct WsEndpointConfig { + /// Endpoint name. + pub name: std::string::String, + /// Enables this endpoint. + pub enabled: bool, + /// Provider code. + pub provider: std::string::String, + /// Cluster code. + pub cluster: std::string::String, + /// Full endpoint URL. + pub url: std::string::String, + /// Connection timeout in milliseconds. + #[ts(type = "number")] + pub connect_timeout_ms: u64, + /// Request timeout in milliseconds. + #[ts(type = "number")] + pub request_timeout_ms: u64, + /// Unsubscribe timeout in milliseconds. + #[ts(type = "number")] + pub unsubscribe_timeout_ms: u64, + /// Writer channel capacity. + pub write_channel_capacity: u32, + /// Event channel capacity. + pub event_channel_capacity: u32, + /// Enables automatic reconnect for this endpoint. + pub auto_reconnect: bool, + /// Role-specific limits for this URL. + pub roles: std::vec::Vec, +} + +/// Role-specific endpoint limits. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/EndpointRoleConfig.ts" +)] +pub struct EndpointRoleConfig { + /// Role code used by endpoint pools. + pub role: std::string::String, + /// Enables this role on the endpoint. + pub enabled: bool, + /// Request or subscription kinds handled by this role. + pub request_kinds: std::vec::Vec, + /// Role priority where lower values are preferred. + pub priority: u32, + /// Requests per second allowed for this role on this URL. + pub requests_per_second: u32, + /// Burst capacity allowed for this role on this URL. + pub burst_capacity: u32, + /// Maximum concurrent requests allowed for this role on this URL. + pub max_concurrent_requests: u32, + /// Maximum subscriptions allowed for this role on this URL. + pub max_subscriptions: u32, + /// Pause after a rate limit response in milliseconds. + #[ts(type = "number")] + pub pause_after_rate_limit_ms: u64, +} + +/// Listener configuration used by standard WebSocket subscriptions. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/ListenerConfig.ts" +)] +pub struct ListenerConfig { + /// Enables listener creation. + pub enabled: bool, + /// Default commitment used by listener subscriptions. + pub default_commitment: std::string::String, + /// Log listeners filtered by program mentions. + pub log_listeners: std::vec::Vec, + /// Program account listeners. + pub program_listeners: std::vec::Vec, + /// Account listeners. + pub account_listeners: std::vec::Vec, +} + +/// Log listener filtered by a program identifier mention. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/LogListenerConfig.ts" +)] +pub struct LogListenerConfig { + /// Listener name. + pub name: std::string::String, + /// Enables this listener. + pub enabled: bool, + /// Required endpoint role. + pub endpoint_role: std::string::String, + /// Program identifier used as logsSubscribe mention. + pub program_id: std::string::String, + /// Human-readable listener purpose. + pub purpose: std::string::String, +} + +/// Program account listener. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/ProgramListenerConfig.ts" +)] +pub struct ProgramListenerConfig { + /// Listener name. + pub name: std::string::String, + /// Enables this listener. + pub enabled: bool, + /// Required endpoint role. + pub endpoint_role: std::string::String, + /// Program identifier used by programSubscribe. + pub program_id: std::string::String, + /// Human-readable listener purpose. + pub purpose: std::string::String, +} + +/// Account listener. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/AccountListenerConfig.ts" +)] +pub struct AccountListenerConfig { + /// Listener name. + pub name: std::string::String, + /// Enables this listener. + pub enabled: bool, + /// Required endpoint role. + pub endpoint_role: std::string::String, + /// Account public key used by accountSubscribe. + pub account_pubkey: std::string::String, + /// Human-readable listener purpose. + pub purpose: std::string::String, +} + +/// Wallet configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/WalletConfig.ts")] +pub struct WalletConfig { + /// Wallet directory path. + pub wallet_dir: std::string::String, + /// Selected cluster name. + pub cluster: std::string::String, + /// Enables the managed temporary wallet for this profile. + pub temporary_wallet_enabled: bool, + /// Alias used for the managed temporary wallet. + pub temporary_wallet_alias: std::string::String, + /// Persists the managed temporary wallet between process restarts. + pub temporary_wallet_persist: bool, + /// Enables local validator transaction sending. + pub localnet_send_enabled: bool, + /// Enables devnet transaction sending. + pub devnet_send_enabled: bool, + /// Enables testnet transaction sending. + pub testnet_send_enabled: bool, + /// Enables mainnet transaction sending. + pub mainnet_send_enabled: bool, +} + +/// Execution safety configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_config/settings/ExecutionConfig.ts" +)] +pub struct ExecutionConfig { + /// Enables dry-run for newly prepared execution policies. + pub dry_run_default: bool, + /// Requires simulation before send. + pub require_simulation: bool, + /// Requires explicit operator confirmation before send. + pub require_operator_confirmation: bool, + /// Maximum spend in lamports for local validator tests. + #[ts(type = "number")] + pub localnet_max_spend_lamports: u64, + /// Maximum spend in lamports for devnet tests. + #[ts(type = "number")] + pub devnet_max_spend_lamports: u64, + /// Maximum spend in lamports for testnet tests. + #[ts(type = "number")] + pub testnet_max_spend_lamports: u64, + /// Maximum spend in lamports for mainnet operations. + #[ts(type = "number")] + pub mainnet_max_spend_lamports: u64, + /// Maximum estimated transaction fee in lamports. + #[ts(type = "number")] + pub max_fee_lamports: u64, + /// Maximum compute-unit price in micro-lamports. + #[ts(type = "number")] + pub max_compute_unit_price_micro_lamports: u64, + /// Maximum accepted age for a recent blockhash in slots. + #[ts(type = "number")] + pub recent_blockhash_max_age_slots: u64, + /// Maximum node retransmission retries requested by `sendTransaction`. + pub send_max_retries: u32, + /// Delay between transaction confirmation polls. + #[ts(type = "number")] + pub confirmation_poll_interval_ms: u64, + /// Maximum number of transaction confirmation polls. + pub confirmation_max_attempts: u32, + /// Maximum devnet faucet airdrop allowed for a temporary wallet. + #[ts(type = "number")] + pub devnet_airdrop_max_lamports: u64, +} + +/// Demo application configuration. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] +#[ts(export, export_to = "../frontend/ts/bindings/kb_config/settings/DemoConfig.ts")] +pub struct DemoConfig { + /// Enables live demo pages. + pub live_demo_enabled: bool, + /// Enables trading demo pages. + pub trading_demo_enabled: bool, +} + +/// Returns the embedded JSON Schema text used for configuration validation. +pub fn config_json_schema_text() -> &'static str { + return CONFIG_JSON_SCHEMA; +} + +/// Parses the embedded JSON Schema into a JSON value. +pub fn config_json_schema_value() -> kb_core::Result { + let schema_result = serde_json::from_str::(CONFIG_JSON_SCHEMA); + return match schema_result { + std::result::Result::Ok(schema) => std::result::Result::Ok(schema), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "config_schema_parse_failed", + error.to_string(), + )), + }; +} + +/// Validates a raw JSON configuration string against the embedded JSON Schema. +pub fn validate_config_json_schema(raw_json: &str) -> kb_core::Result<()> { + let schema = match config_json_schema_value() { + std::result::Result::Ok(schema) => schema, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instance = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(instance) => instance, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "config_json_parse_failed", + error.to_string(), + )); + }, + }; + let validator = match jsonschema::validator_for(&schema) { + std::result::Result::Ok(validator) => validator, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "config_schema_compile_failed", + error.to_string(), + )); + }, + }; + let validation_result = validator.validate(&instance); + return match validation_result { + std::result::Result::Ok(()) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "config_json_schema_validation_failed", + error.to_string(), + )), + }; +} + +/// Parses the application configuration from a JSON string and validates it. +pub fn parse_config_json(raw_json: &str) -> kb_core::Result { + match 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::(raw_json) { + std::result::Result::Ok(config) => config, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "config_json_decode_failed", + error.to_string(), + )); + }, + }; + return match 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 the application configuration from a filesystem path. +pub fn read_config_json_file(path: &std::path::Path) -> kb_core::Result { + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "config_file_read_failed", + error.to_string(), + )); + }, + }; + return parse_config_json(&raw_json); +} + +/// Serializes a configuration value to compact JSON. +pub fn serialize_config_json(config: &AppConfig) -> kb_core::Result { + match validate_config(config) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + return match serde_json::to_string(config) { + std::result::Result::Ok(serialized) => std::result::Result::Ok(serialized), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "config_json_serialize_failed", + error.to_string(), + )), + }; +} + +/// Serializes a configuration value to pretty JSON. +pub fn serialize_config_json_pretty(config: &AppConfig) -> kb_core::Result { + match validate_config(config) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + return match serde_json::to_string_pretty(config) { + std::result::Result::Ok(serialized) => std::result::Result::Ok(serialized), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "config_json_pretty_serialize_failed", + error.to_string(), + )), + }; +} + +/// Returns the active profile declared by the root configuration. +pub fn active_profile(config: &AppConfig) -> kb_core::Result<&ProfileConfig> { + for profile in &config.profiles { + if profile.name == config.active_profile { + return std::result::Result::Ok(profile); + } + } + return std::result::Result::Err(kb_core::Error::new( + "active_profile_not_found", + config.active_profile.clone(), + )); +} + +/// Validates a typed configuration after JSON Schema validation and deserialization. +pub fn validate_config(config: &AppConfig) -> kb_core::Result<()> { + match validate_root_config(config) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + for profile in &config.profiles { + match validate_profile(profile) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return match 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) -> kb_core::Result<()> { + match require_non_empty(&config.active_profile, "active_profile") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.profiles.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "config_profiles_empty", + "at least one profile is required", + )); + } + let mut names = std::collections::BTreeSet::::new(); + let mut active_count = 0_u32; + for profile in &config.profiles { + match require_non_empty(&profile.name, "profile.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if !names.insert(profile.name.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "profile_name_duplicate", + profile.name.clone(), + )); + } + if profile.name == config.active_profile { + active_count += 1; + } + } + if active_count != 1 { + return std::result::Result::Err(kb_core::Error::new( + "active_profile_count_invalid", + format!("active profile '{}' must match exactly one profile", config.active_profile), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_profile(profile: &ProfileConfig) -> kb_core::Result<()> { + match validate_app_section(&profile.app) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_logging(&profile.logging) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_database(&profile.database) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_data(&profile.data) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_solana(&profile.solana) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_wallet(&profile.wallet) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match 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); +} + +fn validate_app_section(config: &AppSectionConfig) -> kb_core::Result<()> { + match require_non_empty(&config.name, "app.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + return require_non_empty(&config.environment, "app.environment"); +} + +fn validate_logging(config: &LoggingConfig) -> kb_core::Result<()> { + match validate_log_level(&config.default_level, "logging.default_level") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.targets.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "logging_targets_empty", + "at least one logging target is required", + )); + } + let mut enabled_count = 0_u32; + let mut names = std::collections::BTreeSet::::new(); + for target in &config.targets { + if target.enabled { + enabled_count += 1; + } + if !names.insert(target.name.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "logging_target_duplicate", + target.name.clone(), + )); + } + match validate_log_target(target) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + for filter in &config.target_filters { + match require_non_empty(&filter.target, "logging.target_filters.target") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_log_level(&filter.level, "logging.target_filters.level") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if enabled_count == 0 { + return std::result::Result::Err(kb_core::Error::new( + "logging_no_enabled_targets", + "at least one logging target must be enabled", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_log_target(config: &LogTargetConfig) -> kb_core::Result<()> { + match require_non_empty(&config.name, "logging.targets.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_log_level(&config.level, "logging.targets.level") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.sink != "console" && config.sink != "file" { + return std::result::Result::Err(kb_core::Error::new( + "logging_sink_invalid", + config.sink.clone(), + )); + } + if config.rotation != "none" + && config.rotation != "never" + && config.rotation != "daily" + && config.rotation != "hourly" + { + return std::result::Result::Err(kb_core::Error::new( + "logging_rotation_invalid", + config.rotation.clone(), + )); + } + if config.format != "human" + && config.format != "compact" + && config.format != "pretty" + && config.format != "json" + { + return std::result::Result::Err(kb_core::Error::new( + "logging_format_invalid", + config.format.clone(), + )); + } + if config.sink == "file" && config.path.trim().is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "logging_file_path_empty", + config.name.clone(), + )); + } + if config.sink == "console" && !config.path.trim().is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "logging_console_path_not_empty", + config.name.clone(), + )); + } + for target in &config.targets { + match require_non_empty(target, "logging.targets.targets") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_database(config: &DatabaseConfig) -> kb_core::Result<()> { + if config.backend != "postgres" && config.backend != "sqlite" { + return std::result::Result::Err(kb_core::Error::new( + "database_backend_invalid", + config.backend.clone(), + )); + } + match require_non_empty(&config.postgres.url, "database.postgres.url") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.postgres.max_connections == 0 || config.sqlite.max_connections == 0 { + return std::result::Result::Err(kb_core::Error::new( + "database_max_connections_zero", + "database connection limits must be greater than zero", + )); + } + if config.postgres.connect_timeout_ms == 0 || config.sqlite.busy_timeout_ms == 0 { + return std::result::Result::Err(kb_core::Error::new( + "database_timeout_zero", + "database timeouts must be greater than zero", + )); + } + return require_non_empty(&config.sqlite.path, "database.sqlite.path"); +} + +fn validate_data(config: &DataConfig) -> kb_core::Result<()> { + match require_non_empty(&config.wallets_directory, "data.wallets_directory") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + return require_non_empty(&config.logs_directory, "data.logs_directory"); +} + +fn validate_solana(config: &SolanaConfig) -> kb_core::Result<()> { + if config.http_endpoints.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "http_endpoints_empty", + "at least one HTTP JSON-RPC endpoint is required", + )); + } + if config.ws_endpoints.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "ws_endpoints_empty", + "at least one standard Solana WebSocket endpoint is required", + )); + } + let mut http_names = std::collections::BTreeSet::::new(); + for endpoint in &config.http_endpoints { + if !http_names.insert(endpoint.name.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "http_endpoint_duplicate", + endpoint.name.clone(), + )); + } + match validate_http_endpoint(endpoint) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let mut ws_names = std::collections::BTreeSet::::new(); + for endpoint in &config.ws_endpoints { + if !ws_names.insert(endpoint.name.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "ws_endpoint_duplicate", + endpoint.name.clone(), + )); + } + match validate_ws_endpoint(endpoint) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return validate_listeners(&config.listeners); +} + +fn validate_http_endpoint(config: &HttpEndpointConfig) -> kb_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), + } + if !config.url.starts_with("http://") && !config.url.starts_with("https://") { + return std::result::Result::Err(kb_core::Error::new( + "http_endpoint_url_invalid", + config.url.clone(), + )); + } + if config.connect_timeout_ms == 0 + || config.request_timeout_ms == 0 + || config.max_idle_connections_per_host == 0 + { + return std::result::Result::Err(kb_core::Error::new( + "http_endpoint_limit_zero", + config.name.clone(), + )); + } + for role in &config.roles { + if role.max_subscriptions != 0 { + return std::result::Result::Err(kb_core::Error::new( + "http_endpoint_subscriptions_invalid", + role.role.clone(), + )); + } + } + return std::result::Result::Ok(()); +} + +fn validate_ws_endpoint(config: &WsEndpointConfig) -> kb_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), + } + if !config.url.starts_with("ws://") && !config.url.starts_with("wss://") { + return std::result::Result::Err(kb_core::Error::new( + "ws_endpoint_url_invalid", + config.url.clone(), + )); + } + if config.connect_timeout_ms == 0 + || config.request_timeout_ms == 0 + || config.unsubscribe_timeout_ms == 0 + || config.write_channel_capacity == 0 + || config.event_channel_capacity == 0 + { + return std::result::Result::Err(kb_core::Error::new( + "ws_endpoint_limit_zero", + config.name.clone(), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_endpoint_common( + name: &str, + provider: &str, + cluster: &str, + roles: &[EndpointRoleConfig], +) -> kb_core::Result<()> { + match 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") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match require_non_empty(cluster, "endpoint.cluster") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if roles.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "endpoint_roles_empty", + name.to_string(), + )); + } + let mut role_names = std::collections::BTreeSet::::new(); + for role in roles { + if !role_names.insert(role.role.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "endpoint_role_duplicate", + role.role.clone(), + )); + } + match validate_endpoint_role(role) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_endpoint_role(config: &EndpointRoleConfig) -> kb_core::Result<()> { + match require_non_empty(&config.role, "endpoint.roles.role") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.request_kinds.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "endpoint_role_request_kinds_empty", + config.role.clone(), + )); + } + for request_kind in &config.request_kinds { + match require_non_empty(request_kind, "endpoint.roles.request_kinds") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if config.requests_per_second == 0 + || config.burst_capacity == 0 + || config.max_concurrent_requests == 0 + || config.pause_after_rate_limit_ms == 0 + { + return std::result::Result::Err(kb_core::Error::new( + "endpoint_role_limit_zero", + config.role.clone(), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_listeners(config: &ListenerConfig) -> kb_core::Result<()> { + match 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), + } + for listener in &config.log_listeners { + match validate_log_listener(listener) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + for listener in &config.program_listeners { + match validate_program_listener(listener) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + for listener in &config.account_listeners { + match validate_account_listener(listener) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_log_listener(config: &LogListenerConfig) -> kb_core::Result<()> { + match 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") { + 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"); +} + +fn validate_program_listener(config: &ProgramListenerConfig) -> kb_core::Result<()> { + match 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") { + 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"); +} + +fn validate_account_listener(config: &AccountListenerConfig) -> kb_core::Result<()> { + match 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") { + 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"); +} + +fn validate_wallet(config: &WalletConfig) -> kb_core::Result<()> { + match 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.cluster, "wallet.cluster") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_wallet_alias(&config.temporary_wallet_alias) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if config.temporary_wallet_persist && !config.temporary_wallet_enabled { + return std::result::Result::Err(kb_core::Error::new( + "wallet_temporary_persist_without_enablement", + "temporary wallet persistence requires temporary_wallet_enabled", + )); + } + if config.temporary_wallet_enabled && config.cluster == "mainnet-beta" { + return std::result::Result::Err(kb_core::Error::new( + "wallet_temporary_mainnet_forbidden", + "the managed temporary wallet is forbidden on mainnet-beta profiles", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_wallet_alias(value: &str) -> kb_core::Result<()> { + if value.is_empty() || value.len() > 64 { + return std::result::Result::Err(kb_core::Error::new( + "wallet_alias_length_invalid", + "temporary wallet alias length must be between 1 and 64 bytes", + )); + } + if !value.bytes().enumerate().all(|(index, byte)| { + return byte.is_ascii_alphanumeric() || (index > 0 && (byte == b'_' || byte == b'-')); + }) { + return std::result::Result::Err(kb_core::Error::new( + "wallet_alias_invalid", + "temporary wallet alias contains invalid characters", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_execution(config: &ExecutionConfig) -> kb_core::Result<()> { + let configured_spend = config + .localnet_max_spend_lamports + .saturating_add(config.devnet_max_spend_lamports) + .saturating_add(config.testnet_max_spend_lamports) + .saturating_add(config.mainnet_max_spend_lamports); + if configured_spend > 0 && !config.require_simulation { + return std::result::Result::Err(kb_core::Error::new( + "execution_spend_without_simulation", + "any configured spend requires simulation", + )); + } + if config.mainnet_max_spend_lamports > 0 && !config.require_operator_confirmation { + return std::result::Result::Err(kb_core::Error::new( + "execution_mainnet_without_confirmation", + "mainnet spend requires operator confirmation", + )); + } + if config.max_fee_lamports == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_fee_limit_zero", + "max_fee_lamports must be greater than zero", + )); + } + if config.recent_blockhash_max_age_slots == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_blockhash_age_zero", + "recent_blockhash_max_age_slots must be greater than zero", + )); + } + if config.confirmation_poll_interval_ms == 0 || config.confirmation_poll_interval_ms > 60_000 { + return std::result::Result::Err(kb_core::Error::new( + "execution_confirmation_poll_interval_invalid", + "confirmation_poll_interval_ms must be between 1 and 60000", + )); + } + if config.confirmation_max_attempts == 0 || config.confirmation_max_attempts > 10_000 { + return std::result::Result::Err(kb_core::Error::new( + "execution_confirmation_attempts_invalid", + "confirmation_max_attempts must be between 1 and 10000", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_wallet_execution_pair( + wallet: &WalletConfig, + execution: &ExecutionConfig, +) -> kb_core::Result<()> { + for (enabled, limit, cluster) in [ + (wallet.localnet_send_enabled, execution.localnet_max_spend_lamports, "localnet"), + (wallet.devnet_send_enabled, execution.devnet_max_spend_lamports, "devnet"), + (wallet.testnet_send_enabled, execution.testnet_max_spend_lamports, "testnet"), + ( + wallet.mainnet_send_enabled, + execution.mainnet_max_spend_lamports, + "mainnet-beta", + ), + ] { + if enabled && limit == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_enabled_cluster_without_spend_limit", + format!("{cluster} sending requires a positive spend limit"), + )); + } + } + if wallet.mainnet_send_enabled && !execution.require_operator_confirmation { + return std::result::Result::Err(kb_core::Error::new( + "execution_mainnet_send_without_confirmation", + "mainnet sending requires operator confirmation", + )); + } + if wallet.cluster != "devnet" && execution.devnet_airdrop_max_lamports > 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_devnet_airdrop_on_non_devnet_profile", + "devnet_airdrop_max_lamports must be zero outside devnet profiles", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_log_level(value: &str, field_name: &str) -> kb_core::Result<()> { + if value == "trace" + || value == "debug" + || value == "info" + || value == "warn" + || value == "error" + || value == "off" + { + return std::result::Result::Ok(()); + } + return std::result::Result::Err(kb_core::Error::new( + "log_level_invalid", + format!("{field_name}: {value}"), + )); +} + +fn require_non_empty(value: &str, field_name: &str) -> kb_core::Result<()> { + if value.trim().is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "config_field_empty", + field_name.to_string(), + )); + } + return std::result::Result::Ok(()); +} + +#[cfg(test)] +mod tests { + use ts_rs::TS; // rust-rules: derive-import + + #[test] + fn exported_json_configuration_numbers_do_not_use_bigint() { + let config = ts_rs::Config::default(); + let declarations = [ + ::decl(&config), + ::decl(&config), + ::decl(&config), + ::decl(&config), + ::decl(&config), + ::decl(&config), + ]; + for declaration in declarations { + assert!(!declaration.contains("bigint")); + } + } + + const EXAMPLE_CONFIG: &str = include_str!("../../config/example.config.json"); + + fn parse_example_value() -> serde_json::Value { + let result = serde_json::from_str::(EXAMPLE_CONFIG); + match result { + std::result::Result::Ok(value) => return value, + std::result::Result::Err(error) => panic!("example config must be valid JSON: {error}"), + } + } + + fn value_to_json(value: &serde_json::Value) -> std::string::String { + let result = serde_json::to_string(value); + match result { + std::result::Result::Ok(raw_json) => return raw_json, + std::result::Result::Err(error) => panic!("test value must serialize: {error}"), + } + } + + fn tracing_crate_names() -> std::vec::Vec { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = match manifest_dir.parent() { + std::option::Option::Some(path) => path, + std::option::Option::None => panic!("workspace root must exist"), + }; + let read_result = std::fs::read_dir(workspace_root); + let entries = match read_result { + std::result::Result::Ok(entries) => entries, + std::result::Result::Err(error) => panic!("workspace must be readable: {error}"), + }; + let mut names = std::vec::Vec::::new(); + for entry_result in entries { + let entry = match entry_result { + std::result::Result::Ok(entry) => entry, + std::result::Result::Err(error) => { + panic!("workspace entry must be readable: {error}") + }, + }; + let cargo_path = entry.path().join("Cargo.toml"); + if !cargo_path.is_file() { + continue; + } + let cargo_result = std::fs::read_to_string(&cargo_path); + let cargo_toml = match cargo_result { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => panic!("Cargo.toml must be readable: {error}"), + }; + if !cargo_toml.lines().any(|line| return line.trim() == "tracing.workspace = true") { + continue; + } + let mut in_package = false; + for line in cargo_toml.lines() { + let trimmed = line.trim(); + if trimmed == "[package]" { + in_package = true; + continue; + } + if in_package && trimmed.starts_with('[') { + break; + } + if in_package && trimmed.starts_with("name = ") { + names.push(trimmed.trim_start_matches("name = ").trim_matches('"').to_string()); + break; + } + } + } + names.sort(); + return names; + } + + #[test] + fn schema_text_is_valid_json() { + let result = super::config_json_schema_value(); + assert!(result.is_ok()); + } + + #[test] + fn example_config_validates_against_schema() { + let result = super::validate_config_json_schema(EXAMPLE_CONFIG); + assert!(result.is_ok()); + } + + #[test] + fn example_config_routes_global_and_operational_crate_files() { + let config_result = super::parse_config_json(EXAMPLE_CONFIG); + let config = match config_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config must parse: {error}"), + }; + let operational_crates = tracing_crate_names(); + assert!(!operational_crates.is_empty()); + for profile in config.profiles { + let directory = match profile.name.as_str() { + "local_devnet" => "devnet", + "mainnet_research" => "mainnet_research", + "mainnet" => "mainnet", + value => panic!("unexpected example profile: {value}"), + }; + for (suffix, level, format) in [ + ("debug.log", "debug", "human"), + ("info.log", "info", "human"), + ("error.jsonl", "error", "json"), + ] { + let expected_path = format!("logs/{directory}/{suffix}"); + assert!(profile.logging.targets.iter().any(|target| { + return target.enabled + && target.path == expected_path + && target.level == level + && target.format == format + && target.targets == std::vec!["*".to_string()]; + })); + } + assert!(profile.logging.targets.iter().any(|target| { + return target.enabled + && target.path == format!("logs/{directory}/app.log") + && target.level == "debug" + && target.format == "human" + && target.targets == std::vec!["kb_app_demo".to_string()]; + })); + for crate_name in &operational_crates { + for (suffix, level, format) in [ + ("debug.log", "debug", "human"), + ("info.log", "info", "human"), + ("error.jsonl", "error", "json"), + ] { + let expected_path = format!("logs/{directory}/{crate_name}/{suffix}"); + let route_exists = profile.logging.targets.iter().any(|target| { + return target.enabled + && target.path == expected_path + && target.level == level + && target.format == format + && target.targets == std::vec![crate_name.clone()]; + }); + assert!( + route_exists, + "profile {} is missing the canonical {} route for {} at {}", + profile.name, level, crate_name, expected_path + ); + } + } + } + } + + #[test] + fn example_config_parses_and_resolves_active_profile() { + let config_result = super::parse_config_json(EXAMPLE_CONFIG); + assert!(config_result.is_ok()); + let config = match config_result { + std::result::Result::Ok(config) => config, + std::result::Result::Err(error) => panic!("example config must parse: {error}"), + }; + let active_result = super::active_profile(&config); + assert!(active_result.is_ok()); + //let active = + match active_result { + std::result::Result::Ok(active) => active, + std::result::Result::Err(error) => panic!("active profile must resolve: {error}"), + }; + //assert_eq!(active.name, "local_devnet"); + } + + #[test] + fn example_config_serializes_and_roundtrips() { + let config_result = super::parse_config_json(EXAMPLE_CONFIG); + assert!(config_result.is_ok()); + let config = match config_result { + std::result::Result::Ok(config) => config, + std::result::Result::Err(error) => panic!("example config must parse: {error}"), + }; + let serialized_result = super::serialize_config_json_pretty(&config); + assert!(serialized_result.is_ok()); + let serialized = match serialized_result { + std::result::Result::Ok(serialized) => serialized, + std::result::Result::Err(error) => panic!("example config must serialize: {error}"), + }; + let reparsed_result = super::parse_config_json(&serialized); + assert!(reparsed_result.is_ok()); + let reparsed = match reparsed_result { + std::result::Result::Ok(reparsed) => reparsed, + std::result::Result::Err(error) => panic!("serialized config must parse: {error}"), + }; + assert_eq!(config, reparsed); + } + + #[test] + fn parser_rejects_missing_active_profile() { + let mut value = parse_example_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); + assert!(result.is_err()); + } + + #[test] + fn schema_rejects_profile_enabled_field() { + let mut value = parse_example_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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_duplicate_profile_names() { + let mut value = parse_example_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); + assert!(result.is_err()); + } + + #[test] + fn schema_rejects_unknown_endpoint_secret_field() { + let mut value = parse_example_value(); + value["profiles"][0]["solana"]["http_endpoints"][0]["secret_env_field"] = + serde_json::Value::String("HELIUS_API_KEY".to_string()); + let raw_json = value_to_json(&value); + let result = super::validate_config_json_schema(&raw_json); + assert!(result.is_err()); + } + + #[test] + fn schema_rejects_runtime_idls_directory_field() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + + #[test] + fn schema_rejects_unsupported_transport_surface() { + let mut value = parse_example_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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_invalid_http_endpoint_url() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_invalid_ws_endpoint_url() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_file_logging_target_without_path() { + let mut value = parse_example_value(); + value["profiles"][0]["logging"]["targets"][1]["path"] = + serde_json::Value::String("".to_string()); + let raw_json = value_to_json(&value); + let result = super::parse_config_json(&raw_json); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_invalid_temporary_wallet_alias() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_persistent_disabled_temporary_wallet() { + let mut value = parse_example_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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_enabled_cluster_without_spend_limit() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + + #[test] + fn parser_rejects_zero_fee_or_blockhash_limits() { + for field in [ + "max_fee_lamports", + "recent_blockhash_max_age_slots", + "confirmation_poll_interval_ms", + "confirmation_max_attempts", + ] { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } + } + + #[test] + fn parser_rejects_http_role_with_subscriptions() { + let mut value = parse_example_value(); + 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); + assert!(result.is_err()); + } +} diff --git a/kb-lib/src/decoder.rs b/kb-lib/src/decoder.rs index 42dfcbb..8d262aa 100644 --- a/kb-lib/src/decoder.rs +++ b/kb-lib/src/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder.rs -// version: 8 +// version: 9 //! Consolidated decoder modules. @@ -466,8 +466,6 @@ pub(crate) use self::metadata::DC_METADATA_MTM_SIGN_METADATA_DISCRIMINATOR; pub(crate) use self::metadata::DC_METADATA_MTM_SURFACE_CODE; /// Thaw Delegated Account discriminator. pub(crate) use self::metadata::DC_METADATA_MTM_THAW_DELEGATED_ACCOUNT_DISCRIMINATOR; -/// Canonical tracing target for this crate. -pub(crate) use self::metadata::DC_METADATA_MTM_TRACING_TARGET; /// Transfer discriminator. pub(crate) use self::metadata::DC_METADATA_MTM_TRANSFER_DISCRIMINATOR; /// Transfer Out Of Escrow discriminator. @@ -498,6 +496,8 @@ pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_COLLECTION_DISCRIMINATOR; pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_DISCRIMINATOR; /// Verify Sized Collection Item discriminator. pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR; +/// Canonical tracing target for this crate. +pub(crate) use self::metadata::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA; /// Crate-root access to `decode` from `instruction`. pub(crate) use self::metadata::decoder_metadata_metaplex_token_metadata_decode; /// Crate-root access to `entry_for_discriminator` from `instruction`. @@ -528,7 +528,6 @@ pub(crate) use self::solana::DC_SOLANA_CORE_SIGNATURE_BYTES; pub(crate) use self::solana::DC_SOLANA_CORE_SLASHING_SURFACE_CODE; pub(crate) use self::solana::DC_SOLANA_CORE_STAKE_SURFACE_CODE; pub(crate) use self::solana::DC_SOLANA_CORE_SYSTEM_SURFACE_CODE; -pub(crate) use self::solana::DC_SOLANA_CORE_TRACING_TARGET; pub(crate) use self::solana::DC_SOLANA_CORE_U8_OFFSETS_BYTES; pub(crate) use self::solana::DC_SOLANA_CORE_U16_OFFSETS_BYTES; pub(crate) use self::solana::DC_SOLANA_CORE_VOTE_SURFACE_CODE; @@ -536,6 +535,7 @@ pub(crate) use self::solana::DC_SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE; pub(crate) use self::solana::DC_SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE; pub(crate) use self::solana::DcSolanaCoreAccountRole; pub(crate) use self::solana::DcSolanaCoreResolvedInstructionPayload; +pub(crate) use self::solana::TRACING_TARGET_DECODER_SOLANA_CORE; pub(crate) use self::solana::decoder_solana_core_address_lookup_table_coverage; pub(crate) use self::solana::decoder_solana_core_address_lookup_table_decode; pub(crate) use self::solana::decoder_solana_core_address_lookup_table_recognize; @@ -605,16 +605,12 @@ pub(crate) use self::spl::DC_SPL_ATA_MAX_INSTRUCTION_BYTES; pub(crate) use self::spl::DC_SPL_ATA_MAX_TRANSACTION_KEYS; /// Stable protocol and surface code for the Associated Token Account program. pub(crate) use self::spl::DC_SPL_ATA_SURFACE_CODE; -/// Canonical tracing target for the Associated Token Account decoder. -pub(crate) use self::spl::DC_SPL_ATA_TRACING_TARGET; /// Current stable SPL ElGamal registry event contract version. pub(crate) use self::spl::DC_SPL_ELGAMAL_REGISTRY_EVENT_VERSION; /// Maximum retained SPL ElGamal registry instruction payload size. pub(crate) use self::spl::DC_SPL_ELGAMAL_REGISTRY_MAX_INSTRUCTION_BYTES; /// Stable SPL ElGamal registry surface code. pub(crate) use self::spl::DC_SPL_ELGAMAL_REGISTRY_SURFACE_CODE; -/// Canonical tracing target for the SPL ElGamal registry decoder. -pub(crate) use self::spl::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; /// Maximum payload prefix retained for bounded diagnostics. pub(crate) use self::spl::DC_SPL_MEMO_DIAGNOSTIC_PREFIX_BYTES; /// Current stable Memo decoded event contract version. @@ -623,8 +619,6 @@ pub(crate) use self::spl::DC_SPL_MEMO_EVENT_VERSION; pub(crate) use self::spl::DC_SPL_MEMO_MAX_PAYLOAD_BYTES; /// Stable protocol code shared by Memo generations. pub(crate) use self::spl::DC_SPL_MEMO_PROTOCOL_CODE; -/// Canonical tracing target for the SPL Memo decoder. -pub(crate) use self::spl::DC_SPL_MEMO_TRACING_TARGET; /// Stable Memo v1 surface code. pub(crate) use self::spl::DC_SPL_MEMO_V1_SURFACE_CODE; /// Stable Memo v3 surface code. @@ -645,8 +639,6 @@ pub(crate) use self::spl::DC_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS; pub(crate) use self::spl::DC_SPL_TOKEN_MAX_INSTRUCTION_BYTES; /// Stable protocol and surface code for the classic SPL Token program. pub(crate) use self::spl::DC_SPL_TOKEN_SURFACE_CODE; -/// Canonical tracing target for the classic SPL Token decoder. -pub(crate) use self::spl::DC_SPL_TOKEN_TRACING_TARGET; /// Maximum prefix retained in bounded Token-2022 diagnostics. pub(crate) use self::spl::DC_SPL_TOKEN2022_DIAGNOSTIC_PREFIX_BYTES; /// Current stable Token-2022 decoded event contract version. @@ -663,10 +655,18 @@ pub(crate) use self::spl::DC_SPL_TOKEN2022_MAX_INSTRUCTION_BYTES; pub(crate) use self::spl::DC_SPL_TOKEN2022_MAX_METADATA_STRING_BYTES; /// Stable Token-2022 surface code. pub(crate) use self::spl::DC_SPL_TOKEN2022_SURFACE_CODE; -/// Canonical tracing target for the Token-2022 decoder. -pub(crate) use self::spl::DC_SPL_TOKEN2022_TRACING_TARGET; /// One parsed SPL ElGamal registry instruction. pub(crate) use self::spl::DcSplElGamalRegistryParsedRegistryInstruction; +/// Canonical tracing target for the Associated Token Account decoder. +pub(crate) use self::spl::TRACING_TARGET_DECODER_SPL_ATA; +/// Canonical tracing target for the SPL ElGamal registry decoder. +pub(crate) use self::spl::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY; +/// Canonical tracing target for the SPL Memo decoder. +pub(crate) use self::spl::TRACING_TARGET_DECODER_SPL_MEMO; +/// Canonical tracing target for the classic SPL Token decoder. +pub(crate) use self::spl::TRACING_TARGET_DECODER_SPL_TOKEN; +/// Canonical tracing target for the Token-2022 decoder. +pub(crate) use self::spl::TRACING_TARGET_DECODER_SPL_TOKEN2022; /// Decodes one bounded Associated Token Account instruction. pub(crate) use self::spl::decoder_spl_associated_token_account_decode; /// Returns the exact entry matching an Associated Token Account wire. diff --git a/kb-lib/src/decoder/metadata.rs b/kb-lib/src/decoder/metadata.rs index b6845b0..0086695 100644 --- a/kb-lib/src/decoder/metadata.rs +++ b/kb-lib/src/decoder/metadata.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata.rs -// version: 4 +// version: 5 //! `metadata` decoder family. @@ -179,8 +179,6 @@ pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_SIGN_METADATA_DISC pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_SURFACE_CODE; /// Thaw Delegated Account discriminator. pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_THAW_DELEGATED_ACCOUNT_DISCRIMINATOR; -/// Canonical tracing target for this crate. -pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_TRACING_TARGET; /// Transfer discriminator. pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_TRANSFER_DISCRIMINATOR; /// Transfer Out Of Escrow discriminator. @@ -211,6 +209,8 @@ pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_VERIFY_COLLECTION_ pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_VERIFY_DISCRIMINATOR; /// Verify Sized Collection Item discriminator. pub(crate) use self::metaplex_token_metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR; +/// Canonical tracing target for this crate. +pub(crate) use self::metaplex_token_metadata::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA; /// Crate-root access to `decode` from `instruction`. pub(crate) use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode; /// Crate-root access to `entry_for_discriminator` from `instruction`. diff --git a/kb-lib/src/decoder/metadata/metaplex_token_metadata.rs b/kb-lib/src/decoder/metadata/metaplex_token_metadata.rs index 5f6ba06..5db4e78 100644 --- a/kb-lib/src/decoder/metadata/metaplex_token_metadata.rs +++ b/kb-lib/src/decoder/metadata/metaplex_token_metadata.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata/metaplex_token_metadata.rs -// version: 3 +// version: 4 //! Contextual instruction and bounded account decoder for Metaplex Token Metadata. @@ -180,8 +180,6 @@ pub(crate) use self::constants::DC_METADATA_MTM_SIGN_METADATA_DISCRIMINATOR; pub(crate) use self::constants::DC_METADATA_MTM_SURFACE_CODE; /// Thaw Delegated Account discriminator. pub(crate) use self::constants::DC_METADATA_MTM_THAW_DELEGATED_ACCOUNT_DISCRIMINATOR; -/// Canonical tracing target for this crate. -pub(crate) use self::constants::DC_METADATA_MTM_TRACING_TARGET; /// Transfer discriminator. pub(crate) use self::constants::DC_METADATA_MTM_TRANSFER_DISCRIMINATOR; /// Transfer Out Of Escrow discriminator. @@ -212,6 +210,8 @@ pub(crate) use self::constants::DC_METADATA_MTM_VERIFY_COLLECTION_DISCRIMINATOR; pub(crate) use self::constants::DC_METADATA_MTM_VERIFY_DISCRIMINATOR; /// Verify Sized Collection Item discriminator. pub(crate) use self::constants::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR; +/// Canonical tracing target for this crate. +pub(crate) use self::constants::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA; /// Crate-root access to `decode` from `instruction`. pub(crate) use self::instruction::decoder_metadata_metaplex_token_metadata_decode; /// Crate-root access to `entry_for_discriminator` from `instruction`. diff --git a/kb-lib/src/decoder/metadata/metaplex_token_metadata/constants.rs b/kb-lib/src/decoder/metadata/metaplex_token_metadata/constants.rs index a8bcb59..04dc2a6 100644 --- a/kb-lib/src/decoder/metadata/metaplex_token_metadata/constants.rs +++ b/kb-lib/src/decoder/metadata/metaplex_token_metadata/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata/metaplex_token_metadata/constants.rs -// version: 29 +// version: 30 //! Internal constants for Metaplex Token Metadata decoding. @@ -324,5 +324,5 @@ pub(crate) const DC_METADATA_MTM_INSTRUCTION_ENTRIES: &[(u8, &str, bool)] = &[ ]; /// Canonical tracing target for this decoder. -pub(crate) const DC_METADATA_MTM_TRACING_TARGET: &str = +pub(crate) const TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA: &str = "kb-lib.decoder.metadata.metaplex_token_metadata"; diff --git a/kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs b/kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs index b9080cf..354bedb 100644 --- a/kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs +++ b/kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs -// version: 28 +// version: 29 //! Exact contextual dispatch for Metaplex Token Metadata. @@ -112,7 +112,7 @@ impl crate::DcApiInstructionDecoder for crate::DcMetadataMetaplexTokenMetadataDe | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_METADATA_MTM_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -127,7 +127,7 @@ impl crate::DcApiInstructionDecoder for crate::DcMetadataMetaplexTokenMetadataDe ); } tracing::debug!( - target: crate::DC_METADATA_MTM_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, @@ -498,7 +498,7 @@ mod tests { &[kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID] ); assert_eq!( - crate::DC_METADATA_MTM_TRACING_TARGET, + crate::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA, "kb-lib.decoder.metadata.metaplex_token_metadata" ); assert_eq!(crate::DcApiInstructionDecoder::coverage(&decoder).len(), 58); diff --git a/kb-lib/src/decoder/solana.rs b/kb-lib/src/decoder/solana.rs index 99b6dcc..f09b4e3 100644 --- a/kb-lib/src/decoder/solana.rs +++ b/kb-lib/src/decoder/solana.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/solana.rs -// version: 4 +// version: 5 //! `solana` decoder family. @@ -32,7 +32,6 @@ pub(crate) use self::core::DC_SOLANA_CORE_SIGNATURE_BYTES; pub(crate) use self::core::DC_SOLANA_CORE_SLASHING_SURFACE_CODE; pub(crate) use self::core::DC_SOLANA_CORE_STAKE_SURFACE_CODE; pub(crate) use self::core::DC_SOLANA_CORE_SYSTEM_SURFACE_CODE; -pub(crate) use self::core::DC_SOLANA_CORE_TRACING_TARGET; pub(crate) use self::core::DC_SOLANA_CORE_U8_OFFSETS_BYTES; pub(crate) use self::core::DC_SOLANA_CORE_U16_OFFSETS_BYTES; pub(crate) use self::core::DC_SOLANA_CORE_VOTE_SURFACE_CODE; @@ -40,6 +39,7 @@ pub(crate) use self::core::DC_SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE; pub(crate) use self::core::DC_SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE; pub(crate) use self::core::DcSolanaCoreAccountRole; pub(crate) use self::core::DcSolanaCoreResolvedInstructionPayload; +pub(crate) use self::core::TRACING_TARGET_DECODER_SOLANA_CORE; pub(crate) use self::core::decoder_solana_core_address_lookup_table_coverage; pub(crate) use self::core::decoder_solana_core_address_lookup_table_decode; pub(crate) use self::core::decoder_solana_core_address_lookup_table_recognize; diff --git a/kb-lib/src/decoder/solana/core.rs b/kb-lib/src/decoder/solana/core.rs index 745275a..4b67def 100644 --- a/kb-lib/src/decoder/solana/core.rs +++ b/kb-lib/src/decoder/solana/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/solana/core.rs -// version: 18 +// version: 19 //! Solana Core decoder component. @@ -94,8 +94,6 @@ pub(crate) use self::constants::DC_SOLANA_CORE_SLASHING_SURFACE_CODE; pub(crate) use self::constants::DC_SOLANA_CORE_STAKE_SURFACE_CODE; /// Stable System Program surface code. pub(crate) use self::constants::DC_SOLANA_CORE_SYSTEM_SURFACE_CODE; -/// Canonical tracing target for the Solana Core decoder component. -pub(crate) use self::constants::DC_SOLANA_CORE_TRACING_TARGET; /// Byte length of one secp256k1 offsets entry. pub(crate) use self::constants::DC_SOLANA_CORE_U8_OFFSETS_BYTES; /// Byte length of one Ed25519 or secp256r1 offsets entry. @@ -106,6 +104,8 @@ pub(crate) use self::constants::DC_SOLANA_CORE_VOTE_SURFACE_CODE; pub(crate) use self::constants::DC_SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE; /// Stable historical ZK Token Proof surface code. pub(crate) use self::constants::DC_SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE; +/// Canonical tracing target for the Solana Core decoder component. +pub(crate) use self::constants::TRACING_TARGET_DECODER_SOLANA_CORE; /// Builds one exact decoded native observation. pub(crate) use self::event::decoder_solana_core_decoded_result; /// Builds one failed native decode result. diff --git a/kb-lib/src/decoder/solana/core/constants.rs b/kb-lib/src/decoder/solana/core/constants.rs index 38f0d09..a26e742 100644 --- a/kb-lib/src/decoder/solana/core/constants.rs +++ b/kb-lib/src/decoder/solana/core/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/solana/core/constants.rs -// version: 13 +// version: 14 //! Local constants for the Solana Core decoder component. @@ -67,4 +67,4 @@ pub(crate) const DC_SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES: usize = 4_ pub(crate) const DC_SOLANA_CORE_NATIVE_EVENT_VERSION: u32 = 1; /// Canonical tracing target for the Solana Core decoder component. -pub(crate) const DC_SOLANA_CORE_TRACING_TARGET: &str = "kb-lib.decoder.solana.core"; +pub(crate) const TRACING_TARGET_DECODER_SOLANA_CORE: &str = "kb-lib.decoder.solana.core"; diff --git a/kb-lib/src/decoder/solana/core/decoder.rs b/kb-lib/src/decoder/solana/core/decoder.rs index 2471630..44f57af 100644 --- a/kb-lib/src/decoder/solana/core/decoder.rs +++ b/kb-lib/src/decoder/solana/core/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/solana/core/decoder.rs -// version: 24 +// version: 25 //! Runtime-native Solana program classifier for the common decode pipeline. @@ -149,7 +149,7 @@ impl crate::DcApiProtocolDecoder for crate::DcSolanaCoreDecoder { &self, _observation: &crate::MdProgramObservation, ) -> kb_core::Result> { - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, "legacy native decoder adapter classified an observation without maximal decoding"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, "legacy native decoder adapter classified an observation without maximal decoding"); return std::result::Result::Ok(std::vec::Vec::new()); } } @@ -211,14 +211,14 @@ impl crate::DcApiInstructionDecoder for crate::DcSolanaCoreDecoder { &self, input: &crate::MdCoreInstructionReplayInput, ) -> crate::DcApiDecoderRecognition { - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, transaction_failed = input.transaction_failed, "recognize native Solana contextual instruction"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "recognize", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, transaction_failed = input.transaction_failed, "recognize native Solana contextual instruction"); let surface = NATIVE_SURFACES .iter() .find(|surface| return surface.program_id == input.program_id); let surface = match surface { std::option::Option::Some(value) => value, std::option::Option::None => { - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, compatible = false, "native Solana decoder does not support program id"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, compatible = false, "native Solana decoder does not support program id"); return crate::DcApiDecoderRecognition::incompatible(); }, }; @@ -255,7 +255,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSolanaCoreDecoder { crate::decoder_api_discriminator_8_hex(input), ) }; - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, surface_code = surface.surface_code, recognition = ?recognition, compatible = true, "native Solana decoder recognized contextual instruction"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, surface_code = surface.surface_code, recognition = ?recognition, compatible = true, "native Solana decoder recognized contextual instruction"); return recognition; } @@ -298,7 +298,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSolanaCoreDecoder { | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SOLANA_CORE_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -317,7 +317,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSolanaCoreDecoder { "native instruction was not decoded successfully" ); } - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, action = "decode", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, payload_hash = ?input.instruction_payload_hash, transaction_failed = input.transaction_failed, transaction_error = ?input.transaction_err_json, result_status = ?result.status, recognized_entry_code = ?result.recognized_entry_code, observation_count = result.observations.len(), diagnostic_count = result.diagnostics.len(), "native instruction decode completed"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "decode", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, payload_hash = ?input.instruction_payload_hash, transaction_failed = input.transaction_failed, transaction_error = ?input.transaction_err_json, result_status = ?result.status, recognized_entry_code = ?result.recognized_entry_code, observation_count = result.observations.len(), diagnostic_count = result.diagnostics.len(), "native instruction decode completed"); return result; } } diff --git a/kb-lib/src/decoder/solana/core/loaders.rs b/kb-lib/src/decoder/solana/core/loaders.rs index 86c25b7..48b5d1e 100644 --- a/kb-lib/src/decoder/solana/core/loaders.rs +++ b/kb-lib/src/decoder/solana/core/loaders.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/solana/core/loaders.rs -// version: 9 +// version: 10 //! Exact bounded decoding for Solana native loader generations. @@ -221,7 +221,7 @@ pub(crate) fn decoder_solana_core_loaders_decode( input: &crate::MdCoreInstructionReplayInput, ) -> crate::DcApiDecoderExecutionResult { if input.program_id == kb_program_ids::NATIVE_LOADER_PROGRAM_ID { - tracing::debug!(target: crate::DC_SOLANA_CORE_TRACING_TARGET, action = "decode_native_loader", signature = %input.signature, instruction_path = %input.instruction_path, source = SOURCE_NATIVE_LOADER, "ignore opaque direct Native Loader invocation without inventing an instruction schema"); + tracing::debug!(target: crate::TRACING_TARGET_DECODER_SOLANA_CORE, action = "decode_native_loader", signature = %input.signature, instruction_path = %input.instruction_path, source = SOURCE_NATIVE_LOADER, "ignore opaque direct Native Loader invocation without inventing an instruction schema"); return crate::DcApiDecoderExecutionResult::ignored(std::option::Option::Some( "direct_invocation_opaque".to_string(), )); diff --git a/kb-lib/src/decoder/spl.rs b/kb-lib/src/decoder/spl.rs index 7c722d7..1c0c804 100644 --- a/kb-lib/src/decoder/spl.rs +++ b/kb-lib/src/decoder/spl.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl.rs -// version: 7 +// version: 8 //! `spl` decoder family. @@ -31,10 +31,10 @@ pub(crate) use self::associated_token_account::DC_SPL_ATA_MAX_INSTRUCTION_BYTES; pub(crate) use self::associated_token_account::DC_SPL_ATA_MAX_TRANSACTION_KEYS; /// Stable protocol and surface code for the Associated Token Account program. pub(crate) use self::associated_token_account::DC_SPL_ATA_SURFACE_CODE; -/// Canonical tracing target for the Associated Token Account decoder. -pub(crate) use self::associated_token_account::DC_SPL_ATA_TRACING_TARGET; /// Exact decoder for the SPL Associated Token Account program. pub use self::associated_token_account::DcSplAssociatedTokenAccountDecoder; +/// Canonical tracing target for the Associated Token Account decoder. +pub(crate) use self::associated_token_account::TRACING_TARGET_DECODER_SPL_ATA; /// Decodes one bounded Associated Token Account instruction. pub(crate) use self::associated_token_account::decoder_spl_associated_token_account_decode; /// Returns the exact entry matching an Associated Token Account wire. @@ -49,14 +49,14 @@ pub(crate) use self::elgamal_registry::DC_SPL_ELGAMAL_REGISTRY_EVENT_VERSION; pub(crate) use self::elgamal_registry::DC_SPL_ELGAMAL_REGISTRY_MAX_INSTRUCTION_BYTES; /// Stable SPL ElGamal registry surface code. pub(crate) use self::elgamal_registry::DC_SPL_ELGAMAL_REGISTRY_SURFACE_CODE; -/// Canonical tracing target for the SPL ElGamal registry decoder. -pub(crate) use self::elgamal_registry::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; /// One parsed SPL ElGamal registry instruction. pub(crate) use self::elgamal_registry::DcSplElGamalRegistryParsedRegistryInstruction; /// Parsed SPL ElGamal public-key registry account. pub use self::elgamal_registry::DcSplElGamalRegistryState; /// Exact decoder for the SPL ElGamal registry program. pub use self::elgamal_registry::DcSplElgamalRegistryDecoder; +/// Canonical tracing target for the SPL ElGamal registry decoder. +pub(crate) use self::elgamal_registry::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY; /// Decodes one bounded SPL ElGamal registry instruction. pub(crate) use self::elgamal_registry::decoder_spl_elgamal_registry_decode; /// Decodes one bounded SPL ElGamal registry instruction payload. @@ -73,8 +73,6 @@ pub(crate) use self::memo::DC_SPL_MEMO_EVENT_VERSION; pub(crate) use self::memo::DC_SPL_MEMO_MAX_PAYLOAD_BYTES; /// Stable protocol code shared by Memo generations. pub(crate) use self::memo::DC_SPL_MEMO_PROTOCOL_CODE; -/// Canonical tracing target for the SPL Memo decoder. -pub(crate) use self::memo::DC_SPL_MEMO_TRACING_TARGET; /// Stable Memo v1 surface code. pub(crate) use self::memo::DC_SPL_MEMO_V1_SURFACE_CODE; /// Stable Memo v3 surface code. @@ -83,6 +81,8 @@ pub(crate) use self::memo::DC_SPL_MEMO_V3_SURFACE_CODE; pub(crate) use self::memo::DC_SPL_MEMO_V4_SURFACE_CODE; /// Exact decoder for the three registered SPL Memo generations. pub use self::memo::DcSplMemoDecoder; +/// Canonical tracing target for the SPL Memo decoder. +pub(crate) use self::memo::TRACING_TARGET_DECODER_SPL_MEMO; /// Decodes one bounded SPL Memo instruction. pub(crate) use self::memo::decoder_spl_memo_decode; /// Reserved `kb_decoder_spl_noop` decoder. @@ -105,10 +105,10 @@ pub(crate) use self::token::DC_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS; pub(crate) use self::token::DC_SPL_TOKEN_MAX_INSTRUCTION_BYTES; /// Stable protocol and surface code for the classic SPL Token program. pub(crate) use self::token::DC_SPL_TOKEN_SURFACE_CODE; -/// Canonical tracing target for the classic SPL Token decoder. -pub(crate) use self::token::DC_SPL_TOKEN_TRACING_TARGET; /// Exact decoder for the classic SPL Token program. pub use self::token::DcSplTokenDecoder; +/// Canonical tracing target for the classic SPL Token decoder. +pub(crate) use self::token::TRACING_TARGET_DECODER_SPL_TOKEN; /// Decodes one bounded classic SPL Token instruction. pub(crate) use self::token::decoder_spl_token_decode; /// Returns the published entry matching one classic SPL Token tag. @@ -131,8 +131,6 @@ pub(crate) use self::token2022::DC_SPL_TOKEN2022_MAX_INSTRUCTION_BYTES; pub(crate) use self::token2022::DC_SPL_TOKEN2022_MAX_METADATA_STRING_BYTES; /// Stable Token-2022 surface code. pub(crate) use self::token2022::DC_SPL_TOKEN2022_SURFACE_CODE; -/// Canonical tracing target for the Token-2022 decoder. -pub(crate) use self::token2022::DC_SPL_TOKEN2022_TRACING_TARGET; /// Exact decoder for the Token-2022 program. pub use self::token2022::DcSplToken2022Decoder; /// Parsed Token-2022 Mint, Account, or Multisig state. @@ -141,6 +139,8 @@ pub use self::token2022::DcToken2022State; pub use self::token2022::DcToken2022StateKind; /// One exact Token-2022 TLV entry. pub use self::token2022::DcToken2022TlvEntry; +/// Canonical tracing target for the Token-2022 decoder. +pub(crate) use self::token2022::TRACING_TARGET_DECODER_SPL_TOKEN2022; /// Decodes one bounded Token-2022 instruction. pub(crate) use self::token2022::decoder_spl_token2022_decode; /// Returns the exact entry matching one Token-2022 tag. diff --git a/kb-lib/src/decoder/spl/associated_token_account.rs b/kb-lib/src/decoder/spl/associated_token_account.rs index 2f8b683..888a98e 100644 --- a/kb-lib/src/decoder/spl/associated_token_account.rs +++ b/kb-lib/src/decoder/spl/associated_token_account.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/associated_token_account.rs -// version: 3 +// version: 4 //! Exact SPL Associated Token Account decoder component. @@ -27,7 +27,7 @@ pub(crate) use self::constants::DC_SPL_ATA_MAX_TRANSACTION_KEYS; /// Stable protocol and surface code for the Associated Token Account program. pub(crate) use self::constants::DC_SPL_ATA_SURFACE_CODE; /// Canonical tracing target for the Associated Token Account decoder. -pub(crate) use self::constants::DC_SPL_ATA_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_DECODER_SPL_ATA; /// Decodes one bounded Associated Token Account instruction. pub(crate) use self::wire::decoder_spl_associated_token_account_decode; /// Returns the exact entry matching an Associated Token Account wire. diff --git a/kb-lib/src/decoder/spl/associated_token_account/constants.rs b/kb-lib/src/decoder/spl/associated_token_account/constants.rs index 527ca9f..f605d38 100644 --- a/kb-lib/src/decoder/spl/associated_token_account/constants.rs +++ b/kb-lib/src/decoder/spl/associated_token_account/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/associated_token_account/constants.rs -// version: 1 +// version: 2 //! Local constants for the `kb-lib` SPL Associated Token Account component. @@ -24,4 +24,5 @@ pub(crate) const DC_SPL_ATA_INSTRUCTION_ENTRIES: &[(u8, &str, bool)] = &[ (2, "recover_nested", false), ]; /// Canonical tracing target for the Associated Token Account decoder. -pub(crate) const DC_SPL_ATA_TRACING_TARGET: &str = "kb-lib.decoder.spl.associated_token_account"; +pub(crate) const TRACING_TARGET_DECODER_SPL_ATA: &str = + "kb-lib.decoder.spl.associated_token_account"; diff --git a/kb-lib/src/decoder/spl/associated_token_account/decoder.rs b/kb-lib/src/decoder/spl/associated_token_account/decoder.rs index 2885db7..1b7f633 100644 --- a/kb-lib/src/decoder/spl/associated_token_account/decoder.rs +++ b/kb-lib/src/decoder/spl/associated_token_account/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/associated_token_account/decoder.rs -// version: 1 +// version: 2 //! Exact SPL Associated Token Account dispatch for the common decode pipeline. @@ -111,7 +111,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplAssociatedTokenAccountDecode | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SPL_ATA_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_ATA, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -126,7 +126,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplAssociatedTokenAccountDecode ); } tracing::debug!( - target: crate::DC_SPL_ATA_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_ATA, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, diff --git a/kb-lib/src/decoder/spl/elgamal_registry.rs b/kb-lib/src/decoder/spl/elgamal_registry.rs index 831bc6c..609f333 100644 --- a/kb-lib/src/decoder/spl/elgamal_registry.rs +++ b/kb-lib/src/decoder/spl/elgamal_registry.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/elgamal_registry.rs -// version: 2 +// version: 3 //! Exact SPL Token-2022 ElGamal public-key registry decoder component. @@ -24,7 +24,7 @@ pub(crate) use self::constants::DC_SPL_ELGAMAL_REGISTRY_MAX_INSTRUCTION_BYTES; /// Stable SPL ElGamal registry surface code. pub(crate) use self::constants::DC_SPL_ELGAMAL_REGISTRY_SURFACE_CODE; /// Canonical tracing target for the SPL ElGamal registry decoder. -pub(crate) use self::constants::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY; /// One parsed SPL ElGamal registry instruction. pub(crate) use self::wire::DcSplElGamalRegistryParsedRegistryInstruction; /// Decodes one bounded SPL ElGamal registry instruction. diff --git a/kb-lib/src/decoder/spl/elgamal_registry/constants.rs b/kb-lib/src/decoder/spl/elgamal_registry/constants.rs index 7c5442e..2fce804 100644 --- a/kb-lib/src/decoder/spl/elgamal_registry/constants.rs +++ b/kb-lib/src/decoder/spl/elgamal_registry/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/elgamal_registry/constants.rs -// version: 1 +// version: 2 //! Constants for the SPL ElGamal registry decoder. @@ -8,5 +8,5 @@ pub(crate) const DC_SPL_ELGAMAL_REGISTRY_EVENT_VERSION: u32 = 1; pub(crate) const DC_SPL_ELGAMAL_REGISTRY_MAX_INSTRUCTION_BYTES: usize = 16; /// Canonical tracing target for this crate. -pub(crate) const DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET: &str = +pub(crate) const TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY: &str = "kb-lib.decoder.spl.elgamal_registry"; diff --git a/kb-lib/src/decoder/spl/elgamal_registry/decoder.rs b/kb-lib/src/decoder/spl/elgamal_registry/decoder.rs index efd8212..dec7764 100644 --- a/kb-lib/src/decoder/spl/elgamal_registry/decoder.rs +++ b/kb-lib/src/decoder/spl/elgamal_registry/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/elgamal_registry/decoder.rs -// version: 1 +// version: 2 //! Exact dispatch for the SPL ElGamal registry program. @@ -125,7 +125,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplElgamalRegistryDecoder { | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -142,7 +142,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplElgamalRegistryDecoder { ); } tracing::debug!( - target: crate::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, diff --git a/kb-lib/src/decoder/spl/memo.rs b/kb-lib/src/decoder/spl/memo.rs index 9a8e03b..ff946c0 100644 --- a/kb-lib/src/decoder/spl/memo.rs +++ b/kb-lib/src/decoder/spl/memo.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/memo.rs -// version: 2 +// version: 3 //! Exact SPL Memo v1, v3 and v4 decoder component. @@ -18,13 +18,13 @@ pub(crate) use self::constants::DC_SPL_MEMO_EVENT_VERSION; pub(crate) use self::constants::DC_SPL_MEMO_MAX_PAYLOAD_BYTES; /// Stable protocol code shared by Memo generations. pub(crate) use self::constants::DC_SPL_MEMO_PROTOCOL_CODE; -/// Canonical tracing target for the SPL Memo decoder. -pub(crate) use self::constants::DC_SPL_MEMO_TRACING_TARGET; /// Stable Memo v1 surface code. pub(crate) use self::constants::DC_SPL_MEMO_V1_SURFACE_CODE; /// Stable Memo v3 surface code. pub(crate) use self::constants::DC_SPL_MEMO_V3_SURFACE_CODE; /// Stable Memo v4 surface code. pub(crate) use self::constants::DC_SPL_MEMO_V4_SURFACE_CODE; +/// Canonical tracing target for the SPL Memo decoder. +pub(crate) use self::constants::TRACING_TARGET_DECODER_SPL_MEMO; /// Decodes one bounded SPL Memo instruction. pub(crate) use self::payload::decoder_spl_memo_decode; diff --git a/kb-lib/src/decoder/spl/memo/constants.rs b/kb-lib/src/decoder/spl/memo/constants.rs index c64ecc6..7feb23c 100644 --- a/kb-lib/src/decoder/spl/memo/constants.rs +++ b/kb-lib/src/decoder/spl/memo/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/memo/constants.rs -// version: 2 +// version: 3 //! Local constants for the `kb-lib` SPL Memo component. Program identifiers live in `kb_program_ids`. @@ -18,4 +18,4 @@ pub(crate) const DC_SPL_MEMO_DIAGNOSTIC_PREFIX_BYTES: usize = 32; /// Current stable Memo decoded event contract version. pub(crate) const DC_SPL_MEMO_EVENT_VERSION: u32 = 1; /// Canonical tracing target for this crate. -pub(crate) const DC_SPL_MEMO_TRACING_TARGET: &str = "kb-lib.decoder.spl.memo"; +pub(crate) const TRACING_TARGET_DECODER_SPL_MEMO: &str = "kb-lib.decoder.spl.memo"; diff --git a/kb-lib/src/decoder/spl/memo/decoder.rs b/kb-lib/src/decoder/spl/memo/decoder.rs index d2b167e..1ec1ab9 100644 --- a/kb-lib/src/decoder/spl/memo/decoder.rs +++ b/kb-lib/src/decoder/spl/memo/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/memo/decoder.rs -// version: 1 +// version: 2 //! Exact SPL Memo v1, v3 and v4 dispatch for the common decode pipeline. @@ -124,7 +124,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplMemoDecoder { | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SPL_MEMO_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_MEMO, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -141,7 +141,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplMemoDecoder { ); } tracing::debug!( - target: crate::DC_SPL_MEMO_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_MEMO, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, diff --git a/kb-lib/src/decoder/spl/token.rs b/kb-lib/src/decoder/spl/token.rs index d9cfca3..ae397ed 100644 --- a/kb-lib/src/decoder/spl/token.rs +++ b/kb-lib/src/decoder/spl/token.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token.rs -// version: 2 +// version: 3 //! Exact classic SPL Token decoder component. @@ -25,7 +25,7 @@ pub(crate) use self::constants::DC_SPL_TOKEN_MAX_INSTRUCTION_BYTES; /// Stable protocol and surface code for the classic SPL Token program. pub(crate) use self::constants::DC_SPL_TOKEN_SURFACE_CODE; /// Canonical tracing target for the classic SPL Token decoder. -pub(crate) use self::constants::DC_SPL_TOKEN_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_DECODER_SPL_TOKEN; /// Decodes one bounded classic SPL Token instruction. pub(crate) use self::wire::decoder_spl_token_decode; /// Returns the published entry matching one classic SPL Token tag. diff --git a/kb-lib/src/decoder/spl/token/constants.rs b/kb-lib/src/decoder/spl/token/constants.rs index 65e20e0..370ba8f 100644 --- a/kb-lib/src/decoder/spl/token/constants.rs +++ b/kb-lib/src/decoder/spl/token/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token/constants.rs -// version: 1 +// version: 2 //! Local constants for the `kb-lib` SPL Token component. Program identifiers live in `kb_program_ids`. @@ -47,4 +47,4 @@ pub(crate) const DC_SPL_TOKEN_INSTRUCTION_ENTRIES: &[(u8, &str, bool)] = &[ (255, "batch", false), ]; /// Canonical tracing target for this crate. -pub(crate) const DC_SPL_TOKEN_TRACING_TARGET: &str = "kb-lib.decoder.spl.token"; +pub(crate) const TRACING_TARGET_DECODER_SPL_TOKEN: &str = "kb-lib.decoder.spl.token"; diff --git a/kb-lib/src/decoder/spl/token/decoder.rs b/kb-lib/src/decoder/spl/token/decoder.rs index a0a02f6..31a614e 100644 --- a/kb-lib/src/decoder/spl/token/decoder.rs +++ b/kb-lib/src/decoder/spl/token/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token/decoder.rs -// version: 1 +// version: 2 //! Exact classic SPL Token dispatch for the common decode pipeline. @@ -109,7 +109,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplTokenDecoder { | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SPL_TOKEN_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_TOKEN, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -125,7 +125,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplTokenDecoder { ); } tracing::debug!( - target: crate::DC_SPL_TOKEN_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_TOKEN, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, diff --git a/kb-lib/src/decoder/spl/token2022.rs b/kb-lib/src/decoder/spl/token2022.rs index 2faecbf..56d5b7f 100644 --- a/kb-lib/src/decoder/spl/token2022.rs +++ b/kb-lib/src/decoder/spl/token2022.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token2022.rs -// version: 2 +// version: 3 //! Exact Token-2022 instruction and state decoder component. @@ -36,7 +36,7 @@ pub(crate) use self::constants::DC_SPL_TOKEN2022_MAX_METADATA_STRING_BYTES; /// Stable Token-2022 surface code. pub(crate) use self::constants::DC_SPL_TOKEN2022_SURFACE_CODE; /// Canonical tracing target for the Token-2022 decoder. -pub(crate) use self::constants::DC_SPL_TOKEN2022_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_DECODER_SPL_TOKEN2022; /// Decodes one bounded Token-2022 instruction. pub(crate) use self::wire::decoder_spl_token2022_decode; /// Returns the exact entry matching one Token-2022 tag. diff --git a/kb-lib/src/decoder/spl/token2022/constants.rs b/kb-lib/src/decoder/spl/token2022/constants.rs index f464bbb..da3ffb3 100644 --- a/kb-lib/src/decoder/spl/token2022/constants.rs +++ b/kb-lib/src/decoder/spl/token2022/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token2022/constants.rs -// version: 1 +// version: 2 //! Local constants for the exact Token-2022 decoder. Program identifiers live in `kb_program_ids`. @@ -69,4 +69,4 @@ pub(crate) const DC_SPL_TOKEN2022_INSTRUCTION_ENTRIES: &[(u8, &str, bool)] = &[ (255, "batch", false), ]; /// Canonical tracing target for this crate. -pub(crate) const DC_SPL_TOKEN2022_TRACING_TARGET: &str = "kb-lib.decoder.spl.token2022"; +pub(crate) const TRACING_TARGET_DECODER_SPL_TOKEN2022: &str = "kb-lib.decoder.spl.token2022"; diff --git a/kb-lib/src/decoder/spl/token2022/decoder.rs b/kb-lib/src/decoder/spl/token2022/decoder.rs index 1ca2859..eee3c71 100644 --- a/kb-lib/src/decoder/spl/token2022/decoder.rs +++ b/kb-lib/src/decoder/spl/token2022/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token2022/decoder.rs -// version: 1 +// version: 2 //! Exact Token-2022 dispatch for the common decode pipeline. @@ -109,7 +109,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplToken2022Decoder { | crate::DcApiDecoderOutcomeStatus::Unsupported ) { tracing::error!( - target: crate::DC_SPL_TOKEN2022_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_TOKEN2022, action = "decode_failure", signature = %input.signature, slot = input.slot, @@ -125,7 +125,7 @@ impl crate::DcApiInstructionDecoder for crate::DcSplToken2022Decoder { ); } tracing::debug!( - target: crate::DC_SPL_TOKEN2022_TRACING_TARGET, + target: crate::TRACING_TARGET_DECODER_SPL_TOKEN2022, action = "decode", signature = %input.signature, instruction_path = %input.instruction_path, diff --git a/kb-lib/src/executor.rs b/kb-lib/src/executor.rs index ca3a1f2..090d92e 100644 --- a/kb-lib/src/executor.rs +++ b/kb-lib/src/executor.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor.rs -// version: 6 +// version: 7 //! Consolidated executor modules. @@ -823,7 +823,7 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor; pub use self::weighted::ExWeightedSwapStabbleExecutor; /// Canonical tracing target for this crate. -pub(crate) use self::solana::EX_SOLANA_CORE_TRACING_TARGET; +pub(crate) use self::solana::TRACING_TARGET_EXECUTOR_SOLANA_CORE; /// Crate-root access to `build_prepared_plan` from `address_lookup_table`. pub(crate) use self::solana::executor_solana_core_address_lookup_table_build_prepared_plan; /// Crate-root access to `build_prepared_plan` from `builder`. @@ -860,20 +860,20 @@ pub(crate) use self::solana::executor_solana_core_validate_seeded_address; pub(crate) use self::solana::executor_solana_core_vote_build_prepared_plan; /// Crate-root access to `build_prepared_plan` from `zk_elgamal`. pub(crate) use self::solana::executor_solana_core_zk_elgamal_build_prepared_plan; -/// Canonical SPL ATA tracing target. -pub(crate) use self::spl::EX_SPL_ATA_TRACING_TARGET; -/// Canonical SPL ElGamal registry tracing target. -pub(crate) use self::spl::EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; -/// Canonical SPL Memo tracing target. -pub(crate) use self::spl::EX_SPL_MEMO_TRACING_TARGET; /// Maximum ordered multisig signer occurrences for SPL Token-2022. pub(crate) use self::spl::EX_SPL_TOKEN2022_MAX_MULTISIG_SIGNERS; /// Maximum transfer-fee source account count for SPL Token-2022. pub(crate) use self::spl::EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNTS; /// Maximum UI amount byte length for SPL Token-2022. pub(crate) use self::spl::EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES; +/// Canonical SPL ATA tracing target. +pub(crate) use self::spl::TRACING_TARGET_EXECUTOR_SPL_ATA; +/// Canonical SPL ElGamal registry tracing target. +pub(crate) use self::spl::TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY; +/// Canonical SPL Memo tracing target. +pub(crate) use self::spl::TRACING_TARGET_EXECUTOR_SPL_MEMO; /// Canonical SPL Token-2022 tracing target. -pub(crate) use self::spl::EX_SPL_TOKEN2022_TRACING_TARGET; +pub(crate) use self::spl::TRACING_TARGET_EXECUTOR_SPL_TOKEN2022; /// Internal SPL ATA plan builder. pub(crate) use self::spl::executor_spl_ata_build_prepared_plan; /// Internal SPL ElGamal registry plan builder. diff --git a/kb-lib/src/executor/solana.rs b/kb-lib/src/executor/solana.rs index f283501..44ac7f8 100644 --- a/kb-lib/src/executor/solana.rs +++ b/kb-lib/src/executor/solana.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/solana.rs -// version: 3 +// version: 4 //! `solana` executor family. @@ -255,7 +255,7 @@ pub use self::core::ExSolanaCoreZkElGamalContextState; pub use self::core::ExSolanaCoreZkElGamalProofType; /// Canonical tracing target for this crate. -pub(crate) use self::core::EX_SOLANA_CORE_TRACING_TARGET; +pub(crate) use self::core::TRACING_TARGET_EXECUTOR_SOLANA_CORE; /// Crate-root access to `build_prepared_plan` from `address_lookup_table`. pub(crate) use self::core::executor_solana_core_address_lookup_table_build_prepared_plan; /// Crate-root access to `build_prepared_plan` from `builder`. diff --git a/kb-lib/src/executor/solana/core.rs b/kb-lib/src/executor/solana/core.rs index f01964a..057e940 100644 --- a/kb-lib/src/executor/solana/core.rs +++ b/kb-lib/src/executor/solana/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/solana/core.rs -// version: 3 +// version: 4 //! Safe typed executor for supported Solana core operations. @@ -274,7 +274,7 @@ pub(crate) use self::builder::executor_solana_core_builder_build_prepared_plan; /// Crate-root access to `finalize_prepared_plan` from `builder`. pub(crate) use self::builder::executor_solana_core_finalize_prepared_plan; /// Canonical tracing target for this crate. -pub(crate) use self::constants::EX_SOLANA_CORE_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SOLANA_CORE; /// Crate-root access to `build_prepared_plan` from `loader_v3`. pub(crate) use self::loader_v3::executor_solana_core_loader_v3_build_prepared_plan; /// Crate-root access to `build_prepared_plan` from `loader_v4`. diff --git a/kb-lib/src/executor/solana/core/builder.rs b/kb-lib/src/executor/solana/core/builder.rs index e329871..321d410 100644 --- a/kb-lib/src/executor/solana/core/builder.rs +++ b/kb-lib/src/executor/solana/core/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/solana/core/builder.rs -// version: 1 +// version: 2 //! Official Solana core instruction builders and shared plan finalization. @@ -1239,7 +1239,7 @@ pub(crate) fn executor_solana_core_finalize_prepared_plan( std::result::Result::Err(error) => return std::result::Result::Err(error), } tracing::debug!( - target: crate::EX_SOLANA_CORE_TRACING_TARGET, + target: crate::TRACING_TARGET_EXECUTOR_SOLANA_CORE, action = "executor_solana_core_builder_build_prepared_plan", intent_id = %intent.intent_id, operation_code, diff --git a/kb-lib/src/executor/solana/core/constants.rs b/kb-lib/src/executor/solana/core/constants.rs index de6a3ed..02a95c5 100644 --- a/kb-lib/src/executor/solana/core/constants.rs +++ b/kb-lib/src/executor/solana/core/constants.rs @@ -1,7 +1,7 @@ // file: kb-lib/src/executor/solana/core/constants.rs -// version: 1 +// version: 2 //! Local constants for the `kb_executor_solana_core` crate. Program identifiers live in `kb_program_ids`. /// Canonical tracing target for this crate. -pub(crate) const EX_SOLANA_CORE_TRACING_TARGET: &str = "kb-lib.executor.solana.core"; +pub(crate) const TRACING_TARGET_EXECUTOR_SOLANA_CORE: &str = "kb-lib.executor.solana.core"; diff --git a/kb-lib/src/executor/spl.rs b/kb-lib/src/executor/spl.rs index 44e650a..5b9d6e8 100644 --- a/kb-lib/src/executor/spl.rs +++ b/kb-lib/src/executor/spl.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl.rs -// version: 5 +// version: 6 //! `spl` executor family. @@ -309,15 +309,15 @@ pub use self::token2022::ExSplTokenSingleOperation; pub use self::token2022::executor_spl_token2022_validate_confidential_proof_references; /// Canonical SPL ATA tracing target. -pub(crate) use self::associated_token_account::EX_SPL_ATA_TRACING_TARGET; +pub(crate) use self::associated_token_account::TRACING_TARGET_EXECUTOR_SPL_ATA; /// Internal SPL ATA plan builder. pub(crate) use self::associated_token_account::executor_spl_ata_build_prepared_plan; /// Canonical SPL ElGamal registry tracing target. -pub(crate) use self::elgamal_registry::EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; +pub(crate) use self::elgamal_registry::TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY; /// Internal SPL ElGamal registry plan builder. pub(crate) use self::elgamal_registry::executor_spl_elgamal_registry_build_prepared_plan; /// Canonical SPL Memo tracing target. -pub(crate) use self::memo::EX_SPL_MEMO_TRACING_TARGET; +pub(crate) use self::memo::TRACING_TARGET_EXECUTOR_SPL_MEMO; /// Internal SPL Memo plan builder. pub(crate) use self::memo::executor_spl_memo_build_prepared_plan; /// Maximum ordered multisig signer occurrences for SPL Token-2022. @@ -327,6 +327,6 @@ pub(crate) use self::token2022::EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNT /// Maximum UI amount byte length for SPL Token-2022. pub(crate) use self::token2022::EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES; /// Canonical SPL Token-2022 tracing target. -pub(crate) use self::token2022::EX_SPL_TOKEN2022_TRACING_TARGET; +pub(crate) use self::token2022::TRACING_TARGET_EXECUTOR_SPL_TOKEN2022; /// Internal SPL Token-2022 prepared-plan builder. pub(crate) use self::token2022::executor_spl_token2022_build_prepared_plan; diff --git a/kb-lib/src/executor/spl/associated_token_account.rs b/kb-lib/src/executor/spl/associated_token_account.rs index 7519d13..730d1cb 100644 --- a/kb-lib/src/executor/spl/associated_token_account.rs +++ b/kb-lib/src/executor/spl/associated_token_account.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/associated_token_account.rs -// version: 3 +// version: 4 //! Safe typed executor for SPL Associated Token Account. @@ -28,4 +28,4 @@ pub use self::intent::ExSplAssociatedTokenProgram; /// Internal SPL ATA plan builder. pub(crate) use self::builder::executor_spl_ata_build_prepared_plan; /// Canonical tracing target for SPL ATA execution. -pub(crate) use self::constants::EX_SPL_ATA_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SPL_ATA; diff --git a/kb-lib/src/executor/spl/associated_token_account/builder.rs b/kb-lib/src/executor/spl/associated_token_account/builder.rs index 7333fbb..6938d2a 100644 --- a/kb-lib/src/executor/spl/associated_token_account/builder.rs +++ b/kb-lib/src/executor/spl/associated_token_account/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/associated_token_account/builder.rs -// version: 4 +// version: 5 //! Official ATA builders and conservative simulation-first plan validation. @@ -108,7 +108,7 @@ pub(crate) fn executor_spl_ata_build_prepared_plan( let planned_instruction = planned_instruction(intent.operation.operation_code(), &instruction); let required_signers = required_signers(&intent.fee_payer, &planned_instruction.accounts); tracing::debug!( - target: crate::EX_SPL_ATA_TRACING_TARGET, + target: crate::TRACING_TARGET_EXECUTOR_SPL_ATA, action = "build_prepared_plan", intent_id = %intent.intent_id, operation_code = intent.operation.operation_code(), diff --git a/kb-lib/src/executor/spl/associated_token_account/constants.rs b/kb-lib/src/executor/spl/associated_token_account/constants.rs index fbfdb7b..814ec7c 100644 --- a/kb-lib/src/executor/spl/associated_token_account/constants.rs +++ b/kb-lib/src/executor/spl/associated_token_account/constants.rs @@ -1,7 +1,8 @@ // file: kb-lib/src/executor/spl/associated_token_account/constants.rs -// version: 3 +// version: 4 //! Local constants for the `kb_executor_spl_associated_token_account` crate. Program identifiers live in `kb_program_ids`. /// Canonical tracing target for this crate. -pub(crate) const EX_SPL_ATA_TRACING_TARGET: &str = "kb-lib.executor.spl.associated_token_account"; +pub(crate) const TRACING_TARGET_EXECUTOR_SPL_ATA: &str = + "kb-lib.executor.spl.associated_token_account"; diff --git a/kb-lib/src/executor/spl/elgamal_registry.rs b/kb-lib/src/executor/spl/elgamal_registry.rs index 63b82fb..e7f280a 100644 --- a/kb-lib/src/executor/spl/elgamal_registry.rs +++ b/kb-lib/src/executor/spl/elgamal_registry.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/elgamal_registry.rs -// version: 3 +// version: 4 //! Typed executor for the SPL ElGamal public-key registry. @@ -24,4 +24,4 @@ pub use self::intent::ExSplElgamalRegistryProofLocation; /// Crate-root access to the exact ElGamal registry plan builder. pub(crate) use self::builder::executor_spl_elgamal_registry_build_prepared_plan; /// Canonical tracing target for the SPL ElGamal registry executor. -pub(crate) use self::constants::EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY; diff --git a/kb-lib/src/executor/spl/elgamal_registry/builder.rs b/kb-lib/src/executor/spl/elgamal_registry/builder.rs index 4002ec6..ffa7a35 100644 --- a/kb-lib/src/executor/spl/elgamal_registry/builder.rs +++ b/kb-lib/src/executor/spl/elgamal_registry/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/elgamal_registry/builder.rs -// version: 4 +// version: 5 //! Official registry builders and conservative plan validation. @@ -125,7 +125,7 @@ pub(crate) fn executor_spl_elgamal_registry_build_prepared_plan( }); } tracing::debug!( - target: crate::EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET, + target: crate::TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY, action = "build_prepared_plan", intent_id = %intent.intent_id, operation_code, diff --git a/kb-lib/src/executor/spl/elgamal_registry/constants.rs b/kb-lib/src/executor/spl/elgamal_registry/constants.rs index f89263a..39bc778 100644 --- a/kb-lib/src/executor/spl/elgamal_registry/constants.rs +++ b/kb-lib/src/executor/spl/elgamal_registry/constants.rs @@ -1,8 +1,8 @@ // file: kb-lib/src/executor/spl/elgamal_registry/constants.rs -// version: 2 +// version: 3 //! Executor-local constants. /// Canonical tracing target for this crate. -pub(crate) const EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET: &str = +pub(crate) const TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY: &str = "kb-lib.executor.spl.elgamal_registry"; diff --git a/kb-lib/src/executor/spl/memo.rs b/kb-lib/src/executor/spl/memo.rs index a3602f3..10309ef 100644 --- a/kb-lib/src/executor/spl/memo.rs +++ b/kb-lib/src/executor/spl/memo.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/memo.rs -// version: 4 +// version: 5 //! Safe typed executor for `spl_memo`. @@ -28,4 +28,4 @@ pub use self::intent::ExSplMemoSigner; /// Internal SPL Memo plan builder. pub(crate) use self::builder::executor_spl_memo_build_prepared_plan; /// Canonical tracing target for SPL Memo execution. -pub(crate) use self::constants::EX_SPL_MEMO_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SPL_MEMO; diff --git a/kb-lib/src/executor/spl/memo/builder.rs b/kb-lib/src/executor/spl/memo/builder.rs index d458f5b..57631e6 100644 --- a/kb-lib/src/executor/spl/memo/builder.rs +++ b/kb-lib/src/executor/spl/memo/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/memo/builder.rs -// version: 1 +// version: 2 //! Official SPL Memo instruction builder and conservative plan validation. @@ -74,7 +74,7 @@ pub(crate) fn executor_spl_memo_build_prepared_plan( planned_instruction(crate::EX_SPL_MEMO_ADD_MEMO_OPERATION, &instruction); let required_signers = required_signers(&intent.fee_payer, signers.as_slice()); tracing::debug!( - target: crate::EX_SPL_MEMO_TRACING_TARGET, + target: crate::TRACING_TARGET_EXECUTOR_SPL_MEMO, action = "build_prepared_plan", intent_id = %intent.intent_id, operation_code = crate::EX_SPL_MEMO_ADD_MEMO_OPERATION, diff --git a/kb-lib/src/executor/spl/memo/constants.rs b/kb-lib/src/executor/spl/memo/constants.rs index 2ee3ab0..b34c961 100644 --- a/kb-lib/src/executor/spl/memo/constants.rs +++ b/kb-lib/src/executor/spl/memo/constants.rs @@ -1,7 +1,7 @@ // file: kb-lib/src/executor/spl/memo/constants.rs -// version: 1 +// version: 2 //! Local constants for the `kb_executor_spl_memo` crate. Program identifiers live in `kb_program_ids`. /// Canonical tracing target for this crate. -pub(crate) const EX_SPL_MEMO_TRACING_TARGET: &str = "kb-lib.executor.spl.memo"; +pub(crate) const TRACING_TARGET_EXECUTOR_SPL_MEMO: &str = "kb-lib.executor.spl.memo"; diff --git a/kb-lib/src/executor/spl/token2022.rs b/kb-lib/src/executor/spl/token2022.rs index 27cb0ae..0cfc0d8 100644 --- a/kb-lib/src/executor/spl/token2022.rs +++ b/kb-lib/src/executor/spl/token2022.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token2022.rs -// version: 3 +// version: 4 //! SPL Token-2022 executor. @@ -258,4 +258,4 @@ pub(crate) use self::constants::EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNT /// Maximum UI amount byte length. pub(crate) use self::constants::EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES; /// Canonical SPL Token-2022 tracing target. -pub(crate) use self::constants::EX_SPL_TOKEN2022_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SPL_TOKEN2022; diff --git a/kb-lib/src/executor/spl/token2022/builder.rs b/kb-lib/src/executor/spl/token2022/builder.rs index f82dd0e..a69c071 100644 --- a/kb-lib/src/executor/spl/token2022/builder.rs +++ b/kb-lib/src/executor/spl/token2022/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token2022/builder.rs -// version: 27 +// version: 28 //! Official Token-2022 builders and conservative plan validation. @@ -58,7 +58,7 @@ pub(crate) fn executor_spl_token2022_build_prepared_plan( let planned_instruction = planned_instruction(intent.operation.operation_code(), &instruction); let required_signers = required_signers(&intent.fee_payer, &instruction.accounts); tracing::debug!( - target: crate::EX_SPL_TOKEN2022_TRACING_TARGET, + target: crate::TRACING_TARGET_EXECUTOR_SPL_TOKEN2022, action = "executor_spl_token2022_build_prepared_plan", intent_id = %intent.intent_id, operation_code = intent.operation.operation_code(), diff --git a/kb-lib/src/executor/spl/token2022/constants.rs b/kb-lib/src/executor/spl/token2022/constants.rs index 9a6c028..5954464 100644 --- a/kb-lib/src/executor/spl/token2022/constants.rs +++ b/kb-lib/src/executor/spl/token2022/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token2022/constants.rs -// version: 5 +// version: 6 //! Local constants for the `kb_executor_spl_token_2022` crate. Program identifiers live in `kb_program_ids`. @@ -10,4 +10,4 @@ pub(crate) const EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES: usize = 255; /// Maximum ordered token accounts accepted by transfer-fee harvest or withdraw builders. pub(crate) const EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNTS: usize = 255; /// Canonical tracing target for this crate. -pub(crate) const EX_SPL_TOKEN2022_TRACING_TARGET: &str = "kb-lib.executor.spl.token2022"; +pub(crate) const TRACING_TARGET_EXECUTOR_SPL_TOKEN2022: &str = "kb-lib.executor.spl.token2022"; diff --git a/kb-lib/src/lib.rs b/kb-lib/src/lib.rs index f7efc76..aabc016 100644 --- a/kb-lib/src/lib.rs +++ b/kb-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/lib.rs -// version: 21 +// version: 22 //! Consolidated decoder, executor, materializer and shared model library. #![warn(missing_docs)] @@ -1382,8 +1382,6 @@ pub(crate) use self::decoder::DC_METADATA_MTM_SIGN_METADATA_DISCRIMINATOR; pub(crate) use self::decoder::DC_METADATA_MTM_SURFACE_CODE; /// Thaw Delegated Account discriminator. pub(crate) use self::decoder::DC_METADATA_MTM_THAW_DELEGATED_ACCOUNT_DISCRIMINATOR; -/// Canonical tracing target for this crate. -pub(crate) use self::decoder::DC_METADATA_MTM_TRACING_TARGET; /// Transfer discriminator. pub(crate) use self::decoder::DC_METADATA_MTM_TRANSFER_DISCRIMINATOR; /// Transfer Out Of Escrow discriminator. @@ -1462,8 +1460,6 @@ pub(crate) use self::decoder::DC_SOLANA_CORE_SLASHING_SURFACE_CODE; pub(crate) use self::decoder::DC_SOLANA_CORE_STAKE_SURFACE_CODE; /// Stable System Program surface code. pub(crate) use self::decoder::DC_SOLANA_CORE_SYSTEM_SURFACE_CODE; -/// Canonical tracing target for this crate. -pub(crate) use self::decoder::DC_SOLANA_CORE_TRACING_TARGET; /// Byte length of one secp256k1 offsets entry. pub(crate) use self::decoder::DC_SOLANA_CORE_U8_OFFSETS_BYTES; /// Byte length of one Ed25519 or secp256r1 offsets entry. @@ -1490,16 +1486,12 @@ pub(crate) use self::decoder::DC_SPL_ATA_MAX_INSTRUCTION_BYTES; pub(crate) use self::decoder::DC_SPL_ATA_MAX_TRANSACTION_KEYS; /// Stable protocol and surface code for the Associated Token Account program. pub(crate) use self::decoder::DC_SPL_ATA_SURFACE_CODE; -/// Canonical tracing target for the Associated Token Account decoder. -pub(crate) use self::decoder::DC_SPL_ATA_TRACING_TARGET; /// Current stable SPL ElGamal registry event contract version. pub(crate) use self::decoder::DC_SPL_ELGAMAL_REGISTRY_EVENT_VERSION; /// Maximum retained SPL ElGamal registry instruction payload size. pub(crate) use self::decoder::DC_SPL_ELGAMAL_REGISTRY_MAX_INSTRUCTION_BYTES; /// Stable SPL ElGamal registry surface code. pub(crate) use self::decoder::DC_SPL_ELGAMAL_REGISTRY_SURFACE_CODE; -/// Canonical tracing target for the SPL ElGamal registry decoder. -pub(crate) use self::decoder::DC_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; /// Maximum payload prefix retained for bounded Memo diagnostics. pub(crate) use self::decoder::DC_SPL_MEMO_DIAGNOSTIC_PREFIX_BYTES; /// Current stable Memo decoded event contract version. @@ -1508,8 +1500,6 @@ pub(crate) use self::decoder::DC_SPL_MEMO_EVENT_VERSION; pub(crate) use self::decoder::DC_SPL_MEMO_MAX_PAYLOAD_BYTES; /// Stable protocol code shared by Memo generations. pub(crate) use self::decoder::DC_SPL_MEMO_PROTOCOL_CODE; -/// Canonical tracing target for the SPL Memo decoder. -pub(crate) use self::decoder::DC_SPL_MEMO_TRACING_TARGET; /// Stable Memo v1 surface code. pub(crate) use self::decoder::DC_SPL_MEMO_V1_SURFACE_CODE; /// Stable Memo v3 surface code. @@ -1530,8 +1520,6 @@ pub(crate) use self::decoder::DC_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS; pub(crate) use self::decoder::DC_SPL_TOKEN_MAX_INSTRUCTION_BYTES; /// Stable protocol and surface code for the classic SPL Token program. pub(crate) use self::decoder::DC_SPL_TOKEN_SURFACE_CODE; -/// Canonical tracing target for the classic SPL Token decoder. -pub(crate) use self::decoder::DC_SPL_TOKEN_TRACING_TARGET; /// Maximum prefix retained in bounded Token-2022 diagnostics. pub(crate) use self::decoder::DC_SPL_TOKEN2022_DIAGNOSTIC_PREFIX_BYTES; /// Current stable Token-2022 decoded event contract version. @@ -1548,14 +1536,26 @@ pub(crate) use self::decoder::DC_SPL_TOKEN2022_MAX_INSTRUCTION_BYTES; pub(crate) use self::decoder::DC_SPL_TOKEN2022_MAX_METADATA_STRING_BYTES; /// Stable Token-2022 surface code. pub(crate) use self::decoder::DC_SPL_TOKEN2022_SURFACE_CODE; -/// Canonical tracing target for the Token-2022 decoder. -pub(crate) use self::decoder::DC_SPL_TOKEN2022_TRACING_TARGET; /// Expected role and privileges for one positional instruction account. pub(crate) use self::decoder::DcSolanaCoreAccountRole; /// One resolved outer instruction payload and its provenance relative to the target instruction. pub(crate) use self::decoder::DcSolanaCoreResolvedInstructionPayload; /// One parsed SPL ElGamal registry instruction. pub(crate) use self::decoder::DcSplElGamalRegistryParsedRegistryInstruction; +/// Canonical tracing target for this crate. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA; +/// Canonical tracing target for this crate. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SOLANA_CORE; +/// Canonical tracing target for the Associated Token Account decoder. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SPL_ATA; +/// Canonical tracing target for the SPL ElGamal registry decoder. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SPL_ELGAMAL_REGISTRY; +/// Canonical tracing target for the SPL Memo decoder. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SPL_MEMO; +/// Canonical tracing target for the classic SPL Token decoder. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SPL_TOKEN; +/// Canonical tracing target for the Token-2022 decoder. +pub(crate) use self::decoder::TRACING_TARGET_DECODER_SPL_TOKEN2022; /// Crate-root access to `decode` from `instruction`. pub(crate) use self::decoder::decoder_metadata_metaplex_token_metadata_decode; /// Crate-root access to `entry_for_discriminator` from `instruction`. @@ -1694,22 +1694,22 @@ pub(crate) use self::decoder::decoder_spl_token2022_decode; pub(crate) use self::decoder::decoder_spl_token2022_entry_for_tag; /// Returns the first byte of one retained Token-2022 payload. pub(crate) use self::decoder::decoder_spl_token2022_payload_tag; -/// Canonical tracing target for this crate. -pub(crate) use self::executor::EX_SOLANA_CORE_TRACING_TARGET; -/// Canonical SPL Memo tracing target. -pub(crate) use self::executor::EX_SPL_ATA_TRACING_TARGET; -/// Canonical tracing target for the SPL ElGamal registry executor. -pub(crate) use self::executor::EX_SPL_ELGAMAL_REGISTRY_TRACING_TARGET; -/// Canonical SPL Memo tracing target. -pub(crate) use self::executor::EX_SPL_MEMO_TRACING_TARGET; /// Maximum ordered multisig signer occurrences for SPL Token-2022. pub(crate) use self::executor::EX_SPL_TOKEN2022_MAX_MULTISIG_SIGNERS; /// Maximum transfer-fee source account count for SPL Token-2022. pub(crate) use self::executor::EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNTS; /// Maximum UI amount byte length for SPL Token-2022. pub(crate) use self::executor::EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES; +/// Canonical tracing target for this crate. +pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SOLANA_CORE; +/// Canonical SPL Memo tracing target. +pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_ATA; +/// Canonical tracing target for the SPL ElGamal registry executor. +pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_ELGAMAL_REGISTRY; +/// Canonical SPL Memo tracing target. +pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_MEMO; /// Canonical SPL Token-2022 tracing target. -pub(crate) use self::executor::EX_SPL_TOKEN2022_TRACING_TARGET; +pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN2022; /// Crate-root access to `build_prepared_plan` from `address_lookup_table`. pub(crate) use self::executor::executor_solana_core_address_lookup_table_build_prepared_plan; /// Crate-root access to `build_prepared_plan` from `builder`. @@ -1756,31 +1756,31 @@ pub(crate) use self::executor::executor_spl_memo_build_prepared_plan; pub(crate) use self::executor::executor_spl_token2022_build_prepared_plan; /// Canonical processor name for the native and SPL administration materializer. pub(crate) use self::materializer::MT_ADMIN_PROCESSOR_NAME; -/// Canonical tracing target for the native and SPL administration materializer. -pub(crate) use self::materializer::MT_ADMIN_TRACING_TARGET; -/// Canonical tracing target for the native compliance audit materializer. -pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_TRACING_TARGET; -/// Canonical tracing target for the native lifecycle materializer. -pub(crate) use self::materializer::MT_LIFECYCLE_TRACING_TARGET; /// Stable processor name for SPL Token and ATA risk facts. pub(crate) use self::materializer::MT_RISK_PROCESSOR_NAME; /// Stable projection contract version for SPL Token and ATA risk facts. pub(crate) use self::materializer::MT_RISK_PROJECTION_VERSION; -/// Canonical tracing target for SPL Token and ATA risk facts. -pub(crate) use self::materializer::MT_RISK_TRACING_TARGET; -/// Canonical tracing target for the native stake and vote materializer. -pub(crate) use self::materializer::MT_STAKING_TRACING_TARGET; /// Stable processor name for Token, ATA and Token-2022 account projections. pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME; /// Stable projection contract version for Token, ATA and Token-2022 account projections. pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION; -/// Canonical tracing target for Token, ATA and Token-2022 account projections. -pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_TRACING_TARGET; /// Maximum Memo payload accepted by transaction annotations. pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES; /// Stable processor name for transaction annotations. pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME; /// Stable projection contract version for transaction annotations. pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION; +/// Canonical tracing target for the native and SPL administration materializer. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_ADMIN; +/// Canonical tracing target for the native compliance audit materializer. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT; +/// Canonical tracing target for the native lifecycle materializer. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_LIFECYCLE; +/// Canonical tracing target for SPL Token and ATA risk facts. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_RISK; +/// Canonical tracing target for the native stake and vote materializer. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_STAKING; +/// Canonical tracing target for Token, ATA and Token-2022 account projections. +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS; /// Canonical tracing target for transaction annotations. -pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET; +pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS; diff --git a/kb-lib/src/materializer.rs b/kb-lib/src/materializer.rs index 0d2265d..a444cfa 100644 --- a/kb-lib/src/materializer.rs +++ b/kb-lib/src/materializer.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer.rs -// version: 5 +// version: 6 //! Consolidated materializer modules. @@ -115,25 +115,25 @@ pub use self::vault::MtVaultMaterializer; /// Canonical processor name for the native and SPL administration materializer. pub(crate) use self::admin::MT_ADMIN_PROCESSOR_NAME; /// Canonical tracing target for the native and SPL administration materializer. -pub(crate) use self::admin::MT_ADMIN_TRACING_TARGET; +pub(crate) use self::admin::TRACING_TARGET_MATERIALIZER_ADMIN; /// Canonical tracing target for the native compliance audit materializer. -pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_TRACING_TARGET; +pub(crate) use self::compliance::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT; /// Canonical tracing target for the native lifecycle materializer. -pub(crate) use self::lifecycle::MT_LIFECYCLE_TRACING_TARGET; +pub(crate) use self::lifecycle::TRACING_TARGET_MATERIALIZER_LIFECYCLE; /// Stable processor name for SPL Token and ATA risk facts. pub(crate) use self::risk::MT_RISK_PROCESSOR_NAME; /// Stable projection contract version for SPL Token and ATA risk facts. pub(crate) use self::risk::MT_RISK_PROJECTION_VERSION; /// Canonical tracing target for SPL Token and ATA risk facts. -pub(crate) use self::risk::MT_RISK_TRACING_TARGET; +pub(crate) use self::risk::TRACING_TARGET_MATERIALIZER_RISK; /// Canonical tracing target for the native stake and vote materializer. -pub(crate) use self::staking::MT_STAKING_TRACING_TARGET; +pub(crate) use self::staking::TRACING_TARGET_MATERIALIZER_STAKING; /// Stable processor name for Token, ATA and Token-2022 account projections. pub(crate) use self::token::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME; /// Stable projection contract version for Token, ATA and Token-2022 account projections. pub(crate) use self::token::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION; /// Canonical tracing target for Token, ATA and Token-2022 account projections. -pub(crate) use self::token::MT_TOKEN_ACCOUNTS_TRACING_TARGET; +pub(crate) use self::token::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS; /// Maximum Memo payload accepted by transaction annotations. pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES; /// Stable processor name for transaction annotations. @@ -141,7 +141,7 @@ pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME; /// Stable projection contract version for transaction annotations. pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION; /// Canonical tracing target for transaction annotations. -pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET; +pub(crate) use self::transaction::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS; #[cfg(test)] mod tests { diff --git a/kb-lib/src/materializer/admin.rs b/kb-lib/src/materializer/admin.rs index 4cfc0b1..5e25f1b 100644 --- a/kb-lib/src/materializer/admin.rs +++ b/kb-lib/src/materializer/admin.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/admin.rs -// version: 5 +// version: 6 //! `admin` materializer family. @@ -16,4 +16,4 @@ pub use self::core::materializer_admin_materialize_token2022_state_snapshots; /// Canonical processor name for the native and SPL administration materializer. pub(crate) use self::constants::MT_ADMIN_PROCESSOR_NAME; /// Canonical tracing target for the native and SPL administration materializer. -pub(crate) use self::constants::MT_ADMIN_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_ADMIN; diff --git a/kb-lib/src/materializer/admin/constants.rs b/kb-lib/src/materializer/admin/constants.rs index e4e717e..aac246e 100644 --- a/kb-lib/src/materializer/admin/constants.rs +++ b/kb-lib/src/materializer/admin/constants.rs @@ -1,9 +1,9 @@ // file: kb-lib/src/materializer/admin/constants.rs -// version: 1 +// version: 2 //! Local constants for the native and SPL administration materializer. /// Canonical processor name for this component. pub(crate) const MT_ADMIN_PROCESSOR_NAME: &str = "solana_native_admin"; /// Canonical tracing target for this component. -pub(crate) const MT_ADMIN_TRACING_TARGET: &str = "kb-lib.materializer.admin"; +pub(crate) const TRACING_TARGET_MATERIALIZER_ADMIN: &str = "kb-lib.materializer.admin"; diff --git a/kb-lib/src/materializer/admin/core.rs b/kb-lib/src/materializer/admin/core.rs index 9e7db2a..e42126d 100644 --- a/kb-lib/src/materializer/admin/core.rs +++ b/kb-lib/src/materializer/admin/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/admin/core.rs -// version: 16 +// version: 17 //! Stable administrative projections derived from exact committed observations. @@ -155,7 +155,7 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer { std::option::Option::Some(value) => value, std::option::Option::None => { tracing::debug!( - target: crate::MT_ADMIN_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_ADMIN, action = "materialize_admin", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -173,7 +173,7 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer { } if observation.transaction_failed || !observation.observation_committed { tracing::debug!( - target: crate::MT_ADMIN_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_ADMIN, action = "materialize_admin", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -223,7 +223,7 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer { }), }; tracing::debug!( - target: crate::MT_ADMIN_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_ADMIN, action = "materialize_admin", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), diff --git a/kb-lib/src/materializer/compliance.rs b/kb-lib/src/materializer/compliance.rs index 0dc4850..7dd5772 100644 --- a/kb-lib/src/materializer/compliance.rs +++ b/kb-lib/src/materializer/compliance.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/compliance.rs -// version: 3 +// version: 4 //! `compliance` materializer family. @@ -10,4 +10,4 @@ mod constants; pub use self::audit::MtComplianceAuditMaterializer; /// Canonical tracing target for the native compliance audit materializer. -pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT; diff --git a/kb-lib/src/materializer/compliance/audit.rs b/kb-lib/src/materializer/compliance/audit.rs index 4da25d6..7aa881a 100644 --- a/kb-lib/src/materializer/compliance/audit.rs +++ b/kb-lib/src/materializer/compliance/audit.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/compliance/audit.rs -// version: 7 +// version: 8 //! Stable compliance-audit projections for committed native bytecode mutations and execution profiles. @@ -113,7 +113,7 @@ impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer { std::option::Option::Some(value) => value, std::option::Option::None => { tracing::debug!( - target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT, action = "materialize_compliance_audit", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -130,7 +130,7 @@ impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer { && (observation.transaction_failed || !observation.observation_committed) { tracing::debug!( - target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT, action = "materialize_compliance_audit", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -178,7 +178,7 @@ impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer { }), }; tracing::debug!( - target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT, action = "materialize_compliance_audit", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -225,7 +225,7 @@ fn materialize_compute_budget_profile( }), }; tracing::debug!( - target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT, action = "materialize_compute_budget_profile", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), diff --git a/kb-lib/src/materializer/compliance/constants.rs b/kb-lib/src/materializer/compliance/constants.rs index 21a5c9b..d24998e 100644 --- a/kb-lib/src/materializer/compliance/constants.rs +++ b/kb-lib/src/materializer/compliance/constants.rs @@ -1,7 +1,8 @@ // file: kb-lib/src/materializer/compliance/constants.rs -// version: 1 +// version: 2 //! Local constants for the native compliance audit materializer. /// Canonical tracing target for this component. -pub(crate) const MT_COMPLIANCE_AUDIT_TRACING_TARGET: &str = "kb-lib.materializer.compliance"; +pub(crate) const TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT: &str = + "kb-lib.materializer.compliance"; diff --git a/kb-lib/src/materializer/lifecycle.rs b/kb-lib/src/materializer/lifecycle.rs index c3e8379..cb18e30 100644 --- a/kb-lib/src/materializer/lifecycle.rs +++ b/kb-lib/src/materializer/lifecycle.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/lifecycle.rs -// version: 3 +// version: 4 //! `lifecycle` materializer family. @@ -10,4 +10,4 @@ mod core; pub use self::core::MtLifecycleMaterializer; /// Canonical tracing target for the native lifecycle materializer. -pub(crate) use self::constants::MT_LIFECYCLE_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_LIFECYCLE; diff --git a/kb-lib/src/materializer/lifecycle/constants.rs b/kb-lib/src/materializer/lifecycle/constants.rs index f4e63d0..36539a3 100644 --- a/kb-lib/src/materializer/lifecycle/constants.rs +++ b/kb-lib/src/materializer/lifecycle/constants.rs @@ -1,7 +1,7 @@ // file: kb-lib/src/materializer/lifecycle/constants.rs -// version: 1 +// version: 2 //! Local constants for the native lifecycle materializer. /// Canonical tracing target for this component. -pub(crate) const MT_LIFECYCLE_TRACING_TARGET: &str = "kb-lib.materializer.lifecycle"; +pub(crate) const TRACING_TARGET_MATERIALIZER_LIFECYCLE: &str = "kb-lib.materializer.lifecycle"; diff --git a/kb-lib/src/materializer/lifecycle/core.rs b/kb-lib/src/materializer/lifecycle/core.rs index 1fca2a8..bc2934d 100644 --- a/kb-lib/src/materializer/lifecycle/core.rs +++ b/kb-lib/src/materializer/lifecycle/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/lifecycle/core.rs -// version: 14 +// version: 15 //! Stable lifecycle projections derived from exact decoded observations. @@ -181,7 +181,7 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer { std::option::Option::Some(value) => value, std::option::Option::None => { tracing::debug!( - target: crate::MT_LIFECYCLE_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_LIFECYCLE, action = "materialize_lifecycle", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -193,7 +193,7 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer { }; if observation.transaction_failed || !observation.observation_committed { tracing::debug!( - target: crate::MT_LIFECYCLE_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_LIFECYCLE, action = "materialize_lifecycle", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -225,7 +225,7 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer { let projection_details = projection_details(projection, &accounts, ¶meters, operation.as_str()); tracing::debug!( - target: crate::MT_LIFECYCLE_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_LIFECYCLE, action = "materialize_lifecycle", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), diff --git a/kb-lib/src/materializer/risk.rs b/kb-lib/src/materializer/risk.rs index 6e20652..f96a07c 100644 --- a/kb-lib/src/materializer/risk.rs +++ b/kb-lib/src/materializer/risk.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/risk.rs -// version: 2 +// version: 3 //! `risk` materializer family. @@ -14,4 +14,4 @@ pub(crate) use self::constants::MT_RISK_PROCESSOR_NAME; /// Stable projection contract version for SPL Token and ATA risk facts. pub(crate) use self::constants::MT_RISK_PROJECTION_VERSION; /// Canonical tracing target for SPL Token and ATA risk facts. -pub(crate) use self::constants::MT_RISK_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_RISK; diff --git a/kb-lib/src/materializer/risk/constants.rs b/kb-lib/src/materializer/risk/constants.rs index d8ebb01..2e83152 100644 --- a/kb-lib/src/materializer/risk/constants.rs +++ b/kb-lib/src/materializer/risk/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/risk/constants.rs -// version: 1 +// version: 2 //! Local constants for fact-only risk projections. @@ -8,4 +8,4 @@ pub(crate) const MT_RISK_PROCESSOR_NAME: &str = "spl_token_risk"; /// Stable projection payload contract version. pub(crate) const MT_RISK_PROJECTION_VERSION: u32 = 2; /// Canonical tracing target for this component. -pub(crate) const MT_RISK_TRACING_TARGET: &str = "kb-lib.materializer.risk"; +pub(crate) const TRACING_TARGET_MATERIALIZER_RISK: &str = "kb-lib.materializer.risk"; diff --git a/kb-lib/src/materializer/risk/core.rs b/kb-lib/src/materializer/risk/core.rs index 8da7b4b..c87d93a 100644 --- a/kb-lib/src/materializer/risk/core.rs +++ b/kb-lib/src/materializer/risk/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/risk/core.rs -// version: 2 +// version: 3 //! Stable fact-only risk projections for committed SPL Token and ATA observations. @@ -157,7 +157,7 @@ impl crate::MtApiEventMaterializer for crate::MtRiskMaterializer { } }), }; - tracing::debug!(target: crate::MT_RISK_TRACING_TARGET, action = "materialize_token_risk_fact", risk_kind, operation, ata, "materialize committed SPL Token or ATA risk fact"); + tracing::debug!(target: crate::TRACING_TARGET_MATERIALIZER_RISK, action = "materialize_token_risk_fact", risk_kind, operation, ata, "materialize committed SPL Token or ATA risk fact"); return crate::MtApiMaterializerExecutionResult { status: crate::MtApiMaterializerOutcomeStatus::Inserted, outputs: std::vec![output], diff --git a/kb-lib/src/materializer/staking.rs b/kb-lib/src/materializer/staking.rs index fac1669..e0a55d2 100644 --- a/kb-lib/src/materializer/staking.rs +++ b/kb-lib/src/materializer/staking.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/staking.rs -// version: 3 +// version: 4 //! `staking` materializer family. @@ -10,4 +10,4 @@ mod core; pub use self::core::MtStakingMaterializer; /// Canonical tracing target for the native stake and vote materializer. -pub(crate) use self::constants::MT_STAKING_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_STAKING; diff --git a/kb-lib/src/materializer/staking/constants.rs b/kb-lib/src/materializer/staking/constants.rs index 5be4894..bd54a1e 100644 --- a/kb-lib/src/materializer/staking/constants.rs +++ b/kb-lib/src/materializer/staking/constants.rs @@ -1,7 +1,7 @@ // file: kb-lib/src/materializer/staking/constants.rs -// version: 1 +// version: 2 //! Local constants for the native stake and vote materializer. /// Canonical tracing target for this component. -pub(crate) const MT_STAKING_TRACING_TARGET: &str = "kb-lib.materializer.staking"; +pub(crate) const TRACING_TARGET_MATERIALIZER_STAKING: &str = "kb-lib.materializer.staking"; diff --git a/kb-lib/src/materializer/staking/core.rs b/kb-lib/src/materializer/staking/core.rs index 4ea45ec..ad50b7d 100644 --- a/kb-lib/src/materializer/staking/core.rs +++ b/kb-lib/src/materializer/staking/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/staking/core.rs -// version: 7 +// version: 8 //! Instruction-level staking projections for native Stake and Vote observations. @@ -162,7 +162,7 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer { std::option::Option::Some(value) => value, std::option::Option::None => { tracing::debug!( - target: crate::MT_STAKING_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_STAKING, action = "materialize_staking", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -177,7 +177,7 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer { } if observation.transaction_failed || !observation.observation_committed { tracing::debug!( - target: crate::MT_STAKING_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_STAKING, action = "materialize_staking", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), @@ -225,7 +225,7 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer { }), }; tracing::debug!( - target: crate::MT_STAKING_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_STAKING, action = "materialize_staking", surface_code = %observation.event.surface_code.0.as_str(), entry_code = %observation.event.event_name.0.as_str(), diff --git a/kb-lib/src/materializer/token.rs b/kb-lib/src/materializer/token.rs index 3b3a8a1..dc25882 100644 --- a/kb-lib/src/materializer/token.rs +++ b/kb-lib/src/materializer/token.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/token.rs -// version: 4 +// version: 5 //! `token` materializer family. @@ -19,4 +19,4 @@ pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME; /// Stable projection contract version for Token, ATA and Token-2022 account projections. pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION; /// Canonical tracing target for Token, ATA and Token-2022 account projections. -pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS; diff --git a/kb-lib/src/materializer/token/accounts.rs b/kb-lib/src/materializer/token/accounts.rs index a2b9a71..12f4397 100644 --- a/kb-lib/src/materializer/token/accounts.rs +++ b/kb-lib/src/materializer/token/accounts.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/token/accounts.rs -// version: 3 +// version: 4 //! Stable committed SPL Token mutation and ATA lifecycle projections. @@ -157,7 +157,7 @@ impl crate::MtApiEventMaterializer for crate::MtTokenAccountsMaterializer { }), }; tracing::debug!( - target: crate::MT_TOKEN_ACCOUNTS_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS, action = "materialize_token_account_mutation", signature = %observation.event.signature.0, instruction_path = %observation.event.instruction_path.0, @@ -285,7 +285,7 @@ fn materialize_ata( }), }; tracing::debug!( - target: crate::MT_TOKEN_ACCOUNTS_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS, action = "materialize_ata_lifecycle", signature = %observation.event.signature.0, instruction_path = %observation.event.instruction_path.0, diff --git a/kb-lib/src/materializer/token/constants.rs b/kb-lib/src/materializer/token/constants.rs index 495381c..0bb0686 100644 --- a/kb-lib/src/materializer/token/constants.rs +++ b/kb-lib/src/materializer/token/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/token/constants.rs -// version: 1 +// version: 2 //! Local constants for classic SPL Token account projections. @@ -8,4 +8,4 @@ pub(crate) const MT_TOKEN_ACCOUNTS_PROCESSOR_NAME: &str = "spl_token_accounts"; /// Stable projection payload contract version. pub(crate) const MT_TOKEN_ACCOUNTS_PROJECTION_VERSION: u32 = 3; /// Canonical tracing target for this component. -pub(crate) const MT_TOKEN_ACCOUNTS_TRACING_TARGET: &str = "kb-lib.materializer.token"; +pub(crate) const TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS: &str = "kb-lib.materializer.token"; diff --git a/kb-lib/src/materializer/transaction.rs b/kb-lib/src/materializer/transaction.rs index 6356de4..9933d0b 100644 --- a/kb-lib/src/materializer/transaction.rs +++ b/kb-lib/src/materializer/transaction.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/transaction.rs -// version: 2 +// version: 3 //! `transaction` materializer family. @@ -16,4 +16,4 @@ pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME; /// Stable projection contract version for transaction annotations. pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION; /// Canonical tracing target for transaction annotations. -pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET; +pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS; diff --git a/kb-lib/src/materializer/transaction/annotations.rs b/kb-lib/src/materializer/transaction/annotations.rs index 8c82394..29d31d7 100644 --- a/kb-lib/src/materializer/transaction/annotations.rs +++ b/kb-lib/src/materializer/transaction/annotations.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/transaction/annotations.rs -// version: 3 +// version: 4 //! Exact committed SPL Memo transaction annotation projection. @@ -179,7 +179,7 @@ impl crate::MtApiEventMaterializer for crate::MtTransactionAnnotationMaterialize }), }; tracing::debug!( - target: crate::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET, + target: crate::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS, action = "materialize_transaction_annotation", signature = %observation.event.signature.0, instruction_path = %observation.event.instruction_path.0, diff --git a/kb-lib/src/materializer/transaction/constants.rs b/kb-lib/src/materializer/transaction/constants.rs index cd8db32..92bb2b9 100644 --- a/kb-lib/src/materializer/transaction/constants.rs +++ b/kb-lib/src/materializer/transaction/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/materializer/transaction/constants.rs -// version: 1 +// version: 2 //! Local constants for the transaction annotation materializer. @@ -10,5 +10,5 @@ pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME: &str = "transaction_ /// Stable projection contract version. pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION: u32 = 1; /// Canonical tracing target for this component. -pub(crate) const MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET: &str = +pub(crate) const TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS: &str = "kb-lib.materializer.transaction"; diff --git a/kb-logging/Cargo.toml b/kb-logging/Cargo.toml index 6ecf759..0413564 100644 --- a/kb-logging/Cargo.toml +++ b/kb-logging/Cargo.toml @@ -1,5 +1,5 @@ # file: kb-logging/Cargo.toml -# version: 1 +# version: 2 [package] name = "kb-logging" @@ -8,5 +8,11 @@ edition.workspace = true license.workspace = true publish.workspace = true +[dependencies] +kb-core = { path = "../kb-core" } +tracing.workspace = true +tracing-appender.workspace = true +tracing-subscriber.workspace = true + [lints] workspace = true diff --git a/kb-logging/README.md b/kb-logging/README.md new file mode 100644 index 0000000..f26e32a --- /dev/null +++ b/kb-logging/README.md @@ -0,0 +1,38 @@ + + + +# kb-logging + +`kb-logging` initialise le routage `tracing` commun du workspace. + +## Responsabilité + +Le crate transforme la configuration du profil actif en layers `tracing_subscriber` et route les événements vers la console, les fichiers humains et les fichiers JSONL. Le `LoggingGuard` retourné par `init_logging` doit rester vivant pendant toute la durée du processus afin de conserver les writers non bloquants. + +Les décisions de niveau et de target sont appliquées par chaque writer de route, avec un seul filtre d’admission agrégé en amont. Elles ne créent pas un `Filtered` layer par sortie : `tracing-subscriber` réserve seulement 64 identifiants de filtres par subscriber, tandis que la matrice complète comporte désormais plus de 64 routes activables. Le filtrage writer conserve les niveaux, targets exacts, préfixes et overrides configurés sans imposer cette limite au nombre de sorties ; le filtre agrégé évite de formater un événement qu’aucune route n’accepte. + +Les chemins relatifs sont résolus depuis la racine du workspace. L’ANSI est désactivé dans les formatters fichier et retiré une seconde fois par `StripAnsiMakeWriter` afin de nettoyer les messages provenant d’une WebView ou d’une dépendance externe. + +## Contrat de targets + +`kb-logging` utilise le target canonique `kb-logging`, défini dans `src/constants.rs`. Chaque crate qui dépend de `tracing` doit suivre le même contrat avec son propre nom Cargo. + +Le test `every_tracing_crate_has_one_canonical_target_constant` inspecte les manifests du workspace et vérifie qu’une crate déclarant `tracing.workspace = true` possède exactement une constante conforme. + +## Matrice de fichiers + +Chaque profil actif configure : + +```text +logs//debug.log +logs//info.log +logs//error.jsonl +logs//app.log +logs///debug.log +logs///info.log +logs///error.jsonl +``` + +Les fichiers sont en rotation quotidienne. Les routes `debug` et `info` sont cumulatives ; les routes `error.jsonl` sont forcées au niveau `error` et n’ajoutent pas les overrides verbeux des dépendances. + +Le contrat détaillé se trouve dans `docs/LOGGING.md` et `docs/TRACING_CONTRACT.md`. diff --git a/kb-logging/src/config.rs b/kb-logging/src/config.rs new file mode 100644 index 0000000..fc68d21 --- /dev/null +++ b/kb-logging/src/config.rs @@ -0,0 +1,58 @@ +// file: kb-logging/src/config.rs +// version: 4 + +//! Logging configuration data structures consumed by the tracing runtime. + +/// Legacy file route shape kept for early callers that only need human file logs. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LogFileRoute { + /// Output file path. + pub file: std::string::String, + /// Logging level filter. + pub level: std::string::String, + /// Target filters handled by this route. + pub targets: std::vec::Vec, +} + +/// One target-level filter shared by routes that include the wildcard target. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LogTargetFilterConfig { + /// Tracing target or crate prefix. + pub target: std::string::String, + /// Minimum level assigned to this target. + pub level: std::string::String, +} + +/// One logging output route. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LogTargetConfig { + /// Route name used for diagnostics. + pub name: std::string::String, + /// Enables this route. + pub enabled: bool, + /// Sink kind: `console` or `file`. + pub sink: std::string::String, + /// Minimum level for this route. + pub level: std::string::String, + /// File path for file sinks, empty for console sinks. + pub path: std::string::String, + /// Rotation mode: `none`, `never`, `daily`, or `hourly`. + pub rotation: std::string::String, + /// Format kind: `human`, `compact`, `pretty`, or `json`. + pub format: std::string::String, + /// Enables ANSI formatting for this route. + pub ansi: bool, + /// Included tracing targets or wildcard target globs. + pub targets: std::vec::Vec, +} + +/// Logging configuration consumed by `kb-logging` before profile binding exists. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LoggingConfig { + /// Default log level used when a route does not provide target directives. + pub default_level: std::string::String, + /// Output routes. + pub targets: std::vec::Vec, + /// Target-specific overrides appended to wildcard routes. + pub target_filters: std::vec::Vec, +} diff --git a/kb-logging/src/constants.rs b/kb-logging/src/constants.rs index f122210..057325a 100644 --- a/kb-logging/src/constants.rs +++ b/kb-logging/src/constants.rs @@ -1,5 +1,5 @@ // file: kb-logging/src/constants.rs -// version: 1 +// version: 2 /// Canonical tracing target for this crate. pub(crate) const TRACING_TARGET: &str = "kb-logging"; diff --git a/kb-logging/src/lib.rs b/kb-logging/src/lib.rs index 90207bc..8fba06c 100644 --- a/kb-logging/src/lib.rs +++ b/kb-logging/src/lib.rs @@ -1,17 +1,30 @@ // file: kb-logging/src/lib.rs -// version: 1 +// version: 6 -#![forbid(unsafe_code)] -#![deny(unreachable_pub)] +//! Logging and tracing initialization for applications and workers. #![warn(missing_docs)] +#![deny(unreachable_pub)] +#![forbid(unsafe_code)] -//! Logging contracts for Khadhroony Bot3. - +mod config; mod constants; +mod tracing_runtime; /// Canonical tracing target for this crate. pub(crate) use self::constants::TRACING_TARGET; +/// Exposes the legacy file route shape kept for early file-only callers. +pub use self::config::LogFileRoute; +/// Exposes one configured logging output target. +pub use self::config::LogTargetConfig; +/// Exposes one configured tracing target filter. +pub use self::config::LogTargetFilterConfig; +/// Exposes the logging configuration consumed by this crate. +pub use self::config::LoggingConfig; +/// Exposes the guard that keeps non-blocking logging workers alive. +pub use self::tracing_runtime::LoggingGuard; +/// Exposes initialization from a raw logging configuration section. +pub use self::tracing_runtime::init_logging; /// Returns the canonical tracing target. pub fn tracing_target() -> &'static str { return crate::TRACING_TARGET; diff --git a/kb-logging/src/tracing_runtime.rs b/kb-logging/src/tracing_runtime.rs new file mode 100644 index 0000000..ed755b5 --- /dev/null +++ b/kb-logging/src/tracing_runtime.rs @@ -0,0 +1,1120 @@ +// file: kb-logging/src/tracing_runtime.rs +// version: 14 + +//! Runtime initialization helpers for `tracing_subscriber`. + +use tracing_subscriber::Layer; // rust-rules: trait-import +use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import +use tracing_subscriber::util::SubscriberInitExt; // rust-rules: trait-import + +/// Guard keeping non-blocking logging workers alive. +#[derive(Debug)] +pub struct LoggingGuard { + guards: std::vec::Vec, + route_names: std::vec::Vec, +} + +impl crate::LoggingGuard { + /// Returns the number of active non-blocking writer guards. + pub fn guard_count(&self) -> usize { + return self.guards.len(); + } + + /// Returns the number of enabled logging routes installed at startup. + pub fn route_count(&self) -> usize { + return self.route_names.len(); + } + + /// Returns the configured names of the enabled routes installed at startup. + pub fn route_names(&self) -> &[std::string::String] { + return &self.route_names; + } +} + +type BoxedLayer = std::boxed::Box< + dyn tracing_subscriber::Layer + + std::marker::Send + + std::marker::Sync, +>; + +/// Initializes global tracing from a logging configuration. +pub fn init_logging(config: &crate::LoggingConfig) -> kb_core::Result { + let mut guards = std::vec::Vec::::new(); + let mut route_names = std::vec::Vec::::new(); + let layers_result = build_enabled_layers(config, &mut guards, &mut route_names); + let layers = match layers_result { + std::result::Result::Ok(layers) => layers, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if layers.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "logging_no_enabled_routes", + "at least one logging target must be enabled", + )); + } + let admission_filters_result = build_enabled_route_writer_filters(config); + let admission_filters = match admission_filters_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let admission_filter = tracing_subscriber::filter::filter_fn(move |metadata| { + return admission_filters.iter().any(|filter| return filter.allows(metadata)); + }); + let init_result = tracing_subscriber::registry() + .with(layers.with_filter(admission_filter)) + .try_init(); + return match init_result { + std::result::Result::Ok(()) => { + tracing::debug!( + target: crate::TRACING_TARGET, + action = "initialize", + route_count = route_names.len(), + route_names = ?route_names, + "tracing subscriber initialized" + ); + std::result::Result::Ok(crate::LoggingGuard { guards, route_names }) + }, + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "logging_subscriber_init_failed", + format!("cannot initialize tracing subscriber: {error}"), + )), + }; +} + +fn build_enabled_layers( + config: &crate::LoggingConfig, + guards: &mut std::vec::Vec, + route_names: &mut std::vec::Vec, +) -> kb_core::Result> { + let mut layers = std::vec::Vec::::new(); + let enabled_routes = enabled_routes(config); + for route in enabled_routes { + let filter = match build_route_writer_filter(route, config) { + std::result::Result::Ok(filter) => filter, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if route.sink == "console" { + let (writer, guard) = tracing_appender::non_blocking(std::io::stdout()); + let layer_result = build_console_layer(route, filter, writer); + let layer = match layer_result { + std::result::Result::Ok(layer) => layer, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + guards.push(guard); + route_names.push(route.name.clone()); + layers.push(layer); + continue; + } + if route.sink == "file" { + let writer_result = build_file_writer(route); + let (writer, guard) = match writer_result { + std::result::Result::Ok(parts) => parts, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let layer_result = build_file_layer(route, filter, writer); + let layer = match layer_result { + std::result::Result::Ok(layer) => layer, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + guards.push(guard); + route_names.push(route.name.clone()); + layers.push(layer); + continue; + } + return std::result::Result::Err(kb_core::Error::new( + "logging_unknown_sink", + format!("unknown logging sink '{}' for route '{}'", route.sink, route.name), + )); + } + return std::result::Result::Ok(layers); +} + +fn enabled_routes(config: &crate::LoggingConfig) -> std::vec::Vec<&crate::LogTargetConfig> { + let mut routes = std::vec::Vec::<&crate::LogTargetConfig>::new(); + for route in &config.targets { + if route.enabled { + routes.push(route); + } + } + return routes; +} + +fn build_enabled_route_writer_filters( + config: &crate::LoggingConfig, +) -> kb_core::Result> { + let mut filters = std::vec::Vec::new(); + for route in enabled_routes(config) { + let result = build_route_writer_filter(route, config); + match result { + std::result::Result::Ok(value) => filters.push(value), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(filters); +} + +fn build_console_layer( + route: &crate::LogTargetConfig, + filter: RouteWriterFilter, + writer: tracing_appender::non_blocking::NonBlocking, +) -> kb_core::Result { + let route_writer = RouteMakeWriter::new(writer, filter); + return match route.format.as_str() { + "compact" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .compact() + .with_timer(timer()) + .with_ansi(route.ansi) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "pretty" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .pretty() + .with_timer(timer()) + .with_ansi(route.ansi) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "json" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .json() + .with_timer(timer()) + .with_ansi(false) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "human" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .with_timer(timer()) + .with_ansi(route.ansi) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + _ => std::result::Result::Err(kb_core::Error::new( + "logging_unknown_format", + format!("unknown logging format '{}' for route '{}'", route.format, route.name), + )), + }; +} + +fn build_file_layer( + route: &crate::LogTargetConfig, + filter: RouteWriterFilter, + writer: StripAnsiMakeWriter, +) -> kb_core::Result { + let route_writer = RouteMakeWriter::new(writer, filter); + return match route.format.as_str() { + "compact" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .compact() + .with_timer(timer()) + .with_ansi(false) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "pretty" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .pretty() + .with_timer(timer()) + .with_ansi(false) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "json" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .json() + .with_timer(timer()) + .with_ansi(false) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + "human" => std::result::Result::Ok( + tracing_subscriber::fmt::layer() + .with_timer(timer()) + .with_ansi(false) + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_writer(route_writer) + .boxed(), + ), + _ => std::result::Result::Err(kb_core::Error::new( + "logging_unknown_format", + format!("unknown logging format '{}' for route '{}'", route.format, route.name), + )), + }; +} + +fn timer() -> tracing_subscriber::fmt::time::ChronoLocal { + return tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%dT%H:%M:%S%.3f%:z".to_string()); +} + +fn is_error_route(route: &crate::LogTargetConfig) -> bool { + let name = route.name.to_ascii_lowercase(); + return route.level == "error" || name.contains("error"); +} + +#[derive(Clone, Debug)] +struct RouteWriterFilter { + default_level: std::option::Option, + target_levels: std::vec::Vec<(std::string::String, u8)>, +} + +impl RouteWriterFilter { + fn allows(&self, metadata: &tracing::Metadata<'_>) -> bool { + return self.allows_target_level(metadata.target(), metadata.level()); + } + + fn allows_target_level(&self, target: &str, level: &tracing::Level) -> bool { + let mut selected_level = self.default_level; + let mut selected_target_length = 0_usize; + for (configured_target, configured_level) in &self.target_levels { + if target_matches(configured_target, target) + && configured_target.len() >= selected_target_length + { + selected_level = std::option::Option::Some(*configured_level); + selected_target_length = configured_target.len(); + } + } + return match selected_level { + std::option::Option::Some(maximum) => tracing_level_rank(level) <= maximum, + std::option::Option::None => false, + }; + } +} + +fn build_route_writer_filter( + route: &crate::LogTargetConfig, + config: &crate::LoggingConfig, +) -> kb_core::Result { + let route_level_text = if is_error_route(route) { "error" } else { route.level.as_str() }; + let route_level_result = parse_level_rank(route_level_text); + let route_level = match route_level_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut default_level = std::option::Option::None; + let mut target_levels = std::vec::Vec::new(); + for target in &route.targets { + match normalize_target_filter(target) { + std::option::Option::Some(normalized) => { + target_levels.push((normalized, route_level)); + }, + std::option::Option::None => default_level = std::option::Option::Some(route_level), + } + } + if route.targets.is_empty() { + default_level = std::option::Option::Some(route_level); + } + if default_level.is_some() && !is_error_route(route) { + for filter in &config.target_filters { + let normalized = match normalize_target_filter(&filter.target) { + std::option::Option::Some(value) => value, + std::option::Option::None => continue, + }; + let level_result = parse_level_rank(&filter.level); + let level = match level_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + target_levels.push((normalized, level)); + } + } + return std::result::Result::Ok(RouteWriterFilter { default_level, target_levels }); +} + +fn parse_level_rank(level: &str) -> kb_core::Result { + return match level.trim().to_ascii_lowercase().as_str() { + "error" => std::result::Result::Ok(1), + "warn" => std::result::Result::Ok(2), + "info" => std::result::Result::Ok(3), + "debug" => std::result::Result::Ok(4), + "trace" => std::result::Result::Ok(5), + _ => std::result::Result::Err(kb_core::Error::new( + "logging_filter_build_failed", + format!("unsupported tracing level '{level}'"), + )), + }; +} + +fn tracing_level_rank(level: &tracing::Level) -> u8 { + if *level == tracing::Level::ERROR { + return 1; + } + if *level == tracing::Level::WARN { + return 2; + } + if *level == tracing::Level::INFO { + return 3; + } + if *level == tracing::Level::DEBUG { + return 4; + } + return 5; +} + +fn target_matches(configured: &str, observed: &str) -> bool { + return observed == configured + || observed + .strip_prefix(configured) + .is_some_and(|suffix| return suffix.starts_with("::")); +} + +#[cfg(test)] +fn build_route_filter_expression( + route: &crate::LogTargetConfig, + config: &crate::LoggingConfig, +) -> std::string::String { + let mut directives = std::vec::Vec::::new(); + let mut has_wildcard = false; + let route_level = if is_error_route(route) { "error" } else { route.level.as_str() }; + for target in &route.targets { + let normalized_option = normalize_target_filter(target); + match normalized_option { + std::option::Option::Some(normalized) => { + directives.push(format!("{}={}", normalized, route_level)) + }, + std::option::Option::None => has_wildcard = true, + } + } + if route.targets.is_empty() || has_wildcard { + directives.push(route_level.to_string()); + } + if (route.targets.is_empty() || has_wildcard) && !is_error_route(route) { + for filter in &config.target_filters { + let normalized_option = normalize_target_filter(&filter.target); + if let std::option::Option::Some(normalized) = normalized_option { + directives.push(format!("{}={}", normalized, filter.level)); + } + } + } + if directives.is_empty() { + directives.push(config.default_level.clone()); + } + return directives.join(","); +} + +fn normalize_target_filter(target: &str) -> std::option::Option { + let trimmed = target.trim(); + if trimmed.is_empty() || trimmed == "*" { + return std::option::Option::None; + } + if trimmed.ends_with(".*") { + let shortened = trimmed.trim_end_matches(".*"); + if shortened.is_empty() { + return std::option::Option::None; + } + return std::option::Option::Some(shortened.to_string()); + } + if trimmed.ends_with("::*") { + let shortened = trimmed.trim_end_matches("::*"); + if shortened.is_empty() { + return std::option::Option::None; + } + return std::option::Option::Some(shortened.to_string()); + } + return std::option::Option::Some(trimmed.to_string()); +} + +fn build_file_writer( + route: &crate::LogTargetConfig, +) -> kb_core::Result<( + StripAnsiMakeWriter, + tracing_appender::non_blocking::WorkerGuard, +)> { + let path = resolve_workspace_relative_path(&route.path); + let parent = match path.parent() { + std::option::Option::Some(parent) => parent.to_path_buf(), + std::option::Option::None => workspace_root_dir(), + }; + let create_result = std::fs::create_dir_all(&parent); + if let std::result::Result::Err(error) = create_result { + return std::result::Result::Err(kb_core::Error::new( + "logging_directory_create_failed", + format!("cannot create logging directory '{}': {error}", parent.display()), + )); + } + let rotation = if route.rotation == "hourly" { + tracing_appender::rolling::Rotation::HOURLY + } else if route.rotation == "none" || route.rotation == "never" { + tracing_appender::rolling::Rotation::NEVER + } else { + tracing_appender::rolling::Rotation::DAILY + }; + let file_name = match path.file_name() { + std::option::Option::Some(value) => match value.to_str() { + std::option::Option::Some(value) => value.to_string(), + std::option::Option::None => route.name.clone(), + }, + std::option::Option::None => route.name.clone(), + }; + let (prefix, suffix) = split_file_name(&file_name); + let appender_result = tracing_appender::rolling::Builder::default() + .rotation(rotation) + .filename_prefix(prefix) + .filename_suffix(suffix) + .build(&parent); + let appender = match appender_result { + std::result::Result::Ok(appender) => appender, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "logging_appender_build_failed", + format!("cannot build rolling logging appender in '{}': {error}", parent.display()), + )); + }, + }; + let (writer, guard) = tracing_appender::non_blocking(appender); + return std::result::Result::Ok((StripAnsiMakeWriter::new(writer), guard)); +} + +fn split_file_name(file_name: &str) -> (std::string::String, std::string::String) { + let path = std::path::Path::new(file_name); + let stem = match path.file_stem() { + std::option::Option::Some(value) => match value.to_str() { + std::option::Option::Some(value) => value.to_string(), + std::option::Option::None => file_name.to_string(), + }, + std::option::Option::None => file_name.to_string(), + }; + let suffix = match path.extension() { + std::option::Option::Some(value) => match value.to_str() { + std::option::Option::Some(value) => value.to_string(), + std::option::Option::None => "log".to_string(), + }, + std::option::Option::None => "log".to_string(), + }; + return (stem, suffix); +} + +fn workspace_root_dir() -> std::path::PathBuf { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + return match manifest_dir.parent() { + std::option::Option::Some(parent) => parent.to_path_buf(), + std::option::Option::None => manifest_dir, + }; +} + +fn resolve_workspace_relative_path(path: &str) -> std::path::PathBuf { + let input_path = std::path::PathBuf::from(path); + if input_path.is_absolute() { + return input_path; + } + return workspace_root_dir().join(input_path); +} + +struct RouteMakeWriter { + inner: W, + filter: RouteWriterFilter, +} + +impl RouteMakeWriter { + fn new(inner: W, filter: RouteWriterFilter) -> Self { + return Self { inner, filter }; + } +} + +enum RouteWriter { + Enabled(W), + Disabled(std::io::Sink), +} + +impl<'a, W> tracing_subscriber::fmt::MakeWriter<'a> for RouteMakeWriter +where + W: tracing_subscriber::fmt::MakeWriter<'a>, +{ + type Writer = RouteWriter; + + fn make_writer(&'a self) -> Self::Writer { + return RouteWriter::Disabled(std::io::sink()); + } + + fn make_writer_for(&'a self, metadata: &tracing::Metadata<'_>) -> Self::Writer { + if self.filter.allows(metadata) { + return RouteWriter::Enabled(self.inner.make_writer_for(metadata)); + } + return RouteWriter::Disabled(std::io::sink()); + } +} + +impl std::io::Write for RouteWriter +where + W: std::io::Write, +{ + fn write(&mut self, buf: &[u8]) -> std::io::Result { + return match self { + Self::Enabled(writer) => writer.write(buf), + Self::Disabled(writer) => writer.write(buf), + }; + } + + fn flush(&mut self) -> std::io::Result<()> { + return match self { + Self::Enabled(writer) => writer.flush(), + Self::Disabled(writer) => writer.flush(), + }; + } +} + +struct StripAnsiMakeWriter { + inner: W, +} + +impl StripAnsiMakeWriter { + fn new(inner: W) -> Self { + return Self { inner }; + } +} + +struct StripAnsiWriter { + inner: W, +} + +impl<'a, W> tracing_subscriber::fmt::MakeWriter<'a> for StripAnsiMakeWriter +where + W: tracing_subscriber::fmt::MakeWriter<'a>, +{ + type Writer = StripAnsiWriter; + + fn make_writer(&'a self) -> Self::Writer { + return StripAnsiWriter { inner: self.inner.make_writer() }; + } +} + +impl std::io::Write for StripAnsiWriter +where + W: std::io::Write, +{ + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let stripped = strip_ansi_bytes(buf); + let write_result = self.inner.write_all(&stripped); + return match write_result { + std::result::Result::Ok(()) => std::result::Result::Ok(buf.len()), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; + } + + fn flush(&mut self) -> std::io::Result<()> { + return self.inner.flush(); + } +} + +fn strip_ansi_bytes(input: &[u8]) -> std::vec::Vec { + let mut output = std::vec::Vec::with_capacity(input.len()); + let mut index = 0usize; + while index < input.len() { + let byte = input[index]; + if byte == 0x1B && index + 1 < input.len() && input[index + 1] == b'[' { + index += 2; + while index < input.len() { + let current = input[index]; + if (0x40..=0x7E).contains(¤t) { + index += 1; + break; + } + index += 1; + } + continue; + } + output.push(byte); + index += 1; + } + return output; +} + +#[cfg(test)] +mod tests { + use tracing_subscriber::Layer; // rust-rules: trait-import + use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import + + #[derive(Clone, Debug)] + struct TestMakeWriter { + output: std::sync::Arc>>, + } + + #[derive(Debug)] + struct TestWriter { + output: std::sync::Arc>>, + } + + impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for TestMakeWriter { + type Writer = TestWriter; + + fn make_writer(&'a self) -> Self::Writer { + return TestWriter { output: self.output.clone() }; + } + } + + impl std::io::Write for TestWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let lock_result = self.output.lock(); + let mut output = match lock_result { + std::result::Result::Ok(output) => output, + std::result::Result::Err(_) => { + return std::result::Result::Err(std::io::Error::other("poisoned test writer")); + }, + }; + output.extend_from_slice(buf); + return std::result::Result::Ok(buf.len()); + } + + fn flush(&mut self) -> std::io::Result<()> { + return std::result::Result::Ok(()); + } + } + + fn test_route( + name: &str, + sink: &str, + level: &str, + format: &str, + enabled: bool, + targets: std::vec::Vec, + ) -> crate::LogTargetConfig { + return crate::LogTargetConfig { + name: name.to_string(), + enabled, + sink: sink.to_string(), + level: level.to_string(), + path: format!("logs/{name}.log"), + rotation: "daily".to_string(), + format: format.to_string(), + ansi: false, + targets, + }; + } + + fn sample_logging_config() -> crate::LoggingConfig { + return crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![test_route( + "console_main", + "console", + "debug", + "human", + true, + std::vec!["kb_rpc.*".to_string()] + )], + target_filters: std::vec![crate::LogTargetFilterConfig { + target: "hyper".to_string(), + level: "warn".to_string() + }], + }; + } + + fn package_name(cargo_toml: &str) -> std::option::Option { + let mut in_package = false; + for line in cargo_toml.lines() { + let trimmed = line.trim(); + if trimmed == "[package]" { + in_package = true; + continue; + } + if in_package && trimmed.starts_with('[') { + return std::option::Option::None; + } + if in_package && trimmed.starts_with("name = ") { + let value = trimmed.trim_start_matches("name = ").trim_matches('"'); + return std::option::Option::Some(value.to_string()); + } + } + return std::option::Option::None; + } + + fn count_tracing_target_constants(path: &std::path::Path) -> usize { + let read_result = std::fs::read_dir(path); + let entries = match read_result { + std::result::Result::Ok(entries) => entries, + std::result::Result::Err(_) => return 0, + }; + let mut count = 0_usize; + for entry_result in entries { + let entry = match entry_result { + std::result::Result::Ok(entry) => entry, + std::result::Result::Err(_) => continue, + }; + let entry_path = entry.path(); + if entry_path.is_dir() { + count += count_tracing_target_constants(&entry_path); + continue; + } + if entry_path.extension().and_then(std::ffi::OsStr::to_str) + != std::option::Option::Some("rs") + { + continue; + } + let content_result = std::fs::read_to_string(&entry_path); + let content = match content_result { + std::result::Result::Ok(content) => content, + std::result::Result::Err(_) => continue, + }; + count += content + .lines() + .filter(|line| { + let trimmed = line.trim(); + return trimmed.starts_with("pub(crate) const ") + && trimmed.contains("TRACING_TARGET") + && trimmed.contains(": &str"); + }) + .count(); + } + return count; + } + + #[test] + fn every_tracing_crate_exposes_canonical_targets() { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = match manifest_dir.parent() { + std::option::Option::Some(path) => path, + std::option::Option::None => panic!("workspace root must exist"), + }; + let read_result = std::fs::read_dir(workspace_root); + let entries = match read_result { + std::result::Result::Ok(entries) => entries, + std::result::Result::Err(error) => panic!("workspace must be readable: {error}"), + }; + let mut tracing_crate_count = 0_usize; + for entry_result in entries { + let entry = match entry_result { + std::result::Result::Ok(entry) => entry, + std::result::Result::Err(error) => { + panic!("workspace entry must be readable: {error}") + }, + }; + let crate_path = entry.path(); + if !crate_path.is_dir() { + continue; + } + let cargo_path = crate_path.join("Cargo.toml"); + if !cargo_path.is_file() { + continue; + } + let cargo_result = std::fs::read_to_string(&cargo_path); + let cargo_toml = match cargo_result { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => panic!("Cargo.toml must be readable: {error}"), + }; + if !cargo_toml.lines().any(|line| return line.trim() == "tracing.workspace = true") { + continue; + } + tracing_crate_count += 1; + let package_name = match package_name(&cargo_toml) { + std::option::Option::Some(name) => name, + std::option::Option::None => panic!("tracing crate must declare a package name"), + }; + let target_count = count_tracing_target_constants(&crate_path.join("src")); + assert!( + target_count > 0, + "tracing crate {package_name} must declare at least one canonical target constant" + ); + let constants_path = crate_path.join("src/constants.rs"); + if constants_path.is_file() { + let constants_result = std::fs::read_to_string(&constants_path); + let constants = match constants_result { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + panic!("tracing crate {package_name} constants must be readable: {error}") + }, + }; + let expected = + format!("pub(crate) const TRACING_TARGET: &str = \"{package_name}\";"); + assert!(constants.contains(&expected), "invalid root target for {package_name}"); + assert_eq!( + target_count, 1, + "tracing crate {package_name} with a root target must declare exactly one target constant" + ); + } + } + assert!(tracing_crate_count > 0); + } + + #[test] + fn route_filter_converts_wildcard_prefix() { + let config = sample_logging_config(); + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "kb_rpc=debug"); + } + + #[test] + fn route_filter_converts_rust_wildcard_prefix() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec!["kb_rpc::*".to_string()]; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "kb_rpc=debug"); + } + + #[test] + fn route_filter_keeps_exact_target() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec!["kb_rpc::client".to_string()]; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "kb_rpc::client=debug"); + } + + #[test] + fn route_filter_adds_target_filters_for_wildcard_routes() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec!["*".to_string()]; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "debug,hyper=warn"); + } + + #[test] + fn route_filter_uses_route_level_for_empty_targets() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec![]; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "debug,hyper=warn"); + } + + #[test] + fn target_filters_accept_rust_wildcard_prefix() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec!["*".to_string()]; + config.target_filters = std::vec![crate::LogTargetFilterConfig { + target: "kb_store_pg::*".to_string(), + level: "trace".to_string() + }]; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "debug,kb_store_pg=trace"); + } + + #[test] + fn route_writer_filter_preserves_target_and_level_semantics() { + let mut config = sample_logging_config(); + config.targets[0].targets = std::vec!["*".to_string()]; + let result = super::build_route_writer_filter(&config.targets[0], &config); + let filter = match result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("route writer filter failed: {error}"), + }; + assert!(filter.allows_target_level("kb_rpc", &tracing::Level::DEBUG)); + assert!(!filter.allows_target_level("kb_rpc", &tracing::Level::TRACE)); + assert!(filter.allows_target_level("hyper", &tracing::Level::WARN)); + assert!(!filter.allows_target_level("hyper", &tracing::Level::INFO)); + } + + #[test] + fn more_than_64_routes_compose_without_filtered_layer_ids() { + let mut routes = std::vec::Vec::new(); + for index in 0..65_u32 { + let route_name = format!("console_{index}"); + routes.push(test_route( + route_name.as_str(), + "console", + "info", + "human", + true, + std::vec![format!("target_{index}")], + )); + } + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: routes, + target_filters: std::vec::Vec::new(), + }; + let mut guards = std::vec::Vec::new(); + let mut route_names = std::vec::Vec::new(); + let result = super::build_enabled_layers(&config, &mut guards, &mut route_names); + let layers = match result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("layer construction failed: {error}"), + }; + assert_eq!(layers.len(), 65); + assert_eq!(route_names.len(), 65); + let filter_result = super::build_enabled_route_writer_filters(&config); + let filters = match filter_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("admission filters failed: {error}"), + }; + let admission_filter = tracing_subscriber::filter::filter_fn(move |metadata| { + return filters.iter().any(|filter| return filter.allows(metadata)); + }); + let _subscriber = tracing_subscriber::registry().with(layers.with_filter(admission_filter)); + } + + #[test] + fn error_route_forces_error_level() { + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![test_route( + "file_errors", + "file", + "debug", + "human", + true, + std::vec!["*".to_string()] + )], + target_filters: std::vec![], + }; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "error"); + } + + #[test] + fn error_route_does_not_append_target_filters() { + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![test_route( + "file_errors", + "file", + "debug", + "human", + true, + std::vec!["*".to_string()] + )], + target_filters: std::vec![crate::LogTargetFilterConfig { + target: "hyper".to_string(), + level: "warn".to_string() + }], + }; + let expression = super::build_route_filter_expression(&config.targets[0], &config); + assert_eq!(expression, "error"); + } + + #[test] + fn disabled_routes_are_not_selected() { + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![ + test_route( + "console_main", + "console", + "info", + "human", + false, + std::vec!["*".to_string()] + ), + test_route( + "file_human", + "file", + "info", + "human", + false, + std::vec!["*".to_string()] + ) + ], + target_filters: std::vec![], + }; + let routes = super::enabled_routes(&config); + assert!(routes.is_empty()); + } + + #[test] + fn enabled_routes_keep_all_configured_outputs() { + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![ + test_route( + "console_main", + "console", + "debug", + "compact", + true, + std::vec!["*".to_string()] + ), + test_route( + "file_kb_core", + "file", + "debug", + "human", + true, + std::vec!["kb_core::*".to_string()] + ), + test_route( + "file_kb_config", + "file", + "debug", + "human", + true, + std::vec!["kb_config::*".to_string()] + ), + test_route( + "file_errors", + "file", + "debug", + "json", + true, + std::vec!["*".to_string()] + ) + ], + target_filters: std::vec![], + }; + let routes = super::enabled_routes(&config); + let route_names = routes + .iter() + .map(|route| return route.name.as_str()) + .collect::>(); + assert_eq!( + route_names, + std::vec!["console_main", "file_kb_core", "file_kb_config", "file_errors"] + ); + } + + #[test] + fn init_logging_rejects_configuration_without_enabled_routes() { + let config = crate::LoggingConfig { + default_level: "info".to_string(), + targets: std::vec![test_route( + "console_main", + "console", + "info", + "human", + false, + std::vec!["*".to_string()] + )], + target_filters: std::vec![], + }; + let result = super::init_logging(&config); + assert!(result.is_err()); + } + + #[test] + fn split_file_name_keeps_stem_and_suffix() { + let parts = super::split_file_name("khadhroony.bot2.log"); + assert_eq!(parts, ("khadhroony.bot2".to_string(), "log".to_string())); + } + + #[test] + fn ansi_stripper_removes_escape_sequences() { + let stripped = super::strip_ansi_bytes(b"a\x1b[31mb"); + assert_eq!(stripped, b"ab"); + } + + #[test] + fn ansi_stripping_writer_removes_escape_sequences_before_inner_writer() { + let output = std::sync::Arc::new(std::sync::Mutex::new(std::vec::Vec::::new())); + let make_writer = + super::StripAnsiMakeWriter::new(TestMakeWriter { output: output.clone() }); + let mut writer = tracing_subscriber::fmt::MakeWriter::make_writer(&make_writer); + let write_result = std::io::Write::write_all(&mut writer, b"a\x1b[32mb"); + assert!(write_result.is_ok()); + drop(writer); + let actual = match output.lock() { + std::result::Result::Ok(output) => output.clone(), + std::result::Result::Err(_) => std::vec::Vec::::new(), + }; + assert_eq!(actual, b"ab"); + } +} diff --git a/scripts/audit_khadhroony_workspace_rules.py b/scripts/audit_khadhroony_workspace_rules.py index 2bae1d1..63de0e5 100755 --- a/scripts/audit_khadhroony_workspace_rules.py +++ b/scripts/audit_khadhroony_workspace_rules.py @@ -438,13 +438,13 @@ def audit_reserved_executor_scaffolds(root: pathlib.Path) -> list[Violation]: "reserved executor must build only an explicit zero-instruction plan", ) ) - if reserved_count != 100: + if reserved_count != 99: violations.append( Violation( "KH_EX007", "kb-lib/src/executor", 1, - f"expected 100 reserved executor boundaries, found {reserved_count}", + f"expected 99 reserved executor boundaries, found {reserved_count}", ) ) return violations