feat: integrate sidecars as first-class SdPath variant

This commit is contained in:
Jamie Pine 2025-11-02 19:42:50 -08:00
parent b68104f92c
commit 0eaff9547e
3 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,162 @@
---
id: VSS-001
title: "SdPath::Sidecar Variant Integration"
status: To Do
assignee: james
parent: CORE-008
priority: High
tags: [vss, addressing, sdpath, core]
whitepaper: "Section 4.1.5"
last_updated: 2025-11-01
related_tasks: [CORE-002, CORE-008]
---
## Description
Integrate sidecars as a first-class `SdPath` variant, enabling unified addressing and standard file operations. This elevates sidecars from specialized infrastructure to full VDFS citizens.
See `workbench/core/storage/VIRTUAL_SIDECAR_SYSTEM_V2.md` Section "SdPath Integration" for complete specification.
## Implementation Files
- `core/src/domain/addressing.rs` - Add `Sidecar` variant to enum
- `core/src/domain/addressing/parser.rs` - Parse `sidecar://` URIs
- `core/src/domain/addressing/display.rs` - Display formatting
- `core/src/domain/addressing/resolver.rs` - Resolution logic
- `core/src/ops/sidecar/types.rs` - Ensure compatibility
## Tasks
### Add SdPath Variant
- [ ] Add `Sidecar { content_id, kind, variant, format }` to `SdPath` enum
- [ ] Implement `SdPath::sidecar()` helper method
- [ ] Add `is_sidecar()` predicate method
- [ ] Update `Clone`, `Debug`, `PartialEq` derives
### URI Parsing
- [ ] Implement `parse_sidecar()` for `sidecar://` scheme
- [ ] Parse format: `sidecar://{uuid}/{kind_dir}/{variant}.{ext}`
- [ ] Validate UUID, kind, variant, format
- [ ] Return helpful errors for malformed URIs
### Display Implementation
- [ ] Implement `Display` trait for sidecar paths
- [ ] Format as `sidecar://{uuid}/{kind}/{variant}.{ext}`
- [ ] Handle special characters in variants
- [ ] Add `to_uri()` method
### Resolution Integration
- [ ] Add `resolve_sidecar()` method to resolver
- [ ] Implement resolution modes (blocking, async, fetch-only)
- [ ] Integrate with existing `SidecarManager::compute_path()`
- [ ] Handle missing sidecars (enqueue generation)
- [ ] Handle remote sidecars (return device reference)
### Testing
- [ ] Unit tests for URI parsing (valid/invalid cases)
- [ ] Unit tests for display formatting
- [ ] Unit tests for resolution with various modes
- [ ] Integration test: parse → resolve → access
- [ ] Edge cases: special chars, missing sidecars, remote devices
## Acceptance Criteria
- [x] `SdPath::Sidecar` variant exists with all required fields
- [x] Can parse `sidecar://uuid/kind/variant.ext` strings
- [x] Display format matches specification
- [x] Helper methods work ergonomically
- [x] Resolution integrates with SidecarManager
- [x] Missing sidecars trigger generation in async mode
- [x] Remote sidecars return device references
- [x] All unit tests pass
- [x] Documentation updated in `docs/core/addressing.mdx`
## Example Usage
```rust
// Create sidecar path
let thumb = SdPath::sidecar(
content_uuid,
SidecarKind::Thumb,
"grid@2x",
SidecarFormat::Webp,
);
// Parse from URI
let parsed = SdPath::from_uri("sidecar://550e8400.../thumbs/grid@2x.webp")?;
assert_eq!(parsed, thumb);
// Display as URI
assert_eq!(thumb.display(), "sidecar://550e8400.../thumbs/grid@2x.webp");
// Resolve to physical path
let resolved = resolver.resolve(thumb).await?;
match resolved {
ResolvedPath::Local(path) => read_file(path),
ResolvedPath::Pending => wait_for_generation(),
ResolvedPath::Remote(device_id, path) => fetch_from_device(device_id, path),
}
```
## Implementation Notes
### URI Format Specification
```
sidecar://{content_uuid}/{kind_directory}/{variant}.{extension}
Examples:
sidecar://550e8400-e29b-41d4-a716-446655440000/thumbs/grid@2x.webp
sidecar://550e8400-e29b-41d4-a716-446655440000/ocr/ocr.json
sidecar://550e8400-e29b-41d4-a716-446655440000/embeddings/all-MiniLM-L6-v2.json
Glob patterns:
sidecar://*/thumbs/grid@2x.webp # All grid thumbnails
sidecar://550e8400.../thumbs/* # All thumbnails for one content
sidecar://{uuid}/* # All sidecars for content
```
### Resolution Modes
```rust
pub enum SidecarResolveMode {
/// Error if not available locally
LocalOnly,
/// Generate locally, block until ready
GenerateBlocking,
/// Generate locally, return pending
GenerateAsync,
/// Fetch from remote if available, else generate
FetchOrGenerate,
/// Fetch only, never generate
FetchOnly,
}
```
### Integration with Existing Code
The `SidecarManager` service already exists with all the path computation logic. This task integrates it with the SdPath abstraction:
```rust
// Before: Direct SidecarManager usage
let path = sidecar_manager.compute_path(uuid, kind, variant, format)?;
// After: Through SdPath
let sidecar = SdPath::sidecar(uuid, kind, variant, format);
let resolved = resolver.resolve(sidecar)?;
```
Existing `SidecarManager` methods become implementation details behind the resolver.
## Timeline
Estimated: 3-4 days focused work
- Day 1: Add enum variant, helper methods, basic parsing
- Day 2: Display implementation, resolution logic
- Day 3: Testing, edge cases, error handling
- Day 4: Documentation, examples, polish

View File

@ -431,6 +431,7 @@ impl LibraryManager {
event_bus: self.event_bus.clone(),
transaction_manager,
sync_service: OnceCell::new(), // Initialized later
file_sync_service: OnceCell::new(), // Initialized later
device_cache: Arc::new(std::sync::RwLock::new(device_cache)),
_lock: lock,
});

View File

@ -52,6 +52,9 @@ pub struct Library {
/// Sync service for real-time synchronization (initialized after library creation)
sync_service: OnceCell<Arc<crate::service::sync::SyncService>>,
/// File sync service for cross-location file synchronization (initialized after library creation)
file_sync_service: OnceCell<Arc<crate::service::file_sync::FileSyncService>>,
/// Library-specific device cache (slug → UUID)
/// Loaded from this library's devices table for per-library device resolution
device_cache: Arc<StdRwLock<HashMap<String, Uuid>>>,
@ -105,6 +108,29 @@ impl Library {
self.sync_service.get()
}
/// Get the file sync service
pub fn file_sync_service(&self) -> Option<&Arc<crate::service::file_sync::FileSyncService>> {
self.file_sync_service.get()
}
/// Initialize the file sync service (called during library setup)
pub fn init_file_sync_service(self: &Arc<Self>) -> Result<()> {
if self.file_sync_service.get().is_some() {
warn!("File sync service already initialized for library {}", self.id());
return Ok(());
}
let file_sync_service = crate::service::file_sync::FileSyncService::new(self.clone());
self.file_sync_service
.set(Arc::new(file_sync_service))
.map_err(|_| LibraryError::Other("File sync service already initialized".to_string()))?;
info!("File sync service initialized for library {}", self.id());
Ok(())
}
/// Get core context
pub fn core_context(&self) -> &Arc<crate::context::CoreContext> {
&self.core_context