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>
This commit is contained in:
2026-06-14 13:13:36 +02:00
parent 9677903ca6
commit 3063d3c44f
5 changed files with 277 additions and 1 deletions
+6 -1
View File
@@ -82,7 +82,11 @@ en el body (el plugin parsea el body, no el código HTTP).
Xandikos — fuente de verdad del lado agenda/calendario. `contacts.note_path`
se enlaza contra `persons` matcheando por UID `osint-<slug>`, por el
`dav_uid` extraído del campo `fuente` de la ficha, por teléfono normalizado
o por email.
o por email. `network_scans` (schema `main`) registra escaneos de red
(whois/rdap/dns/nmap/traceroute/ping) que otras herramientas guardan vía
`POST /api/scan`; lleva `note_path` (la nota `dominios/<slug>/recon/<...>.md`
donde se documenta cada escaneo) y NO se reconstruye en ningún ingest — la
pueblan los escaneos y nunca se trunca.
3. **Derivadas** (schema `derived`): SOLO datos computados. **Regla dura:
ninguna tabla de `derived` lleva columna que referencie notas** (`note_path`
prohibido ahí; hay un test que lo verifica vía `information_schema`). Se
@@ -131,6 +135,7 @@ Health check: `curl http://127.0.0.1:8771/api/health`.
| POST/PUT/DELETE | `/api/event[/{uid}]` | CRUD de eventos CalDAV. Push `caldav_put_event`/`dav_delete_resource` |
| POST | `/api/addressbook` | `{slug, display_name?, description?, color?}``dav_make_addressbook` + INSERT en `addressbooks` |
| POST | `/api/calendar` | `{slug, display_name?, color?}``dav_make_calendar` (paridad) |
| POST | `/api/scan` | `{target, target_slug, scan_type, note_path, tool?, summary?, scan_ts?}` → registra un escaneo de red en `network_scans`. `id = <target_slug>:<scan_type>:<YYYYMMDD-HHMM>` derivado de `scan_ts` (default: ahora); idempotente por id (upsert). Responde `{"status":"ok","id":<id>}` |
| POST | `/api/push/dav` | reconcilia en bloque por HTTP: recorre `contacts` y `events` de la DB y los empuja a Xandikos (1 PUT + 1 PROPFIND + 1 commit git por recurso, sin borrar). Fallback cuando no hay SSH al host de Xandikos |
| POST | `/api/push/dav-bulk` | vía RÁPIDA del push de **contactos** por DISCO: genera todos los `.vcf` en un tmpdir local y hace **1 rsync + 1 commit + 1 PROPFIND** contra el working tree git que Xandikos sirve. Reconcilia ~1000 contactos en <1s en vez de ~6 min. Requiere SSH por clave |