Fase 5b del issue 0172. Backend stdlib http (solo 127.0.0.1) que orquesta
las funciones del grupo obsidian del fn_registry para servir el vault OSINT:
grafo agregado (/api/graph), tablas por tipo (/api/nodes), fichas con
attachments (/api/node, /api/attachment con bloqueo de path traversal) y
busqueda (/api/search). Cache en memoria con POST /api/refresh.
Tests pytest (10) sobre vault fixture: grafo golden, tipo filtrado, ficha
con attachments, wikilink dangling, slug con acentos, traversal bloqueado,
vault inexistente (exit 2) y e2e HTTP en puerto efimero. Frontend (React +
Vite + Mantine + sigma.js) queda para la fase siguiente.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
App web local para explorar el vault OSINT de Obsidian: grafo sigma.js, tablas por tipo y fichas con galería de attachments. Backend Python stdlib que orquesta el grupo obsidian; escucha solo en 127.0.0.1 (datos sensibles).
App del issue 0172 (project osint). Lee directamente los .md del vault de
Obsidian ~/Obsidian/osint (sin BD intermedia — decisión KISS) y ofrece tres
vistas: grafo explorable (sigma.js), tablas filtradas por tipo y fichas con la
galería de attachments de cada nodo.
Registry-first: el backend NO parsea el vault — orquesta las funciones del
grupo de capacidad obsidian (build_obsidian_graph, read_obsidian_note,
resolve_obsidian_embed, ...) declaradas en uses_functions.
Arrancar el backend
cd projects/osint/apps/osint_web
../../../../python/.venv/bin/python3 server/main.py --vault ~/Obsidian/osint --port 8470
El servidor cachea el grafo en memoria al arrancar; POST /api/refresh
re-escanea el vault bajo demanda (botón "refrescar" del frontend).
Endpoints
Método
Ruta
Devuelve
GET
/api/health
estado + nº de nodos/aristas cacheados
GET
/api/graph
grafo completo {nodes, edges} para sigma.js
GET
/api/nodes?tipo=persona
filas de la tabla de ese tipo (id, label, tipo, frontmatter)
GET
/api/node/<slug>
ficha: frontmatter + body Markdown + attachments + wikilinks
GET
/api/attachment?path=<rel>
binario del attachment (path relativo al vault, allowlist)
GET
/api/search?q=...
nodos cuyo contenido matchea la query
POST
/api/refresh
re-escanea el vault y reconstruye la caché
Seguridad
El vault contiene datos personales sensibles (DNIs, fotos): el server escucha
solo en 127.0.0.1 — no hay flag para exponerlo a red y NO es un service
desplegable a VPS (sin tag service).
/api/attachment bloquea path traversal: realpath del candidato debe quedar
estrictamente bajo el realpath del vault; cualquier otro caso → 403.
Vault inexistente al arrancar → error claro en stderr + exit 2 (nunca 500
silencioso).
Tests
cd projects/osint/apps/osint_web
../../../../python/.venv/bin/python3 -m pytest server -q
Cubren el DoD backend del issue 0172: grafo golden, tabla por tipo, ficha con
attachments, wikilink dangling (nodo fantasma), slug con acentos
([[María del Mar Pérez]] → maria-del-mar-perez), path traversal bloqueado,
vault inexistente y un e2e HTTP contra el server real en puerto efímero.
Estado / pendiente
Hecho (fase 5b): scaffold del sub-repo + backend completo con tests.
Pendiente (fase siguiente): frontend/ React + Vite + Mantine v9 +
@fn_library con sigma.js + graphology (GraphView, TablesView, NodeCard).
Onboarding previsto: pnpm dev en frontend/ + backend en 8470 → abrir
http://127.0.0.1:5173.
Cuando exista el manifest de sub-repos del project (issue 0171), añadir esta
app a projects/osint/subrepos.yaml.