// file: crates/ksp-app-config-desk/src/profiles.rs // version: 4 //! Safe Config profile inspection and provenance projections for Config Desk. use ts_rs::TS; // rust-rules: trait-import /// One Config document that exposes the standard `default_profile` / `profiles` contract. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/profiles/ConfigProfileDocumentDto.ts")] pub(crate) struct ConfigProfileDocumentDto { /// Stable Config file identifier. pub(crate) file_id: String, /// Physical path resolved by Config. pub(crate) path: String, /// Autonomous default profile declared by the document. pub(crate) default_profile: String, /// Available unique profile identifiers in source order. pub(crate) profile_ids: std::vec::Vec, } /// Top-level origin of one effective profile key. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/profiles/ConfigProfileValueOriginDto.ts")] pub(crate) struct ConfigProfileValueOriginDto { /// Effective top-level key. pub(crate) key: String, /// `global` or `profile`. pub(crate) origin: String, } /// Safe provenance record for one environment contribution in the resolved effective profile. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/profiles/ConfigProfileEnvironmentProvenanceDto.ts")] pub(crate) struct ConfigProfileEnvironmentProvenanceDto { /// RFC 6901 pointer of the resolved value. pub(crate) json_pointer: String, /// Referenced environment variable name; never its value. pub(crate) variable_name: String, /// Winning source: `process`, `dotenv` or `fallback`. pub(crate) source: String, /// Sensitivity derived from the variable namespace. pub(crate) sensitivity: String, } /// Safe detailed view of one resolved Config profile. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/profiles/ConfigProfileDetailDto.ts")] pub(crate) struct ConfigProfileDetailDto { /// Stable Config file identifier. pub(crate) file_id: String, /// Physical path resolved by Config. pub(crate) path: String, /// Autonomous default profile declared by the document. pub(crate) default_profile: String, /// Available profile identifiers. pub(crate) profile_ids: std::vec::Vec, /// Profile selected for this inspection. pub(crate) selected_profile: String, /// `default_profile` or `explicit`. pub(crate) selection_source: String, /// Pretty-printed document-global source values. pub(crate) globals_json: String, /// Pretty-printed selected source profile object. pub(crate) profile_json: String, /// Pretty-printed environment-resolved effective tree using only the safe/redacted representation. pub(crate) effective_safe_json: String, /// Strongest sensitivity present in the resolved effective tree. pub(crate) effective_sensitivity: String, /// Global/Profile provenance for effective top-level keys. pub(crate) value_origins: std::vec::Vec, /// Environment provenance records without resolved values. pub(crate) environment_provenance: std::vec::Vec, } struct ProfileContract { default_profile: String, profile_ids: std::vec::Vec, } /// Lists validated Config documents that expose standard profiles. pub(crate) fn profile_inventory(state: &crate::AppState) -> ksp_core_lib::Result> { return inventory_from_management(state.config_management()); } /// Resolves one default or explicitly selected profile into a safe inspection DTO. pub(crate) fn profile_detail( state: &crate::AppState, file_id: &str, requested_profile: std::option::Option<&str>, ) -> ksp_core_lib::Result { return detail_from_management(state.config_management(), file_id, requested_profile); } fn inventory_from_management(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result> { let mut result = std::vec::Vec::::new(); for descriptor in management.engine().registry().descriptors() { if descriptor.kind() != ksp_config_lib::ConfigFileKind::Config { continue; } let document = management.engine().load_validated_document(descriptor.file_id()); let document = match document { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let contract = profile_contract(&document); let contract = match contract { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => continue, std::result::Result::Err(error) => return std::result::Result::Err(error), }; result.push(ConfigProfileDocumentDto { file_id: descriptor.file_id().as_str().to_owned(), path: document.path().to_string_lossy().into_owned(), default_profile: contract.default_profile, profile_ids: contract.profile_ids, }); } ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_PROFILES, profile_document_count = result.len(), "Config profile document inventory evaluated" ); return std::result::Result::Ok(result); } fn detail_from_management( management: &ksp_config_lib::ConfigManagement, file_id: &str, requested_profile: std::option::Option<&str>, ) -> ksp_core_lib::Result { let file_id = ksp_config_lib::ConfigFileId::new(file_id.to_owned()); let file_id = match file_id { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let document = management.engine().load_validated_document(&file_id); let document = match document { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let contract = profile_contract(&document); let contract = match contract { std::result::Result::Ok(std::option::Option::Some(value)) => value, std::result::Result::Ok(std::option::Option::None) => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_CONTRACT_MISSING, "Config document does not expose the standard profile contract") .with_context("file_id", file_id.as_str()), ); }, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let resolved = management.engine().load_resolved_profile(&file_id, requested_profile); let resolved = match resolved { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; 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 effective = resolved.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 globals_json = pretty_json(resolved.globals()); let globals_json = match globals_json { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let profile_json = pretty_json(resolved.profile()); let profile_json = match profile_json { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let effective_safe_json = serde_json::to_string_pretty(effective.safe_value()); let effective_safe_json = match effective_safe_json { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_PROJECTION_FAILED, "Cannot serialize safe effective Config profile").with_source(error), ); }, }; let value_origins = value_origins(&resolved); let environment_provenance = environment_provenance(&effective); let environment_provenance = match environment_provenance { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let selection_source = selection_source_label(resolved.selection_source()).to_owned(); ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_PROFILES, file_id = file_id.as_str(), profile_id = resolved.profile_id(), selection_source = selection_source.as_str(), effective_sensitivity = sensitivity_label(effective.sensitivity()), environment_provenance_count = environment_provenance.len(), "Config profile detail resolved" ); return std::result::Result::Ok(ConfigProfileDetailDto { file_id: file_id.as_str().to_owned(), path: resolved.path().to_string_lossy().into_owned(), default_profile: contract.default_profile, profile_ids: contract.profile_ids, selected_profile: resolved.profile_id().to_owned(), selection_source, globals_json, profile_json, effective_safe_json, effective_sensitivity: sensitivity_label(effective.sensitivity()).to_owned(), value_origins, environment_provenance, }); } fn profile_contract(document: &ksp_config_lib::ConfigJsonDocument) -> ksp_core_lib::Result> { let root = match document.value().as_object() { std::option::Option::Some(value) => value, std::option::Option::None => return std::result::Result::Ok(std::option::Option::None), }; let default_profile = root.get("default_profile"); let profiles = root.get("profiles"); if default_profile.is_none() && profiles.is_none() { return std::result::Result::Ok(std::option::Option::None); } let default_profile = match default_profile.and_then(serde_json::Value::as_str) { std::option::Option::Some(value) => value.to_owned(), std::option::Option::None => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_PROJECTION_FAILED, "Validated Config profile document has no readable default_profile") .with_context("file_id", document.file_id().as_str()), ); }, }; let profiles = match profiles.and_then(serde_json::Value::as_array) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_PROJECTION_FAILED, "Validated Config profile document has no readable profiles array") .with_context("file_id", document.file_id().as_str()), ); }, }; let mut profile_ids = std::vec::Vec::::new(); for profile in profiles { let profile_id = profile .as_object() .and_then(|value| { return value.get("profile_id"); }) .and_then(serde_json::Value::as_str); let profile_id = match profile_id { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_PROJECTION_FAILED, "Validated Config profile entry has no readable profile_id") .with_context("file_id", document.file_id().as_str()), ); }, }; profile_ids.push(profile_id.to_owned()); } return std::result::Result::Ok(std::option::Option::Some(ProfileContract { default_profile, profile_ids })); } fn pretty_json(value: &serde_json::Map) -> ksp_core_lib::Result { let serialized = serde_json::to_string_pretty(value); return match serialized { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_PROFILE_PROJECTION_FAILED, "Cannot serialize Config profile source projection").with_source(error), ), }; } fn value_origins(resolved: &ksp_config_lib::ResolvedConfigProfile) -> std::vec::Vec { let mut result = std::vec::Vec::::new(); for key in resolved.effective().keys() { let origin = resolved.origin(key.as_str()); let origin = match origin { std::option::Option::Some(ksp_config_lib::ConfigValueOrigin::Global) => "global", std::option::Option::Some(ksp_config_lib::ConfigValueOrigin::Profile) => "profile", std::option::Option::None => "unknown", }; result.push(ConfigProfileValueOriginDto { key: key.clone(), origin: origin.to_owned() }); } return result; } fn environment_provenance(effective: &ksp_config_lib::ResolvedConfigJson) -> ksp_core_lib::Result> { let mut result = std::vec::Vec::::new(); for (json_pointer, provenance) in effective.provenance() { for segment in provenance { let variable_name = segment.variable_name(); let variable_name = match variable_name { std::option::Option::Some(value) => value, std::option::Option::None => continue, }; let source = segment.environment_source(); let source = match source { std::option::Option::Some(value) => environment_source_label(value), std::option::Option::None => continue, }; let sensitivity = ksp_config_lib::ConfigSensitivity::from_variable_name(variable_name); let sensitivity = match sensitivity { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; result.push(ConfigProfileEnvironmentProvenanceDto { json_pointer: json_pointer.clone(), variable_name: variable_name.to_owned(), source: source.to_owned(), sensitivity: sensitivity_label(sensitivity).to_owned(), }); } } return std::result::Result::Ok(result); } const fn selection_source_label(source: ksp_config_lib::ConfigProfileSelectionSource) -> &'static str { return match source { ksp_config_lib::ConfigProfileSelectionSource::DefaultProfile => "default_profile", ksp_config_lib::ConfigProfileSelectionSource::Explicit => "explicit", ksp_config_lib::ConfigProfileSelectionSource::Composite => "composite", }; } const fn environment_source_label(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str { return match source { ksp_config_lib::ConfigEnvironmentSource::Process => "process", ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv", ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback", }; } const fn sensitivity_label(sensitivity: ksp_config_lib::ConfigSensitivity) -> &'static str { return match sensitivity { ksp_config_lib::ConfigSensitivity::Public => "public", ksp_config_lib::ConfigSensitivity::Internal => "internal", ksp_config_lib::ConfigSensitivity::Secret => "secret", }; } #[cfg(test)] #[path = "../unit_tests/profiles.rs"] mod tests;