feat(werkbank): make the loop runnable — enqueue + artifact serve/fetch (WB-05b) (#209)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 3m43s
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped

This commit was merged in pull request #209.
This commit is contained in:
2026-07-17 14:34:00 +00:00
parent 70a4ee55ab
commit 94c0d51a11
6 changed files with 208 additions and 6 deletions
+24
View File
@@ -32,6 +32,30 @@ pub fn hash_file(path: &Path) -> Result<(String, u64), AgentError> {
Ok((hex::encode(hasher.finalize()), total))
}
/// Store raw bytes in the content-addressed blob store under `base`, returning
/// the SHA-256 digest. Used to stash a small derived artifact (e.g. the extracted
/// PLC program source) so a Werkbank runner can fetch it by hash. Idempotent.
pub fn store_bytes(base: &Path, bytes: &[u8]) -> Result<String, AgentError> {
let sha = hex::encode(Sha256::digest(bytes));
let dir = base.join("blobs").join(&sha[0..2]);
fs::create_dir_all(&dir)?;
let dest = dir.join(&sha);
if !dest.exists() {
fs::write(&dest, bytes)?;
}
Ok(sha)
}
/// Read a blob's bytes by its SHA-256 digest. Rejects a non-hex/wrong-length hash
/// so a request can't traverse outside the blob store.
pub fn read_blob(base: &Path, sha: &str) -> Result<Vec<u8>, AgentError> {
if sha.len() != 64 || !sha.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(AgentError::Other(format!("invalid content hash '{sha}'")));
}
let path = base.join("blobs").join(&sha[0..2]).join(sha);
Ok(fs::read(path)?)
}
/// Copy `src` into the content-addressed blob store under `base`, returning the
/// stored path. Idempotent: an already-present blob is not rewritten.
pub fn store_file(base: &Path, src: &Path, sha: &str) -> Result<PathBuf, AgentError> {