permissions: make SessionConfigured profile-only

This commit is contained in:
Michael Bolin
2026-04-27 14:46:11 -07:00
parent 333c2c3d73
commit bbec1f19f7
17 changed files with 192 additions and 161 deletions
+88 -15
View File
@@ -3538,7 +3538,7 @@ pub struct SessionNetworkProxyRuntime {
pub socks_addr: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
#[derive(Debug, Clone, Serialize, JsonSchema, TS)]
pub struct SessionConfiguredEvent {
pub session_id: ThreadId,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -3566,16 +3566,8 @@ pub struct SessionConfiguredEvent {
#[serde(default)]
pub approvals_reviewer: ApprovalsReviewer,
/// Legacy sandbox projection for commands executed in the system.
///
/// Consumers should prefer `permission_profile` when it is present. This
/// field remains available as a compatibility fallback for older emitters
/// and sessions that only expose legacy sandbox state.
pub sandbox_policy: SandboxPolicy,
/// Canonical effective permissions for commands executed in the session.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub permission_profile: Option<PermissionProfile>,
pub permission_profile: PermissionProfile,
/// Working directory that should be treated as the *root* of the
/// session.
@@ -3606,6 +3598,67 @@ pub struct SessionConfiguredEvent {
pub rollout_path: Option<PathBuf>,
}
impl<'de> Deserialize<'de> for SessionConfiguredEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wire {
session_id: ThreadId,
forked_from_id: Option<ThreadId>,
#[serde(default)]
thread_name: Option<String>,
model: String,
model_provider_id: String,
service_tier: Option<ServiceTier>,
approval_policy: AskForApproval,
#[serde(default)]
approvals_reviewer: ApprovalsReviewer,
sandbox_policy: Option<SandboxPolicy>,
permission_profile: Option<PermissionProfile>,
cwd: AbsolutePathBuf,
reasoning_effort: Option<ReasoningEffortConfig>,
history_log_id: u64,
history_entry_count: usize,
initial_messages: Option<Vec<EventMsg>>,
network_proxy: Option<SessionNetworkProxyRuntime>,
rollout_path: Option<PathBuf>,
}
let wire = Wire::deserialize(deserializer)?;
let permission_profile = match (wire.permission_profile, wire.sandbox_policy) {
(Some(permission_profile), _) => permission_profile,
(None, Some(sandbox_policy)) => PermissionProfile::from_legacy_sandbox_policy_for_cwd(
&sandbox_policy,
wire.cwd.as_path(),
),
(None, None) => {
return Err(serde::de::Error::missing_field("permission_profile"));
}
};
Ok(Self {
session_id: wire.session_id,
forked_from_id: wire.forked_from_id,
thread_name: wire.thread_name,
model: wire.model,
model_provider_id: wire.model_provider_id,
service_tier: wire.service_tier,
approval_policy: wire.approval_policy,
approvals_reviewer: wire.approvals_reviewer,
permission_profile,
cwd: wire.cwd,
reasoning_effort: wire.reasoning_effort,
history_log_id: wire.history_log_id,
history_entry_count: wire.history_entry_count,
initial_messages: wire.initial_messages,
network_proxy: wire.network_proxy,
rollout_path: wire.rollout_path,
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct ThreadNameUpdatedEvent {
pub thread_id: ThreadId,
@@ -5085,6 +5138,7 @@ mod tests {
fn serialize_event() -> Result<()> {
let conversation_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
let rollout_file = NamedTempFile::new()?;
let permission_profile = PermissionProfile::read_only();
let event = Event {
id: "1234".to_string(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
@@ -5096,8 +5150,7 @@ mod tests {
service_tier: None,
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
permission_profile: permission_profile.clone(),
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -5117,9 +5170,7 @@ mod tests {
"model_provider_id": "openai",
"approval_policy": "never",
"approvals_reviewer": "user",
"sandbox_policy": {
"type": "read-only"
},
"permission_profile": permission_profile,
"cwd": test_path_buf("/home/user/project"),
"reasoning_effort": "medium",
"history_log_id": 0,
@@ -5131,6 +5182,28 @@ mod tests {
Ok(())
}
#[test]
fn deserialize_legacy_session_configured_event_uses_sandbox_policy() -> Result<()> {
let cwd = test_path_buf("/home/user/project");
let value = json!({
"session_id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
"model": "codex-mini-latest",
"model_provider_id": "openai",
"approval_policy": "never",
"approvals_reviewer": "user",
"sandbox_policy": {
"type": "read-only"
},
"cwd": cwd,
"history_log_id": 0,
"history_entry_count": 0,
});
let event: SessionConfiguredEvent = serde_json::from_value(value)?;
assert_eq!(event.permission_profile, PermissionProfile::read_only());
Ok(())
}
#[test]
fn vec_u8_as_base64_serialization_and_deserialization() -> Result<()> {
let event = ExecCommandOutputDeltaEvent {