Files
fn_registry/bash/functions/infra/audit_doctor_snapshot.sh
egutierrez 6aec0413bb feat(infra): auto-commit con 6 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 16:16:36 +02:00

170 lines
5.5 KiB
Bash

#!/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