detection-retention-cleanup.sh 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env bash
  2. # S8-DETECTION-LOG-RETENTION-1:TEMP_SCHED_* detection_log / rule_detection_state 治理脚本(dev/test only, aidopdev)。
  3. #
  4. # 严格范围:
  5. # - 仅 rule_code LIKE 'TEMP_SCHED_%' 的数据
  6. # - 默认 dry-run(只统计、不删除);--apply 才真删
  7. # - 默认 --days=1,对早于 NOW - days 的 TEMP 行做 DELETE 候选
  8. # - 不动 ado_s8_exception
  9. # - 不动 demo rule 10/11/12 数据
  10. # - 不动非 TEMP_SCHED_% 的真实业务数据
  11. #
  12. # 用法:
  13. # bash scripts/s8/detection-retention-cleanup.sh # dry-run,默认 1 天
  14. # bash scripts/s8/detection-retention-cleanup.sh --days 7 # dry-run,7 天
  15. # bash scripts/s8/detection-retention-cleanup.sh --apply # 真删,默认 1 天
  16. # bash scripts/s8/detection-retention-cleanup.sh --apply --days 0 # 真删全部 TEMP 历史(dev/test 验证用)
  17. #
  18. # 退出码:0 总是(只要查询/删除未抛异常);致命错误 → 2。
  19. set -uo pipefail
  20. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  21. # shellcheck source=./s8-regression-common.sh
  22. source "${SCRIPT_DIR}/s8-regression-common.sh"
  23. APPLY=0
  24. DAYS=1
  25. while [[ $# -gt 0 ]]; do
  26. case "$1" in
  27. --apply) APPLY=1; shift ;;
  28. --days) DAYS="${2:-1}"; shift 2 ;;
  29. -h|--help)
  30. sed -n '2,20p' "$0"
  31. exit 0 ;;
  32. *) echo "unknown arg: $1" >&2; exit 2 ;;
  33. esac
  34. done
  35. [[ "${DAYS}" =~ ^[0-9]+$ ]] || s8reg_die "--days must be a non-negative integer, got '${DAYS}'"
  36. CUTOFF=$(mysql_run "SELECT DATE_SUB(NOW(), INTERVAL ${DAYS} DAY);")
  37. NOW_TS=$(mysql_run "SELECT NOW();")
  38. MODE=$([[ "${APPLY}" == "1" ]] && echo "APPLY (DELETE)" || echo "DRY-RUN (no delete)")
  39. echo "============================================================"
  40. echo "S8 detection retention cleanup"
  41. echo " DB : ${DB_HOST}:${DB_PORT}/${DB_NAME}"
  42. echo " now : ${NOW_TS}"
  43. echo " cutoff : ${CUTOFF} (--days=${DAYS})"
  44. echo " mode : ${MODE}"
  45. echo " scope : rule_code LIKE 'TEMP_SCHED_%' (real business data NOT touched)"
  46. echo "============================================================"
  47. # ---------------------------------------------------------------------------
  48. # 守恒前 snapshot
  49. # ---------------------------------------------------------------------------
  50. baseline_before=$(read_baseline)
  51. demo_state_before=$(mysql_run "SELECT GROUP_CONCAT(CONCAT(id,':',enabled,':',IFNULL(paused_until,'NULL'),':',trigger_count_required,':',recover_count_required) ORDER BY id) FROM ado_s8_watch_rule WHERE id IN (10,11,12);")
  52. temp_active_exc_before=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code LIKE 'TEMP_SCHED_%' AND is_deleted=0;")
  53. echo
  54. echo "---- BEFORE ----"
  55. echo " baseline = ${baseline_before}"
  56. echo " demo rule 10/11/12 = ${demo_state_before}"
  57. echo " TEMP active exception (is_deleted=0) = ${temp_active_exc_before}"
  58. # ---------------------------------------------------------------------------
  59. # detection_log 统计
  60. # ---------------------------------------------------------------------------
  61. log_total=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log;")
  62. log_temp_total=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code LIKE 'TEMP_SCHED_%';")
  63. log_temp_candidates=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code LIKE 'TEMP_SCHED_%' AND detected_at < '${CUTOFF}';")
  64. echo
  65. echo "---- detection_log ----"
  66. echo " total = ${log_total}"
  67. echo " TEMP_SCHED_% total = ${log_temp_total}"
  68. echo " TEMP candidates < cutoff = ${log_temp_candidates}"
  69. if [[ "${log_temp_candidates}" != "0" ]]; then
  70. echo " per-rule breakdown of candidates:"
  71. mysql_run "SELECT rule_code, COUNT(*) AS cnt, MIN(detected_at) AS earliest, MAX(detected_at) AS latest FROM ado_s8_detection_log WHERE rule_code LIKE 'TEMP_SCHED_%' AND detected_at < '${CUTOFF}' GROUP BY rule_code ORDER BY cnt DESC;" | sed 's/^/ /'
  72. fi
  73. # ---------------------------------------------------------------------------
  74. # rule_detection_state 统计
  75. # ---------------------------------------------------------------------------
  76. state_total=$(mysql_run "SELECT COUNT(*) FROM ado_s8_rule_detection_state;")
  77. state_temp_total=$(mysql_run "SELECT COUNT(*) FROM ado_s8_rule_detection_state WHERE rule_code LIKE 'TEMP_SCHED_%';")
  78. state_temp_candidates=$(mysql_run "SELECT COUNT(*) FROM ado_s8_rule_detection_state WHERE rule_code LIKE 'TEMP_SCHED_%' AND last_seen_at < '${CUTOFF}';")
  79. echo
  80. echo "---- rule_detection_state ----"
  81. echo " total = ${state_total}"
  82. echo " TEMP_SCHED_% total = ${state_temp_total}"
  83. echo " TEMP candidates < cutoff = ${state_temp_candidates}"
  84. if [[ "${state_temp_candidates}" != "0" ]]; then
  85. echo " per-rule breakdown of candidates:"
  86. mysql_run "SELECT rule_code, dedup_key, last_seen_at FROM ado_s8_rule_detection_state WHERE rule_code LIKE 'TEMP_SCHED_%' AND last_seen_at < '${CUTOFF}' ORDER BY last_seen_at;" | sed 's/^/ /'
  87. fi
  88. # ---------------------------------------------------------------------------
  89. # Apply
  90. # ---------------------------------------------------------------------------
  91. log_deleted=0
  92. state_deleted=0
  93. if [[ "${APPLY}" == "1" ]]; then
  94. echo
  95. echo "---- APPLY ----"
  96. log_deleted=$(mysql_run "DELETE FROM ado_s8_detection_log WHERE rule_code LIKE 'TEMP_SCHED_%' AND detected_at < '${CUTOFF}'; SELECT ROW_COUNT();")
  97. echo " detection_log: deleted ${log_deleted} row(s)"
  98. state_deleted=$(mysql_run "DELETE FROM ado_s8_rule_detection_state WHERE rule_code LIKE 'TEMP_SCHED_%' AND last_seen_at < '${CUTOFF}'; SELECT ROW_COUNT();")
  99. echo " rule_detection_state: deleted ${state_deleted} row(s)"
  100. if [[ "${log_deleted}" != "${log_temp_candidates}" ]]; then
  101. echo " WARNING: detection_log delete count (${log_deleted}) != pre-count (${log_temp_candidates}) — possible concurrent insert"
  102. fi
  103. if [[ "${state_deleted}" != "${state_temp_candidates}" ]]; then
  104. echo " WARNING: rule_detection_state delete count (${state_deleted}) != pre-count (${state_temp_candidates})"
  105. fi
  106. else
  107. echo
  108. echo "---- DRY-RUN (no DELETE executed) ----"
  109. echo " would-delete detection_log: ${log_temp_candidates}"
  110. echo " would-delete rule_detection_state: ${state_temp_candidates}"
  111. echo " re-run with --apply to actually delete"
  112. fi
  113. # ---------------------------------------------------------------------------
  114. # 守恒后 snapshot
  115. # ---------------------------------------------------------------------------
  116. baseline_after=$(read_baseline)
  117. demo_state_after=$(mysql_run "SELECT GROUP_CONCAT(CONCAT(id,':',enabled,':',IFNULL(paused_until,'NULL'),':',trigger_count_required,':',recover_count_required) ORDER BY id) FROM ado_s8_watch_rule WHERE id IN (10,11,12);")
  118. temp_active_exc_after=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code LIKE 'TEMP_SCHED_%' AND is_deleted=0;")
  119. echo
  120. echo "---- AFTER ----"
  121. echo " baseline = ${baseline_after}"
  122. echo " demo rule 10/11/12 = ${demo_state_after}"
  123. echo " TEMP active exception (is_deleted=0) = ${temp_active_exc_after}"
  124. echo
  125. echo "---- ASSERTIONS ----"
  126. fail=0
  127. if [[ "${baseline_before}" == "${baseline_after}" ]]; then
  128. echo " PASS: baseline unchanged (${baseline_before})"
  129. else
  130. echo " FAIL: baseline drifted ${baseline_before} → ${baseline_after}"
  131. fail=$((fail+1))
  132. fi
  133. if [[ "${demo_state_before}" == "${demo_state_after}" ]]; then
  134. echo " PASS: demo rule 10/11/12 conserved"
  135. else
  136. echo " FAIL: demo rule drift before=${demo_state_before} after=${demo_state_after}"
  137. fail=$((fail+1))
  138. fi
  139. if [[ "${temp_active_exc_before}" == "${temp_active_exc_after}" ]]; then
  140. echo " PASS: TEMP active exception count unchanged (${temp_active_exc_before})"
  141. else
  142. echo " FAIL: TEMP active exception drift before=${temp_active_exc_before} after=${temp_active_exc_after}"
  143. fail=$((fail+1))
  144. fi
  145. echo
  146. echo "============================================================"
  147. if [[ "${APPLY}" == "1" ]]; then
  148. echo "Summary: APPLY mode — deleted log=${log_deleted} state=${state_deleted}; days=${DAYS} cutoff=${CUTOFF}"
  149. else
  150. echo "Summary: DRY-RUN — would-delete log=${log_temp_candidates} state=${state_temp_candidates}; days=${DAYS} cutoff=${CUTOFF}"
  151. fi
  152. echo "============================================================"
  153. exit $(( fail > 0 ? 1 : 0 ))