mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Extract codex-config from codex-core
## Why
`codex-core` has accumulated config loading, requirements parsing, constraint logic, and config-layer state handling in one large crate. This refactor pulls that cohesive subsystem into a dedicated crate so we can reduce the compile/test surface area of `codex-core` and make future config work more isolated.
This is part of the broader goal of right-sizing crates to reduce monolithic rebuild cost and improve incremental development speed.
## What Changed
### New crate
- Added a new workspace crate: `codex-rs/config` (`codex-config`)
- Added workspace wiring in `codex-rs/Cargo.toml`
- Added dependency from `codex-core` to `codex-config`
### Moved config internals from `core` to `config`
Moved these modules into `codex-config`:
- `core/src/config/constraint.rs` -> `config/src/constraint.rs`
- `core/src/config_loader/cloud_requirements.rs` -> `config/src/config_loader/cloud_requirements.rs`
- `core/src/config_loader/config_requirements.rs` -> `config/src/config_loader/config_requirements.rs`
- `core/src/config_loader/fingerprint.rs` -> `config/src/config_loader/fingerprint.rs`
- `core/src/config_loader/merge.rs` -> `config/src/config_loader/merge.rs`
- `core/src/config_loader/overrides.rs` -> `config/src/config_loader/overrides.rs`
- `core/src/config_loader/requirements_exec_policy.rs` -> `config/src/config_loader/requirements_exec_policy.rs`
- `core/src/config_loader/state.rs` -> `config/src/config_loader/state.rs`
### Removed shim modules in `core`
After the move, the temporary one-line `pub use` shim files under `core/src/config_loader/` were deleted so history is a clean move/delete rather than introducing extra permanent forwarding modules.
`core/src/config_loader/mod.rs` now imports/re-exports directly from `codex_config`, including direct use of `build_cli_overrides_layer` and test-only access to `version_for_toml`.
### Follow-on fixes for direct imports
- Updated `core/src/config_loader/macos.rs` to use `super::{ConfigRequirementsToml, ConfigRequirementsWithSources, RequirementSource}`.
- Updated `core/src/config_loader/tests.rs` imports to reference `crate::config_loader` re-exports and `codex_config` exec-policy TOML types.
## Behavior and API Notes
- Config behavior is intended to be unchanged.
- `codex-core` continues to expose the same config-loader-facing API surface to its internal callers via `core/src/config_loader/mod.rs` re-exports.
- The main functional change is crate ownership and dependency direction, not config semantics.
## Validation
Ran:
- `cargo test -p codex-config`
- `cargo test -p codex-core --no-run`
- `cargo test -p codex-core config_loader::tests::load_requirements_toml_produces_expected_constraints`
- `cargo test -p codex-core config_loader::tests::requirements_exec_policy_tests::parses_single_prefix_rule_from_raw_toml`
- `just fmt`
- `just fix -p codex-config -p codex-core`
- `just fix -p codex-core`
All listed commands completed successfully in this workspace environment.
This commit is contained in:
22
codex-rs/Cargo.lock
generated
22
codex-rs/Cargo.lock
generated
@@ -1638,6 +1638,26 @@ dependencies = [
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-config"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-app-server-protocol",
|
||||
"codex-execpolicy",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"futures",
|
||||
"multimap",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-core"
|
||||
version = "0.0.0"
|
||||
@@ -1660,6 +1680,7 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-async-utils",
|
||||
"codex-client",
|
||||
"codex-config",
|
||||
"codex-execpolicy",
|
||||
"codex-file-search",
|
||||
"codex-git",
|
||||
@@ -1696,7 +1717,6 @@ dependencies = [
|
||||
"landlock",
|
||||
"libc",
|
||||
"maplit",
|
||||
"multimap",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"openssl-sys",
|
||||
|
||||
@@ -16,6 +16,7 @@ members = [
|
||||
"cloud-tasks-client",
|
||||
"cli",
|
||||
"common",
|
||||
"config",
|
||||
"shell-command",
|
||||
"core",
|
||||
"hooks",
|
||||
@@ -83,6 +84,7 @@ codex-chatgpt = { path = "chatgpt" }
|
||||
codex-cli = { path = "cli"}
|
||||
codex-client = { path = "codex-client" }
|
||||
codex-common = { path = "common" }
|
||||
codex-config = { path = "config" }
|
||||
codex-shell-command = { path = "shell-command" }
|
||||
codex-core = { path = "core" }
|
||||
codex-hooks = { path = "hooks" }
|
||||
|
||||
6
codex-rs/config/BUILD.bazel
Normal file
6
codex-rs/config/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "config",
|
||||
crate_name = "codex_config",
|
||||
)
|
||||
26
codex-rs/config/Cargo.toml
Normal file
26
codex-rs/config/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "codex-config"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
futures = { workspace = true, features = ["alloc", "std"] }
|
||||
multimap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
@@ -10,8 +10,8 @@ use std::fmt;
|
||||
|
||||
use super::requirements_exec_policy::RequirementsExecPolicy;
|
||||
use super::requirements_exec_policy::RequirementsExecPolicyToml;
|
||||
use crate::config::Constrained;
|
||||
use crate::config::ConstraintError;
|
||||
use crate::Constrained;
|
||||
use crate::ConstraintError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RequirementSource {
|
||||
@@ -80,7 +80,7 @@ pub struct ConfigRequirements {
|
||||
pub sandbox_policy: ConstrainedWithSource<SandboxPolicy>,
|
||||
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
|
||||
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
|
||||
pub(crate) exec_policy: Option<Sourced<RequirementsExecPolicy>>,
|
||||
pub exec_policy: Option<Sourced<RequirementsExecPolicy>>,
|
||||
pub enforce_residency: ConstrainedWithSource<Option<ResidencyRequirement>>,
|
||||
/// Managed network constraints derived from requirements.
|
||||
pub network: Option<Sourced<NetworkConstraints>>,
|
||||
@@ -560,7 +560,6 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config_loader::system_requirements_toml_file;
|
||||
use anyhow::Result;
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_execpolicy::Evaluation;
|
||||
@@ -574,6 +573,12 @@ mod tests {
|
||||
cmd.iter().map(std::string::ToString::to_string).collect()
|
||||
}
|
||||
|
||||
fn requirements_toml_file() -> Result<AbsolutePathBuf> {
|
||||
Ok(AbsolutePathBuf::try_from(
|
||||
std::env::temp_dir().join("requirements.toml"),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources {
|
||||
let ConfigRequirementsToml {
|
||||
allowed_approval_policies,
|
||||
@@ -732,7 +737,7 @@ mod tests {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let requirements_toml_file = system_requirements_toml_file()?;
|
||||
let requirements_toml_file = requirements_toml_file()?;
|
||||
let source_location = RequirementSource::SystemRequirementsToml {
|
||||
file: requirements_toml_file,
|
||||
};
|
||||
@@ -1149,7 +1154,7 @@ mod tests {
|
||||
]
|
||||
"#;
|
||||
let config: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
let requirements_toml_file = system_requirements_toml_file()?;
|
||||
let requirements_toml_file = requirements_toml_file()?;
|
||||
let source_location = RequirementSource::SystemRequirementsToml {
|
||||
file: requirements_toml_file,
|
||||
};
|
||||
@@ -34,7 +34,7 @@ pub(super) fn record_origins(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn version_for_toml(value: &TomlValue) -> String {
|
||||
pub fn version_for_toml(value: &TomlValue) -> String {
|
||||
let json = serde_json::to_value(value).unwrap_or(JsonValue::Null);
|
||||
let canonical = canonical_json(&json);
|
||||
let serialized = serde_json::to_vec(&canonical).unwrap_or_default();
|
||||
29
codex-rs/config/src/config_loader/mod.rs
Normal file
29
codex-rs/config/src/config_loader/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
pub mod cloud_requirements;
|
||||
pub mod config_requirements;
|
||||
pub mod fingerprint;
|
||||
pub mod merge;
|
||||
pub mod overrides;
|
||||
pub mod requirements_exec_policy;
|
||||
pub mod state;
|
||||
|
||||
pub use cloud_requirements::CloudRequirementsLoader;
|
||||
pub use config_requirements::ConfigRequirements;
|
||||
pub use config_requirements::ConfigRequirementsToml;
|
||||
pub use config_requirements::ConfigRequirementsWithSources;
|
||||
pub use config_requirements::ConstrainedWithSource;
|
||||
pub use config_requirements::McpServerIdentity;
|
||||
pub use config_requirements::McpServerRequirement;
|
||||
pub use config_requirements::NetworkConstraints;
|
||||
pub use config_requirements::NetworkRequirementsToml;
|
||||
pub use config_requirements::RequirementSource;
|
||||
pub use config_requirements::ResidencyRequirement;
|
||||
pub use config_requirements::SandboxModeRequirement;
|
||||
pub use config_requirements::Sourced;
|
||||
pub use config_requirements::WebSearchModeRequirement;
|
||||
pub use fingerprint::version_for_toml;
|
||||
pub use merge::merge_toml_values;
|
||||
pub use overrides::build_cli_overrides_layer;
|
||||
pub use state::ConfigLayerEntry;
|
||||
pub use state::ConfigLayerStack;
|
||||
pub use state::ConfigLayerStackOrdering;
|
||||
pub use state::LoaderOverrides;
|
||||
@@ -4,7 +4,7 @@ pub(crate) fn default_empty_table() -> TomlValue {
|
||||
TomlValue::Table(Default::default())
|
||||
}
|
||||
|
||||
pub(crate) fn build_cli_overrides_layer(cli_overrides: &[(String, TomlValue)]) -> TomlValue {
|
||||
pub fn build_cli_overrides_layer(cli_overrides: &[(String, TomlValue)]) -> TomlValue {
|
||||
let mut root = default_empty_table();
|
||||
for (path, value) in cli_overrides {
|
||||
apply_toml_override(&mut root, path, value.clone());
|
||||
@@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RequirementsExecPolicy {
|
||||
pub struct RequirementsExecPolicy {
|
||||
policy: Policy,
|
||||
}
|
||||
|
||||
241
codex-rs/config/src/constraint.rs
Normal file
241
codex-rs/config/src/constraint.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config_loader::RequirementSource;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum ConstraintError {
|
||||
#[error(
|
||||
"invalid value for `{field_name}`: `{candidate}` is not in the allowed set {allowed} (set by {requirement_source})"
|
||||
)]
|
||||
InvalidValue {
|
||||
field_name: &'static str,
|
||||
candidate: String,
|
||||
allowed: String,
|
||||
requirement_source: RequirementSource,
|
||||
},
|
||||
|
||||
#[error("field `{field_name}` cannot be empty")]
|
||||
EmptyField { field_name: String },
|
||||
|
||||
#[error("invalid rules in requirements (set by {requirement_source}): {reason}")]
|
||||
ExecPolicyParse {
|
||||
requirement_source: RequirementSource,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConstraintError {
|
||||
pub fn empty_field(field_name: impl Into<String>) -> Self {
|
||||
Self::EmptyField {
|
||||
field_name: field_name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ConstraintResult<T> = Result<T, ConstraintError>;
|
||||
|
||||
impl From<ConstraintError> for std::io::Error {
|
||||
fn from(err: ConstraintError) -> Self {
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, err)
|
||||
}
|
||||
}
|
||||
|
||||
type ConstraintValidator<T> = dyn Fn(&T) -> ConstraintResult<()> + Send + Sync;
|
||||
/// A ConstraintNormalizer is a function which transforms a value into another of the same type.
|
||||
/// `Constrained` uses normalizers to transform values to satisfy constraints or enforce values.
|
||||
type ConstraintNormalizer<T> = dyn Fn(T) -> T + Send + Sync;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Constrained<T> {
|
||||
value: T,
|
||||
validator: Arc<ConstraintValidator<T>>,
|
||||
normalizer: Option<Arc<ConstraintNormalizer<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Constrained<T> {
|
||||
pub fn new(
|
||||
initial_value: T,
|
||||
validator: impl Fn(&T) -> ConstraintResult<()> + Send + Sync + 'static,
|
||||
) -> ConstraintResult<Self> {
|
||||
let validator: Arc<ConstraintValidator<T>> = Arc::new(validator);
|
||||
validator(&initial_value)?;
|
||||
Ok(Self {
|
||||
value: initial_value,
|
||||
validator,
|
||||
normalizer: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// normalized creates a `Constrained` value with a normalizer function and a validator that allows any value.
|
||||
pub fn normalized(
|
||||
initial_value: T,
|
||||
normalizer: impl Fn(T) -> T + Send + Sync + 'static,
|
||||
) -> ConstraintResult<Self> {
|
||||
let validator: Arc<ConstraintValidator<T>> = Arc::new(|_| Ok(()));
|
||||
let normalizer: Arc<ConstraintNormalizer<T>> = Arc::new(normalizer);
|
||||
let normalized = normalizer(initial_value);
|
||||
validator(&normalized)?;
|
||||
Ok(Self {
|
||||
value: normalized,
|
||||
validator,
|
||||
normalizer: Some(normalizer),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn allow_any(initial_value: T) -> Self {
|
||||
Self {
|
||||
value: initial_value,
|
||||
validator: Arc::new(|_| Ok(())),
|
||||
normalizer: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow any value of T, using T's Default as the initial value.
|
||||
pub fn allow_any_from_default() -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::allow_any(T::default())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn value(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn can_set(&self, candidate: &T) -> ConstraintResult<()> {
|
||||
(self.validator)(candidate)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: T) -> ConstraintResult<()> {
|
||||
let value = if let Some(normalizer) = &self.normalizer {
|
||||
normalizer(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
(self.validator)(&value)?;
|
||||
self.value = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Constrained<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Constrained<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Constrained")
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Constrained<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn invalid_value(candidate: impl Into<String>, allowed: impl Into<String>) -> ConstraintError {
|
||||
ConstraintError::InvalidValue {
|
||||
field_name: "<unknown>",
|
||||
candidate: candidate.into(),
|
||||
allowed: allowed.into(),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_allow_any_accepts_any_value() {
|
||||
let mut constrained = Constrained::allow_any(5);
|
||||
constrained.set(-10).expect("allow any accepts all values");
|
||||
assert_eq!(constrained.value(), -10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_allow_any_default_uses_default_value() {
|
||||
let constrained = Constrained::<i32>::allow_any_from_default();
|
||||
assert_eq!(constrained.value(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> {
|
||||
let mut constrained = Constrained::normalized(-1, |value| value.max(0))?;
|
||||
assert_eq!(constrained.value(), 0);
|
||||
constrained.set(-5)?;
|
||||
assert_eq!(constrained.value(), 0);
|
||||
constrained.set(10)?;
|
||||
assert_eq!(constrained.value(), 10);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_new_rejects_invalid_initial_value() {
|
||||
let result = Constrained::new(0, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(result, Err(invalid_value("0", "positive values")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_set_rejects_invalid_value_and_leaves_previous() {
|
||||
let mut constrained = Constrained::new(1, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
})
|
||||
.expect("initial value should be accepted");
|
||||
|
||||
let err = constrained
|
||||
.set(-5)
|
||||
.expect_err("negative values should be rejected");
|
||||
assert_eq!(err, invalid_value("-5", "positive values"));
|
||||
assert_eq!(constrained.value(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_can_set_allows_probe_without_setting() {
|
||||
let constrained = Constrained::new(1, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
})
|
||||
.expect("initial value should be accepted");
|
||||
|
||||
constrained
|
||||
.can_set(&2)
|
||||
.expect("can_set should accept positive value");
|
||||
let err = constrained
|
||||
.can_set(&-1)
|
||||
.expect_err("can_set should reject negative value");
|
||||
assert_eq!(err, invalid_value("-1", "positive values"));
|
||||
assert_eq!(constrained.value(), 1);
|
||||
}
|
||||
}
|
||||
33
codex-rs/config/src/lib.rs
Normal file
33
codex-rs/config/src/lib.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
pub mod config_loader;
|
||||
mod constraint;
|
||||
|
||||
pub use config_loader::CloudRequirementsLoader;
|
||||
pub use config_loader::ConfigLayerEntry;
|
||||
pub use config_loader::ConfigLayerStack;
|
||||
pub use config_loader::ConfigLayerStackOrdering;
|
||||
pub use config_loader::ConfigRequirements;
|
||||
pub use config_loader::ConfigRequirementsToml;
|
||||
pub use config_loader::ConfigRequirementsWithSources;
|
||||
pub use config_loader::ConstrainedWithSource;
|
||||
pub use config_loader::LoaderOverrides;
|
||||
pub use config_loader::McpServerIdentity;
|
||||
pub use config_loader::McpServerRequirement;
|
||||
pub use config_loader::NetworkConstraints;
|
||||
pub use config_loader::NetworkRequirementsToml;
|
||||
pub use config_loader::RequirementSource;
|
||||
pub use config_loader::ResidencyRequirement;
|
||||
pub use config_loader::SandboxModeRequirement;
|
||||
pub use config_loader::Sourced;
|
||||
pub use config_loader::WebSearchModeRequirement;
|
||||
pub use config_loader::build_cli_overrides_layer;
|
||||
pub use config_loader::merge_toml_values;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicy;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicyDecisionToml;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicyParseError;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicyPatternTokenToml;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicyPrefixRuleToml;
|
||||
pub use config_loader::requirements_exec_policy::RequirementsExecPolicyToml;
|
||||
pub use config_loader::version_for_toml;
|
||||
pub use constraint::Constrained;
|
||||
pub use constraint::ConstraintError;
|
||||
pub use constraint::ConstraintResult;
|
||||
@@ -33,6 +33,7 @@ codex-app-server-protocol = { workspace = true }
|
||||
codex-apply-patch = { workspace = true }
|
||||
codex-async-utils = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
@@ -62,7 +63,6 @@ indexmap = { workspace = true }
|
||||
indoc = { workspace = true }
|
||||
keyring = { workspace = true, features = ["crypto-rust"] }
|
||||
libc = { workspace = true }
|
||||
multimap = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
os_info = { workspace = true }
|
||||
|
||||
@@ -1,241 +1,3 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config_loader::RequirementSource;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum ConstraintError {
|
||||
#[error(
|
||||
"invalid value for `{field_name}`: `{candidate}` is not in the allowed set {allowed} (set by {requirement_source})"
|
||||
)]
|
||||
InvalidValue {
|
||||
field_name: &'static str,
|
||||
candidate: String,
|
||||
allowed: String,
|
||||
requirement_source: RequirementSource,
|
||||
},
|
||||
|
||||
#[error("field `{field_name}` cannot be empty")]
|
||||
EmptyField { field_name: String },
|
||||
|
||||
#[error("invalid rules in requirements (set by {requirement_source}): {reason}")]
|
||||
ExecPolicyParse {
|
||||
requirement_source: RequirementSource,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConstraintError {
|
||||
pub fn empty_field(field_name: impl Into<String>) -> Self {
|
||||
Self::EmptyField {
|
||||
field_name: field_name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ConstraintResult<T> = Result<T, ConstraintError>;
|
||||
|
||||
impl From<ConstraintError> for std::io::Error {
|
||||
fn from(err: ConstraintError) -> Self {
|
||||
std::io::Error::new(std::io::ErrorKind::InvalidInput, err)
|
||||
}
|
||||
}
|
||||
|
||||
type ConstraintValidator<T> = dyn Fn(&T) -> ConstraintResult<()> + Send + Sync;
|
||||
/// A ConstraintNormalizer is a function which transforms a value into another of the same type.
|
||||
/// `Constrained` uses normalizers to transform values to satisfy constraints or enforce values.
|
||||
type ConstraintNormalizer<T> = dyn Fn(T) -> T + Send + Sync;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Constrained<T> {
|
||||
value: T,
|
||||
validator: Arc<ConstraintValidator<T>>,
|
||||
normalizer: Option<Arc<ConstraintNormalizer<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Constrained<T> {
|
||||
pub fn new(
|
||||
initial_value: T,
|
||||
validator: impl Fn(&T) -> ConstraintResult<()> + Send + Sync + 'static,
|
||||
) -> ConstraintResult<Self> {
|
||||
let validator: Arc<ConstraintValidator<T>> = Arc::new(validator);
|
||||
validator(&initial_value)?;
|
||||
Ok(Self {
|
||||
value: initial_value,
|
||||
validator,
|
||||
normalizer: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// normalized creates a `Constrained` value with a normalizer function and a validator that allows any value.
|
||||
pub fn normalized(
|
||||
initial_value: T,
|
||||
normalizer: impl Fn(T) -> T + Send + Sync + 'static,
|
||||
) -> ConstraintResult<Self> {
|
||||
let validator: Arc<ConstraintValidator<T>> = Arc::new(|_| Ok(()));
|
||||
let normalizer: Arc<ConstraintNormalizer<T>> = Arc::new(normalizer);
|
||||
let normalized = normalizer(initial_value);
|
||||
validator(&normalized)?;
|
||||
Ok(Self {
|
||||
value: normalized,
|
||||
validator,
|
||||
normalizer: Some(normalizer),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn allow_any(initial_value: T) -> Self {
|
||||
Self {
|
||||
value: initial_value,
|
||||
validator: Arc::new(|_| Ok(())),
|
||||
normalizer: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow any value of T, using T's Default as the initial value.
|
||||
pub fn allow_any_from_default() -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::allow_any(T::default())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn value(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn can_set(&self, candidate: &T) -> ConstraintResult<()> {
|
||||
(self.validator)(candidate)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: T) -> ConstraintResult<()> {
|
||||
let value = if let Some(normalizer) = &self.normalizer {
|
||||
normalizer(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
(self.validator)(&value)?;
|
||||
self.value = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Constrained<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Constrained<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Constrained")
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Constrained<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn invalid_value(candidate: impl Into<String>, allowed: impl Into<String>) -> ConstraintError {
|
||||
ConstraintError::InvalidValue {
|
||||
field_name: "<unknown>",
|
||||
candidate: candidate.into(),
|
||||
allowed: allowed.into(),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_allow_any_accepts_any_value() {
|
||||
let mut constrained = Constrained::allow_any(5);
|
||||
constrained.set(-10).expect("allow any accepts all values");
|
||||
assert_eq!(constrained.value(), -10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_allow_any_default_uses_default_value() {
|
||||
let constrained = Constrained::<i32>::allow_any_from_default();
|
||||
assert_eq!(constrained.value(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> {
|
||||
let mut constrained = Constrained::normalized(-1, |value| value.max(0))?;
|
||||
assert_eq!(constrained.value(), 0);
|
||||
constrained.set(-5)?;
|
||||
assert_eq!(constrained.value(), 0);
|
||||
constrained.set(10)?;
|
||||
assert_eq!(constrained.value(), 10);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_new_rejects_invalid_initial_value() {
|
||||
let result = Constrained::new(0, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(result, Err(invalid_value("0", "positive values")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_set_rejects_invalid_value_and_leaves_previous() {
|
||||
let mut constrained = Constrained::new(1, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
})
|
||||
.expect("initial value should be accepted");
|
||||
|
||||
let err = constrained
|
||||
.set(-5)
|
||||
.expect_err("negative values should be rejected");
|
||||
assert_eq!(err, invalid_value("-5", "positive values"));
|
||||
assert_eq!(constrained.value(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrained_can_set_allows_probe_without_setting() {
|
||||
let constrained = Constrained::new(1, |value| {
|
||||
if *value > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_value(value.to_string(), "positive values"))
|
||||
}
|
||||
})
|
||||
.expect("initial value should be accepted");
|
||||
|
||||
constrained
|
||||
.can_set(&2)
|
||||
.expect("can_set should accept positive value");
|
||||
let err = constrained
|
||||
.can_set(&-1)
|
||||
.expect_err("can_set should reject negative value");
|
||||
assert_eq!(err, invalid_value("-1", "positive values"));
|
||||
assert_eq!(constrained.value(), 1);
|
||||
}
|
||||
}
|
||||
pub use codex_config::Constrained;
|
||||
pub use codex_config::ConstraintError;
|
||||
pub use codex_config::ConstraintResult;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::config_requirements::ConfigRequirementsToml;
|
||||
use super::config_requirements::ConfigRequirementsWithSources;
|
||||
use super::config_requirements::RequirementSource;
|
||||
use super::ConfigRequirementsToml;
|
||||
use super::ConfigRequirementsWithSources;
|
||||
use super::RequirementSource;
|
||||
use base64::Engine;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use core_foundation::base::TCFType;
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
mod cloud_requirements;
|
||||
mod config_requirements;
|
||||
mod diagnostics;
|
||||
mod fingerprint;
|
||||
mod layer_io;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
mod merge;
|
||||
mod overrides;
|
||||
mod requirements_exec_policy;
|
||||
mod state;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -16,10 +9,10 @@ mod tests;
|
||||
use crate::config::CONFIG_TOML_FILE;
|
||||
use crate::config::ConfigToml;
|
||||
use crate::config::deserialize_config_toml_with_base;
|
||||
use crate::config_loader::config_requirements::ConfigRequirementsWithSources;
|
||||
use crate::config_loader::layer_io::LoadedConfigLayers;
|
||||
use crate::git_info::resolve_root_git_project_for_trust;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_config::config_loader::ConfigRequirementsWithSources;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -33,19 +26,27 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
pub use cloud_requirements::CloudRequirementsLoader;
|
||||
pub use config_requirements::ConfigRequirements;
|
||||
pub use config_requirements::ConfigRequirementsToml;
|
||||
pub use config_requirements::ConstrainedWithSource;
|
||||
pub use config_requirements::McpServerIdentity;
|
||||
pub use config_requirements::McpServerRequirement;
|
||||
pub use config_requirements::NetworkConstraints;
|
||||
pub use config_requirements::NetworkRequirementsToml;
|
||||
pub use config_requirements::RequirementSource;
|
||||
pub use config_requirements::ResidencyRequirement;
|
||||
pub use config_requirements::SandboxModeRequirement;
|
||||
pub use config_requirements::Sourced;
|
||||
pub use config_requirements::WebSearchModeRequirement;
|
||||
pub use codex_config::config_loader::CloudRequirementsLoader;
|
||||
pub use codex_config::config_loader::ConfigLayerEntry;
|
||||
pub use codex_config::config_loader::ConfigLayerStack;
|
||||
pub use codex_config::config_loader::ConfigLayerStackOrdering;
|
||||
pub use codex_config::config_loader::ConfigRequirements;
|
||||
pub use codex_config::config_loader::ConfigRequirementsToml;
|
||||
pub use codex_config::config_loader::ConstrainedWithSource;
|
||||
pub use codex_config::config_loader::LoaderOverrides;
|
||||
pub use codex_config::config_loader::McpServerIdentity;
|
||||
pub use codex_config::config_loader::McpServerRequirement;
|
||||
pub use codex_config::config_loader::NetworkConstraints;
|
||||
pub use codex_config::config_loader::NetworkRequirementsToml;
|
||||
pub use codex_config::config_loader::RequirementSource;
|
||||
pub use codex_config::config_loader::ResidencyRequirement;
|
||||
pub use codex_config::config_loader::SandboxModeRequirement;
|
||||
pub use codex_config::config_loader::Sourced;
|
||||
pub use codex_config::config_loader::WebSearchModeRequirement;
|
||||
#[cfg(test)]
|
||||
pub(crate) use codex_config::config_loader::fingerprint::version_for_toml;
|
||||
pub use codex_config::config_loader::merge_toml_values;
|
||||
pub(crate) use codex_config::config_loader::overrides::build_cli_overrides_layer;
|
||||
pub use diagnostics::ConfigError;
|
||||
pub use diagnostics::ConfigLoadError;
|
||||
pub use diagnostics::TextPosition;
|
||||
@@ -56,12 +57,6 @@ pub(crate) use diagnostics::first_layer_config_error_from_entries;
|
||||
pub use diagnostics::format_config_error;
|
||||
pub use diagnostics::format_config_error_with_source;
|
||||
pub(crate) use diagnostics::io_error_from_config_error;
|
||||
pub use merge::merge_toml_values;
|
||||
pub(crate) use overrides::build_cli_overrides_layer;
|
||||
pub use state::ConfigLayerEntry;
|
||||
pub use state::ConfigLayerStack;
|
||||
pub use state::ConfigLayerStackOrdering;
|
||||
pub use state::LoaderOverrides;
|
||||
|
||||
/// On Unix systems, load default settings from this file path, if present.
|
||||
/// Note that /etc/codex/ is treated as a "config folder," so subfolders such
|
||||
@@ -145,7 +140,7 @@ pub async fn load_config_layers_state(
|
||||
let cli_overrides_layer = if cli_overrides.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let cli_overrides_layer = overrides::build_cli_overrides_layer(cli_overrides);
|
||||
let cli_overrides_layer = build_cli_overrides_layer(cli_overrides);
|
||||
let base_dir = cwd
|
||||
.as_ref()
|
||||
.map(AbsolutePathBuf::as_path)
|
||||
|
||||
@@ -11,10 +11,10 @@ use crate::config_loader::ConfigLayerEntry;
|
||||
use crate::config_loader::ConfigLoadError;
|
||||
use crate::config_loader::ConfigRequirements;
|
||||
use crate::config_loader::ConfigRequirementsToml;
|
||||
use crate::config_loader::config_requirements::ConfigRequirementsWithSources;
|
||||
use crate::config_loader::config_requirements::RequirementSource;
|
||||
use crate::config_loader::fingerprint::version_for_toml;
|
||||
use crate::config_loader::ConfigRequirementsWithSources;
|
||||
use crate::config_loader::RequirementSource;
|
||||
use crate::config_loader::load_requirements_toml;
|
||||
use crate::config_loader::version_for_toml;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -1231,19 +1231,19 @@ async fn project_root_markers_supports_alternate_markers() -> std::io::Result<()
|
||||
}
|
||||
|
||||
mod requirements_exec_policy_tests {
|
||||
use super::super::config_requirements::ConfigRequirementsWithSources;
|
||||
use super::super::requirements_exec_policy::RequirementsExecPolicyDecisionToml;
|
||||
use super::super::requirements_exec_policy::RequirementsExecPolicyParseError;
|
||||
use super::super::requirements_exec_policy::RequirementsExecPolicyPatternTokenToml;
|
||||
use super::super::requirements_exec_policy::RequirementsExecPolicyPrefixRuleToml;
|
||||
use super::super::requirements_exec_policy::RequirementsExecPolicyToml;
|
||||
use crate::config_loader::ConfigLayerEntry;
|
||||
use crate::config_loader::ConfigLayerStack;
|
||||
use crate::config_loader::ConfigRequirements;
|
||||
use crate::config_loader::ConfigRequirementsToml;
|
||||
use crate::config_loader::ConfigRequirementsWithSources;
|
||||
use crate::config_loader::RequirementSource;
|
||||
use crate::exec_policy::load_exec_policy;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_config::config_loader::requirements_exec_policy::RequirementsExecPolicyDecisionToml;
|
||||
use codex_config::config_loader::requirements_exec_policy::RequirementsExecPolicyParseError;
|
||||
use codex_config::config_loader::requirements_exec_policy::RequirementsExecPolicyPatternTokenToml;
|
||||
use codex_config::config_loader::requirements_exec_policy::RequirementsExecPolicyPrefixRuleToml;
|
||||
use codex_config::config_loader::requirements_exec_policy::RequirementsExecPolicyToml;
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_execpolicy::Evaluation;
|
||||
use codex_execpolicy::RuleMatch;
|
||||
|
||||
@@ -82,18 +82,27 @@ async fn run_cmd_result_with_writable_roots(
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: writable_roots
|
||||
.iter()
|
||||
.map(|p| AbsolutePathBuf::try_from(p.as_path()).unwrap())
|
||||
.collect(),
|
||||
network_access: false,
|
||||
let mut sandbox_policy = SandboxPolicy::new_workspace_write_policy();
|
||||
let SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: sandbox_writable_roots,
|
||||
network_access,
|
||||
// Exclude tmp-related folders from writable roots because we need a
|
||||
// folder that is writable by tests but that we intentionally disallow
|
||||
// writing to in the sandbox.
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
..
|
||||
} = &mut sandbox_policy
|
||||
else {
|
||||
panic!("workspace-write policy expected");
|
||||
};
|
||||
*sandbox_writable_roots = writable_roots
|
||||
.iter()
|
||||
.map(|p| AbsolutePathBuf::try_from(p.as_path()).unwrap())
|
||||
.collect();
|
||||
*network_access = false;
|
||||
*exclude_tmpdir_env_var = true;
|
||||
*exclude_slash_tmp = true;
|
||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
||||
let codex_linux_sandbox_exe = Some(PathBuf::from(sandbox_program));
|
||||
|
||||
|
||||
@@ -98,6 +98,31 @@ mod tests {
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn workspace_write_policy(
|
||||
writable_roots: Vec<AbsolutePathBuf>,
|
||||
network_access: bool,
|
||||
exclude_tmpdir_env_var: bool,
|
||||
exclude_slash_tmp: bool,
|
||||
) -> SandboxPolicy {
|
||||
let mut policy = SandboxPolicy::new_workspace_write_policy();
|
||||
let SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: policy_writable_roots,
|
||||
network_access: policy_network_access,
|
||||
exclude_tmpdir_env_var: policy_exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp: policy_exclude_slash_tmp,
|
||||
..
|
||||
} = &mut policy
|
||||
else {
|
||||
panic!("workspace-write policy expected");
|
||||
};
|
||||
|
||||
*policy_writable_roots = writable_roots;
|
||||
*policy_network_access = network_access;
|
||||
*policy_exclude_tmpdir_env_var = exclude_tmpdir_env_var;
|
||||
*policy_exclude_slash_tmp = exclude_slash_tmp;
|
||||
policy
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn includes_additional_writable_roots() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
@@ -106,12 +131,12 @@ mod tests {
|
||||
let _ = fs::create_dir_all(&command_cwd);
|
||||
let _ = fs::create_dir_all(&extra_root);
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(extra_root.as_path()).unwrap()],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let policy = workspace_write_policy(
|
||||
vec![AbsolutePathBuf::try_from(extra_root.as_path()).unwrap()],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new());
|
||||
|
||||
@@ -132,12 +157,7 @@ mod tests {
|
||||
let _ = fs::create_dir_all(&command_cwd);
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let policy = workspace_write_policy(vec![], false, true, false);
|
||||
let mut env_map = HashMap::new();
|
||||
env_map.insert("TEMP".into(), temp_dir.to_string_lossy().to_string());
|
||||
|
||||
@@ -159,12 +179,7 @@ mod tests {
|
||||
let git_dir = command_cwd.join(".git");
|
||||
let _ = fs::create_dir_all(&git_dir);
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let policy = workspace_write_policy(vec![], false, true, false);
|
||||
|
||||
let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new());
|
||||
let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]
|
||||
@@ -186,12 +201,7 @@ mod tests {
|
||||
let _ = fs::create_dir_all(&command_cwd);
|
||||
let _ = fs::write(&git_file, "gitdir: .git/worktrees/example");
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let policy = workspace_write_policy(vec![], false, true, false);
|
||||
|
||||
let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new());
|
||||
let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]
|
||||
@@ -211,12 +221,7 @@ mod tests {
|
||||
let command_cwd = tmp.path().join("workspace");
|
||||
let _ = fs::create_dir_all(&command_cwd);
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let policy = workspace_write_policy(vec![], false, true, false);
|
||||
|
||||
let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new());
|
||||
assert_eq!(paths.allow.len(), 1);
|
||||
|
||||
@@ -467,12 +467,16 @@ mod windows_impl {
|
||||
use crate::policy::SandboxPolicy;
|
||||
|
||||
fn workspace_policy(network_access: bool) -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
}
|
||||
let mut policy = SandboxPolicy::new_workspace_write_policy();
|
||||
let SandboxPolicy::WorkspaceWrite {
|
||||
network_access: policy_network_access,
|
||||
..
|
||||
} = &mut policy
|
||||
else {
|
||||
panic!("workspace-write policy expected");
|
||||
};
|
||||
*policy_network_access = network_access;
|
||||
policy
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -508,12 +508,16 @@ mod windows_impl {
|
||||
use crate::policy::SandboxPolicy;
|
||||
|
||||
fn workspace_policy(network_access: bool) -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
}
|
||||
let mut policy = SandboxPolicy::new_workspace_write_policy();
|
||||
let SandboxPolicy::WorkspaceWrite {
|
||||
network_access: policy_network_access,
|
||||
..
|
||||
} = &mut policy
|
||||
else {
|
||||
panic!("workspace-write policy expected");
|
||||
};
|
||||
*policy_network_access = network_access;
|
||||
policy
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user