Phase 6c fix: TagStore list/get see stamped tags #73
@@ -234,7 +234,15 @@ impl TagStore {
|
|||||||
let path = self.tag_path(key);
|
let path = self.tag_path(key);
|
||||||
let bytes = match tokio::fs::read(&path).await {
|
let bytes = match tokio::fs::read(&path).await {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
// Phase 6c fix (2026-07-14): legacy tag file absent
|
||||||
|
// → fall through to the stamped store so modern pins
|
||||||
|
// are visible via the plain `get` API.
|
||||||
|
return Ok(self
|
||||||
|
.get_stamped(key)
|
||||||
|
.await?
|
||||||
|
.map(|s| s.value));
|
||||||
|
}
|
||||||
Err(e) => return Err(anyhow::Error::from(e)),
|
Err(e) => return Err(anyhow::Error::from(e)),
|
||||||
};
|
};
|
||||||
let (parsed_key, value) = decode_record(&bytes).with_context(|| {
|
let (parsed_key, value) = decode_record(&bytes).with_context(|| {
|
||||||
@@ -284,9 +292,13 @@ impl TagStore {
|
|||||||
let tags_root = self.root.join("tags");
|
let tags_root = self.root.join("tags");
|
||||||
let mut entries: Vec<TagEntry> = Vec::new();
|
let mut entries: Vec<TagEntry> = Vec::new();
|
||||||
|
|
||||||
let mut top = tokio::fs::read_dir(&tags_root)
|
let mut top = match tokio::fs::read_dir(&tags_root).await {
|
||||||
.await
|
Ok(t) => t,
|
||||||
.with_context(|| format!("reading {}", tags_root.display()))?;
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return self.list_stamped_only().await
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e).with_context(|| format!("reading {}", tags_root.display())),
|
||||||
|
};
|
||||||
while let Some(bucket) = top.next_entry().await? {
|
while let Some(bucket) = top.next_entry().await? {
|
||||||
if !bucket.file_type().await?.is_dir() {
|
if !bucket.file_type().await?.is_dir() {
|
||||||
continue;
|
continue;
|
||||||
@@ -305,10 +317,63 @@ impl TagStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Phase 6c fix (2026-07-14): fold in stamped tags too.
|
||||||
|
// Pin/PutTagVersioned writes here; list() must see them or
|
||||||
|
// downstream consumers (FUSE tag layer, humans running
|
||||||
|
// `list-tags`) miss modern pins entirely.
|
||||||
|
let stamped = self.list_stamped_only().await?;
|
||||||
|
let seen: std::collections::HashSet<String> =
|
||||||
|
entries.iter().map(|e| e.key.clone()).collect();
|
||||||
|
for e in stamped {
|
||||||
|
if !seen.contains(&e.key) {
|
||||||
|
entries.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
entries.sort_by(|a, b| a.key.cmp(&b.key));
|
entries.sort_by(|a, b| a.key.cmp(&b.key));
|
||||||
Ok(entries)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_stamped_only(&self) -> Result<Vec<TagEntry>> {
|
||||||
|
let stamped_root = self.root.join("tags-v2");
|
||||||
|
let mut entries: Vec<TagEntry> = Vec::new();
|
||||||
|
let mut top = match tokio::fs::read_dir(&stamped_root).await {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(entries),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e).with_context(|| format!("reading {}", stamped_root.display()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
while let Some(bucket) = top.next_entry().await? {
|
||||||
|
if !bucket.file_type().await?.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut inner = tokio::fs::read_dir(bucket.path()).await?;
|
||||||
|
while let Some(entry) = inner.next_entry().await? {
|
||||||
|
if !entry.file_type().await?.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.is_none_or(|n| !n.ends_with(".svtag"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let bytes = match tokio::fs::read(entry.path()).await {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
if let Ok((key, stamped)) = decode_stamped_record(&bytes) {
|
||||||
|
entries.push(TagEntry {
|
||||||
|
key,
|
||||||
|
value_hex: hex32(&stamped.value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 4 (2026-07-13): union of every 32-byte value referenced
|
/// Phase 4 (2026-07-13): union of every 32-byte value referenced
|
||||||
/// by a tag in either the legacy `tags/` or the Phase-3c
|
/// by a tag in either the legacy `tags/` or the Phase-3c
|
||||||
/// `tags-v2/` namespace. Feeds pin-aware eviction: any blob whose
|
/// `tags-v2/` namespace. Feeds pin-aware eviction: any blob whose
|
||||||
|
|||||||
Reference in New Issue
Block a user