Files
hyungi_document_server/clients/ds-app/contract/contract-check.sh
T

81 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# contract-check.sh — S2-Ff: 라이브 엔드포인트 ↔ 동결 fixture *모양* 드리프트 감지 (비차단 runbook).
#
# 라이브 재호출 → 키/타입 diff(LLM 값은 무시) → 드리프트면 비0 exit + fixture-update 안내.
# PR 게이트 아님 — 수동/Tailscale-CI 트리거([[feedback_pr_gate_vs_runbook_separation]]).
# fixture 는 동결·불변이며, 이 도구가 감지한 드리프트가 **재캡처 PR 의 유일 합법 트리거**(S2-0d).
#
# exit: 0 = 드리프트 없음(체크된 것 중) · 1 = breaking 드리프트 · 2 = 아무것도 체크 못함(전부 도달불가)
# env override: AIFABRIC_LOCALMLX_URL, AIFABRIC_DS_URL
#
# 스킵은 항상 *가시적*으로 출력(silent green 금지, [[feedback_silent_skip_accumulation]]).
set -uo pipefail # NOT -e: 개별 체크 실패를 모아 보고
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIX="$SCRIPT_DIR/fixtures"
PY="$(command -v python3 || echo /usr/bin/python3)"
LLM_URL="${AIFABRIC_LOCALMLX_URL:-http://100.76.254.116:8890}"
DS_URL="${AIFABRIC_DS_URL:-https://document.hyungi.net/api}"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
breaking=0
checked=0
echo "== S2-Ff contract drift check =="
echo " llm-router: $LLM_URL"
echo " DS: $DS_URL"
# --- 1) llm-router /v1/chat/completions (S2 fixture) ---
echo
echo "[1] llm-router /v1/chat/completions ↔ llm-router-chat.response.json"
# 동결 request fixture 에서 _meta 떼고 그대로 wire body 로 POST(call-shape 도 함께 검증).
"$PY" -c 'import json,sys; o=json.load(open(sys.argv[1],encoding="utf-8")); o.pop("_meta",None); json.dump(o,open(sys.argv[2],"w"))' \
"$FIX/llm-router-chat.request.json" "$TMP/req.json" 2>"$TMP/strip_err" || { echo " SKIP — request fixture 처리 실패: $(head -c160 "$TMP/strip_err")"; }
if [ -s "$TMP/req.json" ]; then
code="$(curl -sS -m 60 -o "$TMP/llm_live.json" -w '%{http_code}' \
-H 'Content-Type: application/json' --data @"$TMP/req.json" \
"$LLM_URL/v1/chat/completions" 2>"$TMP/llm_err" || true)"
if [ "$code" = "200" ]; then
checked=$((checked + 1))
if "$PY" "$SCRIPT_DIR/shape_diff.py" "$FIX/llm-router-chat.response.json" "$TMP/llm_live.json"; then
echo " PASS — shape 일치"
else
echo " >> 드리프트: llm-router-chat.{request,response}.json 재캡처 PR 필요"
breaking=1
fi
else
echo " SKIP — 도달 불가/비200 (http=${code:-curlfail}; 맥미니 offline?) $(head -c120 "$TMP/llm_err" 2>/dev/null)"
fi
fi
# --- 2) DS /search/ask (S1 fixture, best-effort: 인증 필요할 수 있음) ---
echo
echo "[2] DS /search/ask ↔ ask.json (best-effort)"
code="$(curl -sS -m 30 -G -o "$TMP/ds_live.json" -w '%{http_code}' \
--data-urlencode 'q=충격시험은 언제 면제되나' --data-urlencode 'backend=mac-mini-default' \
"$DS_URL/search/ask" 2>"$TMP/ds_err" || true)"
case "$code" in
200)
checked=$((checked + 1))
if "$PY" "$SCRIPT_DIR/shape_diff.py" "$FIX/ask.json" "$TMP/ds_live.json"; then
echo " PASS — shape 일치"
else
echo " >> 드리프트: ask.json(S1) 재캡처 검토"
breaking=1
fi ;;
401|403) echo " SKIP — 인증 필요(JWT). DS ask 체크는 토큰 주입 시에만(S1 도메인)." ;;
*) echo " SKIP — 도달 불가/비200 (http=${code:-curlfail})." ;;
esac
echo
if [ "$breaking" -ne 0 ]; then
echo "RESULT: DRIFT 발견 — 위 fixture 재캡처 PR 필요(S2-0d 의 유일 합법 fixture 변경 경로)."
exit 1
elif [ "$checked" -eq 0 ]; then
echo "RESULT: 체크한 엔드포인트 0건(전부 도달 불가) — 드리프트 판정 불가. (green 아님 → exit 2)"
exit 2
else
echo "RESULT: OK — 체크 ${checked}건 모두 shape 일치, 드리프트 없음."
exit 0
fi