Commit Graph

9 Commits

Author SHA1 Message Date
egutierrez 3063d3c44f feat(scans): persistencia de escaneos de red + POST /api/scan
Tabla network_scans (migración 005, schema main, lleva note_path) que otras
herramientas pueblan vía HTTP con escaneos de reconocimiento (whois/rdap/dns/
nmap/traceroute/ping). Endpoint POST /api/scan: id determinista
<target_slug>:<scan_type>:<YYYYMMDD-HHMM> derivado de scan_ts, idempotente por
id (duckdb_upsert ON CONFLICT DO UPDATE) bajo el lock single-writer del service.
summary (dict) se serializa a JSON.

network_scans no se deriva de notas: ni ingest_vault ni ingest_dav la tocan, así
que un re-ingest del vault no la trunca (test lo verifica).

Tests: inserción + id derivado, idempotencia mismo-minuto, validación de campos
requeridos (422), y no-truncado por ingest del vault.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:13:36 +02:00
egutierrez 9677903ca6 feat(org): contacto-empresa en la agenda con los telefonos de sus personas etiquetados
sync_org_contact_cards crea un contacto de agenda por organizacion (uid
org-<slug>); el push compone su vCard via _org_contact_vcard con un item.TEL +
item.X-ABLabel por persona de contacto (nombre + rol) desde derived.org_contacts.
Asi, al abrir la empresa en el movil, se ven todos los telefonos identificados por
persona. Sin campos OSINT (misma privacidad que el resto de la agenda). Nuevo
endpoint POST /api/org/sync-contact-cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:24:46 +02:00
egutierrez d53d7a9a7e fix(events): envolver VEVENT en VCALENDAR al push (Xandikos 500) + INSERT explicito en contacts (columna import_key)
El raw de un evento guardaba solo BEGIN:VEVENT...END:VEVENT; subirlo a CalDAV
genera un .ics invalido que rompe Xandikos (assert isinstance(cal, Calendar) ->
500 en todo el calendario). _ensure_vcalendar lo envuelve en el push. Ademas, la
columna import_key (migracion 004) rompia los INSERT posicionales de contacts:
ahora son explicitos por columna y el ingest puebla import_key con la funcion del
registry. Tests actualizados (4 derivadas, INSERT explicito).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:15:27 +02:00
egutierrez 36c4e06779 feat(org): derived.org_contacts + materializacion de contactos en la ficha de cada organizacion
Deriva del campo relaciones del frontmatter ("[[org-slug]] — rol") los telefonos
de las personas de contacto de cada organizacion (124 orgs, 185 pares) y los
expone en dos sitios: la tabla derived.org_contacts (consultable) y un bloque
sentinel 'org-contacts' en la ficha .md de cada organizacion (tabla persona/rol/
telefono). Nuevo endpoint POST /api/org/render-contacts. Asi, al buscar una
empresa, aparecen todos los telefonos de sus contactos.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:03:44 +02:00
egutierrez 058180ea1a feat: POST /api/push/dav-bulk — push masivo por disco + 1 commit (segundos vs minutos)
Vía rápida DB→Xandikos para operaciones masivas: genera todos los vCards de agenda desde
DuckDB a un tmpdir, rsync de golpe al working tree de la colección en magnus (excluyendo
.git/.xandikos), UN solo git commit, y 1 PROPFIND para capturar todos los etags en batch.
~0.5s vs ~6min del push HTTP (que hace N PUTs + N PROPFINDs + N commits). El push HTTP
push_all_dav se mantiene como fallback (y para CalDAV). Config DAV_BULK_SSH_HOST/REMOTE_DIR.
22 tests verdes.
2026-06-13 11:11:32 +02:00
egutierrez b620cc38c2 feat: push de agenda sin OSINT (compone persona enlazada) + sync inverso por etag
Privacidad (decisión del usuario: al móvil solo datos de agenda):
- _compose_agenda_vcard compone el vCard desde el contacto (fn/tels/emails) + las
  direcciones (ADR) y aliases (NICKNAME) de la persona enlazada por note_path, SIN pasar
  nunca el dict osint a build_vcard → el vCard jamás lleva X-OSINT-* (DNI/sexo/fecha-nac
  quedan solo en DuckDB+Obsidian). Usado en upsert_contact y en el push masivo push_all_dav
  (que antes leía solo contacts y perdía las direcciones).

Sync inverso DAVx5→DuckDB (last-write-wins por etag):
- Tras cada push se captura el etag nuevo del recurso (dav_list_resources) y se persiste en
  contacts.etag, para no confundir el push propio con una edición del móvil.
- POST /api/sync/dav-pull: pull incremental — compara etags, descarga SOLO los recursos
  cambiados/nuevos (dav_get_resource + parse_vcard + upsert), borra los que el móvil quitó,
  re-enlaza. Distinto del ingest_dav (DELETE+INSERT ciego): respeta la verdad de la DB salvo
  donde el etag prueba un cambio externo.

20 tests verdes (18 + 2 nuevos: vCard sin OSINT con direcciones; pull incremental por etag).
2026-06-13 10:53:23 +02:00
egutierrez 77728cda59 fix(security): TrustedHostMiddleware (anti DNS-rebinding) + escape iCal en _build_vcalendar
- TrustedHostMiddleware (allowed_hosts 127.0.0.1/localhost/testserver): cierra el vector
  por el que una web maliciosa rebindea su dominio a 127.0.0.1 y alcanza /api/query desde
  el navegador del usuario (el service no tiene auth por ser local).
- _build_vcalendar escapaba nada: UID/SUMMARY/LOCATION/RRULE crudos permitían iCal
  injection. Ahora _ical_escape (summary/location) + _ical_sanitize (uid/rrule, quita
  saltos de línea sin tocar los separadores legítimos de la regla).

Auditoría de seguridad: el fallo CRÍTICO (LFI/escritura via /api/query) se cierra con el
sandbox de duckdb_query_readonly en el registry; este commit cubre los hallazgos ALTA
(DNS-rebinding) y MEDIA (iCal injection).
2026-06-13 01:21:01 +02:00
egutierrez 63f37257cd feat: DuckDB como fuente de verdad (multi-valor, ownership selectivo, escritura, libretas)
F1 — migraciones: 002_multivalue (persons +telefonos/emails/direcciones/extra_fm JSON,
backfill desde singulares con to_json) + 003_addressbooks (tabla addressbooks + seed
idempotente de la libreta por defecto). Conteos intactos (697/1065/98).

F2 — ingest_vault selectivo (anti-pisado): personas que ya existen en DB solo actualizan
note_path + extra_fm vía duckdb_upsert(update_cols=...), NO pisan los campos OWNED por la
DB; personas nuevas = bootstrap completo. _link_contacts enlaza por listas telefonos[]/
emails[] además del singular. ingest_dav itera todas las libretas de la tabla addressbooks.

F3 — escritura estructurada (server/writes.py + endpoints en main.py): CRUD
/api/person|contact|event, /api/addressbook, /api/calendar, /api/person/{slug}/render
(DB→nota preservando la prosa del cuerpo), /api/push/dav (reconcilia DB→Xandikos). El push
DAV y el render ocurren fuera de la transacción de escritura para no bloquear la DB con
latencia de red. registry_bridge.py importa las funciones nuevas; app.md actualizado.

Verificado: 18 tests verdes; ownership probado sobre datos reales (un centinela DB-owned
sobrevivió a POST /api/ingest/vault sobre las 697 fichas); person CRUD + materialización
de la ficha .md en vivo, con cleanup sin residuo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 00:44:02 +02:00
agent 2716edd5a0 feat: initial scaffold of osint_db (DuckDB source-of-truth service) 2026-06-13 00:02:41 +02:00