1use super::hypermedia_store::{CommonsVisibility, HypermediaStore, LibraryEntry, LibrarySection};
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11pub const MODEL_MEDIA_TYPE: &str = "application/x-webizen-model-manifest";
13pub const ONTOLOGY_MEDIA_TYPE: &str = "application/x-webizen-ontology";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub struct ModelCatalogEntry {
18 pub id: &'static str,
19 pub title: &'static str,
20 pub rel_path: &'static str,
22 pub role: &'static str,
23 pub is_seed_reference: bool,
25 pub category: &'static str,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub struct OntologyCatalogEntry {
30 pub id: &'static str,
31 pub title: &'static str,
32 pub rel_path: &'static str,
33 pub domain: &'static str,
34 pub licence_note: &'static str,
35}
36
37pub const PERCEPTION_MODEL_CATALOG: &[ModelCatalogEntry] = &[
39 ModelCatalogEntry {
40 id: "vision-seed-qvwt",
41 title: "Vision seed QVWT (detector head)",
42 rel_path: "models/vision_seed.qvwt",
43 role: "vision-encoder-detector",
44 is_seed_reference: true,
45 category: "vision",
46 },
47 ModelCatalogEntry {
49 id: "lib-computer-vision-cv",
50 title: "computer_vision::cv (classical filters/edges/features)",
51 rel_path: "crates/qualia-core-db/src/specialized_libs/computer_vision/cv/",
52 role: "specialized-lib-cv",
53 is_seed_reference: false,
54 category: "vision-library",
55 },
56 ModelCatalogEntry {
57 id: "lib-computer-vision-sr",
58 title: "computer_vision::sr (classical + tiled SR + device policy)",
59 rel_path: "crates/qualia-core-db/src/specialized_libs/computer_vision/sr/",
60 role: "specialized-lib-sr",
61 is_seed_reference: false,
62 category: "vision-library",
63 },
64 ModelCatalogEntry {
65 id: "lib-computer-vision-bio",
66 title: "computer_vision::bio (histo/radiomics/DICOM-lite)",
67 rel_path: "crates/qualia-core-db/src/specialized_libs/computer_vision/bio/",
68 role: "specialized-lib-bio",
69 is_seed_reference: false,
70 category: "vision-library",
71 },
72 ModelCatalogEntry {
73 id: "lib-computer-vision-spatial",
74 title: "computer_vision::spatial (MeshIR export/quality/σ)",
75 rel_path: "crates/qualia-core-db/src/specialized_libs/computer_vision/spatial/",
76 role: "specialized-lib-spatial",
77 is_seed_reference: false,
78 category: "vision-library",
79 },
80 ModelCatalogEntry {
81 id: "audio-seed-aed",
82 title: "Audio seed AED (.qaed)",
83 rel_path: "models/aed_seed.qaed",
84 role: "auditory-event-detection",
85 is_seed_reference: true,
86 category: "audio",
87 },
88 ModelCatalogEntry {
89 id: "audio-seed-speech",
90 title: "Speech seed phones (.qspk)",
91 rel_path: "models/speech_seed.qspk",
92 role: "speech-encoder-phones",
93 is_seed_reference: true,
94 category: "audio",
95 },
96 ModelCatalogEntry {
97 id: "llm-gguf-slot",
98 title: "Local GGUF slot (user-supplied)",
99 rel_path: "models/",
100 role: "llm-gguf",
101 is_seed_reference: false,
102 category: "language",
103 },
104 ModelCatalogEntry {
105 id: "vision-yunet-face",
106 title: "YuNet 2D Face Detector (MIT)",
107 rel_path: "models/vision/face/yunet/face_detection_yunet_2023mar.onnx",
108 role: "vision-face-detector",
109 is_seed_reference: false,
110 category: "vision",
111 },
112 ModelCatalogEntry {
113 id: "vision-sface-embed",
114 title: "SFace Biometric Embeddings (Apache-2.0)",
115 rel_path: "models/vision/face/sface/face_recognition_sface_2021dec.onnx",
116 role: "vision-face-embedding",
117 is_seed_reference: false,
118 category: "vision",
119 },
120 ModelCatalogEntry {
121 id: "vision-mediapipe-facemesh",
122 title: "MediaPipe 468-pt Face Mesh (Apache-2.0)",
123 rel_path: "models/vision/face/mediapipe_landmarker/face_landmarker.task",
124 role: "vision-face-landmarks",
125 is_seed_reference: false,
126 category: "vision",
127 },
128 ModelCatalogEntry {
129 id: "vision-midas-depth",
130 title: "MiDaS 3D Monocular Depth (MIT)",
131 rel_path: "models/vision/depth/midas_v21_small.onnx",
132 role: "vision-monocular-depth",
133 is_seed_reference: false,
134 category: "vision",
135 },
136 ModelCatalogEntry {
137 id: "vision-espcn-sr",
138 title: "ESPCN Image Super-Resolution 2x/4x (Apache-2.0)",
139 rel_path: "models/vision/sr/espcn/espcn_x4.onnx",
140 role: "vision-super-resolution",
141 is_seed_reference: false,
142 category: "vision",
143 },
144 ModelCatalogEntry {
145 id: "vision-yolonas-object",
146 title: "YOLO-NAS Object Detector (Apache-2.0)",
147 rel_path: "models/vision/detect/yolo_nas/yolo_nas_s.onnx",
148 role: "vision-object-detector",
149 is_seed_reference: false,
150 category: "vision",
151 },
152 ModelCatalogEntry {
153 id: "audio-chatterbox-voice",
154 title: "Resemble AI Chatterbox Voice & TTS (Apache-2.0 / MIT)",
155 rel_path: "models/audio/tts/chatterbox/chatterbox.onnx",
156 role: "audio-voice-cloning-tts",
157 is_seed_reference: false,
158 category: "audio",
159 },
160 ModelCatalogEntry {
161 id: "audio-kokoro-82m-tts",
162 title: "Kokoro-82M Ultra-Lightweight TTS (Apache-2.0)",
163 rel_path: "models/audio/tts/kokoro/kokoro_v0_88.onnx",
164 role: "audio-fast-tts",
165 is_seed_reference: false,
166 category: "audio",
167 },
168 ModelCatalogEntry {
169 id: "audio-f5tts-clone",
170 title: "F5-TTS Flow-Matching Voice Cloner (MIT)",
171 rel_path: "models/audio/tts/f5tts/f5tts_small.onnx",
172 role: "audio-voice-cloning",
173 is_seed_reference: false,
174 category: "audio",
175 },
176];
177
178pub const PERCEPTION_ONTOLOGY_CATALOG: &[OntologyCatalogEntry] = &[
180 OntologyCatalogEntry {
181 id: "shacl",
182 title: "SHACL",
183 rel_path: "bundled/ontologies/shacl.ttl",
184 domain: "validation",
185 licence_note: "W3C SHACL",
186 },
187 OntologyCatalogEntry {
188 id: "ldp",
189 title: "Linked Data Platform (LDP)",
190 rel_path: "bundled/ontologies/w3c-archives/ldp.ttl",
191 domain: "solid",
192 licence_note: "W3C",
193 },
194 OntologyCatalogEntry {
195 id: "acl",
196 title: "Web Access Control (ACL)",
197 rel_path: "bundled/ontologies/w3c-archives/auth-acl.ttl",
198 domain: "solid",
199 licence_note: "W3C / Solid",
200 },
201 OntologyCatalogEntry {
202 id: "solid-terms",
203 title: "Solid Terms",
204 rel_path: "bundled/ontologies/w3c-archives/solid-terms.ttl",
205 domain: "solid",
206 licence_note: "Solid",
207 },
208 OntologyCatalogEntry {
209 id: "solid-oidc",
210 title: "Solid-OIDC",
211 rel_path: "bundled/ontologies/w3c-archives/solid-oidc.ttl",
212 domain: "solid",
213 licence_note: "Solid",
214 },
215 OntologyCatalogEntry {
216 id: "pim-space",
217 title: "PIM Space",
218 rel_path: "bundled/ontologies/w3c-archives/pim-space.ttl",
219 domain: "solid",
220 licence_note: "Solid",
221 },
222 OntologyCatalogEntry {
223 id: "foaf",
224 title: "FOAF",
225 rel_path: "bundled/ontologies/w3c-archives/foaf.ttl",
226 domain: "social",
227 licence_note: "FOAF",
228 },
229 OntologyCatalogEntry {
230 id: "prov",
231 title: "PROV-O",
232 rel_path: "bundled/ontologies/w3c/prov.ttl",
233 domain: "provenance",
234 licence_note: "W3C",
235 },
236 OntologyCatalogEntry {
237 id: "skos",
238 title: "SKOS",
239 rel_path: "bundled/ontologies/w3c/skos.ttl",
240 domain: "vocab",
241 licence_note: "W3C",
242 },
243 OntologyCatalogEntry {
244 id: "dcterms",
245 title: "Dublin Core Terms",
246 rel_path: "bundled/ontologies/purl/dcterms.ttl",
247 domain: "metadata",
248 licence_note: "Dublin Core",
249 },
250 OntologyCatalogEntry {
251 id: "owl",
252 title: "OWL",
253 rel_path: "bundled/ontologies/w3c/owl.ttl",
254 domain: "logic",
255 licence_note: "W3C",
256 },
257 OntologyCatalogEntry {
258 id: "rdfs",
259 title: "RDFS",
260 rel_path: "bundled/ontologies/w3c/rdfs.ttl",
261 domain: "logic",
262 licence_note: "W3C",
263 },
264 OntologyCatalogEntry {
265 id: "time",
266 title: "OWL-Time",
267 rel_path: "bundled/ontologies/w3c/time.ttl",
268 domain: "temporal",
269 licence_note: "W3C",
270 },
271 OntologyCatalogEntry {
272 id: "sosa",
273 title: "SOSA (sensors)",
274 rel_path: "bundled/ontologies/w3c/sosa.ttl",
275 domain: "sensing",
276 licence_note: "W3C",
277 },
278 OntologyCatalogEntry {
279 id: "music",
280 title: "Music Ontology (purl)",
281 rel_path: "bundled/ontologies/purl/music.ttl",
282 domain: "audio",
283 licence_note: "Music Ontology",
284 },
285 OntologyCatalogEntry {
286 id: "consent",
287 title: "Consent ontology (purl)",
288 rel_path: "bundled/ontologies/purl/consent.ttl",
289 domain: "rights",
290 licence_note: "bundled purl",
291 },
292];
293
294fn primary_subject_for_uri(uri: &str) -> u64 {
295 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
296 const FNV_PRIME: u64 = 0x100_0000_01b3;
297 let mut h = FNV_OFFSET;
298 for b in uri.as_bytes() {
299 h ^= u64::from(*b);
300 h = h.wrapping_mul(FNV_PRIME);
301 }
302 h & 0x0FFF_FFFF_FFFF_FFFF
303}
304
305fn now_unix() -> u64 {
306 std::time::SystemTime::now()
307 .duration_since(std::time::UNIX_EPOCH)
308 .map(|d| d.as_secs())
309 .unwrap_or(0)
310}
311
312fn model_to_library(m: ModelCatalogEntry, storage_root: &Path, now: u64) -> LibraryEntry {
313 let uri = format!("model://webizen/{}", m.id);
314 let abs = storage_root.join(m.rel_path);
315 let on_disk = abs.is_file() || (m.rel_path.ends_with('/') && abs.is_dir());
316 let honesty = if m.is_seed_reference {
317 "SEED/REFERENCE — not a certified foundation model. Do not use for production accuracy claims."
318 } else {
319 "User-supplied path slot — activate only licence-compatible weights you control."
320 };
321 let mut le = LibraryEntry {
322 asset_uri: uri.clone(),
323 primary_subject: primary_subject_for_uri(&uri),
324 media_type: MODEL_MEDIA_TYPE.into(),
325 quins: Vec::new(),
326 topics: vec![
327 "model".into(),
328 m.category.into(),
329 m.role.into(),
330 if m.is_seed_reference {
331 "seed-reference".into()
332 } else {
333 "user-slot".into()
334 },
335 ],
336 projects: vec![format!("perception:{}", m.category)],
337 purposes: vec!["model".into(), "software".into(), "perception".into()],
338 place: Some(abs.display().to_string()),
339 occurred_at: None,
340 lat: None,
341 lon: None,
342 flags: if m.is_seed_reference {
343 vec![super::hypermedia_store::LibraryFlag {
344 kind: "honesty:seed_reference".into(),
345 severity_level: 1,
346 detail: honesty.into(),
347 }]
348 } else {
349 Vec::new()
350 },
351 ingested_unix: now,
352 excerpt: format!(
353 "{} — role={} path={} on_disk={} — {}",
354 m.title, m.role, m.rel_path, on_disk, honesty
355 ),
356 sensitivity: "public".into(),
357 section: LibrarySection::Software.as_str().into(),
358 commons_visibility: CommonsVisibility::None,
359 cml_signals: Vec::new(),
360 cml_concept_count: 0,
361 cml_n3: String::new(),
362 cof_html: String::new(),
363 cof_segment_count: 0,
364 cof_segment_index: 0,
365 cof_profile: String::new(),
366 };
367 le.recompute_section();
368 le
369}
370
371fn ontology_to_library(o: OntologyCatalogEntry, now: u64) -> LibraryEntry {
372 let uri = format!("ontology://webizen/{}", o.id);
373 let mut le = LibraryEntry {
374 asset_uri: uri.clone(),
375 primary_subject: primary_subject_for_uri(&uri),
376 media_type: ONTOLOGY_MEDIA_TYPE.into(),
377 quins: Vec::new(),
378 topics: vec!["ontology".into(), o.domain.into(), o.id.into()],
379 projects: vec![format!("ontology:{}", o.domain)],
380 purposes: vec!["ontology".into(), "software".into(), "knowledge".into()],
381 place: Some(o.rel_path.into()),
382 occurred_at: None,
383 lat: None,
384 lon: None,
385 flags: Vec::new(),
386 ingested_unix: now,
387 excerpt: format!(
388 "{} — domain={} · {} · source {}",
389 o.title, o.domain, o.licence_note, o.rel_path
390 ),
391 sensitivity: "public".into(),
392 section: LibrarySection::Software.as_str().into(),
393 commons_visibility: CommonsVisibility::None,
394 cml_signals: Vec::new(),
395 cml_concept_count: 0,
396 cml_n3: String::new(),
397 cof_html: String::new(),
398 cof_segment_count: 0,
399 cof_segment_index: 0,
400 cof_profile: String::new(),
401 };
402 le.recompute_section();
403 le
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct PerceptionSeedReport {
408 pub models_added: usize,
409 pub models_updated: usize,
410 pub ontologies_added: usize,
411 pub ontologies_updated: usize,
412 pub weights_ensured: Vec<String>,
413 pub note: String,
414}
415
416pub fn seed_perception_into_library(
418 store: &HypermediaStore,
419 storage_root: &Path,
420) -> Result<PerceptionSeedReport, String> {
421 let mut weights_ensured = Vec::new();
422 if let Ok(s) = crate::audio_pipeline::ensure_aed_weights(storage_root) {
423 weights_ensured.push(s);
424 }
425 if let Ok(s) = crate::audio_pipeline::ensure_speech_weights(storage_root) {
426 weights_ensured.push(s);
427 }
428 if let Ok(s) = crate::vision_pipeline::ensure_vision_weights(storage_root) {
429 weights_ensured.push(s);
430 }
431
432 let now = now_unix();
433 let mut entries = store.load().map_err(|e| e.to_string())?;
434 let mut by_uri: std::collections::HashMap<String, usize> = entries
435 .iter()
436 .enumerate()
437 .map(|(i, e)| (e.asset_uri.clone(), i))
438 .collect();
439
440 let mut models_added = 0usize;
441 let mut models_updated = 0usize;
442 for row in PERCEPTION_MODEL_CATALOG {
443 let mut entry = model_to_library(*row, storage_root, now);
444 let uri = entry.asset_uri.clone();
445 if let Some(&idx) = by_uri.get(&uri) {
446 entry.ingested_unix = entries[idx].ingested_unix;
447 entries[idx] = entry;
448 models_updated += 1;
449 } else {
450 by_uri.insert(uri, entries.len());
451 entries.push(entry);
452 models_added += 1;
453 }
454 }
455
456 let mut ontologies_added = 0usize;
457 let mut ontologies_updated = 0usize;
458 for row in PERCEPTION_ONTOLOGY_CATALOG {
459 let mut entry = ontology_to_library(*row, now);
460 let uri = entry.asset_uri.clone();
461 if let Some(&idx) = by_uri.get(&uri) {
462 entry.ingested_unix = entries[idx].ingested_unix;
463 entries[idx] = entry;
464 ontologies_updated += 1;
465 } else {
466 by_uri.insert(uri, entries.len());
467 entries.push(entry);
468 ontologies_added += 1;
469 }
470 }
471
472 store.replace_all(&entries).map_err(|e| e.to_string())?;
473
474 Ok(PerceptionSeedReport {
475 models_added,
476 models_updated,
477 ontologies_added,
478 ontologies_updated,
479 weights_ensured,
480 note: "Models and ontologies catalogued in Library → Software with honesty flags on seed weights. Not foundation models.".into(),
481 })
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn catalogs_nonempty() {
490 assert!(!PERCEPTION_MODEL_CATALOG.is_empty());
491 assert!(PERCEPTION_ONTOLOGY_CATALOG.len() >= 10);
492 for m in PERCEPTION_MODEL_CATALOG {
493 assert!(!m.id.is_empty());
494 }
495 }
496
497 #[test]
498 fn seed_idempotent() {
499 let dir = tempfile::tempdir().unwrap();
500 let store = HypermediaStore::open(dir.path()).unwrap();
501 let r1 = seed_perception_into_library(&store, dir.path()).unwrap();
502 assert!(r1.models_added + r1.ontologies_added > 0);
503 let r2 = seed_perception_into_library(&store, dir.path()).unwrap();
504 assert_eq!(r2.models_added, 0);
505 assert_eq!(r2.ontologies_added, 0);
506 assert!(r2.models_updated >= PERCEPTION_MODEL_CATALOG.len());
507 let software = store.by_section(LibrarySection::Software).unwrap();
508 assert!(software.iter().any(|e| e.asset_uri.starts_with("model://")));
509 assert!(software
510 .iter()
511 .any(|e| e.asset_uri.starts_with("ontology://")));
512 }
513
514 #[test]
515 fn test_vision_catalog_contains_permissive_models() {
516 let model_ids: Vec<&str> = PERCEPTION_MODEL_CATALOG.iter().map(|m| m.id).collect();
517
518 assert!(model_ids.contains(&"vision-yunet-face"));
519 assert!(model_ids.contains(&"vision-sface-embed"));
520 assert!(model_ids.contains(&"vision-mediapipe-facemesh"));
521 assert!(model_ids.contains(&"vision-midas-depth"));
522 assert!(model_ids.contains(&"vision-espcn-sr"));
523 assert!(model_ids.contains(&"vision-yolonas-object"));
524 }
525}