feat(agents): hook Stop anti-fabrication verify-rla-api (gate deploiement + preuve pdftotext)
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# .agents/verify-rla-api.sh
|
||||||
|
# Hook Stop Antigravity pour rla-api.
|
||||||
|
# Bloque la fin de tour tant que l'appli n'est pas reellement verifiee (pas juste racontee).
|
||||||
|
# Ecrit toujours une preuve brute horodatee dans .agents/last-verify.log,
|
||||||
|
# lisible directement par Claude sans reconstruire toute la chaine de verification.
|
||||||
|
#
|
||||||
|
# Contrat Stop hook Antigravity : stdout DOIT etre un JSON avec "decision".
|
||||||
|
# "continue" = force l'agent a reprendre la main (echec, "reason" est injecte comme message systeme).
|
||||||
|
# Toute autre valeur = laisse terminer normalement.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
LOGDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
LOGFILE="$LOGDIR/last-verify.log"
|
||||||
|
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||||
|
FAIL=0
|
||||||
|
REASONS=()
|
||||||
|
|
||||||
|
run() { ssh nas "$1" 2>&1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "=== Verification rla-api - $TS ==="
|
||||||
|
|
||||||
|
echo "-- 1. Etat conteneur --"
|
||||||
|
STATUS="$(run "docker ps --filter name=rla-api --format '{{.Status}}'")"
|
||||||
|
echo "$STATUS"
|
||||||
|
if [[ "$STATUS" != *"healthy"* ]]; then
|
||||||
|
FAIL=1; REASONS+=("conteneur rla-api pas healthy (etat observe: ${STATUS:-aucun conteneur trouve})")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "-- 2. .env interne au conteneur == .env compose (piege dotenv override deja rencontre) --"
|
||||||
|
ENV_COMPOSE="$(run "grep -E '^BASEROW_API_URL=' /volume1/docker/rla-api/.env")"
|
||||||
|
ENV_INTERNE="$(run "docker exec rla-api cat /app/.env 2>/dev/null | grep -E '^BASEROW_API_URL='")"
|
||||||
|
echo "compose: $ENV_COMPOSE"
|
||||||
|
echo "interne: $ENV_INTERNE"
|
||||||
|
if [[ -z "$ENV_INTERNE" || "$ENV_COMPOSE" != "$ENV_INTERNE" ]]; then
|
||||||
|
FAIL=1; REASONS+=(".env interne absent ou different du .env compose (cf runbook pdfkit/dotenv, meme classe de piege)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "-- 3. Auth + endpoint /api/synthese repond avec des donnees --"
|
||||||
|
JWT_SECRET="$(run "grep -E '^JWT_SECRET=' /volume1/docker/rla-api/.env | cut -d= -f2-")"
|
||||||
|
TOKEN=""
|
||||||
|
if [[ -n "$JWT_SECRET" ]]; then
|
||||||
|
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
|
||||||
|
HEADER=$(printf '{"alg":"HS256","typ":"JWT"}' | b64url)
|
||||||
|
EXP=$(( $(date +%s) + 600 ))
|
||||||
|
PAYLOAD=$(printf '{"id":1,"username":"nabil","role":"superadmin","region":"all","exp":%d}' "$EXP" | b64url)
|
||||||
|
SIG=$(printf '%s' "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -hmac "$JWT_SECRET" -binary | b64url)
|
||||||
|
TOKEN="${HEADER}.${PAYLOAD}.${SIG}"
|
||||||
|
API_OUT="$(run "curl -s --max-time 10 -H 'Authorization: Bearer $TOKEN' http://172.17.0.1:3005/api/synthese")"
|
||||||
|
echo "${API_OUT:0:400}"
|
||||||
|
API_COUNT="$(printf '%s' "$API_OUT" | grep -o '\"count\":[0-9]*' | head -1 | grep -o '[0-9]*')"
|
||||||
|
if [[ -z "$API_COUNT" || "$API_COUNT" -lt 1 ]]; then
|
||||||
|
FAIL=1; REASONS+=("endpoint /api/synthese ne renvoie pas de donnees exploitables")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
FAIL=1; REASONS+=("JWT_SECRET introuvable dans .env, impossible de tester l'API authentifiee")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "-- 4. Export PDF synthese : coherence titre annonce vs lignes reellement extraites --"
|
||||||
|
if [[ -n "$TOKEN" ]] && run "command -v pdftotext" >/dev/null 2>&1; then
|
||||||
|
run "curl -s --max-time 15 -H 'Authorization: Bearer $TOKEN' 'http://172.17.0.1:3005/api/export/pdf?view=synthese' -o /tmp/verify-rla-api.pdf"
|
||||||
|
PDFTXT="$(run "pdftotext -layout /tmp/verify-rla-api.pdf - 2>/dev/null")"
|
||||||
|
ANNONCE="$(printf '%s' "$PDFTXT" | grep -oE 'Top [0-9]+ Alertes' | grep -oE '[0-9]+' | head -1)"
|
||||||
|
REELLES="$(printf '%s' "$PDFTXT" | grep -c 'CRITIQUE')"
|
||||||
|
echo "titre annonce: ${ANNONCE:-?} lignes | lignes reellement extraites: ${REELLES:-0}"
|
||||||
|
if [[ -n "$ANNONCE" && "${REELLES:-0}" -lt "$ANNONCE" ]]; then
|
||||||
|
FAIL=1; REASONS+=("PDF synthese : titre annonce $ANNONCE lignes mais $REELLES extraites (regression classe widthOfString/troncature)")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "pdftotext absent sur cette machine (ou token manquant) - check ignore. Installer poppler-utils pour une couverture complete."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Resultat : $([[ $FAIL -eq 0 ]] && echo OK || echo ECHEC) ==="
|
||||||
|
} > "$LOGFILE" 2>&1
|
||||||
|
|
||||||
|
if [[ $FAIL -eq 1 ]]; then
|
||||||
|
REASON_TXT="Verification automatique echouee : $(IFS='; '; echo "${REASONS[*]}"). Preuve complete dans .agents/last-verify.log. Corrige avant de conclure, puis relance la verification."
|
||||||
|
ESCAPED=$(printf '%s' "$REASON_TXT" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))' 2>/dev/null || printf '"%s"' "$REASON_TXT")
|
||||||
|
printf '{"decision":"continue","reason":%s}\n' "$ESCAPED"
|
||||||
|
else
|
||||||
|
printf '{"decision":"stop"}\n'
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user