feat(nouser): hidratación del daemon vía sled + path_hint

El daemon ya no recomputa ciegamente al arrancar. Si la DB tiene
Mónadas previas con centroid_model válido, las publica instantáneo
y el re-scan reusa sus IDs vía path_hint.

Schema:
- MonadManifest.path_hint: Option<String> — identidad estable
  derivada del origen (para by_directory, el parent dir canónico).
  Permite reusar ULID across re-scans.

Cluster:
- Nueva fn cluster::by_directory_hydrated(files, min_files, prior).
  Con prior, busca Mónada con mismo path_hint Y mismo centroid_model;
  si la encuentra, reusa id, lineage y created_at_ms.
- by_directory queda como wrapper sin hidratación (back-compat).

Daemon (cmd_daemon):
1. Open sled si NOUSER_DB_PATH existe.
2. Publica Mónadas previas con centroid_model válido (las inválidas
   se descartan con log explícito).
3. Re-scan + by_directory_hydrated(prior=&db).
4. Sólo spawnea sidecars para Mónadas con id NUEVO. Los path_hints
   existentes preservan identidad, evitando duplicados en el broker.
5. Persiste el set actualizado.

Validación:
  $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core
  # arranque 1: re-scan 102 archivos → 5 mónadas (5 nuevas)
  $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core
  # arranque 2: hidratadas 5 mónadas en O(1)
  #             re-scan → 5 mónadas (0 nuevas vs hidratación)

Costo del arranque 2: ~0.06s user CPU.

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:55:05 +00:00
parent 820a1a33bf
commit 65af98da13
4 changed files with 158 additions and 17 deletions
+11
View File
@@ -134,6 +134,16 @@ pub struct MonadManifest {
#[serde(default)]
pub centroid_model: Option<String>,
/// Identidad estable derivada del origen de los miembros. Para
/// Mónadas creadas por `cluster::by_directory`, es el path
/// canónico del directorio padre. Permite que la hidratación
/// reuse el mismo ULID across re-scans (mismo path_hint = misma
/// identidad, aunque cambien los miembros internamente).
/// `None` para Mónadas creadas por estrategias que no se anclan a
/// un origen físico.
#[serde(default)]
pub path_hint: Option<String>,
/// Tokens dominantes: extensiones, palabras clave, etc.
/// 5-10 elementos típicamente.
#[serde(default)]
@@ -213,6 +223,7 @@ impl MonadManifest {
summary: String::new(),
centroid: Vec::new(),
centroid_model: None,
path_hint: None,
keywords: Vec::new(),
cardinality: 0,
entropy: 0.0,