Phase 6c fix: TagStore delete()/contains() also see stamped tags
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Failing after 3s

Companion to the previous list/get fix. Same layer-split problem:
* delete() only unlinked tags/, leaving stamped tags-v2/ behind.
  Result: `unpin` prints \"no such tag\" for pins created via
  Phase 3c+ RPC even though the tag is right there on disk.
* contains() only checked tags/. Same false-negative.

Fix: both APIs now inspect BOTH layers. delete() unlinks
whichever files exist (either or both) AND removes the TTL
expiry sidecar if present. Returns true when anything was
actually removed.

Legacy behavior preserved: tests unchanged, 381 tests pass.
This commit is contained in:
Omar Sobh
2026-07-14 12:27:36 -07:00
parent 14a8221e00
commit 2c51f0917e
+26 -9
View File
@@ -268,22 +268,39 @@ impl TagStore {
/// no such tag existed. /// no such tag existed.
pub async fn delete(&self, key: &str) -> Result<bool> { pub async fn delete(&self, key: &str) -> Result<bool> {
validate_key(key)?; validate_key(key)?;
let path = self.tag_path(key); // Phase 6c fix (2026-07-14): try both layers. Modern pins
match tokio::fs::remove_file(&path).await { // land in tags-v2/; without unlinking the stamped file too,
Ok(()) => Ok(true), // `unpin` prints "no such tag" and leaves the tag behind.
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), let mut removed = false;
Err(e) => Err(anyhow::Error::from(e)), let legacy = self.tag_path(key);
match tokio::fs::remove_file(&legacy).await {
Ok(()) => removed = true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(anyhow::Error::from(e)),
} }
let stamped = self.stamped_tag_path(key);
match tokio::fs::remove_file(&stamped).await {
Ok(()) => removed = true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(anyhow::Error::from(e)),
}
// Also clear any TTL sidecar.
let expiry = self.stamped_expiry_path(key);
let _ = tokio::fs::remove_file(&expiry).await;
Ok(removed)
} }
/// Whether a tag with the given name exists. /// Whether a tag with the given name exists.
pub async fn contains(&self, key: &str) -> Result<bool> { pub async fn contains(&self, key: &str) -> Result<bool> {
validate_key(key)?; validate_key(key)?;
match tokio::fs::metadata(self.tag_path(key)).await { // Same fallthrough as get(): visible via either layer counts.
Ok(_) => Ok(true), if tokio::fs::metadata(self.tag_path(key)).await.is_ok() {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), return Ok(true);
Err(e) => Err(anyhow::Error::from(e)),
} }
if tokio::fs::metadata(self.stamped_tag_path(key)).await.is_ok() {
return Ok(true);
}
Ok(false)
} }
/// List every stored tag. Sorted by key for deterministic output. /// List every stored tag. Sorted by key for deterministic output.