# task-2993 — 미배포 상시 탐지 (머지됐으나 프로덕션에 없는 커밋 감시)

**레벨**: Lv.2 · **팀**: dev5-team (마르둑) · **회장 승인 완료** (2026-08-20)

## 배경 — 오늘 이 갭에서 사고가 두 번 났다
```
봇 작업 → 커밋 → PR → CI(검증만) → 머지 → ??? → 프로덕션
                                            ↑
                                     사람 기억에 의존. 자주 누락됨
```
1. **머지됐는데 배포 안 된 상태가 16시간** 지속됐다. 디스크는 새 커밋인데 구동 프로세스는 옛 커밋이었고,
   `Restart=always` 때문에 크래시가 곧 비통제 배포 트리거였다.
2. ANU 가 디스크를 옛 커밋에 임시 고정해 막았는데, **봇이 `git pull` 을 해서 하루도 못 가 무력화**됐다.
3. 그 결과 금소법 직결 결함(실손계산기 과대추정 최대 4.67배)이 코드상 고쳐진 뒤에도
   **프로덕션에는 반영되지 않은 채** 남아 있었다.

**핵심**: "코드가 고쳐졌다"와 "프로덕션이 고쳐졌다"는 다르다. 지금은 그 차이를 아무도 감시하지 않는다.

## 이 태스크의 목표
**머지됐으나 프로덕션에 반영되지 않은 상태를 상시 탐지하고 알린다.**
자동 배포는 이 태스크 범위가 **아니다**(후속 task 에서 다룬다). 여기서는 **감시와 알림만** 한다.

## 실측된 현재 환경
```
백엔드 : user systemd + uvicorn
         WorkingDirectory=/home/jay/insuro-releases/current/server
         /api/status 가 sha · sha_short · sha_source · ready · db · uptime_sec 반환 (포트 8001)
release: /home/jay/insuro-releases/releases/<full_sha>/ · current symlink · previous symlink
저장소 : /home/jay/projects/InsuRo (origin/main 이 배포 대상 기준)
프론트 : Cloudflare Pages, wrangler 수동 배포
```

## 요구사항 (ANU·Codex 합의 설계)

### 1. 3자 비교
단순히 두 값을 비교하지 말고 **세 지점을 교차검증**한다.
- **A: `origin/main` 의 최신 sha** — 배포되어야 할 것
- **B: `current` symlink 가 가리키는 release sha** — 디스크가 준비한 것
- **C: `/api/status` 가 보고하는 구동 중 sha** — 실제로 돌고 있는 것

```
A == B == C   → 정상
A != B        → 미배포 (머지됐는데 release 가 안 만들어짐)
B != C        → ★ 오늘 사고 유형 (디스크는 새 것, 프로세스는 옛 것)
```
**B != C 를 반드시 별도로 탐지하라.** 이게 크래시 시 비통제 배포로 이어지는 상태다.

### 2. 2단계 판정 — 오탐 방지
- **경고(WARNING)**: 불일치가 유예 시간 이내. 기본 **30분**. 배포 중일 수 있다
- **장애(CRITICAL)**: 유예 초과. 기본 30분 초과
- **배포 진행 중 표시**: 배포 스크립트가 도는 동안 만드는 마커 파일(예: `$RELEASES_ROOT/.deploying`)이
  있으면 판정을 보류한다. 배포 스크립트(`scripts/deploy/insuro-deploy.sh`)가 이 마커를 만들지
  **먼저 확인**하고, 없으면 마커 생성을 그 스크립트에 추가하는 것을 제안하라(직접 수정은 ANU 승인 후).

### 3. 알림
- CRITICAL 시 회장 텔레그램으로 1회 알린다. **반복 스팸 금지** — 같은 불일치가 지속되는 동안 재발송하지 않고,
  상태가 바뀔 때만 다시 알린다.
- 알림 문구에 A/B/C 세 sha 와 경과 시간, 그리고 **무엇을 하면 해소되는지**를 담는다.
- 발송 수단은 기존 cokacdir 경로를 쓴다. 키 파일 경로는 하드코딩하지 말고 설정으로 분리한다.

### 4. 주기 실행
- user systemd timer 로 **10분 간격** 실행. 기존 `insuro-api.service` 와 동일한 user 스코프를 쓴다.
  (★ `systemctl --user`. system 스코프는 빈 값을 반환해 오판을 유발한다)
- 상태를 파일에 남겨 이전 판정과 비교 가능하게 한다.

### 5. 프론트는 이번 범위 밖
Cloudflare Pages 의 현재 서비스 sha 를 확인할 방법이 미확정이다. **조사만 하고 결론을 보고**하라.
구현은 하지 말 것. 확인되면 후속 태스크에서 같은 감시에 붙인다.

## 검증 (실측 증거 필요)
1. **정상 상태**에서 감시가 조용한지 확인 (오탐 0)
2. **B != C 를 인위적으로 만들어** 탐지되는지 확인.
   방법: release 를 하나 더 만들고 `current` 만 바꾼 뒤 재기동하지 않는다. 그러면 디스크≠프로세스가 된다.
   **검증 후 반드시 원상복구**하고, 서비스가 정상인지 `/api/status` 로 확인하라
3. **A != B** 도 같은 방식으로 확인
4. 유예 시간 내에는 WARNING, 초과 시 CRITICAL 로 바뀌는지 확인
5. 배포 마커가 있을 때 판정이 보류되는지 확인
6. 알림이 **중복 발송되지 않는지** 확인

## ★ 안전 규칙 (엄수)
- 이 태스크는 **감시만** 한다. **배포·재기동·`git pull`·symlink 전환을 절대 실행하지 마라.**
  검증 2·3번에서 상태를 만들 때만 예외이며, **즉시 원상복구**하고 그 사실을 보고한다.
- 라이브 서비스(`insuro-api.service`)를 중단시키지 마라. 검증 중 서비스가 죽으면 즉시 복구하고 보고하라.
- `insuro-api.service` 유닛 파일을 수정하지 마라.

## 범위 · 금지
**허용**: 감시 스크립트 신규(`ops/monitor/**` 또는 유사) · 감시용 systemd timer/service 유닛 신규 ·
`docs/` 문서 · `memory/reports/task-2993.md`

**금지**:
- `scripts/deploy/**` 직접 수정 — 배포 마커 추가가 필요하면 **제안만** 하고 ANU 승인을 받는다
- `insuro-api.service` 유닛 수정
- `supabase/migrations/**` · `src/pages/CustomerChat.tsx` — task-2991 작업 중
- `server/**` · `src/**` 애플리케이션 코드

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "ops/monitor/**"
    - "ops/*.timer"
    - "ops/*.service"
    - "docs/deploy/**"
    - "tests/**"
    - "memory/reports/task-2993.md"
  forbidden_paths:
    - "scripts/deploy/**"
    - "server/**"
    - "src/**"
    - "supabase/**"
    - ".github/**"
  commands:
    - "systemctl --user"
    - "curl"
    - "git"
  merge_policy: "tiered"
  ttl_hours: 48
```

## 운영 계약
- `origin/main` 기준 `git pull --ff-only` 후 시작(작업 저장소 한정. **서비스 워킹트리를 건드리지 말 것**)
- 워크플로우 `/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` 금지.

## 완료 조건
3자 비교 동작 · B!=C 탐지 실증 · 2단계 판정 · 중복알림 없음 · 오탐 0 · 검증 후 원상복구 · 서비스 무중단

## 보고
**PR 생성까지가 범위다. 머지는 ANU 가 한다. 직접 머지하지 말 것.**
`memory/reports/task-2993.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.