# task-3008 — 경보 배선 복구 (고장이 나도 아무도 모르는 구조 해소)

**레벨**: Lv.2 · **팀**: dev7-team (이참나) · **회장 승인 완료** (2026-08-25)

## 배경 — 경보가 3중으로 죽어 있다

2026-08-25 조사에서 **같은 유형의 장애 4건**이 드러났다. 공통점은 "실패가 조용해서 아무도 모른다"다.
그중 **알림 경로 자체가 배선되지 않은 것**을 이 태스크가 고친다.

### 고장 지점 3개 (ANU 실측 · 전부 별개 원인)

```
① COKACDIR_CHAT_ID / COKACDIR_KEY 미설정
   server/scripts/daily_health_check.py:63
     "COKACDIR_CHAT_ID 또는 COKACDIR_KEY가 설정되지 않아 Telegram 알림을 건너뜁니다"
   → 매일 경고를 만들어놓고 발송을 건너뛴다

② run_trend_pipeline.sh 가 없는 인자로 호출한다
   notify_failure() → python3 scripts/daily_health_check.py --force-alert "$msg"
   실행 결과: "error: unrecognized arguments: --force-alert"
   usage 는 [-h] [--dry-run] 뿐 → **실패 알림 함수 자체가 항상 실패**

③ insuro-drift-monitor.service 의 알림 경로 부재
   유닛의 EnvironmentFile 이 주석 처리되어 있고, 참조 대상
   /home/jay/insuro-releases/shared/drift-monitor.env 파일이 존재하지 않는다
   → verdict=CRITICAL 을 정확히 탐지하고도 발송하지 못한다
```

### 지금 무엇이 묻혀 있나 (2026-08-25 06:00 파이프라인 로그 실측)
```
⚠️ 트렌드 수집: 0/2,000건 (기준 1,800)
⚠️ 포화도 수집: 1,000/2,000건 (기준 1,800)
⚠️ 랭킹 산출: 0건 (기준 1+)
→ 매일 발생하는 경고. 4개월째(trend_data 마지막 유입 2026-04-30) 아무에게도 도달하지 않았다.
```

---

## 고칠 것

### ① 알림 자격증명 배선
`COKACDIR_CHAT_ID` / `COKACDIR_KEY` 를 cron·systemd 실행 환경에서 읽을 수 있게 한다.
```
CHAT_ID = 6937032012
key-file = /home/jay/.cokacdir/bot_keys/f392d0e199d4177b4e209bca7fd61066e10056034678934cc2798be054dbcc61.key
```
★ **키 값을 git 에 커밋하지 마라.** `.env.keys` 또는 비-git EnvironmentFile 경로를 쓰고,
   저장소에는 **경로만** 남긴다. `.gitignore` 확인 필수.
★ cron 은 로그인 셸 환경을 상속하지 않는다. 스크립트가 자체적으로 자격증명을 로드하는지
   확인하고, 안 하면 로드 구문을 넣어라(기존 `source /home/jay/workspace/.env.keys` 패턴 참고).

### ② `--force-alert` 인자 불일치 해소
`daily_health_check.py` 가 `--force-alert <메시지>` 를 받도록 하거나,
`run_trend_pipeline.sh` 가 올바른 호출 방식을 쓰도록 한다. **어느 쪽이든 실측으로 동작을 확인**하라.
★ 둘 다 고치는 것이 가장 단순하면 그렇게 하되, **호출-정의 계약이 한 곳에서 일치**하게 만들어라.

### ③ 드리프트 감시 알림 경로 복구
`insuro-drift-monitor.service` 가 알림 자격증명을 받도록 EnvironmentFile 을 실제로 배선한다.
★ 유닛 파일은 `/home/jay/.config/systemd/user/` 에 있다(user 스코프). `systemctl --user` 를 쓴다.
★ 이 서비스는 **CRITICAL 판정 시 exit 1 로 끝나는 것이 정상 설계**다.
   systemd 가 `failed` 로 표시한다고 고장난 것이 아니다. **exit code 를 바꾸지 마라.**

### ④ 알림 폭주 억제
배선 즉시 **이미 고장난 것들이 매일 여러 건 쏟아진다**(트렌드 3종 등). 이건 의도된 결과다.
그러나 같은 사유가 매시간 반복되면 알림 피로로 다시 무시하게 된다.
- **같은 사유는 1일 1회**로 억제하라(중복 억제 키: 스크립트명 + 사유)
- ★ 억제 때문에 **새로운 종류의 실패가 묻히면 안 된다.** 사유가 다르면 각각 발송한다

### ⑤ 민감정보 차단
알림 본문에 다음을 넣지 마라: API 키·토큰·비밀번호 · 푸시 endpoint 전체(경로에 토큰 포함) ·
고객 이름/연락처/주민번호 · DB 접속정보. **호스트·건수·사유 수준까지만.**

---

## ★ 이 태스크가 하지 않는 것 (혼동 방지)
- **트렌드 수집이 0건인 원인은 고치지 않는다.** 이 태스크는 *알림이 도달하게* 만들 뿐이다.
  작업 후 "트렌드 수집 0건" 경고가 **매일 오는 것이 정상**이다. 원인 규명은 별도 태스크다
- **InsuWiki 백업 실패**(GCP 인증)도 범위 밖이다. GitHub Actions 쪽이라 이 배선과 무관하다
- **`exit=0` 인데 0건 수집인 조용한 성공**(트렌드 Step 1)도 별도다. 발견하면 **보고만** 하라

## 검증 (실측 강제)
1. **수정 전 재현** — 현재 상태로 헬스체크를 실행해 "알림을 건너뜁니다" 로그와
   `--force-alert` 인자 오류를 각각 재현해 기록하라
2. **실제 수신 확인** — 강제 실패를 만들어 **텔레그램에 실제로 도착하는지** 확인하라.
   ★ 로그에 "발송함"이 찍힌 것만으로 성공 판정하지 마라. **수신 여부가 판정 기준**이다
3. **드리프트 감시** — CRITICAL 상태에서 알림이 도달하는지 확인
4. **중복 억제** — 같은 사유 2회 연속 실행 시 1회만 발송되는지, 다른 사유는 각각 오는지 확인
5. **민감정보 스캔** — 발송 본문에 키·토큰·PII 가 없는지 grep 으로 확인
6. **봉인** — 자격증명 미설정 상태로 되돌렸을 때 FAIL 하는 테스트를 넣고, **변이로 실증**하라

## 범위 · 금지
**수정 허용**: `server/scripts/daily_health_check.py` · `server/scripts/run_trend_pipeline.sh` ·
`~/.config/systemd/user/insuro-drift-monitor.service` · `ops/monitor/**` · 관련 테스트

**금지**:
- **키 값을 git 에 커밋** (경로만 남긴다)
- 드리프트 감시의 **exit code 의미 변경** (CRITICAL=exit 1 은 정상 설계)
- `server/policy_analyzer.py` — task-3007 동시 진행 영역
- `server/silson/**` · `src/**` · `supabase/**` · `extension/**`
- 트렌드 수집 로직 자체 수정 (범위 밖 — 보고만)

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "server/scripts/daily_health_check.py"
    - "server/scripts/run_trend_pipeline.sh"
    - "server/scripts/**"
    - "ops/monitor/**"
    - "server/tests/**"
    - "memory/reports/task-3008.md"
  forbidden_paths:
    - "server/policy_analyzer.py"
    - "server/silson/**"
    - "server/main.py"
    - "src/**"
    - "supabase/**"
    - "extension/**"
    - ".github/**"
  commands:
    - "python3"
    - "pytest"
    - "systemctl"
    - "bash"
  merge_policy: "tiered"
  ttl_hours: 48
```

## 운영 계약
- `origin/main` 기준 `git pull --ff-only` 후 시작
- ★ `gh` 호출 시 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 주입 필수 (회장 개인 PAT 금지 — 감사기록 오염)
- 워크플로우 `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 금지.
- **명세와 실측이 다르면 임의 판단하지 말고 ANU 에 보고하라.**

## 완료 조건
3개 고장 지점 각각 수정 전 재현 → 수정 후 **실제 텔레그램 수신 확인** ·
중복 억제 동작 · 민감정보 0 · 변이로 봉인 실증 · 회귀 유지

## 보고
**★★★ PR 생성까지가 범위다. 머지는 ANU 가 한다. 절대 직접 머지하지 마라.**
`memory/reports/task-3008.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.
★ 봉투 첫 줄에 **"실제 텔레그램 수신 확인 여부"** 를 담아라. 이것이 이 태스크의 결론이다.