#!/usr/bin/env bash set -euo pipefail # CASAN Level 5 drift detector. # Usage: # drift-detect.sh GOLDEN="${1:-}" CANDIDATE="${2:-}" REPORT="${3:-}" if [[ -z "$GOLDEN" || -z "$CANDIDATE" || -z "$REPORT" ]]; then echo "Usage: drift-detect.sh " >&2 exit 64 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" mkdir -p "$(dirname "$REPORT")" "$PROJECT_ROOT/.specify/logs/level5" python3 - "$GOLDEN" "$CANDIDATE" "$REPORT" <<'PY' import difflib import hashlib import json import pathlib import sys from datetime import datetime, timezone golden_path = pathlib.Path(sys.argv[1]) candidate_path = pathlib.Path(sys.argv[2]) report_path = pathlib.Path(sys.argv[3]) golden = golden_path.read_text(encoding="utf-8") candidate = candidate_path.read_text(encoding="utf-8") similarity = difflib.SequenceMatcher(None, golden, candidate).ratio() length_delta = abs(len(candidate) - len(golden)) / max(len(golden), 1) status = "pass" action = "allow" if similarity < 0.70 or length_delta > 0.50: status = "fail" action = "block_or_fallback" elif similarity < 0.85 or length_delta > 0.30: status = "warn" action = "require_review" report = { "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "harness": "L5-drift-detection", "status": status, "action": action, "similarity_ratio": round(similarity, 4), "length_delta_ratio": round(length_delta, 4), "golden_hash": hashlib.sha256(golden.encode()).hexdigest(), "candidate_hash": hashlib.sha256(candidate.encode()).hexdigest(), "golden_file": str(golden_path), "candidate_file": str(candidate_path), } report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") print(f"DRIFT_{status.upper()} similarity={report['similarity_ratio']} length_delta={report['length_delta_ratio']} report={report_path}") if status == "fail": raise SystemExit(2) PY