feat(nouser): centroid_model — versionado de embeddings

Protege contra el bug silencioso de mezclar centroides de modelos
distintos (mock 32-d vs real 384-d), que daría scores sin sentido.

- MonadManifest.centroid_model: Option<String>. None = legacy.
- nouser_core::embed::MODEL_ID = "nouser-pseudo-32d". Cluster lo
  setea en cada Mónada que genera.
- nouser-nous-mock reusa la misma constante (use
  nouser_core::embed::MODEL_ID): produce vectores idénticos al
  cluster local, reportar el mismo ID es honesto.
- nouser-nous-real ya reportaba "real-fastembed-allMiniLML6V2-384d";
  el filter ahora lo descarta automáticamente cuando los centroides
  cacheados son del mock.
- cmd_attract:
  - Captura el model_id del embedding del target.
  - Filtra Mónadas cuyo centroid_model no matchee.
  - Reporta "embed: <source> (<model>)" y "skipped: N" cuando
    descarta.

Resultado: cambiar de mock a real vía BRAHMAN_BROKER_CONTEXT=prod
hace que attract filtre las Mónadas viejas con cero score en lugar
de fingir que las puede comparar.

Tests: 7 (card) + 24 (core) verdes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-05-09 00:24:38 +00:00
parent 9c371ee43e
commit 820a1a33bf
6 changed files with 89 additions and 17 deletions
+7
View File
@@ -32,6 +32,13 @@ use nouser_card::{FileEntry, MonadId, MonadManifest};
/// Dimensión del vector embedding.
pub const EMBED_DIM: usize = 32;
/// Identificador del modelo que produce este embedding. Se usa para
/// taggear `MonadManifest.centroid_model`: los consumidores comparan
/// este string contra el suyo antes de hacer cosine similarity.
/// Mezclar centroides de distinto MODEL_ID corrompe scores
/// silenciosamente (dimensiones distintas, semántica distinta).
pub const MODEL_ID: &str = "nouser-pseudo-32d";
/// Computa el embedding de un archivo. Determinístico: misma input
/// → mismo vector. El vector queda L2-normalizado.
pub fn embed(file: &FileEntry) -> [f32; EMBED_DIM] {