Skip to main content

kcode_rust_libs_v2/
lib.rs

1//! Complete managed Rust-library snapshots without duplicated documentation.
2
3mod model;
4mod repository;
5mod sandbox;
6
7use std::fmt;
8use std::fs;
9use std::io;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15pub use model::{
16    CheckResult, CheckStage, CheckStageResult, Error, Result, RustLibFile, RustLibPath,
17};
18use repository::{RepositorySnapshot, read_repository, validate_name, write_repository};
19
20static WORK_COUNTER: AtomicU64 = AtomicU64::new(0);
21
22/// Manager for a persistent root of small agent-authored Rust libraries.
23#[derive(Clone)]
24pub struct KcodeRustLibs {
25    rust_libs_root: PathBuf,
26    shared: Arc<Shared>,
27}
28
29impl KcodeRustLibs {
30    /// Creates or opens the library root and retains the publication token privately.
31    pub fn new(
32        rust_libs_root: impl AsRef<Path>,
33        crates_io_registry_token: impl AsRef<str>,
34    ) -> Result<Self> {
35        let token = RegistryToken::new(crates_io_registry_token.as_ref())?;
36        let requested_root = absolute_path(rust_libs_root.as_ref())?;
37        fs::create_dir_all(&requested_root)
38            .map_err(|source| Error::io("create Rust libraries root", &requested_root, source))?;
39        let rust_libs_root = fs::canonicalize(&requested_root).map_err(|source| {
40            Error::io("canonicalize Rust libraries root", &requested_root, source)
41        })?;
42        if !fs::metadata(&rust_libs_root)
43            .map_err(|source| Error::io("inspect Rust libraries root", &rust_libs_root, source))?
44            .is_dir()
45        {
46            return Err(Error::RustLibIsNotDirectory(
47                rust_libs_root.display().to_string(),
48            ));
49        }
50
51        let work_root = WorkRoot::new()?;
52        if paths_overlap(&rust_libs_root, work_root.path()) {
53            return Err(Error::RootsOverlap {
54                rust_libs_root,
55                work_root: work_root.path().to_path_buf(),
56            });
57        }
58        Ok(Self {
59            rust_libs_root,
60            shared: Arc::new(Shared { token, work_root }),
61        })
62    }
63
64    /// Creates a new Rust 2024 library at version 0.1.0 and opens it.
65    pub fn create_rust_lib(&self, name: &str) -> Result<OpenedRustLib> {
66        validate_name(name)?;
67        let root = self.rust_libs_root.join(name);
68        match fs::symlink_metadata(&root) {
69            Ok(_) => return Err(Error::RustLibAlreadyExists(name.to_owned())),
70            Err(source) if source.kind() == io::ErrorKind::NotFound => {}
71            Err(source) => {
72                return Err(Error::io("inspect Rust library destination", &root, source));
73            }
74        }
75        fs::create_dir(&root).map_err(|source| Error::io("create Rust library", &root, source))?;
76        let creation = (|| {
77            let source = root.join("src");
78            fs::create_dir(&source)
79                .map_err(|error| Error::io("create source directory", &source, error))?;
80            let manifest = format!(
81                "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
82            );
83            fs::write(root.join("Cargo.toml"), manifest).map_err(|error| {
84                Error::io("write initial manifest", root.join("Cargo.toml"), error)
85            })?;
86            fs::write(root.join("Documentation.md"), "").map_err(|error| {
87                Error::io(
88                    "write initial documentation",
89                    root.join("Documentation.md"),
90                    error,
91                )
92            })?;
93            fs::write(source.join("lib.rs"), "")
94                .map_err(|error| Error::io("write initial source", source.join("lib.rs"), error))?;
95            self.open_rust_lib(name)
96        })();
97        if creation.is_err() {
98            let _ = fs::remove_dir_all(&root);
99        }
100        creation
101    }
102
103    /// Opens and completely loads an existing managed Rust library.
104    pub fn open_rust_lib(&self, name: &str) -> Result<OpenedRustLib> {
105        validate_name(name)?;
106        let root = self.rust_libs_root.join(name);
107        let metadata = match fs::symlink_metadata(&root) {
108            Ok(metadata) => metadata,
109            Err(source) if source.kind() == io::ErrorKind::NotFound => {
110                return Err(Error::RustLibNotFound(name.to_owned()));
111            }
112            Err(source) => return Err(Error::io("inspect Rust library", &root, source)),
113        };
114        if metadata.file_type().is_symlink() {
115            return Err(Error::SymlinkNotAllowed(root));
116        }
117        if !metadata.is_dir() {
118            return Err(Error::RustLibIsNotDirectory(name.to_owned()));
119        }
120        let snapshot = read_repository(&root)?;
121        Ok(OpenedRustLib {
122            name: name.to_owned(),
123            root,
124            snapshot,
125            shared: Arc::clone(&self.shared),
126        })
127    }
128}
129
130impl fmt::Debug for KcodeRustLibs {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.debug_struct("KcodeRustLibs")
133            .field("rust_libs_root", &self.rust_libs_root)
134            .field("registry_token", &"[REDACTED]")
135            .finish_non_exhaustive()
136    }
137}
138
139/// One fully loaded managed Rust library.
140pub struct OpenedRustLib {
141    name: String,
142    root: PathBuf,
143    snapshot: RepositorySnapshot,
144    shared: Arc<Shared>,
145}
146
147impl OpenedRustLib {
148    pub fn name(&self) -> &str {
149        &self.name
150    }
151
152    /// Returns the canonical root-package version.
153    pub fn version(&self) -> &str {
154        &self.snapshot.version
155    }
156
157    /// Returns every managed UTF-8 file exactly once, sorted by path.
158    pub fn files(&self) -> &[RustLibFile] {
159        &self.snapshot.files
160    }
161
162    /// Creates or completely replaces supplied files and refreshes the snapshot.
163    pub fn write(&mut self, files: &[RustLibFile]) -> Result<()> {
164        self.snapshot = write_repository(&self.root, &self.snapshot.files, files)?;
165        Ok(())
166    }
167
168    /// Runs the complete six-stage validation pipeline.
169    pub fn check(&self) -> Result<CheckResult> {
170        sandbox::check_repository(&self.root, self.shared.work_root.path())
171    }
172
173    /// Rechecks and publishes the root package to crates.io.
174    pub fn publish(&self) -> Result<()> {
175        let check = self.check()?;
176        if !check.passed() {
177            return Err(Error::CheckFailed(check));
178        }
179        sandbox::publish_repository(
180            &self.root,
181            self.shared.work_root.path(),
182            self.shared.token.expose(),
183        )
184    }
185}
186
187struct Shared {
188    token: RegistryToken,
189    work_root: WorkRoot,
190}
191
192struct RegistryToken(Arc<str>);
193
194impl RegistryToken {
195    fn new(value: &str) -> Result<Self> {
196        let value = value.trim();
197        if value.is_empty() {
198            return Err(Error::InvalidRegistryToken);
199        }
200        Ok(Self(Arc::from(value)))
201    }
202
203    fn expose(&self) -> &str {
204        &self.0
205    }
206}
207
208impl fmt::Debug for RegistryToken {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        f.write_str("[REDACTED]")
211    }
212}
213
214struct WorkRoot(PathBuf);
215
216impl WorkRoot {
217    fn new() -> Result<Self> {
218        let parent = std::env::temp_dir().join("kcode-rust-libs-v2");
219        fs::create_dir_all(&parent)
220            .map_err(|source| Error::io("create temporary work parent", &parent, source))?;
221        for _ in 0..100 {
222            let timestamp = SystemTime::now()
223                .duration_since(UNIX_EPOCH)
224                .unwrap_or_default()
225                .as_nanos();
226            let counter = WORK_COUNTER.fetch_add(1, Ordering::Relaxed);
227            let path = parent.join(format!(
228                "process-{}-{timestamp}-{counter}",
229                std::process::id()
230            ));
231            match fs::create_dir(&path) {
232                Ok(()) => {
233                    let canonical = fs::canonicalize(&path).map_err(|source| {
234                        Error::io("canonicalize temporary work root", &path, source)
235                    })?;
236                    return Ok(Self(canonical));
237                }
238                Err(source) if source.kind() == io::ErrorKind::AlreadyExists => continue,
239                Err(source) => {
240                    return Err(Error::io("create temporary work root", path, source));
241                }
242            }
243        }
244        Err(Error::Sandbox {
245            stage: "prepare-work-root".to_owned(),
246            message: "could not allocate a unique process work root".to_owned(),
247        })
248    }
249
250    fn path(&self) -> &Path {
251        &self.0
252    }
253}
254
255impl Drop for WorkRoot {
256    fn drop(&mut self) {
257        let _ = fs::remove_dir_all(&self.0);
258    }
259}
260
261fn absolute_path(path: &Path) -> Result<PathBuf> {
262    if path.is_absolute() {
263        Ok(path.to_path_buf())
264    } else {
265        let current = std::env::current_dir()
266            .map_err(|source| Error::io("read current directory", ".", source))?;
267        Ok(current.join(path))
268    }
269}
270
271fn paths_overlap(left: &Path, right: &Path) -> bool {
272    left.starts_with(right) || right.starts_with(left)
273}