docs(ai-character): 관리자 API 계약 문서를 고정한다

This commit is contained in:
2026-07-28 02:19:26 +09:00
parent dc1816aea7
commit 2f93e2c9c3
4 changed files with 1843 additions and 80 deletions

View File

@@ -2,7 +2,8 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** `ADMIN`이 AI 캐릭터용 Member로 로그인하지 않고 `characterId` 기준으로 캐릭터, 콘텐츠, 시리즈, 커뮤니티, FanTalk 답변을 안전하게 대리 관리하는 신규 v2 관리자 API를 구현한다.
**Goal:** `ADMIN`이 AI 캐릭터용 Member로 로그인하지 않고 `characterId` 기준으로 캐릭터, 콘텐츠, 시리즈, 커뮤니티,
FanTalk 목록·답변을 안전하게 대리 관리하는 신규 v2 관리자 API를 구현한다.
**Architecture:** 신규 외부 경계는 `/api/v2/admin/ai-characters` 하위 controller/facade/application에 둔다. 공통 target resolver가 `characterId -> ChatCharacter.creatorMember`를 해석하고 `CREATOR + AI_CHARACTER` 불변식과 ownership을 먼저 검증한 뒤, 각 domain vertical slice가 기존 entity/repository/S3/CloudFront/event 컴포넌트를 테스트로 고정해 선택적으로 재사용한다.
@@ -13,20 +14,20 @@
| 상태 | 구현 중 |
| 작성일 | 2026-07-24 |
| 요구사항 기준 | `docs/20260724_AI캐릭터_관리자_API/prd.md` |
| API 기준 | 이 문서의 `Endpoint Contract Summary` |
| 현재 Phase | Phase 3 6차 리뷰 완료 |
| 현재 활성 Goal | `P4-T1` 대기 |
| API 기준 | `docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json` |
| 현재 Phase | Phase 2·3 레거시 계약 정합화 |
| 현재 활성 Goal | `P23-CONTRACT-2` 대기 |
## 현재 상태
| Phase | 상태 | 기존 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---:|---|---:|---|---|
| 1 | 완료 | 7/7 | 완료 | 없음 |
| 2 | 완료 | 11/11 | 완료 | 없음 |
| 3 | 완료 | 15/15 | 완료 | 없음 |
| 4 | 대기 | 0/6 | `P4-T1` | `P3-R5-GATE`, 사용자 진행 지시 |
| 2 | 후속 보완 대기 | 11/11 | `P23-CONTRACT-2` | 레거시 JSON 계약과 현재 v2 구현 정합화 |
| 3 | 후속 보완 대기 | 15/15 | `P23-CONTRACT-3` | `P23-CONTRACT-2` |
| 4 | 대기 | 0/6 | `P4-T1` | `P23-CONTRACT-GATE` |
| 5 | 대기 | 0/6 | `P5-T1` | `P4-GATE` |
| 6 | 대기 | 0/4 | `P6-T1` | `P5-GATE` |
| 6 | 대기 | 0/5 | `P6-T1` | `P5-GATE` |
| 7 | 대기 | 0/2 | `P7-T1` | Phase 1~6 Gate 완료 |
- Phase는 결과와 의존성을 묶는 문서 단위다. `create_goal`에는 Task 또는 Phase Gate 하나만 등록한다.
@@ -56,6 +57,15 @@
415 `Accept` 표준 header를 유지한다.
- 문서 작성 규칙: `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md`, `docs/agent-guides/테스트스타일.md`
- 기존 AI 캐릭터 연결 문서: `docs/20260611_AI캐릭터_크리에이터기능_최소연결/{prd.md,plan-task.md}`
- 2026-07-28 후속 확정 정책: 신규 endpoint의 JSON request/response는 레거시 필드명·타입·optional/nullable·기본값과
성공 `data` 형태를 그대로 유지한다. path로 이동한 ID만 body에서 제거한다. FanTalk 답변 작성만 계획의 축약 응답을
유지하고, FanTalk 목록은 공개 v2 `CreatorChannelFanTalkTabResponse` 필드 형태의 관리자 전용 endpoint로 제공한다.
따라서 `DEC-P2-T5-001`의 캐릭터 `isActive=false` 단독 입력 제한은 계약 차원에서 폐기하고, 레거시처럼 다른 optional
field와 동시 입력을 허용하되 비활성화만 반영한다.
- 기계 검증 가능한 API 계약 원본:
`docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json`
- 계약 근거와 예외 설명:
`docs/20260724_AI캐릭터_관리자_API/api-contract.md`
## Endpoint Contract Summary
@@ -67,7 +77,37 @@
`characterId`는 target resource endpoint의 외부 대상 식별자다. 캐릭터 목록/검색은 선택된 target이 없어 `characterId`를 받지 않고, 캐릭터 생성은 새 `ChatCharacter`를 만드는 endpoint라 path `characterId`를 받지 않는다.
모든 목록/검색 endpoint`page` 기본값 0, `size` 기본값 20, 최소 20, 최대 50 보정을 적용하고 경계값 테스트를 둔다.
목록/검색 endpoint의 query와 pagination은 `api-contract.openapi.json`에 명시한 각 레거시 API를 따른다. FanTalk 관리자
목록만 공개 v2 query policy의 `page` 기본값 0, `size` 기본값 20, 최소 20, 최대 50 보정을 적용한다.
정식 endpoint, request/response schema, 타입, required/optional/nullable, 기본값과 multipart encoding은
`api-contract.openapi.json`만 기준으로 사용한다. 사람이 읽는 레거시 근거와 생성 방법은 `api-contract.md`를 참고한다.
| Domain | operation | 구현 상태와 소유 Goal |
|---|---:|---|
| Character | 4 | runtime 정합화 필요, `P23-CONTRACT-2` |
| AudioContent | 5 | runtime 정합화 필요, `P23-CONTRACT-3` |
| Series | 9 | 구현 예정, Phase 4 |
| Community | 3 | 구현 예정, Phase 5 |
| FanTalk | 2 | 구현 예정, Phase 6 |
- 신규 path에 포함된 `characterId`, `contentId`, `seriesId`, `postId`, `fanTalkId`만 레거시 request body에서 제거한다.
- 그 밖의 JSON 필드명·타입·optional/nullable·기본값과 성공 `data` 형태는 레거시 API를 유지한다.
- 레거시 mutation의 성공 `data``null`이고 오디오 콘텐츠 생성만 `CreateAudioContentResponse(contentId)`를 반환한다.
- FanTalk 답변 작성만 승인된 축약 응답 `fanTalkId`, `replyId`, `creatorMemberId`, `content`, `createdAtUtc`를 사용한다.
- 캐릭터 수정의 `isActive=false`는 다른 optional JSON field와 함께 받을 수 있으며 레거시 의미대로 비활성화만 반영한다.
- multipart의 `request` part는 `application/json`이고 각 파일 part의 이름과 required 여부는 OpenAPI encoding을 따른다.
- 공통 오류는 400/401/403/404/405/406/415/500과 `ApiResponse.error`를 사용한다. 405의 `Allow`, 415의 `Accept`,
미지원 `Accept-Language`의 KO fallback과 Spring CORS 정책 거부 403 예외를 유지한다.
- Character·AudioContent 9개 operation은 `P23-CONTRACT-GATE` 완료 전 production 호출 호환을 보장하지 않는다.
---
## 과거 구현 계약 이력 (비규범)
아래 축약 예시는 2026-07-28 레거시 계약 확정 전 Phase 2·3 구현과 계획 변경 이력을 보존하기 위한 자료다.
클라이언트 개발, 신규 구현, 테스트의 계약으로 사용하지 않으며 위 Endpoint Contract Summary와
`api-contract.openapi.json`이 항상 우선한다.
```json
{
@@ -652,6 +692,8 @@ Response `data`:
---
## 구현 Phase
### Phase 1: 공통 ADMIN 인증과 AI 캐릭터 target resolver 기반
#### 공통 Task 실행 규칙
@@ -956,11 +998,14 @@ AI 캐릭터 목록/검색/상세/생성/수정/비활성화를 신규 ADMIN v2
- 기존 `ChatCharacterService`, `ChatCharacterCreatorMemberService`, image/S3/event 관련 컴포넌트 동작을 특성화해야 한다.
#### API endpoint와 request/response contract
- `GET /api/v2/admin/ai-characters?search=&page=&size=` -> `AiCharacterAdminListResponse(totalCount, items, page, size, hasNext)`
- `GET /api/v2/admin/ai-characters/{characterId}` -> `AiCharacterAdminDetailResponse`
- `POST /api/v2/admin/ai-characters` multipart `image?`, `request: CreateAiCharacterAdminRequest` -> detail
- `PUT /api/v2/admin/ai-characters/{characterId}` multipart `image?`, `request: UpdateAiCharacterAdminRequest` -> detail
- `UpdateAiCharacterAdminRequest.isActive=false`는 soft delete 의미다.
- 정식 schema는 `api-contract.openapi.json`의 Character operation을 따른다.
- `GET /api/v2/admin/ai-characters?searchTerm=&page=&size=` ->
`ChatCharacterListPageResponse(totalCount, content)` 또는 동일 필드의 검색 response.
- `GET /api/v2/admin/ai-characters/{characterId}` -> nested 필드 전체를 포함한 `ChatCharacterDetailResponse`.
- `POST /api/v2/admin/ai-characters` multipart 필수 `image`, 필수 `request: ChatCharacterRegisterRequest` -> `data: null`.
- `PUT /api/v2/admin/ai-characters/{characterId}` multipart optional `image`, 필수
`request: ChatCharacterUpdateRequest`에서 `id` 제외 -> `data: null`.
- update request의 `isActive=false`는 레거시 soft delete 의미다.
#### entity, repository, service 변경
- Entity: 변경 없음.
@@ -1397,29 +1442,21 @@ git diff --check
#### API endpoint와 request/response contract
- `GET /api/v2/admin/ai-characters/audio-content-themes`
- Request: query/body 없음.
- Response `data`:
```json
[
{
"themeId": 11,
"themeName": "ASMR",
"imageUrl": "https://cdn.example.com/audio-content-theme/asmr.png"
}
]
```
- 기존 크리에이터 관리자 콘텐츠 등록 화면의 콘텐츠 테마(카테고리) 조회와 같은 기능이다.
- 기존 내부/legacy DTO의 `id`, `theme`, `image` 필드명은 frontend 계약으로 노출하지 않고, 신규 v2 DTO의 `themeId`, `themeName`, `imageUrl`만 사용한다.
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents`
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
- Response: `List<GetAudioContentThemeResponse(id, theme, image)>`.
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents?search_word=&page=&size=` ->
`GetCreatorAdminContentListResponse` 전체 필드.
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}?timezone=Asia/Seoul` ->
`GetAudioContentDetailResponse` 전체 nested 필드.
- `POST /api/v2/admin/ai-characters/{characterId}/audio-contents`
- multipart `coverImage`, `audioFile`, `request` JSON string part를 사용한다.
- `request` JSON은 `title`, `description`, `tags`, `price`, `purchaseOption`, `limited`, `isAdult`, `isActive`, `themeId`, `releaseDateUtc?`, `seriesIds`, `isGeneratePreview`, `isOnlyRental`, `isPointAvailable`, `isCommentAvailable`, `isFullDetailVisible`, `previewStartTime`, `previewEndTime`, `languageCode`를 포함한다.
- legacy `CreateAudioContentRequest`의 `detail`은 v2 `description`, `releaseDate`는 UTC ISO-8601 `releaseDateUtc`로 받으며 facade에서 기존 pipeline 입력으로 변환한다.
- multipart 필수 `contentFile`, `coverImage`, `request: CreateAudioContentRequest`.
- Response: `CreateAudioContentResponse(contentId)`.
- `PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
- `audioFile` 교체는 기존 creator/admin 수정 pipeline에 없는 동작이므로 Phase 3 범위에서는 제공하지 않는다. 오디오 파일 교체가 필요하면 별도 upload/processing parity 설계 후 추가한다.
- response item은 현 v2 목록 계약을 유지한다. response detail에는 기존 `GetAudioContentDetailResponse`의 필드 전체를 포함하고, `description`, `audioSignedUrl`, `releaseDateUtc`, `seriesIds`, `createdAtUtc`, `updatedAtUtc` 같은 v2 관리자 필드도 유지한다. 구매·좋아요·핀·추천·댓글 목록처럼 viewer 상태가 필요한 legacy 상세 필드는 관리자 상세에서 안전한 기본값을 반환한다.
- multipart optional `coverImage`, 필수 `request: UpdateCreatorAdminContentRequest`에서 `id` 제외.
- Response: `data: null`.
- 수정 `audioFile` 교체는 레거시 creator admin 수정 pipeline에 없어 제공하지 않는다.
- 정식 전체 schema와 optional/nullable은 `api-contract.openapi.json`의 AudioContent operation을 따른다. 현재 구현의
`description`, `releaseDateUtc`, `audioSignedUrl`, `status`, `seriesIds`, `themeName`, `imageUrl` 별칭은
`P23-CONTRACT-3`에서 레거시 계약으로 정합화한다.
#### entity, repository, service 변경
- Entity: 변경 없음.
@@ -1964,6 +2001,135 @@ git diff --check
---
### Phase 2·3 후속: 레거시 JSON 계약 정합화
기존 Phase 2·3 완료 이력은 보존한다. 2026-07-28 확정된 레거시 필드명·전체 payload 이관 정책에 따라 문서 계약을 먼저
고정하고, 현재 구현된 캐릭터·오디오 콘텐츠 v2 DTO와 endpoint를 별도 후속 Goal에서 정합화한다.
- [x] **Task 3.16: 전체 API OpenAPI 계약 고정**
**Goal 실행 `P23-CONTRACT-1`:** 23개 endpoint의 request/response를 레거시 DTO 전체 필드와 직접 대조해 OpenAPI 3.1
JSON과 설명 문서로 고정한다.
- **시작 조건:** 사용자 확정 정책과 시리즈 미연결 콘텐츠 검색 endpoint 분리 결정.
- **완료 증거:** JSON 문법·OpenAPI lint/validate·TypeScript client 생성 및 `tsc --noEmit` 성공, 23개 operation과 누락 `$ref` 0건,
`./gradlew tasks --all` 성공 및 검증 기록.
- **범위 밖:** production DTO/controller/test 수정, legacy/public endpoint 변경.
- **TDD 예외 사유:** 실행 코드를 변경하지 않는 계약 문서 작성 Task다.
- **대체 검증 방법:** 레거시 Kotlin DTO의 생성자 필드와 OpenAPI schema를 대조하고 두 validator와 client generator로
기계 검증한다.
**Files:**
- Modify: `docs/20260724_AI캐릭터_관리자_API/prd.md`
- Modify: `docs/20260724_AI캐릭터_관리자_API/plan-task.md`
- Create: `docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json`
- Create: `docs/20260724_AI캐릭터_관리자_API/api-contract.md`
- [x] 캐릭터, 테마·오디오 콘텐츠, 시리즈·연결 콘텐츠 검색, 커뮤니티, FanTalk의 레거시 DTO 전체 필드를 schema로
옮긴다.
- [x] path로 이동한 ID만 body에서 제거하고 나머지 query/body/response 필드명은 레거시와 동일하게 유지한다.
- [x] 레거시 mutation의 `data: null`, 오디오 생성의 `data.contentId`, FanTalk 답변 축약 응답 예외를 operation별로
고정한다.
- [x] FanTalk 관리자 목록과 시리즈 미연결 콘텐츠 검색을 별도 operation으로 포함해 총 23개 endpoint를 검증한다.
- [x] OpenAPI lint/validate, TypeScript Fetch client 생성과 `tsc --noEmit`을 실행하고 결과를 기록한다.
- [ ] **Task 3.17: Phase 2 캐릭터 runtime 계약 정합화**
**Goal 실행 `P23-CONTRACT-2`:** 현재 구현된 캐릭터 4개 endpoint를
`api-contract.openapi.json`의 레거시 필드명·전체 request/response·mutation 응답에 맞춘다.
- **시작 조건:** `P23-CONTRACT-1` 완료.
- **완료 증거:** 4개 actual endpoint의 exact JSON field/required/nullable/multipart/`data: null` RED/GREEN,
Phase 2 focused·legacy 회귀와 Progress 기록.
- **범위 안:** `isActive=false`와 다른 optional JSON field의 동시 입력 허용 및 나머지 JSON field 미반영이라는 레거시
request 의미 복구.
- **범위 밖:** 외부 API·ownership·soft delete persistence 결과 변경, 신규 character business behavior.
**Files:**
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterController.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterDto.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterMapper.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerTest.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt`
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterizationTest.kt`
- [ ] **RED:** 목록 `content`, 상세 전체 nested 필드, 생성 전체 request와 필수 `image`, 수정 optional 필드,
`isActive=false`와 다른 optional JSON field의 동시 입력·미반영, 생성·수정 `data: null` exact JSON 테스트를 작성해
현재 v2 축약/변환 DTO와의 불일치를 확인한다.
- [ ] **GREEN:** business pipeline을 바꾸지 않는 최소 DTO/controller/facade/mapper 변경으로 OpenAPI 계약을 통과시킨다.
- [ ] **REFACTOR:** Phase 2 actual endpoint와 legacy characterization, 공통 오류 계약을 회귀하고 결과를 기록한다.
```bash
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*' \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
```
- [ ] **Task 3.18: Phase 3 오디오 콘텐츠 runtime 계약 정합화**
**Goal 실행 `P23-CONTRACT-3`:** 현재 구현된 테마·오디오 콘텐츠 5개 endpoint를
`api-contract.openapi.json`의 레거시 필드명·전체 request/response·성공 응답에 맞춘다.
- **시작 조건:** `P23-CONTRACT-2` 완료.
- **완료 증거:** 5개 actual endpoint의 exact query/multipart/JSON schema RED/GREEN, Phase 3 focused·legacy 회귀와
Progress 기록.
- **범위 밖:** upload/processing pipeline, signed URL 정책, series 연결 behavior 변경.
**Files:**
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentController.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentDto.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentMapper.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentCreateTest.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentQueryTest.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentThemeControllerTest.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentUpdateTest.kt`
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentOwnershipTest.kt`
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/LegacyCreatorAdminAudioContentCharacterizationTest.kt`
- [ ] **RED:** 테마 `id/theme/image`, 목록 전체 item, 상세 전체 nested DTO, 생성 `contentFile`과
`CreateAudioContentRequest`, 수정 `UpdateCreatorAdminContentRequest` 및 각 성공 `data` 형태를 exact JSON으로 고정한다.
- [ ] **GREEN:** 기존 business pipeline과 signed URL 계산을 유지하는 최소 DTO/controller/facade/mapper adapter를 구현한다.
- [ ] **REFACTOR:** Phase 3 actual endpoint·legacy characterization·ownership/error 계약을 회귀하고 결과를 기록한다.
```bash
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*' \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
```
#### Phase 2·3 계약 정합화 Gate
**Goal 실행 `P23-CONTRACT-GATE`:** 문서 계약과 구현된 9개 endpoint의 runtime 응답이 일치하고 Phase 4가 같은 계약을
소비할 수 있는지 판정한다.
- [ ] **`P23-CONTRACT-GATE` 완료:** `P23-CONTRACT-1`~`P23-CONTRACT-3` 완료 후 character/content focused·legacy
회귀, OpenAPI validate/client 생성과 `ktlintCheck`를 fresh 실행한다.
- **범위 밖:** Gate에서 직접 production code 수정, Phase 4 이후 기능 구현.
```bash
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*' \
--tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*' \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
npx --yes @openapitools/openapi-generator-cli validate \
-i docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
npx --yes @openapitools/openapi-generator-cli generate \
-i docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json \
-g typescript-fetch \
-o /tmp/ai-character-admin-typescript-client
```
---
### Phase 4: 시리즈 관리 vertical slice
#### 목표
@@ -1979,11 +2145,15 @@ git diff --check
- 기존 creator series 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior를 통과하는 특성화 테스트로 먼저 고정해야 한다.
#### API endpoint와 request/response contract
- 정식 전체 schema는 `api-contract.openapi.json`의 Series operation 9개를 따른다.
- `GET/POST/PUT /api/v2/admin/ai-characters/{characterId}/series...`
- `GET /series/{seriesId}/contents` query: `search?`, `page`, `size`
- `POST /series/{seriesId}/contents` request: `AddAiCharacterAdminSeriesContentsRequest(contentIds: List<Long>)`
- `GET /series/{seriesId}/contents` query: `page`, `size`; response:
`GetCreatorAdminContentSeriesContentResponse(totalCount, items)`
- `GET /series/{seriesId}/contents/search` query: 필수 `search_word`; response:
`List<SearchContentNotInSeriesResponse>`
- `POST /series/{seriesId}/contents` request: `AddingContentToTheSeriesRequest(contentIdList: List<Long>)`
- `DELETE /series/{seriesId}/contents/{contentId}`
- `PUT /series/orders` request: `UpdateAiCharacterAdminSeriesOrdersRequest(seriesIds: List<Long>)`
- `PUT /series/orders` request: `UpdateOrdersRequest(ids: List<Long>)`
#### entity, repository, service 변경
- Entity: 변경 없음.
@@ -2028,7 +2198,7 @@ git diff --check
**Goal 실행 `P4-T1`:** 기존 creator series의 CRUD·연결·검색·순서 동작을 신규 v2 구현의 비교 기준으로 고정한다.
- **시작 조건:** 최신 Phase 3 후속 Gate인 `P3-R5-GATE` 완료와 사용자 진행 지시.
- **시작 조건:** Phase 2·3 runtime 계약 정합화의 `P23-CONTRACT-GATE` 완료와 사용자 진행 지시.
- **완료 증거:** production 변경 전 특성화 테스트 통과, 관찰된 오류·side-effect 정책과 Progress 기록.
- **범위 밖:** 신규 v2 series production code 구현.
@@ -2044,10 +2214,10 @@ git diff --check
- [ ] **Task 4.2: 시리즈 목록·상세 조회 구현**
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 pagination 계약으로 제공한다.
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 레거시 response 계약으로 제공한다.
- **시작 조건:** `P4-T1` 완료와 Phase 4 오류 계약의 계획 반영.
- **완료 증거:** 목록·상세·inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
- **완료 증거:** 목록·상세 전체 필드, inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
- **범위 밖:** 시리즈 mutation, 콘텐츠 연결, 순서 변경.
**Files:**
@@ -2060,7 +2230,8 @@ git diff --check
- [ ] 미구현 목록·상세, inactive 제외, cross-owner 거부와 pagination 경계 실패 test를 작성한다.
- [ ] owner-scoped 목록·상세 최소 구현으로 focused test를 통과시킨다.
- [ ] `page` 기본 0, `size` 기본·최소 20·최대 50과 legacy DTO 비노출을 검증한다.
- [ ] 목록 `totalCount/items`와 item 전체 필드, 상세의 문자열 `publishedDaysOfWeek/genre/keywords/state`를
`api-contract.openapi.json`과 exact JSON으로 검증한다.
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
- [ ] **Task 4.3: 시리즈 생성·수정·soft delete 구현**
@@ -2088,7 +2259,8 @@ git diff --check
**Goal 실행 `P4-T4`:** 동일 owner의 시리즈와 콘텐츠만 검색·연결·해제할 수 있도록 한다.
- **시작 조건:** `P4-T2`, `P4-T3`와 Phase 3 owner query 계약 완료.
- **완료 증거:** 검색·pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과 Progress 기록.
- **완료 증거:** 연결 목록·미연결 검색의 분리된 응답, pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과
Progress 기록.
- **범위 밖:** 콘텐츠 자체 수정, 시리즈 순서 변경.
**Files:**
@@ -2098,9 +2270,10 @@ git diff --check
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesRepository.kt`
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesContentTest.kt`
- [ ] 콘텐츠 조회·검색·연결·해제와 cross-owner ID 실패 test를 작성한다.
- [ ] `GET .../contents`와 `GET .../contents/search?search_word=...`의 별도 응답, 연결·해제와 cross-owner ID 실패
test를 작성한다.
- [ ] 모든 series/content ID를 mutation 전에 검증하는 최소 구현을 통과시킨다.
- [ ] 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
- [ ] `contentIdList` 필드와 mutation `data: null`, 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
- [ ] focused/Phase 3 owner query 회귀와 `ktlintCheck` 결과를 Progress에 기록한다.
- [ ] **Task 4.5: owner-scoped 시리즈 순서 변경 구현**
@@ -2168,10 +2341,14 @@ git diff --check
- 기존 community write behavior 특성화 테스트.
#### API endpoint와 request/response contract
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts`
- `POST /api/v2/admin/ai-characters/{characterId}/community-posts`
- `PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`
- update request는 `isFixed`, `isActive`, 본문/이미지/오디오/가격 필드를 포함한다.
- 정식 전체 schema는 `api-contract.openapi.json`의 Community operation 3개를 따른다.
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts?timezone=&page=&size=` ->
`List<GetCommunityPostListResponse>`.
- `POST /api/v2/admin/ai-characters/{characterId}/community-posts`는 optional `audioFile`, optional `postImage`, 필수
`request: CreateCommunityPostRequest`를 받고 `data: null`을 반환한다.
- `PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`는 optional `postImage`, 필수 request를 받는다.
- update request는 두 레거시 update DTO에서 ID를 제외한 `content`, `isCommentAvailable`, `isAdult`, `isActive`,
`isFixed`만 포함하고 `data: null`을 반환한다. 수정 `audioFile`, `price`는 레거시 계약에 없어 포함하지 않는다.
#### entity, repository, service 변경
- Entity: 변경 없음.
@@ -2340,13 +2517,15 @@ git diff --check
---
### Phase 6: FanTalk 답변 vertical slice
### Phase 6: FanTalk 목록·답변 vertical slice
#### 목표
선택한 AI 캐릭터 자신의 활성 root FanTalk에만 creator reply를 작성하는 v2 관리자 API를 제공한다.
선택한 AI 캐릭터의 FanTalk 목록을 관리자 전용 경계에서 조회하고 자신의 활성 root FanTalk에만 creator reply를 작성하는
v2 관리자 API를 제공한다.
#### 범위와 비범위
- 포함: root FanTalk 존재/활성/owner 검증, creator reply 저장, 언어 감지와 기존 응답 의미 parity.
- 포함: 관리자용 root FanTalk/creator reply 목록, root FanTalk 존재/활성/owner 검증, creator reply 저장, 언어 감지와
기존 응답 의미 parity.
- 제외: FanTalk 원글 작성, nested reply, 구매/댓글형 기능, AI 캐릭터 일반 사용자 활동.
#### 선행 Phase 및 의존성
@@ -2354,14 +2533,19 @@ git diff --check
- 기존 FanTalk 저장 엔티티와 응답 DTO 의미 특성화.
#### API endpoint와 request/response contract
- 정식 전체 schema는 `api-contract.openapi.json`의 FanTalk operation 2개를 따른다.
- `GET /api/v2/admin/ai-characters/{characterId}/fan-talks?page=0&size=20`
- Response: 공개 v2 `CreatorChannelFanTalkTabResponse`와 동일한
`fanTalkCount`, `fanTalks`, `page`, `size`, `hasNext` 및 nested root/reply 필드
- `POST /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies`
- Request: `CreateAiCharacterAdminFanTalkReplyRequest(content: String)`
- Response: `AiCharacterAdminFanTalkReplyResponse(fanTalkId, replyId, creatorMemberId, content, createdAtUtc)`
#### entity, repository, service 변경
- Entity: 변경 없음.
- Repository: `CreatorCheers` 또는 FanTalk repository에 root/active/creator owner 조회 adapter 추가 가능.
- Service: 신규 FanTalk reply application service 구현. target 검증 후 기존 저장/언어 감지 로직을 필요한 만큼 재사용한다.
- Repository: 관리자 목록용 owner-scoped root/reply 조회와 root/active/creator owner 검증 adapter 추가한다.
- Service: 관리자 목록은 공개 v2 DTO 형태로 조립하되 viewer/block 필터를 적용하지 않는다. reply는 target 검증 후 기존
저장/언어 감지 로직을 필요한 만큼 재사용한다.
#### DB migration
- 없음.
@@ -2376,23 +2560,24 @@ git diff --check
#### acceptance criteria
- target AI character는 자신의 활성 root FanTalk에만 답변할 수 있다.
- 관리자는 공개 v2와 동일한 필드 형태로 target AI character의 root FanTalk와 creator reply를 조회할 수 있다.
- cross-character, nested parent, inactive/missing FanTalk는 4xx이며 reply 저장과 이벤트 발행이 없다.
- 저장된 답변의 writer/creator는 해석된 creatorMember와 일관된다.
#### targeted test
- Characterization: `LegacyFanTalkReplyCharacterizationTest`.
- V2 RED/GREEN: `AiCharacterAdminFanTalkReplyServiceTest`.
- V2 RED/GREEN: `AiCharacterAdminFanTalkQueryTest`, `AiCharacterAdminFanTalkReplyServiceTest`.
- Run: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`
#### 전체 회귀 테스트 영향
- 기존 FanTalk 조회/작성 관련 테스트가 통과해야 한다.
#### rollback 전략
- 신규 FanTalk reply v2 admin route/facade를 제거한다.
- 신규 FanTalk 목록·reply v2 admin route/facade를 제거한다.
- 신규 DDL이 없으므로 schema rollback은 없다.
#### 권장 commit 경계
- `feat: add ai character admin fan talk reply slice`
- `feat: add ai character admin fan talk slice`
- [ ] **Task 6.1: 기존 FanTalk reply 의미 특성화 baseline 고정**
@@ -2412,11 +2597,32 @@ git diff --check
- [ ] production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
- [ ] fixture만 정리하고 test와 `ktlintCheck` 결과를 Progress에 기록한다.
- [ ] **Task 6.2: FanTalk root reply 저장 구현**
- [ ] **Task 6.2: FanTalk 관리자 목록 조회 구현**
**Goal 실행 `P6-T2`:** 선택한 AI 캐릭터의 활성 root FanTalk creator reply를 저장하고 전용 응답을 반환한다.
**Goal 실행 `P6-T2`:** 선택한 AI 캐릭터의 root FanTalk creator reply를 공개 v2 응답 필드 형태로 조회한다.
- **시작 조건:** `P6-T1` 완료와 Phase 6 오류 계약의 계획 반영.
- **완료 증거:** owner-scoped 목록·nested reply·pagination exact JSON RED/GREEN과 Progress 기록.
- **범위 밖:** 공개 v2 endpoint 변경, viewer/block 필터 재사용, FanTalk 원글 작성.
**Files:**
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkController.kt`
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkDto.kt`
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkFacade.kt`
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkRepository.kt`
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkQueryTest.kt`
- [ ] **RED:** `fanTalkCount/fanTalks/page/size/hasNext`와 root/reply 전체 필드, target owner, pagination 실패
test를 작성한다.
- [ ] **GREEN:** 공개 v2 DTO 필드 형태를 유지하면서 관리자 target 정책으로 조회하는 최소 구현을 통과시킨다.
- [ ] **REFACTOR:** 공개 v2 endpoint 계약 불변, viewer/block 필터 비적용과 ADMIN 인가를 회귀하고 기록한다.
- [ ] **Task 6.3: FanTalk root reply 저장 구현**
**Goal 실행 `P6-T3`:** 선택한 AI 캐릭터의 활성 root FanTalk에 creator reply를 저장하고 전용 응답을 반환한다.
- **시작 조건:** `P6-T1`, `P6-T2` 완료와 Phase 6 오류 계약의 계획 반영.
- **완료 증거:** 정상 저장·언어 감지·DTO·writer/creator RED/GREEN과 Progress 기록.
- **범위 밖:** FanTalk 원글, nested reply, 일반 사용자 대리 작성.
@@ -2433,11 +2639,11 @@ git diff --check
- [ ] writer/creator가 resolver의 creatorMember와 일치하고 principal impersonation이 없음을 검증한다.
- [ ] focused/legacy characterization test와 `ktlintCheck` 결과를 Progress에 기록한다.
- [ ] **Task 6.3: FanTalk target·root·ownership 거부 구현**
- [ ] **Task 6.4: FanTalk target·root·ownership 거부 구현**
**Goal 실행 `P6-T3`:** cross-character, nested, inactive, missing FanTalk를 저장·이벤트 없이 거부한다.
**Goal 실행 `P6-T4`:** cross-character, nested, inactive, missing FanTalk를 저장·이벤트 없이 거부한다.
- **시작 조건:** `P6-T1`, `P6-T2` 완료.
- **시작 조건:** `P6-T1`~`P6-T3` 완료.
- **완료 증거:** 네 거부 분기의 정확한 오류 계약, DB/event 0건과 Progress 기록.
- **범위 밖:** 새로운 중복 답변 차단 정책.
@@ -2452,11 +2658,11 @@ git diff --check
- [ ] 각 실패의 정확한 status/message key/KO·EN·JA와 reply insert/event 0건을 검증한다.
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
- [ ] **Task 6.4: Phase 6 보안·오류·회귀 검증**
- [ ] **Task 6.5: Phase 6 보안·오류·회귀 검증**
**Goal 실행 `P6-T4`:** FanTalk reply endpoint의 ADMIN·오류 계약과 기존 FanTalk 회귀를 고정한다.
**Goal 실행 `P6-T5`:** FanTalk 목록·reply endpoint의 ADMIN·오류 계약과 기존 FanTalk 회귀를 고정한다.
- **시작 조건:** `P6-T2`, `P6-T3` 완료.
- **시작 조건:** `P6-T2`~`P6-T4` 완료.
- **완료 증거:** 권한 매트릭스, request binding/domain 오류, legacy 회귀와 Progress 기록.
- **범위 밖:** Phase 7 외 전체 기능 수정.
@@ -2472,11 +2678,11 @@ git diff --check
#### Phase 6 Gate
**Goal 실행 `P6-GATE`:** Phase 6 FanTalk reply의 root·ownership·저장·회귀 품질을 최종 판정한다.
**Goal 실행 `P6-GATE`:** Phase 6 FanTalk 목록·reply의 조회·root·ownership·저장·회귀 품질을 최종 판정한다.
- [ ] **`P6-GATE` 완료:** 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다.
- **시작 조건:** `P6-T1`~`P6-T4` 완료.
- **시작 조건:** `P6-T1`~`P6-T5` 완료.
- **완료 증거:** `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`와
`./gradlew ktlintCheck` 성공, Progress 기록.
- **범위 밖:** 실패와 무관한 신규 기능.
@@ -2496,7 +2702,7 @@ git diff --check
- Phase 1~6 완료.
#### API endpoint와 request/response contract
- Endpoint Contract Summary의 모든 endpoint가 구현되어야 한다.
- `api-contract.openapi.json`의 23개 operation이 모두 구현되어야 한다.
- 모든 신규 endpoint가 JWT ADMIN + 현재 DB ADMIN 이중 인가와 공통 오류 envelope/i18n을 공유해야 한다.
- legacy/public endpoint URI와 성공·오류 status/body/message diff가 없어야 한다.
@@ -2614,10 +2820,11 @@ git diff --check
| 12 | `P3-R5` → `P3-R6` → `P3-R4-GATE` | `P2-R4-GATE`, 5차 Phase 3 review | 아니요 | `REV-016`~`REV-017` 소유 Task에서 수정·증거 보강 |
| 13 | `P2-R5` → `P2-R5-GATE` | 6차 Phase 2 review | 아니요 | `REV-018` 문서 계약 동기화 |
| 14 | `P3-R7` → `P3-R8` → `P3-R5-GATE` | `P2-R5-GATE`, 6차 Phase 3 review | 아니요 | `REV-019`~`REV-020` 소유 Task에서 수정·증거 보강 |
| 15 | `P4-T1`~`P4-T6` → `P4-GATE` | `P3-R5-GATE`, 사용자 진행 지시 | 아니요 | 실패 소유 Task로 되돌림 |
| 16 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
| 17 | `P6-T1`~`P6-T4` → `P6-GATE` | `P5-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
| 18 | `P7-T1``P7-T2` → `P7-GATE` | Phase 1~6 최신 Gate | 아니요 | 실패 소유 Phase에 회귀 수정 Goal 추가 |
| 15 | `P23-CONTRACT-1` → `P23-CONTRACT-2` → `P23-CONTRACT-3` → `P23-CONTRACT-GATE` | `P3-R5-GATE`, 사용자 계약 확정 | 아니요 | 문서 또는 runtime 불일치 소유 Goal에서 수정 |
| 16 | `P4-T1`~`P4-T6` → `P4-GATE` | `P23-CONTRACT-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
| 17 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
| 18 | `P6-T1`~`P6-T5` → `P6-GATE` | `P5-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
| 19 | `P7-T1` → `P7-T2` → `P7-GATE` | Phase 1~6 최신 Gate | 아니요 | 실패 소유 Phase에 회귀 수정 Goal 추가 |
## 변경 금지·중단 규칙
@@ -2842,6 +3049,38 @@ git diff --check
불일치를 확인해 `처리 완료`로 동기화했다.
- 남은 항목: 없음. 다음 Goal은 `P4-T1`이지만 사용자 진행 지시 전까지 시작하지 않는다.
### `P23-CONTRACT-1` 완료 — 2026-07-28
- 상태: 완료
- 무엇을: 캐릭터, 테마·오디오 콘텐츠, 시리즈, 커뮤니티, FanTalk의 신규 관리자 API 23개 request/response를
OpenAPI 3.1 JSON과 설명 문서로 고정했다.
- 왜: 신규 path와 관리자 target만 적용하고 클라이언트 JSON 계약은 레거시 필드명·타입·optional/nullable·기본값과 성공
`data` 형태를 그대로 이관해야 하기 때문이다.
- 어떻게: 레거시 Kotlin DTO·controller·service와 schema를 대조하고 path ID만 body에서 제거했다. FanTalk 답변 축약 응답,
관리자 FanTalk 목록, 분리된 시리즈 미연결 콘텐츠 검색만 확정 예외로 반영했다. 독립 리뷰에서 확인한
`Accept-Language` fallback, 캐릭터 비활성화 혼합 입력, 시리즈 상세 문자열 `state`, Phase 4 선행 Gate 문제를 교정했다.
- 결과: JSON parse, 내부 `$ref` 누락 0건, 고유 operation 23개와 상태 9/14를 확인했다. Redocly lint와 OpenAPI Generator
validate가 통과했고 `typescript-fetch` 생성 후 TypeScript 5.9.3 `tsc --noEmit`도 성공했다.
`./gradlew tasks --all`은 `BUILD SUCCESSFUL in 898ms`였으며 독립 최종 리뷰는 Critical 0, Important 0이었다.
- 남은 항목: 현재 구현된 캐릭터 4개 endpoint의 runtime DTO를 맞추는 `P23-CONTRACT-2`. production code는 변경하지 않았다.
### `P23-CONTRACT-2`~`P7-GATE` 실행 계획 보완 — 2026-07-28
- 상태: 구현 시작 준비 완료
- 무엇을: `P23-CONTRACT-2`·`P23-CONTRACT-3`의 실제 facade와 전용 query/theme/ownership/legacy test 범위를 보강하고,
Task별 focused 명령을 추가했다. Phase 4~6은 각 OpenAPI tag를 정식 schema로, Phase 7은 OpenAPI 23개 operation을
최종 구현 기준으로 명시했다.
- 왜: 확정 계약이 있어도 실제 request parsing·response 조립을 담당하는 facade와 전용 테스트가 계획에서 누락되면
구현 중 범위가 다시 흔들릴 수 있기 때문이다.
- 어떻게: OpenAPI operation 23개와 Character 4, AudioContent 5, Series 9, Community 3, FanTalk 2 분류를 Phase/Goal과
대조하고 기존 source/test 파일 존재, Goal ID 중복, 선행 Gate와 PRD Open Questions를 확인했다.
- 결과: 과거 축약 예시는 비규범 이력으로 분리했고 현재 Endpoint Contract Summary는 OpenAPI와 같은 9/14 상태 및 예외만
제공한다. Task 3.17부터 `P7-GATE`까지 시작 조건·파일·RED/GREEN/REFACTOR·검증 명령이 연결됐으며
`./gradlew tasks --all`은 `BUILD SUCCESSFUL in 922ms`였다.
- 제외: 생성 TypeScript의 `tsc` 실행은 클라이언트 build/CI 책임으로 두고 server runtime `P23-CONTRACT-GATE`에는 추가하지
않았다. `P23-CONTRACT-1`의 일회성 생성·컴파일 검증 기록은 유지한다.
- 다음 행동: production 변경 없이 문서 보완만 완료했다. 구현 시작 Goal은 `P23-CONTRACT-2`다.
## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
@@ -2856,6 +3095,10 @@ git diff --check
| 2026-07-28 | `DEC-REVIEW-005` | 확정 | 5차 보완과 완료 이력은 보존하고, 추가로 확인한 문서 계약·empty-file 경계·detached publisher 증거 문제를 `REV-018`~`REV-020`과 새 후속 Task/Gate로 추적한다. | fresh focused 216건과 lint는 통과했지만 Endpoint Contract Summary, `MultipartFile.isEmpty` 처리와 실제 service publisher field를 코드·test 단위로 대조해 세 문제가 재현됐다. | `P2-R5`~`P3-R5-GATE`, 두 6차 review |
| 2026-07-28 | `DEC-P3-R7-001` | 확정 | 생성의 빈 `coverImage`·`audioFile`은 400 `common.error.invalid_request`로 거부한다. 수정의 빈 `coverImage`는 생략으로 정규화하고, 수정 `audioFile`은 미지원이므로 part가 존재하면 크기와 관계없이 400으로 거부한다. | non-null binding은 part 누락만 차단하며 빈 파일은 0-byte upload와 cover 교체를 유발할 수 있다. optional cover의 빈 part는 일반 multipart client의 생략 표현으로 안전하게 처리할 수 있지만 미지원 audio part는 존재 자체가 계약 위반이다. | `P3-R7`, `REV-019`, Endpoint Contract Summary |
| 2026-07-28 | `DEC-REVIEW-006` | 확정 | Phase 2·3 6차 보완의 코드와 완료 이력은 유지하고 새 production Goal은 추가하지 않는다. 종결 Gate가 있는 `REV-001`~`REV-009`의 종합 표 상태만 `처리 완료`로 동기화한다. | fresh focused 130건이 모두 통과했고 6차 보완 범위의 추가 production 결함은 재현되지 않았지만, 하단 종합 표 상태가 각 Gate·Progress의 처리 완료 판정과 모순됐다. | `P2-GATE`, `P2-R2-GATE`, `P3-GATE`, 하단 발견된 문제 표 |
| 2026-07-28 | `DEC-API-CONTRACT-001` | 확정 | 신규 관리자 endpoint는 레거시 request/response의 필드명·타입·optional/nullable·기본값·성공 `data` 형태를 유지하고 path로 이동한 ID만 body에서 제거한다. FanTalk 답변은 축약 응답을 유지한다. | 신규 endpoint의 목적이 로그인 불가능한 AI 캐릭터를 관리자 경계로 대리 관리하는 것이며, 클라이언트 계약까지 임의로 재설계하는 범위가 아니기 때문이다. | PRD, `api-contract.openapi.json`, `P23-CONTRACT-1`~`P23-CONTRACT-GATE` |
| 2026-07-28 | `DEC-API-CONTRACT-002` | 확정 | FanTalk 목록은 공개 v2 field 형태를 유지하는 관리자 전용 endpoint로 추가하고, 시리즈 연결 목록과 미연결 검색은 응답 형태가 달라 별도 endpoint로 분리한다. | 공개 v2 직접 호출은 `creatorId`, viewer/block filter와 CORS 경계가 관리자 요구와 다르고, 시리즈 두 legacy API의 응답은 wrapper와 direct array로 서로 다르다. | Phase 4, Phase 6, `api-contract.openapi.json` |
| 2026-07-28 | `DEC-API-CONTRACT-003` | 확정 | `DEC-P2-T5-001`의 캐릭터 `isActive=false` 단독 입력 제한을 최신 JSON 계약에서 폐기한다. `ChatCharacterUpdateRequest`처럼 다른 optional field와 동시 입력을 허용하고, `isActive=false`이면 레거시 service와 같이 비활성화만 반영한다. | 사용자 확정 원칙은 path ID만 제거하고 레거시 request를 그대로 이관하는 것이다. 레거시 controller는 혼합 request를 받으며 service는 비활성화 분기에서 나머지 JSON field를 적용하지 않는다. | `api-contract.openapi.json`, `P23-CONTRACT-2` |
| 2026-07-28 | `DEC-API-CONTRACT-004` | 확정 | 생성된 TypeScript의 `tsc` 실행은 클라이언트 build/CI 책임으로 두고 server runtime `P23-CONTRACT-GATE` 완료 조건에는 추가하지 않는다. | 서버 Gate는 실제 HTTP runtime과 OpenAPI validate/client 생성 가능성을 판정하며, 언어별 client compile은 소비 클라이언트의 toolchain에서 검증해야 한다. 계약 작성 Task의 일회성 TypeScript 생성·컴파일 검증은 이미 완료됐다. | `P23-CONTRACT-GATE`, `api-contract.md` |
## 발견된 문제
@@ -2883,6 +3126,20 @@ git diff --check
| `REV-020` | Low | 처리 완료 | Phase 3 ownership/domain test의 event no-interaction mock이 실제 `AudioContentService`·`CreatorAdminContentService` publisher field에 연결되지 않았다. | `P3-R8`, `P3-R5-GATE` | 실제 두 service proxy target의 publisher를 mock으로 교체·복원하고 identity/no-interaction을 단언했다. |
## 검증 기록
- 실행 계획 동기화 검증(2026-07-28): OpenAPI 23개 고유 operation과 Character 4, AudioContent 5, Series 9,
Community 3, FanTalk 2 분류를 현재 Endpoint Contract Summary 및 `P23-CONTRACT-2`~Phase 7과 대조했다. Goal ID 중복은
없고 PRD Open Questions는 `없음`이며 Task 3.17·3.18에 추가한 production/test/characterization 파일의 존재를 확인했다.
- 실행 계획 명령 유효성(2026-07-28): `./gradlew tasks --all` 실행 결과 `test`, `ktlintCheck`, `tasks`가 존재했고
`BUILD SUCCESSFUL in 922ms`였다. `git diff --check`, `git diff --cached --check`는 출력이 없었다.
- `P23-CONTRACT-1` OpenAPI 검증(2026-07-28): `jq empty`, 23개 고유 operation과
`implemented-contract-alignment-required` 9개/`planned` 14개 assertion, 내부 `$ref` 해석을 실행해 모두 성공했다.
`npx --yes @redocly/cli lint --skip-rule info-license ...`는 `valid`, OpenAPI Generator `validate`는
`No validation issues detected`였다.
- `P23-CONTRACT-1` 클라이언트 검증(2026-07-28): OpenAPI Generator 7.24.0 `typescript-fetch` 생성을 완료하고,
생성된 23개 Raw operation(Character 4, AudioContent 5, Series 9, Community 3, FanTalk 2)을 확인했다.
`tsc --noEmit --target ES2020 --module commonjs --lib ES2020,DOM .../index.ts`는 TypeScript 5.9.3에서 성공했다.
- `P23-CONTRACT-1` 문서·리뷰 Gate(2026-07-28): `./gradlew tasks --all`은 `BUILD SUCCESSFUL in 898ms`였고,
독립 레거시 DTO/controller/service 대조 및 생성물 재검증 결과 Critical 0, Important 0이었다.
- Phase 2·3 6차 보완 재점검 focused 검증(2026-07-28): `./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentUpdateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentOwnershipTest` 실행 결과 `BUILD SUCCESSFUL in 4m 19s`였다. 다섯 XML 합계 130건의 failure/error/skipped는 모두 0이었다.
- Phase 2·3 6차 보완 재점검 문서 명령 유효성(2026-07-28): `./gradlew tasks --all` 실행 결과 `test`, `ktlintCheck`, `tasks`가 존재했고 `BUILD SUCCESSFUL in 858ms`였다.
- Phase 2·3 6차 보완 재점검 판정(2026-07-28): `REV-018`~`REV-020`의 production/test 보완과 Gate 완료 증거가 일치했고 추가 production finding은 확정되지 않았다. 하단 종합 표에서만 미처리로 남은 `REV-001`~`REV-009`를 각 소유 Gate·Progress 판정에 맞춰 `처리 완료`로 동기화했다.