mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Move permission profile snapshots into the protocol (#38651)
## What changed - Define `PermissionProfileSnapshot` as a protocol model and re-export it from `core-api`. - Store snapshots directly in core permission state while continuing to apply constraints to their concrete `PermissionProfile`. - Preserve active profile identity and profile-declared workspace roots without the core-only resolved profile variants. GitOrigin-RevId: cc5e03908e62054cde5de691faa47e471f2e1af5
This commit is contained in:
@@ -110,6 +110,7 @@ pub use codex_protocol::dynamic_tools::DynamicToolNamespaceTool;
|
||||
pub use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
pub use codex_protocol::error::Result as CodexResult;
|
||||
pub use codex_protocol::models::PermissionProfile;
|
||||
pub use codex_protocol::models::PermissionProfileSnapshot;
|
||||
pub use codex_protocol::openai_models::ModelPreset;
|
||||
pub use codex_protocol::protocol::AskForApproval;
|
||||
pub use codex_protocol::protocol::EnvironmentConfigState;
|
||||
|
||||
@@ -106,6 +106,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::BaseInstructionsProvenance;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
pub use codex_protocol::models::PermissionProfileSnapshot;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::openai_models::ModelMessages;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -179,7 +180,6 @@ use permission_profile_catalog::permission_profile_catalog_from_permissions;
|
||||
use permission_profile_catalog::permission_profile_is_allowed;
|
||||
use permission_profile_catalog::validate_permission_profile_for_deny_read;
|
||||
pub(crate) use permissions::is_builtin_permission_profile_name;
|
||||
pub use resolved_permission_profile::PermissionProfileSnapshot;
|
||||
pub(crate) use resolved_permission_profile::PermissionProfileState;
|
||||
|
||||
const DEFAULT_IGNORE_LARGE_UNTRACKED_DIRS: i64 = 200;
|
||||
@@ -361,10 +361,8 @@ impl Permissions {
|
||||
snapshot: PermissionProfileSnapshot,
|
||||
) -> ConstraintResult<()> {
|
||||
let permission_profile = Constrained::allow_only(snapshot.permission_profile().clone());
|
||||
self.permission_profile_state = PermissionProfileState::from_constrained_resolved(
|
||||
permission_profile,
|
||||
snapshot.into_resolved_permission_profile(),
|
||||
)?;
|
||||
self.permission_profile_state =
|
||||
PermissionProfileState::from_constrained_snapshot(permission_profile, snapshot)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,241 +1,22 @@
|
||||
use codex_config::Constrained;
|
||||
use codex_config::ConstraintResult;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::PermissionProfileSnapshot;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum BuiltInPermissionProfileId {
|
||||
ReadOnly,
|
||||
Workspace,
|
||||
DangerFullAccess,
|
||||
}
|
||||
|
||||
impl BuiltInPermissionProfileId {
|
||||
fn from_str(id: &str) -> Option<Self> {
|
||||
match id {
|
||||
BUILT_IN_PERMISSION_PROFILE_READ_ONLY => Some(Self::ReadOnly),
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE => Some(Self::Workspace),
|
||||
BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS => Some(Self::DangerFullAccess),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::ReadOnly => BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
|
||||
Self::Workspace => BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
Self::DangerFullAccess => BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum ResolvedPermissionProfile {
|
||||
Legacy(LegacyPermissionProfile),
|
||||
BuiltIn(BuiltInPermissionProfile),
|
||||
Named(NamedPermissionProfile),
|
||||
}
|
||||
|
||||
/// Trusted snapshot of a resolved permission profile.
|
||||
///
|
||||
/// This is a bridge for already-resolved session/config state. It keeps the
|
||||
/// concrete `PermissionProfile`, optional active profile id, and
|
||||
/// profile-defined workspace roots together so `Permissions` can validate and
|
||||
/// install them atomically. It is not a resolver: callers that are handling
|
||||
/// user-selected profile ids should resolve those ids through config instead
|
||||
/// of constructing this type directly.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PermissionProfileSnapshot {
|
||||
resolved_permission_profile: ResolvedPermissionProfile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct LegacyPermissionProfile {
|
||||
permission_profile: PermissionProfile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct BuiltInPermissionProfile {
|
||||
id: BuiltInPermissionProfileId,
|
||||
extends: Option<String>,
|
||||
permission_profile: PermissionProfile,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NamedPermissionProfile {
|
||||
id: String,
|
||||
extends: Option<String>,
|
||||
permission_profile: PermissionProfile,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
impl ResolvedPermissionProfile {
|
||||
pub(crate) fn from_active_profile(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
) -> Self {
|
||||
let Some(active_permission_profile) = active_permission_profile else {
|
||||
return Self::legacy(permission_profile);
|
||||
};
|
||||
|
||||
let ActivePermissionProfile { id, extends } = active_permission_profile;
|
||||
if let Some(built_in_id) = BuiltInPermissionProfileId::from_str(&id) {
|
||||
Self::BuiltIn(BuiltInPermissionProfile {
|
||||
id: built_in_id,
|
||||
extends,
|
||||
permission_profile,
|
||||
profile_workspace_roots,
|
||||
})
|
||||
} else {
|
||||
Self::Named(NamedPermissionProfile {
|
||||
id,
|
||||
extends,
|
||||
permission_profile,
|
||||
profile_workspace_roots,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn legacy(permission_profile: PermissionProfile) -> Self {
|
||||
Self::Legacy(LegacyPermissionProfile { permission_profile })
|
||||
}
|
||||
|
||||
pub(crate) fn permission_profile(&self) -> &PermissionProfile {
|
||||
match self {
|
||||
Self::Legacy(profile) => &profile.permission_profile,
|
||||
Self::BuiltIn(profile) => &profile.permission_profile,
|
||||
Self::Named(profile) => &profile.permission_profile,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
match self {
|
||||
Self::Legacy(_) => None,
|
||||
Self::BuiltIn(profile) => Some(ActivePermissionProfile {
|
||||
id: profile.id.as_str().to_string(),
|
||||
extends: profile.extends.clone(),
|
||||
}),
|
||||
Self::Named(profile) => Some(ActivePermissionProfile {
|
||||
id: profile.id.clone(),
|
||||
extends: profile.extends.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn profile_workspace_roots(&self) -> &[AbsolutePathBuf] {
|
||||
match self {
|
||||
Self::Legacy(_) => &[],
|
||||
Self::BuiltIn(profile) => &profile.profile_workspace_roots,
|
||||
Self::Named(profile) => &profile.profile_workspace_roots,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PermissionProfileSnapshot {
|
||||
/// Create a snapshot with no active profile id.
|
||||
///
|
||||
/// Prefer this only for legacy data or local overrides that genuinely do
|
||||
/// not have a named/built-in profile identity. Using this for a built-in or
|
||||
/// named profile will intentionally clear the active profile metadata.
|
||||
pub fn legacy(permission_profile: PermissionProfile) -> Self {
|
||||
Self {
|
||||
resolved_permission_profile: ResolvedPermissionProfile::legacy(permission_profile),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a snapshot for a known active profile id.
|
||||
///
|
||||
/// Use this only after a trusted caller has already resolved the active id
|
||||
/// to the supplied concrete `PermissionProfile`. This constructor does not
|
||||
/// verify that the id and profile match; `Permissions` will still enforce
|
||||
/// configured permission constraints when the snapshot is installed.
|
||||
pub fn active(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
) -> Self {
|
||||
Self::active_with_profile_workspace_roots(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a snapshot for a known active profile id with profile roots.
|
||||
///
|
||||
/// As with `active`, the caller is responsible for passing the concrete
|
||||
/// profile and active id that were resolved together. Use this variant when
|
||||
/// the selected profile declared workspace roots that should remain
|
||||
/// distinct from turn-scoped runtime workspace roots.
|
||||
pub fn active_with_profile_workspace_roots(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resolved_permission_profile: ResolvedPermissionProfile::from_active_profile(
|
||||
permission_profile,
|
||||
Some(active_permission_profile),
|
||||
profile_workspace_roots,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstruct a trusted snapshot from session state.
|
||||
///
|
||||
/// This is intended for session responses emitted by core, where the
|
||||
/// concrete profile and active profile id were captured together. Avoid
|
||||
/// using this as a shortcut for arbitrary user input because mismatched
|
||||
/// arguments can still misrepresent the active profile identity.
|
||||
pub fn from_session_snapshot(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
) -> Self {
|
||||
match active_permission_profile {
|
||||
Some(active_permission_profile) => {
|
||||
Self::active(permission_profile, active_permission_profile)
|
||||
}
|
||||
None => Self::legacy(permission_profile),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the concrete permission profile captured in this snapshot.
|
||||
pub fn permission_profile(&self) -> &PermissionProfile {
|
||||
self.resolved_permission_profile.permission_profile()
|
||||
}
|
||||
|
||||
/// Return the active profile id captured in this snapshot, if any.
|
||||
pub fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.resolved_permission_profile.active_permission_profile()
|
||||
}
|
||||
|
||||
/// Borrow profile-declared workspace roots captured in this snapshot.
|
||||
pub fn profile_workspace_roots(&self) -> &[AbsolutePathBuf] {
|
||||
self.resolved_permission_profile.profile_workspace_roots()
|
||||
}
|
||||
|
||||
pub(crate) fn into_resolved_permission_profile(self) -> ResolvedPermissionProfile {
|
||||
self.resolved_permission_profile
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PermissionProfileState {
|
||||
resolved_permission_profile: Constrained<ResolvedPermissionProfile>,
|
||||
permission_profile: Constrained<PermissionProfileSnapshot>,
|
||||
}
|
||||
|
||||
impl PermissionProfileState {
|
||||
pub(crate) fn from_constrained_legacy(
|
||||
constrained_permission_profile: Constrained<PermissionProfile>,
|
||||
) -> ConstraintResult<Self> {
|
||||
let resolved =
|
||||
ResolvedPermissionProfile::legacy(constrained_permission_profile.get().clone());
|
||||
Self::from_constrained_resolved(constrained_permission_profile, resolved)
|
||||
let snapshot =
|
||||
PermissionProfileSnapshot::legacy(constrained_permission_profile.get().clone());
|
||||
Self::from_constrained_snapshot(constrained_permission_profile, snapshot)
|
||||
}
|
||||
|
||||
pub(crate) fn from_constrained_active_profile(
|
||||
@@ -243,73 +24,66 @@ impl PermissionProfileState {
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
) -> ConstraintResult<Self> {
|
||||
let resolved = ResolvedPermissionProfile::from_active_profile(
|
||||
constrained_permission_profile.get().clone(),
|
||||
active_permission_profile,
|
||||
profile_workspace_roots,
|
||||
);
|
||||
Self::from_constrained_resolved(constrained_permission_profile, resolved)
|
||||
let permission_profile = constrained_permission_profile.get().clone();
|
||||
let snapshot = match active_permission_profile {
|
||||
Some(active_permission_profile) => {
|
||||
PermissionProfileSnapshot::active_with_profile_workspace_roots(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
profile_workspace_roots,
|
||||
)
|
||||
}
|
||||
None => PermissionProfileSnapshot::legacy(permission_profile),
|
||||
};
|
||||
Self::from_constrained_snapshot(constrained_permission_profile, snapshot)
|
||||
}
|
||||
|
||||
pub(crate) fn from_constrained_resolved(
|
||||
pub(crate) fn from_constrained_snapshot(
|
||||
constrained_permission_profile: Constrained<PermissionProfile>,
|
||||
resolved_permission_profile: ResolvedPermissionProfile,
|
||||
permission_profile: PermissionProfileSnapshot,
|
||||
) -> ConstraintResult<Self> {
|
||||
let permission_profile_constraint = constrained_permission_profile;
|
||||
let resolved_permission_profile = Constrained::new(
|
||||
resolved_permission_profile,
|
||||
move |candidate: &ResolvedPermissionProfile| {
|
||||
permission_profile_constraint.can_set(candidate.permission_profile())
|
||||
},
|
||||
)?;
|
||||
Ok(Self {
|
||||
resolved_permission_profile,
|
||||
})
|
||||
let permission_profile = Constrained::new(permission_profile, move |candidate| {
|
||||
constrained_permission_profile.can_set(candidate.permission_profile())
|
||||
})?;
|
||||
Ok(Self { permission_profile })
|
||||
}
|
||||
|
||||
pub(crate) fn permission_profile(&self) -> &PermissionProfile {
|
||||
self.resolved_permission_profile.get().permission_profile()
|
||||
self.permission_profile.get().permission_profile()
|
||||
}
|
||||
|
||||
pub(crate) fn snapshot(&self) -> PermissionProfileSnapshot {
|
||||
PermissionProfileSnapshot {
|
||||
resolved_permission_profile: self.resolved_permission_profile.get().clone(),
|
||||
}
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.resolved_permission_profile
|
||||
.get()
|
||||
.active_permission_profile()
|
||||
self.permission_profile.get().active_permission_profile()
|
||||
}
|
||||
|
||||
pub(crate) fn profile_workspace_roots(&self) -> &[AbsolutePathBuf] {
|
||||
self.resolved_permission_profile
|
||||
.get()
|
||||
.profile_workspace_roots()
|
||||
self.permission_profile.get().profile_workspace_roots()
|
||||
}
|
||||
|
||||
pub(crate) fn can_set_legacy_permission_profile(
|
||||
&self,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
let candidate = ResolvedPermissionProfile::legacy(permission_profile.clone());
|
||||
self.resolved_permission_profile.can_set(&candidate)
|
||||
let candidate = PermissionProfileSnapshot::legacy(permission_profile.clone());
|
||||
self.permission_profile.can_set(&candidate)
|
||||
}
|
||||
|
||||
pub(crate) fn set_legacy_permission_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
self.resolved_permission_profile
|
||||
.set(ResolvedPermissionProfile::legacy(permission_profile))
|
||||
self.permission_profile
|
||||
.set(PermissionProfileSnapshot::legacy(permission_profile))
|
||||
}
|
||||
|
||||
pub(crate) fn set_permission_profile_snapshot(
|
||||
&mut self,
|
||||
snapshot: PermissionProfileSnapshot,
|
||||
) -> ConstraintResult<()> {
|
||||
self.resolved_permission_profile
|
||||
.set(snapshot.into_resolved_permission_profile())
|
||||
self.permission_profile.set(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod network_policy;
|
||||
pub mod num_format;
|
||||
pub mod openai_models;
|
||||
pub mod parse_command;
|
||||
mod permission_profile_snapshot;
|
||||
pub mod permissions;
|
||||
pub mod plan_tool;
|
||||
pub mod protocol;
|
||||
|
||||
@@ -33,6 +33,7 @@ mod executed_tool_calls;
|
||||
|
||||
pub use crate::local_media::MAX_PROMPT_AUDIO_INPUT_BYTES;
|
||||
pub use crate::local_media::snapshot_local_user_input;
|
||||
pub use crate::permission_profile_snapshot::PermissionProfileSnapshot;
|
||||
pub use executed_tool_calls::ExecutedToolCall;
|
||||
pub use executed_tool_calls::ExecutedToolCallArguments;
|
||||
pub use executed_tool_calls::ExecutedToolCallTruncation;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::models::ActivePermissionProfile;
|
||||
use crate::models::PermissionProfile;
|
||||
|
||||
/// Trusted snapshot of a resolved permission profile.
|
||||
///
|
||||
/// Keeps the concrete permissions, optional active profile identity, and
|
||||
/// profile-defined workspace roots together for atomic installation. Callers
|
||||
/// handling user-selected profile ids must resolve those ids before
|
||||
/// constructing a snapshot.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PermissionProfileSnapshot {
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
impl PermissionProfileSnapshot {
|
||||
/// Create a snapshot with no active profile identity.
|
||||
///
|
||||
/// Use this only for legacy data or local overrides that genuinely do not
|
||||
/// have a named or built-in profile identity.
|
||||
pub fn legacy(permission_profile: PermissionProfile) -> Self {
|
||||
Self {
|
||||
permission_profile,
|
||||
active_permission_profile: None,
|
||||
profile_workspace_roots: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a snapshot for an already-resolved active profile.
|
||||
pub fn active(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
) -> Self {
|
||||
Self::active_with_profile_workspace_roots(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a snapshot for an active profile and its declared roots.
|
||||
///
|
||||
/// Profile roots remain distinct from turn-scoped runtime workspace roots.
|
||||
pub fn active_with_profile_workspace_roots(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
profile_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
permission_profile,
|
||||
active_permission_profile: Some(active_permission_profile),
|
||||
profile_workspace_roots,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstruct a trusted snapshot from already-resolved session state.
|
||||
pub fn from_session_snapshot(
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
) -> Self {
|
||||
match active_permission_profile {
|
||||
Some(active_permission_profile) => {
|
||||
Self::active(permission_profile, active_permission_profile)
|
||||
}
|
||||
None => Self::legacy(permission_profile),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the concrete permissions captured in this snapshot.
|
||||
pub fn permission_profile(&self) -> &PermissionProfile {
|
||||
&self.permission_profile
|
||||
}
|
||||
|
||||
/// Return the active profile identity captured in this snapshot, if any.
|
||||
pub fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.active_permission_profile.clone()
|
||||
}
|
||||
|
||||
/// Borrow profile-declared workspace roots captured in this snapshot.
|
||||
pub fn profile_workspace_roots(&self) -> &[AbsolutePathBuf] {
|
||||
&self.profile_workspace_roots
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user