Scope-Lockの実装

This commit is contained in:
2026-04-18 19:25:03 +09:00
parent 4ba58723dc
commit a7b9b6fa4b
9 changed files with 1196 additions and 4 deletions
+16
View File
@@ -138,6 +138,22 @@ impl Scope {
self.allow.iter().map(|r| r.target.as_path())
}
/// Allow rules with their targets resolved to absolute paths.
///
/// Used by the scope-lock registry, where every Pod's allocation
/// must be expressed in absolute terms so prefix comparisons are
/// meaningful across processes.
pub fn allow_rules(&self) -> Vec<ScopeRule> {
self.allow
.iter()
.map(|r| ScopeRule {
target: r.target.clone(),
permission: r.permission,
recursive: r.recursive,
})
.collect()
}
/// Iterate over absolute paths granted `Write` by an allow rule.
/// Subset of [`readable_paths`](Self::readable_paths).
pub fn writable_paths(&self) -> impl Iterator<Item = &Path> {
+2
View File
@@ -22,6 +22,8 @@ tools = { version = "0.1.0", path = "../tools" }
minijinja = "2.19.0"
chrono = "0.4.44"
include_dir = "0.7.4"
fs4 = { version = "0.13.1", features = ["sync"] }
libc = "0.2.185"
[dev-dependencies]
async-trait = "0.1.89"
+1
View File
@@ -2,6 +2,7 @@ pub mod controller;
pub mod hook;
pub mod notifier;
pub mod runtime_dir;
pub mod scope_lock;
pub mod shared_state;
pub mod socket_server;
+29
View File
@@ -23,6 +23,8 @@ use crate::notification_buffer::NotificationBuffer;
use crate::notifier::Notifier;
use crate::pod_interceptor::PodInterceptor;
use crate::prompt_loader::PromptLoader;
use crate::runtime_dir;
use crate::scope_lock::{self, ScopeAllocationGuard, ScopeLockError};
use crate::system_prompt::{SystemPromptContext, SystemPromptError, SystemPromptTemplate};
use crate::usage_tracker::UsageTracker;
use protocol::{NotificationLevel, NotificationSource};
@@ -105,6 +107,13 @@ pub struct Pod<C: LlmClient, St: Store> {
/// injection into the next LLM request. Shared with the
/// PodInterceptor installed in `ensure_interceptor_installed`.
pending_notifications: NotificationBuffer,
/// Scope allocation in the machine-wide lock file. `Some` for
/// Pods built via `from_manifest` (production path); `None` for
/// lower-level constructors (`Pod::new`, `Pod::restore`) that
/// bypass the registry. Kept purely for its `Drop` impl, which
/// releases the allocation when the Pod is dropped.
#[allow(dead_code)]
scope_allocation: Option<ScopeAllocationGuard>,
}
impl<C: LlmClient, St: Store> Pod<C, St> {
@@ -146,6 +155,7 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
system_prompt_template: None,
notifier: None,
pending_notifications: NotificationBuffer::new(),
scope_allocation: None,
};
pod.apply_prune_from_manifest();
Ok(pod)
@@ -195,6 +205,7 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
system_prompt_template: None,
notifier: None,
pending_notifications: NotificationBuffer::new(),
scope_allocation: None,
};
pod.apply_prune_from_manifest();
Ok(pod)
@@ -875,6 +886,20 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
return Err(PodError::PwdOutsideScope { pwd });
}
// Register this Pod in the machine-wide scope-lock registry
// before building anything else, so a spawn that conflicts on
// scope fails fast (and without having paid for client setup).
let socket_path = runtime_dir::default_base()
.map_err(ScopeLockError::from)?
.join(&manifest.pod.name)
.join("sock");
let scope_allocation = scope_lock::install_top_level(
manifest.pod.name.clone(),
std::process::id(),
socket_path,
scope.allow_rules(),
)?;
let client = provider::build_client(&manifest.provider)?;
let mut worker = Worker::new(client);
apply_worker_manifest(&mut worker, &manifest.worker);
@@ -910,6 +935,7 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
system_prompt_template,
notifier: None,
pending_notifications: NotificationBuffer::new(),
scope_allocation: Some(scope_allocation),
};
pod.apply_prune_from_manifest();
Ok(pod)
@@ -1058,6 +1084,9 @@ pub enum PodError {
#[source]
source: SystemPromptError,
},
#[error(transparent)]
ScopeLock(#[from] ScopeLockError),
}
/// Canonicalize an absolute pwd (resolves symlinks and any `.`/`..`
+5 -1
View File
@@ -86,7 +86,11 @@ async fn atomic_write(target: &Path, content: &[u8]) -> Result<(), io::Error> {
}
/// Resolve the default base directory for runtime data.
fn default_base() -> Result<PathBuf, io::Error> {
///
/// Public so the scope-lock registry (which lives outside the
/// `RuntimeDir` instance lifecycle) can predict a Pod's socket path
/// without constructing a `RuntimeDir` first.
pub fn default_base() -> Result<PathBuf, io::Error> {
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
Ok(PathBuf::from(runtime_dir).join("insomnia"))
} else if let Ok(home) = std::env::var("HOME") {
File diff suppressed because it is too large Load Diff