runtime: canonicalize Worker aggregates

This commit is contained in:
2026-08-11 22:38:29 +09:00
parent 33d3d1a43f
commit 34f8949e85
16 changed files with 2013 additions and 201 deletions
+130
View File
@@ -319,6 +319,115 @@ pub trait WorkerMetadataStore: Send + Sync {
}
}
/// Metadata store for one canonical Worker aggregate.
///
/// The Worker key is fixed by Runtime authority and metadata lives directly at
/// `<worker-aggregate>/metadata.json`. This store cannot address another
/// Worker's global metadata root.
#[derive(Clone)]
pub struct WorkerAggregateStore {
root: PathBuf,
worker_name: String,
}
impl WorkerAggregateStore {
pub fn new(
root: impl Into<PathBuf>,
worker_name: impl Into<String>,
) -> Result<Self, WorkerStoreError> {
let root = root.into();
let worker_name = worker_name.into();
validate_worker_name(&worker_name)?;
fs::create_dir_all(&root)?;
Ok(Self { root, worker_name })
}
fn validate_name(&self, worker_name: &str) -> Result<(), WorkerStoreError> {
validate_worker_name(worker_name)?;
if worker_name == self.worker_name {
Ok(())
} else {
Err(WorkerStoreError::InvalidWorkerName(format!(
"aggregate owns `{}`; requested `{worker_name}`",
self.worker_name
)))
}
}
fn metadata_path(&self) -> PathBuf {
self.root.join("metadata.json")
}
}
impl WorkerMetadataStore for WorkerAggregateStore {
fn write(&self, metadata: &WorkerMetadata) -> Result<(), WorkerStoreError> {
self.validate_name(&metadata.worker_name)?;
let mut content = serde_json::to_vec_pretty(metadata)?;
content.push(b'\n');
let path = self.metadata_path();
let temp = self.root.join(format!(
".metadata.json.tmp-{}-{}",
std::process::id(),
uuid::Uuid::now_v7()
));
let result = (|| -> Result<(), WorkerStoreError> {
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&temp)?;
file.write_all(&content)?;
file.sync_all()?;
drop(file);
fs::rename(&temp, &path)?;
std::fs::File::open(&self.root)?.sync_all()?;
Ok(())
})();
if result.is_err() {
let _ = fs::remove_file(temp);
}
result
}
fn read_by_name(&self, worker_name: &str) -> Result<Option<WorkerMetadata>, WorkerStoreError> {
self.validate_name(worker_name)?;
let content = match fs::read(self.metadata_path()) {
Ok(content) => content,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(error.into()),
};
let metadata: WorkerMetadata = serde_json::from_slice(&content)?;
if metadata.worker_name != self.worker_name {
return Err(WorkerStoreError::InvalidWorkerName(format!(
"aggregate identity mismatch: expected `{}`, found `{}`",
self.worker_name, metadata.worker_name
)));
}
Ok(Some(metadata))
}
fn list_names(&self) -> Result<Vec<String>, WorkerStoreError> {
Ok(if self.metadata_path().is_file() {
vec![self.worker_name.clone()]
} else {
Vec::new()
})
}
fn root_dir(&self) -> Option<PathBuf> {
Some(self.root.clone())
}
fn delete_by_name(&self, worker_name: &str) -> Result<(), WorkerStoreError> {
self.validate_name(worker_name)?;
match fs::remove_file(self.metadata_path()) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error.into()),
}
}
}
/// Filesystem-backed Worker metadata store.
#[derive(Clone)]
pub struct FsWorkerStore {
@@ -555,6 +664,27 @@ mod tests {
assert_eq!(restored, metadata);
}
#[test]
fn worker_aggregate_store_writes_one_fixed_metadata_identity() {
let tmp = tempfile::tempdir().unwrap();
let store = WorkerAggregateStore::new(tmp.path(), "worker-runtime-7").unwrap();
let metadata = WorkerMetadata::new("worker-runtime-7", None);
store.write(&metadata).unwrap();
assert!(tmp.path().join("metadata.json").is_file());
assert_eq!(store.list_names().unwrap(), vec!["worker-runtime-7"]);
assert_eq!(
store
.read_by_name("worker-runtime-7")
.unwrap()
.unwrap()
.worker_name,
"worker-runtime-7"
);
let error = store.read_by_name("worker-runtime-8").unwrap_err();
assert!(error.to_string().contains("aggregate owns"));
}
#[test]
fn fs_store_writes_under_worker_state_root_only() {
let tmp = tempfile::TempDir::new().unwrap();