El campo `notes` es lo que el usuario escribe en el panel Note del
Inspector (doble click sobre el nodo) — sitio canonico para texto
largo. Antes los enrichers leian metadata.text/description/query como
prioridad, dejando notes ignorado y forzando al usuario a inyectar
texto via la UI metadata-extra (poco descubrible).
Cambios:
- Ambos run.py abren la BD y leen `entities.notes` por SQL antes de
fallback a node_name. metadata.text/description/query ya no se
consultan (KISS — solo notes y name).
- conftest.make_node admite kwarg `notes` para inyectar contenido
en la columna notes desde tests.
- Tests actualizados: SAMPLE_TEXT y los IoC dumps van por `notes=`
en lugar de `metadata={"text": ...}`.
- Renombrado el test que verificaba prioridad: ahora se llama
`*_uses_notes_priority` y verifica notes > name.
Tests verdes WSL (44) y Windows (33 + 11 skipped).
Para probar la app sin depender de red (DDG bloquea con captcha desde
ciertas IPs). Ambos aplican grouping (umbral 50, preview K=10) replicando
el patron de web_search.
- split_sentences: parte texto en frases (regex), crea nodos Sentence
conectados con SENTENCE_OF.
- extract_iocs_text: variante de extract_text_entities que lee directo
metadata.text/description/name, sin requerir fetch previo. Vendoriza
extract_iocs_py_cybersecurity. Multi-tipo, agrupado en un solo Group
heterogeneo (decision 6 multi-grupo-por-tipo es fase 2).
- Tipo Sentence en types.yaml.
Tests pytest cubren below/above threshold para ambos.
Cuando un enricher web_search produce >= 50 resultados, los primeros 10
quedan sueltos colgando del source (preview Twitter/Reddit) y los
restantes entran como hijos de un nuevo nodo Group cuadrado.
Cambios:
- enrichers/web_search/run.py:
- DEFAULT_GROUP_THRESHOLD=50, GROUP_PREVIEW_K=10 (constantes globales).
- has_group_id_column(): detecta si el schema soporta agrupacion.
- insert_group_entity(): crea nodo Group con metadata
{enricher, query, count, batch_id}.
- insert_url_entity() acepta batch_id y group_id; los inyecta en
metadata/columna respectivamente. Nodos existentes mantienen su
group_id actual (no se machaca).
- Generacion de batch_id (UUID4 hex) por ejecucion, compartido por
todos los nodos creados (group + sueltos + agrupados).
- Cada hijo del grupo conserva su relacion individual SEARCH_RESULT_OF
al source original — la procedencia es la relacion real, no el
contenedor.
- El JSON de salida añade batch_id, group_id, grouped.
- tests/conftest.py: añade columna entities.group_id al SCHEMA_SQL y
expone group_id en list_entities() para que los tests lo verifiquen.
- tests/test_web_search.py: 3 tests nuevos
- below_threshold_no_group: 5 resultados → 0 Groups, comportamiento clasico.
- above_threshold_creates_group_and_preview: 100 resultados → 1 Group +
10 sueltos + 90 con group_id, todos con SEARCH_RESULT_OF al source.
- batch_id_shared_across_outputs: group + preview + hijos comparten
batch_id.
- _build_lite_html() genera HTML sintetico con N resultados sin
necesidad de fixture estatico grande.
Tests: 35 passed (32 previos + 3 nuevos) en WSL.
24 passed + 11 skipped en Windows.
Refs: issues/0035c-web-search-creates-groups.md
Bloque de cambios revisados y validados con el usuario en sesiones
previas que no habian aterrizado en commits propios. Lista por tema:
* enrichers: web_search ahora usa lite.duckduckgo.com como endpoint
primario (mas tolerante con bot detection desde IP residencial),
con fallback al endpoint html. Detecta pagina captcha y emite
error claro si ambos fallan. Anyade _DDGLiteParser para el formato
lite + auto-pick de parser por contenido.
* enrichers: tipo Webpage unificado en Url (campos de cuerpo
cacheado viven en metadata del Url). Manifests actualizados
(applies_to: [Url]). fetch_webpage ya no convierte Url->Webpage.
* enrichers/manifest: campo `params` parseado a EnricherSpec.params
(name, type, default_value, description). UI puede renderizar
dialog de configuracion.
* jobs: fix de path conversion para Python embebido nativo Windows
(no convertir a /mnt/c/... cuando el subproceso es Windows-native;
solo cuando es bash o python via WSL).
* main.cpp: ventana ImGui (no modal) "Run enricher" con layout
2-col (label izq, input der). Inserta job con JSON tipado. Layout
clustering apretado: hijos del mismo anchor en un solo anillo
alrededor del padre, sin desperdigar por anillos crecientes.
* views: inspector con layout 2-col via BeginTable (Identity,
Schema fields, Extras). Description full-width debajo de su label.
* tests: portable conftest (auto-detecta REGISTRY_ROOT, PYTHON_BIN,
ENRICHERS_DIR para WSL y Windows portable). _runner.py trampoline
inyecta stub via sys.path porque embedded Python ignora PYTHONPATH.
Tests bash-only (vendor_script, freeze, dispatcher bash, resolver
Linux-binary) skipean en Windows. Tests existentes adaptados a
Webpage->Url.
Resultado actual: 32 passed WSL, 21 passed + 11 skipped Windows.
Cada enricher con `lang: python` y `uses_functions` no vacio ahora
puede empaquetar las funciones del registry que necesita en
`<enricher>/_vendored/`. El run.py importa de ahi en lugar de
`<registry_root>/python/functions/`, lo que hace al binario
distribuible sin dependencia de un fn_registry montado.
Cambios:
1. tools/vendor_enricher_python.sh
- Lee `uses_functions` del manifest (filtrando IDs `*_py_*`).
- Resuelve `file_path` desde registry.db.
- Copia recursivamente con expansion transitiva: si un fichero
vendorizado importa siblings del mismo dominio, los siblings
tambien se copian (resuelve el caso `extract_iocs.py` que
importa 7 modulos hermanos).
- Genera `.vendor.lock` con `<id> <sha256> <src_path>` por
funcion declarada para auditoria.
- Idempotente — si todos los hashes coinciden, no rehace nada.
2. Manifests actualizados con `uses_functions`:
- fetch_webpage: normalize_url + html_to_markdown
- extract_links: extract_urls
- extract_text_entities: extract_iocs
3. run.py de los 3 enrichers afectados: importan de `_vendored/`
si existe, fallback a `<registry_root>/python/functions/` en
modo dev (mantiene los tests pytest funcionando).
4. app.md: anade `cryptography` a python_runtime_deps porque el
blob `cybersecurity.cybersecurity` lo importa al top.
5. Tests:
- test_vendor_script.py — 6 tests del script: layout correcto,
transitive siblings, lock con SHA256, idempotencia, modulos
importables en aislamiento.
- 16 tests de enrichers existentes pasan via vendoring (no usan
registry_root porque _vendored/ tiene prioridad).
6. Issue 0033b movido a issues/completed/.
Tests: 32/32 verde (16 enrichers + 6 dispatcher + 4 runtime + 6
vendor).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Anade enricher web_search aplicable a nodos text/Concept/Topic. Hace
POST a html.duckduckgo.com con la query del nodo, parsea resultados
con HTMLParser stdlib, decodifica el redirect uddg= y crea N nodos
Url con relacion SEARCH_RESULT_OF apuntando al nodo origen.
Encadenable: tras web_search, fetch_webpage sobre cada Url completa
el pipeline search -> fetch -> extract.
Defensa contra ops_db_path mal resuelto: normaliza backslashes,
resuelve relativo contra app_dir, valida que la tabla entities
exista antes de tocar nada (exit codes 7/8/9 con JSON resumen).
Tests pytest (16/16 verde): conftest con operations.db temp +
schema minimo, stub de requests via PYTHONPATH para mockear red.
Cubre los 5 enrichers (extract_domain, fetch_webpage, extract_links,
extract_text_entities, web_search) + sanity check de manifests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>