use frame_core::component::ComponentId;
use haematite::{BranchKind, Hash, remove_branch};
use crate::codec::{archive_meta_key, decode_meta};
use crate::engine::{Storage, archive_name, commit_named, namespace_name};
use crate::error::{ReconcileInconsistency, StateError};
use crate::types::{AbandonedWork, Archive, MetaState};
const SHARD: usize = 0;
impl Storage {
pub(crate) fn outstanding_work(
&mut self,
component: ComponentId,
) -> Result<Vec<AbandonedWork>, StateError> {
let lineage = namespace_name(component);
let names: Vec<String> = self
.refs
.list()
.filter(|record| {
record.kind == BranchKind::Work
&& record.resolved_namespace_lineage() == Some(lineage.as_str())
})
.map(|record| record.name.clone())
.collect();
let mut abandoned = Vec::with_capacity(names.len());
for name in names {
let root = self.commit_work(&name)?;
abandoned.push(AbandonedWork {
branch: name,
kind: BranchKind::Work,
last_root: root,
});
}
Ok(abandoned)
}
pub(crate) fn pin_remove_archive(
&mut self,
component: ComponentId,
generation: u64,
abandoned: &[AbandonedWork],
) -> Result<(), StateError> {
let branch = self.namespace(component)?;
commit_named(&branch, &mut self.nodes, &self.registry, &mut self.refs)?;
let root = branch.current_root();
let snapshot = archive_name(component, generation);
match self.snapshots.get(&snapshot) {
Some(actual) if actual == root => {}
Some(actual) => {
return Err(ReconcileInconsistency::SnapshotRootMismatch {
snapshot,
expected: root,
actual,
}
.into());
}
None => self.snapshots.name(&snapshot, root)?,
}
for work in abandoned {
let observed = remove_branch(&work.branch, &mut self.refs)?;
if observed.is_none() && self.work.contains_key(&work.branch) {
return Err(StateError::WorkNotFound {
branch: work.branch.clone(),
});
}
self.work.remove(&work.branch);
}
let removed = remove_branch(&namespace_name(component), &mut self.refs)?;
if removed.is_none() {
return Err(StateError::NotActive { component });
}
self.active.remove(&component);
Ok(())
}
pub(crate) fn archives(&self, component: ComponentId) -> Result<Vec<Archive>, StateError> {
let prefix = format!("archive/{component}/");
let mut archives = Vec::new();
for (name, root, _) in self.snapshots.list_snapshots() {
let Some(generation_text) = name.strip_prefix(&prefix) else {
continue;
};
let generation =
generation_text
.parse::<u64>()
.map_err(|error| StateError::CorruptRecord {
detail: format!(
"archive snapshot '{name}' has invalid generation: {error}"
),
})?;
let evidence = self
.meta
.get(SHARD, &self.nodes, &archive_meta_key(component, generation))?
.ok_or_else(|| StateError::CorruptRecord {
detail: format!(
"archive snapshot '{name}' has no immutable meta evidence record"
),
})?;
let abandoned_work = match decode_meta(component, &evidence)?.state {
MetaState::Archived {
generation: recorded,
abandoned_work,
} if recorded == generation => abandoned_work,
_ => {
return Err(StateError::CorruptRecord {
detail: format!("archive snapshot '{name}' has mismatched meta evidence"),
});
}
};
archives.push(Archive {
generation,
snapshot_name: name,
root,
abandoned_work,
});
}
archives.sort_by_key(|archive| archive.generation);
Ok(archives)
}
pub(crate) fn archived_root(
&self,
component: ComponentId,
generation: u64,
) -> Result<Hash, StateError> {
let name = archive_name(component, generation);
self.snapshots
.get(&name)
.ok_or_else(|| StateError::CorruptRecord {
detail: format!("archive snapshot '{name}' is absent"),
})
}
}