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

@ -3206,6 +3206,89 @@ pub struct ChatAttachmentDto {
pub created_at: u64,
}
/// Request for allocating an IdeA-managed temporary file/directory.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AllocateManagedTempRequestDto {
/// Optional project that requested this allocation.
#[serde(default)]
pub owner_project_id: Option<String>,
/// Physical allocation kind (`directory` or `file`).
pub kind: domain::ManagedTempKind,
/// Short human/debug purpose retained in metadata.
pub purpose: String,
/// Optional TTL override in milliseconds.
#[serde(default)]
pub ttl_ms: Option<u64>,
}
/// Request for resolving/releasing one managed temporary handle.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedTempHandleRequestDto {
/// Opaque managed temporary handle.
pub handle: String,
}
/// Managed temporary lease DTO.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedTempLeaseDto {
/// Opaque managed temporary handle.
pub handle: String,
/// Physical allocation kind.
pub kind: domain::ManagedTempKind,
/// Optional project that requested this allocation.
pub owner_project_id: Option<String>,
/// Short human/debug purpose.
pub purpose: String,
/// IdeA-controlled local path for the allocation data.
pub path: String,
/// Lease state.
pub state: domain::ManagedTempState,
/// Creation time, epoch milliseconds.
pub created_at_ms: u64,
/// Last successful access time, epoch milliseconds.
pub last_access_at_ms: u64,
/// Expiry time, epoch milliseconds.
pub expires_at_ms: Option<u64>,
/// Explicit release time, epoch milliseconds.
pub released_at_ms: Option<u64>,
}
impl From<domain::ManagedTempLease> for ManagedTempLeaseDto {
fn from(lease: domain::ManagedTempLease) -> Self {
Self {
handle: lease.handle.as_str().to_owned(),
kind: lease.kind,
owner_project_id: lease.owner_project_id.map(|id| id.to_string()),
purpose: lease.purpose,
path: lease.path.as_str().to_owned(),
state: lease.state,
created_at_ms: lease.created_at_ms,
last_access_at_ms: lease.last_access_at_ms,
expires_at_ms: lease.expires_at_ms,
released_at_ms: lease.released_at_ms,
}
}
}
/// Response containing one managed temporary lease.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedTempLeaseResponseDto {
/// Managed temporary lease.
pub lease: ManagedTempLeaseDto,
}
/// Managed temporary GC response.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedTempGcResponseDto {
/// Number of deleted allocations.
pub deleted: usize,
}
/// Canonical attachment projection attached to a streamed user prompt.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]