Reemplaza el patron N+1 (PROPFIND + un GET por cada .vcf/.ics, paralelizado con
ThreadPoolExecutor pero ~9s para 1064 contactos) por UNA llamada a la funcion
del registry dav_get_collection (REPORT addressbook-query / calendar-query que
trae todo el contenido inline). Anade cache en disco (.cache/contacts.json y
calendar.json) validada por el ctag de la coleccion (dav_collection_ctag): al
arrancar, si el ctag no cambio, sirve del disco sin tocar la red. POST
/api/refresh fuerza recarga (ignora el ctag). _force_reload distingue refresh
forzado de validacion normal.
Cambios en /api/contacts y /api/calendar: el N+1 (_fetch_resources +
ThreadPoolExecutor) se sustituye por _load_collection (ctag -> disco o REPORT).
El parseo vCard/iCal y el shape JSON no cambian; los items cacheados en disco
preservan el shape completo (osint, nombre, telefonos). Tests actualizados:
fake_dav mockea dav_get_collection + dav_collection_ctag con cache en tmpdir;
nuevos tests de disk-cache-hit en proceso nuevo y recarga al cambiar ctag.
Medido contra Xandikos real: contacts 1064 cold 1.15s / warm 6ms / disco 0.5s;
calendar 98 cold 0.29s. Registry-first: la logica DAV nueva vive en el grupo dav
(dav_get_collection_py_infra + dav_collection_ctag_py_infra), no inline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>