Skip to main content

oxios_kernel/kernel_handle/
mount_api.rs

1//! Mount API — Mount management system calls (RFC-025).
2//!
3//! Provides API endpoints for:
4//! - Listing and querying Mounts
5//! - CRUD on Mounts (minimal "name + path" input)
6//! - Agent-driven enrichment (`update_enrichment`)
7//! - Detection
8
9use std::path::PathBuf;
10use std::sync::Arc;
11
12use anyhow::{Context, Result};
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16use crate::mount::{Mount, MountId, MountManager, MountMeta, MountSource};
17
18/// Serialized Mount info for API responses.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[allow(missing_docs)]
21pub struct MountInfo {
22    pub id: String,
23    pub name: String,
24    pub paths: Vec<String>,
25    pub auto_description: String,
26    pub auto_meta: MountMeta,
27    pub source: String,
28    pub enrichment_pending: bool,
29    pub last_enriched_at: Option<String>,
30    pub created_at: String,
31    pub updated_at: String,
32    pub last_active_at: String,
33}
34
35impl From<&Mount> for MountInfo {
36    fn from(m: &Mount) -> Self {
37        Self {
38            id: m.id.to_string(),
39            name: m.name.clone(),
40            paths: m
41                .paths
42                .iter()
43                .map(|p| p.to_string_lossy().to_string())
44                .collect(),
45            auto_description: m.auto_description.clone(),
46            auto_meta: m.auto_meta.clone(),
47            source: m.source.to_string(),
48            enrichment_pending: m.enrichment_pending,
49            last_enriched_at: m.last_enriched_at.map(|t| t.to_rfc3339()),
50            created_at: m.created_at.to_rfc3339(),
51            updated_at: m.updated_at.to_rfc3339(),
52            last_active_at: m.last_active_at.to_rfc3339(),
53        }
54    }
55}
56
57/// Mount system calls.
58#[allow(dead_code)]
59pub struct MountApi {
60    /// Mount manager.
61    pub(crate) mount_manager: Arc<MountManager>,
62}
63
64impl MountApi {
65    /// Create a new MountApi.
66    pub fn new(mount_manager: Arc<MountManager>) -> Self {
67        Self { mount_manager }
68    }
69
70    /// List all Mounts.
71    pub fn list_mounts(&self) -> Vec<MountInfo> {
72        self.mount_manager
73            .list_mounts()
74            .iter()
75            .map(MountInfo::from)
76            .collect()
77    }
78
79    /// Get a Mount by ID.
80    pub fn get_mount(&self, id: &str) -> Option<MountInfo> {
81        let mount_id: MountId = Uuid::parse_str(id).ok()?;
82        self.mount_manager
83            .get_mount(mount_id)
84            .as_ref()
85            .map(MountInfo::from)
86    }
87
88    /// Get a Mount by name.
89    pub fn get_mount_by_name(&self, name: &str) -> Option<MountInfo> {
90        self.mount_manager
91            .get_mount_by_name(name)
92            .as_ref()
93            .map(MountInfo::from)
94    }
95
96    /// Resolve a comma-separated list of Mount IDs to Mount records,
97    /// preserving order. Used by the orchestrator/runtime.
98    pub fn resolve_mounts(&self, mount_ids: &[MountId]) -> Vec<Mount> {
99        self.mount_manager.get_mounts_ordered(mount_ids)
100    }
101
102    /// Create a new Mount (minimal RFC-025 input: name + paths).
103    pub fn create_mount(&self, name: String, paths: Vec<String>) -> Result<MountInfo> {
104        let paths: Vec<PathBuf> = paths.into_iter().map(PathBuf::from).collect();
105        let mount = self
106            .mount_manager
107            .create_mount(name, paths, MountSource::Manual)?;
108        Ok(MountInfo::from(&mount))
109    }
110
111    /// Update a Mount's agent-enriched fields (RFC-025 Phase 3).
112    pub fn update_enrichment(
113        &self,
114        id: &str,
115        auto_description: Option<String>,
116        auto_meta: Option<MountMeta>,
117    ) -> Result<MountInfo> {
118        let mount_id: MountId = Uuid::parse_str(id).context("Invalid mount ID")?;
119        let mount = self
120            .mount_manager
121            .update_enrichment(mount_id, auto_description, auto_meta)?;
122        Ok(MountInfo::from(&mount))
123    }
124
125    /// Rename a Mount.
126    pub fn rename_mount(&self, id: &str, new_name: String) -> Result<MountInfo> {
127        let mount_id: MountId = Uuid::parse_str(id).context("Invalid mount ID")?;
128        let mount = self.mount_manager.rename(mount_id, new_name)?;
129        Ok(MountInfo::from(&mount))
130    }
131
132    /// Remove a Mount.
133    pub fn remove_mount(&self, id: &str) -> Result<()> {
134        let mount_id: MountId = Uuid::parse_str(id).context("Invalid mount ID")?;
135        self.mount_manager.remove_mount(mount_id)
136    }
137
138    /// Record that a Mount was used (touch activity timestamp).
139    pub fn touch_mount(&self, id: &str) {
140        if let Ok(mount_id) = Uuid::parse_str(id) {
141            self.mount_manager.touch(mount_id);
142        }
143    }
144
145    /// Re-seed auto_meta from the filesystem (RFC-025 manual rescan).
146    pub fn rescan(&self, id: &str) -> Result<MountInfo> {
147        let mount_id: MountId = Uuid::parse_str(id).context("Invalid mount ID")?;
148        self.mount_manager
149            .seed_auto_meta(mount_id)
150            .context("Failed to rescan mount")?;
151        self.get_mount(id)
152            .ok_or_else(|| anyhow::anyhow!("Mount not found after rescan"))
153    }
154
155    /// Detect a Mount from a message, returning the matched Mount's info.
156    pub fn detect(&self, message: &str) -> Option<MountInfo> {
157        use crate::mount::DetectionResult;
158        match self.mount_manager.detect(message) {
159            DetectionResult::Found(id) => self
160                .mount_manager
161                .get_mount(id)
162                .as_ref()
163                .map(MountInfo::from),
164            DetectionResult::NoMatch { .. } => None,
165        }
166    }
167}