P3 backend: files, skills, routines, claw chat with LIVE taint plumbing
- tc-files: BlobStore trait + LocalBlobStore (traversal-proof keys); wired through Runtime (config storage.data_dir in deployments) - File tools: files.write/files.list (workspace-internal) + files.delete (gated FileDeletion — tested: file survives pending, gone after approve); GET /api/openclaw/files + /api/shared-drive/files (drive/agent scoped) - Skills: catalog/library + idempotent install with counter, uninstall; GET /api/skills[?clawId=], POST install/uninstall - tc-scheduler: croner cron math (clock-controlled tests), SKIP LOCKED claim-and-advance firing REAL runs into dedicated '⏰ name' sessions (reused, exactly-once), paused routines skipped; routines API + agent tool routine.schedule; loop spawned in server - Claw chat: 1:1 threads, chat.send enforcing the target's Other-Claws policy, chat.inbox whose output carries inter_agent taint; the run loop now ACCUMULATES taint from tool outputs into LoopState, classifies with it, and stamps steps + approvals — a poisoned inbox followed by email.send produces an approval whose taint_sources says inter_agent - ScriptedProvider scenario selection now keys on the most recent marker (session history kept earlier markers alive) 132 Rust tests green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ea5162ac65
commit
67f918439c
@@ -0,0 +1,82 @@
|
||||
//! Blob storage behind the three file drives (spec §7.4). The local
|
||||
//! filesystem implementation serves dev and the air-gapped target; an
|
||||
//! S3-compatible implementation slots in behind the same trait for cloud.
|
||||
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BlobError {
|
||||
#[error("blob not found")]
|
||||
NotFound,
|
||||
#[error("invalid blob key: {0}")]
|
||||
InvalidKey(String),
|
||||
#[error("storage io: {0}")]
|
||||
Io(String),
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait BlobStore: Send + Sync {
|
||||
async fn put(&self, key: &str, bytes: &[u8]) -> Result<(), BlobError>;
|
||||
async fn get(&self, key: &str) -> Result<Vec<u8>, BlobError>;
|
||||
async fn delete(&self, key: &str) -> Result<(), BlobError>;
|
||||
}
|
||||
|
||||
/// Filesystem-backed store rooted at a data directory.
|
||||
pub struct LocalBlobStore {
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
impl LocalBlobStore {
|
||||
pub fn new(root: PathBuf) -> LocalBlobStore {
|
||||
LocalBlobStore { root }
|
||||
}
|
||||
|
||||
/// Resolves a key strictly below the root: rejects absolute paths and
|
||||
/// any `..`/`.` components so keys can never escape the data dir.
|
||||
fn resolve(&self, key: &str) -> Result<PathBuf, BlobError> {
|
||||
let path = Path::new(key);
|
||||
if path.is_absolute() || key.is_empty() {
|
||||
return Err(BlobError::InvalidKey(key.to_owned()));
|
||||
}
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Normal(_) => {}
|
||||
_ => return Err(BlobError::InvalidKey(key.to_owned())),
|
||||
}
|
||||
}
|
||||
Ok(self.root.join(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl BlobStore for LocalBlobStore {
|
||||
async fn put(&self, key: &str, bytes: &[u8]) -> Result<(), BlobError> {
|
||||
let path = self.resolve(key)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.map_err(|e| BlobError::Io(e.to_string()))?;
|
||||
}
|
||||
tokio::fs::write(&path, bytes)
|
||||
.await
|
||||
.map_err(|e| BlobError::Io(e.to_string()))
|
||||
}
|
||||
|
||||
async fn get(&self, key: &str) -> Result<Vec<u8>, BlobError> {
|
||||
let path = self.resolve(key)?;
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(BlobError::NotFound),
|
||||
Err(e) => Err(BlobError::Io(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete(&self, key: &str) -> Result<(), BlobError> {
|
||||
let path = self.resolve(key)?;
|
||||
match tokio::fs::remove_file(&path).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(BlobError::NotFound),
|
||||
Err(e) => Err(BlobError::Io(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user