From 6df60a4683c09c8c77e10d833a5b7eb231234007 Mon Sep 17 00:00:00 2001 From: bellman Date: Thu, 14 May 2026 17:40:28 +0900 Subject: [PATCH] omx(team): auto-checkpoint worker-2 [unknown] --- rust/crates/runtime/src/config.rs | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index 15661892..a69a2e61 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -414,6 +414,17 @@ impl RuntimeConfig { pub fn trusted_roots(&self) -> &[String] { &self.feature_config.trusted_roots } + + /// Merge config-level default trusted roots with per-call roots. + /// + /// Config roots are defaults and are kept first; per-call roots extend the + /// allowlist for a specific worker/session creation request. Duplicates are + /// removed without reordering the first occurrence so evidence remains + /// deterministic while avoiding repeated trust checks. + #[must_use] + pub fn trusted_roots_with_overrides(&self, per_call_roots: &[String]) -> Vec { + merge_trusted_roots(self.trusted_roots(), per_call_roots) + } } impl RuntimeFeatureConfig { @@ -483,6 +494,22 @@ impl RuntimeFeatureConfig { pub fn trusted_roots(&self) -> &[String] { &self.trusted_roots } + + /// Merge this config's default trusted roots with per-call roots. + #[must_use] + pub fn trusted_roots_with_overrides(&self, per_call_roots: &[String]) -> Vec { + merge_trusted_roots(self.trusted_roots(), per_call_roots) + } +} + +fn merge_trusted_roots(config_roots: &[String], per_call_roots: &[String]) -> Vec { + let mut merged = Vec::with_capacity(config_roots.len() + per_call_roots.len()); + for root in config_roots.iter().chain(per_call_roots.iter()) { + if !merged.contains(root) { + merged.push(root.clone()); + } + } + merged } impl ProviderFallbackConfig { @@ -1502,6 +1529,51 @@ mod tests { fs::remove_dir_all(root).expect("cleanup temp dir"); } + #[test] + fn trusted_roots_with_overrides_preserves_config_defaults_and_adds_per_call_roots() { + // given + let root = temp_dir(); + let cwd = root.join("project"); + let home = root.join("home").join(".claw"); + fs::create_dir_all(&home).expect("home config dir"); + fs::create_dir_all(&cwd).expect("project dir"); + fs::write( + home.join("settings.json"), + r#"{"trustedRoots": ["/tmp/config-default", "/tmp/shared"]}"#, + ) + .expect("write settings"); + + // when + let loaded = ConfigLoader::new(&cwd, &home) + .load() + .expect("config should load"); + let merged = loaded.trusted_roots_with_overrides(&[ + "/tmp/per-call".to_string(), + "/tmp/shared".to_string(), + ]); + + // then + assert_eq!( + merged, + ["/tmp/config-default", "/tmp/shared", "/tmp/per-call"] + ); + + fs::remove_dir_all(root).expect("cleanup temp dir"); + } + + #[test] + fn runtime_feature_trusted_roots_with_overrides_matches_runtime_config_merge() { + let config = RuntimeFeatureConfig { + trusted_roots: vec!["/tmp/config".to_string()], + ..RuntimeFeatureConfig::default() + }; + + assert_eq!( + config.trusted_roots_with_overrides(&["/tmp/per-call".to_string()]), + ["/tmp/config", "/tmp/per-call"] + ); + } + #[test] fn trusted_roots_default_is_empty_when_unset() { // given