d3ro-voice/docs/v3/play/05-content-reporting-operations.md
2026-08-29 18:33:45 +09:00

184 lines
8.8 KiB
Markdown

# AI 콘텐츠 신고 운영 runbook
기준일: 2026-08-24
이 문서는 D3RO Voice의 AI 결과 신고를 production에서 접수·검토·종결·정리하는
절차다. 현재는 구현과 local 검증만 완료됐으며, production 배포·운영 담당 지정·자동
purge·실제 설치본 E2E는 완료되지 않았다.
## 1. 책임과 접근 경계
출시 전에 다음 값을 실제 담당자로 교체한다.
| 역할 | 책임 | 현재 상태 |
| ----------------- | -------------------------------------------------- | -------------------------------- |
| moderation owner | 매 영업일 queue 확인, SLA와 종결 품질 관리 | `<PLACEHOLDER_MODERATION_OWNER>` |
| safety escalation | 아동 안전, 자해·폭력, 명백한 불법 콘텐츠 즉시 판단 | `<PLACEHOLDER_SAFETY_ON_CALL>` |
| privacy owner | 개인정보·계정 삭제·보관 예외 검토 | `<PLACEHOLDER_PRIVACY_OWNER>` |
| purge owner | 매일 purge 실행·실패 알림·월간 보관 감사 | `<PLACEHOLDER_PURGE_OWNER>` |
- 일반 사용자는 authenticated `content-report` Edge Function으로만 신고한다.
- `content_generation_receipts`, `content_reports`,
`content_report_review_actions``anon``authenticated`의 직접 접근을 모두
거부한다.
- queue 조회·상태 변경 RPC는 `profiles.role``manager`, `admin`,
`super_admin`인 인증 사용자만 호출할 수 있다.
- `profiles.role``profiles.tier`는 일반 사용자의 column UPDATE 권한에서 제외한다.
표시 이름, 아바타, locale만 self-service 수정이 가능하다.
- service-role key는 모바일 앱, 브라우저, 로그, 티켓, Play reviewer 안내에 넣지 않는다.
근거 마이그레이션:
- `server/supabase/migrations/20260824000029_content_reporting.sql`
- `server/supabase/migrations/20260824000030_profile_managed_field_acl.sql`
## 2. Production 배포 순서
다음 순서를 한 release window 안에서 수행한다.
1. 권위 있는 release commit과 production Supabase project ref를 기록한다.
2. migration `20260824000029`, `20260824000030`의 dry-run diff를 검토한 뒤 적용한다.
3. `llm-proxy``content-report`를 같은 source SHA에서 배포한다.
4. Talk, Commands, Actions 클라이언트를 포함한 AAB를 배포한다.
5. purge job과 실패 알림을 활성화한다.
6. Free/Pro+ 테스트 계정으로 제출→queue→검토→종결→감사 원장 E2E를 수행한다.
예시 명령의 placeholder는 실행 전에 secret manager와 release record의 실제 값으로
대체한다. 이 문서의 명령 실행 자체가 production 배포 승인인 것은 아니다.
```powershell
supabase db push --linked --dry-run
supabase db push --linked
supabase functions deploy llm-proxy --project-ref '<PROJECT_REF>'
supabase functions deploy content-report --project-ref '<PROJECT_REF>'
```
배포 직후 다음을 확인한다.
- `X-D3RO-Generation-Purpose`
`talk_response|command_response|action_response`만 허용된다.
- 성공한 provider 응답에는 UUID `X-D3RO-Generation-Id`가 있다.
- receipt에는 user/model/purpose/expiry만 있고 prompt·AI 출력은 없다.
- 신고에는 사용자가 미리 확인한 snapshot, reason, 선택 comment만 있다.
- 음성, 전체 대화, 원래 prompt는 자동 첨부되지 않는다.
- 일반 사용자 role/tier 직접 UPDATE와 ledger 직접 SELECT/INSERT가 거부된다.
## 3. Queue와 처리 SLA
운영 도구는 로그인한 manager 이상 사용자의 access token으로 다음 RPC를 호출한다.
브라우저 콘솔이나 공유 스크립트에 token을 붙여 넣지 않는다.
```ts
const { data, error } = await supabase.rpc('admin_list_content_reports_v1', {
p_status: 'pending',
p_limit: 100,
p_before: null
})
```
권장 SLA:
- 아동 성적 학대·착취 또는 즉각적인 생명 위험: 발견 즉시 escalation
- 개인정보 노출, 구체적 폭력 위협, 자해 조장: 4시간 이내 1차 검토
- 혐오·괴롭힘·성적·사기·스팸·오정보·기타: 1영업일 이내 1차 검토
- 미처리 `pending`의 최장 age가 24시간을 넘으면 moderation owner와 on-call에 알림
검토자는 snapshot과 사용자가 선택한 사유·comment만 필요한 범위에서 본다. 다른
대화, 녹음, 계정 데이터는 자동으로 확장 조회하지 않는다.
## 4. 검토와 종결
각 조치는 새 UUID idempotency key와 3~1000자의 사실 기반 note가 필요하다.
```ts
await supabase.rpc('admin_act_on_content_report_v1', {
p_report_id: reportId,
p_idempotency_key: crypto.randomUUID(),
p_action: 'begin_review',
p_note: 'Initial policy review started'
})
```
허용 전이는 다음뿐이다.
| action | 결과 상태 | 사용 조건 |
| ------------------- | ----------- | -------------------------------------------- |
| `begin_review` | `reviewing` | 담당자가 증거를 확인하기 시작함 |
| `dismiss` | `dismissed` | 정책 위반이 아니거나 증거가 불충분함 |
| `confirm_violation` | `actioned` | 정책 위반을 확인하고 별도 완화 조치를 기록함 |
`confirm_violation`은 원장 상태를 확정하지만 계정 정지나 provider 차단을 자동 실행하지
않는다. 필요한 계정·모델·prompt 정책 조치는 기존 승인된 관리자 절차에서 별도로
수행하고, 신고 note에는 비밀이나 snapshot 원문을 복제하지 말고 조치 식별자만 남긴다.
의도적인 허위·보복·반복 남용 신고는 신고 자체를 삭제해 숨기지 않는다. `dismiss`
종결하고 반복 패턴은 별도 abuse case로 escalation한다. 선의의 신고자에게 불이익을
주지 않는다.
## 5. 보관과 purge
- generation receipt: 생성 후 30일
- `pending`, `reviewing` 신고 증거: 해결할 때까지
- `dismissed`, `actioned` 신고 증거: 해결 시점부터 180일
- 계정 삭제: reporter/reviewer identity와 moderation audit actor는 `NULL`로 분리하지만,
미해결 또는 180일 보관 창의 선택 증거는 moderation·분쟁 대응을 위해 남을 수 있다.
- general audit log에는 snapshot이나 전체 생성 결과를 복제하지 않는다.
정리는 `purge_expired_content_reporting_data_v1()`만 사용한다. 이 RPC는
service-role만 실행할 수 있다. scheduler는 secret manager에서 service credential을
주입하는 서버 측 job이어야 하며, 모바일·브라우저 job은 금지한다.
성공 응답 예시는 다음과 같다.
```json
{ "purgedReports": 3, "purgedReceipts": 121 }
```
매일 실행 결과에 실행 시각, release SHA, 숫자만 기록한다. report ID, snapshot,
comment, service credential은 scheduler log에 남기지 않는다. 24시간 동안 성공 실행이
없거나 RPC가 실패하면 purge owner에게 알리고, 72시간 이내 복구되지 않으면 privacy
owner에게 escalation한다.
## 6. 모니터링과 월간 감사
운영 모니터는 원문 없이 다음 집계만 보관한다.
- status별 수와 최장 pending age
- 시간·일 rate-limit 발생 수
- 제출 4xx/5xx와 idempotency conflict 수
- review action 성공·실패 수
- 마지막 purge 성공 시각과 삭제 건수
매월 다음을 표본 확인한다.
- 일반 사용자 role/tier self-update가 계속 거부되는가
- expired receipt가 남아 있지 않은가
- resolved report가 180일 이후 purge되는가
- pending/reviewing 증거가 time purge되지 않는가
- audit log에 snapshot·comment·prompt가 복제되지 않았는가
- manager/admin/super_admin 이외 계정이 moderation RPC를 호출하지 못하는가
## 7. Release 증거와 rollback
release evidence에는 비밀이나 신고 원문 없이 다음만 보관한다.
- source commit SHA와 migration/function deployment ID
- local SQL, Edge HTTP, Deno test 결과
- Play 설치본의 Talk/Commands/Actions 신고 왕복 캡처
- queue 도달, `begin_review`, terminal action의 report ID 해시와 상태 시각
- purge job 설정, 마지막 성공 시각, alert test
- privacy/terms 공개 URL과 해당 버전의 hash
배포 후 신고 제출이나 generation receipt가 실패하면 AI 생성 경로를 정상처럼 계속
노출하지 않는다. 영향 surface를 비활성화하거나 이전 검증 release로 rollback하고,
receipt와 report schema를 임의로 drop하거나 moderation evidence를 수동 삭제하지 않는다.
## 현재 미해결 gate
- production migration/Edge/mobile 동시 배포
- manager용 moderation UI 또는 승인된 내부 operator client
- 실제 운영 담당자와 on-call 지정
- 매일 purge scheduler와 실패 알림
- provider safety/filter 검증
- Play가 서명한 실기기 신고 E2E
- Talk/Commands/Actions 밖의 AI 생성 문서 신고 범위