feat(infra): auto-commit con 6 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
---
|
||||
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 <base>/<sub>/<stamp>.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 <base>/<subcommand>/ con los snapshots fechados y latest.json."
|
||||
output: "Resumen a stdout: '[audit:<sub>] 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 `<stamp>.json`.
|
||||
- **Si cambias de subcomando, el subdirectorio es distinto** (`<base>/unused/` vs `<base>/artefacts/`), así que no hay contaminación entre subcomandos aunque compartan el mismo `base_dir`.
|
||||
- **Si `fn doctor <sub>` 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`.
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# audit_doctor_snapshot — ejecuta un subcomando de fn doctor, guarda snapshot JSON
|
||||
# fechado, compara con la corrida anterior y emite resumen legible de cambios.
|
||||
#
|
||||
# Uso: audit_doctor_snapshot <doctor_subcommand> <snapshot_base_dir>
|
||||
#
|
||||
# Ejemplo:
|
||||
# audit_doctor_snapshot unused /home/enmanuel/fn_registry/apps/dag_engine/local_files/audits/daily
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
audit_doctor_snapshot() {
|
||||
local sub="${1:-}"
|
||||
local base="${2:-}"
|
||||
|
||||
# --- validacion de argumentos ---
|
||||
if [[ -z "$sub" || -z "$base" ]]; then
|
||||
echo "usage: audit_doctor_snapshot <subcommand> <base_dir>" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# --- resolver binario fn ---
|
||||
local fn_bin="${FN_BIN:-${FN_REGISTRY_ROOT:-$HOME/fn_registry}/fn}"
|
||||
if [[ ! -x "$fn_bin" ]]; then
|
||||
echo "audit_doctor_snapshot: binario fn no encontrado o no ejecutable: $fn_bin" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# --- preparar directorio ---
|
||||
local dir="$base/$sub"
|
||||
mkdir -p "$dir"
|
||||
|
||||
# --- ejecutar fn doctor ---
|
||||
local stderr_tmp
|
||||
stderr_tmp="$(mktemp /tmp/audit_doctor_snapshot_stderr.XXXXXX)"
|
||||
local json rc
|
||||
json="$("$fn_bin" doctor "$sub" --json 2>"$stderr_tmp")" || rc=$?
|
||||
rc="${rc:-0}"
|
||||
|
||||
if [[ "$rc" -ne 0 ]]; then
|
||||
cat "$stderr_tmp" >&2
|
||||
echo "audit_doctor_snapshot: 'fn doctor $sub' fallo (rc=$rc)" >&2
|
||||
rm -f "$stderr_tmp"
|
||||
return "$rc"
|
||||
fi
|
||||
rm -f "$stderr_tmp"
|
||||
|
||||
# --- normalizar con jq (diff estable) ---
|
||||
local stamp
|
||||
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
local curr="$dir/${stamp}.json"
|
||||
local nojson=0
|
||||
|
||||
if ! echo "$json" | jq -S . > "$curr" 2>/dev/null; then
|
||||
# salida no es JSON valido -> guardar crudo
|
||||
printf '%s' "$json" > "$curr"
|
||||
nojson=1
|
||||
fi
|
||||
|
||||
# --- snapshot anterior ---
|
||||
local prev="$dir/latest.json"
|
||||
|
||||
# --- contar hallazgos actuales ---
|
||||
local count="?"
|
||||
if [[ "$nojson" -eq 0 ]]; then
|
||||
if jq -e 'type == "array"' "$curr" >/dev/null 2>&1; then
|
||||
count="$(jq 'length' "$curr")"
|
||||
elif jq -e 'type == "object"' "$curr" >/dev/null 2>&1; then
|
||||
count="$(jq 'keys | length' "$curr")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- contar hallazgos previos ---
|
||||
local prevcount="-"
|
||||
if [[ -f "$prev" ]]; then
|
||||
if jq -e 'type == "array"' "$prev" >/dev/null 2>&1; then
|
||||
prevcount="$(jq 'length' "$prev")"
|
||||
elif jq -e 'type == "object"' "$prev" >/dev/null 2>&1; then
|
||||
prevcount="$(jq 'keys | length' "$prev")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- diff de identidad ---
|
||||
local new_count=0
|
||||
local resolved_count=0
|
||||
local new_ids=()
|
||||
local resolved_ids=()
|
||||
local diff_label=""
|
||||
|
||||
if [[ ! -f "$prev" ]]; then
|
||||
diff_label="baseline (sin corrida previa)"
|
||||
elif [[ "$nojson" -eq 1 ]]; then
|
||||
if ! diff -q "$prev" "$curr" >/dev/null 2>&1; then
|
||||
diff_label="changed (textual)"
|
||||
else
|
||||
diff_label="+0 new -0 resolved"
|
||||
fi
|
||||
else
|
||||
# extraer IDs estables: .ID o .id
|
||||
local curr_ids prev_ids
|
||||
curr_ids="$(jq -r 'if type=="array" then .[].ID // .[].id // empty else to_entries[].value.ID // to_entries[].value.id // empty end' "$curr" 2>/dev/null | sort -u)"
|
||||
prev_ids="$(jq -r 'if type=="array" then .[].ID // .[].id // empty else to_entries[].value.ID // to_entries[].value.id // empty end' "$prev" 2>/dev/null | sort -u)"
|
||||
|
||||
if [[ -n "$curr_ids" || -n "$prev_ids" ]]; then
|
||||
# NEW: en curr pero no en prev
|
||||
local new_raw resolved_raw
|
||||
new_raw="$(comm -23 <(echo "$curr_ids") <(echo "$prev_ids") 2>/dev/null || true)"
|
||||
resolved_raw="$(comm -13 <(echo "$curr_ids") <(echo "$prev_ids") 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$new_raw" ]]; then
|
||||
mapfile -t new_ids <<< "$new_raw"
|
||||
fi
|
||||
if [[ -n "$resolved_raw" ]]; then
|
||||
mapfile -t resolved_ids <<< "$resolved_raw"
|
||||
fi
|
||||
|
||||
new_count="${#new_ids[@]}"
|
||||
resolved_count="${#resolved_ids[@]}"
|
||||
diff_label="+${new_count} new -${resolved_count} resolved"
|
||||
else
|
||||
# sin campo .ID/.id — fallback textual
|
||||
if ! diff -q "$prev" "$curr" >/dev/null 2>&1; then
|
||||
diff_label="changed (textual)"
|
||||
else
|
||||
diff_label="+0 new -0 resolved"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- resumen a stdout ---
|
||||
echo "[audit:$sub] count=$count prev=$prevcount $diff_label"
|
||||
|
||||
# listar nuevos (max 8)
|
||||
if [[ "${#new_ids[@]}" -gt 0 ]]; then
|
||||
local listed=("${new_ids[@]:0:8}")
|
||||
local extra=$(( ${#new_ids[@]} - 8 ))
|
||||
local line
|
||||
line="$(IFS=', '; echo "${listed[*]}")"
|
||||
if [[ "$extra" -gt 0 ]]; then
|
||||
line="${line} (+${extra} más)"
|
||||
fi
|
||||
echo " NEW: $line"
|
||||
fi
|
||||
|
||||
# listar resueltos (max 8)
|
||||
if [[ "${#resolved_ids[@]}" -gt 0 ]]; then
|
||||
local listed_r=("${resolved_ids[@]:0:8}")
|
||||
local extra_r=$(( ${#resolved_ids[@]} - 8 ))
|
||||
local line_r
|
||||
line_r="$(IFS=', '; echo "${listed_r[*]}")"
|
||||
if [[ "$extra_r" -gt 0 ]]; then
|
||||
line_r="${line_r} (+${extra_r} más)"
|
||||
fi
|
||||
echo " RESOLVED: $line_r"
|
||||
fi
|
||||
|
||||
# --- actualizar puntero latest ---
|
||||
cp "$curr" "$prev"
|
||||
|
||||
# --- retención: borrar snapshots fechados > 30 días ---
|
||||
find "$dir" -maxdepth 1 -name '*.json' ! -name 'latest.json' -mtime +30 -delete 2>/dev/null || true
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Permitir ejecución directa
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
audit_doctor_snapshot "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user