# 금소법 면책동의 UX 개선 — 법적 방어력 확보

## 작업 레벨: Lv.3 (프론트엔드 UX + 백엔드 검증 + DB 스키마)

## 미팅 합의 참조
`/home/jay/workspace/memory/meetings/2026-04-24-compliance-consent-ux.md`

## 배경/목적
InsuRo AI 콘텐츠 작성 페이지에서 금소법 면책동의 체크박스가 "고급 옵션" 안에 숨어있어 유저가 찾지 못함. "콘텐츠 유형 빠른 선택"으로 금소법 필터를 켜도 동의 체크박스가 보이지 않아 생성 시 에러 발생.

### 법적 설계 원칙 (★★★ 최우선)
- **InsuRo는 도구 제공자**이지 콘텐츠 발행자가 아님
- AI가 생성한 콘텐츠의 **최종 검토 책임은 설계사(사용자)** 본인에게 있음
- **면책 동의 + DB 기록 = "고지 의무 충족"** 방어선
- 동의 기록은 향후 법적 분쟁 시 증거로 사용 가능해야 함

---

## Phase 1: DB 스키마 (백엔드)

### 1.1 동의 이력 테이블 생성

`compliance_consents` 테이블 신규 생성:

```sql
CREATE TABLE IF NOT EXISTS compliance_consents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  consent_type TEXT NOT NULL DEFAULT 'compliance_disclaimer',
  consent_version TEXT NOT NULL,          -- 면책 문구 버전 (예: "v1.0")
  content_hash TEXT NOT NULL,             -- 면책 문구 SHA-256 해시
  agreed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  expires_at TIMESTAMPTZ NOT NULL,        -- agreed_at + 30일
  ip_address TEXT,
  user_agent TEXT,
  is_active BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 인덱스: 유저별 활성 동의 빠른 조회
CREATE INDEX idx_compliance_consents_user_active 
ON compliance_consents(user_id, consent_type, is_active) 
WHERE is_active = true;

-- RLS 정책
ALTER TABLE compliance_consents ENABLE ROW LEVEL SECURITY;

-- 본인 동의만 조회 가능
CREATE POLICY "Users can view own consents" ON compliance_consents
  FOR SELECT USING (auth.uid() = user_id);

-- 본인 동의만 삽입 가능
CREATE POLICY "Users can insert own consents" ON compliance_consents
  FOR INSERT WITH CHECK (auth.uid() = user_id);
```

### 1.2 면책 문구 버전 관리

```sql
CREATE TABLE IF NOT EXISTS compliance_versions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  version TEXT NOT NULL UNIQUE,           -- "v1.0"
  content TEXT NOT NULL,                  -- 면책 문구 전문
  content_hash TEXT NOT NULL,             -- SHA-256
  effective_from TIMESTAMPTZ NOT NULL DEFAULT now(),
  is_current BOOLEAN NOT NULL DEFAULT true,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 초기 면책 문구 삽입
INSERT INTO compliance_versions (version, content, content_hash, is_current)
VALUES (
  'v1.0',
  '본 AI 금소법 필터링 기능은 참고용이며, AI가 생성한 콘텐츠의 금융소비자보호법 준수 여부에 대한 최종 판단 및 책임은 사용자(설계사) 본인에게 있습니다. 당사는 AI 필터링의 정확성, 완전성을 보증하지 않습니다.',
  -- content_hash는 코드에서 생성
  '',
  true
);
```

---

## Phase 2: 백엔드 API (서버)

### 2.1 동의 확인/저장 API

파일: `/home/jay/projects/InsuRo/server/main.py`

**새 엔드포인트 2개:**

```
GET  /api/insuro/compliance/consent-status
POST /api/insuro/compliance/consent
```

#### GET /api/insuro/compliance/consent-status
- JWT 인증 필수
- 반환: `{ "has_valid_consent": true/false, "expires_at": "...", "version": "v1.0" }`
- 로직: user_id로 compliance_consents에서 is_active=true, expires_at > now() 조건 조회
- 현재 면책 문구 버전과 동의 버전이 다르면 has_valid_consent=false (재동의 필요)

#### POST /api/insuro/compliance/consent
- JWT 인증 필수
- 요청: `{ "consent_version": "v1.0", "content_hash": "..." }`
- 로직:
  1. compliance_versions에서 현재 버전 확인
  2. content_hash 일치 검증 (클라이언트가 보낸 해시 = 서버 저장 해시)
  3. 기존 동의 is_active=false로 비활성화
  4. 새 동의 레코드 삽입 (expires_at = now + 30일)
  5. IP, User-Agent 기록
- 반환: `{ "success": true, "expires_at": "..." }`

### 2.2 콘텐츠 생성 API 동의 검증 강화

파일: `/home/jay/projects/InsuRo/server/main.py`

기존 `handleGenerate` → `/api/insuro/keywords/analyze` 등 콘텐츠 생성 엔드포인트에서:

```python
# 기존: 클라이언트가 보낸 complianceFilter만 확인
# 변경: 서버에서 DB 동의 여부도 확인

def require_compliance_consent():
    """콘텐츠 생성 시 금소법 동의 여부 서버 검증"""
    def _dependency(payload: dict = Depends(verify_jwt)) -> dict:
        user_id = payload.get("sub", "")
        # DB에서 유효한 동의 확인
        sb = _get_supabase_client()
        consent = sb.table("compliance_consents") \
            .select("id, consent_version, expires_at") \
            .eq("user_id", user_id) \
            .eq("is_active", True) \
            .gt("expires_at", "now()") \
            .limit(1) \
            .execute()
        
        if not consent.data:
            raise HTTPException(
                status_code=403,
                detail="금소법 면책 동의가 필요합니다. 동의 후 다시 시도해주세요."
            )
        return {"_consent": consent.data[0], "_user_id": user_id}
    return _dependency
```

**★ 주의**: complianceFilter가 "off"인 경우에는 동의 검증을 건너뛰어야 함. 요청 body의 complianceFilter 값을 확인하는 로직 필요.

---

## Phase 3: 프론트엔드 UX

### 3.1 인라인 면책 배너 컴포넌트 신규 생성

파일: `/home/jay/projects/InsuRo/src/components/ComplianceConsentBanner.tsx`

**디자인 스펙:**
- 위치: 콘텐츠 유형 빠른 선택(블로그용/SNS용/제안서용) 바로 아래
- 조건: complianceFilter가 "basic" 또는 "full"일 때만 표시
- 애니메이션: 슬라이드다운 (height 0 → auto)
- 배경: amber/yellow 계열 (bg-amber-50 border-amber-200)
- 아이콘: Shield 또는 AlertTriangle (lucide-react)
- 레이아웃:
  - 아이콘 + 1줄 요약 + "전문 보기" 토글 링크
  - 1줄 요약: "AI 생성 콘텐츠의 금소법 준수 최종 책임은 사용자에게 있습니다"
  - 전문 보기: 클릭 시 아코디언으로 전문 표시
  - 체크박스: "위 내용을 확인하고 동의합니다"
- 상태:
  - DB에 유효한 동의 있음 → 배너 축소, "동의 완료 ✓ (만료: YYYY-MM-DD)" 표시
  - DB에 동의 없음 → 배너 확장, 체크박스 미체크 상태

### 3.2 생성 버튼 비활성화

파일: `/home/jay/projects/InsuRo/src/pages/Generate.tsx`

- complianceFilter !== "off" && !hasValidConsent → 생성 버튼 disabled
- disabled 상태에서 hover 시 툴팁: "금소법 면책 동의가 필요합니다"
- 기존 handleGenerate의 토스트 에러(라인 134-136)는 제거하고, 버튼 비활성화로 사전 차단

### 3.3 동의 로직 중앙화

파일: `/home/jay/projects/InsuRo/src/hooks/use-compliance-consent.ts` (신규)

```typescript
interface ComplianceConsentResult {
  hasValidConsent: boolean;
  loading: boolean;
  expiresAt: string | null;
  version: string | null;
  submitConsent: () => Promise<boolean>;
  refreshStatus: () => Promise<void>;
}

export function useComplianceConsent(): ComplianceConsentResult {
  // 1. 페이지 로드 시 GET /api/insuro/compliance/consent-status 호출
  // 2. hasValidConsent 상태 관리
  // 3. submitConsent: 체크박스 체크 시 POST /api/insuro/compliance/consent 호출
  // 4. 성공 시 hasValidConsent=true 업데이트
}
```

### 3.4 고급옵션 내 금소법 섹션 연동

파일: `/home/jay/projects/InsuRo/src/components/GenerateSettingsPanel.tsx`

- 기존 금소법 필터링 섹션(라인 609-713)의 면책 동의 체크박스를 useComplianceConsent 훅으로 교체
- 고급옵션에서 filter 모드를 변경해도 동일한 동의 상태 참조
- complianceAgreed prop 대신 useComplianceConsent 훅의 hasValidConsent 사용

### 3.5 면책 문구 해시 관리

프론트엔드에서 면책 문구를 상수로 관리하고 SHA-256 해시 생성:

파일: `/home/jay/projects/InsuRo/src/config/complianceConfig.ts` (신규)

```typescript
export const COMPLIANCE_DISCLAIMER = {
  version: "v1.0",
  summary: "AI 생성 콘텐츠의 금소법 준수 최종 책임은 사용자에게 있습니다",
  fullText: "본 AI 금소법 필터링 기능은 참고용이며, AI가 생성한 콘텐츠의 금융소비자보호법 준수 여부에 대한 최종 판단 및 책임은 사용자(설계사) 본인에게 있습니다. 당사는 AI 필터링의 정확성, 완전성을 보증하지 않습니다.",
  // contentHash는 빌드 시 또는 런타임에 생성
};
```

---

## 검증 시나리오 (★★★ 모두 통과해야 성공)

### 시나리오 1: 첫 방문 유저 (동의 없음)
1. 새 유저로 로그인
2. AI 콘텐츠 작성 페이지 진입
3. "블로그용" 빠른 선택 클릭
4. **확인**: 빠른 선택 아래에 amber 면책 배너가 슬라이드다운으로 나타남
5. **확인**: "콘텐츠 생성하기" 버튼이 비활성화 상태
6. 면책 문구 "전문 보기" 클릭 → 전문 표시됨
7. 체크박스 클릭 → POST /consent API 호출
8. **확인**: 버튼 활성화, 배너 축소 (동의 완료 표시)
9. 콘텐츠 생성 성공

### 시나리오 2: 기존 동의 유저 (30일 이내)
1. 이미 동의한 유저로 로그인
2. AI 콘텐츠 작성 페이지 진입
3. "블로그용" 빠른 선택 클릭
4. **확인**: 배너가 축소 상태로 표시 (동의 완료 ✓)
5. **확인**: 생성 버튼 즉시 활성화
6. 콘텐츠 생성 성공

### 시나리오 3: 동의 만료 유저 (30일 초과)
1. 동의 expires_at이 과거인 유저
2. **확인**: 배너 확장, 체크박스 미체크, 재동의 필요

### 시나리오 4: 면책 문구 버전 변경
1. compliance_versions에 v2.0 추가 (is_current=true, v1.0은 false)
2. v1.0 동의한 유저 로그인
3. **확인**: 배너 확장, "면책 내용이 업데이트되었습니다. 재동의가 필요합니다" 표시

### 시나리오 5: complianceFilter "off"
1. 고급옵션에서 금소법 필터를 "끄기"로 변경
2. **확인**: 배너 숨김, 생성 버튼 활성화 (동의 불필요)
3. **확인**: 서버에서도 동의 검증 스킵

### 시나리오 6: 서버 검증 (클라이언트 우회 방어)
1. 브라우저 콘솔에서 강제로 버튼 활성화 후 생성 시도
2. **확인**: 서버에서 403 "금소법 면책 동의가 필요합니다" 반환

### 시나리오 7: 고급옵션 내 직접 필터 변경
1. 빠른 선택 안 쓰고 고급옵션 열어서 금소법 필터를 "전체 적용"으로 변경
2. **확인**: 고급옵션 내에서도 동의 상태가 반영됨
3. 미동의 상태면 생성 버튼 비활성화

### 시나리오 8: DB 동의 기록 검증
1. 동의 완료 후 Supabase DB 확인
2. **확인**: compliance_consents에 레코드 존재
3. **확인**: user_id, consent_version, content_hash, agreed_at, ip_address, user_agent 모두 기록됨

---

## 주의사항

1. **기존 complianceAgreed state 제거하지 말 것** — useComplianceConsent 훅과 공존시키고, 기존 로직을 점진적으로 교체. 한 번에 뜯어내면 리그레션 위험.
2. **면책 문구 하드코딩 금지** — complianceConfig.ts에서 단일 소스로 관리. 서버의 compliance_versions 테이블과 동기화.
3. **feature flag 래핑** — 배포 시 환경변수로 새 UX ON/OFF 가능하게. 문제 시 즉시 롤백.
4. **모바일 반응형** — 배너가 모바일에서도 정상 표시되는지 확인 필수.
5. **e2e 테스트** — 시나리오 1~8 중 최소 시나리오 1, 2, 5, 6은 e2e 테스트로 작성.

## 참고 파일
- 프론트: `/home/jay/projects/InsuRo/src/components/GenerateSettingsPanel.tsx` (라인 354-713)
- 프론트: `/home/jay/projects/InsuRo/src/pages/Generate.tsx` (라인 134-136, 319, 487-503)
- 서버: `/home/jay/projects/InsuRo/server/main.py`
- 미팅 기록: `/home/jay/workspace/memory/meetings/2026-04-24-compliance-consent-ux.md`