6aec0413bb
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
170 lines
5.5 KiB
Bash
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
|