feat(permissions): expose network permission state (#103)
This commit is contained in:
@ -14,19 +14,21 @@ use application::{
|
||||
CloseProjectInput, CreateAgentInput, CreateLayoutInput, CreateMemoryInput, CreateSkillInput,
|
||||
DeleteAgentInput, DeleteEmbedderProfileInput, DeleteLayoutInput, DeleteMemoryInput,
|
||||
DeleteSkillInput, DeleteTemplateInput, DetectAgentDriftInput, GetMemoryInput,
|
||||
GetProjectWorkStateInput, GitBranchesInput, GitCheckoutInput, GitCommitInput, GitGraphInput,
|
||||
GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput, InspectConversationInput,
|
||||
LaunchAgentInput, ListAgentsInput, ListDevicesInput, ListLayoutsInput, ListMemoriesInput,
|
||||
ListResumableAgentsInput, ListSkillsInput, LiveSessions, LoadLayoutInput, McpRuntime,
|
||||
MutateLayoutInput, OpenProjectInput, ReadAgentContextInput, ReadConversationPageInput,
|
||||
ReadMcpToolPermissionsInput, ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput,
|
||||
ReconcileLayoutsInput, ReconcileLiveStateInput, RenameDeviceInput, RenameLayoutInput,
|
||||
ResolveAgentPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
GetProjectSystemPermissionsInput, GetProjectWorkStateInput, GitBranchesInput, GitCheckoutInput,
|
||||
GitCommitInput, GitGraphInput, GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput,
|
||||
InspectConversationInput, LaunchAgentInput, ListAgentsInput, ListDevicesInput,
|
||||
ListLayoutsInput, ListMemoriesInput, ListResumableAgentsInput, ListSkillsInput, LiveSessions,
|
||||
LoadLayoutInput, McpRuntime, MutateLayoutInput, OpenProjectInput, ReadAgentContextInput,
|
||||
ReadConversationPageInput, ReadMcpToolPermissionsInput, ReadMemoryIndexInput,
|
||||
ReadProjectContextInput, RecallMemoryInput, ReconcileLayoutsInput, ReconcileLiveStateInput,
|
||||
RenameDeviceInput, RenameLayoutInput, ResolveAgentPermissionsInput,
|
||||
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
RotateConversationLogInput, SetActiveLayoutInput, SnapshotRunningAgentsInput,
|
||||
StopLiveAgentInput, SyncAgentWithTemplateInput, UnassignSkillFromAgentInput,
|
||||
UpdateAgentContextInput, UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UpdateMemoryInput, UpdateProjectContextInput, UpdateProjectMcpToolPermissionsInput,
|
||||
UpdateProjectPermissionsInput, UpdateSkillInput,
|
||||
UpdateAgentSystemPermissionsInput, UpdateMemoryInput, UpdateProjectContextInput,
|
||||
UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput,
|
||||
UpdateProjectSystemPermissionsInput, UpdateSkillInput,
|
||||
};
|
||||
use domain::ports::ModelServerRuntime;
|
||||
use domain::ports::PtyHandle;
|
||||
@ -52,19 +54,22 @@ use crate::dto::{
|
||||
MemoryDto, MemoryIndexDto, MemoryLinksDto, MemoryListDto, ModelServerConfigDto,
|
||||
ModelServerConfigListDto, OpenCodeProviderListDto, OpenTerminalRequestDto,
|
||||
PreviewModelServerCommandDto, ProfileDto, ProfileListDto, ProjectDto, ProjectListDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectWorkStateDto,
|
||||
ReadAgentContextResponseDto, ReadConversationPageRequestDto, ReattachChatDto,
|
||||
ReattachResultDto, RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk,
|
||||
ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto, ResumableAgentListDto,
|
||||
SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectSystemPermissionsDto,
|
||||
ProjectWorkStateDto, ReadAgentContextResponseDto, ReadConversationPageRequestDto,
|
||||
ReattachChatDto, ReattachResultDto, RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk,
|
||||
ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
ResumableAgentListDto, SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
SaveOpenCodeProviderProfileRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto,
|
||||
SetActiveLayoutResultDto, SkillDto, SkillListDto, StopLiveAgentRequestDto,
|
||||
StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
|
||||
TemplateListDto, TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
|
||||
UpdateAgentContextRequestDto, UpdateAgentMcpToolPermissionsRequestDto,
|
||||
UpdateAgentPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateAgentPermissionsRequestDto, UpdateAgentSystemPermissionsRequestDto,
|
||||
UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateProjectMcpToolPermissionsRequestDto, UpdateProjectPermissionsRequestDto,
|
||||
UpdateSkillRequestDto, UpdateTemplateRequestDto, WriteTerminalRequestDto,
|
||||
UpdateProjectSystemPermissionsRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
WriteTerminalRequestDto,
|
||||
};
|
||||
use crate::embedded_server::{
|
||||
EmbeddedServerStatusDto, ServerExposurePreviewDto, ServerExposureSettingsDto,
|
||||
@ -549,6 +554,87 @@ pub async fn resolve_agent_permissions(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `get_project_system_permissions` — read `.ideai/system-permissions.json`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid project id or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn get_project_system_permissions(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectSystemPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
state
|
||||
.get_project_system_permissions
|
||||
.execute(GetProjectSystemPermissionsInput { project })
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_project_system_permissions` — replace project default system permissions.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid project id or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn update_project_system_permissions(
|
||||
request: UpdateProjectSystemPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectSystemPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.update_project_system_permissions
|
||||
.execute(UpdateProjectSystemPermissionsInput {
|
||||
project,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_agent_system_permissions` — replace or remove one agent system override.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid ids or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn update_agent_system_permissions(
|
||||
request: UpdateAgentSystemPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectSystemPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.update_agent_system_permissions
|
||||
.execute(UpdateAgentSystemPermissionsInput {
|
||||
project,
|
||||
agent_id,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `resolve_agent_system_permissions` — resolve wanted plus runtime-constrained system permissions.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid ids or store/probe failure.
|
||||
#[tauri::command]
|
||||
pub async fn resolve_agent_system_permissions(
|
||||
request: ResolveAgentSystemPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ResolvedAgentSystemPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.resolve_agent_system_permissions
|
||||
.execute(ResolveAgentSystemPermissionsInput { project, agent_id })
|
||||
.await
|
||||
.map(|out| ResolvedAgentSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `get_mcp_tool_permissions` — read `.ideai/mcp-tool-permissions.json` plus catalogue.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@ -229,6 +229,10 @@ pub fn run() {
|
||||
commands::update_project_permissions,
|
||||
commands::update_agent_permissions,
|
||||
commands::resolve_agent_permissions,
|
||||
commands::get_project_system_permissions,
|
||||
commands::update_project_system_permissions,
|
||||
commands::update_agent_system_permissions,
|
||||
commands::resolve_agent_system_permissions,
|
||||
commands::get_mcp_tool_permissions,
|
||||
commands::update_project_mcp_tool_permissions,
|
||||
commands::update_agent_mcp_tool_permissions,
|
||||
|
||||
69
crates/app-tauri/tests/dto_system_permissions.rs
Normal file
69
crates/app-tauri/tests/dto_system_permissions.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use app_tauri_lib::dto::{
|
||||
ProjectSystemPermissionsDto, ResolvedAgentSystemPermissionsDto,
|
||||
UpdateProjectSystemPermissionsRequestDto,
|
||||
};
|
||||
use domain::{
|
||||
NetworkPolicy, ProjectSystemPermissions, ResolvedAgentSystemPermissions, RuntimeLock,
|
||||
RuntimeLockState, SystemPermissionControl, SystemPermissionControlMode, SystemPermissionSet,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn project_system_permissions_dto_serializes_network_policy_contract() {
|
||||
let dto = ProjectSystemPermissionsDto(ProjectSystemPermissions::new(
|
||||
Some(SystemPermissionSet::new(Some(NetworkPolicy::Ask))),
|
||||
vec![],
|
||||
));
|
||||
|
||||
let value = serde_json::to_value(dto).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"version": 1,
|
||||
"projectDefault": {
|
||||
"network": "ask",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_project_system_permissions_request_deserializes_allow_deny_ask() {
|
||||
let dto: UpdateProjectSystemPermissionsRequestDto = serde_json::from_value(json!({
|
||||
"projectId": "project",
|
||||
"permissions": {
|
||||
"network": "allow",
|
||||
},
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
dto.permissions.and_then(|permissions| permissions.network),
|
||||
Some(NetworkPolicy::Allow)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolved_agent_system_permissions_dto_carries_runtime_lock_and_read_only_control() {
|
||||
let dto = ResolvedAgentSystemPermissionsDto(ResolvedAgentSystemPermissions {
|
||||
wanted: Some(NetworkPolicy::Allow),
|
||||
effective: NetworkPolicy::Deny,
|
||||
runtime_lock: RuntimeLock {
|
||||
state: RuntimeLockState::Locked,
|
||||
source: Some("external-runtime".to_owned()),
|
||||
reason: Some("not inspectable".to_owned()),
|
||||
},
|
||||
control: SystemPermissionControl {
|
||||
mode: SystemPermissionControlMode::ReadOnly,
|
||||
reason: Some("not editable".to_owned()),
|
||||
},
|
||||
});
|
||||
|
||||
let value = serde_json::to_value(dto).unwrap();
|
||||
|
||||
assert_eq!(value["wanted"], "allow");
|
||||
assert_eq!(value["effective"], "deny");
|
||||
assert_eq!(value["runtimeLock"]["state"], "locked");
|
||||
assert_eq!(value["control"]["mode"], "readOnly");
|
||||
}
|
||||
@ -32,6 +32,7 @@ pub mod project;
|
||||
pub mod remote;
|
||||
pub mod skill;
|
||||
pub mod sprints;
|
||||
pub mod system_permissions;
|
||||
pub mod template;
|
||||
pub mod terminal;
|
||||
pub mod ticket_assistant;
|
||||
@ -170,6 +171,13 @@ pub use sprints::{
|
||||
RenameSprintInput, ReorderSprints, ReorderSprintsInput, ReorderSprintsOutput, SprintListEntry,
|
||||
SprintOutput, UnassignTicketFromSprint, UnassignTicketFromSprintInput,
|
||||
};
|
||||
pub use system_permissions::{
|
||||
GetProjectSystemPermissions, GetProjectSystemPermissionsInput,
|
||||
GetProjectSystemPermissionsOutput, ResolveAgentSystemPermissions,
|
||||
ResolveAgentSystemPermissionsInput, ResolveAgentSystemPermissionsOutput,
|
||||
UpdateAgentSystemPermissions, UpdateAgentSystemPermissionsInput,
|
||||
UpdateProjectSystemPermissions, UpdateProjectSystemPermissionsInput,
|
||||
};
|
||||
pub use template::{
|
||||
AgentDrift, CreateAgentFromTemplate, CreateAgentFromTemplateInput,
|
||||
CreateAgentFromTemplateOutput, CreateTemplate, CreateTemplateInput, CreateTemplateOutput,
|
||||
|
||||
168
crates/application/src/system_permissions.rs
Normal file
168
crates/application/src/system_permissions.rs
Normal file
@ -0,0 +1,168 @@
|
||||
//! System permission use cases.
|
||||
//!
|
||||
//! These use cases persist the wanted project/agent policies separately from
|
||||
//! filesystem/bash permissions and resolve them through a read-only runtime
|
||||
//! probe.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ports::{RuntimePermissionProbe, SystemPermissionStore};
|
||||
use domain::{
|
||||
resolve_agent_system_permissions, AgentId, Project, ProjectSystemPermissions,
|
||||
ResolvedAgentSystemPermissions, SystemPermissionSet,
|
||||
};
|
||||
|
||||
use crate::error::AppError;
|
||||
|
||||
/// Reads the full project system permission document.
|
||||
pub struct GetProjectSystemPermissions {
|
||||
store: Arc<dyn SystemPermissionStore>,
|
||||
}
|
||||
|
||||
impl GetProjectSystemPermissions {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(store: Arc<dyn SystemPermissionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
|
||||
/// Executes the read.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: GetProjectSystemPermissionsInput,
|
||||
) -> Result<GetProjectSystemPermissionsOutput, AppError> {
|
||||
let permissions = self.store.load_system_permissions(&input.project).await?;
|
||||
Ok(GetProjectSystemPermissionsOutput { permissions })
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`GetProjectSystemPermissions`].
|
||||
pub struct GetProjectSystemPermissionsInput {
|
||||
/// Target project.
|
||||
pub project: Project,
|
||||
}
|
||||
|
||||
/// Output for project system permission reads/mutations.
|
||||
pub struct GetProjectSystemPermissionsOutput {
|
||||
/// Persisted system permission document.
|
||||
pub permissions: ProjectSystemPermissions,
|
||||
}
|
||||
|
||||
/// Replaces the project default system permissions.
|
||||
pub struct UpdateProjectSystemPermissions {
|
||||
store: Arc<dyn SystemPermissionStore>,
|
||||
}
|
||||
|
||||
impl UpdateProjectSystemPermissions {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(store: Arc<dyn SystemPermissionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
|
||||
/// Executes the mutation.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: UpdateProjectSystemPermissionsInput,
|
||||
) -> Result<GetProjectSystemPermissionsOutput, AppError> {
|
||||
let mut doc = self.store.load_system_permissions(&input.project).await?;
|
||||
doc.set_project_default(input.permissions);
|
||||
self.store
|
||||
.save_system_permissions(&input.project, &doc)
|
||||
.await?;
|
||||
Ok(GetProjectSystemPermissionsOutput { permissions: doc })
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`UpdateProjectSystemPermissions`].
|
||||
pub struct UpdateProjectSystemPermissionsInput {
|
||||
/// Target project.
|
||||
pub project: Project,
|
||||
/// New project default policy. `None` removes project defaults.
|
||||
pub permissions: Option<SystemPermissionSet>,
|
||||
}
|
||||
|
||||
/// Replaces one agent system permission override.
|
||||
pub struct UpdateAgentSystemPermissions {
|
||||
store: Arc<dyn SystemPermissionStore>,
|
||||
}
|
||||
|
||||
impl UpdateAgentSystemPermissions {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(store: Arc<dyn SystemPermissionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
|
||||
/// Executes the mutation.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: UpdateAgentSystemPermissionsInput,
|
||||
) -> Result<GetProjectSystemPermissionsOutput, AppError> {
|
||||
let mut doc = self.store.load_system_permissions(&input.project).await?;
|
||||
doc.set_agent_permissions(input.agent_id, input.permissions);
|
||||
self.store
|
||||
.save_system_permissions(&input.project, &doc)
|
||||
.await?;
|
||||
Ok(GetProjectSystemPermissionsOutput { permissions: doc })
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`UpdateAgentSystemPermissions`].
|
||||
pub struct UpdateAgentSystemPermissionsInput {
|
||||
/// Target project.
|
||||
pub project: Project,
|
||||
/// Target agent.
|
||||
pub agent_id: AgentId,
|
||||
/// New agent policy. `None` removes the override.
|
||||
pub permissions: Option<SystemPermissionSet>,
|
||||
}
|
||||
|
||||
/// Resolves effective system permissions for one agent.
|
||||
pub struct ResolveAgentSystemPermissions {
|
||||
store: Arc<dyn SystemPermissionStore>,
|
||||
runtime_probe: Arc<dyn RuntimePermissionProbe>,
|
||||
}
|
||||
|
||||
impl ResolveAgentSystemPermissions {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
store: Arc<dyn SystemPermissionStore>,
|
||||
runtime_probe: Arc<dyn RuntimePermissionProbe>,
|
||||
) -> Self {
|
||||
Self {
|
||||
store,
|
||||
runtime_probe,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the resolution.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: ResolveAgentSystemPermissionsInput,
|
||||
) -> Result<ResolveAgentSystemPermissionsOutput, AppError> {
|
||||
let doc = self.store.load_system_permissions(&input.project).await?;
|
||||
let runtime = self
|
||||
.runtime_probe
|
||||
.probe_runtime_permissions(&input.project, input.agent_id)
|
||||
.await?;
|
||||
Ok(ResolveAgentSystemPermissionsOutput {
|
||||
permissions: resolve_agent_system_permissions(&doc, input.agent_id, runtime),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`ResolveAgentSystemPermissions`].
|
||||
pub struct ResolveAgentSystemPermissionsInput {
|
||||
/// Target project.
|
||||
pub project: Project,
|
||||
/// Target agent.
|
||||
pub agent_id: AgentId,
|
||||
}
|
||||
|
||||
/// Output for [`ResolveAgentSystemPermissions`].
|
||||
pub struct ResolveAgentSystemPermissionsOutput {
|
||||
/// Resolved system permissions.
|
||||
pub permissions: ResolvedAgentSystemPermissions,
|
||||
}
|
||||
154
crates/application/tests/system_permission_usecases.rs
Normal file
154
crates/application/tests/system_permission_usecases.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use application::{
|
||||
ResolveAgentSystemPermissions, ResolveAgentSystemPermissionsInput,
|
||||
UpdateAgentSystemPermissions, UpdateAgentSystemPermissionsInput,
|
||||
UpdateProjectSystemPermissions, UpdateProjectSystemPermissionsInput,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use domain::ids::{AgentId, ProjectId};
|
||||
use domain::ports::{RuntimeError, RuntimePermissionProbe, StoreError, SystemPermissionStore};
|
||||
use domain::project::{Project, ProjectPath};
|
||||
use domain::remote::RemoteRef;
|
||||
use domain::{
|
||||
NetworkPolicy, ProjectSystemPermissions, RuntimeLockState, RuntimePermissionSnapshot,
|
||||
SystemPermissionControlMode, SystemPermissionSet,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeSystemPermissionStore {
|
||||
doc: Mutex<ProjectSystemPermissions>,
|
||||
saves: Mutex<usize>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SystemPermissionStore for FakeSystemPermissionStore {
|
||||
async fn load_system_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
) -> Result<ProjectSystemPermissions, StoreError> {
|
||||
Ok(self.doc.lock().unwrap().clone())
|
||||
}
|
||||
|
||||
async fn save_system_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
permissions: &ProjectSystemPermissions,
|
||||
) -> Result<(), StoreError> {
|
||||
*self.doc.lock().unwrap() = permissions.clone();
|
||||
*self.saves.lock().unwrap() += 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct LockedProbe;
|
||||
|
||||
#[async_trait]
|
||||
impl RuntimePermissionProbe for LockedProbe {
|
||||
async fn probe_runtime_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
_agent_id: AgentId,
|
||||
) -> Result<RuntimePermissionSnapshot, RuntimeError> {
|
||||
Ok(RuntimePermissionSnapshot::locked_uninspectable())
|
||||
}
|
||||
}
|
||||
|
||||
fn project() -> Project {
|
||||
Project::new(
|
||||
ProjectId::new_random(),
|
||||
"system-permissions",
|
||||
ProjectPath::new("/home/me/proj").unwrap(),
|
||||
RemoteRef::local(),
|
||||
1_700_000_000_000,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_project_system_permissions_replaces_defaults_and_persists() {
|
||||
let store = Arc::new(FakeSystemPermissionStore::default());
|
||||
let use_case = UpdateProjectSystemPermissions::new(store.clone());
|
||||
|
||||
let out = use_case
|
||||
.execute(UpdateProjectSystemPermissionsInput {
|
||||
project: project(),
|
||||
permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Ask))),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
out.permissions.project_default.unwrap().network,
|
||||
Some(NetworkPolicy::Ask)
|
||||
);
|
||||
assert_eq!(*store.saves.lock().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_agent_system_permissions_adds_and_removes_sparse_override() {
|
||||
let store = Arc::new(FakeSystemPermissionStore::default());
|
||||
let use_case = UpdateAgentSystemPermissions::new(store.clone());
|
||||
let agent = AgentId::new_random();
|
||||
|
||||
use_case
|
||||
.execute(UpdateAgentSystemPermissionsInput {
|
||||
project: project(),
|
||||
agent_id: agent,
|
||||
permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Deny))),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store
|
||||
.doc
|
||||
.lock()
|
||||
.unwrap()
|
||||
.agent_permissions(agent)
|
||||
.unwrap()
|
||||
.network,
|
||||
Some(NetworkPolicy::Deny)
|
||||
);
|
||||
|
||||
let out = use_case
|
||||
.execute(UpdateAgentSystemPermissionsInput {
|
||||
project: project(),
|
||||
agent_id: agent,
|
||||
permissions: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(out.permissions.agent_permissions(agent).is_none());
|
||||
assert_eq!(*store.saves.lock().unwrap(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_agent_system_permissions_exposes_locked_runtime_read_only() {
|
||||
let agent = AgentId::new_random();
|
||||
let store = Arc::new(FakeSystemPermissionStore {
|
||||
doc: Mutex::new(ProjectSystemPermissions::new(
|
||||
Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))),
|
||||
vec![],
|
||||
)),
|
||||
saves: Mutex::new(0),
|
||||
});
|
||||
let use_case = ResolveAgentSystemPermissions::new(store, Arc::new(LockedProbe));
|
||||
|
||||
let out = use_case
|
||||
.execute(ResolveAgentSystemPermissionsInput {
|
||||
project: project(),
|
||||
agent_id: agent,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(out.permissions.wanted, Some(NetworkPolicy::Allow));
|
||||
assert_eq!(out.permissions.effective, NetworkPolicy::Deny);
|
||||
assert_eq!(out.permissions.runtime_lock.state, RuntimeLockState::Locked);
|
||||
assert_eq!(
|
||||
out.permissions.control.mode,
|
||||
SystemPermissionControlMode::ReadOnly
|
||||
);
|
||||
}
|
||||
@ -16,7 +16,10 @@ use application::{
|
||||
ListProjectsOutput, LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState,
|
||||
StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage, TurnSource, TurnView,
|
||||
};
|
||||
use domain::{AgentBusyState, PageCursor, PageDirection, Project, ProjectId, TurnRole};
|
||||
use domain::{
|
||||
AgentBusyState, PageCursor, PageDirection, Project, ProjectId, ProjectSystemPermissions,
|
||||
ResolvedAgentSystemPermissions, SystemPermissionSet, TurnRole,
|
||||
};
|
||||
|
||||
pub use crate::ticket_dto::*;
|
||||
|
||||
@ -1889,6 +1892,16 @@ pub struct ProjectPermissionsDto(pub ProjectPermissions);
|
||||
#[serde(transparent)]
|
||||
pub struct EffectivePermissionsDto(pub EffectivePermissions);
|
||||
|
||||
/// Full project system permission document crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProjectSystemPermissionsDto(pub ProjectSystemPermissions);
|
||||
|
||||
/// Resolved agent system permissions crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ResolvedAgentSystemPermissionsDto(pub ResolvedAgentSystemPermissions);
|
||||
|
||||
/// Canonical MCP tool catalogue classification crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -1959,6 +1972,38 @@ pub struct ResolveAgentPermissionsRequestDto {
|
||||
pub agent_id: String,
|
||||
}
|
||||
|
||||
/// Request DTO for updating project default system permissions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProjectSystemPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// New project defaults. `null` removes defaults.
|
||||
pub permissions: Option<SystemPermissionSet>,
|
||||
}
|
||||
|
||||
/// Request DTO for updating one agent system permission override.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateAgentSystemPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// Target agent id.
|
||||
pub agent_id: String,
|
||||
/// New override. `null` removes the override.
|
||||
pub permissions: Option<SystemPermissionSet>,
|
||||
}
|
||||
|
||||
/// Request DTO for resolving one agent's effective system permissions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolveAgentSystemPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// Target agent id.
|
||||
pub agent_id: String,
|
||||
}
|
||||
|
||||
/// Request DTO for updating project default MCP tool permissions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@ -22,33 +22,35 @@ use application::{
|
||||
DeleteModelServer, DeleteProfile, DeleteSkill, DeleteSprint, DeleteTemplate,
|
||||
DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles, DismissEmbedderSuggestion,
|
||||
EnsureLocalModelServer, FirstRunState, GetAppExitWorkGuardState, GetLiveStateLean, GetMemory,
|
||||
GetProjectPermissions, GetProjectWorkState, GitBranches, GitCheckout, GitCommit, GitGraph,
|
||||
GitInit, GitLog, GitStage, GitStatus, GitUnstage, HarvestMemoryFromTurn, HealthUseCase,
|
||||
InspectConversation, InstallPluginFromArchive, InstallPluginFromDirectory,
|
||||
JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput, LinkIssues, ListAgents,
|
||||
ListAgentsInput, ListDevices, ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories,
|
||||
ListModelServers, ListOpenCodeProviders, ListPluginRuntimeContributions, ListPlugins,
|
||||
ListProfiles, ListProjects, ListResumableAgents, ListSkills, ListSprints, ListTemplates,
|
||||
LiveAgentRegistry, LiveSessions, LiveStateLeanProvider, LiveStateProvider,
|
||||
LiveStateReadProvider, LoadLayout, McpRuntime, McpToolPermissionCatalogue, MoveTabToNewWindow,
|
||||
MutateLayout, OnnxModelView, OpenProject, OpenTerminal, OpenTicketAssistant,
|
||||
OrchestratorService, PairAttemptLimiter, PairDevice, PermissionProjectorRegistry,
|
||||
ProposeContext, ReadAgentContext, ReadContext, ReadConversationPage, ReadIssue,
|
||||
ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory, ReadMemoryIndex, ReadProjectContext,
|
||||
ReadSkill, ReadTemplate, RecallMemory, ReconcileLayouts, ReconcileLiveState,
|
||||
ReconcileLiveStateInput, ReconcilePluginMcpServers, RecordTurn, RecordTurnProvider,
|
||||
ReferenceProfiles, RenameDevice, RenameLayout, RenameSprint, ReorderSprints, ResizeTerminal,
|
||||
ResolveAgentPermissions, ResolveMemoryLinks, RestoreOpenWindows, RetryBackgroundTask,
|
||||
ReviewPluginPackage, RevokeAllDevices, RevokeDevice, RotateConversationLog,
|
||||
SaveEmbedderProfile, SaveModelServer, SaveOpenCodeProviderProfile, SaveProfile,
|
||||
SessionLimitService, SetActiveLayout, SetPluginEnabled, SnapshotOpenWindows,
|
||||
SnapshotRunningAgents, SpawnBackgroundCommand, StopLiveAgent, StructuredRoutingMode,
|
||||
StructuredSessions, SuggestedThisSession, SyncAgentWithTemplate, TerminalSessions, TouchDevice,
|
||||
UnassignSkillFromAgent, UnassignTicketFromSprint, UninstallPlugin, UnlinkIssues,
|
||||
UpdateAgentContext, UpdateAgentMcpToolPermissions, UpdateAgentPermissions, UpdateIssue,
|
||||
UpdateIssueCarnet, UpdateLiveState, UpdateMemory, UpdateProjectContext,
|
||||
UpdateProjectMcpToolPermissions, UpdateProjectPermissions, UpdateSkill, UpdateTemplate,
|
||||
WakeSessionProvider, WriteMemory, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
GetProjectPermissions, GetProjectSystemPermissions, GetProjectWorkState, GitBranches,
|
||||
GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
|
||||
HarvestMemoryFromTurn, HealthUseCase, InspectConversation, InstallPluginFromArchive,
|
||||
InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput,
|
||||
LinkIssues, ListAgents, ListAgentsInput, ListDevices, ListEmbedderProfiles, ListIssues,
|
||||
ListLayouts, ListMemories, ListModelServers, ListOpenCodeProviders,
|
||||
ListPluginRuntimeContributions, ListPlugins, ListProfiles, ListProjects, ListResumableAgents,
|
||||
ListSkills, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions, LiveStateLeanProvider,
|
||||
LiveStateProvider, LiveStateReadProvider, LoadLayout, McpRuntime, McpToolPermissionCatalogue,
|
||||
MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject, OpenTerminal,
|
||||
OpenTicketAssistant, OrchestratorService, PairAttemptLimiter, PairDevice,
|
||||
PermissionProjectorRegistry, ProposeContext, ReadAgentContext, ReadContext,
|
||||
ReadConversationPage, ReadIssue, ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory,
|
||||
ReadMemoryIndex, ReadProjectContext, ReadSkill, ReadTemplate, RecallMemory, ReconcileLayouts,
|
||||
ReconcileLiveState, ReconcileLiveStateInput, ReconcilePluginMcpServers, RecordTurn,
|
||||
RecordTurnProvider, ReferenceProfiles, RenameDevice, RenameLayout, RenameSprint,
|
||||
ReorderSprints, ResizeTerminal, ResolveAgentPermissions, ResolveAgentSystemPermissions,
|
||||
ResolveMemoryLinks, RestoreOpenWindows, RetryBackgroundTask, ReviewPluginPackage,
|
||||
RevokeAllDevices, RevokeDevice, RotateConversationLog, SaveEmbedderProfile, SaveModelServer,
|
||||
SaveOpenCodeProviderProfile, SaveProfile, SessionLimitService, SetActiveLayout,
|
||||
SetPluginEnabled, SnapshotOpenWindows, SnapshotRunningAgents, SpawnBackgroundCommand,
|
||||
StopLiveAgent, StructuredRoutingMode, StructuredSessions, SuggestedThisSession,
|
||||
SyncAgentWithTemplate, TerminalSessions, TouchDevice, UnassignSkillFromAgent,
|
||||
UnassignTicketFromSprint, UninstallPlugin, UnlinkIssues, UpdateAgentContext,
|
||||
UpdateAgentMcpToolPermissions, UpdateAgentPermissions, UpdateAgentSystemPermissions,
|
||||
UpdateIssue, UpdateIssueCarnet, UpdateLiveState, UpdateMemory, UpdateProjectContext,
|
||||
UpdateProjectMcpToolPermissions, UpdateProjectPermissions, UpdateProjectSystemPermissions,
|
||||
UpdateSkill, UpdateTemplate, WakeSessionProvider, WriteMemory, WriteToTerminal,
|
||||
AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use domain::ports::{
|
||||
@ -58,9 +60,10 @@ use domain::ports::{
|
||||
EmbedderProfileStore, EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator,
|
||||
IssueNumberAllocator, IssueStore, McpToolPermissionStore, MemoryRecall, MemoryStore,
|
||||
PermissionStore, PluginManifestValidator, PluginMcpSupervisor, PluginPackageStore,
|
||||
PluginRegistryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, ScheduledTask,
|
||||
Scheduler, SecretStore, SkillStore, SprintStore, StructuredSessionEnvironmentPreparer,
|
||||
TemplateStore, ToolInvoker, WakeError, WakeReason, WindowStateStore,
|
||||
PluginRegistryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort,
|
||||
RuntimePermissionProbe, ScheduledTask, Scheduler, SecretStore, SkillStore, SprintStore,
|
||||
StructuredSessionEnvironmentPreparer, SystemPermissionStore, TemplateStore, ToolInvoker,
|
||||
WakeError, WakeReason, WindowStateStore,
|
||||
};
|
||||
use domain::profile::{
|
||||
AgentProfile, ContextInjection, McpConfigStrategy, McpTransport, StructuredAdapter,
|
||||
@ -83,16 +86,16 @@ use infrastructure::{
|
||||
FsIssueNumberAllocator, FsIssueStore, FsLiveStateStore, FsMcpToolPermissionStore,
|
||||
FsMemoryStore, FsModelServerRegistry, FsOrchestratorWatcher, FsPermissionStore,
|
||||
FsPluginPackageStore, FsPluginRegistryStore, FsProfileStore, FsProjectStore,
|
||||
FsProviderSessionStore, FsSecretStore, FsSkillStore, FsSprintStore, FsTemplateStore,
|
||||
FsWindowStateStore, Git2Repository, HeuristicHandoffSummarizer, HfModelArtifactDownloader,
|
||||
HttpOpenAiCompatibleProbe, IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox,
|
||||
InMemoryPairAttemptLimiter, LlamaCppRuntime, LocalFileSystem, LocalManagedProcess,
|
||||
LocalProcessSpawner, McpServer, MediatedInbox, NaiveMemoryRecall, OrchestratorWatchHandle,
|
||||
PortablePtyAdapter, RwFileGuard, StructuredSessionFactory, SystemClock, SystemMillisClock,
|
||||
TemplateToolProvider, TicketAssistantEnvironmentPreparer, TicketToolProvider,
|
||||
TokioBroadcastEventBus, TokioScheduler, ToolPolicyRegistry, UuidGenerator, VectorMemoryRecall,
|
||||
DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR, RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED,
|
||||
VECTOR_ONNX_ENABLED,
|
||||
FsProviderSessionStore, FsSecretStore, FsSkillStore, FsSprintStore, FsSystemPermissionStore,
|
||||
FsTemplateStore, FsWindowStateStore, Git2Repository, HeuristicHandoffSummarizer,
|
||||
HfModelArtifactDownloader, HttpOpenAiCompatibleProbe, IdeaiContextStore,
|
||||
InMemoryConversationRegistry, InMemoryMailbox, InMemoryPairAttemptLimiter, LlamaCppRuntime,
|
||||
LocalFileSystem, LocalManagedProcess, LocalProcessSpawner, McpServer, MediatedInbox,
|
||||
NaiveMemoryRecall, OrchestratorWatchHandle, PortablePtyAdapter, ReadOnlyRuntimePermissionProbe,
|
||||
RwFileGuard, StructuredSessionFactory, SystemClock, SystemMillisClock, TemplateToolProvider,
|
||||
TicketAssistantEnvironmentPreparer, TicketToolProvider, TokioBroadcastEventBus, TokioScheduler,
|
||||
ToolPolicyRegistry, UuidGenerator, VectorMemoryRecall, DEFAULT_OLLAMA_BASE_URL,
|
||||
ONNX_CACHE_SUBDIR, RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
|
||||
};
|
||||
|
||||
pub mod dto;
|
||||
@ -1063,6 +1066,14 @@ pub struct BackendCore {
|
||||
pub update_agent_permissions: Arc<UpdateAgentPermissions>,
|
||||
/// Resolve effective permissions for one agent.
|
||||
pub resolve_agent_permissions: Arc<ResolveAgentPermissions>,
|
||||
/// Read the project system permission document.
|
||||
pub get_project_system_permissions: Arc<GetProjectSystemPermissions>,
|
||||
/// Update project-level default system permissions.
|
||||
pub update_project_system_permissions: Arc<UpdateProjectSystemPermissions>,
|
||||
/// Update one agent system permission override.
|
||||
pub update_agent_system_permissions: Arc<UpdateAgentSystemPermissions>,
|
||||
/// Resolve effective system permissions for one agent.
|
||||
pub resolve_agent_system_permissions: Arc<ResolveAgentSystemPermissions>,
|
||||
// --- Windows (L10) ---
|
||||
/// Detach a tab into a new OS window (persists the workspace topology).
|
||||
pub move_tab: Arc<MoveTabToNewWindow>,
|
||||
@ -1640,6 +1651,24 @@ impl BackendCore {
|
||||
// --- Project permissions (LP1) ---
|
||||
let permission_store = Arc::new(FsPermissionStore::new(Arc::clone(&fs_port)));
|
||||
let permission_store_port = Arc::clone(&permission_store) as Arc<dyn PermissionStore>;
|
||||
let system_permission_store = Arc::new(FsSystemPermissionStore::new(Arc::clone(&fs_port)));
|
||||
let system_permission_store_port =
|
||||
Arc::clone(&system_permission_store) as Arc<dyn SystemPermissionStore>;
|
||||
let runtime_permission_probe =
|
||||
Arc::new(ReadOnlyRuntimePermissionProbe) as Arc<dyn RuntimePermissionProbe>;
|
||||
let get_project_system_permissions = Arc::new(GetProjectSystemPermissions::new(
|
||||
Arc::clone(&system_permission_store_port),
|
||||
));
|
||||
let update_project_system_permissions = Arc::new(UpdateProjectSystemPermissions::new(
|
||||
Arc::clone(&system_permission_store_port),
|
||||
));
|
||||
let update_agent_system_permissions = Arc::new(UpdateAgentSystemPermissions::new(
|
||||
Arc::clone(&system_permission_store_port),
|
||||
));
|
||||
let resolve_agent_system_permissions = Arc::new(ResolveAgentSystemPermissions::new(
|
||||
Arc::clone(&system_permission_store_port),
|
||||
Arc::clone(&runtime_permission_probe),
|
||||
));
|
||||
let mcp_tool_permission_store =
|
||||
Arc::new(FsMcpToolPermissionStore::new(Arc::clone(&fs_port)));
|
||||
let mcp_tool_permission_store_port =
|
||||
@ -2686,6 +2715,10 @@ impl BackendCore {
|
||||
update_project_permissions,
|
||||
update_agent_permissions,
|
||||
resolve_agent_permissions,
|
||||
get_project_system_permissions,
|
||||
update_project_system_permissions,
|
||||
update_agent_system_permissions,
|
||||
resolve_agent_system_permissions,
|
||||
create_template,
|
||||
read_template,
|
||||
update_template,
|
||||
|
||||
@ -64,6 +64,7 @@ pub mod sandbox;
|
||||
pub mod session_limit;
|
||||
pub mod skill;
|
||||
pub mod sprint;
|
||||
pub mod system_permissions;
|
||||
pub mod template;
|
||||
pub mod terminal;
|
||||
|
||||
@ -195,6 +196,13 @@ pub use permission::{
|
||||
PERMISSIONS_VERSION,
|
||||
};
|
||||
|
||||
pub use system_permissions::{
|
||||
resolve_agent_system_permissions, AgentSystemPermissionOverride, NetworkPolicy,
|
||||
ProjectSystemPermissions, ResolvedAgentSystemPermissions, RuntimeLock, RuntimeLockState,
|
||||
RuntimePermissionSnapshot, SystemPermissionControl, SystemPermissionControlMode,
|
||||
SystemPermissionSet, SYSTEM_PERMISSIONS_VERSION,
|
||||
};
|
||||
|
||||
pub use plugin::{
|
||||
ContentHash, CustomPluginLayout, PluginBundleUrl, PluginCapability, PluginCommandId,
|
||||
PluginContributionSet, PluginDescriptor, PluginError, PluginId, PluginInstallSource,
|
||||
@ -228,7 +236,8 @@ pub use ports::{
|
||||
PluginManifestError, PluginManifestValidator, PluginMcpError, PluginMcpSupervisor,
|
||||
PluginPackageStore, PluginRegistryError, PluginRegistryStore, PluginStoreError,
|
||||
PreparedContext, ProcessError, ProcessSpawner, ProfileStore, ProjectStore, PtyError, PtyHandle,
|
||||
PtyPort, RemoteError, RemoteHost, RemotePath, RuntimeError, ScheduledTask, Scheduler,
|
||||
SpawnSpec, SprintStore, SprintStoreError, StoreError, StructuredSessionEnvironment,
|
||||
StructuredSessionEnvironmentPreparer, TemplateStore, WindowStateStore,
|
||||
PtyPort, RemoteError, RemoteHost, RemotePath, RuntimeError, RuntimePermissionProbe,
|
||||
ScheduledTask, Scheduler, SpawnSpec, SprintStore, SprintStoreError, StoreError,
|
||||
StructuredSessionEnvironment, StructuredSessionEnvironmentPreparer, SystemPermissionStore,
|
||||
TemplateStore, WindowStateStore,
|
||||
};
|
||||
|
||||
@ -59,6 +59,7 @@ use crate::project::{Project, ProjectPath};
|
||||
use crate::remote::RemoteKind;
|
||||
use crate::skill::{Skill, SkillScope};
|
||||
use crate::sprint::{Sprint, SprintIndexEntry, SprintVersion};
|
||||
use crate::system_permissions::{ProjectSystemPermissions, RuntimePermissionSnapshot};
|
||||
use crate::template::AgentTemplate;
|
||||
use crate::terminal::PtySize;
|
||||
|
||||
@ -2026,6 +2027,44 @@ pub trait PermissionStore: Send + Sync {
|
||||
) -> Result<(), StoreError>;
|
||||
}
|
||||
|
||||
/// Reads/writes a project's `.ideai/system-permissions.json`.
|
||||
#[async_trait]
|
||||
pub trait SystemPermissionStore: Send + Sync {
|
||||
/// Loads the project's system permission document. Missing file returns the
|
||||
/// default empty document.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`StoreError`] on I/O or deserialisation failure.
|
||||
async fn load_system_permissions(
|
||||
&self,
|
||||
project: &Project,
|
||||
) -> Result<ProjectSystemPermissions, StoreError>;
|
||||
|
||||
/// Saves the project's system permission document.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`StoreError`] on I/O or serialisation failure.
|
||||
async fn save_system_permissions(
|
||||
&self,
|
||||
project: &Project,
|
||||
permissions: &ProjectSystemPermissions,
|
||||
) -> Result<(), StoreError>;
|
||||
}
|
||||
|
||||
/// Read-only probe for host/provider system permission constraints.
|
||||
#[async_trait]
|
||||
pub trait RuntimePermissionProbe: Send + Sync {
|
||||
/// Returns the effective runtime permission state visible to IdeA.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`RuntimeError`] when probing itself fails.
|
||||
async fn probe_runtime_permissions(
|
||||
&self,
|
||||
project: &Project,
|
||||
agent_id: AgentId,
|
||||
) -> Result<RuntimePermissionSnapshot, RuntimeError>;
|
||||
}
|
||||
|
||||
/// Reads/writes a project's `.ideai/mcp-tool-permissions.json`.
|
||||
///
|
||||
/// This is intentionally distinct from [`PermissionStore`]: it governs IdeA MCP
|
||||
|
||||
324
crates/domain/src/system_permissions.rs
Normal file
324
crates/domain/src/system_permissions.rs
Normal file
@ -0,0 +1,324 @@
|
||||
//! System-level agent permissions, distinct from filesystem/bash permissions.
|
||||
//!
|
||||
//! This model stores the policy the user wants IdeA to apply. It does not claim
|
||||
//! that an external assistant runtime can be elevated live: resolution combines
|
||||
//! the wanted policy with a read-only runtime probe.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ids::AgentId;
|
||||
|
||||
/// Current schema version for `.ideai/system-permissions.json`.
|
||||
pub const SYSTEM_PERMISSIONS_VERSION: u32 = 1;
|
||||
|
||||
/// Wanted/effective network policy.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum NetworkPolicy {
|
||||
/// Network access is wanted/observed as allowed.
|
||||
Allow,
|
||||
/// Network access is wanted/observed as denied.
|
||||
Deny,
|
||||
/// Ask before allowing network access when the runtime supports it.
|
||||
Ask,
|
||||
}
|
||||
|
||||
/// Optional system permissions.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SystemPermissionSet {
|
||||
/// Network policy. `None` means no IdeA-level policy has been set.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub network: Option<NetworkPolicy>,
|
||||
}
|
||||
|
||||
impl SystemPermissionSet {
|
||||
/// Builds a set from an optional network policy.
|
||||
#[must_use]
|
||||
pub const fn new(network: Option<NetworkPolicy>) -> Self {
|
||||
Self { network }
|
||||
}
|
||||
|
||||
/// Whether the set carries no policy.
|
||||
#[must_use]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.network.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-agent system permission override.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentSystemPermissionOverride {
|
||||
/// Agent id.
|
||||
pub agent_id: AgentId,
|
||||
/// Agent-specific permissions.
|
||||
pub permissions: SystemPermissionSet,
|
||||
}
|
||||
|
||||
impl AgentSystemPermissionOverride {
|
||||
/// Builds an override.
|
||||
#[must_use]
|
||||
pub const fn new(agent_id: AgentId, permissions: SystemPermissionSet) -> Self {
|
||||
Self {
|
||||
agent_id,
|
||||
permissions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Persisted project system permission document.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProjectSystemPermissions {
|
||||
/// Document format version.
|
||||
pub version: u32,
|
||||
/// Optional project-wide default permissions.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub project_default: Option<SystemPermissionSet>,
|
||||
/// Per-agent overrides.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub agents: Vec<AgentSystemPermissionOverride>,
|
||||
}
|
||||
|
||||
impl Default for ProjectSystemPermissions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: SYSTEM_PERMISSIONS_VERSION,
|
||||
project_default: None,
|
||||
agents: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProjectSystemPermissions {
|
||||
/// Builds a document.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
project_default: Option<SystemPermissionSet>,
|
||||
agents: Vec<AgentSystemPermissionOverride>,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: SYSTEM_PERMISSIONS_VERSION,
|
||||
project_default,
|
||||
agents,
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the project default permissions.
|
||||
pub fn set_project_default(&mut self, permissions: Option<SystemPermissionSet>) {
|
||||
self.project_default = permissions.filter(|set| !set.is_empty());
|
||||
}
|
||||
|
||||
/// Returns the override for one agent, if present.
|
||||
#[must_use]
|
||||
pub fn agent_permissions(&self, agent_id: AgentId) -> Option<&SystemPermissionSet> {
|
||||
self.agents
|
||||
.iter()
|
||||
.find(|entry| entry.agent_id == agent_id)
|
||||
.map(|entry| &entry.permissions)
|
||||
}
|
||||
|
||||
/// Replaces or removes an agent override.
|
||||
pub fn set_agent_permissions(
|
||||
&mut self,
|
||||
agent_id: AgentId,
|
||||
permissions: Option<SystemPermissionSet>,
|
||||
) {
|
||||
self.agents.retain(|entry| entry.agent_id != agent_id);
|
||||
if let Some(permissions) = permissions.filter(|set| !set.is_empty()) {
|
||||
self.agents
|
||||
.push(AgentSystemPermissionOverride::new(agent_id, permissions));
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the wanted network policy before runtime constraints.
|
||||
#[must_use]
|
||||
pub fn wanted_network_for(&self, agent_id: AgentId) -> Option<NetworkPolicy> {
|
||||
self.agent_permissions(agent_id)
|
||||
.and_then(|set| set.network)
|
||||
.or_else(|| self.project_default.as_ref().and_then(|set| set.network))
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime lock state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RuntimeLockState {
|
||||
/// No runtime lock is known.
|
||||
None,
|
||||
/// Runtime locks the effective network policy.
|
||||
Locked,
|
||||
}
|
||||
|
||||
/// Runtime lock details.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeLock {
|
||||
/// Lock state.
|
||||
pub state: RuntimeLockState,
|
||||
/// Runtime/source that imposes the lock, when known.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
/// Human-readable reason.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl RuntimeLock {
|
||||
/// No known runtime lock.
|
||||
#[must_use]
|
||||
pub const fn none() -> Self {
|
||||
Self {
|
||||
state: RuntimeLockState::None,
|
||||
source: None,
|
||||
reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime-locked state.
|
||||
#[must_use]
|
||||
pub fn locked(source: impl Into<String>, reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
state: RuntimeLockState::Locked,
|
||||
source: Some(source.into()),
|
||||
reason: Some(reason.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// UI control mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SystemPermissionControlMode {
|
||||
/// IdeA can edit the wanted policy.
|
||||
Editable,
|
||||
/// IdeA can only display the state.
|
||||
ReadOnly,
|
||||
}
|
||||
|
||||
/// UI control state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SystemPermissionControl {
|
||||
/// Control mode.
|
||||
pub mode: SystemPermissionControlMode,
|
||||
/// Human-readable reason for read-only state.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl SystemPermissionControl {
|
||||
/// Editable control.
|
||||
#[must_use]
|
||||
pub const fn editable() -> Self {
|
||||
Self {
|
||||
mode: SystemPermissionControlMode::Editable,
|
||||
reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only control.
|
||||
#[must_use]
|
||||
pub fn read_only(reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
mode: SystemPermissionControlMode::ReadOnly,
|
||||
reason: Some(reason.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only snapshot from the host/provider runtime.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RuntimePermissionSnapshot {
|
||||
/// Effective network policy observed or conservatively inferred.
|
||||
pub effective_network: NetworkPolicy,
|
||||
/// Runtime lock details.
|
||||
pub runtime_lock: RuntimeLock,
|
||||
/// Control state exposed to the UI.
|
||||
pub control: SystemPermissionControl,
|
||||
}
|
||||
|
||||
impl RuntimePermissionSnapshot {
|
||||
/// Snapshot for a runtime that IdeA cannot inspect or elevate.
|
||||
#[must_use]
|
||||
pub fn locked_uninspectable() -> Self {
|
||||
const REASON: &str =
|
||||
"IdeA cannot inspect or change network access for the active external runtime.";
|
||||
Self {
|
||||
effective_network: NetworkPolicy::Deny,
|
||||
runtime_lock: RuntimeLock::locked("external-runtime", REASON),
|
||||
control: SystemPermissionControl::read_only(REASON),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolved read-model for one agent.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolvedAgentSystemPermissions {
|
||||
/// Wanted network policy from project/agent configuration.
|
||||
pub wanted: Option<NetworkPolicy>,
|
||||
/// Effective network policy after runtime constraints.
|
||||
pub effective: NetworkPolicy,
|
||||
/// Runtime lock state.
|
||||
pub runtime_lock: RuntimeLock,
|
||||
/// Whether the UI can edit the policy live.
|
||||
pub control: SystemPermissionControl,
|
||||
}
|
||||
|
||||
/// Resolves wanted system permissions against the runtime snapshot.
|
||||
#[must_use]
|
||||
pub fn resolve_agent_system_permissions(
|
||||
doc: &ProjectSystemPermissions,
|
||||
agent_id: AgentId,
|
||||
runtime: RuntimePermissionSnapshot,
|
||||
) -> ResolvedAgentSystemPermissions {
|
||||
let wanted = doc.wanted_network_for(agent_id);
|
||||
let effective = match runtime.runtime_lock.state {
|
||||
RuntimeLockState::Locked => runtime.effective_network,
|
||||
RuntimeLockState::None => wanted.unwrap_or(runtime.effective_network),
|
||||
};
|
||||
ResolvedAgentSystemPermissions {
|
||||
wanted,
|
||||
effective,
|
||||
runtime_lock: runtime.runtime_lock,
|
||||
control: runtime.control,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn agent_override_wins_over_project_default_for_wanted_policy() {
|
||||
let agent = AgentId::new_random();
|
||||
let doc = ProjectSystemPermissions::new(
|
||||
Some(SystemPermissionSet::new(Some(NetworkPolicy::Ask))),
|
||||
vec![AgentSystemPermissionOverride::new(
|
||||
agent,
|
||||
SystemPermissionSet::new(Some(NetworkPolicy::Deny)),
|
||||
)],
|
||||
);
|
||||
|
||||
assert_eq!(doc.wanted_network_for(agent), Some(NetworkPolicy::Deny));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_runtime_controls_effective_policy_without_fake_allow() {
|
||||
let agent = AgentId::new_random();
|
||||
let doc = ProjectSystemPermissions::default();
|
||||
|
||||
let resolved = resolve_agent_system_permissions(
|
||||
&doc,
|
||||
agent,
|
||||
RuntimePermissionSnapshot::locked_uninspectable(),
|
||||
);
|
||||
|
||||
assert_eq!(resolved.wanted, None);
|
||||
assert_eq!(resolved.effective, NetworkPolicy::Deny);
|
||||
assert_eq!(resolved.runtime_lock.state, RuntimeLockState::Locked);
|
||||
assert_eq!(resolved.control.mode, SystemPermissionControlMode::ReadOnly);
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@ pub mod pty;
|
||||
pub mod ratelimit;
|
||||
pub mod remote;
|
||||
pub mod runtime;
|
||||
pub mod runtime_permission;
|
||||
pub mod sandbox;
|
||||
pub mod scheduler;
|
||||
pub mod session;
|
||||
@ -87,6 +88,7 @@ pub use pty::PortablePtyAdapter;
|
||||
pub use ratelimit::RateLimitParser;
|
||||
pub use remote::{remote_host, LocalHost};
|
||||
pub use runtime::CliAgentRuntime;
|
||||
pub use runtime_permission::ReadOnlyRuntimePermissionProbe;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use sandbox::LandlockSandbox;
|
||||
pub use sandbox::{default_enforcer, NoopSandbox};
|
||||
@ -102,8 +104,8 @@ pub use store::{
|
||||
AdaptiveMemoryRecall, BackgroundTaskReconcileReport, EmbedderEnvProbe, FsBackgroundTaskStore,
|
||||
FsDeviceSessionStore, FsEmbedderProfileStore, FsEmbedderPromptStore, FsLiveStateStore,
|
||||
FsMcpToolPermissionStore, FsMemoryStore, FsPermissionStore, FsProfileStore, FsProjectStore,
|
||||
FsSecretStore, FsSkillStore, FsTemplateStore, FsWindowStateStore, HashEmbedder,
|
||||
IdeaiContextStore, NaiveMemoryRecall, OnnxModelInfo, StubEmbedder, VectorMemoryRecall,
|
||||
DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR, RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED,
|
||||
VECTOR_ONNX_ENABLED,
|
||||
FsSecretStore, FsSkillStore, FsSystemPermissionStore, FsTemplateStore, FsWindowStateStore,
|
||||
HashEmbedder, IdeaiContextStore, NaiveMemoryRecall, OnnxModelInfo, StubEmbedder,
|
||||
VectorMemoryRecall, DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR, RECOMMENDED_ONNX_MODELS,
|
||||
VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
|
||||
};
|
||||
|
||||
24
crates/infrastructure/src/runtime_permission.rs
Normal file
24
crates/infrastructure/src/runtime_permission.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! Read-only runtime permission probe.
|
||||
//!
|
||||
//! V1 deliberately does not claim live control over provider/network sandboxing.
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use domain::ports::{RuntimeError, RuntimePermissionProbe};
|
||||
use domain::{AgentId, Project, RuntimePermissionSnapshot};
|
||||
|
||||
/// Conservative probe used when IdeA cannot inspect or pilot runtime network
|
||||
/// permissions.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ReadOnlyRuntimePermissionProbe;
|
||||
|
||||
#[async_trait]
|
||||
impl RuntimePermissionProbe for ReadOnlyRuntimePermissionProbe {
|
||||
async fn probe_runtime_permissions(
|
||||
&self,
|
||||
_project: &Project,
|
||||
_agent_id: AgentId,
|
||||
) -> Result<RuntimePermissionSnapshot, RuntimeError> {
|
||||
Ok(RuntimePermissionSnapshot::locked_uninspectable())
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ mod profile;
|
||||
mod project;
|
||||
mod secrets;
|
||||
mod skill;
|
||||
mod system_permission;
|
||||
mod template;
|
||||
mod vector;
|
||||
mod window_state;
|
||||
@ -40,6 +41,7 @@ pub use profile::{FsEmbedderProfileStore, FsProfileStore};
|
||||
pub use project::FsProjectStore;
|
||||
pub use secrets::FsSecretStore;
|
||||
pub use skill::FsSkillStore;
|
||||
pub use system_permission::FsSystemPermissionStore;
|
||||
pub use template::FsTemplateStore;
|
||||
pub use vector::{should_use_vector, AdaptiveMemoryRecall, VectorMemoryRecall};
|
||||
pub use window_state::FsWindowStateStore;
|
||||
|
||||
68
crates/infrastructure/src/store/system_permission.rs
Normal file
68
crates/infrastructure/src/store/system_permission.rs
Normal file
@ -0,0 +1,68 @@
|
||||
//! Filesystem-backed [`SystemPermissionStore`] for project system permissions.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use domain::ports::{FileSystem, FsError, RemotePath, StoreError, SystemPermissionStore};
|
||||
use domain::{Project, ProjectSystemPermissions};
|
||||
|
||||
const SYSTEM_PERMISSIONS_FILE: &str = "system-permissions.json";
|
||||
|
||||
/// JSON-file implementation for `<project>/.ideai/system-permissions.json`.
|
||||
#[derive(Clone)]
|
||||
pub struct FsSystemPermissionStore {
|
||||
fs: Arc<dyn FileSystem>,
|
||||
}
|
||||
|
||||
impl FsSystemPermissionStore {
|
||||
/// Builds the store from an injected filesystem port.
|
||||
#[must_use]
|
||||
pub fn new(fs: Arc<dyn FileSystem>) -> Self {
|
||||
Self { fs }
|
||||
}
|
||||
|
||||
fn path(project: &Project) -> RemotePath {
|
||||
let root = project.root.as_str().trim_end_matches(['/', '\\']);
|
||||
RemotePath::new(format!("{root}/.ideai/{SYSTEM_PERMISSIONS_FILE}"))
|
||||
}
|
||||
|
||||
async fn ensure_ideai(&self, project: &Project) -> Result<(), StoreError> {
|
||||
let root = project.root.as_str().trim_end_matches(['/', '\\']);
|
||||
self.fs
|
||||
.create_dir_all(&RemotePath::new(format!("{root}/.ideai")))
|
||||
.await
|
||||
.map_err(|e| StoreError::Io(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SystemPermissionStore for FsSystemPermissionStore {
|
||||
async fn load_system_permissions(
|
||||
&self,
|
||||
project: &Project,
|
||||
) -> Result<ProjectSystemPermissions, StoreError> {
|
||||
match self.fs.read(&Self::path(project)).await {
|
||||
Ok(bytes) => {
|
||||
serde_json::from_slice(&bytes).map_err(|e| StoreError::Serialization(e.to_string()))
|
||||
}
|
||||
Err(FsError::NotFound(_)) => Ok(ProjectSystemPermissions::default()),
|
||||
Err(e) => Err(StoreError::Io(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_system_permissions(
|
||||
&self,
|
||||
project: &Project,
|
||||
permissions: &ProjectSystemPermissions,
|
||||
) -> Result<(), StoreError> {
|
||||
self.ensure_ideai(project).await?;
|
||||
let mut bytes = serde_json::to_vec_pretty(permissions)
|
||||
.map_err(|e| StoreError::Serialization(e.to_string()))?;
|
||||
bytes.push(b'\n');
|
||||
self.fs
|
||||
.write(&Self::path(project), &bytes)
|
||||
.await
|
||||
.map_err(|e| StoreError::Io(e.to_string()))
|
||||
}
|
||||
}
|
||||
@ -39,21 +39,22 @@ use application::{
|
||||
CreateAgentInput, CreateMemoryInput, CreateSkillInput, CreateSprintInput, DeleteAgentInput,
|
||||
DeleteEmbedderProfileInput, DeleteIssueInput, DeleteMemoryInput, DeleteSkillInput,
|
||||
DeleteSprintInput, DeleteTemplateInput, DetectAgentDriftInput, GetMemoryInput,
|
||||
GetProjectWorkStateInput, GitBranchesInput, GitCheckoutInput, GitCommitInput, GitGraphInput,
|
||||
GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput, InspectConversationInput,
|
||||
LaunchAgentInput, LinkIssuesInput, ListAgentsInput, ListDevicesInput, ListIssuesInput,
|
||||
ListMemoriesInput, ListResumableAgentsInput, ListSkillsInput, ListSprintsInput, LiveSessions,
|
||||
McpRuntime, OpenProjectInput, PairAttemptDecision, PairDeviceInput, RateLimitKey,
|
||||
ReadAgentContextInput, ReadConversationPageInput, ReadIssueCarnetInput, ReadIssueInput,
|
||||
ReadMcpToolPermissionsInput, ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput,
|
||||
RenameDeviceInput, RenameSprintInput, ReorderSprintsInput, ResizeTerminalInput,
|
||||
ResolveAgentPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
GetProjectSystemPermissionsInput, GetProjectWorkStateInput, GitBranchesInput, GitCheckoutInput,
|
||||
GitCommitInput, GitGraphInput, GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput,
|
||||
InspectConversationInput, LaunchAgentInput, LinkIssuesInput, ListAgentsInput, ListDevicesInput,
|
||||
ListIssuesInput, ListMemoriesInput, ListResumableAgentsInput, ListSkillsInput,
|
||||
ListSprintsInput, LiveSessions, McpRuntime, OpenProjectInput, PairAttemptDecision,
|
||||
PairDeviceInput, RateLimitKey, ReadAgentContextInput, ReadConversationPageInput,
|
||||
ReadIssueCarnetInput, ReadIssueInput, ReadMcpToolPermissionsInput, ReadMemoryIndexInput,
|
||||
ReadProjectContextInput, RecallMemoryInput, RenameDeviceInput, RenameSprintInput,
|
||||
ReorderSprintsInput, ResizeTerminalInput, ResolveAgentPermissionsInput,
|
||||
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
RotateConversationLogInput, StopLiveAgentInput, SyncAgentWithTemplateInput, TouchDeviceInput,
|
||||
UnassignSkillFromAgentInput, UnassignTicketFromSprintInput, UnlinkIssuesInput,
|
||||
UpdateAgentContextInput, UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UpdateIssueCarnetInput, UpdateMemoryInput, UpdateProjectContextInput,
|
||||
UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput, UpdateSkillInput,
|
||||
WriteToTerminalInput,
|
||||
UpdateAgentSystemPermissionsInput, UpdateIssueCarnetInput, UpdateMemoryInput,
|
||||
UpdateProjectContextInput, UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput,
|
||||
UpdateProjectSystemPermissionsInput, UpdateSkillInput, WriteToTerminalInput,
|
||||
};
|
||||
use domain::ports::PtyHandle;
|
||||
use domain::IssueActor;
|
||||
@ -78,22 +79,23 @@ use backend::dto::{
|
||||
GraphCommitListDto, HealthRequestDto, HealthResponseDto, InspectConversationRequestDto,
|
||||
LaunchAgentRequestDto, LiveAgentListDto, MemoryDto, MemoryIndexDto, MemoryLinksDto,
|
||||
MemoryListDto, OpenTerminalRequestDto, ProfileDto, ProfileListDto, ProjectDto, ProjectListDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectWorkStateDto,
|
||||
ReadAgentContextResponseDto, ReadConversationPageRequestDto, RecallMemoryRequestDto,
|
||||
ResolveAgentPermissionsRequestDto, ResumableAgentListDto, SaveEmbedderProfileRequestDto,
|
||||
SaveProfileRequestDto, SkillDto, SkillListDto, SprintCreateRequestDto, SprintDeleteRequestDto,
|
||||
SprintDto, SprintListDto, SprintListRequestDto, SprintRenameRequestDto,
|
||||
SprintReorderRequestDto, StopLiveAgentRequestDto, StopLiveAgentResponseDto,
|
||||
SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalSessionDto, TicketAssignRequestDto, TicketCarnetDto, TicketCreateRequestDto,
|
||||
TicketDeleteRequestDto, TicketDto, TicketLinkCommandRequestDto, TicketListPageInput,
|
||||
TicketListRequestDto, TicketReadRequestDto, TicketSprintAssignRequestDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectSystemPermissionsDto,
|
||||
ProjectWorkStateDto, ReadAgentContextResponseDto, ReadConversationPageRequestDto,
|
||||
RecallMemoryRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
ResumableAgentListDto, SaveEmbedderProfileRequestDto, SaveProfileRequestDto, SkillDto,
|
||||
SkillListDto, SprintCreateRequestDto, SprintDeleteRequestDto, SprintDto, SprintListDto,
|
||||
SprintListRequestDto, SprintRenameRequestDto, SprintReorderRequestDto, StopLiveAgentRequestDto,
|
||||
StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
|
||||
TemplateListDto, TerminalSessionDto, TicketAssignRequestDto, TicketCarnetDto,
|
||||
TicketCreateRequestDto, TicketDeleteRequestDto, TicketDto, TicketLinkCommandRequestDto,
|
||||
TicketListPageInput, TicketListRequestDto, TicketReadRequestDto, TicketSprintAssignRequestDto,
|
||||
TicketSprintUnassignRequestDto, TicketUnlinkCommandRequestDto, TicketUpdateCarnetRequestDto,
|
||||
TicketUpdateRequestDto, TurnPageDto, UnassignSkillRequestDto, UpdateAgentContextRequestDto,
|
||||
UpdateAgentMcpToolPermissionsRequestDto, UpdateAgentPermissionsRequestDto,
|
||||
UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateAgentSystemPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateProjectMcpToolPermissionsRequestDto, UpdateProjectPermissionsRequestDto,
|
||||
UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
UpdateProjectSystemPermissionsRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
};
|
||||
use backend::events::DomainEventDto;
|
||||
type PtyChunk = Vec<u8>;
|
||||
@ -2396,6 +2398,18 @@ async fn invoke(
|
||||
"resolve_agent_permissions" => {
|
||||
invoke_resolve_agent_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"get_project_system_permissions" => {
|
||||
invoke_get_project_system_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"update_project_system_permissions" => {
|
||||
invoke_update_project_system_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"update_agent_system_permissions" => {
|
||||
invoke_update_agent_system_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"resolve_agent_system_permissions" => {
|
||||
invoke_resolve_agent_system_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"get_mcp_tool_permissions" => {
|
||||
invoke_get_mcp_tool_permissions(&request.args, &state.app).await
|
||||
}
|
||||
@ -3472,6 +3486,88 @@ async fn invoke_resolve_agent_permissions(
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_get_project_system_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
) -> Result<Value, ErrorDto> {
|
||||
let project = resolve_project_readonly(
|
||||
string_arg(args, "projectId", "get_project_system_permissions")?,
|
||||
state,
|
||||
)
|
||||
.await?;
|
||||
let output = state
|
||||
.get_project_system_permissions
|
||||
.execute(GetProjectSystemPermissionsInput { project })
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)?;
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_update_project_system_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
) -> Result<Value, ErrorDto> {
|
||||
let request = required_request::<UpdateProjectSystemPermissionsRequestDto>(
|
||||
"update_project_system_permissions",
|
||||
args,
|
||||
)?;
|
||||
let project = resolve_project_readonly(&request.project_id, state).await?;
|
||||
let output = state
|
||||
.update_project_system_permissions
|
||||
.execute(UpdateProjectSystemPermissionsInput {
|
||||
project,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)?;
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_update_agent_system_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
) -> Result<Value, ErrorDto> {
|
||||
let request = required_request::<UpdateAgentSystemPermissionsRequestDto>(
|
||||
"update_agent_system_permissions",
|
||||
args,
|
||||
)?;
|
||||
let project = resolve_project_readonly(&request.project_id, state).await?;
|
||||
let output = state
|
||||
.update_agent_system_permissions
|
||||
.execute(UpdateAgentSystemPermissionsInput {
|
||||
project,
|
||||
agent_id: parse_agent_id(&request.agent_id)?,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)?;
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_resolve_agent_system_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
) -> Result<Value, ErrorDto> {
|
||||
let request = required_request::<ResolveAgentSystemPermissionsRequestDto>(
|
||||
"resolve_agent_system_permissions",
|
||||
args,
|
||||
)?;
|
||||
let project = resolve_project_readonly(&request.project_id, state).await?;
|
||||
let output = state
|
||||
.resolve_agent_system_permissions
|
||||
.execute(ResolveAgentSystemPermissionsInput {
|
||||
project,
|
||||
agent_id: parse_agent_id(&request.agent_id)?,
|
||||
})
|
||||
.await
|
||||
.map(|out| ResolvedAgentSystemPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)?;
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_get_mcp_tool_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
@ -7617,6 +7713,10 @@ mod tests {
|
||||
"update_project_permissions",
|
||||
"update_agent_permissions",
|
||||
"resolve_agent_permissions",
|
||||
"get_project_system_permissions",
|
||||
"update_project_system_permissions",
|
||||
"update_agent_system_permissions",
|
||||
"resolve_agent_system_permissions",
|
||||
"get_mcp_tool_permissions",
|
||||
"update_project_mcp_tool_permissions",
|
||||
"update_agent_mcp_tool_permissions",
|
||||
|
||||
Reference in New Issue
Block a user