v0.1.3-pre.012

This commit is contained in:
2026-08-16 05:01:33 +02:00
parent 6a13cea614
commit a9405ec7ff
13 changed files with 1139 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/environment.rs
// version: 2
// version: 3
/// Default local environment file read by Config from the process launch directory.
pub const DEFAULT_DOTENV_PATH: &str = ".env";
@@ -246,7 +246,7 @@ impl ConfigEnvironment {
}
#[cfg(test)]
fn from_maps(process: std::collections::BTreeMap<String, String>, dotenv: std::collections::BTreeMap<String, String>) -> Self {
pub(crate) fn from_maps(process: std::collections::BTreeMap<String, String>, dotenv: std::collections::BTreeMap<String, String>) -> Self {
return Self { process, dotenv, dotenv_path: std::path::PathBuf::from(DEFAULT_DOTENV_PATH) };
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/error.rs
// version: 6
// version: 7
/// Error code used when a Config bootstrap argument is missing its value.
pub const ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "bootstrap_argument_missing_value");
@@ -57,3 +57,6 @@ pub const ERROR_CODE_ENVIRONMENT_VALUE_INVALID: ksp_core_lib::ErrorCode = ksp_co
/// Error code used when a `${NAME}` / `${NAME:-fallback}` expression is malformed.
pub const ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_placeholder_invalid");
/// Error code used when an environment-resolved Config cannot be mapped safely to a runtime consumer contract.
pub const ERROR_CODE_EFFECTIVE_CONFIG_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "effective_config_invalid");

View File

@@ -1,21 +1,22 @@
// file: crates/ksp-config-lib/src/lib.rs
// version: 7
// version: 8
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! KSP-owned application configuration facade.
//!
//! `0.1.3-pre.011` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading, standard-document profile resolution, generic composite
//! `0.1.3-pre.012` owns bootstrap roots, the logical file registry, generic JSON/JSON Schema loading, standard-document profile resolution, generic composite
//! resolution and KSP/KSPB environment resolution through process + `.env` + fallback precedence. The standard Logging document remains the first registered
//! runtime document. Environment-derived values now preserve real/safe representations, sensitivity and provenance; persistence remains in a later bounded
//! prerelease.
//! runtime document. Environment-derived values preserve real/safe representations, sensitivity and provenance; the standard Logging profile can now be
//! mapped explicitly to `ksp_logging_lib::LoggingSettings`. Persistence remains in a later bounded prerelease.
mod bootstrap;
mod composite;
mod document;
mod environment;
mod error;
mod logging;
mod profile;
mod registry;
mod sensitivity;
@@ -60,6 +61,8 @@ pub use self::error::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID;
pub use self::error::ERROR_CODE_DOTENV_FILE_READ_FAILED;
/// Error code used when the local `.env` file contains invalid syntax.
pub use self::error::ERROR_CODE_DOTENV_SYNTAX_INVALID;
/// Error code used when an environment-resolved Config cannot map safely to a runtime consumer contract.
pub use self::error::ERROR_CODE_EFFECTIVE_CONFIG_INVALID;
/// Error code used when a Config environment placeholder is malformed.
pub use self::error::ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID;
/// Error code used when a supported Config environment variable has a non-UTF-8 process value.
@@ -86,6 +89,8 @@ pub use self::error::ERROR_CODE_PROFILE_NOT_FOUND;
pub use self::error::ERROR_CODE_SCHEMA_INVALID;
/// Error code used when a Config document fails its registered JSON Schema validation.
pub use self::error::ERROR_CODE_SCHEMA_VALIDATION_FAILED;
/// Effective standard Logging configuration mapped to `ksp_logging_lib::LoggingSettings`.
pub use self::logging::ResolvedLoggingConfig;
/// Source that selected an effective standard Config profile.
pub use self::profile::ConfigProfileSelectionSource;
/// Origin of one top-level value in a resolved standard Config profile.

View File

@@ -0,0 +1,437 @@
// file: crates/ksp-config-lib/src/logging.rs
// version: 1
/// Effective standard Logging configuration resolved from Config and mapped to the Logging runtime contract.
#[derive(Clone, Eq, PartialEq)]
pub struct ResolvedLoggingConfig {
file_id: crate::ConfigFileId,
source_path: std::path::PathBuf,
profile_id: String,
selection_source: crate::ConfigProfileSelectionSource,
effective: crate::ResolvedConfigJson,
logs_directory: std::path::PathBuf,
settings: ksp_logging_lib::LoggingSettings,
}
impl ResolvedLoggingConfig {
/// 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 physical source Config document path.
#[must_use]
pub fn source_path(&self) -> &std::path::Path {
return self.source_path.as_path();
}
/// Returns the selected standard Logging profile identifier.
#[must_use]
pub fn profile_id(&self) -> &str {
return self.profile_id.as_str();
}
/// Returns the source that selected the standard Logging profile.
#[must_use]
pub const fn selection_source(&self) -> crate::ConfigProfileSelectionSource {
return self.selection_source;
}
/// Returns the detailed environment-resolved effective Config view.
///
/// The real tree is available to legitimate runtime consumers and the safe tree is suitable for ordinary diagnostics.
#[must_use]
pub const fn effective(&self) -> &crate::ResolvedConfigJson {
return &self.effective;
}
/// Returns the validated real Logging root directory.
///
/// Relative Config values are anchored to the process current working directory when this adapter runs. Absolute Config values are preserved.
#[must_use]
pub fn logs_directory(&self) -> &std::path::Path {
return self.logs_directory.as_path();
}
/// Returns the mapped runtime Logging settings.
#[must_use]
pub const fn settings(&self) -> &ksp_logging_lib::LoggingSettings {
return &self.settings;
}
/// Consumes this resolved Config and returns the mapped runtime Logging settings.
#[must_use]
pub fn into_settings(self) -> ksp_logging_lib::LoggingSettings {
return self.settings;
}
}
impl std::fmt::Debug for ResolvedLoggingConfig {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("ResolvedLoggingConfig")
.field("file_id", &self.file_id)
.field("source_path", &self.source_path)
.field("profile_id", &self.profile_id)
.field("selection_source", &self.selection_source)
.field("effective", &self.effective)
.finish_non_exhaustive();
}
}
impl crate::ConfigDocumentEngine {
/// Loads the standard Logging document, selects a profile, resolves environment placeholders and maps the effective result to `LoggingSettings`.
///
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. Source JSON validation remains distinct
/// from effective runtime validation: an environment value that resolves to an invalid Logging setting returns
/// [`crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID`] and does not silently fall back to the placeholder fallback.
pub fn load_resolved_logging_config(
&self,
requested_profile: std::option::Option<&str>,
environment: &crate::ConfigEnvironment,
) -> ksp_core_lib::Result<ResolvedLoggingConfig> {
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
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_logging_profile(&profile, environment);
}
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EffectiveLoggingSource {
format_version: u32,
logs_directory: String,
profile_id: String,
default_filter: String,
span_events: String,
console: EffectiveConsoleSource,
files: std::vec::Vec<EffectiveFileSource>,
target_filters: std::vec::Vec<EffectiveTargetFilterSource>,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EffectiveConsoleSource {
enabled: bool,
output: String,
ansi: bool,
format: String,
filter: EffectiveOutputFilterSource,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EffectiveFileSource {
output_id: String,
enabled: bool,
path: String,
rotation: String,
format: String,
ansi: bool,
filter: EffectiveOutputFilterSource,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EffectiveOutputFilterSource {
level: String,
targets: std::vec::Vec<String>,
domains: std::vec::Vec<String>,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EffectiveTargetFilterSource {
target_prefix: String,
level: String,
}
fn resolve_logging_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedLoggingConfig> {
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),
};
let sensitivity_validation = validate_logging_sensitivity(profile, &effective);
if let std::result::Result::Err(error) = sensitivity_validation {
return std::result::Result::Err(error);
}
let source = serde_json::from_value::<EffectiveLoggingSource>(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 Logging 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 Logging format_version is unsupported"));
}
if source.profile_id != profile.profile_id() {
return std::result::Result::Err(effective_error(profile, "effective Logging profile_id differs from the selected source profile"));
}
let safe_logs_directory = safe_string_at(effective.safe_value(), "/logs_directory");
let logs_directory = resolve_logs_directory(source.logs_directory.as_str(), safe_logs_directory.as_str(), profile);
let logs_directory = match logs_directory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let default_filter = map_level(source.default_filter.as_str(), "default_filter", profile);
let default_filter = match default_filter {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let span_events = map_span_events(source.span_events.as_str(), profile);
let span_events = match span_events {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let console = map_console(source.console, profile);
let console = match console {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let files = map_files(source.files, logs_directory.as_path(), profile);
let files = match files {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut settings = ksp_logging_lib::LoggingSettings::new(default_filter, span_events, std::option::Option::Some(console), files);
for target_filter in source.target_filters {
let level = map_level(target_filter.level.as_str(), "target_filters.level", profile);
let level = match level {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
settings = settings.with_target_filter(ksp_logging_lib::TargetFilter::new(target_filter.target_prefix, level));
}
let validation = settings.validate();
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(
effective_error(profile, "effective Logging settings fail the Logging runtime contract")
.with_context("logging_error_domain", error.code().domain())
.with_context("logging_error_code", error.code().code()),
);
}
return std::result::Result::Ok(ResolvedLoggingConfig {
file_id: profile.file_id().clone(),
source_path: profile.path().to_path_buf(),
profile_id: profile.profile_id().to_owned(),
selection_source: profile.selection_source(),
effective,
logs_directory,
settings,
});
}
fn validate_logging_sensitivity(profile: &crate::ResolvedConfigProfile, effective: &crate::ResolvedConfigJson) -> ksp_core_lib::Result<()> {
if effective.sensitivity().is_secret() {
return std::result::Result::Err(effective_error(profile, "standard Logging configuration must not consume Secret environment values"));
}
return std::result::Result::Ok(());
}
fn map_console(source: EffectiveConsoleSource, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<ksp_logging_lib::ConsoleSettings> {
let output = match source.output.as_str() {
"stdout" => ksp_logging_lib::ConsoleOutput::Stdout,
"stderr" => ksp_logging_lib::ConsoleOutput::Stderr,
_ => return std::result::Result::Err(effective_field_error(profile, "console.output", "effective Logging console output is unsupported")),
};
let format = map_format(source.format.as_str(), "console.format", profile);
let format = match format {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter = map_output_filter(source.filter, "console.filter", profile);
let filter = match filter {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_logging_lib::ConsoleSettings::new(source.enabled, output, source.ansi, format, filter));
}
fn map_files(
sources: std::vec::Vec<EffectiveFileSource>,
logs_directory: &std::path::Path,
profile: &crate::ResolvedConfigProfile,
) -> ksp_core_lib::Result<std::vec::Vec<ksp_logging_lib::FileSettings>> {
let mut files = std::vec::Vec::<ksp_logging_lib::FileSettings>::with_capacity(sources.len());
for source in sources {
if !relative_file_path_is_valid(source.path.as_str()) {
return std::result::Result::Err(
effective_field_error(profile, "files.path", "effective Logging file path must stay relative to logs_directory without traversal")
.with_context("output_id", source.output_id.as_str()),
);
}
let file_path = std::path::Path::new(source.path.as_str());
let file_name = match file_path.file_name().and_then(std::ffi::OsStr::to_str) {
std::option::Option::Some(value) if !value.is_empty() => value.to_owned(),
_ => return std::result::Result::Err(effective_field_error(profile, "files.path", "effective Logging file path has no UTF-8 file name")),
};
let relative_directory = match file_path.parent() {
std::option::Option::Some(value) => value,
std::option::Option::None => std::path::Path::new(""),
};
let directory = logs_directory.join(relative_directory);
let rotation = map_rotation(source.rotation.as_str(), profile);
let rotation = match rotation {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let format = map_format(source.format.as_str(), "files.format", profile);
let format = match format {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter = map_output_filter(source.filter, "files.filter", profile);
let filter = match filter {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let file = ksp_logging_lib::FileSettings::new(source.output_id, source.enabled, directory, file_name, rotation, format, filter).with_ansi(source.ansi);
files.push(file);
}
return std::result::Result::Ok(files);
}
fn map_output_filter(
source: EffectiveOutputFilterSource,
field: &'static str,
profile: &crate::ResolvedConfigProfile,
) -> ksp_core_lib::Result<ksp_logging_lib::OutputFilter> {
let level = map_level(source.level.as_str(), field, profile);
let level = match level {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_logging_lib::OutputFilter::new(level, source.targets, source.domains));
}
fn map_level(value: &str, field: &'static str, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<ksp_logging_lib::LogFilterLevel> {
return match value {
"off" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Off),
"error" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Error),
"warn" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Warn),
"info" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Info),
"debug" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Debug),
"trace" => std::result::Result::Ok(ksp_logging_lib::LogFilterLevel::Trace),
_ => std::result::Result::Err(effective_field_error(profile, field, "effective Logging level is unsupported")),
};
}
fn map_span_events(value: &str, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<ksp_logging_lib::SpanEvents> {
return match value {
"off" => std::result::Result::Ok(ksp_logging_lib::SpanEvents::Off),
"new_and_close" => std::result::Result::Ok(ksp_logging_lib::SpanEvents::NewAndClose),
"full" => std::result::Result::Ok(ksp_logging_lib::SpanEvents::Full),
_ => std::result::Result::Err(effective_field_error(profile, "span_events", "effective Logging span_events value is unsupported")),
};
}
fn map_format(value: &str, field: &'static str, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<ksp_logging_lib::LogFormat> {
return match value {
"human" => std::result::Result::Ok(ksp_logging_lib::LogFormat::Human),
"compact" => std::result::Result::Ok(ksp_logging_lib::LogFormat::Compact),
"pretty" => std::result::Result::Ok(ksp_logging_lib::LogFormat::Pretty),
"json" => std::result::Result::Ok(ksp_logging_lib::LogFormat::Json),
_ => std::result::Result::Err(effective_field_error(profile, field, "effective Logging format is unsupported")),
};
}
fn map_rotation(value: &str, profile: &crate::ResolvedConfigProfile) -> ksp_core_lib::Result<ksp_logging_lib::FileRotation> {
return match value {
"never" => std::result::Result::Ok(ksp_logging_lib::FileRotation::Never),
"hourly" => std::result::Result::Ok(ksp_logging_lib::FileRotation::Hourly),
"daily" => std::result::Result::Ok(ksp_logging_lib::FileRotation::Daily),
_ => std::result::Result::Err(effective_field_error(profile, "files.rotation", "effective Logging rotation is unsupported")),
};
}
fn resolve_logs_directory(value: &str, safe_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, "logs_directory", "effective Logging logs_directory must not be empty").with_context("safe_value", safe_value),
);
}
let configured = std::path::PathBuf::from(value);
let resolved = if configured.is_absolute() {
configured
} else {
let current_directory = std::env::current_dir();
let current_directory = match current_directory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
effective_field_error(profile, "logs_directory", "process current working directory cannot be resolved").with_source(error),
);
},
};
current_directory.join(configured)
};
let metadata = std::fs::metadata(resolved.as_path());
match metadata {
std::result::Result::Ok(value) if !value.is_dir() => {
return std::result::Result::Err(
effective_field_error(profile, "logs_directory", "effective Logging logs_directory resolves to an existing non-directory path")
.with_context("safe_value", safe_value),
);
},
std::result::Result::Ok(_) => {},
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {},
std::result::Result::Err(error) => {
return std::result::Result::Err(
effective_field_error(profile, "logs_directory", "effective Logging logs_directory cannot be inspected")
.with_context("safe_value", safe_value)
.with_source(error),
);
},
}
return std::result::Result::Ok(resolved);
}
fn relative_file_path_is_valid(value: &str) -> bool {
let path = std::path::Path::new(value);
if path.is_absolute() {
return false;
}
let mut has_normal_component = false;
for component in path.components() {
match component {
std::path::Component::Normal(_) => has_normal_component = true,
std::path::Component::CurDir | std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_) => return false,
}
}
return has_normal_component;
}
fn safe_string_at(value: &serde_json::Value, pointer: &str) -> String {
return match value.pointer(pointer).and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value.to_owned(),
std::option::Option::None => "<unavailable>".to_owned(),
};
}
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("profile_id", profile.profile_id())
.with_context("reason", reason);
}
#[cfg(test)]
#[path = "../unit_tests/logging.rs"]
mod tests;

View File

@@ -1,7 +1,8 @@
// file: crates/ksp-config-lib/tests/public_api.rs
// version: 8
// version: 9
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution and sensitivity contracts.
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity and
//! Logging-adapter contracts.
#[test]
fn bootstrap_contract_is_available_from_crate_root() {
@@ -168,3 +169,12 @@ fn sensitivity_and_safe_resolution_contracts_are_available_from_crate_root() {
assert_eq!(provenance.variable_name(), std::option::Option::Some("KSP_SECRET_TOKEN"));
assert_eq!(provenance.environment_source(), std::option::Option::Some(ksp_config_lib::ConfigEnvironmentSource::Fallback));
}
#[test]
fn logging_adapter_contract_is_available_from_crate_root() {
let adapter = ksp_config_lib::ConfigDocumentEngine::load_resolved_logging_config;
let _ = 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);
}

View File

@@ -0,0 +1,329 @@
// file: crates/ksp-config-lib/unit_tests/logging.rs
// version: 1
#[test]
fn committed_logging_profile_maps_complete_runtime_contract() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
assert!(resolved.is_ok(), "committed Logging Config should map");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.file_id().as_str(), crate::FILE_ID_STD_LOGGING);
assert_eq!(resolved.profile_id(), "local_dev");
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
assert_eq!(resolved.logs_directory(), current_directory().join("logs").as_path());
assert_eq!(resolved.effective().value()["logs_directory"], serde_json::Value::String("logs".to_owned()));
let settings = resolved.settings();
assert_eq!(settings.default_filter(), ksp_logging_lib::LogFilterLevel::Warn);
assert_eq!(settings.span_events(), ksp_logging_lib::SpanEvents::NewAndClose);
assert_eq!(settings.target_filters().len(), 2);
assert_eq!(settings.target_filters()[0].target_prefix(), "ksp-config-lib");
assert_eq!(settings.target_filters()[0].level(), ksp_logging_lib::LogFilterLevel::Trace);
assert_eq!(settings.target_filters()[1].target_prefix(), "ksp-logging-lib");
assert_eq!(settings.target_filters()[1].level(), ksp_logging_lib::LogFilterLevel::Debug);
let console = settings.console();
assert!(console.is_some(), "committed Logging Config declares console settings");
let console = match console {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(console.enabled());
assert_eq!(console.output(), ksp_logging_lib::ConsoleOutput::Stderr);
assert!(console.ansi());
assert_eq!(console.format(), ksp_logging_lib::LogFormat::Compact);
assert_eq!(console.filter().level(), ksp_logging_lib::LogFilterLevel::Debug);
assert_eq!(console.filter().targets(), &["*".to_owned()]);
assert_eq!(console.filter().domains(), &["*".to_owned()]);
assert_eq!(settings.files().len(), 2);
assert_file(
&settings.files()[0],
"file.all.debug",
current_directory().join("logs/debug").as_path(),
"ksp-debug.log",
ksp_logging_lib::FileRotation::Daily,
ksp_logging_lib::LogFormat::Human,
ksp_logging_lib::LogFilterLevel::Debug,
&["*"],
&["*"],
);
assert_file(
&settings.files()[1],
"file.config.error",
current_directory().join("logs/config").as_path(),
"ksp-config-errors.jsonl",
ksp_logging_lib::FileRotation::Daily,
ksp_logging_lib::LogFormat::Json,
ksp_logging_lib::LogFilterLevel::Error,
&["ksp-config-lib"],
&["config"],
);
}
#[test]
fn relative_logs_directory_is_anchored_to_process_current_directory() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = environment_with_logs_directory("relative-ksp-logs");
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
assert!(resolved.is_ok(), "relative Logging root should map");
if let std::result::Result::Ok(resolved) = resolved {
assert_eq!(resolved.logs_directory(), current_directory().join("relative-ksp-logs").as_path());
assert_eq!(resolved.settings().files()[0].directory(), current_directory().join("relative-ksp-logs/debug").as_path());
}
}
#[test]
fn absolute_logs_directory_is_preserved() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let absolute = std::env::temp_dir().join(format!("ksp-pre012-absolute-{}", std::process::id()));
let environment = environment_with_logs_directory(absolute.to_string_lossy().as_ref());
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
assert!(resolved.is_ok(), "absolute Logging root should map even when it does not exist yet");
if let std::result::Result::Ok(resolved) = resolved {
assert_eq!(resolved.logs_directory(), absolute.as_path());
}
}
#[test]
fn effective_file_paths_cannot_escape_logging_root() {
assert!(super::relative_file_path_is_valid("debug/ksp.log"));
assert!(super::relative_file_path_is_valid("ksp.log"));
assert!(!super::relative_file_path_is_valid("../ksp.log"));
assert!(!super::relative_file_path_is_valid("./ksp.log"));
assert!(!super::relative_file_path_is_valid("/var/log/ksp.log"));
}
#[test]
fn explicit_empty_logs_directory_is_invalid_instead_of_using_fallback() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = environment_with_logs_directory("");
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
let error = match resolved {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
assert!(error.context().iter().any(|item| -> bool {
return item.key() == "field" && item.value() == "logs_directory";
}));
}
#[test]
fn existing_non_directory_logging_root_is_rejected_without_secret_leak() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let secret = format!("ksp-secret-path-canary-{}", std::process::id());
let path = std::env::temp_dir().join(secret.as_str());
let write = std::fs::write(path.as_path(), b"not a directory");
assert!(write.is_ok(), "secret canary file should be created");
let profile = load_committed_profile(&engine);
let profile = match profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
cleanup_file(path.as_path());
return;
},
};
let resolved = super::resolve_logs_directory(path.to_string_lossy().as_ref(), crate::REDACTED_CONFIG_VALUE, &profile);
cleanup_file(path.as_path());
let error = match resolved {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
let debug = format!("{error:?}");
assert!(!debug.contains(secret.as_str()), "effective Config diagnostics must not reveal real secret-derived paths");
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
}
#[test]
fn logging_adapter_rejects_secret_effective_values_without_exposing_canary() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let profile = load_committed_profile(&engine);
let profile = match profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let canary = "ksp-pre012-secret-canary-c3e4";
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_SECRET_LOGGING_CANARY".to_owned(), canary.to_owned());
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
let effective = environment.resolve_json_detailed(&serde_json::json!({"canary": "${KSP_SECRET_LOGGING_CANARY}"}));
assert!(effective.is_ok(), "secret canary fixture should resolve");
let effective = match effective {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let validation = super::validate_logging_sensitivity(&profile, &effective);
let error = match validation {
std::result::Result::Ok(()) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
let debug = format!("{error:?}");
assert!(!debug.contains(canary), "Logging adapter diagnostics must not reveal secret canaries");
}
#[test]
fn mapped_logging_settings_can_initialize_and_reinitialize_runtime() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let root = std::env::temp_dir().join(format!("ksp-pre012-runtime-{}", std::process::id()));
cleanup_directory(root.as_path());
let environment = environment_with_logs_directory(root.to_string_lossy().as_ref());
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
assert!(resolved.is_ok(), "runtime Logging Config should map");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let guard = ksp_logging_lib::initialize(resolved.settings());
assert!(guard.is_ok(), "mapped Logging settings should initialize the Logging runtime");
let mut guard = match guard {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(root.join("debug").is_dir(), "Logging initialization should create the first configured file directory");
assert!(root.join("config").is_dir(), "Logging initialization should create the second configured file directory");
let reload = ksp_logging_lib::reinitialize(&mut guard, resolved.settings());
assert!(reload.is_ok(), "mapped Logging settings should support hot reload");
let disabled = ksp_logging_lib::LoggingSettings::new(
ksp_logging_lib::LogFilterLevel::Off,
ksp_logging_lib::SpanEvents::Off,
std::option::Option::None,
std::vec::Vec::new(),
);
let disable = ksp_logging_lib::reinitialize(&mut guard, &disabled);
assert!(disable.is_ok(), "test Logging runtime should disable outputs before cleanup");
drop(guard);
cleanup_directory(root.as_path());
}
#[test]
fn resolved_logging_debug_uses_safe_effective_view() {
let engine = committed_engine();
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let root = std::env::temp_dir().join(format!("ksp-pre012-debug-{}", std::process::id()));
let environment = environment_with_logs_directory(root.to_string_lossy().as_ref());
let resolved = engine.load_resolved_logging_config(std::option::Option::None, &environment);
assert!(resolved.is_ok(), "Logging Config should map for Debug contract");
if let std::result::Result::Ok(resolved) = resolved {
let debug = format!("{resolved:?}");
assert!(debug.contains("ResolvedLoggingConfig"));
assert!(debug.contains("effective"));
}
}
fn assert_file(
file: &ksp_logging_lib::FileSettings,
output_id: &str,
directory: &std::path::Path,
file_name: &str,
rotation: ksp_logging_lib::FileRotation,
format: ksp_logging_lib::LogFormat,
level: ksp_logging_lib::LogFilterLevel,
targets: &[&str],
domains: &[&str],
) {
assert_eq!(file.output_id(), output_id);
assert!(file.enabled());
assert_eq!(file.directory(), directory);
assert_eq!(file.file_name_prefix(), file_name);
assert_eq!(file.rotation(), rotation);
assert_eq!(file.format(), format);
assert!(!file.ansi());
assert_eq!(file.filter().level(), level);
assert_eq!(file.filter().targets().iter().map(String::as_str).collect::<std::vec::Vec<&str>>(), targets.to_vec());
assert_eq!(file.filter().domains().iter().map(String::as_str).collect::<std::vec::Vec<&str>>(), domains.to_vec());
}
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
let workspace = workspace_root();
let bootstrap = crate::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 = crate::ConfigFileRegistry::defaults();
let registry = match registry {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
}
fn load_committed_profile(engine: &crate::ConfigDocumentEngine) -> ksp_core_lib::Result<crate::ResolvedConfigProfile> {
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return engine.load_resolved_profile(&file_id, std::option::Option::None);
}
fn environment_with_logs_directory(value: &str) -> crate::ConfigEnvironment {
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_LOGS_DIRECTORY".to_owned(), value.to_owned());
return crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
}
fn workspace_root() -> std::path::PathBuf {
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
}
fn current_directory() -> std::path::PathBuf {
let current = std::env::current_dir();
return match current {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => std::path::PathBuf::new(),
};
}
fn cleanup_file(path: &std::path::Path) {
let removal = std::fs::remove_file(path);
if let std::result::Result::Err(error) = removal
&& error.kind() != std::io::ErrorKind::NotFound
{
eprintln!("unable to cleanup Config Logging adapter file {}: {error}", path.display());
}
}
fn cleanup_directory(path: &std::path::Path) {
let removal = std::fs::remove_dir_all(path);
if let std::result::Result::Err(error) = removal
&& error.kind() != std::io::ErrorKind::NotFound
{
eprintln!("unable to cleanup Config Logging adapter directory {}: {error}", path.display());
}
}