---
name: audit_doctor_snapshot
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "audit_doctor_snapshot(doctor_subcommand: string, snapshot_base_dir: string) -> void"
description: "Ejecuta un subcomando de fn doctor --json, guarda un snapshot JSON fechado en //.json, lo compara con la corrida anterior (latest.json) y emite a stdout un resumen legible: count actual, count previo, IDs nuevos y resueltos. Pieza de observabilidad Nivel 1 para DAGs de auditoría periódica."
tags: [audit, registry, infra, doctor, snapshot, diff, dag]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: doctor_subcommand
desc: "Subcomando de fn doctor a ejecutar (unused, capabilities, artefacts, copied-code, uses-functions, cpp-apps, services, sync, etc.)."
- name: snapshot_base_dir
desc: "Directorio base donde se crea la carpeta // con los snapshots fechados y latest.json."
output: "Resumen a stdout: '[audit:] count=N prev=M +X new -Y resolved'. Si hay IDs nuevos/resueltos, líneas adicionales NEW:/RESOLVED: con hasta 8 IDs. Snapshots JSON en disco."
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/audit_doctor_snapshot.sh"
---
## Ejemplo
```bash
# Primera corrida — establece baseline
FN_REGISTRY_ROOT=/home/enmanuel/fn_registry \
FN_BIN=/home/enmanuel/fn_registry/fn \
bash bash/functions/infra/audit_doctor_snapshot.sh \
unused \
/home/enmanuel/fn_registry/apps/dag_engine/local_files/audits/daily
# => [audit:unused] count=12 prev=- baseline (sin corrida previa)
# Segunda corrida — compara contra latest.json
FN_REGISTRY_ROOT=/home/enmanuel/fn_registry \
FN_BIN=/home/enmanuel/fn_registry/fn \
bash bash/functions/infra/audit_doctor_snapshot.sh \
unused \
/home/enmanuel/fn_registry/apps/dag_engine/local_files/audits/daily
# => [audit:unused] count=12 prev=12 +0 new -0 resolved
# Con otro subcomando (directorio independiente automático)
audit_doctor_snapshot artefacts /tmp/audits/weekly
```
## Cuando usarla
Úsala en un DAG/cron que ejecuta `fn doctor` periódicamente y quieres **persistir el resultado y ver qué cambió desde la última corrida**: funciones huérfanas que aparecieron, artefactos rotos nuevos, capabilities sin doc, etc. Es la pieza "snapshot + diff" del Nivel 1 de observabilidad de auditorías — el DAG llama esta función en vez de descartar el output de `fn doctor`.
## Gotchas
- **Depende de `FN_BIN` o `FN_REGISTRY_ROOT`** en el entorno. Si ninguno está seteado, asume `$HOME/fn_registry/fn`. En DAGs, asegúrate de exportar `FN_REGISTRY_ROOT` antes de invocar.
- **`latest.json` se sobreescribe cada corrida** — es el snapshot de referencia para el diff siguiente. No es un historial acumulado; el historial está en los archivos fechados `.json`.
- **Si cambias de subcomando, el subdirectorio es distinto** (`/unused/` vs `/artefacts/`), así que no hay contaminación entre subcomandos aunque compartan el mismo `base_dir`.
- **Si `fn doctor ` falla (rc != 0)**, la función propaga ese exit code. Esto es intencional: doctor roto = problema real que el DAG debe reportar. Los hallazgos normales (funciones huérfanas, artefactos con drift) tienen rc=0 en `fn doctor`.
- **jq es dependencia requerida**. Está disponible en el ecosistema del registry pero si el entorno no lo tiene, los conteos y diffs de IDs caen a `?`/textual respectivamente.
- **Retención automática**: snapshots fechados con más de 30 días se borran con `find -mtime +30`. `latest.json` nunca se borra.
- **Estructura del JSON de `fn doctor`**: el diff de IDs busca campos `.ID` o `.id` en los elementos. Si el subcomando produce una estructura distinta (objeto anidado sin esos campos), el diff cae a comparación textual, que sigue siendo útil.
## Notas
Diseñada para ser invocada desde steps del dag_engine (`daily-registry-audit`, `weekly-deep-scan`) como reemplazo del descarte silencioso del output de `fn doctor --json`. La salida stdout es legible por humanos y parseable por el orquestador del DAG para decidir si crear proposals.
Binario `fn` resuelto en orden: `$FN_BIN` → `${FN_REGISTRY_ROOT}/fn` → `$HOME/fn_registry/fn`.