feat(infra): tmp partagé inter-projets géré par IdeA (handles opaques, leases, GC, projection sandbox) — #189 (QA verte)

- Domaine: port ManagedTempStore + modèle (handle opaque, kind Directory/File, état Active/Released, allocation/lease, expiry).
- Application: use cases Allocate/Resolve/Release/GcManagedTemp, horodatés via Clock.
- Infrastructure: impl FS FsManagedTempStore sous app_data_dir/managed-tmp (root global inter-projets, métadonnées par handle, GC des leases expirés/libérés).
- Exposition MCP contrôlée (ManagedTempToolProvider, catalogue) + câblage sandbox: les agents résolvent un handle au lieu d'inventer un path libre.
- Tests: application/infrastructure managed_temp.
This commit is contained in:
2026-08-13 13:34:33 +02:00
parent d7dfbcdfcb
commit fdca55ecd4
22 changed files with 2000 additions and 72 deletions

View File

@ -34,6 +34,7 @@ use super::jsonrpc::{
error_codes, JsonRpcError, JsonRpcRequest, JsonRpcResponse, Transport, TransportError,
JSONRPC_VERSION,
};
use super::managed_temp::ManagedTempToolProvider;
use super::policy::ToolPolicyRegistry;
use super::templates::TemplateToolProvider;
use super::tickets::TicketToolProvider;
@ -80,6 +81,9 @@ pub struct McpServer {
/// Optional public template provider. The MCP surface says `template`; the
/// provider maps those calls to application/domain `Template` use cases.
template_tools: Option<Arc<dyn TemplateToolProvider>>,
/// Optional public managed-temp provider. The MCP surface says `managed_temp`;
/// the provider maps those calls to application managed-temp use cases.
managed_temp_tools: Option<Arc<dyn ManagedTempToolProvider>>,
/// Optional per-requester MCP tool policy registry for constrained sessions.
tool_policies: Option<Arc<ToolPolicyRegistry>>,
/// Optional durable MCP tool permission store. When wired, absence of project or
@ -101,6 +105,7 @@ impl McpServer {
ready_sink: None,
ticket_tools: None,
template_tools: None,
managed_temp_tools: None,
tool_policies: None,
mcp_tool_permissions: None,
}
@ -141,6 +146,16 @@ impl McpServer {
self
}
/// Attaches the public managed-temp tool provider.
#[must_use]
pub fn with_managed_temp_tools(
mut self,
managed_temp_tools: Arc<dyn ManagedTempToolProvider>,
) -> Self {
self.managed_temp_tools = Some(managed_temp_tools);
self
}
/// Attaches the requester-scoped MCP tool policy registry.
#[must_use]
pub fn with_tool_policies(mut self, tool_policies: Arc<ToolPolicyRegistry>) -> Self {
@ -176,6 +191,7 @@ impl McpServer {
ready_sink: self.ready_sink.clone(),
ticket_tools: self.ticket_tools.clone(),
template_tools: self.template_tools.clone(),
managed_temp_tools: self.managed_temp_tools.clone(),
tool_policies: self.tool_policies.clone(),
mcp_tool_permissions: self.mcp_tool_permissions.clone(),
}
@ -543,6 +559,44 @@ impl McpServer {
};
}
if tools::is_managed_temp_tool(&name) {
let result = match &self.managed_temp_tools {
Some(provider) => {
provider
.handle_managed_temp_tool(&self.project, &self.requester, &name, arguments)
.await
}
None => Err(super::managed_temp::ManagedTempToolError::new(
"notConfigured",
"managed temp tools are not configured",
)),
};
self.publish_processed(&name, result.is_ok());
return match result {
Ok(value) => {
let text = serde_json::to_string(&value).unwrap_or_else(|_| "null".to_owned());
application::diag!(
"[mcp] tools_call end tool={name} requester={requester_label} \
target={arg_target} ok=true is_error=false result_len={} elapsed_ms={}",
text.len(),
started.elapsed().as_millis(),
);
Ok(tool_result_text(&text, false))
}
Err(err) => {
let text =
serde_json::to_string(&err.to_value()).unwrap_or_else(|_| err.to_string());
application::diag!(
"[mcp] tools_call end tool={name} requester={requester_label} \
target={arg_target} ok=false is_error=true result_len={} elapsed_ms={}",
text.len(),
started.elapsed().as_millis(),
);
Ok(tool_result_text(&text, true))
}
};
}
// The handshake-provided requester is still passed to the mapper for tools that
// need peer identity.
let command = match tools::map_tool_call(&name, &arguments, &self.requester) {