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