feat(permissions): expose network permission state (#103)

This commit is contained in:
2026-07-25 23:05:55 +02:00
parent e8731834f4
commit 3047dc9195
31 changed files with 2080 additions and 94 deletions

View File

@ -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,
};

View 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())
}
}

View File

@ -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;

View 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()))
}
}