v0.2.6-pre.003
This commit is contained in:
@@ -5,6 +5,14 @@
|
||||
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
||||
KSP_LOGS_DIRECTORY=logs
|
||||
|
||||
# KSP Wallet root directory. Used by config/std.wallet.json before an optional profile subdirectory is appended.
|
||||
# The committed Wallet document falls back to "wallets" when neither the process environment nor .env defines this variable.
|
||||
KSP_WALLETS_DIRECTORY=wallets
|
||||
|
||||
# Optional Wallet Desk password candidates are introduced by the later unlock tranche.
|
||||
# Additional KSP_SECRET_WALLET_PASS_<LABEL> variables may be defined locally; never commit real password values.
|
||||
# KSP_SECRET_WALLET_PASS_01=
|
||||
|
||||
# Optional public Solana Devnet HTTP endpoint override used by config/std.transport.json.
|
||||
# The committed Transport document falls back to https://api.devnet.solana.com when this variable is absent.
|
||||
KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL=https://api.devnet.solana.com
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 165
|
||||
# version: 166
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.6-pre.2"
|
||||
version = "0.2.6-pre.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 58 -->
|
||||
<!-- version: 59 -->
|
||||
|
||||
# Roadmap KSP
|
||||
|
||||
@@ -50,7 +50,7 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
|
||||
- [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants.
|
||||
- [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication.
|
||||
- [X] `0.2.5` — Wallet foundation stable : `.kspwallet` V1, VIEW/OWNER indépendants, Argon2id/XChaCha20-Poly1305, autorité Ed25519 OWNER, persistence no-clobber, signature, administration/rotations/révocation VIEW forte, import/export Solana CLI JSON + Base58, canaris adversariaux, interop externe et documentation durable publiés. La clôture `pre.010-fix.001`–`fix.003` ajoute `ed25519-dalek 3.0.0` direct, normalise le Rust workspace et installe l’audit structurel Python complémentaire à rustfmt/Clippy. `Pubkey` reste via `ksp-core-lib`, la keypair reste encapsulée dans Wallet et Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet.
|
||||
- [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP. `pre.001` fixe le sizing et le gabarit ; `pre.002` matérialise la crate Tauri et son shell splash/main avec Config + Logging bootstrap, ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, resize-observer-polyfill, TS-RS et bridge frontend Logging. Les tranches suivantes ajoutent `cfg.std.wallet`, inventory, lifecycle VIEW/OWNER, secrets `KSP_SECRET_WALLET_PASS_*`, identité autorisée + `getBalance`, puis administration/import/export, avec une dernière tranche prévue pour README/USAGE/docs/validation, prompt `0.2.7` et build Tauri final.
|
||||
- [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP. `pre.001` fixe le sizing et le gabarit ; `pre.002` matérialise la crate Tauri et son shell splash/main avec Config + Logging bootstrap, ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, resize-observer-polyfill, TS-RS et bridge frontend Logging. `pre.003` matérialise `cfg.std.wallet`/`schema.std.wallet`, le composite `cfg.composite.ksp-app-wallet-desk`, `ResolvedWalletConfig`, `wallets_directory` global + `wallets_subdirectory` par profil et la préparation automatique des répertoires côté application avec logs debug/error. Les tranches suivantes ajoutent inventory, lifecycle VIEW/OWNER, secrets `KSP_SECRET_WALLET_PASS_*`, identité autorisée + `getBalance`, puis administration/import/export, avec une dernière tranche prévue pour README/USAGE/docs/validation, prompt `0.2.7` et build Tauri final.
|
||||
- [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
|
||||
- [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
|
||||
- [ ] `0.2.9` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
|
||||
|
||||
66
config/composite.ksp-app-wallet-desk.json
Normal file
66
config/composite.ksp-app-wallet-desk.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "devnet",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"profile_id": "temporary",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "temporary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"profile_id": "tests",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
66
config/examples/composite.ksp-app-wallet-desk.example.json
Normal file
66
config/examples/composite.ksp-app-wallet-desk.example.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"default_profile": "devnet",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "devnet",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"profile_id": "temporary",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "temporary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"profile_id": "tests",
|
||||
"documents": [
|
||||
{
|
||||
"component_id": "logging",
|
||||
"file_id": "cfg.std.logging",
|
||||
"profile_id": "local_dev"
|
||||
},
|
||||
{
|
||||
"component_id": "transport",
|
||||
"file_id": "cfg.std.transport",
|
||||
"profile_id": "devnet_public"
|
||||
},
|
||||
{
|
||||
"component_id": "wallet",
|
||||
"file_id": "cfg.std.wallet",
|
||||
"profile_id": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
config/examples/std.wallet.example.json
Normal file
18
config/examples/std.wallet.example.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}",
|
||||
"default_profile": "tests",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "default"
|
||||
},
|
||||
{
|
||||
"profile_id": "scenarios",
|
||||
"wallets_subdirectory": "scenarios/ephemeral"
|
||||
},
|
||||
{
|
||||
"profile_id": "tests",
|
||||
"wallets_subdirectory": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
54
config/schemas/std.wallet.schema.json
Normal file
54
config/schemas/std.wallet.schema.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "urn:ksp:schema:std.wallet:v1",
|
||||
"title": "KSP standard Wallet configuration",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"format_version",
|
||||
"wallets_directory",
|
||||
"default_profile",
|
||||
"profiles"
|
||||
],
|
||||
"properties": {
|
||||
"format_version": {
|
||||
"const": 1
|
||||
},
|
||||
"wallets_directory": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"default_profile": {
|
||||
"$ref": "#/$defs/profileId"
|
||||
},
|
||||
"profiles": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/profile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"profileId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||||
},
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"profile_id"
|
||||
],
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"$ref": "#/$defs/profileId"
|
||||
},
|
||||
"wallets_subdirectory": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
config/std.wallet.json
Normal file
18
config/std.wallet.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"format_version": 1,
|
||||
"wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}",
|
||||
"default_profile": "default",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_id": "default"
|
||||
},
|
||||
{
|
||||
"profile_id": "temporary",
|
||||
"wallets_subdirectory": "temporary"
|
||||
},
|
||||
{
|
||||
"profile_id": "tests",
|
||||
"wallets_subdirectory": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -37,9 +37,9 @@
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<div>
|
||||
<h1 id="viewTitle" class="h3 mb-1">Dashboard</h1>
|
||||
<p class="text-body-secondary mb-0">Fondation UI de Wallet Desk — fonctionnalités Wallet branchées dans les tranches suivantes.</p>
|
||||
<p class="text-body-secondary mb-0">Composition Config Wallet Desk active — inventory Wallet branché en pre.004.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.002</span>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.003</span>
|
||||
</div>
|
||||
<section data-view-panel="dashboard">
|
||||
<div class="row g-3">
|
||||
@@ -51,8 +51,14 @@
|
||||
<dt class="col-sm-5">Version</dt><dd id="runtimeVersion" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Phase</dt><dd id="runtimeShellPhase" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Documents Config enregistrés</dt><dd id="runtimeConfigDocuments" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil composite</dt><dd id="runtimeCompositeProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil Wallet</dt><dd id="runtimeWalletProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Racine Wallet</dt><dd id="runtimeWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Sous-répertoire Wallet</dt><dd id="runtimeWalletsSubdirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Répertoire effectif</dt><dd id="runtimeEffectiveWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Créé au démarrage</dt><dd id="runtimeWalletDirectoryCreated" class="col-sm-7">—</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +102,7 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="app-footer bg-dark text-white-50 d-flex align-items-center px-4">
|
||||
<span>KSP Wallet Desk · shell pre.002</span>
|
||||
<span>KSP Wallet Desk · Config composition pre.003</span>
|
||||
</footer>
|
||||
<script type="module" src="./ts/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
import "bootstrap";
|
||||
import DataTable from "datatables.net-bs5";
|
||||
@@ -81,21 +81,33 @@ function initializeWalletTable(): void {
|
||||
zeroRecords: "Aucun wallet correspondant.",
|
||||
},
|
||||
});
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.002-shell" });
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.003-config-wallet" });
|
||||
}
|
||||
|
||||
function renderRuntimeStatus(status: RuntimeStatusDto): void {
|
||||
const version = document.querySelector<HTMLElement>("#runtimeVersion");
|
||||
const profile = document.querySelector<HTMLElement>("#runtimeLoggingProfile");
|
||||
const compositeProfile = document.querySelector<HTMLElement>("#runtimeCompositeProfile");
|
||||
const loggingProfile = document.querySelector<HTMLElement>("#runtimeLoggingProfile");
|
||||
const walletProfile = document.querySelector<HTMLElement>("#runtimeWalletProfile");
|
||||
const fallback = document.querySelector<HTMLElement>("#runtimeLoggingFallback");
|
||||
const documents = document.querySelector<HTMLElement>("#runtimeConfigDocuments");
|
||||
const walletsRoot = document.querySelector<HTMLElement>("#runtimeWalletsDirectory");
|
||||
const walletsSubdirectory = document.querySelector<HTMLElement>("#runtimeWalletsSubdirectory");
|
||||
const effectiveWalletsDirectory = document.querySelector<HTMLElement>("#runtimeEffectiveWalletsDirectory");
|
||||
const walletDirectoryCreated = document.querySelector<HTMLElement>("#runtimeWalletDirectoryCreated");
|
||||
const phase = document.querySelector<HTMLElement>("#runtimeShellPhase");
|
||||
const shellStatus = document.querySelector<HTMLElement>("#shellStatus");
|
||||
if (version) {
|
||||
version.textContent = status.applicationVersion;
|
||||
}
|
||||
if (profile) {
|
||||
profile.textContent = status.activeLoggingProfile ?? "fallback transitoire";
|
||||
if (compositeProfile) {
|
||||
compositeProfile.textContent = status.activeCompositeProfile;
|
||||
}
|
||||
if (loggingProfile) {
|
||||
loggingProfile.textContent = status.activeLoggingProfile ?? "fallback transitoire";
|
||||
}
|
||||
if (walletProfile) {
|
||||
walletProfile.textContent = status.activeWalletProfile;
|
||||
}
|
||||
if (fallback) {
|
||||
fallback.textContent = status.fallbackLoggingActive ? "oui" : "non";
|
||||
@@ -103,15 +115,30 @@ function renderRuntimeStatus(status: RuntimeStatusDto): void {
|
||||
if (documents) {
|
||||
documents.textContent = status.configDocumentCount.toString();
|
||||
}
|
||||
if (walletsRoot) {
|
||||
walletsRoot.textContent = status.walletsDirectory;
|
||||
}
|
||||
if (walletsSubdirectory) {
|
||||
walletsSubdirectory.textContent = status.walletsSubdirectory ?? "— (racine globale)";
|
||||
}
|
||||
if (effectiveWalletsDirectory) {
|
||||
effectiveWalletsDirectory.textContent = status.effectiveWalletsDirectory;
|
||||
}
|
||||
if (walletDirectoryCreated) {
|
||||
walletDirectoryCreated.textContent = status.effectiveWalletsDirectoryCreatedOnStartup ? "oui" : "non, déjà présent";
|
||||
}
|
||||
if (phase) {
|
||||
phase.textContent = status.shellPhase;
|
||||
}
|
||||
if (shellStatus) {
|
||||
shellStatus.textContent = "Shell Wallet Desk prêt.";
|
||||
shellStatus.textContent = "Config Wallet Desk résolue ; répertoire Wallet prêt.";
|
||||
}
|
||||
frontendTrace("main", "Wallet Desk runtime status rendered", {
|
||||
compositeProfile: status.activeCompositeProfile,
|
||||
fallbackLoggingActive: status.fallbackLoggingActive,
|
||||
shellPhase: status.shellPhase,
|
||||
walletDirectoryCreatedOnStartup: status.effectiveWalletsDirectoryCreatedOnStartup,
|
||||
walletProfile: status.activeWalletProfile,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ksp-app-wallet-desk",
|
||||
"private": true,
|
||||
"version": "0.2.6-pre.2",
|
||||
"version": "0.2.6-pre.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/app_state.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Shared backend state owned by the Wallet Desk Tauri application.
|
||||
|
||||
@@ -9,10 +9,11 @@ pub(crate) struct AppState {
|
||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||
splash_settings: crate::SplashSettings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||
wallet_config_startup: crate::WalletConfigStartup,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
/// Initializes Config ownership, Logging and the common desktop splash state.
|
||||
/// Initializes Config composition, Logging, Wallet directory preparation and the common desktop splash state.
|
||||
pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
|
||||
let config_management = crate::config_management(arguments);
|
||||
let config_management = match config_management {
|
||||
@@ -29,6 +30,14 @@ impl AppState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet_config_startup = crate::initialize_wallet_config(&config_management);
|
||||
let wallet_config_startup = match wallet_config_startup {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, error_domain = error.code().domain(), error_code = error.code().code(), "Wallet Desk Config/Wallet directory bootstrap failed");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let splash_settings = crate::SplashSettings::load();
|
||||
let splash_settings = match splash_settings {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -48,10 +57,11 @@ impl AppState {
|
||||
}),
|
||||
splash_settings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
wallet_config_startup,
|
||||
});
|
||||
}
|
||||
|
||||
/// Builds the safe shell status DTO exposed during pre.002.
|
||||
/// Builds the safe Config-composition status DTO exposed during pre.003.
|
||||
pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result<crate::RuntimeStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
let document_count = u32::try_from(document_count);
|
||||
@@ -61,7 +71,7 @@ impl AppState {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_INVALID,
|
||||
"Config registry contains too many descriptors for the Wallet Desk shell DTO",
|
||||
"Config registry contains too many descriptors for the Wallet Desk runtime DTO",
|
||||
)
|
||||
.with_source(error),
|
||||
);
|
||||
@@ -78,13 +88,22 @@ impl AppState {
|
||||
},
|
||||
};
|
||||
let _keep_guard_alive = &runtime.guard;
|
||||
let resolved = self.wallet_config_startup.resolved();
|
||||
let wallets_subdirectory = resolved.wallets_subdirectory().map(|value| return value.to_string_lossy().into_owned());
|
||||
return std::result::Result::Ok(crate::RuntimeStatusDto {
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
config_document_count: document_count,
|
||||
active_composite_profile: self.wallet_config_startup.composite_profile_id().to_owned(),
|
||||
active_logging_profile: runtime.active_profile_id.clone(),
|
||||
active_wallet_profile: resolved.profile_id().to_owned(),
|
||||
config_document_count: document_count,
|
||||
effective_wallets_directory: resolved.effective_wallets_directory().to_string_lossy().into_owned(),
|
||||
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
|
||||
shell_phase: "pre.003-config-wallet".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
shell_phase: "pre.002-shell".to_owned(),
|
||||
wallets_directory: resolved.wallets_directory().to_string_lossy().into_owned(),
|
||||
wallets_subdirectory,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Config and Logging bootstrap for Wallet Desk.
|
||||
//! Config composite and Logging bootstrap for Wallet Desk.
|
||||
|
||||
/// Crate-internal Logging startup state shared by the application state.
|
||||
pub(crate) struct LoggingStartup {
|
||||
@@ -43,7 +43,7 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||
}
|
||||
|
||||
/// Initializes the Logging runtime from Config, with the same bounded fallback policy as Config Desk.
|
||||
/// Initializes the Logging runtime from the Wallet Desk composite, with the same bounded fallback policy as Config Desk.
|
||||
pub(crate) fn initialize_logging(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn initialize_logging(
|
||||
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||
match guard {
|
||||
std::result::Result::Ok(guard) => {
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized Wallet Desk logging from managed configuration");
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized Wallet Desk logging from composite-managed configuration");
|
||||
std::result::Result::Ok(LoggingStartup {
|
||||
guard,
|
||||
active_profile_id: std::option::Option::Some(active_profile_id),
|
||||
@@ -71,6 +71,47 @@ pub(crate) fn initialize_logging(
|
||||
};
|
||||
}
|
||||
|
||||
/// Loads the concrete Wallet Desk Config composite using its registered logical `file_id` and autonomous default profile.
|
||||
pub(crate) fn load_wallet_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Returns one required standard profile from the Wallet Desk composite after validating the referenced logical `file_id`.
|
||||
pub(crate) fn required_composite_component_profile(
|
||||
composite: &ksp_config_lib::ResolvedConfigComposite,
|
||||
component_id: &str,
|
||||
expected_file_id: &str,
|
||||
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigProfile> {
|
||||
let component = composite.component(component_id);
|
||||
let component = match component {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite is missing a required component")
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id),
|
||||
);
|
||||
},
|
||||
};
|
||||
if component.resolved().file_id().as_str() != expected_file_id {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite component references an unexpected Config document")
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id)
|
||||
.with_context("expected_file_id", expected_file_id)
|
||||
.with_context("actual_file_id", component.resolved().file_id().as_str()),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(component.resolved().clone());
|
||||
}
|
||||
|
||||
fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||
return ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Info,
|
||||
@@ -86,7 +127,26 @@ fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> Log
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let resolved = management.engine().load_resolved_logging_config(std::option::Option::None, &environment);
|
||||
let composite = crate::load_wallet_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
let logging_profile = match logging_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let transport_profile =
|
||||
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
if let std::result::Result::Err(error) = transport_profile {
|
||||
return fallback_startup_plan(error);
|
||||
}
|
||||
let wallet_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_WALLET, ksp_config_lib::FILE_ID_STD_WALLET);
|
||||
if let std::result::Result::Err(error) = wallet_profile {
|
||||
return fallback_startup_plan(error);
|
||||
}
|
||||
let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/constants.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Logging targets and domains owned by Wallet Desk.
|
||||
//! Logging targets, domains and composite component identifiers owned by Wallet Desk.
|
||||
|
||||
/// Composite-local identifier for the Logging standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
|
||||
/// Composite-local identifier for the HTTP Transport standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
|
||||
/// Composite-local identifier for the Wallet standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_WALLET: &str = "wallet";
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "wallet.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used by the Wallet Desk shell.
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "wallet.shell";
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) const TRACING_DOMAIN_WALLET_CONFIG: &str = "wallet.config";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/dto_common.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Common Tauri DTOs shared by Wallet Desk shell commands.
|
||||
|
||||
@@ -30,23 +30,37 @@ impl CommandErrorDto {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initial application/runtime snapshot exposed to the Wallet Desk shell.
|
||||
/// Safe application/runtime snapshot exposed to the Wallet Desk shell.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/dto_common/RuntimeStatusDto.ts")]
|
||||
pub(crate) struct RuntimeStatusDto {
|
||||
/// Cargo application version.
|
||||
pub(crate) application_version: String,
|
||||
/// Number of documents registered by Config before Wallet-specific documents are introduced.
|
||||
pub(crate) config_document_count: u32,
|
||||
/// Wallet Desk composite profile selected during bootstrap.
|
||||
pub(crate) active_composite_profile: String,
|
||||
/// Active configured Logging profile, or `None` while the transient fallback runtime is active.
|
||||
pub(crate) active_logging_profile: std::option::Option<String>,
|
||||
/// Standard Wallet profile selected by the active composite.
|
||||
pub(crate) active_wallet_profile: String,
|
||||
/// Number of logical Config resources registered by the current application runtime.
|
||||
pub(crate) config_document_count: u32,
|
||||
/// Effective Wallet directory after applying the optional profile subdirectory.
|
||||
pub(crate) effective_wallets_directory: String,
|
||||
/// Whether bootstrap created any missing component of the effective Wallet profile directory.
|
||||
pub(crate) effective_wallets_directory_created_on_startup: bool,
|
||||
/// Whether Wallet Desk had to install its transient in-memory Logging fallback.
|
||||
pub(crate) fallback_logging_active: bool,
|
||||
/// Whether bootstrap created the configured global Wallet root.
|
||||
pub(crate) root_wallets_directory_created_on_startup: bool,
|
||||
/// Current implementation phase exposed for the Config composition tranche.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Safe startup diagnostic that caused fallback Logging, when applicable.
|
||||
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
|
||||
/// Current implementation phase exposed for the pre.002 shell.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Resolved global Wallet root configured by `cfg.std.wallet`.
|
||||
pub(crate) wallets_directory: String,
|
||||
/// Optional profile-relative Wallet subdirectory selected by the active composite.
|
||||
pub(crate) wallets_subdirectory: std::option::Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/errors.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Application-local error codes for the Wallet Desk shell.
|
||||
//! Application-local error codes for Wallet Desk composition and desktop runtime surfaces.
|
||||
|
||||
/// Shared Wallet Desk application state is internally inconsistent.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "app_state_invalid");
|
||||
/// Shared Wallet Desk runtime state cannot be locked safely.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "app_state_lock_failed");
|
||||
/// The Wallet Desk Config composite does not expose one of its required standard components with the expected `file_id`.
|
||||
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "config_composite_invalid");
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "frontend_log_level_invalid");
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
@@ -24,3 +26,8 @@ pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_
|
||||
/// A Tauri window show/focus/destroy/event operation failed.
|
||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("wallet_desk", "tauri_window_operation_failed");
|
||||
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_invalid");
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_prepare_failed");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri desktop application shell for KSP Wallet management and inspection.
|
||||
|
||||
@@ -18,6 +18,7 @@ mod splash;
|
||||
mod tauri;
|
||||
mod tw_main;
|
||||
mod tw_splash;
|
||||
mod wallet_config;
|
||||
|
||||
/// Runs the KSP wallet desktop application.
|
||||
pub use self::tauri::run;
|
||||
@@ -30,12 +31,24 @@ pub(crate) use self::bootstrap::LoggingStartup;
|
||||
pub(crate) use self::bootstrap::config_management;
|
||||
/// Initializes the Logging runtime from Config, with the same bounded fallback policy as Config Desk.
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
/// Loads the concrete Wallet Desk Config composite.
|
||||
pub(crate) use self::bootstrap::load_wallet_desk_composite;
|
||||
/// Resolves and validates one required standard-document component from the Wallet Desk composite.
|
||||
pub(crate) use self::bootstrap::required_composite_component_profile;
|
||||
/// Composite-local identifier for the Logging standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
|
||||
/// Composite-local identifier for the HTTP Transport standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
|
||||
/// Composite-local identifier for the Wallet standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_WALLET;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
/// Structured domain used by the Wallet Desk shell.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_CONFIG;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
@@ -54,6 +67,8 @@ pub(crate) use self::dto_common::RuntimeStatusDto;
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Wallet Desk runtime state cannot be locked safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
/// The Wallet Desk Config composite is missing or misroutes one required standard component.
|
||||
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
@@ -70,6 +85,10 @@ pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
||||
/// A Tauri window show/focus/destroy/event operation failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_INVALID;
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED;
|
||||
/// Log payload sent by Wallet Desk frontend scripts.
|
||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
/// Emits one validated frontend event through the KSP Logging facade.
|
||||
@@ -88,3 +107,7 @@ pub(crate) use self::tw_main::show_main_window;
|
||||
pub(crate) use self::tw_splash::require_splash_window;
|
||||
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||
pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
|
||||
pub(crate) use self::wallet_config::WalletConfigStartup;
|
||||
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.
|
||||
pub(crate) use self::wallet_config::initialize_wallet_config;
|
||||
|
||||
178
crates/ksp-app-wallet-desk/src/wallet_config.rs
Normal file
178
crates/ksp-app-wallet-desk/src/wallet_config.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/wallet_config.rs
|
||||
// version: 1
|
||||
|
||||
//! Wallet Desk composition adapter for the standard Wallet Config and its application-owned directory preparation.
|
||||
|
||||
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
|
||||
pub(crate) struct WalletConfigStartup {
|
||||
composite_profile_id: String,
|
||||
effective_directory_created_on_startup: bool,
|
||||
resolved: ksp_config_lib::ResolvedWalletConfig,
|
||||
root_directory_created_on_startup: bool,
|
||||
}
|
||||
|
||||
impl WalletConfigStartup {
|
||||
/// Returns the selected Wallet Desk composite profile.
|
||||
pub(crate) fn composite_profile_id(&self) -> &str {
|
||||
return self.composite_profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Reports whether bootstrap created at least one missing component of the effective Wallet profile directory.
|
||||
pub(crate) const fn effective_directory_created_on_startup(&self) -> bool {
|
||||
return self.effective_directory_created_on_startup;
|
||||
}
|
||||
|
||||
/// Returns the resolved standard Wallet configuration.
|
||||
pub(crate) const fn resolved(&self) -> &ksp_config_lib::ResolvedWalletConfig {
|
||||
return &self.resolved;
|
||||
}
|
||||
|
||||
/// Reports whether bootstrap created the configured global Wallet root.
|
||||
pub(crate) const fn root_directory_created_on_startup(&self) -> bool {
|
||||
return self.root_directory_created_on_startup;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the Wallet component selected by the Wallet Desk composite and prepares its global/effective directory tree.
|
||||
pub(crate) fn initialize_wallet_config(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<WalletConfigStartup> {
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let composite = crate::load_wallet_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
if let std::result::Result::Err(error) = logging_profile {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transport_profile =
|
||||
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
if let std::result::Result::Err(error) = transport_profile {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let wallet_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_WALLET, ksp_config_lib::FILE_ID_STD_WALLET);
|
||||
let wallet_profile = match wallet_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let resolved = management.engine().resolve_wallet_config_profile(&wallet_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let preparation = prepare_wallet_directories(&resolved);
|
||||
let preparation = match preparation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let root_path = resolved.wallets_directory().to_string_lossy().into_owned();
|
||||
let effective_path = resolved.effective_wallets_directory().to_string_lossy().into_owned();
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, composite_profile = composite.profile_id(), wallet_profile = resolved.profile_id(), root_path = root_path.as_str(), effective_path = effective_path.as_str(), root_created = preparation.root_created, effective_created = preparation.effective_created, "Wallet Desk Wallet directories are ready");
|
||||
return std::result::Result::Ok(WalletConfigStartup {
|
||||
composite_profile_id: composite.profile_id().to_owned(),
|
||||
effective_directory_created_on_startup: preparation.effective_created,
|
||||
resolved,
|
||||
root_directory_created_on_startup: preparation.root_created,
|
||||
});
|
||||
}
|
||||
|
||||
struct WalletDirectoryPreparation {
|
||||
effective_created: bool,
|
||||
root_created: bool,
|
||||
}
|
||||
|
||||
fn prepare_wallet_directories(resolved: &ksp_config_lib::ResolvedWalletConfig) -> ksp_core_lib::Result<WalletDirectoryPreparation> {
|
||||
let root_created = ensure_global_wallet_root(resolved.wallets_directory());
|
||||
let root_created = match root_created {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let effective_created = match resolved.wallets_subdirectory() {
|
||||
std::option::Option::Some(subdirectory) => {
|
||||
let created = ensure_profile_wallet_directory(resolved.wallets_directory(), subdirectory);
|
||||
match created {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
},
|
||||
std::option::Option::None => root_created,
|
||||
};
|
||||
return std::result::Result::Ok(WalletDirectoryPreparation { effective_created, root_created });
|
||||
}
|
||||
|
||||
fn ensure_global_wallet_root(path: &std::path::Path) -> ksp_core_lib::Result<bool> {
|
||||
let metadata = std::fs::metadata(path);
|
||||
return match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() => std::result::Result::Ok(false),
|
||||
std::result::Result::Ok(_) => directory_invalid(path, "configured Wallet root exists but is not a directory"),
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
let creation = std::fs::create_dir_all(path);
|
||||
match creation {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(true),
|
||||
std::result::Result::Err(error) => directory_prepare_error(path, "configured Wallet root cannot be created", error),
|
||||
}
|
||||
},
|
||||
std::result::Result::Err(error) => directory_prepare_error(path, "configured Wallet root cannot be inspected", error),
|
||||
};
|
||||
}
|
||||
|
||||
fn ensure_profile_wallet_directory(root: &std::path::Path, subdirectory: &std::path::Path) -> ksp_core_lib::Result<bool> {
|
||||
let mut current = root.to_path_buf();
|
||||
let mut created = false;
|
||||
for component in subdirectory.components() {
|
||||
let normal = match component {
|
||||
std::path::Component::Normal(value) => value,
|
||||
_ => return directory_invalid(subdirectory, "Wallet profile subdirectory contains an unsafe path component"),
|
||||
};
|
||||
current.push(normal);
|
||||
let metadata = std::fs::symlink_metadata(current.as_path());
|
||||
match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.file_type().is_symlink() => {
|
||||
return directory_invalid(current.as_path(), "Wallet profile directory path traverses a symbolic link");
|
||||
},
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() => {},
|
||||
std::result::Result::Ok(_) => return directory_invalid(current.as_path(), "Wallet profile directory path contains a non-directory entry"),
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
let creation = std::fs::create_dir(current.as_path());
|
||||
if let std::result::Result::Err(error) = creation {
|
||||
return directory_prepare_error(current.as_path(), "Wallet profile directory component cannot be created", error);
|
||||
}
|
||||
created = true;
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
return directory_prepare_error(current.as_path(), "Wallet profile directory component cannot be inspected", error);
|
||||
},
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(created);
|
||||
}
|
||||
|
||||
fn directory_invalid<T>(path: &std::path::Path, reason: &'static str) -> ksp_core_lib::Result<T> {
|
||||
let path_text = path.to_string_lossy().into_owned();
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, path = path_text.as_str(), reason, "Wallet directory preparation rejected an invalid filesystem object");
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_DIRECTORY_INVALID, "Configured Wallet directory is invalid")
|
||||
.with_context("path", path_text)
|
||||
.with_context("reason", reason),
|
||||
);
|
||||
}
|
||||
|
||||
fn directory_prepare_error<T>(path: &std::path::Path, reason: &'static str, source: std::io::Error) -> ksp_core_lib::Result<T> {
|
||||
let path_text = path.to_string_lossy().into_owned();
|
||||
let source_kind = std::format!("{:?}", source.kind());
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, path = path_text.as_str(), reason, source_kind = source_kind.as_str(), "Wallet directory preparation failed");
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED, "Configured Wallet directory cannot be prepared")
|
||||
.with_context("path", path_text)
|
||||
.with_context("reason", reason)
|
||||
.with_source(source),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/wallet_config.rs"]
|
||||
mod tests;
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "KSP Wallet Desk",
|
||||
"version": "0.2.6-pre.2",
|
||||
"version": "0.2.6-pre.3",
|
||||
"identifier": "com.sasedev.ksp-app-wallet-desk",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
48
crates/ksp-app-wallet-desk/tests/config_composition.rs
Normal file
48
crates/ksp-app-wallet-desk/tests/config_composition.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// file: crates/ksp-app-wallet-desk/tests/config_composition.rs
|
||||
// version: 1
|
||||
|
||||
//! Config composition contracts introduced for Wallet Desk pre.003.
|
||||
|
||||
fn workspace_engine() -> ksp_core_lib::Result<ksp_config_lib::ConfigDocumentEngine> {
|
||||
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
|
||||
return match registry {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_desk_composite_selects_logging_transport_and_wallet_by_file_id() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert!(file_id.is_ok(), "Wallet Desk composite file_id should be valid: {file_id:?}");
|
||||
if let std::result::Result::Ok(file_id) = file_id {
|
||||
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None);
|
||||
assert!(composite.is_ok(), "committed Wallet Desk composite should resolve: {composite:?}");
|
||||
if let std::result::Result::Ok(composite) = composite {
|
||||
assert_eq!(composite.profile_id(), "devnet");
|
||||
assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "local_dev");
|
||||
assert_component(&composite, "transport", ksp_config_lib::FILE_ID_STD_TRANSPORT, "devnet_public");
|
||||
assert_component(&composite, "wallet", ksp_config_lib::FILE_ID_STD_WALLET, "default");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_component(composite: &ksp_config_lib::ResolvedConfigComposite, component_id: &str, file_id: &str, profile_id: &str) {
|
||||
let component = composite.component(component_id);
|
||||
assert!(component.is_some(), "missing component {component_id}");
|
||||
if let std::option::Option::Some(component) = component {
|
||||
assert_eq!(component.resolved().file_id().as_str(), file_id);
|
||||
assert_eq!(component.resolved().profile_id(), profile_id);
|
||||
assert_eq!(component.resolved().selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Desktop build and shell contract audits for Wallet Desk pre.002.
|
||||
//! Desktop build, shell and Config-status contract audits for Wallet Desk.
|
||||
|
||||
fn app_root() -> std::path::PathBuf {
|
||||
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
@@ -78,6 +78,8 @@ fn shell_contains_wallet_navigation_datatable_and_lock_state_icons() {
|
||||
assert!(html.contains("fa-lock"));
|
||||
assert!(html.contains("fa-lock-open"));
|
||||
assert!(html.contains("data-view=\"wallets\""));
|
||||
assert!(html.contains("id=\"runtimeCompositeProfile\""));
|
||||
assert!(html.contains("id=\"runtimeEffectiveWalletsDirectory\""));
|
||||
assert!(main.contains("new DataTable"));
|
||||
assert!(main.contains("datatables.net-select-bs5"));
|
||||
assert!(main.contains("ResizeObserver"));
|
||||
|
||||
79
crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
Normal file
79
crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
// file: crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn directory_preparation_creates_missing_root_and_nested_profile_path() {
|
||||
let root = fixture_path("create");
|
||||
cleanup_fixture(root.as_path());
|
||||
let root_created = super::ensure_global_wallet_root(root.as_path());
|
||||
assert_eq!(root_created.as_ref().ok().copied(), std::option::Option::Some(true));
|
||||
let profile_created = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert_eq!(profile_created.as_ref().ok().copied(), std::option::Option::Some(true));
|
||||
assert!(root.join("tests/ephemeral").is_dir());
|
||||
let root_again = super::ensure_global_wallet_root(root.as_path());
|
||||
assert_eq!(root_again.as_ref().ok().copied(), std::option::Option::Some(false));
|
||||
let profile_again = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert_eq!(profile_again.as_ref().ok().copied(), std::option::Option::Some(false));
|
||||
cleanup_fixture(root.as_path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_preparation_rejects_existing_non_directory_root() {
|
||||
let root = fixture_path("file-root");
|
||||
cleanup_fixture(root.as_path());
|
||||
let parent = root.parent();
|
||||
assert!(parent.is_some());
|
||||
if let std::option::Option::Some(parent) = parent {
|
||||
let created = std::fs::create_dir_all(parent);
|
||||
assert!(created.is_ok(), "fixture parent should be creatable: {created:?}");
|
||||
let written = std::fs::write(root.as_path(), b"not-a-directory");
|
||||
assert!(written.is_ok(), "fixture file should be writable: {written:?}");
|
||||
let result = super::ensure_global_wallet_root(root.as_path());
|
||||
assert!(result.is_err(), "non-directory Wallet root should fail: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WALLET_DIRECTORY_INVALID);
|
||||
}
|
||||
}
|
||||
cleanup_fixture(root.as_path());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn profile_directory_preparation_rejects_symbolic_link_components() {
|
||||
let root = fixture_path("symlink-root");
|
||||
let outside = fixture_path("symlink-outside");
|
||||
cleanup_fixture(root.as_path());
|
||||
cleanup_fixture(outside.as_path());
|
||||
let root_created = std::fs::create_dir_all(root.as_path());
|
||||
let outside_created = std::fs::create_dir_all(outside.as_path());
|
||||
assert!(root_created.is_ok(), "root fixture should be creatable: {root_created:?}");
|
||||
assert!(outside_created.is_ok(), "outside fixture should be creatable: {outside_created:?}");
|
||||
if root_created.is_ok() && outside_created.is_ok() {
|
||||
let linked = std::os::unix::fs::symlink(outside.as_path(), root.join("tests"));
|
||||
assert!(linked.is_ok(), "symlink fixture should be creatable: {linked:?}");
|
||||
let result = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert!(result.is_err(), "profile path must reject symlink traversal: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WALLET_DIRECTORY_INVALID);
|
||||
}
|
||||
}
|
||||
cleanup_fixture(root.as_path());
|
||||
cleanup_fixture(outside.as_path());
|
||||
}
|
||||
|
||||
fn fixture_path(name: &str) -> std::path::PathBuf {
|
||||
return std::env::temp_dir().join(std::format!("ksp-wallet-desk-{name}-{}", std::process::id()));
|
||||
}
|
||||
|
||||
fn cleanup_fixture(path: &std::path::Path) {
|
||||
let metadata = std::fs::symlink_metadata(path);
|
||||
match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => {
|
||||
let _ = std::fs::remove_dir_all(path);
|
||||
},
|
||||
std::result::Result::Ok(_) => {
|
||||
let _ = std::fs::remove_file(path);
|
||||
},
|
||||
std::result::Result::Err(_) => {},
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/README.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# ksp-config-lib
|
||||
|
||||
@@ -33,18 +33,21 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
|
||||
Le registre par défaut connaît :
|
||||
|
||||
```text
|
||||
cfg.std.logging -> config/std.logging.json
|
||||
cfg.std.transport -> config/std.transport.json
|
||||
schema.std.logging -> config/schemas/std.logging.schema.json
|
||||
schema.std.transport -> config/schemas/std.transport.schema.json
|
||||
schema.composite -> config/schemas/composite.schema.json
|
||||
cfg.composite.ksp-app-wallet-desk -> config/composite.ksp-app-wallet-desk.json
|
||||
cfg.std.logging -> config/std.logging.json
|
||||
cfg.std.transport -> config/std.transport.json
|
||||
cfg.std.wallet -> config/std.wallet.json
|
||||
schema.composite -> config/schemas/composite.schema.json
|
||||
schema.std.logging -> config/schemas/std.logging.schema.json
|
||||
schema.std.transport -> config/schemas/std.transport.schema.json
|
||||
schema.std.wallet -> config/schemas/std.wallet.schema.json
|
||||
```
|
||||
|
||||
`ConfigFileRegistry::descriptors()` expose ces descripteurs en lecture seule et dans un ordre déterministe par `file_id`. Une application de management peut ainsi découvrir les fichiers connus sans maintenir une liste parallèle ni dépendre de leurs filenames physiques.
|
||||
|
||||
`ConfigManagement::read_source()` permet d'inspecter le texte brut d'un document Config enregistré même lorsque ce document est invalide. `save_source_candidate()` complète cette frontière : le candidat brut est parsé, validé contre son schema et les invariants sémantiques KSP, puis persisté atomiquement uniquement après validation complète. Le `file_id` doit appartenir au registre et désigner un document Config ; aucun path arbitraire n'est accepté.
|
||||
|
||||
`config/examples/composite.example.json` démontre le format composite sans créer de composite runtime fictif.
|
||||
`config/examples/composite.example.json` conserve l’exemple générique. `config/composite.ksp-app-wallet-desk.json` est le premier composite runtime concret : il sélectionne Logging, Transport et Wallet par `file_id`, sans dépendre de leurs filenames physiques.
|
||||
|
||||
Le fichier local d'environnement est :
|
||||
|
||||
@@ -62,9 +65,9 @@ Les autres crates et applications KSP ne doivent pas :
|
||||
- parser ou écrire directement `.env` ;
|
||||
- ouvrir directement les documents Config connus par leur filename physique ;
|
||||
- réimplémenter la sélection de profils, les compositions ou les placeholders ;
|
||||
- reconstruire elles-mêmes la configuration Logging ou Transport depuis le JSON.
|
||||
- reconstruire elles-mêmes la configuration Logging, Transport ou Wallet depuis le JSON.
|
||||
|
||||
`ksp-config-lib` dépend de `ksp-core-lib` pour `Error`/`Result`, de `ksp-logging-lib` pour les événements Config utiles et le contrat `LoggingSettings`, et de `ksp-onchain-transport-lib` pour construire le contrat runtime Transport dans la direction Config -> Transport.
|
||||
`ksp-config-lib` dépend de `ksp-core-lib` pour `Error`/`Result`, de `ksp-logging-lib` pour les événements Config utiles et le contrat `LoggingSettings`, et de `ksp-onchain-transport-lib` pour construire le contrat runtime Transport dans la direction Config -> Transport. Le document Wallet reste un contrat de chemins/profils Config et n’introduit aucune dépendance Config -> `ksp-wallet-lib`.
|
||||
|
||||
La dépendance inverse est interdite : `ksp-core-lib`, `ksp-logging-lib` et `ksp-onchain-transport-lib` ne dépendent pas de Config.
|
||||
|
||||
@@ -78,7 +81,7 @@ Un secret reste accessible au runtime ou au management lorsqu'un consumer autori
|
||||
|
||||
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
||||
|
||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport les accepte pour les URLs endpoint : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted.
|
||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport les accepte pour les URLs endpoint : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -87,4 +90,6 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
|
||||
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
||||
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard HTTP Transport ;
|
||||
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
||||
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
||||
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/TODO.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# TODO ksp-config-lib
|
||||
|
||||
@@ -35,6 +35,14 @@ Ces points ne nécessitent pas de duplication de logique dans `ksp-config-lib`;
|
||||
|
||||
`0.2.1-pre.006` introduit le premier nouveau domaine standard depuis Logging : `std.transport.json`, son schema, son exemple, son enregistrement et l'adapter Config -> `HttpTransportSettings`. Cette extension confirme que les nouveaux domaines restent ajoutés à la demande d'un consumer réel, sans transformer Config en propriétaire du runtime Transport.
|
||||
|
||||
## Extension `0.2.6` — Wallet Desk
|
||||
|
||||
`0.2.6-pre.003` ajoute le domaine standard Wallet au moment où son premier consumer réel en a besoin : `cfg.std.wallet`, `schema.std.wallet`, `ResolvedWalletConfig` et le composite concret `cfg.composite.ksp-app-wallet-desk`. `wallets_directory` reste global ; `wallets_subdirectory` est optionnel par profil et ne peut pas sortir de la racine par syntaxe de path. L’adapter Config résout les paths et leur provenance mais ne crée pas de répertoire et ne dépend pas de `ksp-wallet-lib`.
|
||||
|
||||
Le même delta ajoute les entrées `resolve_*_config_profile` nécessaires pour mapper un profil Logging/Wallet déjà sélectionné par un composite sans perdre sa provenance `Composite`. La préparation filesystem du répertoire effectif appartient à `ksp-app-wallet-desk`.
|
||||
|
||||
Les futurs passwords `KSP_SECRET_WALLET_PASS_*` restent hors du JSON `std.wallet` et seront introduits dans la tranche d’unlock dédiée ; Config restera leur propriétaire process/`.env`.
|
||||
|
||||
## Futur, uniquement au besoin
|
||||
|
||||
Les capacités suivantes sont différées jusqu'à l'apparition de composants réels :
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Utilisation de ksp-config-lib
|
||||
|
||||
@@ -28,8 +28,10 @@ Les arguments compris par Config sont :
|
||||
```text
|
||||
--cfgpath=/path/to/config
|
||||
--schemapath=/path/to/schemas
|
||||
--filemap=cfg.composite.ksp-app-wallet-desk=my-wallet-desk.json
|
||||
--filemap=cfg.std.logging=my-logging.json
|
||||
--filemap=cfg.std.transport=my-transport.json
|
||||
--filemap=cfg.std.wallet=my-wallet.json
|
||||
```
|
||||
|
||||
`cfgpath` et `schemapath` ne sont jamais lus depuis JSON, `.env` ou une variable KSP : cette règle évite un bootstrap récursif.
|
||||
@@ -140,6 +142,26 @@ Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time:
|
||||
|
||||
La dépendance reste unidirectionnelle : Config connaît le contrat Transport pour le construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
||||
|
||||
### 4.2 Résoudre le répertoire Wallet depuis Config
|
||||
|
||||
`std.wallet` conserve une racine globale et laisse un profil ajouter un sous-répertoire relatif :
|
||||
|
||||
```rust
|
||||
let wallet = match engine.load_resolved_wallet_config(std::option::Option::None, &environment) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
let root = wallet.wallets_directory();
|
||||
let profile_subdirectory = wallet.wallets_subdirectory();
|
||||
let effective = wallet.effective_wallets_directory();
|
||||
let _ = (root, profile_subdirectory, effective);
|
||||
```
|
||||
|
||||
`wallets_directory` accepte `${KSP_WALLETS_DIRECTORY:-wallets}` et une valeur relative est ancrée au current working directory du processus. `wallets_subdirectory` n’accepte que des composants relatifs normaux : chemin absolu, `.` et `..` sont rejetés. Config résout/valide le chemin mais **ne crée pas** les répertoires et n’énumère aucun `.kspwallet`; ces responsabilités appartiennent au consumer applicatif.
|
||||
|
||||
Les chemins Wallet refusent toute valeur `KSP_SECRET_*`. Les futurs `KSP_SECRET_WALLET_PASS_*` constituent un flux de secrets distinct et ne sont pas des champs de `std.wallet.json`.
|
||||
|
||||
## 5. Profils et composites
|
||||
|
||||
Pour un document standard profilé :
|
||||
@@ -152,6 +174,19 @@ let profile = engine.load_resolved_profile(&file_id, std::option::Option::None);
|
||||
|
||||
Un composite référence les documents par `file_id`, jamais par filename. `load_resolved_composite(...)` conserve chaque `ResolvedConfigProfile` composant et sa provenance plutôt que d'aplatir plusieurs domaines dans une map ambiguë.
|
||||
|
||||
Lorsqu’un adapter runtime consomme un profil déjà choisi par un composite, utiliser l’entrée dédiée afin de préserver `ConfigProfileSelectionSource::Composite` :
|
||||
|
||||
```rust
|
||||
let component = match composite.component("wallet") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(/* erreur applicative */),
|
||||
};
|
||||
|
||||
let wallet = engine.resolve_wallet_config_profile(component.resolved(), &environment);
|
||||
```
|
||||
|
||||
La même forme existe pour Logging via `resolve_logging_config_profile`. Le composite concret `cfg.composite.ksp-app-wallet-desk` référence actuellement `logging`, `transport` et `wallet`; Wallet Desk valide ces trois frontières au bootstrap.
|
||||
|
||||
## 6. Management de `std.logging.json`
|
||||
|
||||
Une application de management construit la façade à partir d'un moteur :
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/document.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
/// A Config-managed JSON document that has passed syntax, schema and current semantic validation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -266,6 +266,10 @@ fn validate_document_semantics(engine: &ConfigDocumentEngine, document: &ConfigJ
|
||||
if let std::result::Result::Err(error) = composite_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let wallet_validation = crate::validate_wallet_document_contract(document);
|
||||
if let std::result::Result::Err(error) = wallet_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if document.file_id().as_str() != crate::FILE_ID_STD_LOGGING {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/lib.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -9,8 +9,8 @@
|
||||
//!
|
||||
//! The `0.1.3` surface owns bootstrap roots, the logical file registry, JSON/JSON Schema validation, standard-document profiles, generic composites and
|
||||
//! KSP/KSPB environment resolution through process + `.env` + fallback precedence. Resolved values preserve real/safe representations, sensitivity and
|
||||
//! provenance. Standard Logging and HTTP Transport documents map explicitly to their runtime settings contracts, while the management surface provides
|
||||
//! typed Logging mutation, safe environment reports, explicit privileged reveal calls and atomic JSON/`.env` persistence.
|
||||
//! provenance. Standard Logging, HTTP Transport and Wallet documents map explicitly to their runtime consumer contracts, while the management surface
|
||||
//! provides typed Logging mutation, safe environment reports, explicit privileged reveal calls and atomic JSON/`.env` persistence.
|
||||
|
||||
mod bootstrap;
|
||||
mod composite;
|
||||
@@ -25,6 +25,7 @@ mod profile;
|
||||
mod registry;
|
||||
mod sensitivity;
|
||||
mod transport;
|
||||
mod wallet;
|
||||
|
||||
/// Bootstrap argument used to replace the configuration document root.
|
||||
pub use self::bootstrap::ARG_CFG_PATH;
|
||||
@@ -138,6 +139,8 @@ pub use self::registry::ConfigFileId;
|
||||
pub use self::registry::ConfigFileKind;
|
||||
/// Registry of KSP-known logical Config files and their replaceable physical filenames.
|
||||
pub use self::registry::ConfigFileRegistry;
|
||||
/// Default physical filename for the Wallet Desk composite configuration document.
|
||||
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME;
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub use self::registry::DEFAULT_COMPOSITE_SCHEMA_FILENAME;
|
||||
/// Default physical filename for the standard Logging configuration document.
|
||||
@@ -148,16 +151,26 @@ pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
||||
pub use self::registry::DEFAULT_STD_TRANSPORT_FILENAME;
|
||||
/// Default physical filename for the standard HTTP Transport JSON Schema document.
|
||||
pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME;
|
||||
/// Default physical filename for the standard Wallet configuration document.
|
||||
pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
|
||||
/// Default physical filename for the standard Wallet JSON Schema document.
|
||||
pub use self::registry::DEFAULT_STD_WALLET_SCHEMA_FILENAME;
|
||||
/// Logical file identifier for the Wallet Desk composite configuration document.
|
||||
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_STD_TRANSPORT;
|
||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||
pub use self::registry::FILE_ID_SCHEMA_STD_WALLET;
|
||||
/// Logical file identifier for the standard Logging configuration document.
|
||||
pub use self::registry::FILE_ID_STD_LOGGING;
|
||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
||||
pub use self::registry::FILE_ID_STD_TRANSPORT;
|
||||
/// Logical file identifier for the standard Wallet configuration document.
|
||||
pub use self::registry::FILE_ID_STD_WALLET;
|
||||
/// Sensitivity assigned to one Config value after environment resolution.
|
||||
pub use self::sensitivity::ConfigSensitivity;
|
||||
/// Provenance segment participating in one resolved Config value.
|
||||
@@ -170,6 +183,8 @@ pub use self::sensitivity::ResolvedConfigJson;
|
||||
pub use self::sensitivity::ResolvedConfigText;
|
||||
/// Effective standard HTTP Transport configuration mapped to `ksp_onchain_transport_lib::HttpTransportSettings`.
|
||||
pub use self::transport::ResolvedTransportConfig;
|
||||
/// Effective standard Wallet configuration resolved to validated filesystem roots.
|
||||
pub use self::wallet::ResolvedWalletConfig;
|
||||
|
||||
/// Validates composite document contract.
|
||||
pub(crate) use self::composite::validate_composite_document_contract;
|
||||
@@ -189,3 +204,5 @@ pub(crate) use self::profile::load_resolved_profile_with_source;
|
||||
pub(crate) use self::profile::validate_document_profile_contract;
|
||||
/// Executes the crate-internal build registry operation for the owning module.
|
||||
pub(crate) use self::registry::build_registry;
|
||||
/// Validates standard Wallet source path invariants before environment resolution.
|
||||
pub(crate) use self::wallet::validate_wallet_document_contract;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/logging.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Effective standard Logging configuration resolved from Config and mapped to the Logging runtime contract.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
@@ -103,6 +103,24 @@ impl crate::ConfigDocumentEngine {
|
||||
};
|
||||
return resolve_logging_profile(&profile, environment);
|
||||
}
|
||||
|
||||
/// Maps an already resolved standard Logging profile to the runtime Logging adapter while preserving its selection provenance.
|
||||
///
|
||||
/// This entry point is intended for profiles selected by a composite. The profile must reference `cfg.std.logging`.
|
||||
pub fn resolve_logging_config_profile(
|
||||
&self,
|
||||
profile: &crate::ResolvedConfigProfile,
|
||||
environment: &crate::ConfigEnvironment,
|
||||
) -> ksp_core_lib::Result<ResolvedLoggingConfig> {
|
||||
if profile.file_id().as_str() != crate::FILE_ID_STD_LOGGING {
|
||||
return std::result::Result::Err(effective_error(profile, "resolved Config profile does not reference the standard Logging document"));
|
||||
}
|
||||
let descriptor = self.registry().descriptor(profile.file_id());
|
||||
if let std::result::Result::Err(error) = descriptor {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return resolve_logging_profile(profile, environment);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// file: crates/ksp-config-lib/src/registry.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
/// Bootstrap argument used to replace a known Config filename mapping.
|
||||
pub const ARG_FILE_MAP: &str = "--filemap";
|
||||
/// Default physical filename for the Wallet Desk composite configuration document.
|
||||
pub const DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME: &str = "composite.ksp-app-wallet-desk.json";
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
||||
/// Default physical filename for the standard Logging configuration document.
|
||||
@@ -13,16 +15,26 @@ pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
||||
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
||||
/// Default physical filename for the standard HTTP Transport JSON Schema document.
|
||||
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
||||
/// Default physical filename for the standard Wallet configuration document.
|
||||
pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
|
||||
/// Default physical filename for the standard Wallet JSON Schema document.
|
||||
pub const DEFAULT_STD_WALLET_SCHEMA_FILENAME: &str = "std.wallet.schema.json";
|
||||
/// Logical file identifier for the Wallet Desk composite configuration document.
|
||||
pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-wallet-desk";
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_WALLET: &str = "schema.std.wallet";
|
||||
/// Logical file identifier for the standard Logging configuration document.
|
||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
||||
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
||||
/// Logical file identifier for the standard Wallet configuration document.
|
||||
pub const FILE_ID_STD_WALLET: &str = "cfg.std.wallet";
|
||||
|
||||
/// Stable logical identifier for a Config-managed file.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
@@ -128,6 +140,22 @@ pub struct ConfigFileRegistry {
|
||||
impl ConfigFileRegistry {
|
||||
/// Creates the registry containing the KSP default file mappings known in the current release.
|
||||
pub fn defaults() -> ksp_core_lib::Result<Self> {
|
||||
let composite = ConfigFileDescriptor::new(
|
||||
FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK,
|
||||
ConfigFileKind::Config,
|
||||
DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME,
|
||||
std::option::Option::Some(FILE_ID_SCHEMA_COMPOSITE),
|
||||
);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let composite_schema =
|
||||
ConfigFileDescriptor::new(FILE_ID_SCHEMA_COMPOSITE, ConfigFileKind::Schema, DEFAULT_COMPOSITE_SCHEMA_FILENAME, std::option::Option::None);
|
||||
let composite_schema = match composite_schema {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging = ConfigFileDescriptor::new(
|
||||
FILE_ID_STD_LOGGING,
|
||||
ConfigFileKind::Config,
|
||||
@@ -160,13 +188,23 @@ impl ConfigFileRegistry {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let composite_schema =
|
||||
ConfigFileDescriptor::new(FILE_ID_SCHEMA_COMPOSITE, ConfigFileKind::Schema, DEFAULT_COMPOSITE_SCHEMA_FILENAME, std::option::Option::None);
|
||||
let composite_schema = match composite_schema {
|
||||
let wallet = ConfigFileDescriptor::new(
|
||||
FILE_ID_STD_WALLET,
|
||||
ConfigFileKind::Config,
|
||||
DEFAULT_STD_WALLET_FILENAME,
|
||||
std::option::Option::Some(FILE_ID_SCHEMA_STD_WALLET),
|
||||
);
|
||||
let wallet = match wallet {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::build_registry([logging, logging_schema, transport, transport_schema, composite_schema]);
|
||||
let wallet_schema =
|
||||
ConfigFileDescriptor::new(FILE_ID_SCHEMA_STD_WALLET, ConfigFileKind::Schema, DEFAULT_STD_WALLET_SCHEMA_FILENAME, std::option::Option::None);
|
||||
let wallet_schema = match wallet_schema {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::build_registry([composite, composite_schema, logging, logging_schema, transport, transport_schema, wallet, wallet_schema]);
|
||||
}
|
||||
|
||||
/// Creates the default registry and applies repeatable `--filemap=<file_id>=<filename>` overrides from raw process arguments.
|
||||
|
||||
290
crates/ksp-config-lib/src/wallet.rs
Normal file
290
crates/ksp-config-lib/src/wallet.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
// file: crates/ksp-config-lib/src/wallet.rs
|
||||
// version: 1
|
||||
|
||||
/// Effective standard Wallet configuration resolved from Config.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct ResolvedWalletConfig {
|
||||
effective: crate::ResolvedConfigJson,
|
||||
effective_wallets_directory: std::path::PathBuf,
|
||||
file_id: crate::ConfigFileId,
|
||||
profile_id: String,
|
||||
selection_source: crate::ConfigProfileSelectionSource,
|
||||
source_path: std::path::PathBuf,
|
||||
wallets_directory: std::path::PathBuf,
|
||||
wallets_subdirectory: std::option::Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
impl ResolvedWalletConfig {
|
||||
/// Returns the detailed environment-resolved effective Config view.
|
||||
#[must_use]
|
||||
pub const fn effective(&self) -> &crate::ResolvedConfigJson {
|
||||
return &self.effective;
|
||||
}
|
||||
|
||||
/// Returns the effective Wallet directory after applying the optional profile subdirectory.
|
||||
#[must_use]
|
||||
pub fn effective_wallets_directory(&self) -> &std::path::Path {
|
||||
return self.effective_wallets_directory.as_path();
|
||||
}
|
||||
|
||||
/// Returns the logical Config document identifier used by this runtime configuration.
|
||||
#[must_use]
|
||||
pub const fn file_id(&self) -> &crate::ConfigFileId {
|
||||
return &self.file_id;
|
||||
}
|
||||
|
||||
/// Returns the selected standard Wallet profile identifier.
|
||||
#[must_use]
|
||||
pub fn profile_id(&self) -> &str {
|
||||
return self.profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the source that selected the standard Wallet profile.
|
||||
#[must_use]
|
||||
pub const fn selection_source(&self) -> crate::ConfigProfileSelectionSource {
|
||||
return self.selection_source;
|
||||
}
|
||||
|
||||
/// Returns the physical source Config document path.
|
||||
#[must_use]
|
||||
pub fn source_path(&self) -> &std::path::Path {
|
||||
return self.source_path.as_path();
|
||||
}
|
||||
|
||||
/// Returns the resolved global Wallet root directory.
|
||||
#[must_use]
|
||||
pub fn wallets_directory(&self) -> &std::path::Path {
|
||||
return self.wallets_directory.as_path();
|
||||
}
|
||||
|
||||
/// Returns the validated optional relative Wallet subdirectory selected by the profile.
|
||||
#[must_use]
|
||||
pub fn wallets_subdirectory(&self) -> std::option::Option<&std::path::Path> {
|
||||
return self.wallets_subdirectory.as_deref();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ResolvedWalletConfig {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("ResolvedWalletConfig")
|
||||
.field("effective", &self.effective)
|
||||
.field("effective_wallets_directory", &self.effective_wallets_directory)
|
||||
.field("file_id", &self.file_id)
|
||||
.field("profile_id", &self.profile_id)
|
||||
.field("selection_source", &self.selection_source)
|
||||
.field("source_path", &self.source_path)
|
||||
.field("wallets_directory", &self.wallets_directory)
|
||||
.field("wallets_subdirectory", &self.wallets_subdirectory)
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ConfigDocumentEngine {
|
||||
/// Loads the standard Wallet document, selects a profile, resolves environment placeholders and validates the effective directory contract.
|
||||
///
|
||||
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. `wallets_directory` may be absolute
|
||||
/// or relative to the process current working directory. `wallets_subdirectory` is always relative and may contain nested normal path components only.
|
||||
pub fn load_resolved_wallet_config(
|
||||
&self,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
environment: &crate::ConfigEnvironment,
|
||||
) -> ksp_core_lib::Result<ResolvedWalletConfig> {
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_WALLET);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile = self.load_resolved_profile(&file_id, requested_profile);
|
||||
let profile = match profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return resolve_wallet_profile(&profile, environment);
|
||||
}
|
||||
|
||||
/// Maps an already resolved standard Wallet profile to the runtime Wallet Config adapter.
|
||||
///
|
||||
/// This entry point is intended for profiles selected by a composite and preserves their original selection source instead of reclassifying the profile as
|
||||
/// an explicit selection. The profile must reference `cfg.std.wallet`.
|
||||
pub fn resolve_wallet_config_profile(
|
||||
&self,
|
||||
profile: &crate::ResolvedConfigProfile,
|
||||
environment: &crate::ConfigEnvironment,
|
||||
) -> ksp_core_lib::Result<ResolvedWalletConfig> {
|
||||
if profile.file_id().as_str() != crate::FILE_ID_STD_WALLET {
|
||||
return std::result::Result::Err(effective_error(profile, "resolved Config profile does not reference the standard Wallet document"));
|
||||
}
|
||||
let descriptor = self.registry().descriptor(profile.file_id());
|
||||
if let std::result::Result::Err(error) = descriptor {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return resolve_wallet_profile(profile, environment);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct EffectiveWalletSource {
|
||||
format_version: u32,
|
||||
profile_id: String,
|
||||
wallets_directory: String,
|
||||
wallets_subdirectory: std::option::Option<String>,
|
||||
}
|
||||
|
||||
/// Validates standard Wallet source path invariants that are meaningful before environment resolution.
|
||||
pub(crate) fn validate_wallet_document_contract(document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
if document.file_id().as_str() != crate::FILE_ID_STD_WALLET {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
let profiles = document.value().get("profiles").and_then(serde_json::Value::as_array);
|
||||
let profiles = match profiles {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(()),
|
||||
};
|
||||
for (profile_index, profile) in profiles.iter().enumerate() {
|
||||
let subdirectory = profile.get("wallets_subdirectory").and_then(serde_json::Value::as_str);
|
||||
if let std::option::Option::Some(value) = subdirectory {
|
||||
let validation = validate_relative_subdirectory(value);
|
||||
if let std::result::Result::Err(_) = validation {
|
||||
return std::result::Result::Err(
|
||||
wallet_document_error(document, "wallets_subdirectory must contain only relative normal path components")
|
||||
.with_context("profile_index", profile_index.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn resolve_wallet_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedWalletConfig> {
|
||||
let effective = profile.resolve_effective_environment_detailed(environment);
|
||||
let effective = match effective {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if effective.sensitivity().is_secret() {
|
||||
return std::result::Result::Err(effective_error(profile, "standard Wallet configuration must not consume Secret environment values"));
|
||||
}
|
||||
let source = serde_json::from_value::<EffectiveWalletSource>(effective.value().clone());
|
||||
let source = match source {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
effective_error(profile, "effective Wallet Config cannot be decoded into the runtime adapter contract").with_source(error),
|
||||
);
|
||||
},
|
||||
};
|
||||
if source.format_version != 1 {
|
||||
return std::result::Result::Err(effective_error(profile, "effective Wallet format_version is unsupported"));
|
||||
}
|
||||
if source.profile_id != profile.profile_id() {
|
||||
return std::result::Result::Err(effective_error(profile, "effective Wallet profile_id does not match the selected profile"));
|
||||
}
|
||||
let wallets_directory = resolve_wallets_directory(source.wallets_directory.as_str(), profile);
|
||||
let wallets_directory = match wallets_directory {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallets_subdirectory = resolve_wallets_subdirectory(source.wallets_subdirectory.as_deref(), profile);
|
||||
let wallets_subdirectory = match wallets_subdirectory {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let effective_wallets_directory = match &wallets_subdirectory {
|
||||
std::option::Option::Some(value) => wallets_directory.join(value),
|
||||
std::option::Option::None => wallets_directory.clone(),
|
||||
};
|
||||
return std::result::Result::Ok(ResolvedWalletConfig {
|
||||
effective,
|
||||
effective_wallets_directory,
|
||||
file_id: profile.file_id().clone(),
|
||||
profile_id: profile.profile_id().to_owned(),
|
||||
selection_source: profile.selection_source(),
|
||||
source_path: profile.path().to_path_buf(),
|
||||
wallets_directory,
|
||||
wallets_subdirectory,
|
||||
});
|
||||
}
|
||||
|
||||
fn resolve_wallets_directory(value: &str, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<std::path::PathBuf> {
|
||||
if value.trim().is_empty() {
|
||||
return std::result::Result::Err(effective_field_error(profile, "wallets_directory", "effective Wallet wallets_directory must not be empty"));
|
||||
}
|
||||
let path = std::path::PathBuf::from(value);
|
||||
if path.is_absolute() {
|
||||
return std::result::Result::Ok(path);
|
||||
}
|
||||
let current_directory = std::env::current_dir();
|
||||
return match current_directory {
|
||||
std::result::Result::Ok(current_directory) => std::result::Result::Ok(current_directory.join(path)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
effective_field_error(profile, "wallets_directory", "process current working directory cannot be resolved").with_source(error),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn resolve_wallets_subdirectory(
|
||||
value: std::option::Option<&str>,
|
||||
profile: &crate::ResolvedConfigProfile,
|
||||
) -> ksp_core_lib::Result<std::option::Option<std::path::PathBuf>> {
|
||||
let value = match value {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
let validation = validate_relative_subdirectory(value);
|
||||
if let std::result::Result::Err(()) = validation {
|
||||
return std::result::Result::Err(effective_field_error(
|
||||
profile,
|
||||
"wallets_subdirectory",
|
||||
"effective Wallet wallets_subdirectory must contain only relative normal path components",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(std::option::Option::Some(std::path::PathBuf::from(value)));
|
||||
}
|
||||
|
||||
fn validate_relative_subdirectory(value: &str) -> std::result::Result<(), ()> {
|
||||
if value.trim().is_empty() {
|
||||
return std::result::Result::Err(());
|
||||
}
|
||||
let path = std::path::Path::new(value);
|
||||
if path.is_absolute() {
|
||||
return std::result::Result::Err(());
|
||||
}
|
||||
let mut component_count: usize = 0;
|
||||
for component in path.components() {
|
||||
match component {
|
||||
std::path::Component::Normal(_) => component_count += 1,
|
||||
std::path::Component::CurDir | std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_) => {
|
||||
return std::result::Result::Err(());
|
||||
},
|
||||
}
|
||||
}
|
||||
if component_count == 0 {
|
||||
return std::result::Result::Err(());
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn wallet_document_error(document: &crate::ConfigJsonDocument, reason: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID, "standard Wallet Config violates KSP semantic invariants")
|
||||
.with_context("file_id", document.file_id().as_str())
|
||||
.with_context("path", document.path().to_string_lossy().into_owned())
|
||||
.with_context("reason", reason);
|
||||
}
|
||||
|
||||
fn effective_field_error(profile: &crate::ResolvedConfigProfile, field: &'static str, reason: &'static str) -> ksp_core_lib::Error {
|
||||
return effective_error(profile, reason).with_context("field", field);
|
||||
}
|
||||
|
||||
fn effective_error(profile: &crate::ResolvedConfigProfile, reason: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID, "effective Config cannot be mapped to the requested runtime contract")
|
||||
.with_context("file_id", profile.file_id().as_str())
|
||||
.with_context("path", profile.path().to_string_lossy().into_owned())
|
||||
.with_context("profile_id", profile.profile_id())
|
||||
.with_context("reason", reason);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/wallet.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/tests/ownership.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Workspace ownership audits for KSP application configuration boundaries.
|
||||
|
||||
@@ -241,11 +241,14 @@ fn workspace_crates_do_not_hardcode_config_managed_physical_files() {
|
||||
};
|
||||
for token in [
|
||||
"\".env\"",
|
||||
"\"composite.ksp-app-wallet-desk.json\"",
|
||||
"\"composite.schema.json\"",
|
||||
"\"std.logging.json\"",
|
||||
"\"std.logging.schema.json\"",
|
||||
"\"std.transport.json\"",
|
||||
"\"std.transport.schema.json\"",
|
||||
"\"composite.schema.json\"",
|
||||
"\"std.wallet.json\"",
|
||||
"\"std.wallet.schema.json\"",
|
||||
] {
|
||||
assert!(!source.contains(token), "{} hardcodes Config-managed physical resource {token}; use ksp-config-lib contracts", rust_file.display());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/tests/public_api.rs
|
||||
// version: 17
|
||||
// version: 19
|
||||
|
||||
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
|
||||
//! Logging/Transport adapters and management contracts.
|
||||
@@ -80,18 +80,19 @@ fn registry_descriptor_inventory_is_available_from_crate_root() {
|
||||
assert!(registry.is_ok(), "public registry should remain constructible: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect();
|
||||
assert_eq!(descriptors.len(), 5);
|
||||
assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[0].kind(), ksp_config_lib::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[1].kind(), ksp_config_lib::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
let schema_file_id = descriptors[1].schema_file_id();
|
||||
assert!(schema_file_id.is_some(), "public Transport descriptor should preserve schema association");
|
||||
assert_eq!(descriptors.len(), 8);
|
||||
assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_STD_WALLET);
|
||||
assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[5].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[6].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[7].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET);
|
||||
let schema_file_id = descriptors[3].schema_file_id();
|
||||
assert!(schema_file_id.is_some(), "public Wallet descriptor should preserve schema association");
|
||||
if let std::option::Option::Some(schema_file_id) = schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +199,8 @@ fn sensitivity_and_safe_resolution_contracts_are_available_from_crate_root() {
|
||||
#[test]
|
||||
fn logging_adapter_contract_is_available_from_crate_root() {
|
||||
let adapter = ksp_config_lib::ConfigDocumentEngine::load_resolved_logging_config;
|
||||
let _ = adapter;
|
||||
let composite_adapter = ksp_config_lib::ConfigDocumentEngine::resolve_logging_config_profile;
|
||||
let _ = (adapter, composite_adapter);
|
||||
assert_eq!(ksp_config_lib::ERROR_CODE_EFFECTIVE_CONFIG_INVALID.domain(), "config");
|
||||
assert_eq!(ksp_config_lib::ERROR_CODE_EFFECTIVE_CONFIG_INVALID.code(), "effective_config_invalid");
|
||||
assert!(std::mem::size_of::<ksp_config_lib::ResolvedLoggingConfig>() > 0);
|
||||
@@ -254,3 +256,16 @@ fn transport_adapter_contract_is_available_from_crate_root() {
|
||||
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_FILENAME, "std.transport.json");
|
||||
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME, "std.transport.schema.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_adapter_contract_is_available_from_crate_root() {
|
||||
let _loader = ksp_config_lib::ConfigDocumentEngine::load_resolved_wallet_config;
|
||||
let _composite_loader = ksp_config_lib::ConfigDocumentEngine::resolve_wallet_config_profile;
|
||||
assert!(std::mem::size_of::<ksp_config_lib::ResolvedWalletConfig>() > 0);
|
||||
assert_eq!(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK, "cfg.composite.ksp-app-wallet-desk");
|
||||
assert_eq!(ksp_config_lib::FILE_ID_STD_WALLET, "cfg.std.wallet");
|
||||
assert_eq!(ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET, "schema.std.wallet");
|
||||
assert_eq!(ksp_config_lib::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME, "composite.ksp-app-wallet-desk.json");
|
||||
assert_eq!(ksp_config_lib::DEFAULT_STD_WALLET_FILENAME, "std.wallet.json");
|
||||
assert_eq!(ksp_config_lib::DEFAULT_STD_WALLET_SCHEMA_FILENAME, "std.wallet.schema.json");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/registry.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
#[test]
|
||||
fn descriptors_expose_complete_registry_in_deterministic_file_id_order() {
|
||||
@@ -7,29 +7,21 @@ fn descriptors_expose_complete_registry_in_deterministic_file_id_order() {
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect();
|
||||
assert_eq!(descriptors.len(), 5);
|
||||
assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[0].kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_FILENAME));
|
||||
let logging_schema_file_id = descriptors[0].schema_file_id();
|
||||
assert!(logging_schema_file_id.is_some(), "logging descriptor should expose its validation schema");
|
||||
if let std::option::Option::Some(schema_file_id) = logging_schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
}
|
||||
assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[1].kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[1].filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_FILENAME));
|
||||
let transport_schema_file_id = descriptors[1].schema_file_id();
|
||||
assert!(transport_schema_file_id.is_some(), "transport descriptor should expose its validation schema");
|
||||
if let std::option::Option::Some(schema_file_id) = transport_schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
}
|
||||
assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[2].kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[3].kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[4].kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors.len(), 8);
|
||||
assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME));
|
||||
assert_eq!(descriptors[0].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE));
|
||||
assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_WALLET);
|
||||
assert_eq!(descriptors[3].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME));
|
||||
assert_eq!(descriptors[3].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET));
|
||||
assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET);
|
||||
assert!(descriptors[0..4].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config));
|
||||
assert!(descriptors[4..8].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +110,39 @@ fn defaults_register_transport_document_and_schema_with_distinct_roots() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_register_wallet_document_schema_and_wallet_desk_composite() {
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let wallet_id = crate::ConfigFileId::new(crate::FILE_ID_STD_WALLET);
|
||||
let wallet_schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_WALLET);
|
||||
let composite_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert!(wallet_id.is_ok());
|
||||
assert!(wallet_schema_id.is_ok());
|
||||
assert!(composite_id.is_ok());
|
||||
if let (std::result::Result::Ok(wallet_id), std::result::Result::Ok(wallet_schema_id), std::result::Result::Ok(composite_id)) =
|
||||
(wallet_id, wallet_schema_id, composite_id)
|
||||
{
|
||||
let wallet = registry.descriptor(&wallet_id);
|
||||
let wallet_schema = registry.descriptor(&wallet_schema_id);
|
||||
let composite = registry.descriptor(&composite_id);
|
||||
assert!(wallet.is_ok());
|
||||
assert!(wallet_schema.is_ok());
|
||||
assert!(composite.is_ok());
|
||||
if let (std::result::Result::Ok(wallet), std::result::Result::Ok(wallet_schema), std::result::Result::Ok(composite)) =
|
||||
(wallet, wallet_schema, composite)
|
||||
{
|
||||
assert_eq!(wallet.filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME));
|
||||
assert_eq!(wallet.schema_file_id(), std::option::Option::Some(&wallet_schema_id));
|
||||
assert_eq!(wallet_schema.filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_SCHEMA_FILENAME));
|
||||
assert_eq!(composite.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME));
|
||||
assert_eq!(composite.schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_uses_descriptor_kind_to_select_bootstrap_root() {
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
|
||||
200
crates/ksp-config-lib/unit_tests/wallet.rs
Normal file
200
crates/ksp-config-lib/unit_tests/wallet.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/wallet.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn default_wallet_config_resolves_global_root_without_subdirectory() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = engine.load_resolved_wallet_config(std::option::Option::Some("default"), &environment);
|
||||
assert!(resolved.is_ok(), "default Wallet profile should resolve: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.file_id().as_str(), crate::FILE_ID_STD_WALLET);
|
||||
assert_eq!(resolved.profile_id(), "default");
|
||||
assert!(resolved.wallets_subdirectory().is_none());
|
||||
assert!(resolved.wallets_directory().is_absolute());
|
||||
assert_eq!(resolved.effective_wallets_directory(), resolved.wallets_directory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_profile_appends_relative_subdirectory() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = engine.load_resolved_wallet_config(std::option::Option::Some("tests"), &environment);
|
||||
assert!(resolved.is_ok(), "tests Wallet profile should resolve: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.wallets_subdirectory(), std::option::Option::Some(std::path::Path::new("tests")));
|
||||
let expected = resolved.wallets_directory().join("tests");
|
||||
assert_eq!(resolved.effective_wallets_directory(), expected.as_path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_directory_environment_override_uses_config_precedence() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_WALLETS_DIRECTORY".to_owned(), "process-wallets".to_owned());
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_WALLETS_DIRECTORY".to_owned(), "dotenv-wallets".to_owned());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, dotenv);
|
||||
let resolved = engine.load_resolved_wallet_config(std::option::Option::Some("default"), &environment);
|
||||
assert!(resolved.is_ok(), "Wallet environment override should resolve: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert!(resolved.wallets_directory().ends_with("process-wallets"));
|
||||
assert!(resolved.effective().provenance_at("/wallets_directory").is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsafe_wallet_subdirectories_are_rejected() {
|
||||
for (name, value) in [("absolute", "/escape"), ("parent", "../escape"), ("nested-parent", "tests/../escape"), ("current", "./tests")] {
|
||||
let fixture = fixture_roots(name);
|
||||
let prepared = prepare_wallet_fixture(&fixture, value);
|
||||
assert!(prepared.is_ok(), "wallet fixture should be writable: {prepared:?}");
|
||||
if prepared.is_ok() {
|
||||
let engine = fixture_engine(&fixture);
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_WALLET);
|
||||
assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}");
|
||||
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
||||
let result = engine.load_validated_document(&file_id);
|
||||
assert!(result.is_err(), "unsafe wallets_subdirectory must be rejected: {name} {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup_fixture(&fixture);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composite_selected_wallet_profile_preserves_composite_provenance() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let composite_file_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert!(composite_file_id.is_ok(), "Wallet Desk composite file_id should be valid: {composite_file_id:?}");
|
||||
if let std::result::Result::Ok(composite_file_id) = composite_file_id {
|
||||
let composite = engine.load_resolved_composite(&composite_file_id, std::option::Option::None);
|
||||
assert!(composite.is_ok(), "Wallet Desk composite should resolve: {composite:?}");
|
||||
if let std::result::Result::Ok(composite) = composite {
|
||||
let component = composite.component("wallet");
|
||||
assert!(component.is_some(), "Wallet Desk composite should expose wallet component");
|
||||
if let std::option::Option::Some(component) = component {
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = engine.resolve_wallet_config_profile(component.resolved(), &environment);
|
||||
assert!(resolved.is_ok(), "composite Wallet profile should map through the runtime adapter: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.profile_id(), "default");
|
||||
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Composite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_wallet_directory_placeholder_is_rejected_by_effective_adapter() {
|
||||
let fixture = fixture_roots("secret-root");
|
||||
let prepared = prepare_wallet_root_fixture(&fixture, "${KSP_SECRET_WALLET_DIRECTORY}", std::option::Option::None);
|
||||
assert!(prepared.is_ok(), "wallet fixture should be writable: {prepared:?}");
|
||||
if prepared.is_ok() {
|
||||
let engine = fixture_engine(&fixture);
|
||||
assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_SECRET_WALLET_DIRECTORY".to_owned(), "hidden-wallets".to_owned());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let result = engine.load_resolved_wallet_config(std::option::Option::Some("default"), &environment);
|
||||
assert!(result.is_err(), "Wallet path Config must reject Secret environment sensitivity: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup_fixture(&fixture);
|
||||
}
|
||||
|
||||
fn workspace_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||
let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(root.join("config"), root.join("config/schemas"));
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
return match registry {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
fn fixture_engine(fixture: &FixtureRoots) -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture.config.as_path(), fixture.schemas.as_path());
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
return match registry {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
fn prepare_wallet_fixture(fixture: &FixtureRoots, subdirectory: &str) -> std::io::Result<()> {
|
||||
return prepare_wallet_root_fixture(fixture, "wallets", std::option::Option::Some(subdirectory));
|
||||
}
|
||||
|
||||
fn prepare_wallet_root_fixture(fixture: &FixtureRoots, root: &str, subdirectory: std::option::Option<&str>) -> std::io::Result<()> {
|
||||
cleanup_fixture(fixture);
|
||||
let config = std::fs::create_dir_all(fixture.config.as_path());
|
||||
if let std::result::Result::Err(error) = config {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let schemas = std::fs::create_dir_all(fixture.schemas.as_path());
|
||||
if let std::result::Result::Err(error) = schemas {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let schema = std::fs::copy(workspace.join("config/schemas/std.wallet.schema.json"), fixture.schemas.join(crate::DEFAULT_STD_WALLET_SCHEMA_FILENAME));
|
||||
if let std::result::Result::Err(error) = schema {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let profile = match subdirectory {
|
||||
std::option::Option::Some(value) => format!(r#"{{"profile_id":"default","wallets_subdirectory":"{value}"}}"#),
|
||||
std::option::Option::None => "{\"profile_id\":\"default\"}".to_owned(),
|
||||
};
|
||||
let source = format!(r#"{{"format_version":1,"wallets_directory":"{root}","default_profile":"default","profiles":[{profile}]}}"#);
|
||||
return std::fs::write(fixture.config.join(crate::DEFAULT_STD_WALLET_FILENAME), source);
|
||||
}
|
||||
|
||||
fn fixture_roots(name: &str) -> FixtureRoots {
|
||||
let root = std::env::temp_dir().join(format!("ksp-config-wallet-{name}-{}", std::process::id()));
|
||||
return FixtureRoots { config: root.join("config"), root: root.clone(), schemas: root.join("schemas") };
|
||||
}
|
||||
|
||||
fn cleanup_fixture(fixture: &FixtureRoots) {
|
||||
let removal = std::fs::remove_dir_all(fixture.root.as_path());
|
||||
if let std::result::Result::Err(error) = removal
|
||||
&& error.kind() != std::io::ErrorKind::NotFound
|
||||
{
|
||||
eprintln!("unable to cleanup Config Wallet fixture {}: {error}", fixture.root.display());
|
||||
}
|
||||
}
|
||||
|
||||
struct FixtureRoots {
|
||||
config: std::path::PathBuf,
|
||||
root: std::path::PathBuf,
|
||||
schemas: std::path::PathBuf,
|
||||
}
|
||||
247
deltas/0.2.6/pre.003.md
Normal file
247
deltas/0.2.6/pre.003.md
Normal file
@@ -0,0 +1,247 @@
|
||||
<!-- file: deltas/0.2.6/pre.003.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.6-pre.003` — `std.wallet`, composite Wallet Desk et répertoires
|
||||
|
||||
## Base requise
|
||||
|
||||
```text
|
||||
0.2.6-pre.002 appliquée
|
||||
workspace.package.version = 0.2.6-pre.2
|
||||
```
|
||||
|
||||
Les preuves opérateur de `pre.002` sont vertes : installation npm initiale, `cargo fmt --all`, audit structurel Python, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo test -p ksp-app-wallet-desk` et `cargo tauri dev -c crates/ksp-app-wallet-desk/tauri.conf.json`. Le shell Tauri/Vite a démarré sur le port réservé `1432`.
|
||||
|
||||
## Type de livraison
|
||||
|
||||
```text
|
||||
ksp-general-0.2.6-pre.003.zip
|
||||
```
|
||||
|
||||
Cette tranche modifie Config, le runtime Wallet Desk et les fichiers de configuration exécutables. Elle incrémente donc `workspace.package.version` vers `0.2.6-pre.3`.
|
||||
|
||||
## Objet
|
||||
|
||||
Matérialiser la composition Config nécessaire à Wallet Desk sans ouvrir encore un fichier `.kspwallet` ni implémenter l'inventory de `pre.004`.
|
||||
|
||||
Le delta fournit :
|
||||
|
||||
```text
|
||||
cfg.std.wallet
|
||||
schema.std.wallet
|
||||
cfg.composite.ksp-app-wallet-desk
|
||||
wallets_directory global
|
||||
wallets_subdirectory optionnel par profil
|
||||
profils Wallet default / temporary / tests
|
||||
ResolvedWalletConfig
|
||||
mapping d'un profil déjà résolu par composite vers Logging/Wallet
|
||||
préservation ConfigProfileSelectionSource::Composite
|
||||
bootstrap Wallet Desk logging + transport + wallet component validation
|
||||
création automatique du répertoire global/effectif
|
||||
rejet de root non-directory et de symlinks dans le sous-répertoire de profil
|
||||
logs debug sur directory ready/created
|
||||
logs error sur préparation impossible
|
||||
statut runtime/UI Config + chemins Wallet sûrs
|
||||
```
|
||||
|
||||
## `std.wallet`
|
||||
|
||||
Le document standard est enregistré sous :
|
||||
|
||||
```text
|
||||
file_id = cfg.std.wallet
|
||||
schema_file_id = schema.std.wallet
|
||||
filename = std.wallet.json
|
||||
schema filename = std.wallet.schema.json
|
||||
```
|
||||
|
||||
Shape runtime :
|
||||
|
||||
```json
|
||||
{
|
||||
"format_version": 1,
|
||||
"wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}",
|
||||
"default_profile": "default",
|
||||
"profiles": [
|
||||
{"profile_id": "default"},
|
||||
{"profile_id": "temporary", "wallets_subdirectory": "temporary"},
|
||||
{"profile_id": "tests", "wallets_subdirectory": "tests"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`wallets_directory` est global. `wallets_subdirectory` reste relatif, optionnel et peut contenir plusieurs composants normaux. Chemin absolu, `.` ou `..` sont rejetés. Les valeurs de sensibilité `Secret` sont refusées par l'adapter Wallet : un password ne peut donc pas être injecté comme path Config.
|
||||
|
||||
`ksp-config-lib` résout et valide les paths mais ne crée aucun répertoire et n'énumère aucun wallet.
|
||||
|
||||
## Composite Wallet Desk
|
||||
|
||||
Le premier composite runtime concret est :
|
||||
|
||||
```text
|
||||
file_id = cfg.composite.ksp-app-wallet-desk
|
||||
filename = composite.ksp-app-wallet-desk.json
|
||||
schema = schema.composite
|
||||
```
|
||||
|
||||
Le profil autonome `devnet` sélectionne :
|
||||
|
||||
```text
|
||||
logging -> cfg.std.logging / local_dev
|
||||
transport -> cfg.std.transport / devnet_public
|
||||
wallet -> cfg.std.wallet / default
|
||||
```
|
||||
|
||||
Les profils composites `temporary` et `tests` réutilisent Logging/Transport mais sélectionnent respectivement les profils Wallet `temporary` et `tests`.
|
||||
|
||||
Wallet Desk exige les trois `component_id` et vérifie leurs `file_id` attendus avant de poursuivre son bootstrap. Le pool Transport n'est pas encore construit dans cette tranche ; cette composition est validée maintenant pour ne pas inventer une sélection parallèle plus tard.
|
||||
|
||||
## Adapters Config
|
||||
|
||||
`ResolvedWalletConfig` expose :
|
||||
|
||||
```text
|
||||
file_id
|
||||
source_path
|
||||
profile_id
|
||||
selection_source
|
||||
effective Config sûr/réel selon contrat Config
|
||||
wallets_directory
|
||||
wallets_subdirectory
|
||||
effective_wallets_directory
|
||||
```
|
||||
|
||||
Deux entrées complètent les adapters pour un profil déjà sélectionné par composite :
|
||||
|
||||
```text
|
||||
ConfigDocumentEngine::resolve_logging_config_profile
|
||||
ConfigDocumentEngine::resolve_wallet_config_profile
|
||||
```
|
||||
|
||||
Elles évitent de recharger le même profil comme sélection `Explicit` et conservent `ConfigProfileSelectionSource::Composite`.
|
||||
|
||||
Cette extension ne crée aucune dépendance Config -> `ksp-wallet-lib`.
|
||||
|
||||
## Répertoires Wallet
|
||||
|
||||
Après initialisation du Logging, Wallet Desk résout le composant Wallet et prépare la filesystem surface applicative :
|
||||
|
||||
```text
|
||||
wallets_directory
|
||||
-> création si absent
|
||||
-> optional wallets_subdirectory composant par composant
|
||||
-> effective_wallets_directory prêt
|
||||
```
|
||||
|
||||
Politique de cette tranche :
|
||||
|
||||
```text
|
||||
root déjà directory -> accepté
|
||||
root absent -> create_dir_all
|
||||
root existant non-directory -> wallet_desk.wallet_directory_invalid
|
||||
profile component absent -> create_dir
|
||||
profile component directory -> accepté
|
||||
profile component symlink -> wallet_desk.wallet_directory_invalid
|
||||
profile component fichier -> wallet_desk.wallet_directory_invalid
|
||||
inspection/création I/O KO -> wallet_desk.wallet_directory_prepare_failed
|
||||
```
|
||||
|
||||
Un succès/prêt est journalisé en `debug`. Une impossibilité de préparation est journalisée en `error` puis surfacée au bootstrap. Les paths sont non secrets par contrat `std.wallet`.
|
||||
|
||||
L'inventory `.kspwallet`, la canonicalisation des fichiers candidats et la policy complète de sélection de fichiers restent `pre.004`.
|
||||
|
||||
## Environnement
|
||||
|
||||
`/.env.example` documente désormais :
|
||||
|
||||
```text
|
||||
KSP_WALLETS_DIRECTORY=wallets
|
||||
# KSP_SECRET_WALLET_PASS_01=
|
||||
```
|
||||
|
||||
La seconde ligne documente uniquement le pattern prévu pour la future tranche d'unlock. Aucun provider de password n'est implémenté dans `pre.003` et aucune valeur `KSP_SECRET_WALLET_PASS_*` n'est lue par Wallet Desk à ce stade.
|
||||
|
||||
## UI/runtime status
|
||||
|
||||
Le DTO `RuntimeStatusDto` et le Dashboard affichent maintenant :
|
||||
|
||||
```text
|
||||
version / phase
|
||||
nombre de ressources Config enregistrées
|
||||
profil composite
|
||||
profil Logging
|
||||
profil Wallet
|
||||
fallback Logging
|
||||
wallets_directory
|
||||
wallets_subdirectory
|
||||
répertoire Wallet effectif
|
||||
création du répertoire effectif au démarrage oui/non
|
||||
```
|
||||
|
||||
Aucune Pubkey, aucun alias interne Wallet, aucune note et aucun secret ne sont ajoutés à cette projection.
|
||||
|
||||
## Tests ajoutés/étendus
|
||||
|
||||
Config :
|
||||
|
||||
```text
|
||||
registre à 8 descripteurs
|
||||
cfg.std.wallet + schema + composite enregistrés
|
||||
schema std.wallet
|
||||
root global sans subdirectory
|
||||
profil tests -> subdirectory relatif
|
||||
precedence process > .env pour KSP_WALLETS_DIRECTORY
|
||||
rejet absolute / parent / current subdirectories
|
||||
rejet d'un path Wallet de sensibilité Secret
|
||||
profil Wallet sélectionné par composite conserve provenance Composite
|
||||
public API des nouveaux adapters/constants
|
||||
ownership des filenames Config-managed
|
||||
```
|
||||
|
||||
Wallet Desk :
|
||||
|
||||
```text
|
||||
composite devnet -> logging/local_dev + transport/devnet_public + wallet/default
|
||||
création idempotente root + nested profile directory
|
||||
rejet root non-directory
|
||||
rejet symlink de profile path sur Unix
|
||||
statut frontend expose les projections Config prévues
|
||||
```
|
||||
|
||||
## Dépendances
|
||||
|
||||
Aucune dépendance Cargo ou npm n'est ajoutée/modifiée dans cette tranche. `package.json` et `tauri.conf.json` changent uniquement leur version applicative vers `0.2.6-pre.3`.
|
||||
|
||||
Il n'y a donc **aucune commande npm directe à exécuter pour `pre.003`**.
|
||||
|
||||
## Version technique
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.2.6-pre.3
|
||||
commit = v0.2.6-pre.003
|
||||
```
|
||||
|
||||
Aucun tag stable.
|
||||
|
||||
## Validation opérateur requise
|
||||
|
||||
Après application :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-config-lib
|
||||
cargo test -p ksp-app-wallet-desk
|
||||
cargo test --workspace
|
||||
KSP_WALLETS_DIRECTORY=var/wallet-desk-pre003 cargo tauri dev -c crates/ksp-app-wallet-desk/tauri.conf.json
|
||||
```
|
||||
|
||||
Le parcours Tauri doit vérifier que le Dashboard affiche le profil composite `devnet`, le profil Wallet `default`, la racine override `var/wallet-desk-pre003` résolue et le répertoire effectif prêt/créé, avec événement backend `debug` correspondant. Le répertoire `var/` est déjà ignoré par le repository.
|
||||
|
||||
`cargo tauri build -c crates/ksp-app-wallet-desk/tauri.conf.json` **n'est pas exécuté en `pre.003`**. Il reste la toute dernière opération de validation de `0.2.6`.
|
||||
|
||||
## Suite
|
||||
|
||||
`pre.004` branche l'inventory réel : enumeration non récursive `.kspwallet`, policy symlink/path, `inspect_locked_wallet_file_v1`, DTOs locked, DataTables, sélection de ligne et refresh, sans révéler Pubkey/alias/notes.
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
|
||||
<!-- version: 11 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# Graphe de dépendances KSP
|
||||
|
||||
@@ -388,7 +388,7 @@ ksp-app-wallet-desk
|
||||
-> ksp-logging-lib
|
||||
```
|
||||
|
||||
L'app compose ; elle ne déplace pas Config/Wallet/Transport dans Tauri. Les handles `WalletView`/`WalletOwner` restent côté Rust, et le frontend ne reçoit que des DTOs sûrs. Config peut fournir la racine Wallet et le profil Transport ; les passwords et le secret Solana ne deviennent jamais des valeurs Config.
|
||||
L'app compose ; elle ne déplace pas Config/Wallet/Transport dans Tauri. Les handles `WalletView`/`WalletOwner` restent côté Rust, et le frontend ne reçoit que des DTOs sûrs. Config fournit la racine/profil Wallet et le profil Transport. Les passwords Wallet ne deviennent jamais des champs des documents JSON Config ; ils peuvent être saisis éphémèrement frontend -> Rust ou provenir plus tard de secrets process/`.env` `KSP_SECRET_WALLET_PASS_*` possédés exclusivement par Config. Le secret Solana ne devient jamais une valeur Config ni un DTO frontend.
|
||||
|
||||
## Price Desk
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/000-README.md -->
|
||||
<!-- version: 46 -->
|
||||
<!-- version: 48 -->
|
||||
|
||||
# Plans KSP
|
||||
|
||||
@@ -21,7 +21,7 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou
|
||||
- [`010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de la release stable `0.2.3 — HTTP Transactions`, ouvert par `pre.001`, exécuté jusqu'à `pre.009` puis publié par `rel.001`; il couvre les 11 méthodes, la classification `8 Read / 2 WriteSubmission / 1 Simulation`, `KSP-TRANSPORT-007`, le no-resend et la préparation de `0.2.4`.
|
||||
- [`011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan historique clôturé de la release stable `0.2.4`, ouvert par `pre.001`, exécuté jusqu’à `pre.009`, complété par le fix documentaire Wallet `pre.009-fix.001` puis publié par `rel.001`; il couvre les 10 Blocks + 5 Economics et la compliance finale `52/52 + 14/14` sous `KSP-TRANSPORT-007`.
|
||||
- [`012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](012-V0_2_5_WALLET_FOUNDATION_PLAN.md) — plan historique clôturé de la release stable `0.2.5 — Wallet foundation`, ouvert par `pre.001`, livré jusqu’à `pre.010`, renforcé par `pre.010-fix.001`–`fix.003` pour Dalek 3 et la normalisation Rust/audit structurel, puis publié par `rel.001`; il couvre `.kspwallet` V1, VIEW/OWNER, crypto, persistence, administration, transfer et compliance.
|
||||
- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; il fixe la composition Config/Wallet/Transport/Logging, `std.wallet`, inventory/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, threat model Tauri, balance HTTP, administration retenue et forecast regranularisé jusqu’à `pre.013`.
|
||||
- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; `pre.002` matérialise le shell Tauri et `pre.003` la composition `std.wallet`/composite ainsi que la préparation des répertoires. Le plan fixe aussi secrets `KSP_SECRET_WALLET_PASS_*`, inventory DataTables/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, balance HTTP, administration retenue et forecast souple détaillé jusqu’à `pre.014`, incluant la documentation finale et le prompt `0.2.7`.
|
||||
|
||||
Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
||||
<!-- version: 56 -->
|
||||
<!-- version: 58 -->
|
||||
|
||||
# Séquence des releases fonctionnelles KSP
|
||||
|
||||
@@ -424,11 +424,13 @@ La release fournit `docs/formats/KSPWALLET_V1.md` comme spécification séparée
|
||||
|
||||
Mission : valider Config composite + `.kspwallet` + transport HTTP dans une application Tauri mince.
|
||||
|
||||
Le solde d'un wallet constitue un premier cas de validation réseau obligatoire. L'application garde les handles VIEW/OWNER côté Rust, ne persiste aucun password dans Config/frontend et délègue toute crypto/signature/administration à `ksp-wallet-lib`.
|
||||
Le solde d'un wallet constitue un premier cas de validation réseau obligatoire. L'application garde les handles VIEW/OWNER côté Rust, ne persiste aucun password dans les documents JSON Config ni dans le frontend et délègue toute crypto/signature/administration à `ksp-wallet-lib`. Des secrets opérateur `KSP_SECRET_WALLET_PASS_*` peuvent être fournis par process env ou `.env`, exclusivement via les APIs Config et sans traverser le frontend.
|
||||
|
||||
Le prompt [`../../prompts/011-V0_2_6_START_PROMPT.md`](../../prompts/011-V0_2_6_START_PROMPT.md), préparé par `0.2.5-pre.010`, impose un `pre.001` d'audit/sizing couvrant Config `std.wallet`/composite, inventory/path safety, lifecycle VIEW/OWNER, balance `getBalance`, bridge Tauri/TS-RS, sécurité password/export, logging et validation finale Tauri.
|
||||
|
||||
`0.2.6-pre.001` confirme que le registre Config doit être étendu pour `cfg.std.wallet`/`schema.std.wallet` et `cfg.composite.ksp-app-wallet-desk`; `wallets_directory` reste global avec fallback `${KSP_WALLETS_DIRECTORY:-wallets}` tandis qu'un profil `default` minimal préserve le contrat de résolution composite. Le gate retient une inventory non récursive `.kspwallet`, sans symlink, root-scoped, les handles VIEW/OWNER exclusivement côté Rust, une CSP Tauri explicite, `getBalance` via la Pubkey du handle autorisé et les flows OWNER metadata/rotations/revocation/export fichier déjà possédés par Wallet. Le scope tient dans `0.2.6` mais le forecast initial est regranularisé jusqu'à `pre.013`; le plan actif est `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
|
||||
`0.2.6-pre.001` confirme que le registre Config doit être étendu pour `cfg.std.wallet`/`schema.std.wallet` et `cfg.composite.ksp-app-wallet-desk`. `wallets_directory` reste global avec fallback `${KSP_WALLETS_DIRECTORY:-wallets}` et chaque profil peut définir un `wallets_subdirectory` relatif pour isoler tests, temporaires ou futurs scénarios. Wallet Desk prépare/crée le répertoire effectif après résolution Config et journalise le succès en debug ou l'échec en error. Le gabarit Config Desk est réutilisé avec Bootstrap, Font Awesome, DataTables/Select, SimpleBar et `resize-observer-polyfill`. L'inventory reste non récursive `.kspwallet`, sans symlink d'entrée et root-scoped; les handles VIEW/OWNER restent exclusivement côté Rust. L'ouverture accepte un password éphémère saisi ou une tentative explicite de secrets Config `KSP_SECRET_WALLET_PASS_*`, dont ni nom ni valeur ne traversent le frontend. `getBalance` utilise la Pubkey du handle autorisé et les flows OWNER metadata/rotations/revocation/export fichier délèguent à Wallet. La CSP reste `null` par défaut comme dans Config Desk et n'est réauditée qu'en présence d'un besoin WebView concret. Le forecast souple détaillé s'étend jusqu'à `pre.014`, dernière tranche prévue pour README/USAGE/docs/validation, prompt `0.2.7` et build Tauri final; le plan actif est `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
|
||||
|
||||
`0.2.6-pre.002` matérialise le shell Tauri spécialisé. `0.2.6-pre.003` matérialise ensuite la première composition Config propre à Wallet Desk : `cfg.std.wallet`/`schema.std.wallet`, le composite `cfg.composite.ksp-app-wallet-desk`, `ResolvedWalletConfig`, la racine `${KSP_WALLETS_DIRECTORY:-wallets}`, les sous-répertoires de profils `temporary`/`tests`, la préservation de provenance `Composite` dans les adapters Logging/Wallet et la création sûre des répertoires par l’application. Aucun inventory ou fichier `.kspwallet` n’est encore ouvert dans cette tranche.
|
||||
|
||||
## `0.2.7` — WebSocket Solana standard
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Plan `0.2.6` — Wallet Desk
|
||||
|
||||
@@ -23,6 +23,8 @@ Les validations opérateur du `2026-08-20` ont ensuite confirmé la base `pre.00
|
||||
|
||||
`0.2.6-pre.002` matérialise ensuite la première tranche technique : la crate `ksp-app-wallet-desk` rejoint le workspace avec le shell splash/main du gabarit Config Desk, le bootstrap Config + Logging commun, les ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, `resize-observer-polyfill`, TS-RS et le bridge frontend -> Rust -> `ksp-logging-lib`. Cette tranche ne branche encore ni `std.wallet`, ni inventory filesystem réel, ni `ksp-wallet-lib`, ni Transport : ces responsabilités restent dans les prereleases déjà dimensionnées.
|
||||
|
||||
`0.2.6-pre.003` matérialise la composition Config prévue : `cfg.std.wallet`/`schema.std.wallet`, le composite `cfg.composite.ksp-app-wallet-desk`, les profils Wallet `default`/`temporary`/`tests`, `ResolvedWalletConfig` et les adapters qui peuvent consommer un profil déjà sélectionné par composite sans perdre la provenance `Composite`. Wallet Desk valide les composants `logging`/`transport`/`wallet`, crée la racine et le sous-répertoire effectif absents avec logs `debug`, refuse les objets filesystem invalides/symlinks de sous-répertoire avec logs `error`, et expose uniquement les chemins/profils non secrets dans le statut runtime. Aucun inventory `.kspwallet` n’est encore effectué.
|
||||
|
||||
## 2. Sources relues et hiérarchie appliquée
|
||||
|
||||
L'audit suit l'ordre d'autorité demandé :
|
||||
@@ -1244,22 +1246,25 @@ Le shell peut afficher des données mockées/runtime minimales, mais doit déjà
|
||||
|
||||
### `pre.003` — `std.wallet`, composite et préparation des répertoires
|
||||
|
||||
Objectifs :
|
||||
Livrables de la tranche :
|
||||
|
||||
```text
|
||||
cfg.std.wallet / schema.std.wallet
|
||||
wallets_directory global
|
||||
wallets_subdirectory par profil
|
||||
ResolvedWalletConfig
|
||||
cfg.composite.ksp-app-wallet-desk
|
||||
profils default/tests/temporary ou équivalents retenus
|
||||
.env.example pour KSP_WALLETS_DIRECTORY et pattern KSP_SECRET_WALLET_PASS_*
|
||||
bootstrap Config Wallet Desk
|
||||
ensure/create global + effective directories
|
||||
debug success / error failure
|
||||
cfg.std.wallet / schema.std.wallet enregistrés dans Config
|
||||
wallets_directory global = ${KSP_WALLETS_DIRECTORY:-wallets}
|
||||
wallets_subdirectory optionnel, relatif et multi-composants normaux
|
||||
profils default / temporary / tests
|
||||
ResolvedWalletConfig avec root/subdirectory/effective path/provenance
|
||||
resolve_logging_config_profile / resolve_wallet_config_profile pour profils composites
|
||||
cfg.composite.ksp-app-wallet-desk = logging + transport + wallet
|
||||
.env.example documente KSP_WALLETS_DIRECTORY et le pattern futur KSP_SECRET_WALLET_PASS_*
|
||||
bootstrap Wallet Desk valide les trois component_id/file_id requis
|
||||
création automatique du root et du sous-répertoire effectif absents
|
||||
rejet d'un root non-directory et d'un composant de sous-répertoire symlink/non-directory
|
||||
debug sur répertoires prêts/créés ; error sur préparation impossible
|
||||
status DTO/UI : profil composite, profils Logging/Wallet, root/subdirectory/effective directory
|
||||
```
|
||||
|
||||
Tests : schema, composition, containment, création de répertoires.
|
||||
Tests de tranche : registre/schema, composition et provenance, precedence environnement, containment syntaxique, création idempotente des répertoires, rejet non-directory/symlink et contrats desktop. L’inventory `.kspwallet` reste strictement `pre.004`.
|
||||
|
||||
### `pre.004` — inventory DataTables et inspection locked
|
||||
|
||||
|
||||
Reference in New Issue
Block a user