feat(hardening): production logging, single-instance, CSP, and scoped asset protocol

Phase 4 of the 0.1.0 release prep:
- tauri-plugin-log: rotating file log (5 MB, Info) in the app log dir plus
  stdout; all 54 backend println!/eprintln! sites migrated to log macros so
  release builds finally produce diagnostics
- tauri-plugin-single-instance (registered first): second launches focus the
  existing window instead of racing the same SQLite DB with a second worker
  fleet; tauri-plugin-window-state persists window geometry
- real CSP replacing null (scripts self-only, media/images via the asset
  protocol, IPC endpoints whitelisted, object-src none) with a devCsp
  variant for Vite HMR
- asset protocol scope narrowed from '**' to thumbnails statically plus
  per-folder runtime grants (setup, add_folder, update_folder_path)

Panic audit (plan item) concluded with no changes: worker loops already
catch and log all Result errors; remaining unwraps are startup fail-fast,
poisoned-lock convention, infallible-by-construction, or test code.
This commit is contained in:
2026-06-12 20:12:03 +01:00
parent 890c23bdce
commit 3fb4a9685f
10 changed files with 454 additions and 61 deletions
+7 -7
View File
@@ -20,7 +20,7 @@ fn with_text_embedder<T>(f: impl FnOnce(&ClipImageEmbedder) -> Result<T>) -> Res
.lock()
.map_err(|_| anyhow::anyhow!("Text embedder lock poisoned"))?;
if guard.is_none() {
println!("Initializing CLIP text embedder...");
log::info!("Initializing CLIP text embedder...");
*guard = Some(ClipImageEmbedder::new()?);
}
f(guard.as_ref().unwrap())
@@ -35,13 +35,13 @@ pub struct ClipImageEmbedder {
impl ClipImageEmbedder {
pub fn new() -> Result<Self> {
println!("Initializing CLIP image embedder...");
log::info!("Initializing CLIP image embedder...");
let api = Api::new()?;
let repo = api.repo(Repo::new(
"laion/CLIP-ViT-B-32-laion2B-s34B-b79K".to_string(),
RepoType::Model,
));
println!("Resolving CLIP model weights from Hugging Face cache...");
log::info!("Resolving CLIP model weights from Hugging Face cache...");
let model_path = repo.get("model.safetensors")?;
let tokenizer_repo = api.repo(Repo::new(
"openai/clip-vit-base-patch32".to_string(),
@@ -60,7 +60,7 @@ impl ClipImageEmbedder {
};
let model = ClipModel::new(vb, &config)?;
let tokenizer = Tokenizer::from_file(tokenizer_path).map_err(anyhow::Error::msg)?;
println!("CLIP image embedder ready.");
log::info!("CLIP image embedder ready.");
Ok(Self {
model,
@@ -177,14 +177,14 @@ impl ClipImageEmbedder {
fn resolve_device() -> Result<Device> {
match Device::cuda_if_available(0) {
Ok(device) if device.is_cuda() => {
println!("CLIP embedder: using CUDA GPU (device 0).");
log::info!("CLIP embedder: using CUDA GPU (device 0).");
return Ok(device);
}
Ok(_) => {
println!("CLIP embedder: no compatible CUDA GPU found — using CPU.");
log::info!("CLIP embedder: no compatible CUDA GPU found — using CPU.");
}
Err(e) => {
println!("CLIP embedder: CUDA init failed ({e}) — using CPU.");
log::info!("CLIP embedder: CUDA init failed ({e}) — using CPU.");
}
}
Ok(Device::Cpu)