--- 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`.