262 KiB
AI 캐릭터 관리자 API Implementation Plan
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를 구현한다.
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 컴포넌트를 테스트로 고정해 선택적으로 재사용한다.
Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring Security, JPA/Hibernate, QueryDSL, MySQL, Gradle Wrapper, JUnit5.
| 문서 항목 | 내용 |
|---|---|
| 상태 | 구현 중 |
| 작성일 | 2026-07-24 |
| 요구사항 기준 | docs/20260724_AI캐릭터_관리자_API/prd.md |
| API 기준 | 이 문서의 Endpoint Contract Summary |
| 현재 Phase | Phase 3 6차 리뷰 완료 |
| 현재 활성 Goal | P4-T1 대기 |
현재 상태
| Phase | 상태 | 기존 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---|---|---|---|---|
| 1 | 완료 | 7/7 | 완료 | 없음 |
| 2 | 완료 | 11/11 | 완료 | 없음 |
| 3 | 완료 | 15/15 | 완료 | 없음 |
| 4 | 대기 | 0/6 | P4-T1 |
P3-R5-GATE, 사용자 진행 지시 |
| 5 | 대기 | 0/6 | P5-T1 |
P4-GATE |
| 6 | 대기 | 0/4 | P6-T1 |
P5-GATE |
| 7 | 대기 | 0/2 | P7-T1 |
Phase 1~6 Gate 완료 |
- Phase는 결과와 의존성을 묶는 문서 단위다.
create_goal에는 Task 또는 Phase Gate 하나만 등록한다. - 동시에 하나의 미완료 goal만 운용하고, 사용자가 명시적으로 요청하지 않으면 token budget을 설정하지 않는다.
- 기존
[x]Task와 검증 기록은 당시 완료 이력으로 보존한다. 후속 리뷰에서 발견한 문제는 기존 Task를 다시 열지 않고 새 review/fix Goal로 처리한다. - 각 Goal은 체크박스, focused test, 완료 증거와 Progress 기록이 모두 충족된 뒤에만
complete로 갱신한다. - 검증은 focused test와 영향받는 slice/legacy 회귀를 우선한다. 전체
./gradlew test는 공통 경계·여러 Phase 영향, targeted 결과만으로 영향 범위를 판단할 수 없는 실패 또는 최종 release 판정에 실제로 필요하다고 기록한 경우에만 실행한다. - 전체 회귀를 생략하면 생략 근거와 대신 실행한 focused/영향 범위 회귀 명령을 Progress와 검증 기록에 남긴다.
- 같은 차단 사유가 최초 시도와 자동 후속을 포함해 3회 연속 반복되고, 문서화나 독립 작업도 불가능할 때만
blocked로 갱신한다.
Source of Truth
- 요구사항 원본:
.omx/specs/deep-interview-ai-character-admin-api.md - 2026-07-24 후속 확정 정책: 신규 prefix는 JWT
ROLE_ADMIN+ 현재 DBMember.role == ADMIN이중 인가를 적용하고, stale ADMIN claim은 403으로 거부한다. 신규 prefix의 API application/controller/security filter 오류는 정확한 비2xx status +ApiResponse.error+Accept-Language기반 KO/EN/JA message를 반환한다. 이 후속 정책이 원본과 충돌하면 후속 정책을 우선한다. - 2026-07-25 후속 확정 정책: 신규 prefix의 CORS는 현재 코드에 정의된 캐릭터 관리자 frontend Origin
http://localhost:8888,https://test-character-admin.sodalive.net,https://character-admin.sodalive.net만 허용한다. 기존 범용 관리자 frontend와 creator frontend Origin은 허용하지 않는다. - 2026-07-25 2차 리뷰 후속 확정 정책: 공유
/admin/member/login,/member/logout는 기존 전역 Origin과 캐릭터 관리자 Origin의 합집합을 path-specific으로 허용한다. CORS 정책 거부 403 body는 API 오류 envelope 계약에서 제외한다. 신규 prefix의 406은common.error.invalid_request,MissingPathVariableException은 500common.error.unknown으로 처리하고, 405Allow와 415Accept표준 header를 유지한다. - 문서 작성 규칙:
docs/agent-guides/작업절차.md,docs/agent-guides/문서유지보수.md,docs/agent-guides/테스트스타일.md - 기존 AI 캐릭터 연결 문서:
docs/20260611_AI캐릭터_크리에이터기능_최소연결/{prd.md,plan-task.md}
Endpoint Contract Summary
신규 API prefix는 /api/v2/admin/ai-characters로 한다. 기존 /admin/*, /creator-admin/*, 공개
/api/v2/creator-channels/*의 성공·오류 status/body/message 계약은 변경하지 않는다.
모든 성공 응답은 기존 관례처럼 ApiResponse.ok(...) wrapper를 사용한다. API application/controller/security filter 오류는
오류 의미에 맞는 HTTP status와 ApiResponse.error(...) wrapper를 사용한다.
characterId는 target resource endpoint의 외부 대상 식별자다. 캐릭터 목록/검색은 선택된 target이 없어 characterId를 받지 않고, 캐릭터 생성은 새 ChatCharacter를 만드는 endpoint라 path characterId를 받지 않는다.
모든 목록/검색 endpoint는 page 기본값 0, size 기본값 20, 최소 20, 최대 50 보정을 적용하고 경계값 테스트를 둔다.
{
"success": true,
"message": null,
"data": {}
}
공통 API 오류 응답은 success=false, 현지화된 message, data=null, errorProperty=null을 포함하고 2xx로 normalize하지 않는다.
Accept-Language: ko|en|ja에 따라 KO/EN/JA를 반환하며, 없거나 지원하지 않는 언어는 KO로 fallback한다. security filter 단계도
MVC interceptor에 의존하지 않고 header를 직접 해석한다.
| 오류 | HTTP status | message key |
|---|---|---|
| JWT 없음·잘못됨·만료·폐기 | 401 | common.error.bad_credentials |
| JWT role 비ADMIN | 403 | common.error.access_denied |
| JWT ADMIN + 현재 DB role 비ADMIN stale claim | 403 | common.error.access_denied |
| request binding·target 미존재·creatorMember 누락·role/memberKind 불변식 위반 | 400 | common.error.invalid_request |
| 신규 prefix 미매핑 경로 | 404 | common.error.invalid_request |
| 지원하지 않는 HTTP method | 405 | common.error.invalid_request |
| 지원하지 않는 응답 media type | 406 | common.error.invalid_request |
| 지원하지 않는 요청 media type | 415 | common.error.invalid_request |
MissingPathVariableException·예상하지 못한 서버 오류 |
500 | common.error.unknown |
405 응답은 표준 Allow header를, 415 응답은 표준 Accept header를 유지한다.
{
"success": false,
"message": "Invalid request.",
"data": null,
"errorProperty": null
}
신규 prefix는 캐릭터 관리자 Origin http://localhost:8888, https://test-character-admin.sodalive.net,
https://character-admin.sodalive.net만 허용한다. 공유 /admin/member/login, /member/logout는 기존 전역 Origin과 캐릭터
관리자 Origin의 합집합만 path-specific으로 허용하며, 다른 legacy/public 경로의 허용 범위는 변경하지 않는다. 허용되지 않은
Origin, method 또는 header가 Spring CORS 계층에서 403으로 정책 거부되면 handler 진입 전 종료되는 브라우저 보안 경계이므로
그 응답의 body, content type, 현지화 및 ApiResponse.error envelope는 외부 계약으로 고정하지 않는다.
Phase 2~6에서 추가되는 domain/client/server 오류는 구현 전에 각 Task에서 정확한 비2xx status와 KO/EN/JA message key를 고정하고 같은 envelope를 적용한다. 신규 prefix 전용 오류 처리는 legacy/public endpoint에 적용하지 않는다.
캐릭터 목록/검색
GET /api/v2/admin/ai-characters?search=루나&page=0&size=20
Query parameters:
{
"search": "루나",
"page": 0,
"size": 20
}
Response data:
{
"totalCount": 1,
"page": 0,
"size": 20,
"hasNext": false,
"items": [
{
"characterId": 101,
"name": "루나",
"description": "달빛을 좋아하는 AI 캐릭터",
"imageUrl": "https://cdn.example.com/characters/luna.png",
"creatorMemberId": 9001,
"creatorNickname": "루나",
"originalWorkId": 31,
"externalCharacterId": "ext-luna-001",
"isActive": true,
"createdAtUtc": "2026-07-24T00:00:00Z"
}
]
}
캐릭터 상세
GET /api/v2/admin/ai-characters/{characterId}
Response data:
{
"characterId": 101,
"name": "루나",
"description": "달빛을 좋아하는 AI 캐릭터",
"imageUrl": "https://cdn.example.com/characters/luna.png",
"creatorMemberId": 9001,
"creatorNickname": "루나",
"creatorProfileImageUrl": "https://cdn.example.com/characters/luna.png",
"creatorIntroduce": "달빛을 좋아하는 AI 캐릭터",
"originalWorkId": 31,
"externalCharacterId": "ext-luna-001",
"isActive": true,
"createdAtUtc": "2026-07-24T00:00:00Z",
"updatedAtUtc": "2026-07-24T00:00:00Z"
}
캐릭터 생성
POST /api/v2/admin/ai-characters
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"request": {
"name": "루나",
"systemPrompt": "루나는 달빛을 좋아하는 AI 캐릭터입니다.",
"description": "달빛을 좋아하는 AI 캐릭터",
"originalWorkId": 31
}
}
Response data: 캐릭터 상세와 동일하다.
캐릭터 수정/비활성화
PUT /api/v2/admin/ai-characters/{characterId}
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"request": {
"name": "루나",
"description": "수정된 소개",
"originalWorkId": 31
}
}
externalCharacterId는 response 전용이며 request에 포함하면 400이다. 일반 수정은 image와 함께 보낼 수 있으며 image가 없으면 기존
이미지를 유지한다. soft delete는 request: {"isActive": false}만 허용하고 일반 수정 field 또는 image와 혼합하면 400이다.
Response data: 캐릭터 상세와 동일하다. isActive=false는 soft delete 의미다.
오디오 콘텐츠 목록/검색
GET /api/v2/admin/ai-characters/{characterId}/audio-contents?search=밤&status=OPEN&page=0&size=20
Query parameters:
{
"search": "밤",
"status": "OPEN | SCHEDULED",
"page": 0,
"size": 20
}
Response data:
{
"totalCount": 1,
"page": 0,
"size": 20,
"hasNext": false,
"items": [
{
"contentId": 501,
"title": "밤 산책",
"coverImageUrl": "https://cdn.example.com/audio/501-cover.png",
"audioSignedUrl": "https://cdn.example.com/signed/audio/501.m4a?Expires=...",
"price": 1000,
"isAdult": false,
"isActive": true,
"releaseDateUtc": "2026-07-25T00:00:00Z",
"status": "OPEN"
}
]
}
오디오 콘텐츠 상세
GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}
Response data:
{
"contentId": 501,
"title": "밤 산책",
"description": "조용한 밤 산책 오디오",
"coverImageUrl": "https://cdn.example.com/audio/501-cover.png",
"audioSignedUrl": "https://cdn.example.com/signed/audio/501.m4a?Expires=...",
"price": 1000,
"isAdult": false,
"isActive": true,
"releaseDateUtc": "2026-07-25T00:00:00Z",
"status": "OPEN",
"seriesIds": [701],
"createdAtUtc": "2026-07-24T00:00:00Z",
"updatedAtUtc": "2026-07-24T00:00:00Z"
}
오디오 콘텐츠 생성
POST /api/v2/admin/ai-characters/{characterId}/audio-contents
Content-Type: multipart/form-data
Form fields:
{
"coverImage": "File",
"audioFile": "File",
"request": {
"title": "밤 산책",
"description": "조용한 밤 산책 오디오",
"price": 1000,
"isAdult": false,
"isActive": true,
"themeId": 11,
"releaseDateUtc": "2026-07-25T00:00:00Z",
"seriesIds": [701]
}
}
Response data: 오디오 콘텐츠 상세와 동일하다.
오디오 콘텐츠 수정/soft delete
PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}
Content-Type: multipart/form-data
Form fields:
{
"coverImage": "File | optional",
"audioFile": "미지원",
"request": {
"title": "밤 산책 수정",
"description": "수정된 설명",
"price": 1200,
"isAdult": false,
"isActive": false,
"releaseDateUtc": null,
"seriesIds": [701]
}
}
Response data: 오디오 콘텐츠 상세와 동일하다.
시리즈 목록
GET /api/v2/admin/ai-characters/{characterId}/series?page=0&size=20
Query parameters:
{
"page": 0,
"size": 20
}
Response data:
{
"totalCount": 1,
"page": 0,
"size": 20,
"hasNext": false,
"items": [
{
"seriesId": 701,
"title": "루나의 밤",
"introduction": "밤을 주제로 한 시리즈",
"coverImageUrl": "https://cdn.example.com/series/701.png",
"genreId": 3,
"isAdult": false,
"state": "OPEN",
"isActive": true,
"orders": 1
}
]
}
시리즈 상세
GET /api/v2/admin/ai-characters/{characterId}/series/{seriesId}
Response data:
{
"seriesId": 701,
"title": "루나의 밤",
"introduction": "밤을 주제로 한 시리즈",
"coverImageUrl": "https://cdn.example.com/series/701.png",
"publishedDaysOfWeek": ["MONDAY", "WEDNESDAY"],
"genreId": 3,
"keywords": ["밤", "산책"],
"isAdult": false,
"state": "OPEN",
"isActive": true,
"writer": "루나",
"studio": "소다라이브",
"orders": 1
}
시리즈 생성
POST /api/v2/admin/ai-characters/{characterId}/series
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"request": {
"title": "루나의 밤",
"introduction": "밤을 주제로 한 시리즈",
"publishedDaysOfWeek": ["MONDAY", "WEDNESDAY"],
"genreId": 3,
"keywords": ["밤", "산책"],
"isAdult": false,
"state": "OPEN",
"writer": "루나",
"studio": "소다라이브"
}
}
Response data: 시리즈 상세와 동일하다.
시리즈 수정/soft delete
PUT /api/v2/admin/ai-characters/{characterId}/series/{seriesId}
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"request": {
"title": "루나의 밤 수정",
"introduction": "수정된 소개",
"publishedDaysOfWeek": ["FRIDAY"],
"genreId": 3,
"keywords": ["밤"],
"isAdult": false,
"state": "OPEN",
"isActive": false,
"writer": "루나",
"studio": "소다라이브"
}
}
Response data: 시리즈 상세와 동일하다.
시리즈 콘텐츠 조회
GET /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents?search=밤&page=0&size=20
Query parameters:
{
"search": "밤",
"page": 0,
"size": 20
}
Response data:
{
"totalCount": 1,
"page": 0,
"size": 20,
"hasNext": false,
"items": [
{
"contentId": 501,
"title": "밤 산책",
"coverImageUrl": "https://cdn.example.com/audio/501-cover.png",
"isAdult": false,
"orders": 1
}
]
}
시리즈 콘텐츠 연결
POST /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents
Request body:
{
"contentIds": [501, 502]
}
Response data: 시리즈 상세와 동일하다.
시리즈 콘텐츠 연결 해제
DELETE /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents/{contentId}
Request body 없음.
Response data: 시리즈 상세와 동일하다.
시리즈 순서 변경
PUT /api/v2/admin/ai-characters/{characterId}/series/orders
Request body:
{
"seriesIds": [701, 702, 703]
}
Response data: 시리즈 목록과 동일하다.
커뮤니티 게시글 목록
GET /api/v2/admin/ai-characters/{characterId}/community-posts?page=0&size=20
Query parameters:
{
"page": 0,
"size": 20
}
Response data:
{
"totalCount": 1,
"page": 0,
"size": 20,
"hasNext": false,
"items": [
{
"postId": 801,
"content": "오늘의 소식입니다.",
"imageUrl": "https://cdn.example.com/community/801.png",
"audioSignedUrl": null,
"price": 0,
"isAdult": false,
"isFixed": true,
"fixedAtUtc": "2026-07-24T00:00:00Z",
"isActive": true,
"createdAtUtc": "2026-07-24T00:00:00Z"
}
]
}
커뮤니티 게시글 등록
POST /api/v2/admin/ai-characters/{characterId}/community-posts
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"audioFile": "File | optional",
"request": {
"content": "오늘의 소식입니다.",
"price": 0,
"isAdult": false,
"isFixed": false,
"isActive": true
}
}
Response data:
{
"postId": 801,
"content": "오늘의 소식입니다.",
"imageUrl": "https://cdn.example.com/community/801.png",
"audioSignedUrl": null,
"price": 0,
"isAdult": false,
"isFixed": false,
"fixedAtUtc": null,
"isActive": true,
"createdAtUtc": "2026-07-24T00:00:00Z",
"updatedAtUtc": "2026-07-24T00:00:00Z"
}
커뮤니티 게시글 수정/고정/soft delete
PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}
Content-Type: multipart/form-data
Form fields:
{
"image": "File | optional",
"audioFile": "File | optional",
"request": {
"content": "수정된 소식입니다.",
"price": 0,
"isAdult": false,
"isFixed": false,
"isActive": false
}
}
Response data: 커뮤니티 게시글 등록 응답과 동일하다. isActive=false이면 isFixed=false, fixedAtUtc=null이어야 한다.
FanTalk 답변 작성
POST /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies
Request body:
{
"content": "응원해줘서 고마워요!"
}
Response data:
{
"fanTalkId": 901,
"replyId": 902,
"creatorMemberId": 9001,
"content": "응원해줘서 고마워요!",
"createdAtUtc": "2026-07-24T00:00:00Z"
}
Phase 1: 공통 ADMIN 인증과 AI 캐릭터 target resolver 기반
공통 Task 실행 규칙
- 각 구현 Task는
RED: 실패 테스트 작성/실패 확인,GREEN: 최소 구현/통과 확인,REFACTOR: 정리/회귀 확인을 포함한다. - 테스트 작성이 현실적으로 불가능한 검증 전용 Task는
TDD 예외 사유와대체 검증 방법을 Task에 명시한다. - 기존 business method 재사용 전 특성화/회귀 테스트는 신규 v2 use-case RED 테스트와 분리한다. 특성화 테스트는 기존 legacy/creator-admin 구현을 대상으로 먼저 통과해 baseline을 고정하고, 그 결과를 신규 v2 RED 기대값으로 옮긴다.
- Phase 2~6의 모든 신규 오류 RED는 정확한 비2xx status,
ApiResponse.errorshape,Accept-LanguageKO/EN/JA message를 함께 검증한다. 새 오류 분기는 status, message key, 3개 언어 message, 테스트가 모두 정해지기 전 완료 처리하지 않는다.
목표
모든 신규 API가 공유할 JWT ADMIN + 현재 DB ADMIN 이중 인가, prefix 전용 오류 envelope/i18n, characterId 기반 target 해석,
CREATOR + AI_CHARACTER 불변식, ownership no-side-effect 검증 기반을 만든다.
범위와 비범위
- 포함: 신규 v2 admin package 골격, JWT role + 현재 DB role 이중 인가, prefix 전용 security/application 오류 처리, 공통 target resolver/use-case, no-side-effect 테스트 fixture.
- 제외: 캐릭터/콘텐츠/시리즈/커뮤니티/FanTalk 실제 domain 기능 구현.
선행 Phase 및 의존성
- 선행 Phase 없음.
- 기존
ChatCharacter.creatorMember,MemberRole.CREATOR,MemberKind.AI_CHARACTER가 존재해야 한다.
API endpoint와 request/response contract
- 모든 후속 endpoint에 공통 적용한다.
- target resource request는 JWT
ROLE_ADMIN과 현재 DBMember.role == ADMIN을 모두 만족하는 principal과 path/query/body의characterId를 받는다. 캐릭터 목록/검색과 생성은 Endpoint Contract Summary의 예외를 따른다. - resolver output은 내부 전용
AiCharacterAdminTarget(characterId, chatCharacter, creatorMember)로 계획한다. - Phase 1 API 실패 응답은 Endpoint Contract Summary의 400/401/403/404/405/406/415/500과
ApiResponse.error/KO·EN·JA 계약을 따르며 domain side effect가 없어야 한다. Spring CORS 정책 거부 403 body는 해당 envelope 계약의 예외다.
entity, repository, service 변경
- Entity: 변경 없음.
- Repository:
ChatCharacterRepository에characterId로creatorMember를LEFT JOIN FETCH하는 query를 추가해 반환 직후 연관 객체가 초기화되도록 한다. - Service/Application:
AiCharacterAdminTargetResolver생성. - Controller/Facade:
kr.co.vividnext.sodalive.v2.api.admin.aicharacter하위 신규 controller/facade 패키지 생성.
DB migration
- 없음. 신규 DDL, 신규 migration 파일을 만들지 않는다.
transaction과 concurrency 고려사항
- target resolver는 read-only transaction으로 동작한다.
- 후속 write use-case는 target 검증을 write transaction 시작부에서 먼저 수행한다.
- target 검증 실패는 transaction rollback-only가 필요 없는 사전 실패로 끝나야 한다.
보안 및 개인정보 위험
- JWT
ROLE_ADMIN만 신뢰하지 않고TokenProvider가 이미 조회한MemberAdapter.member.role을 신규 prefix 인가에서 함께 확인한다.TokenProvider의 전역 authority 계산은 변경하지 않는다. - JWT ADMIN + 현재 DB 비ADMIN stale claim과
MemberAdapter가 아닌 principal은 target resolver 실행 전에 403으로 거부한다. creatorMember를 인증 principal로 교체하지 않는다.creatorMemberId를 관리자 입력값으로 신뢰하지 않는다.- Phase 1 resolver의 잘못된 target 요청은 Hibernate 통계로 DB insert/update/delete 0건을 검증한다. resolver는 S3, 외부 API, 이벤트 발행 의존성을 갖지 않으며 Phase 2~6 write slice에서 각 외부 부작용 0건을 별도 검증한다.
acceptance criteria
- JWT ADMIN + 현재 DB ADMIN 요청만 유효한 AI character target을 resolver로 해석할 수 있다.
- JWT 없음·잘못됨·만료·폐기는 401, JWT 비ADMIN 또는 현재 DB 비ADMIN은 403, character 미존재·creatorMember 미존재·target role/memberKind 불일치는 400이다.
- 위 API 오류는 모두
ApiResponse.error와Accept-Language에 따른 KO/EN/JA message를 반환한다. Spring CORS 정책 거부 403 body는 해당 envelope 계약의 예외다. - 405는
Allow, 415는Acceptheader를 유지하고, 지원하지 않는 응답 media type은 406common.error.invalid_request,MissingPathVariableException은 500common.error.unknown으로 반환한다. - Phase 1 resolver 실패는 DB insert/update/delete가 0건이다. S3, 외부 API, 이벤트 부작용은 해당 의존성이 처음 도입되는 Phase 2~6 write slice에서 검증한다.
targeted test
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverTest.kt - Integration Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverIntegrationTest.kt - RED: resolver 미구현 상태에서
shouldResolveAiCharacterCreatorMemberForAdminTarget,shouldRejectMissingCharacterWithoutSideEffect,shouldRejectHumanCreatorMemberWithoutSideEffect테스트를 작성해 실패를 확인한다. - GREEN: resolver와 최소 repository query를 구현해 resolver 테스트를 통과시킨다.
- Controller 권한 테스트:
AiCharacterAdminAuthorizationTest에서 JWT role × 현재 DB role 매트릭스와 stale claim을 고정하고, Phase 2~6 controller test에서 신규 endpoint 전체가 같은 이중 인가를 공유하는지 검증한다. - 오류 계약 테스트:
AiCharacterAdminErrorContractTest에서 400/401/403/404/405/406/415/500, 405Allow, 415Accept, JWT filter 예외, KO/EN/JA body와 legacy fallback을 검증한다. CORS는 미매핑 fallback뿐 아니라 실제 mapped endpoint와/admin/member/login,/member/logout의 허용·거부 Origin/preflight를 검증하고, 정책 거부 403 body에는 envelope를 요구하지 않는다. - Run:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*'
전체 회귀 테스트 영향
- 기존 endpoint를 건드리지 않아야 한다.
- 신규 package 추가로 component scan과 security 설정 충돌이 없어야 한다.
rollback 전략
- 신규 v2 admin controller/facade/resolver/error/security package와 관련 테스트를 제거한다.
SecurityConfig.kt,WebConfig.kt,ExceptionHandlerFilter.kt,TokenProvider.kt,ChatCharacterRepository.kt의 Phase 1 변경을 함께 되돌린다.- DB rollback은 없다.
권장 commit 경계
-
feat: add ai character admin target resolver -
Task 1.1: resolver RED 테스트 작성
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverTest.kt - RED: 유효 target, missing character, wrong role, wrong memberKind, missing creatorMember, cross-owner fixture를 먼저 작성하고 실패를 확인한다.
- GREEN: 구현 전 Task라 production code를 변경하지 않는다.
- REFACTOR: fixture 중복만 정리하고 테스트 의미는 약화하지 않는다.
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest - 검증 기록: 무엇: resolver RED 테스트. 왜: resolver와
findByIdWithCreatorMember미구현을 실제 실패로 고정하기 위해. 어떻게: 위 명령을 실행했다. 결과:compileTestKotlin이 두 미구현 항목으로 실패했다.
- Test:
-
Task 1.2: resolver 최소 구현
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/application/AiCharacterAdminTargetResolver.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverIntegrationTest.kt - RED: Task 1.1 실패 테스트가 같은 실패 이유로 남아 있음을 확인한다.
- GREEN:
characterId로ChatCharacter와creatorMember를 조회하고 strict validation을 적용한다. - REFACTOR: resolver/repository naming과 예외 메시지를 인접 v2 관례에 맞추고 회귀 테스트를 재실행한다.
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverIntegrationTest - 검증 기록: 무엇: resolver 최소 구현과 repository/ownership 통합 검증. 왜: mock 기반 테스트만으로 실제 조회 동작을 확인할 수
없었기 때문이다. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest와./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverIntegrationTest를 실행했다. 결과: 두 실행 모두BUILD SUCCESSFUL이었다.
- Create:
-
Task 1.3: ADMIN 권한 controller smoke 테스트
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt - RED: 최소 sample controller 또는 Phase 2 첫 controller 기준 anonymous/non-admin 접근 실패와 admin 접근 성공 테스트를 먼저 작성해 실패를 확인한다.
- GREEN: 공통 security 설정 또는 controller annotation을 최소 구현해 테스트를 통과시킨다.
- REFACTOR: Phase 2~6의 모든 신규 endpoint controller test가 같은 권한 매트릭스를 따르도록 test helper를 정리한다.
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest - 검증 기록: 무엇: sample route ADMIN 권한 smoke. 왜: 신규 prefix의 ADMIN rule 적용 전후를 확인하기 위해. 어떻게: 위 명령으로
권한 테스트를 실행했다. 결과: 적용 전 권한 실패를 확인했고,
/api/v2/admin/ai-characters/**ADMIN rule 적용 후BUILD SUCCESSFUL이었다.
- Test:
-
Task 1.4: JWT claim + 현재 DB ADMIN 이중 인가
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/jwt/TokenProvider.kt,src/main/kotlin/kr/co/vividnext/sodalive/member/MemberAdapter.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt,src/main/kotlin/kr/co/vividnext/sodalive/jwt/TokenProvider.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminLoginJwtIntegrationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/jwt/TokenProviderTest.kt -
RED:
UsernamePasswordAuthenticationToken(MemberAdapter(currentMember), token, jwtAuthorities)fixture로 JWT ADMIN + DB ADMIN 200, JWT 비ADMIN + DB ADMIN 403, JWT ADMIN + DB 비ADMIN stale claim 403, ADMIN authority + 비MemberAdapterprincipal 403을 작성한다. stale claim은 현재 구현에서 200이므로 이 실패를 확인한다. -
GREEN: 신규 prefix의 matcher 하나에서 아래 세 조건을 AND로 묶고
MemberAdapter, legacy matcher는 변경하지 않는다.TokenProvider는 JWT subject 누락/비숫자 값이 500으로 누수되지 않도록common.error.bad_credentials로만 보정하며, 전역 authority 계산과 token 저장소 검증 의미는 변경하지 않는다..antMatchers("/api/v2/admin/ai-characters/**") .access( "hasRole('ADMIN') and " + "principal instanceof T(kr.co.vividnext.sodalive.member.MemberAdapter) and " + "principal.member.role == T(kr.co.vividnext.sodalive.member.MemberRole).ADMIN" ) -
REFACTOR: production
SecurityConfigmatcher의 비확산은 full-contextAiCharacterAdminAuthorizationTest의/phase1-legacy-sample로 확인한다. 기존AdminAgentReadControllerSecurityTest,AdminContentControllerSecurityTest는 각자의 자체 security chain을 사용하는 controller 회귀 증거로 구분하며 production matcher 비확산의 증거로 해석하지 않는다. -
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminLoginJwtIntegrationTest --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest -
검증 기록: 무엇: JWT claim과 현재 DB ADMIN 이중 인가. 왜: stale ADMIN claim과 비
MemberAdapterprincipal이 허용되면 안 되기 때문이다. 어떻게:./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest를 실행했다. 결과: 6개 중 해당 2개가 403 기대 대비 200으로 실패한 RED를 확인했고, 세 조건을 AND로 적용한 뒤 같은 명령이BUILD SUCCESSFUL이었다.
-
-
Task 1.5: 신규 prefix 오류 envelope/status/i18n 계약
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/error/AiCharacterAdminApiException.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/error/AiCharacterAdminExceptionHandler.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/error/AiCharacterAdminErrorResponseWriter.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/security/AiCharacterAdminSecurityErrorHandler.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt,src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt,src/main/kotlin/kr/co/vividnext/sodalive/common/ExceptionHandlerFilter.kt,src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/application/AiCharacterAdminTargetResolver.kt - Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/i18n/Lang.kt,src/main/kotlin/kr/co/vividnext/sodalive/i18n/SodaMessageSource.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAccessDeniedErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverTest.kt - RED: legacy anonymous/revoked-token/지원하지 않는 method 오류의 현재 status/body를 먼저 통과하는 특성화 baseline으로
고정한다. 신규 prefix의 anonymous·잘못된 JWT·폐기 JWT 401, JWT/DB role 불충족 403, invalid request/target 400, 미매핑 경로
404, 지원하지 않는 method 405, 지원하지 않는 media type 415, 예상하지 못한 controller/JWT filter 오류 500을 KO/EN/JA로
parameterized 검증한다. 확정된 캐릭터 관리자 Origin의 404/405/415와 실제 JWT header를 요청하는 미매핑 경로
preflight에 CORS 응답 header가 적용되는지 확인한다. 허용된 Origin의 API 오류는
success=false, localizedmessage, JSON content type을 확인하고, 기존 범용 관리자와 creator frontend Origin의 CORS 정책 거부는 403과 CORS 허용 header 부재만 확인한다. 현재sendError, hardcoded KO, 기본 Spring error body 때문에 실패하는 것을 확인한다. - GREEN:
AiCharacterAdminErrorResponseWriter가Lang.fromAcceptLanguage와SodaMessageSource로ApiResponse.error를 만들고 JSON을 기록하게 한다.AiCharacterAdminSecurityErrorHandler는 401common.error.bad_credentials와 403common.error.access_denied를 위 writer에 위임한다.SecurityConfig는 신규 prefix matcher에만 이 handler를 선택하고 기존JwtAuthenticationEntryPoint/JwtAccessDeniedHandler를 fallback으로 유지한다.ExceptionHandlerFilter도 신규 prefix에서 잡은 JWT 예외만 신규 401 handler로 위임하고 legacy branch는 그대로 둔다. - GREEN: target resolver는 400 +
common.error.invalid_request를 가진AiCharacterAdminApiException을 던진다. URI matcher로 신규 prefix에만 적용되는AiCharacterAdminExceptionHandler는 controller 선택 전 오류까지 처리해 명시적 API 예외, request binding 400, method 405, media type 415, controllerAccessDeniedException403, 예상하지 못한 오류 500을 각각 정확한 status와 localizedApiResponse.error로 반환한다. 낮은 우선순위의 prefix fallback handler는 미매핑 경로를 404로 반환하고 캐릭터 관리자 Origin 전용 CORS 설정을 적용한다. 단순SodaException교체나 전역SodaExceptionHandler변경은 하지 않는다. - GREEN:
ExceptionHandlerFilter가 잡은 폐기 JWT 등 알려진 인증 실패만 401로 보내고, JWT 처리 중 예상하지 못한 예외는 위 prefix exception handler에 위임해 500common.error.unknown으로 반환한다. - REFACTOR: raw message key 노출, MVC
LangInterceptor의존, legacy 오류 응답 변경이 없는지 확인하고 신규/legacy contract 테스트를 함께 재실행한다. - Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAccessDeniedErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest - 검증 기록: 무엇: 신규 prefix 오류 envelope/status/i18n 계약. 왜: 신규 401/403/400/500 응답이 기존 body·message와 달랐기
때문이다. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest를 실행했다. 결과: 최초 15개 중 13개의 status/content type/message 불일치 RED를 확인했고, prefix 기반 handler와 writer 적용 후 44개 invocation이 모두 통과해BUILD SUCCESSFUL이었다.
- Create:
-
Task 1.6: Phase 1 코드 리뷰 후속 보완
- Modify:
docs/20260724_AI캐릭터_관리자_API/prd.md,docs/20260724_AI캐릭터_관리자_API/plan-task.md,src/main/kotlin/kr/co/vividnext/sodalive/configs/WebConfig.kt,src/main/kotlin/kr/co/vividnext/sodalive/jwt/TokenProvider.kt,src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/error/AiCharacterAdminExceptionHandler.kt,src/test/kotlin/kr/co/vividnext/sodalive/jwt/TokenProviderTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAccessDeniedErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminLoginJwtIntegrationTest.kt - RED: 서명과 subject는 유효하지만
authclaim이 누락, 공백 또는 빈 분할 항목을 포함한 JWT가common.error.bad_credentials로 거부되지 않는 테스트를 작성하고 실패를 확인한다. - GREEN:
TokenProvider가authclaim을 authority로 변환하기 전에 문자열 타입, 공백 여부와 각 분할 항목을 검증하고 잘못된 claim은common.error.bad_credentials로 거부하는 최소 구현을 적용한다. - REFACTOR: Phase 1 테스트 전용 controller를 각 테스트 클래스 내부 nested class로 이동하고
@TestComponent로 component scan에서 제외한 뒤 해당 테스트 context에만 명시적으로 import한다. 테스트 fixture 범위만 바꾸는 구조 정리이므로 별도 동작 RED 대신 targeted/full-context 회귀와 application context 시작 성공으로 검증한다. - CORS: 현재 코드의 캐릭터 관리자 Origin
http://localhost:8888,https://test-character-admin.sodalive.net,https://character-admin.sodalive.net만 허용하고 기존 범용 관리자/creator Origin은 거부하는 정책으로 PRD/plan과 CORS 계약 테스트를 동기화한다. - 2차 리뷰 RED: 신규 prefix의 실제 mapped endpoint와 공유
/admin/member/login,/member/logout에서 캐릭터 관리자 Origin 요청/preflight가 허용되지 않는 실패를 확인한다. 응답 media type 협상 실패 406, 405Allowheader, 415Acceptheader,MissingPathVariableException500 계약 테스트를 추가해 현재 동작과의 불일치를 확인한다. - 2차 리뷰 GREEN: 신규 prefix는 캐릭터 관리자 Origin만 허용하는 기존 정책을 유지하고, 두 공유 인증 경로에만 기존 전역
Origin과 캐릭터 관리자 Origin의 합집합을 적용한다.
AiCharacterAdminExceptionHandler는 406을common.error.invalid_request,MissingPathVariableException을 500common.error.unknown으로 분류하고 405/415 표준 header를 보존한다. Spring CORS 정책 거부 403 body는 localizedApiResponse.errorenvelope 계약에서 제외한다. - 2차 리뷰 REFACTOR: 실제 mapped endpoint, 두 공유 인증 경로와 미매핑 fallback의 허용·거부 Origin/preflight를 함께 회귀하고, path-specific CORS 확장이 다른 legacy/public 경로로 확산되지 않았는지 확인한다.
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' - 검증 기록: 무엇: malformed
authclaim, CORS, HTTP 오류, 테스트 fixture 격리 후속 보완. 왜: claim 누수와 실제 mapped/shared path CORS·405/406/415/500 계약 누락을 해소하기 위해. 어떻게:./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest를 실행했다. 결과: malformed claim 7개 invocation RED 후 9개가 통과했고, fallback 보완 후 targeted+legacy 136개가 모두 통과해BUILD SUCCESSFUL이었다. 추가 로그인/로그아웃 CORS 보완 뒤AiCharacterAdminLoginJwtIntegrationTest도BUILD SUCCESSFUL이었다.
- Modify:
-
Task 1.7: Phase 1 후속 리뷰 전체 반영
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt,src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/security/AiCharacterAdminSecurityErrorHandler.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAccessDeniedErrorContractTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminLoginJwtIntegrationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminTargetResolverIntegrationTest.kt - RED: full-context
AiCharacterAdminLoginJwtIntegrationTest에 허용 Origin의PROPFIND400/i18n/CORS, raw double slash 400/CORS, 미허용 Origin 403/Access-Control-Allow-Origin부재와 legacyRequestRejectedException유지 계약을 추가하고, 신규 prefix 세 요청이RequestRejectedException으로 실패하며 legacy fallback은 통과하는 production-before RED를 확인한다. - GREEN:
SecurityConfig가 기존AiCharacterAdminSecurityErrorHandler를 globalRequestRejectedHandler로 등록하고, handler는 신규 prefix에만 400/CORS 계약을 적용한다.setUnsafeAllowAnyHttpMethod(true)없이 허용된 캐릭터 관리자 Origin에는 CORS header를 포함한 400common.error.invalid_request와 현지화된ApiResponse.error를, 미허용 Origin에는 기존 Spring CORS 정책과 같은 body 계약 없는 403을 반환한다. legacy/public은DefaultRequestRejectedHandler에 위임해 기존RequestRejectedException동작을 유지한다. Spring 5.3의 비표준 method enum 한계는 CORS 검사 request에만GETwrapper를 사용해 우회하고 실제 firewall method 허용 범위는 확장하지 않는다. - REFACTOR:
AiCharacterAdminErrorContractTest,AiCharacterAdminAuthorizationTest,AiCharacterAdminAccessDeniedErrorContractTest를 production@SpringBootTest+ MockMvc + EmbeddedRedis full context로 전환한다. 기존 ErrorContract의 표준POST-> GET-only mapping 405,Allow: GET, CORS 계약도 full context에서 회귀한다.AiCharacterAdminLoginJwtIntegrationTest는MemberTokenRepository.deleteAll()을@AfterEach에 실행해 Redis token fixture를 cleanup한다.AiCharacterAdminTargetResolverIntegrationTest는 repository 조회 직후Hibernate.isInitialized(found.creatorMember)를 단언해 productionLEFT JOIN FETCH가 실제 회귀 방지에 필요함을 고정한다. Phase 1 production에는 Bean Validation provider를 추가하지 않고,MethodArgumentNotValidException은 test-only endpoint에서 의존성 없이 직접 던져 handler 분기를 검증한다. 이 Task의 실행 명령과 결과는 먼저 이 Task 아래에 기록하고, phase/전체 aggregate만 문서 하단 검증 기록에 누적한다. - Verify:
./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest - 검증 기록(RED): 무엇: 신규 prefix firewall와 legacy fallback 계약. 왜: production firewall 거부가 신규 API 오류 계약 밖으로
탈출하는지 확인하기 위해. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminLoginJwtIntegrationTest를 실행했다. 결과: 17개 중 신규 prefix 3개가RequestRejectedException으로 실패했고 legacy fallback 테스트는 통과했다. - 검증 기록(GREEN): 무엇: prefix-aware global
RequestRejectedHandler. 왜: 신규 prefix만 400/i18n/CORS로 변환하고 legacy/public 동작을 보존하기 위해. 어떻게: RED와 동일한 명령을 실행했다. 결과: 17/17,BUILD SUCCESSFUL을 확인했다. - 검증 기록(REFACTOR): 무엇: 네 core controller security/error 클래스의 production full-context 계약과 Redis fixture 격리.
왜: slice 설정이 아닌 실제 security/CORS/filter 구성을 검증하기 위해. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAccessDeniedErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminLoginJwtIntegrationTest를 실행했다. 결과: 115/115, failure/error 0,BUILD SUCCESSFUL을 확인했다. - 검증 기록(FETCH JOIN): 무엇: repository 조회 직후
creatorMember초기화의 non-vacuous 회귀 계약. 왜: resolver transaction 내부 접근만으로 fetch join 누락이 가려지는 것을 막기 위해. 어떻게: production query의LEFT JOIN FETCH를 임시로LEFT JOIN으로 바꾸고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverIntegrationTest를 실행했다. 결과: 4개 중 fetch 테스트 1개가 line 66에서 실패해BUILD FAILED(38초)을 확인했다. 즉시LEFT JOIN FETCH를 복원했고, 복원 상태는 하단 최신 canonical 154/154에 포함되어 통과했다. - 검증 기록(직접 400 분기): 무엇: malformed JSON의
HttpMessageNotReadableException, test-only endpoint에서 의존성 없이 직접 던진MethodArgumentNotValidException, 실제 multipart 필수 part 누락의MissingServletRequestPartException각 KO/EN/JA 총 9 invocation. 왜: exactresolvedException타입과 localized 400 envelope를 각 handler 분기에서 직접 고정하기 위해. 어떻게:./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest' --rerun-tasks를 실행했다. 결과: 98/98, failure/error/skipped 0,BUILD SUCCESSFUL을 확인했고 production/build dependency 변경은 없었다.
- Modify:
Phase 2: AI 캐릭터 관리 vertical slice
목표
AI 캐릭터 목록/검색/상세/생성/수정/비활성화를 신규 ADMIN v2 API로 제공하고 레거시 관리자 동작 parity를 고정한다.
범위와 비범위
- 포함: character CRUD API, 외부 캐릭터 API 연동, 이미지 저장, 원작 연결, 언어 감지/번역 이벤트, creatorMember 생성/표시 정보 동기화 parity.
- 제외: hard delete, cascade delete, 기존 legacy admin endpoint 변경.
선행 Phase 및 의존성
- Phase 1 resolver와 ADMIN 권한 기반이 선행되어야 한다.
- 기존
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}->AiCharacterAdminDetailResponsePOST /api/v2/admin/ai-charactersmultipartimage?,request: CreateAiCharacterAdminRequest-> detailPUT /api/v2/admin/ai-characters/{characterId}multipartimage?,request: UpdateAiCharacterAdminRequest-> detailUpdateAiCharacterAdminRequest.isActive=false는 soft delete 의미다.
entity, repository, service 변경
- Entity: 변경 없음.
- Repository: character 목록/검색용 query adapter 추가 가능.
- Service: 신규
AiCharacterAdminCharacterFacade/ApplicationService에서 기존 하위 service를 선택적으로 재사용한다. - DTO: 신규 admin v2 전용 request/response DTO 생성.
DB migration
- 없음.
transaction과 concurrency 고려사항
- 생성/수정은 단일 transaction에서 character, relation, creatorMember 표시 정보 동기화를 완료한다.
- 외부 API/S3/event 순서는 기존 레거시 동작 특성화 결과를 따른다.
- 중복 이름 검증은 기존 정책을 유지하며 동시 생성 시 DB/서비스 레벨 실패가 부분 저장을 남기지 않아야 한다.
보안 및 개인정보 위험
- 목록/상세 응답에 AI creatorMember 로그인 credential, token, private storage path를 노출하지 않는다.
- ADMIN 외 접근을 허용하지 않는다.
acceptance criteria
- 목록/검색/상세는 AI 캐릭터 관리자 화면에 필요한 필드를 반환한다.
- 생성/수정은 레거시 관리자와 동일한 business side effect를 만든다.
- 비활성화는
isActive=false이며 row와 연결 Member/콘텐츠를 삭제하지 않는다.
targeted test
- Characterization:
LegacyChatCharacterAdminCharacterizationTest에서 기존 character admin create/update/soft delete 결과와 외부 API·S3·event failure order, transaction/compensation 계약을 통과 상태로 고정한다. - V2 RED/GREEN:
AiCharacterAdminCharacterControllerTest,AiCharacterAdminCharacterServiceTest. - Run:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'
전체 회귀 테스트 영향
- 기존
AdminChatCharacterController및 public character 조회 응답이 변하지 않아야 한다. - creatorMember 동기화 기존 테스트가 계속 통과해야 한다.
rollback 전략
- 신규 character admin v2 route/facade만 제거한다.
- 이미 생성/수정된 정상 데이터는 기존 관리자와 같은 domain 데이터라 별도 schema rollback이 없다.
권장 commit 경계
-
feat: add ai character admin character slice -
Task 2.1: 기존 character parity 특성화 baseline 고정
- Goal 이력
P2-H1: 기존 character admin 동작을 신규 v2 구현의 비교 기준으로 고정했다. - 완료 증거: 아래 특성화 테스트 RED/GREEN/REFACTOR 기록과 실제 명령 결과.
- 범위 밖: 신규 v2 endpoint 구현.
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterizationTest.kt - CHARACTERIZE: 기존 character admin 구현을 대상으로 중복 이름, 외부 API·S3·event 호출/실패 순서와 transaction/compensation, original work 연결, 언어 감지/번역 이벤트, creatorMember 표시 정보 동기화,
isActive=false및 연결 Member/콘텐츠 미삭제 결과를 고정한다. - BASELINE: 신규 v2 production code 변경 전에 특성화 테스트가 기존 구현을 대상으로 통과함을 확인한다.
- REFACTOR: fixture와 assertion naming만 정리하고 parity baseline은 변경하지 않는다.
- Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterizationTest - 검증 기록(RED): 무엇: 기존 character admin baseline 특성화 테스트. 왜: 신규 v2 production code 전에 기존 생성/수정/비활성화/원작/언어 이벤트/creatorMember·콘텐츠 보존 계약을 고정하기 위해. 어떻게: 위 테스트를 추가하고 동일 명령을 실행했다. 결과: 이벤트 baseline 추가 직후 Mockito/S3 stub과 request JSON 누락으로 2회 실패해 RED를 확인했다.
- 검증 기록(GREEN): 무엇: Task 2.1 baseline 통과. 왜: 기존 구현이 현재 특성화 계약을 만족하는지 확인하기 위해. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterizationTest를 실행했다. 결과:BUILD SUCCESSFUL을 확인했다. - 검증 기록(REFACTOR): 무엇: Kotlin style과 리뷰 gate. 왜: 테스트-only baseline의 품질과 계획 준수 여부를 확인하기 위해. 어떻게:
./gradlew ktlintCheck와 spec/code-quality read-only review를 실행했다. 결과:BUILD SUCCESSFUL, 두 리뷰 모두APPROVED였다.
- Goal 이력
-
Task 2.2: character controller/facade/DTO 구현
- Goal 이력
P2-H2: 캐릭터 목록·상세·생성·수정의 최초 v2 API 구현을 제공했다. - 완료 증거: 아래 controller test와
ktlintCheck기록. 이 이력만으로 Phase 2 Gate 통과를 의미하지 않는다. - 범위 밖: 후속 심층 리뷰에서 확정되는 누락·회귀 보완.
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/* - RED: Task 2.1 baseline에서 옮긴
AiCharacterAdminCharacterServiceTest와 controller 권한/페이지네이션 경계 테스트를 작성하고 신규 v2 미구현으로 실패함을 확인한다. - RED: 현재 신규 dependency 금지 제약에 따라 기존 Spring binding 또는 수동 validation 전략을 우선하고, 실제 DTO의 invalid 요청 통합 계약을 추가한다. Bean Validation provider가 반드시 필요하면 구현 전에 PRD/계획과 dependency 허용 범위를 명시적으로 변경하고 승인을 받는다.
- GREEN: endpoint contract summary의 character endpoint를 구현하고 목록/검색
page/size기본값·최소·최대 보정을 적용한다. - REFACTOR: 신규 DTO가 legacy/public DTO를 외부 계약으로 재노출하지 않는지 확인하고 회귀 테스트를 재실행한다.
- Verify:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*' - 검증 기록(RED): 무엇: v2 character controller 계약 테스트 7개. 왜: 신규 route가 목록/검색/상세/생성/수정과 ADMIN 경계를 제공하지 않음을 확인하기 위해. 어떻게:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerTest를 실행했다. 결과: 모든 기대 endpoint가 미구현 route의 404를 반환해 7개가 실패했다. - 검증 기록(GREEN): 무엇: 목록/검색 page-size 보정, target resolver 상세 거부, multipart 생성/수정, AI creatorMember 동기화, soft delete, invalid JSON, ADMIN 인가. 왜: Task 2.2 API 계약과 레거시 부작용 재사용을 확인하기 위해. 어떻게:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'를 실행했다. 결과: controller 8개와 Task 2.1 baseline 7개가 모두 통과했다. - 검증 기록(REFACTOR): 무엇: 신규 v2 DTO 경계와 Kotlin style. 왜: legacy/public DTO 비노출과 코드 스타일을 확인하기 위해. 어떻게:
./gradlew ktlintCheck를 실행했다. 결과:BUILD SUCCESSFUL을 확인했다.
- Goal 이력
-
Task 2.3: Phase 2 요구사항·계약·코드 리뷰
Goal 실행 P2-R1: PRD Feature B와 Endpoint Contract Summary를 Phase 2 코드·테스트에 추적해 확정된 누락만 후속 Goal로 전환한다.
- 추적 review ID:
REV-001,REV-002,REV-003,REV-007,REV-008. - 시작 조건:
P2-H1,P2-H2산출물과 검증 기록 존재. - 완료 증거:
docs/sample/sample-review.md형식의 리뷰 문서, endpoint/side-effect 추적표, 모든 후보의 확정·오탐·보류 판정, 후속 Goal 연결과 Progress 기록. - 범위 밖: 리뷰 도중 production code 수정, Phase 3 이후 기능 검토.
- TDD 예외 사유: 구현이 아닌 read-only 리뷰 Task다.
- 대체 검증 방법: PRD·계약·production·test를 대조하고 현재 focused test를 실행해 관찰 결과를 리뷰 문서에 기록한다.
Files:
-
Create:
docs/20260724_AI캐릭터_관리자_API/reviews/phase2-character-review.md -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterController.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterDto.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt -
목록·검색·상세·생성·수정·비활성화 endpoint와 DTO 필드를 PRD/계약에 1:1로 추적한다.
-
중복 이름, 외부 API, S3, 원작, 언어 이벤트, creatorMember 동기화와 실패 순서를 코드·test에 추적한다.
-
ADMIN 이중 인가, pagination, multipart/binding, KO/EN/JA 오류, private 정보 비노출 계약을 확인한다.
-
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'의 실제 결과를 기록한다. -
후보를 확정·오탐·보류로 판정하고 확정 항목을 아래 세부 Goal에 연결하거나 새 회귀 수정 Goal을 계획에 먼저 추가한다.
- 검증 기록: 무엇:
P2-R1Phase 2 character slice read-only 리뷰. 왜:P2-H1,P2-H2완료 이력만으로 Phase 2 Gate를 통과할 수 있는지 판정하기 위해. 어떻게:docs/20260724_AI캐릭터_관리자_API/reviews/phase2-character-review.md를 작성하고./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'를 실행했다. 결과: focused test는BUILD SUCCESSFUL in 51s였고,REV-001REV-003,REV-007,REV-008은 기존P2-T3P2-T6보완 Goal에 연결된 확정 finding으로 판정했다.
- 검증 기록: 무엇:
-
Task 2.4: 캐릭터 목록·검색·상세 보완
Goal 실행 P2-T3: 캐릭터 조회 API의 검색·pagination·응답·target 계약을 독립적으로 검증하고 확정된 누락을 최소 수정한다.
- 추적 review ID:
REV-007중 목록 응답 field set과 DTO 경계. - 시작 조건:
P2-R1완료와 관련 review ID 확정. 확정 finding이 없으면 Decision Log에해당 없음근거를 남긴다. - 완료 증거: 조회 전용 RED/GREEN, focused test, DTO 비노출 점검과 Progress 기록.
- 범위 밖: 생성, 수정, 외부 API·S3 mutation.
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/AiCharacterAdminCharacterFacade.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterDto.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerTest.kt -
RED: 목록 item의 exact JSON key를 고정하고 상세 전용
creatorProfileImageUrl,creatorIntroduce,updatedAtUtc가 노출되는 현재 동작을 실패로 재현한다. -
RED 확인:
page=0,size기본 20·최소 20·최대 50, 검색·hasNext와 상세 target 불변식의 경계 test를 실행해 의도한 assertion 실패를 확인한다. -
GREEN: 목록 전용 DTO와 mapper를 최소 구현해 계약 field만 반환하고 credential·token·private path를 노출하지 않는다.
-
GREEN 확인: 같은 focused test를 다시 실행해 exact 목록 field set과 pagination·target 계약이 모두 통과하는지 확인한다.
-
REFACTOR: 상세 UTC field 계약을 유지하면서 목록/상세 DTO 의존 방향을 점검하고 focused test와
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: 목록 item의 상세 전용 field 비노출 계약. 왜: 목록 응답이 상세 DTO를 재사용해
creatorProfileImageUrl,creatorIntroduce,updatedAtUtc를 노출했기 때문이다. 어떻게:AiCharacterAdminCharacterControllerTest에doesNotExist()assertion 3개를 추가하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerTest를 실행했다. 결과:목록은 음수 page와 최소 미만 size를 기본값으로 보정한다가 line 66에서 실패해 RED를 확인했다. - 검증 기록(GREEN/REFACTOR): 무엇: 목록 전용 DTO와 mapper 분리. 왜: 상세 응답 계약을 유지하면서 목록 field set만 Endpoint Contract Summary에 맞추기 위해. 어떻게:
AiCharacterAdminCharacterListItemResponse와toListItemResponse를 추가하고 목록 mapping만 교체한 뒤 같은 focused test와./gradlew ktlintCheck를 실행했다. 결과: 둘 다BUILD SUCCESSFUL이었다.
- 검증 기록(RED): 무엇: 목록 item의 상세 전용 field 비노출 계약. 왜: 목록 응답이 상세 DTO를 재사용해
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerTest -
Task 2.5: 캐릭터 생성 흐름 보완
Goal 실행 P2-T4: 캐릭터 생성의 중복 검증, 외부 API, 이미지, 원작, creatorMember와 이벤트 흐름을 parity 기준으로 완결한다.
- 추적 review ID:
REV-002,REV-003,REV-007중 생성 request·characterType계약. - 시작 조건:
P2-T3완료와 생성 관련 확정 review ID. - 완료 증거: 정상 생성 및 실패 지점별 RED/GREEN, DB/S3/외부 API/event 결과, focused/legacy test와 Progress 기록.
- 범위 밖: 기존 external character API 계약 변경, 캐릭터 수정·비활성화.
Files:
-
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/AiCharacterAdminCharacterDto.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterExternalApiClient.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterImageStorage.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterizationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterControllerEventCharacterizationTest.kt -
systemPrompt,externalCharacterId, 생성isActive와 invalidcharacterType의 canonical request 계약을 Endpoint Contract Summary·legacy 특성화 결과로 확정하고 충돌 시 코드 수정 전에 Decision Log를 갱신한다. -
RED: 확정된 문서 JSON의 역직렬화·반영, 중복 이름, 외부 API 실패, S3 실패와 존재하지 않는
originalWorkId실패를 각각 재현한다. -
RED 확인: 실패 지점별 DB row·creatorMember·원작 연결·S3 객체·외부 캐릭터·event 결과와 호출 순서를 단언해 현재 부분 저장 또는 고아 부작용을 확인한다.
-
GREEN: 모든 DB 참조를 외부 부작용 전에 검증하고, legacy parity에 맞는 최소 보상/정리 경계로 정상 생성과 실패 원자성을 통과시킨다.
-
GREEN 확인: 같은 생성 focused/characterization test를 다시 실행해 정상 결과와 실패 지점별 잔존 상태가 확정 계약과 일치하는지 확인한다.
-
REFACTOR: creatorMember 표시 정보와 언어 이벤트를 포함한 focused/legacy characterization test 및
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: v2 생성 request와 실패 순서 계약. 왜: 외부 ID·생성 활성 상태가 무시되고, 존재하지 않는 원작이 외부 생성 뒤에 실패했기 때문이다. 어떻게:
AiCharacterAdminCharacterControllerMutationTest에 서버 소유 field, invalidcharacterType, 중복 이름, 원작, 외부 API, S3 실패와 creatorMember·언어 이벤트 assertion을 추가하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest를 실행했다. 결과: 서버 소유 field는 400 기대 대비 200, 원작 실패는 외부 요청 0 기대 대비 1로 실패했다. S3 DB assertion의 최초 실패는 class-level test transaction 관찰 오류였으므로 해당 test만 transaction 밖에서 재실행해 DB rollback을 확인했다. - 검증 기록(GREEN): 무엇: 서버 소유 create field 거부와 원작 선검증. 왜:
externalCharacterId/생성isActive를 client 입력으로 받지 않고, 존재하지 않는 원작에서 외부 캐릭터를 만들지 않기 위해. 어떻게: create DTO의 수신 field를 명시적으로 거부하고 facade에서 원작을 외부 호출 전에 조회한 뒤 같은 focused test를 실행했다. 결과: 10개 test가BUILD SUCCESSFUL이었다. - 검증 기록(REFACTOR): 무엇: 생성 focused/legacy parity 회귀와 formatting. 왜: 정상 원작 연결, AI creatorMember 표시 정보, 언어 감지 event, duplicate·외부 API·S3 실패의 결과를 legacy 특성화와 함께 유지하기 위해. 어떻게: 아래 Verify 명령과
./gradlew ktlintCheck를 실행했다. 결과: focused/legacy 명령은BUILD SUCCESSFUL in 40s,ktlintCheck는BUILD SUCCESSFUL in 11s였다. 전체./gradlew test는 task 범위가 character create slice이고 focused/legacy 명령으로 직접 영향 범위를 확인하므로 실행하지 않았다.
- 검증 기록(RED): 무엇: v2 생성 request와 실패 순서 계약. 왜: 외부 ID·생성 활성 상태가 무시되고, 존재하지 않는 원작이 외부 생성 뒤에 실패했기 때문이다. 어떻게:
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterControllerEventCharacterizationTest -
Task 2.6: 캐릭터 수정·비활성화 흐름 보완
Goal 실행 P2-T5: 캐릭터 수정과 isActive=false가 표시 정보를 동기화하고 연결 Member·콘텐츠를 보존하도록 완결한다.
- 추적 review ID:
REV-002,REV-003,REV-007중 수정 request·응답·soft-delete parity. - 시작 조건:
P2-T4완료와 수정·비활성화 관련 확정 review ID. - 완료 증거: 수정·이미지 유지/교체·soft delete RED/GREEN, 보존/no-partial-update 검증과 Progress 기록.
- 범위 밖: hard delete, cascade delete, 복원 API.
Files:
-
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/AiCharacterAdminCharacterDto.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterExternalApiClient.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterImageStorage.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterMapper.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterizationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterControllerEventCharacterizationTest.kt -
externalCharacterId, 일반 수정과isActive=false혼합 요청, invalidcharacterType의 canonical update 계약을 확정하고 충돌 시 Decision Log를 먼저 갱신한다. -
RED: 문서 PUT JSON의 field 반영, soft delete와 일반 수정 혼합, image 동시 요청, 외부 수정 성공 후 S3/DB 실패,
updatedAtUtc의 flush 전 mapping을 각각 재현한다. -
RED 확인: soft delete 성공·실패에서 row·Member·콘텐츠 보존, 미참조 S3 객체 0건, 외부/DB 상태 일치와 응답 timestamp가 후속 GET과 같은지 확인한다.
-
GREEN: 확정 계약에 맞춰 혼합 요청을 명시적으로 처리하고, 불필요한 upload를 차단하며 외부/S3/DB 보상 경계와 flush 후 response mapping을 최소 구현한다.
-
GREEN 확인: 같은 수정 focused/characterization test를 다시 실행해 field 반영, 보상 결과, soft-delete 보존과 timestamp가 모두 통과하는지 확인한다.
-
REFACTOR: creatorMember 표시 정보·번역 event와 legacy
characterType동작을 포함한 focused/characterization test 및ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: PUT의 서버 소유 external ID,
isActive=false혼합 image, 존재하지 않는 원작과 flush 전 timestamp 계약. 왜: 기존 구현이 client external ID를 무시하고 soft delete 전에 image를 업로드하며, 원작 검증 후 외부 수정과 이전updatedAtUtc를 반환했기 때문이다. 어떻게:AiCharacterAdminCharacterControllerMutationTest에 해당 회귀, image 유지·교체, S3 실패 경계, creatorMember·콘텐츠 보존과 번역 event test를 추가하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest를 실행했다. 결과: 16건 중 server-owned ID, mixed soft delete, missing original work, post-flush timestamp 4건이 의도대로 실패했다. - 검증 기록(GREEN/REFACTOR): 무엇: canonical update 계약과 post-flush 응답. 왜: soft delete의 고아 image를 막고 정상 수정의 creatorMember 동기화·번역 event·timestamp 및 외부/S3/DB 실패 경계를 고정하기 위해. 어떻게:
externalCharacterId명시 거부,isActive=false혼합 거부, 원작 선검증,flush()후 response mapping을 적용한 뒤 아래 Verify 명령과./gradlew ktlintCheck를 실행했다. 결과: 모두BUILD SUCCESSFUL이었다. 전체./gradlew test는 변경 범위가 character update slice이고 지정 focused/legacy 회귀가 직접 영향 범위를 포함하므로 실행하지 않았다.
- 검증 기록(RED): 무엇: PUT의 서버 소유 external ID,
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.LegacyChatCharacterAdminCharacterControllerEventCharacterizationTest -
Task 2.7: Phase 2 보안·오류·회귀 보완
Goal 실행 P2-T6: Phase 2의 모든 endpoint가 공통 ADMIN·오류·CORS 계약을 공유하고 legacy/public 계약을 회귀시키지 않음을 고정한다.
- 추적 review ID:
REV-001,REV-007,REV-008. - 시작 조건:
P2-T5완료 또는 앞선 Goal의 근거 있는해당 없음판정. - 완료 증거: endpoint 권한 매트릭스, 정확한 오류 status/key/KO·EN·JA, legacy 회귀와 Progress 기록.
- 범위 밖: Phase 1 공통 security/error 구조 재설계, Phase 3 기능.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterController.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 -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
RED:
/api/v2/admin/ai-characters/unmapped-path가 detailLongbinding에 잡혀 404 대신 400이 되는 KO/EN/JA·허용 Origin CORS 4건을 현재 Phase 1 오류 계약 test로 재현한다. -
RED: 목록·상세·생성·수정 각각의 JWT role × DB role, stale ADMIN claim과 binding·multipart·domain/client/server 오류의 exact status/key/KO·EN·JA를 parameterized test로 고정한다.
-
RED 확인: 오류 계약과 실제 endpoint matrix를 실행해 404 회귀 4건과 누락된 인가·i18n assertion이 의도대로 실패하는지 확인한다.
-
GREEN: numeric
characterId만 resource handler에 매핑되도록 최소 수정하고, Phase 2 오류 의미를 확정된 message key와ApiResponse.error로 반환한다. -
GREEN 확인: 같은 오류·인가 focused test를 다시 실행해 실제 endpoint의 status/header/envelope와 KO/EN/JA가 모두 통과하는지 확인한다.
-
REFACTOR: 실제 test 파일 목록과 targeted 명령을 대조해 존재하지 않는
AiCharacterAdminCharacterServiceTest참조 및 과거 test 수 기록은 삭제하지 않고 정정 기록을 누적한다. -
기존 admin/public character contract, 신규 DTO 의존 방향과 Phase 2 focused test·
ktlintCheck결과를 Progress에 기록한다.- 정정 기록:
AiCharacterAdminCharacterServiceTest는 현재 존재하지 않는 과거 계획 참조다. P2-T6의 실제 범위는AiCharacterAdminCharacterControllerTest,AiCharacterAdminCharacterControllerMutationTest, 공통AiCharacterAdminAuthorizationTest,AiCharacterAdminErrorContractTest이며 기존 완료 이력과 과거 test 수 기록은 삭제하지 않았다. - 검증 기록(RED): 무엇: 실제 character controller가 문자열 미매핑 경로를 404 fallback으로 넘기는지와 endpoint matrix. 왜:
/{characterId}의Longbinding이 fallback 404 계약을 400으로 바꾸고 있었기 때문이다. 어떻게: 아래 Verify 명령을 production 변경 전 실행했다. 결과: 전체 146건 중unmapped-pathKO/EN/JA와 허용 Origin CORS 4건만 404 기대 대비 400으로 실패했고, 새 목록 binding·상세 target·생성/수정 multipart i18n 및 실제 endpoint non-ADMIN/CORS assertion은 통과했다. - 검증 기록(GREEN/REFACTOR): 무엇: numeric path 제약과 Phase 2 ADMIN/error/CORS/legacy 회귀. 왜: 문자열 segment는 fallback 404로, 숫자 resource와 기존 public/legacy는 기존 계약으로 유지해야 하기 때문이다. 어떻게: controller의 GET/PUT path를
[0-9]+로 제한한 뒤 아래 Verify 명령과./gradlew ktlintCheck를 실행했다. 결과: focused command는BUILD SUCCESSFUL in 1m 26s, ktlint는BUILD SUCCESSFUL in 34s였고, P2-GATE는 이 Task 범위 밖으로 미완료 상태를 유지한다.
- 정정 기록:
-
Verify:
./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
Phase 2 Gate
Goal 실행 P2-GATE: Phase 2 캐릭터 관리의 PRD 추적성, 정상·실패 흐름과 legacy 회귀를 최종 판정한다.
-
P2-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다. -
시작 조건:
P2-R1,P2-T3~P2-T6완료 또는 근거 있는해당 없음판정. -
완료 증거: 아래 명령 성공, review 후보 0건, 확정 finding 처리 완료와 Progress 기록.
-
범위 밖: Gate 통과를 위한 test 삭제·완화, Phase 3 기능 수정.
-
REV-001~REV-003,REV-007,REV-008의 계약 결정·failure matrix·수정 test와 실제 결과가 각 소유 Goal의 Progress에 연결됐다. -
캐릭터 목록 exact key, 문서 mutation JSON, 동시 중복 결과, 외부/S3/DB 보상, post-flush
updatedAtUtc와 실제 endpoint 권한·i18n matrix에 미결정 항목이 없다. -
완료 이력의 누락 test 파일·test 수·failure-order 증거는 원문을 삭제하지 않고 최신 정정 기록으로 재현 가능하게 남겼다.
- 검증 기록: 무엇:
P2-GATEPhase 2 최종 판정. 왜:P2-R1,P2-T3~P2-T6의 확정 finding 처리와 Gate 명령 성공을 확인하기 위해. 어떻게: 아래 세 Gate 명령을 실행했다. 결과: character focused 명령은 최초 병렬 실행 중 XML test result write 충돌로 실패했으나 동일 명령 단독 재실행은BUILD SUCCESSFUL in 1m 11s였다. authorization/error 명령은BUILD SUCCESSFUL in 1m 30s,ktlintCheck는BUILD SUCCESSFUL이었다.git diff --check도 통과했다.
- 검증 기록: 무엇:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
Expected: 모든 명령 exit code 0, Phase 2 review의 후보·확정 미처리 항목 0건, 관련 legacy/public 계약 diff 없음.
Phase 2 후속 리뷰 보완
- Task 2.8: 실제 character endpoint 보안·오류·실패 경계 증거 보강
Goal 실행 P2-R2: REV-009에서 확인한 실제 character endpoint별 인가·CORS·오류와 mutation 실패 경계를 non-vacuous 회귀 test로 고정한다.
- 추적 review ID:
REV-009. - 시작 조건: 기존
P2-GATE완료 이력과phase2-character-review.md2차 리뷰 판정 존재. - 완료 증거: 아래 실제 endpoint test, 공통 authorization/error 회귀,
ktlintCheck와 Progress 기록. - 범위 밖: 공통 Phase 1 security/error 재설계, external API 보상 endpoint·신규 DDL 추가, Phase 3 기능.
- TDD 예외 사유: 현재 production 실패가 아니라 완료 기록 대비 직접 검증 증거 누락이 확정된 test 보강 Task다.
- 대체 검증 방법: 실제 endpoint test를 먼저 추가하고, 현재 동작이 계약을 만족하면 production code 변경 없이 통과 증거를 기록한다. 계약 불일치가 재현될 때만 해당 assertion의 RED를 확인하고 최소 수정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterController.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.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/AiCharacterAdminAuthorizationTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
CONTRACT TEST: 목록·상세·생성·수정 실제 경로에서 JWT role × 현재 DB role과 stale ADMIN claim을 parameterized test로 고정한다.
-
CONTRACT TEST: 실제 GET/POST/PUT의 허용·거부 Origin/preflight와 대표 binding·domain·client·server 오류의 exact status/key/KO·EN·JA envelope를 고정한다.
-
FAILURE TEST: 중복·원작·external API·S3·DB 실패에서 DB/creatorMember/originalWork/S3/external/event 결과를 직접 단언하고 기존 non-compensated external 경계를 유지한다.
-
GREEN: 새 test가 현재 계약 불일치를 재현할 때만 가장 작은 production 수정으로 통과시키고, 이미 통과하면 production code를 변경하지 않는다.
-
REFACTOR: character focused와 legacy characterization, 공통 authorization/error 및
ktlintCheck결과를 Progress에 기록한다.- 검증 기록: 무엇:
REV-009의 실제 character endpoint 증거 보강. 왜: 기존 Gate 기록이 네 endpoint의 stale claim, allow/deny CORS preflight, KO/EN/JA 실패 envelope를 직접 매트릭스로 고정했다는 증거가 부족했기 때문이다. 어떻게:AiCharacterAdminCharacterControllerTest에 목록·상세·생성·수정 실제 경로의 stale ADMIN claim 403과 허용/거부 Origin preflight를 추가하고,AiCharacterAdminCharacterControllerMutationTest의 external API 실패, 생성 S3 실패, 수정 S3 실패를 KO/EN/JA envelope와 잔존 DB/S3/external 상태 단언으로 확장했다. 결과: production code 변경 없이 아래 focused 명령이BUILD SUCCESSFUL in 1m 47s였다.
- 검증 기록: 무엇:
./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
Phase 2 후속 리뷰 Gate
Goal 실행 P2-R2-GATE: REV-009의 직접 증거를 재검토하고 Phase 2 후속 리뷰를 종결한다.
P2-R2-GATE완료:P2-R2완료 후 fresh 검증과 리뷰 문서 수정 후 기록을 남긴다.- 시작 조건:
P2-R2완료. - 완료 증거:
REV-009수정 완료, 위 두 명령 성공,phase2-character-review.md최신 결론과 Progress 동기화. - 범위 밖: 기존
P2-GATE이력 수정, Phase 3 production 변경.- 검증 기록: 무엇:
P2-R2-GATE후속 리뷰 종결. 왜:REV-009의 직접 증거가 추가됐고 Phase 3 후속 보완으로 넘어갈 수 있는지 판정하기 위해. 어떻게:phase2-character-review.md에 3차 후속 검증 기록을 누적하고 위 focused 명령을 fresh 실행했다. 결과:BUILD SUCCESSFUL in 1m 47s였고,REV-009는 처리 완료로 판정했다.ktlintCheck는 Phase 3 후속 보완까지 완료한 뒤 공통으로 실행해 전체 후속 범위 검증 기록에 남긴다.
- 검증 기록: 무엇:
Phase 2 4차 리뷰 보완
- Task 2.9: character DB·event 실패 경계 증거 보강
Goal 실행 P2-R3: REV-012에서 남은 character 생성·수정의 DB/event 실패 후 내부·외부 부작용 경계를 실제 흐름으로 고정한다.
- 추적 review ID:
REV-012. - 시작 조건: 기존
P2-R2-GATE완료 이력과phase2-character-review.md4차 리뷰 판정 존재. - 완료 증거: 실제 mutation failure test, character/common 회귀,
ktlintCheck와 Progress 기록. - 범위 밖: external character API 보상 endpoint 추가, 신규 DDL, Phase 3 이후 production 변경.
- TDD 예외 사유: 현재 production 실패가 아니라
Task 2.8완료 기록 대비 직접 검증 증거 누락이 확정된 test 보강 Task다. - 대체 검증 방법: 실제 DB flush/save 또는 event publish 실패를 먼저 재현하고, 현재 transaction·비보상 경계가 계약과 일치하면 production code 변경 없이 관찰 결과를 고정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.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 -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
FAILURE CHARACTERIZATION: 생성·수정의 DB flush/save와 event publish 실패를 실제 transaction 경계에서 재현한다.
-
CONTRACT TEST: 각 실패 뒤 ChatCharacter·creatorMember·originalWork·event와 이미 발생한 external/S3 결과를 직접 단언한다.
-
CONTRACT TEST: 대표 실패의 exact HTTP status와 KO/EN/JA
ApiResponse.error를 실제 mutation endpoint에서 확인한다. -
GREEN: 현재 계약 위반이 재현될 때만 최소 production 수정으로 통과시키고, 기존 비보상 경계와 일치하면 test-only로 종료한다.
-
REFACTOR: character/common focused 회귀와
ktlintCheck결과를 Progress와 리뷰 수정 후 기록에 누적한다.- 검증 기록: 무엇:
REV-012의 character 생성·수정 실패 경계 증거를 보강했다. 왜: 기존 완료 기록이 external/S3 실패는 확인했지만 event publish 실패와 KO/EN/JA 대표 실패 경계를 직접 고정하지 않았기 때문이다. 어떻게:AiCharacterAdminCharacterControllerMutationTest에 external/S3 실패 locale matrix와 facade 직접 event 실패 특성화를 추가했다. 결과: production code 변경 없이 mutation focused 명령은BUILD SUCCESSFUL in 1m 1s였다.
- 검증 기록: 무엇:
./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
Phase 2 4차 리뷰 Gate
Goal 실행 P2-R3-GATE: REV-012의 DB/event 실패 경계 증거를 재검토하고 Phase 2 4차 리뷰를 종결한다.
P2-R3-GATE완료:P2-R3완료 후 fresh 검증과 리뷰 문서 수정 후 기록을 남긴다.- 시작 조건:
P2-R3완료. - 완료 증거:
REV-012처리 완료, 위 두 명령 성공,phase2-character-review.md최신 결론과 Progress 동기화. - 범위 밖: 기존 Phase 2 완료 이력 수정, Phase 3 production 변경.
- 검증 기록: 무엇: Phase 2 4차 리뷰의
REV-012처리를 종결했다. 왜: Phase 3 4차 보완으로 넘어가기 전 character failure evidence 완료 여부를 판정하기 위해. 어떻게: character mutation focused test와 최종ktlintCheck를 실행하고phase2-character-review.md를 처리 완료로 갱신했다. 결과: mutation focused 명령은BUILD SUCCESSFUL in 1m 1s, 최종ktlintCheck는BUILD SUCCESSFUL in 17s였다.
- 검증 기록: 무엇: Phase 2 4차 리뷰의
Phase 2 5차 리뷰 보완
- Task 2.10: character 실제 transaction DB·event 실패 경계 완결
Goal 실행 P2-R4: REV-015의 생성·수정 persistence/event 실패를 actual endpoint와 Spring transaction 경계에서 재현하고 내부 rollback·외부 비보상 결과를 고정한다.
- 추적 review ID:
REV-015. - 시작 조건: 기존
P2-R3-GATE완료 이력과phase2-character-review.md5차 리뷰 판정 존재. - 완료 증거: actual POST/PUT failure test, transaction 종료 뒤 DB 재조회, character/common 회귀와
ktlintCheck결과 및 Progress 기록. - 범위 밖: external character API 보상 endpoint, S3 object 정리 정책, 신규 DDL, Phase 3 이후 production 변경.
- TDD 예외 사유: 현재 production 결함보다 기존
REV-012완료 기록의 transaction/rollback 직접 증거 누락이 확정된 회귀 검증 Task다. - 대체 검증 방법: test transaction 밖 actual endpoint와 repository/publisher failure injection으로 production proxy를 통과시키고, 요청 종료 뒤 내부 DB와 외부 interaction을 재조회한다. 계약 위반이 재현될 때만 최소 production 수정으로 전환한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.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/AiCharacterAdminErrorContractTest.kt -
FAILURE CHARACTERIZATION:
@Transactional(propagation = Propagation.NOT_SUPPORTED)actual POST/PUT에서ApplicationEventPublisher실패를 주입하고 500common.error.unknownenvelope을 확인한다. -
PERSISTENCE FAILURE:
@SpyBean ChatCharacterRepository로 생성save와 수정flush()실패를 각각 주입해 external 호출 뒤 transaction rollback 순서를 재현한다. -
CONTRACT TEST: 생성 실패 뒤 ChatCharacter·creatorMember·originalWork 부재, 수정 실패 뒤 기존 character·creatorMember·originalWork 상태 유지, S3/event interaction과 external 호출 횟수를 직접 단언한다.
-
CONTRACT TEST: 대표 event/persistence 실패의 KO/EN/JA exact HTTP status/message를 actual mutation endpoint에서 확인한다.
-
GREEN: 현재 transaction 계약 위반이 재현될 때만 가장 작은 production 수정으로 통과시키고, 기존 rollback·비보상 경계와 일치하면 test-only로 종료한다.
-
REFACTOR: direct
createFacadeevent failure test를 actual endpoint 증거로 대체하거나 역할을 명확히 축소하고 character/common 회귀와ktlintCheck결과를 Progress에 기록한다.
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./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
- 검증 기록: 무엇:
REV-015의 생성·수정 event/save/flush 실패가 실제 MockMvc POST/PUT와 facade transaction 경계를 통과해 내부 DB rollback과 외부 비보상 호출을 보이는지 고정했다. 왜: direct facade 호출은 Spring transaction proxy 및 요청 종료 뒤 DB 상태를 증명하지 못했기 때문이다. 어떻게:NOT_SUPPORTEDtest에서 facade proxy target의 publisher mock과@SpyBean ChatCharacterRepositoryfailure를 주입하고,TransactionTemplate재조회로 상태를 확인했다. 결과: 초기 RED는@MockBean이 이미 생성된 facade field를 대체하지 못해 실제 event listener가 실행되고 200이 반환된 것으로 확인됐으며, 실제 proxy target에 같은 mock을 교체한 뒤 KO/EN/JA 500 envelope, 생성 내부 state 부재, 수정 기존 state 유지, 외부 호출 1회와 image 없는 S3 미호출이 통과했다. production 변경은 없었다../gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest는BUILD SUCCESSFUL in 1m 8s, 영향 범위 character/auth/error 회귀는BUILD SUCCESSFUL in 1m 59s,./gradlew ktlintCheck는BUILD SUCCESSFUL in 42s였다.
Phase 2 5차 리뷰 Gate
Goal 실행 P2-R4-GATE: REV-015의 actual transaction·rollback 증거를 재검토하고 Phase 2 5차 리뷰를 종결한다.
P2-R4-GATE완료:P2-R4완료 후 위 명령을 fresh 실행하고 리뷰 문서·Progress를 갱신한다.- 시작 조건:
P2-R4완료. - 완료 증거:
REV-015수정 완료, actual endpoint transaction evidence, focused/영향 범위 회귀와 lint 성공. - 범위 밖: 기존 Phase 2 완료 이력 수정, Phase 3 production 변경.
- 진행 기록:
P2-R4구현과 fresh 검증을 완료했고, 5차 재리뷰에서REV-015보완 완료와 Phase 2 Gate 종료를 확인했다.
- 진행 기록:
Phase 2 6차 리뷰 보완
- Task 2.11: 캐릭터 생성 Endpoint Contract Summary 동기화
Goal 실행 P2-R5: REV-018의 캐릭터 생성 예시를 DEC-P2-T4-001 및 production request 계약과 일치시키고
actual endpoint 회귀로 확인한다.
- 추적 review ID:
REV-018. - 시작 조건: 기존
P2-R4-GATE완료 이력과phase2-character-review.md6차 리뷰 판정 존재. - 완료 증거: Endpoint Contract Summary 생성 JSON 정정, 정상 생성·서버 소유 field 거부 actual endpoint 회귀,
git diff --check와 Progress 기록. - 범위 밖: production DTO/facade 변경, 캐릭터 생성 behavior 변경, 외부 API 계약 변경, Phase 3 이후 production 변경.
- TDD 예외 사유: production과 기존 actual endpoint test는 확정 계약을 충족하고 문서 예시만 반대로 남은 문서 정합성 수정 Task다.
- 대체 검증 방법: 생성 예시의 exact field를 문자열 검색으로 확인하고 기존 정상 생성·서버 소유 field 거부 actual endpoint test를 재실행한다.
Files:
-
Modify:
docs/20260724_AI캐릭터_관리자_API/plan-task.md -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterDto.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt -
CONTRACT DOC: POST 생성 예시에 필수
systemPrompt를 추가하고 request의externalCharacterId,isActive를 제거한다. -
CONTRACT DOC 확인: 생성 예시와
DEC-P2-T4-001을 대조해 request field와 response 전용 field가 일치하는지 확인한다. -
CONTRACT TEST:
systemPrompt를 포함하고 서버 소유 field를 제외한 actual POST가 성공하며 response에 외부 API가 반환한externalCharacterId와 서버 생성isActive=true가 있는지 확인한다. -
REJECTION TEST:
externalCharacterId또는 생성isActive가 포함된 actual POST가 외부/S3/DB/event 부작용 전 400으로 거부되는 기존 회귀를 확인한다. -
REFACTOR: production 변경 없이 문서 diff와 focused test 결과를 Progress에 기록한다.
- 검증 기록: 무엇:
REV-018의 캐릭터 생성 Endpoint Contract Summary 예시를 production 생성 request 계약과 동기화했다. 왜: 예시가 필수systemPrompt를 누락하고 서버 소유externalCharacterId,isActive를 포함했기 때문이다. 어떻게:rg -n -A 12 'POST /api/v2/admin/ai-characters' docs/20260724_AI캐릭터_관리자_API/plan-task.md로 예시 field를 확인하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest를 실행했다. 결과: 예시는systemPrompt포함 및 서버 소유 field 제외로 확인됐고 focused test는BUILD SUCCESSFUL in 30s였다.
- 검증 기록: 무엇:
rg -n -A 12 'POST /api/v2/admin/ai-characters' docs/20260724_AI캐릭터_관리자_API/plan-task.md
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest
git diff --check
Phase 2 6차 리뷰 Gate
Goal 실행 P2-R5-GATE: REV-018의 문서 계약 정합성을 재검토하고 Phase 2 6차 리뷰를 종결한다.
P2-R5-GATE완료:P2-R5완료 후 위 문서/focused 검증을 fresh 실행하고 리뷰 문서·Progress를 갱신한다.- 시작 조건:
P2-R5완료. - 완료 증거:
REV-018처리 완료, Endpoint Contract Summary·Decision Log·production 계약 일치, focused test와 diff check 성공. - 범위 밖: Gate에서 production code 수정, 기존 Phase 2 완료 이력 변경, Phase 3 production 변경.
- 검증 기록: 무엇:
P2-R5-GATE에서REV-018문서 계약 정합성을 종결했다. 왜: Phase 3 6차 보완의 시작 조건이P2-R5-GATE완료이기 때문이다. 어떻게:P2-R5focused 검증 결과와phase2-character-review.md6차 판정을 대조했다. 결과: production 변경 없이REV-018처리 완료로 판정했다.
- 검증 기록: 무엇:
Phase 3: 오디오 콘텐츠 관리와 signed URL vertical slice
목표
선택한 AI 캐릭터 소유 오디오 콘텐츠 목록/검색/상세/생성/수정/soft delete와 관리자 재생용 signed URL을 제공한다.
범위와 비범위
- 포함: 콘텐츠 owner 검증, 기존 파일 처리/가격/공개/예약/번역/알림 parity,
AudioContentCloudFront재사용, private path 비노출. - 제외: 콘텐츠 구매/좋아요/댓글, content upload/processing pipeline 변경, community audio 30분 정책 통합.
선행 Phase 및 의존성
- Phase 1 target resolver.
- 콘텐츠 생성/수정/delete 기존 동작 특성화 테스트.
API endpoint와 request/response contract
GET /api/v2/admin/ai-characters/audio-content-themes-
Request: query/body 없음.
-
Response
data:[ { "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-contentsGET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}POST /api/v2/admin/ai-characters/{characterId}/audio-contents- multipart
coverImage,audioFile,requestJSON string part를 사용한다. requestJSON은title,description,tags,price,purchaseOption,limited,isAdult,isActive,themeId,releaseDateUtc?,seriesIds,isGeneratePreview,isOnlyRental,isPointAvailable,isCommentAvailable,isFullDetailVisible,previewStartTime,previewEndTime,languageCode를 포함한다.- legacy
CreateAudioContentRequest의detail은 v2description,releaseDate는 UTC ISO-8601releaseDateUtc로 받으며 facade에서 기존 pipeline 입력으로 변환한다.
- multipart
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 상세 필드는 관리자 상세에서 안전한 기본값을 반환한다.
entity, repository, service 변경
- Entity: 변경 없음.
- Repository:
AudioContentowner-scoped query adapter 추가 가능. - Service: 신규 content admin application service에서 기존 creator/admin content service를 테스트로 고정 후 선택 위임 또는 최소 추출한다.
- CloudFront: 기존
AudioContentCloudFront그대로 주입해 사용한다.
DB migration
- 없음.
transaction과 concurrency 고려사항
- write transaction 시작 직후 target과
content.member.id == creatorMember.id를 검증한다. - S3 업로드, 이벤트 발행 순서는 기존 동작 parity를 따른다.
- soft delete는 기존 콘텐츠 삭제 동작처럼
isActive=false, 필요한 경우releaseDate=null을 유지한다.
보안 및 개인정보 위험
- private S3 object path, signed key material을 응답하지 않는다.
- 다른 캐릭터 content ID 접근은 4xx와 no side effect다.
acceptance criteria
- target 캐릭터 소유 콘텐츠만 조회/변경된다.
- signed URL 만료 계산이 기존 creator admin policy와 동일하다는 테스트가 있다.
- signed URL 만료 계산·path 처리에서 실제 기존 구현에서 관찰되는 edge case가 특성화 테스트로 고정된다.
- invalid target/ownership 실패 시 DB/S3/event side effect가 없다.
targeted test
- Characterization:
LegacyCreatorAdminAudioContentCharacterizationTest,AudioContentCloudFrontCharacterizationTest. - V2 RED/GREEN:
AiCharacterAdminAudioContentServiceTest,AiCharacterAdminAudioSignedUrlTest. - Run:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'
전체 회귀 테스트 영향
- 기존
CreatorAdminContentController,AdminContentController, public content 조회 테스트가 통과해야 한다.
rollback 전략
- 신규 content v2 admin route/facade를 제거한다.
- 신규 DDL이 없으므로 schema rollback은 없다.
권장 commit 경계
-
feat: add ai character admin content slice -
Task 3.1: 기존 콘텐츠와 signed URL 특성화 baseline 고정
- Goal 이력
P3-H1: 기존 creator/admin 콘텐츠와 signed URL 동작을 신규 v2 비교 기준으로 고정했다. - 완료 증거: 아래 특성화 테스트와
ktlintCheck실행 기록. - 범위 밖: 신규 v2 content endpoint 구현.
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/LegacyCreatorAdminAudioContentCharacterizationTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AudioContentCloudFrontCharacterizationTest.kt - CHARACTERIZE: 기존 creator/admin content 구현을 대상으로 검증, 파일 처리, 가격, 공개/예약, 번역/알림, soft delete, upload/processing pipeline 결과를 고정한다.
- CHARACTERIZE: 기존 creator admin signed URL 만료 계산식과 만료 계산·path 처리에서 실제로 관찰되는 edge case, private path 비노출 계약을 고정한다.
- BASELINE: 신규 v2 production code 변경 전에 두 특성화 테스트가 기존 구현을 대상으로 통과함을 확인한다.
- REFACTOR: 테스트 fixture만 정리하고 content/signed URL parity baseline은 변경하지 않는다.
- Verify:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*CharacterizationTest' - 검증 기록 (2026-07-26): 위 특성화 테스트 6건과
./gradlew ktlintCheck가 모두 통과했다.
- Goal 이력
-
Task 3.2: content controller/facade/DTO 구현
- Goal 이력
P3-H2: 콘텐츠 조회·생성·수정의 최초 v2 API 구현을 제공했다. - 완료 증거: 아래 RED/GREEN 및 후속 보완 기록. 이 이력만으로 Phase 3 Gate 통과를 의미하지 않는다.
- 범위 밖: 후속 심층 리뷰에서 확정되는 누락·회귀 보완.
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/* - RED: Task 3.1 baseline에서 옮긴 content parity, cross-character content 접근, signed URL/private path 계약 테스트와 controller 권한/페이지네이션 경계 테스트를 작성하고 신규 v2 미구현으로 실패함을 확인한다.
- GREEN: content endpoint와 owner-scoped query/write를 구현하고 목록/검색
page/size기본값·최소·최대 보정을 적용한다. - REFACTOR: signed URL/private path mapping과 기존 pipeline 재사용 경계를 정리하고 회귀 테스트를 재실행한다.
- Verify:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*' - 구현 기록(2026-07-26):
GET목록/검색·상세와PUT수정/soft delete를 target resolver와 owner-scoped query로 구현했다. 응답은 v2 DTO만 사용하며 private content path 필드를 반환하지 않는다. - 구현 보완(2026-07-27):
POST생성을 기존AudioContentService.createAudioContent재사용 방식으로 제공한다. v2 adapter에서themeId, ISO-8601releaseDateUtc, owner-scopedseriesIds연결을 처리하고, 업로드 완료 전isActive직접 활성화는 기존 processing pipeline parity를 위해 수행하지 않는다. - 검증 기록(RED, 2026-07-26):
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest'를 실행했고, 신규 route 부재로 목록은 404, 상세와 PUT은 404/405여서 의도대로 실패함을 확인했다. - 검증 기록(GREEN, 2026-07-26):
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'와./gradlew ktlintCheck를 실행해 모두BUILD SUCCESSFUL을 확인했다. - 검토 보완(2026-07-26): 상세 응답에 target owner 범위의 활성 series ID 목록을 추가하고, 다른 캐릭터 콘텐츠 PUT이 DB를 변경하지 않는 통합 테스트를 추가했다.
- 재검증(2026-07-26):
./gradlew test --rerun-tasks --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'를 실행해BUILD SUCCESSFUL을 확인했다. - 검토 보완(2026-07-27): 생성/수정의
seriesIdsowner-scoped 연결, 생성의 ISO-8601releaseDateUtc변환, POST 제공 문서 불일치를 보완했다. 수정audioFile교체는 기존 creator/admin 수정 pipeline에 없어 Phase 3에서 미지원으로 명시했다.
- Goal 이력
-
Task 3.3: Phase 3 요구사항·계약·코드 리뷰
Goal 실행 P3-R1: PRD Feature C와 Endpoint Contract Summary를 콘텐츠 코드·테스트에 추적해 확정된 누락만 후속 Goal로 전환한다.
- 추적 review ID:
REV-004,REV-005,REV-006,REV-007,REV-008. - 시작 조건:
P2-GATE,P3-H1,P3-H2완료 증거 존재. - 완료 증거:
docs/sample/sample-review.md형식의 리뷰 문서, endpoint·pipeline·side-effect 추적표, 모든 후보 판정과 Progress 기록. - 범위 밖: 리뷰 도중 production code 수정, series/community 기능 검토.
- TDD 예외 사유: 구현이 아닌 read-only 리뷰 Task다.
- 대체 검증 방법: PRD·계약·production·test를 대조하고 focused test를 실행해 실제 결과를 리뷰 문서에 기록한다.
Files:
-
Create:
docs/20260724_AI캐릭터_관리자_API/reviews/phase3-audio-content-review.md -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentController.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentDto.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt -
테마·목록·검색·상세·생성·수정·soft delete endpoint와 DTO 필드를 PRD/계약에 추적한다.
-
signed URL TTL/path, private path 비노출, viewer 상태 기본값을 production·test에 추적한다.
-
생성/update pipeline, 파일, 가격, 공개·예약, 번역·알림,
seriesIds, 날짜 변환과 실패 순서를 확인한다. -
owner 검증, no-side-effect, ADMIN 인가, 오류 i18n, pagination/multipart 계약을 확인한다.
-
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'결과와 후보 판정을 리뷰 문서에 기록한다.- 검증 기록: 무엇: Phase 3 오디오 콘텐츠 slice의 read-only 요구사항·계약·코드 리뷰. 왜:
P3-H1,P3-H2완료 이력 이후REV-004REV-008의 실제 확정 여부와 후속 소유 Goal을 고정하기 위해. 어떻게:docs/20260724_AI캐릭터_관리자_API/reviews/phase3-audio-content-review.md에 PRD Feature C, Endpoint Contract Summary, production/test 대조표와 발견 사항을 기록하고./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'를 실행했다. 결과: focused test는BUILD SUCCESSFUL in 2s였고,REV-004REV-008은 각각P3-T3~P3-T7의 기존 소유 Goal로 연결했다. 리뷰 Task이므로 production code는 수정하지 않았다.
- 검증 기록: 무엇: Phase 3 오디오 콘텐츠 slice의 read-only 요구사항·계약·코드 리뷰. 왜:
-
Task 3.4: 활성 콘텐츠 테마 API 보완
Goal 실행 P3-T3: 활성 콘텐츠 테마를 전용 themeId/themeName/imageUrl DTO로 반환하는 관리자 API를 완결한다.
- 추적 review ID:
REV-007,REV-008중 v2 DTO 경계와 endpoint 인가. - 시작 조건:
P3-R1완료와 테마 endpoint review 판정. - 완료 증거: request body 없는 GET, 활성 필터, DTO field contract RED/GREEN과 Progress 기록.
- 범위 밖: 테마 CRUD, legacy DTO 외부 노출.
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 -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentThemeControllerTest.kt -
RED: 테마 endpoint 부재 또는 계약 불일치와 legacy field 노출을 재현하는 가장 작은 실패 test를 작성한다.
-
RED 확인: focused test를 실행해 의도한 route·field assertion 실패를 확인한다.
-
GREEN: 활성 테마만
themeId,themeName,imageUrl로 반환하는 최소 구현을 작성한다. -
GREEN 확인: request body 없음, exact field set과 ADMIN 이중 인가를 포함한 focused test 성공을 확인한다.
-
REFACTOR: v2 DTO 경계만 정리하고 테마 focused test와
ktlintCheck결과를 Progress에 기록한다.- 검증 기록: 무엇:
P3-T3의 활성 콘텐츠 테마 endpoint 계약 증거를 전용 focused test로 분리했다. 왜: production 동작은 이미AiCharacterAdminAudioContentController/Facade/DTO에서 충족하고 있었지만,REV-007,REV-008기준 Gate 증거가 단일 대형 controller test에 섞여 있었기 때문이다. 어떻게:AiCharacterAdminAudioContentThemeControllerTest를 추가해 활성 필터, orders 정렬,themeId/themeName/imageUrlexact field, legacyid/theme/image비노출과 anonymous 401을 검증하고 기존 controller test의 중복 테마 케이스를 제거했다. 결과:./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentThemeControllerTest는BUILD SUCCESSFUL in 35s, 테마+기존 controller focused 회귀는BUILD SUCCESSFUL in 48s,./gradlew ktlintCheck는BUILD SUCCESSFUL in 11s였다. production code는 추가하지 않았다.
- 검증 기록: 무엇:
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentThemeControllerTest -
Task 3.5: 콘텐츠 목록·상세·signed URL 보완
Goal 실행 P3-T4: owner-scoped 콘텐츠 조회와 signed URL·상세 DTO 계약을 독립적으로 완결한다.
- 추적 review ID:
REV-005,REV-007중 상세 parity와 v2 전용 DTO 경계. - 시작 조건:
P3-T3완료와 조회/signed URL 관련 review 판정. - 완료 증거: 검색·status·pagination·상세·TTL/path/private 정보 RED/GREEN과 Progress 기록.
- 범위 밖: 콘텐츠 생성·수정, community audio 30분 정책.
Files:
-
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/AiCharacterAdminAudioContentDto.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentMapper.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentQueryTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AudioContentCloudFrontCharacterizationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt -
RED:
purchaseOption=RENT_ONLY, entityisOnlyRental=true, 미래·과거releaseDate조합에서 legacy 상세와 다른isOnlyRental,purchaseOption,releaseDate를 재현한다. -
RED: 응답의 creator·buyer·other content·comment·translation 중첩 타입이 legacy/public DTO package에 직접 의존하는 현재 경계를 검출하고 exact JSON key를 고정한다.
-
RED 확인: query/legacy baseline test를 실행해 세 compatibility field와 금지 DTO 의존이 의도대로 실패하는지 확인한다.
-
GREEN: legacy 파생 규칙과 현지화된
releaseDate의미를 유지하고 UTC 원본은releaseDateUtc에만 반환하며, 동일 JSON을 v2 전용 중첩 DTO로 최소 매핑한다. -
GREEN 확인: 같은 query/legacy baseline test를 다시 실행해 legacy compatibility field,
releaseDateUtc와 exact JSON schema가 모두 통과하는지 확인한다. -
REFACTOR: owner·검색·status·pagination, 활성 owner-scoped
seriesIds, viewer 기본값, signed URL TTL/path와 private 정보 비노출을 함께 회귀한다. -
조회/signed URL focused test와
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: 상세 응답의 legacy
releaseDate의미, RENT_ONLY 파생값과 v2 전용 중첩 DTO 경계. 왜:REV-005,REV-007에서 상세 DTO가 legacy/public 중첩 DTO에 직접 의존하고, 과거 공개일을 legacyreleaseDate에도 노출하고 있었기 때문이다. 어떻게:AiCharacterAdminAudioContentQueryTest를 추가하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentQueryTest를 실행했다. 결과: 2건이 의도대로 실패했다. 하나는releaseDate가 존재해서 실패했고, 하나는 legacy nested DTO package 누출 assertion으로 실패했다. - 검증 기록(GREEN/REFACTOR): 무엇: 상세 응답의
releaseDateUtc전용 노출, RENT_ONLY 파생 규칙, v2 전용 중첩 DTO. 왜: 관리자 상세는 UTC 원본을releaseDateUtc에만 고정하고, response DTO는 legacy/public DTO 타입을 외부 계약으로 재노출하지 않아야 하기 때문이다. 어떻게:AiCharacterAdminAudioContentDto에 v2 중첩 response DTO를 추가하고,AiCharacterAdminAudioContentMapper의releaseDate,isOnlyRental,purchaseOption,creatormapping만 최소 수정했다. 결과:AiCharacterAdminAudioContentQueryTest는BUILD SUCCESSFUL in 29s, 계획서 Verify 묶음은BUILD SUCCESSFUL in 41s,./gradlew ktlintCheck는 최초 unused import 2건으로 실패 후 정리 재실행에서BUILD SUCCESSFUL in 10s였다.
- 검증 기록(RED): 무엇: 상세 응답의 legacy
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentQueryTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AudioContentCloudFrontCharacterizationTest --tests kr.co.vividnext.sodalive.content.AudioContentServiceTest -
Task 3.6: 콘텐츠 생성 pipeline 보완
Goal 실행 P3-T5: 콘텐츠 생성의 multipart 입력, legacy field 변환, 파일·processing·series 연결과 side effect parity를 완결한다.
- 추적 review ID:
REV-006,REV-007,REV-008중 생성 binding·field·pipeline 특성화. - 시작 조건:
P3-T4완료와 생성 pipeline 관련 review 판정. - 완료 증거: 전체 생성 field·파일·날짜·series RED/GREEN, 실패 순서와 focused/legacy test 기록.
- 범위 밖: upload/processing pipeline 정책 변경, 오디오 파일 교체.
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/AiCharacterAdminAudioContentFacade.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentDto.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentCreateTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/LegacyCreatorAdminAudioContentCharacterizationTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
tags필수 여부와 생성isActive=false의 canonical 계약을 legacy pipeline·Endpoint Contract Summary로 확정하고 충돌 시 코드 수정 전에 Decision Log를 갱신한다. -
RED:
coverImage,audioFile,request각 part 누락에서 Kotlin nullable 때문에MissingServletRequestPartException이 발생하지 않는 현재 binding과 KO/EN/JA envelope 차이, facade·DB·S3·event 호출 0건 기대를 재현한다. -
RED: 생성 request 전체 field,
description/releaseDateUtc변환,tags누락,isActive=false, target·theme·seriesIds오류와 S3/processing/event 실패 순서를 각각 고정한다. -
RED 확인: create/error/legacy characterization test를 실행해 part별 exception·field 계약·failure order가 의도대로 실패하는지 확인한다.
-
GREEN: 필수 file part를 non-null binding으로 만들고 확정된 field 계약, 외부 부작용 전 참조 검증과 legacy upload/processing parity를 최소 구현한다.
-
GREEN 확인: 같은 test를 다시 실행해 part별 400/i18n, 정상 생성과 실패 후 DB/S3/event 결과가 모두 통과하는지 확인한다.
-
REFACTOR: cover/audio upload, 가격·공개·예약·번역·알림 및 실패 후 DB/S3/event 결과를 characterization/focused test로 회귀하고
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: 콘텐츠 생성의 필수 multipart part와 legacy 기본 계약. 왜:
REV-006에서 생성 binding·field·failure-order 증거가 분리되지 않았고,tags누락과isActive=true요청의 canonical 동작을 확정해야 했기 때문이다. 어떻게:AiCharacterAdminAudioContentCreateTest를 추가하고./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest를 실행했다. 결과: 정상 생성 케이스가 200 기대 대비 500으로 실패했고, 원인은 production 계약이 아니라 test fixture의AmazonS3Client.getUrl(String, String)미설정으로S3Uploader.putS3에서 null URL이 발생한 것이었다. - 검증 기록(GREEN/REFACTOR): 무엇: 생성 필수 part 400, 업로드 전 S3 0회,
tags누락 허용,isActive=true요청의 legacy processing 기본값. 왜: 신규 v2 생성은 기존 upload/processing pipeline을 바꾸지 않고 adapter 계약만 고정해야 하기 때문이다. 어떻게: test fixture에amazonS3Client.getUrl(...)mock만 추가하고 production code는 변경하지 않았다. 결과:AiCharacterAdminAudioContentCreateTest는BUILD SUCCESSFUL, create+legacy+error contract 회귀는BUILD SUCCESSFUL,./gradlew ktlintCheck는BUILD SUCCESSFUL in 10s였다.
- 검증 기록(RED): 무엇: 콘텐츠 생성의 필수 multipart part와 legacy 기본 계약. 왜:
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.LegacyCreatorAdminAudioContentCharacterizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest -
Task 3.7: 콘텐츠 수정·soft delete 보완
Goal 실행 P3-T6: 콘텐츠 수정, cover 유지/교체, series 재연결과 soft delete를 owner-safe하게 완결한다.
- 추적 review ID:
REV-004. - 시작 조건:
P3-T5완료와 수정·삭제 관련 review 판정. - 완료 증거: 수정·soft delete·audioFile 미지원·cross-owner RED/GREEN과 Progress 기록.
- 범위 밖: audio file 교체, hard delete.
Files:
-
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/AiCharacterAdminAudioContentRepository.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/series/SeriesContent.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentUpdateTest.kt -
RED: 기존
SeriesContent.orders, row ID,createdAt이 있는 콘텐츠에 동일seriesIds를 PUT했을 때 전부 삭제·재생성되는 현재 동작을 실패 test로 고정한다. -
RED 확인: 동일 ID, 추가 ID, 제거 ID를 각각 요청해 교집합 metadata 보존과 차집합만 insert/delete한다는 기대가 현재 실패하는지 확인한다.
-
GREEN: 기존 연결과 요청 ID의 차집합만 변경하고 교집합 row의 ID·
orders·createdAt을 보존하는 최소 구현을 작성한다. -
GREEN 확인: 같은 update test를 다시 실행해 동일 집합 no-op, 교집합 metadata 보존과 차집합 변경만 발생하는지 확인한다.
-
REFACTOR: cover 유지/교체, 날짜,
audioFile미지원,isActive=false와 cross-owner/invalid series의 DB/S3/event no-side-effect를 회귀한다. -
수정 focused test와
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: 동일
seriesIds수정 시 기존SeriesContentmetadata 보존. 왜:REV-004에서 기존 구현이 연결을 모두 삭제·재생성해 row ID,orders,createdAt을 소실했기 때문이다. 어떻게:AiCharacterAdminAudioContentUpdateTest를 추가하고 focused 실행했다. 결과: 최초 focused test는 200 기대 대비 500으로 실패했고, 원인은 응답 매핑의 CloudFront private key fixture 문제임을 로그로 확인한 뒤 test fixture에AudioContentCloudFrontmock을 추가했다. - 검증 기록(GREEN/REFACTOR): 무엇:
replaceSeriesIds가 요청 ID와 기존 연결의 차집합만 변경하고 교집합 row를 보존하도록 수정했다. 왜: 동일 series 연결의 metadata를 유지하면서 제거·추가만 반영해야 하기 때문이다. 어떻게:requestedIds, 기존 연결 ID set을 비교해 삭제 대상만 remove하고 신규 ID만 persist했다. 결과:AiCharacterAdminAudioContentUpdateTest는BUILD SUCCESSFUL in 46s, update+controller+legacy+error contract 회귀는BUILD SUCCESSFUL in 1m 21s였다. 기존 controller 회귀 2건은 앞선P3-T4확정 계약(releaseDate미노출,RENT_ONLY파생값)에 맞춰 기대값만 갱신했다.
- 검증 기록(RED): 무엇: 동일
-
Verify:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentUpdateTest -
Task 3.8: 콘텐츠 ownership·오류·legacy 회귀 보완
Goal 실행 P3-T7: Phase 3 모든 endpoint의 ownership·ADMIN·오류 계약과 legacy/public 회귀를 고정한다.
- 추적 review ID:
REV-006,REV-007,REV-008. - 시작 조건:
P3-T6완료 또는 앞선 Goal의 근거 있는해당 없음판정. - 완료 증거: 권한 매트릭스, 정확한 domain/client 오류 status/key/KO·EN·JA, no-side-effect와 legacy 회귀 기록.
- 범위 밖: Phase 1 공통 handler 재설계, Phase 4 series 기능.
Files:
-
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt -
Test:
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/AiCharacterAdminAuthorizationTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
RED: 테마·목록·상세·생성·수정 endpoint 각각의 JWT role × DB role, stale ADMIN claim과 허용·거부 Origin을 parameterized test로 고정한다.
-
RED: target/content/theme/series/date와 세 multipart part 누락의 exact status, exception type, message key와 KO/EN/JA envelope를 실제 endpoint에서 고정한다.
-
RED 확인: 실제 endpoint matrix와 legacy characterization을 실행해 누락된 인가·i18n·failure-order assertion이 의도대로 실패하는지 확인한다.
-
GREEN: 확정된 domain/client/server 오류만 최소 매핑하고 ownership 실패 시 DB insert/update/delete, S3, event 0건을 보장한다.
-
GREEN 확인: 같은 endpoint/error/ownership test를 다시 실행해 status/header/envelope, KO/EN/JA와 no-side-effect가 모두 통과하는지 확인한다.
-
REFACTOR: legacy characterization에 validation·파일·가격·공개/예약·번역/알림·failure order를 보강하고 signed URL edge case와 함께 실행한다.
-
실제 test 파일 목록과 targeted 명령을 대조해 존재하지 않는
AiCharacterAdminAudioContentServiceTest,AiCharacterAdminAudioSignedUrlTest참조와 과거 test 수는 삭제하지 않고 정정 기록을 누적한다. -
AiCharacterAdminAudioContentThemeControllerTest,AiCharacterAdminAudioContentQueryTest,AiCharacterAdminAudioContentCreateTest,AiCharacterAdminAudioContentUpdateTest,AiCharacterAdminAudioContentOwnershipTest의 파일 존재와 각 소유 계약 통과를 확인한다. -
creator/admin/public content 회귀, 신규 DTO 의존 방향과 focused test·
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): malformed
characterId/contentId6건이 미매핑 404 EN envelope을 기대한 새 ownership focused test에서 GET 400, write 415를 반환해 18건 중 6건이 실패했다. - 검증 기록(GREEN/REFACTOR): 모든 resource path를
[0-9]+로 제한한 뒤 malformed path 404, 실제 5개 endpoint의 non-ADMIN/stale claim 403, 테마 CORS allow/deny, unknown target 생성 S3 0회를 고정했다. common authorization/error test의 KO·EN·JA matrix와 기존 content/legacy characterization을 재사용했다. focused는BUILD SUCCESSFUL in 1m 5s, content+authorization+error 회귀는BUILD SUCCESSFUL in 2m 8s,ktlintCheck는BUILD SUCCESSFUL in 29s였다.AiCharacterAdminAudioContentServiceTest,AiCharacterAdminAudioSignedUrlTest는 현재 존재하지 않는 과거 계획 참조이며 이 기록으로 정정한다.
- 검증 기록(RED): malformed
-
Verify:
./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
Phase 3 Gate
Goal 실행 P3-GATE: Phase 3 콘텐츠 관리와 signed URL의 PRD 추적성, pipeline 안전성과 legacy 회귀를 최종 판정한다.
-
P3-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다. -
시작 조건:
P3-R1,P3-T3~P3-T7완료 또는 근거 있는해당 없음판정. -
완료 증거: 아래 명령 성공, review 후보 0건, 확정 finding 처리 완료와 Progress 기록.
-
범위 밖: Gate 실패와 무관한 Phase 4 기능 구현.
-
REV-004~REV-008의 response parity matrix, multipart exception, series metadata, DTO 경계와 test 증거가 각 소유 Goal의 Progress에 연결됐다. -
동일
seriesIds의 row metadata 보존, legacyreleaseDate·rental 파생값, 세 필수 part와 실제 endpoint 권한·i18n matrix에 미결정 항목이 없다. -
완료 이력의 누락 test 파일·test 수·characterization 범위는 원문을 삭제하지 않고 최신 정정 기록으로 재현 가능하게 남겼다.
- 검증 기록: 무엇:
P3-GATEPhase 3 최종 판정. 왜:P3-R1,P3-T3P3-T7의 확정 finding 처리와 Gate 명령 성공을 확인하기 위해. 어떻게: 아래 세 Gate 명령을 fresh 실행했다. 결과: content focused 명령은BUILD SUCCESSFUL in 2m 15s, authorization/error 명령은BUILD SUCCESSFUL in 1m 29s,ktlintCheck는BUILD SUCCESSFUL in 1s였다.git diff --check는P3-T7완료 전 실행에서 출력 없음이었다.REV-004REV-008의 Phase 3 소유 항목은 처리 완료로 판정했다.
- 검증 기록: 무엇:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*'
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
Expected: 모든 명령 exit code 0, Phase 3 review의 후보·확정 미처리 항목 0건, Phase 4가 소비할 owner query 계약 확정.
Phase 3 후속 리뷰 보완
- Task 3.9: 콘텐츠 생성 pipeline·multipart 오류 증거 보강
Goal 실행 P3-R2: REV-010의 생성 field adapter, 선검증, multipart 오류와 S3/processing/event 실패 순서를 실제 endpoint에서 고정한다.
- 추적 review ID:
REV-010. - 시작 조건:
P2-R2-GATE완료와 기존P3-GATE완료 이력 존재. - 완료 증거: 생성 actual endpoint/legacy characterization test, 실패 지점별 관찰 결과,
ktlintCheck와 Progress 기록. - 범위 밖: legacy upload/processing 정책 변경, 추정에 의한 S3 보상 추가, audio file 교체.
- TDD 예외 사유: 현재 production 실패가 아니라
P3-T5완료 기록 대비 직접 검증 증거 누락이 확정된 test 보강 Task다. - 대체 검증 방법: legacy와 actual endpoint를 characterization하고 불일치가 재현될 때만 RED/GREEN으로 최소 수정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentController.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.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/LegacyCreatorAdminAudioContentCharacterizationTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
CONTRACT TEST:
coverImage,audioFile,request누락의 실제 endpoint KO/EN/JA status/key/envelope와 facade·DB·S3·event 0건을 고정한다. -
CONTRACT TEST: 생성 request 전체 field의 legacy adapter 결과와 target/theme/series 선검증을 확인한다.
-
FAILURE CHARACTERIZATION: cover upload, audio upload와 event 실패 지점별 DB/S3/event 결과를 legacy parity와 대조하고 비트랜잭션 S3 결과를 명시한다.
-
GREEN: 실제 계약 위반만 최소 수정하고, 현재 동작이 계약을 만족하면 production code를 변경하지 않는다.
-
REFACTOR: create/legacy/error focused test와
ktlintCheck결과를 Progress와 리뷰 수정 후 기록에 누적한다.- 검증 기록: 무엇:
REV-010의 생성 multipart·theme·cover 실패 증거를 실제 endpoint test로 보강했다. 왜: 기존 완료 기록이 part별 KO/EN/JA message, invalid theme 선검증, cover upload 실패 후 DB/event 상태를 직접 고정하지 않았기 때문이다. 어떻게:AiCharacterAdminAudioContentCreateTest에 세 필수 part KO/EN/JA envelope, invalid theme 선검증, cover upload 실패 rollback/event 0회 단언을 추가했다. 결과: production code 변경 없이 create 단독 명령은BUILD SUCCESSFUL in 1m 4s, content/common 회귀 명령은BUILD SUCCESSFUL in 2m 41s였다.
- 검증 기록: 무엇:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.LegacyCreatorAdminAudioContentCharacterizationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./gradlew ktlintCheck
- Task 3.10: 콘텐츠 수정 차집합·cover·ownership 증거 보강
Goal 실행 P3-R3: REV-011의 seriesIds 교집합/차집합, cover 변경과 실제 endpoint ownership·오류 no-side-effect를 고정한다.
- 추적 review ID:
REV-011. - 시작 조건:
P3-R2완료. - 완료 증거: update/ownership actual endpoint test, content/authorization/error 회귀,
ktlintCheck와 Progress 기록. - 범위 밖: audio file 교체, hard delete, Phase 4 series API 구현.
- TDD 예외 사유: 현재 production 실패가 아니라
P3-T6~P3-T7완료 기록 대비 직접 검증 증거 누락이 확정된 test 보강 Task다. - 대체 검증 방법: 교집합+추가+제거와 cover/ownership 계약을 non-vacuous test로 작성하고 실패가 재현될 때만 최소 수정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentRepository.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentUpdateTest.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentOwnershipTest.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt -
CONTRACT TEST: 기존 교집합, 신규 추가, 기존 제거를 한 요청에 포함해 교집합 row ID·
orders·createdAt보존과 차집합만 insert/delete됨을 확인한다. -
CONTRACT TEST: cover 미지정 유지, 성공 교체와 업로드 실패, 날짜 변경, soft delete, cross-owner/invalid series의 DB/S3/event 결과를 고정한다.
-
CONTRACT TEST: 실제 목록·상세·생성·수정의 ownership/domain 오류를 KO/EN/JA envelope과 DB insert/update/delete·S3·event count로 확인한다.
-
GREEN: 실제 계약 위반만 최소 수정하고, 현재 동작이 계약을 만족하면 production code를 변경하지 않는다.
-
REFACTOR: content package와 공통 authorization/error, legacy characterization,
ktlintCheck결과를 Progress와 리뷰 수정 후 기록에 누적한다.- 검증 기록: 무엇:
REV-011의 수정 차집합·cover·ownership 증거를 보강했다. 왜: 기존 완료 기록보다 실제 endpoint의 교집합 보존, cover 유지/교체/실패, ownership/domain no-side-effect 증거가 좁았기 때문이다. 어떻게:AiCharacterAdminAudioContentUpdateTest와AiCharacterAdminAudioContentOwnershipTest에 관련 회귀를 추가하고 content/common 회귀로 재확인했다. 결과: production code 변경 없이 content/common 회귀 명령은BUILD SUCCESSFUL in 2m 41s였다.
- 검증 기록: 무엇:
./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 3 후속 리뷰 Gate
Goal 실행 P3-R2-GATE: REV-010~REV-011의 직접 증거를 재검토하고 Phase 3 후속 리뷰를 종결한다.
P3-R2-GATE완료:P3-R2,P3-R3완료 후 fresh 검증과 리뷰 문서 수정 후 기록을 남긴다.- 시작 조건:
P3-R2,P3-R3완료. - 완료 증거: 두 review ID 수정 완료, content/common 회귀와
ktlintCheck성공,phase3-audio-content-review.md최신 결론과 Progress 동기화. - 범위 밖: 기존
P3-GATE이력 수정, Phase 4 기능 구현.- 검증 기록: 무엇:
P3-R2-GATE후속 리뷰 종결. 왜:REV-010~REV-011의 직접 증거가 추가됐고 Phase 4 전 Phase 3 후속 보완 종료 여부를 판정하기 위해. 어떻게: content/common 회귀와ktlintCheck를 fresh 실행하고phase3-audio-content-review.md에 3차 후속 검증 기록을 누적했다. 결과: content/common 회귀는BUILD SUCCESSFUL in 2m 41s였고,ktlintCheck결과는 아래 Progress 검증 기록에 남긴다.
- 검증 기록: 무엇:
Phase 3 4차 리뷰 보완
- Task 3.11: 생성 후반 실패·ownership no-side-effect 증거 보강
Goal 실행 P3-R4: REV-013~REV-014에서 남은 audio upload/event 실패와 실제 endpoint ownership/domain 오류의 부작용 경계를 고정한다.
- 추적 review ID:
REV-013,REV-014. - 시작 조건:
P2-R3-GATE와 기존P3-R2-GATE완료. - 완료 증거: create/update/ownership actual endpoint test, legacy characterization, content/common 회귀,
ktlintCheck와 Progress 기록. - 범위 밖: S3 보상 정책 신설, audio file 수정 지원, Phase 4 기능 구현.
- TDD 예외 사유: 현재 production 실패가 아니라
Task 3.9~Task 3.10완료 기록 대비 직접 검증 증거 누락이 확정된 test 보강 Task다. - 대체 검증 방법: cover 이후 audio upload와 event 실패, ownership/domain 거부를 실제 endpoint에서 먼저 characterization하고 계약 불일치가 재현될 때만 최소 수정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.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/AiCharacterAdminAudioContentOwnershipTest.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/LegacyCreatorAdminAudioContentCharacterizationTest.kt -
FAILURE CHARACTERIZATION: 두 번째 S3 audio upload 실패와 event publish 실패에서 DB rollback, S3 호출·잔존 결과와 event 결과를 각각 고정한다.
-
CONTRACT TEST: 목록·상세·생성·수정의 target/ownership/domain 거부를 KO/EN/JA exact envelope로 확인한다.
-
CONTRACT TEST: 각 거부 뒤 AudioContent·SeriesContent·S3·event의 insert/update/delete count가 변하지 않음을 직접 단언한다.
-
GREEN: 실제 계약 위반만 최소 수정하고, legacy 비트랜잭션 S3 경계와 일치하면 production code를 변경하지 않는다.
-
REFACTOR: content/common/legacy 회귀와
ktlintCheck결과를 Progress와 리뷰 수정 후 기록에 누적한다.- 검증 기록: 무엇:
REV-013~REV-014의 content 생성 후반 실패와 ownership/domain no-side-effect 증거를 보강했다. 왜: 기존 완료 기록이 cover upload 실패와 일부 ownership 경로에 치우쳐 있었기 때문이다. 어떻게:AiCharacterAdminAudioContentCreateTest에 audio upload 실패와 event publish 실패를 추가하고,AiCharacterAdminAudioContentOwnershipTest에 목록·상세·생성·수정 unknown target KO/EN/JA 및 AudioContent·SeriesContent·S3·event 무변경 단언을 추가했다. 결과: create+ownership focused 명령은BUILD SUCCESSFUL in 1m 8s, content/common 회귀 명령은BUILD SUCCESSFUL in 2m 20s였다.
- 검증 기록: 무엇:
./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 3 4차 리뷰 Gate
Goal 실행 P3-R3-GATE: REV-013~REV-014의 직접 증거를 재검토하고 Phase 3 4차 리뷰를 종결한다.
P3-R3-GATE완료:P3-R4완료 후 fresh 검증과 리뷰 문서 수정 후 기록을 남긴다.- 시작 조건:
P3-R4완료. - 완료 증거: 두 review ID 처리 완료, 위 두 명령 성공,
phase3-audio-content-review.md최신 결론과 Progress 동기화. - 범위 밖: 기존 Phase 3 완료 이력 수정, Phase 4 기능 구현.
- 검증 기록: 무엇: Phase 3 4차 리뷰의
REV-013~REV-014처리를 종결했다. 왜: 사용자 지시에 따라 Phase 3 후속 보완까지만 완료하고 Phase 4로 넘어가지 않기 위해. 어떻게: content/common 회귀와 최종ktlintCheck를 fresh 실행하고phase3-audio-content-review.md를 처리 완료로 갱신했다. 결과: content/common 회귀 명령은BUILD SUCCESSFUL in 2m 20s, 최종ktlintCheck는BUILD SUCCESSFUL in 17s였다.
- 검증 기록: 무엇: Phase 3 4차 리뷰의
Phase 3 5차 리뷰 보완
- Task 3.12: 생성 필수 multipart part의 exact binding 계약 복구
Goal 실행 P3-R5: REV-016의 생성 필수 파일 part를 MVC non-null binding으로 고정하고 세 part 누락의 exact exception·KO/EN/JA 계약을 복구한다.
- 추적 review ID:
REV-016. - 시작 조건:
P2-R4-GATE완료와 PRD API Expectations 179~180의 missing-part 계약. - 완료 증거: 세 part별 RED/GREEN, exact
MissingServletRequestPartException, facade/DB/S3/event 0회, content/common 회귀와 Progress 기록. - 범위 밖: legacy
AudioContentService.createAudioContentsignature 변경, upload/processing pipeline 정책 변경, 오디오 파일 교체.
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/AiCharacterAdminAudioContentFacade.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentCreateTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
RED:
coverImage,audioFile,request각각을 누락한 KO/EN/JA actual endpoint test에서MvcResult.resolvedException이 정확히MissingServletRequestPartException이고 message가common.error.invalid_request인지 단언한다. -
RED 확인: create focused test를 실행해 nullable
coverImage·audioFile이 legacySodaException까지 전달되어 exact exception/message assertion이 실패하는지 확인한다. -
GREEN: 생성 controller와 facade의
coverImage,audioFile을 non-nullMultipartFile로 바꾸고 legacy service에는 검증된 non-null 값을 그대로 전달한다. -
GREEN 확인: 같은 focused test를 재실행해 세 part 누락 9건의 exact exception·KO/EN/JA 400 envelope과 facade/DB/S3/event 0회를 확인한다.
-
REFACTOR: 중복된 missing-part request/assertion만 parameterized helper로 정리하고 create/error/legacy 회귀와
ktlintCheck결과를 Progress에 기록한다.
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
./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
- Task 3.13: ownership/domain KO·EN·JA no-side-effect matrix 완결
Goal 실행 P3-R6: REV-017의 cross-owner와 domain validation 경로를 actual endpoint KO/EN/JA 및 DB/S3/event 무변경 증거로 완결한다.
- 추적 review ID:
REV-017. - 시작 조건:
P3-R5완료와 기존P3-R4unknown target matrix. - 완료 증거: cross-owner detail/update, create/update other-owner series, invalid date의 exact envelope·side-effect assertions, content/common 회귀와 Progress 기록.
- 범위 밖: 새로운 ownership 정책, 오류 key/status 변경, Phase 4 series API 구현.
- TDD 예외 사유: 현재 production 위반보다
REV-014완료 기록 대비 대표 ownership/domain 직접 증거 누락이 확정된 test 보강 Task다. - 대체 검증 방법: 기존 실제 endpoint test를 KO/EN/JA parameterized matrix로 확장하고 요청 전후 entity field·연결 row와 S3/event interaction을 비교한다. 실패가 재현될 때만 validation 순서를 최소 수정한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.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/AiCharacterAdminAudioContentOwnershipTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
OWNERSHIP TEST: 다른 캐릭터 소유 콘텐츠의 detail/update를 KO/EN/JA actual endpoint matrix로 만들고 content field·S3·event 무변경을 단언한다.
-
DOMAIN TEST: create/update의 다른 owner
seriesIds와 invalidreleaseDateUtc를 KO/EN/JA matrix로 만들고 AudioContent·SeriesContent insert/update/delete, S3, event 0회를 단언한다. -
NON-VACUOUS 확인: owner 또는 validation guard를 제거하면 각 matrix가 status/message 또는 side-effect assertion으로 실패하는지 확인한다.
-
GREEN: 현재 계약 위반이 재현될 때만 target/ownership/domain 선검증 순서를 최소 수정하고, 이미 충족하면 test-only로 종료한다.
-
REFACTOR: unknown target과 cross-owner/domain fixture의 공통 assertion만 정리하고 content/common 회귀와
ktlintCheck결과를 Progress에 기록한다.
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentOwnershipTest
./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 3 5차 리뷰 Gate
Goal 실행 P3-R4-GATE: REV-016~REV-017의 exact multipart와 ownership/domain 증거를 재검토하고 Phase 3 5차 리뷰를 종결한다.
P3-R4-GATE완료:P3-R5,P3-R6완료 후 위 content/common 회귀와 lint를 fresh 실행하고 리뷰 문서·Progress를 갱신한다.- 시작 조건:
P3-R5,P3-R6완료. - 완료 증거:
REV-016,REV-017수정 완료, 세 필수 part와 ownership/domain matrix 직접 증거, focused/영향 범위 회귀와 lint 성공. - 범위 밖: 기존 Phase 3 완료 이력 수정, Phase 4 기능 구현.
Phase 3 6차 리뷰 보완
- Task 3.14: 빈 multipart 파일 계약 고정
Goal 실행 P3-R7: REV-019의 생성·수정 empty-file 경계를 v2 facade에서 고정해 0-byte upload와 수정
audioFile 계약 우회를 차단한다.
- 추적 review ID:
REV-019. - 시작 조건:
P2-R5-GATE와 기존P3-R4-GATE완료. - 완료 증거: 생성 empty cover/audio 거부, 수정 empty cover 유지, empty/non-empty audio 거부 actual endpoint RED/GREEN과 DB/S3/event assertion, content/common 회귀 및 Progress 기록.
- 범위 밖: legacy
AudioContentService·CreatorAdminContentService공용 계약 변경, 파일 content-type/확장자 정책 추가, 오디오 파일 교체, Phase 4 기능 구현.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.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/AiCharacterAdminAudioContentUpdateTest.kt -
Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminErrorContractTest.kt -
RED: 생성의 빈
coverImage와 빈audioFileactual endpoint를 KO/EN/JA로 요청해 400common.error.invalid_request, AudioContent/SeriesContent·S3·event 0회를 기대하고 현재 200/업로드 경로로 실패함을 확인한다. -
RED: 수정의 빈
coverImage가 생략과 동일하게 기존 cover path를 유지하고 S3를 호출하지 않는 기대, 빈audioFilepart가 non-empty와 동일하게 400으로 거부되는 기대가 현재 실패함을 확인한다. -
GREEN: create 시작 시
coverImage.isEmpty || audioFile.isEmpty를invalidRequest()로 거부한다. -
GREEN: update는
audioFile != null이면 크기와 관계없이invalidRequest()로 거부하고,coverImage?.takeUnless { it.isEmpty }만 legacy update service에 전달한다. -
GREEN 확인: 같은 focused test를 재실행해 create empty-file의 부작용 0회, update empty cover의 DB/S3 유지와 empty/non-empty audio 거부가 모두 통과하는지 확인한다.
-
REFACTOR: empty-file fixture만 공통화하고 legacy service를 수정하지 않은 채 content/common 회귀와
ktlintCheck결과를 Progress에 기록한다.- 검증 기록(RED): 무엇: 생성 empty
coverImage/audioFileKO/EN/JA와 수정 empty cover/audio actual endpoint 계약을 추가했다. 왜: 빈 multipart 파일이 null/non-empty 검사 사이를 통과하는지 확인하기 위해. 어떻게:./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentUpdateTest를 실행했다. 결과: 26건 중 신규 8건이 line 119, 204, 236에서 실패해 RED를 확인했다. - 검증 기록(GREEN): 무엇: v2 facade empty-file 경계와 실제 service publisher no-interaction 증거. 왜: legacy 공용 service 변경 없이 신규 관리자 API 계약만 고정하고 event 부작용 assertion이 detached mock을 보지 않게 하기 위해. 어떻게: 같은 focused 명령을 재실행했다. 결과: reviewer gate 보완 후 최종 empty create/update focused는
BUILD SUCCESSFUL in 43s, non-empty audio update 보완 focused는BUILD SUCCESSFUL in 44s였다.
- 검증 기록(RED): 무엇: 생성 empty
./gradlew test --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.AiCharacterAdminErrorContractTest
./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
- Task 3.15: ownership/domain event 0회 실제 publisher 증거 복구
Goal 실행 P3-R8: REV-020의 ownership/domain no-side-effect test가 실제 AudioContentService와
CreatorAdminContentService의 publisher를 관찰하도록 연결해 NON-VACUOUS 완료 증거를 복구한다.
- 추적 review ID:
REV-020. - 시작 조건:
P3-R7완료. - 완료 증거: 실제 두 service proxy target의 publisher 교체·복원, mock identity 확인, ownership/domain matrix의 event 0회와 content/common 회귀 및 Progress 기록.
- 범위 밖: production event 발행 순서·payload 변경, application context event infrastructure 변경, 신규 test 전용 production seam 추가, Phase 4 기능 구현.
- TDD 예외 사유: 현재 production의 잘못된 event 발행이 아니라 detached mock으로 인한 완료 증거 공백이 확정된 test-only Task다.
- 대체 검증 방법: Phase 2와 기존 content create event failure test의
AopTestUtils·ReflectionTestUtils방식을 재사용해 실제 proxy target field와 mock identity를 확인한다.
Files:
-
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt -
Confirm:
src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/CreatorAdminContentService.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/AiCharacterAdminAudioContentOwnershipTest.kt -
Confirm:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentCreateTest.kt -
EVIDENCE RED: 현재
@MockBean ApplicationEventPublisher와 실제 두 service proxy target의 publisher가 같은 instance인지 단언해 detached 상태에서 실패함을 확인한다. -
EVIDENCE GREEN: 각 test에서
AudioContentService,CreatorAdminContentServiceproxy target의 기존 publisher를 보관하고 같은 mock으로 교체하며finally/teardown에서 원래 publisher를 복원한다. -
NON-VACUOUS 확인: 교체 직후 실제 두 target field가 mock과 같은 instance인지 단언하고, cross-owner detail/update·other-owner series·invalid date 및 unknown target matrix가 실제 publisher no-interaction을 통과하는지 확인한다.
-
회귀 확인: 기존 create event failure helper와 충돌하지 않고 정상 content 생성·수정의 event 회귀가 유지되는지 content/common 명령으로 확인한다.
-
REFACTOR: Phase 2 및 create failure test의 기존 helper 패턴 범위에서만 중복을 정리하고 production seam이나 공용 test abstraction은 추가하지 않는다.
- 검증 기록: 무엇:
REV-020의 실제 publisher no-interaction 증거를 복구했다. 왜: detached@MockBean ApplicationEventPublisher만 검증하면 실제 service field 호출 여부를 증명할 수 없기 때문이다. 어떻게:AudioContentService,CreatorAdminContentServiceproxy target의applicationEventPublisher를 테스트 mock으로 교체·복원하고 field identity를 단언한 뒤./gradlew test --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 39s였다.
- 검증 기록: 무엇:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentOwnershipTest
./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 3 6차 리뷰 Gate
Goal 실행 P3-R5-GATE: REV-019~REV-020의 empty-file 계약과 actual publisher 증거를 재검토하고 Phase 3
6차 리뷰를 종결한다.
P3-R5-GATE완료:P3-R7,P3-R8완료 후 위 content/common 회귀와 lint를 fresh 실행하고 리뷰 문서·Progress를 갱신한다.- 시작 조건:
P3-R7,P3-R8완료. - 완료 증거:
REV-019,REV-020처리 완료, 생성·수정 empty-file actual endpoint 계약, 실제 두 service publisher no-interaction 증거, focused/영향 범위 회귀와 lint·diff check 성공. - 범위 밖: Gate에서 production code 수정, 기존 Phase 3 완료 이력 변경, Phase 4 기능 구현.
Phase 4: 시리즈 관리 vertical slice
목표
선택한 AI 캐릭터 소유 시리즈 CRUD, soft delete, 콘텐츠 연결/해제/검색/순서 관리를 owner-safe v2 경로로 제공한다.
범위와 비범위
- 포함: 시리즈 목록/상세/생성/수정/soft delete, 콘텐츠 연결/해제, 시리즈 콘텐츠 조회/검색, owner-scoped 순서 변경, 기존 creator series behavior parity 특성화.
- 제외: 기존
CreatorAdminContentSeriesController.updateSeriesOrders(ids)계약 변경.
선행 Phase 및 의존성
- Phase 1 target resolver.
- Phase 3 content owner query를 재사용할 수 있다.
- 기존 creator series 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior를 통과하는 특성화 테스트로 먼저 고정해야 한다.
API endpoint와 request/response contract
GET/POST/PUT /api/v2/admin/ai-characters/{characterId}/series...GET /series/{seriesId}/contentsquery:search?,page,sizePOST /series/{seriesId}/contentsrequest:AddAiCharacterAdminSeriesContentsRequest(contentIds: List<Long>)DELETE /series/{seriesId}/contents/{contentId}PUT /series/ordersrequest:UpdateAiCharacterAdminSeriesOrdersRequest(seriesIds: List<Long>)
entity, repository, service 변경
- Entity: 변경 없음.
- Repository:
Series와SeriesContentowner-scoped query/update adapter 추가. - Service: 신규 series application service에서 기존 CRUD 핵심을 테스트 후 재사용하되, 모든 write 전에 series/content owner를 검증한다.
DB migration
- 없음.
transaction과 concurrency 고려사항
- 순서 변경은 동일 owner의 모든 series ID를 한 transaction에서 검증 후 갱신한다.
- 콘텐츠 연결/해제는 series와 content owner를 모두 검증한 뒤 수행한다.
- 동시에 순서 변경 요청이 들어오면 마지막 transaction 결과가 반영되는 기존 단순 정책을 유지하되 cross-owner 갱신은 절대 허용하지 않는다.
보안 및 개인정보 위험
- ID-only order update로 다른 creator series를 변경하지 못해야 한다.
- 연결 가능한 content 검색은 target owner 범위로 제한한다.
acceptance criteria
- 기존 creator series의 CRUD, soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior parity가 특성화 테스트로 고정된다.
- 모든 시리즈/콘텐츠 ID는 target creatorMember 소유일 때만 변경된다.
- soft delete는
isActive=false이며 활성 조회에서 제외된다. - 기존 owner-less order update 취약 경로가 신규 v2 API에는 없다.
targeted test
- Characterization:
LegacyCreatorAdminSeriesCharacterizationTest. - V2 RED/GREEN:
AiCharacterAdminSeriesServiceTest. - Run:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series.*'
전체 회귀 테스트 영향
- 기존 creator-admin series endpoint 계약은 유지한다.
- 기존 series query/order 테스트가 있으면 함께 실행한다.
rollback 전략
- 신규 series v2 admin route/facade를 제거한다.
- 기존 data model 변경이 없으므로 schema rollback은 없다.
권장 commit 경계
-
feat: add ai character admin series slice -
Task 4.1: 기존 series parity 특성화 baseline 고정
Goal 실행 P4-T1: 기존 creator series의 CRUD·연결·검색·순서 동작을 신규 v2 구현의 비교 기준으로 고정한다.
- 시작 조건: 최신 Phase 3 후속 Gate인
P3-R5-GATE완료와 사용자 진행 지시. - 완료 증거: production 변경 전 특성화 테스트 통과, 관찰된 오류·side-effect 정책과 Progress 기록.
- 범위 밖: 신규 v2 series production code 구현.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/LegacyCreatorAdminSeriesCharacterizationTest.kt -
목록·상세·생성·수정·soft delete와 inactive 조회 baseline test를 작성한다.
-
콘텐츠 연결·해제·검색과 순서 변경의 결과·검증·side effect를 고정한다.
-
Phase 4 domain/client 오류별 정확한 status와 KO/EN/JA message key를 확정해 구현 Goal에 반영한다.
-
production code 변경 없이 특성화 테스트가 기존 구현을 대상으로 통과함을 확인한다.
-
fixture만 정리하고 test와
ktlintCheck결과를 Progress에 기록한다. -
Task 4.2: 시리즈 목록·상세 조회 구현
Goal 실행 P4-T2: target owner의 활성 시리즈 목록·상세를 pagination 계약으로 제공한다.
- 시작 조건:
P4-T1완료와 Phase 4 오류 계약의 계획 반영. - 완료 증거: 목록·상세·inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
- 범위 밖: 시리즈 mutation, 콘텐츠 연결, 순서 변경.
Files:
-
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesController.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesDto.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesFacade.kt -
Create:
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/AiCharacterAdminSeriesQueryTest.kt -
미구현 목록·상세, inactive 제외, cross-owner 거부와 pagination 경계 실패 test를 작성한다.
-
owner-scoped 목록·상세 최소 구현으로 focused test를 통과시킨다.
-
page기본 0,size기본·최소 20·최대 50과 legacy DTO 비노출을 검증한다. -
focused test와
ktlintCheck결과를 Progress에 기록한다. -
Task 4.3: 시리즈 생성·수정·soft delete 구현
Goal 실행 P4-T3: target owner의 시리즈 생성·수정·soft delete를 기존 creator parity로 제공한다.
- 시작 조건:
P4-T1,P4-T2완료. - 완료 증거: CRUD RED/GREEN, ownership·inactive·no-side-effect와 legacy 회귀 기록.
- 범위 밖: 콘텐츠 연결·해제, 순서 변경, hard delete.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesController.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesDto.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesFacade.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesMutationTest.kt -
생성·수정·soft delete와 cross-owner mutation 실패 test를 작성한다.
-
owner 검증 후 최소 CRUD 구현으로 test를 통과시킨다.
-
isActive=false와 활성 조회 제외, invalid target의 DB/event no-side-effect를 검증한다. -
focused/legacy test와
ktlintCheck결과를 Progress에 기록한다. -
Task 4.4: 시리즈 콘텐츠 조회·검색·연결·해제 구현
Goal 실행 P4-T4: 동일 owner의 시리즈와 콘텐츠만 검색·연결·해제할 수 있도록 한다.
- 시작 조건:
P4-T2,P4-T3와 Phase 3 owner query 계약 완료. - 완료 증거: 검색·pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과 Progress 기록.
- 범위 밖: 콘텐츠 자체 수정, 시리즈 순서 변경.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesController.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesFacade.kt -
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를 작성한다.
-
모든 series/content ID를 mutation 전에 검증하는 최소 구현을 통과시킨다.
-
일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
-
focused/Phase 3 owner query 회귀와
ktlintCheck결과를 Progress에 기록한다. -
Task 4.5: owner-scoped 시리즈 순서 변경 구현
Goal 실행 P4-T5: 요청된 모든 series ID의 owner를 먼저 검증한 뒤 한 transaction에서 순서를 변경한다.
- 시작 조건:
P4-T3완료. - 완료 증거: 정상·중복/누락·cross-owner·동시 요청 RED/GREEN과 owner-less 경로 비사용 증거.
- 범위 밖: 기존
CreatorAdminContentSeriesController.updateSeriesOrders(ids)변경.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesController.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesFacade.kt -
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/AiCharacterAdminSeriesOrderTest.kt -
정상 순서와 cross-owner ID-only 취약 경로를 재현하는 실패 test를 작성한다.
-
동일 owner 전체 검증 후 한 transaction에서 갱신하는 최소 구현을 통과시킨다.
-
검증 실패 시 update 0건과 동시 요청의 기존 last-transaction 정책을 확인한다.
-
focused/legacy order test와
ktlintCheck결과를 Progress에 기록한다. -
Task 4.6: Phase 4 보안·오류·회귀 검증
Goal 실행 P4-T6: 모든 series endpoint의 ADMIN·오류·ownership 계약과 legacy 회귀를 고정한다.
- 시작 조건:
P4-T2~P4-T5완료. - 완료 증거: endpoint 권한 매트릭스, domain 오류 status/key/KO·EN·JA, legacy 회귀와 Progress 기록.
- 범위 밖: Phase 5 community 기능.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesContractTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt -
endpoint별 ADMIN 이중 인가와 stale claim을 검증한다.
-
target/series/content/pagination 오류의 정확한 status, message key, KO/EN/JA envelope를 확정·검증한다.
-
invalid ownership의 DB/event side effect 0건과 legacy creator series 계약을 검증한다.
-
Phase 4 focused test와
ktlintCheck결과를 Progress에 기록한다.
Phase 4 Gate
Goal 실행 P4-GATE: Phase 4 series 사용자 흐름과 ownership·회귀 품질을 최종 판정한다.
-
P4-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다. -
시작 조건:
P4-T1~P4-T6완료. -
완료 증거:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.series.*'와./gradlew ktlintCheck성공, Progress 기록. -
범위 밖: 실패와 무관한 Phase 5 구현.
Phase 5: 커뮤니티 게시글 관리 vertical slice
목표
선택한 AI 캐릭터 소유 커뮤니티 게시글 등록, 수정, 고정/해제, soft delete와 관리자 조회를 제공한다.
범위와 비범위
- 포함: owner-scoped community query/write, 최대 고정 3개, soft delete 시 fixed 상태 제거, 이미지/오디오/유료 게시글 검증, 기존 알림/최근 소식 side effect parity.
- 제외: 구매/좋아요/댓글 관리, public community 조회 정책 변경.
선행 Phase 및 의존성
- Phase 1 target resolver.
- 기존 community write behavior 특성화 테스트.
API endpoint와 request/response contract
GET /api/v2/admin/ai-characters/{characterId}/community-postsPOST /api/v2/admin/ai-characters/{characterId}/community-postsPUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}- update request는
isFixed,isActive, 본문/이미지/오디오/가격 필드를 포함한다.
entity, repository, service 변경
- Entity: 변경 없음.
- Repository: creatorMember owner-scoped community post query adapter 추가 가능.
- Service: 신규 community application service에서 target owner 검증 후 기존 핵심 로직을 선택 재사용한다.
DB migration
- 없음.
transaction과 concurrency 고려사항
- 고정 게시글 수 검증과 고정 처리는 같은 transaction에서 수행한다.
- soft delete는 같은 transaction에서
isActive=false,isFixed=false,fixedAt=null을 함께 적용한다. - 동시 고정 요청은 기존 최대 3개 정책이 깨지지 않도록 repository count와 update 순서를 테스트한다.
보안 및 개인정보 위험
- 다른 character/HUMAN creator 게시글 수정, 고정, soft delete를 차단한다.
- 유료 게시글의 접근 정책과 파일 경로 노출 정책을 기존 동작과 맞춘다.
acceptance criteria
- target creatorMember 소유 게시글만 조회/변경된다.
- 최대 고정 수 3개 정책이 유지된다.
- soft delete된 게시글은 fixed 상태와 fixedAt이 제거된다.
- invalid target/ownership 실패 시 DB/S3/event side effect가 없다.
targeted test
- Characterization:
LegacyCommunityPostCharacterizationTest. - V2 RED/GREEN:
AiCharacterAdminCommunityPostServiceTest. - Run:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.community.*'
전체 회귀 테스트 영향
- 기존 v2 community 조회와 legacy community write 테스트가 통과해야 한다.
rollback 전략
- 신규 community v2 admin route/facade를 제거한다.
- 신규 DDL이 없으므로 schema rollback은 없다.
권장 commit 경계
-
feat: add ai character admin community slice -
Task 5.1: 기존 community behavior 특성화 baseline 고정
Goal 실행 P5-T1: 기존 community의 media·유료·고정·soft delete·알림 동작을 신규 v2 비교 기준으로 고정한다.
- 시작 조건:
P4-GATE완료. - 완료 증거: production 변경 전 특성화 테스트 통과, 오류·side-effect·동시성 관찰 결과와 Progress 기록.
- 범위 밖: 신규 v2 community production code 구현.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/LegacyCommunityPostCharacterizationTest.kt -
image/audio/paid post validation과 notification/recent-news side effect baseline을 작성한다.
-
최대 고정 3개와 fixed post soft delete clearing baseline을 작성한다.
-
Phase 5 domain/client 오류별 정확한 status와 KO/EN/JA message key를 확정해 구현 Goal에 반영한다.
-
production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
-
fixture/event spy만 정리하고 test와
ktlintCheck결과를 Progress에 기록한다. -
Task 5.2: 관리자 게시글 목록 조회 구현
Goal 실행 P5-T2: target owner의 관리자용 커뮤니티 게시글 목록을 안전한 전용 DTO와 pagination으로 제공한다.
- 시작 조건:
P5-T1완료와 Phase 5 오류 계약의 계획 반영. - 완료 증거: owner/inactive/pagination/DTO RED/GREEN과 public 조회 무비판적 복제 없음 증거.
- 범위 밖: 게시글 생성·수정·고정·삭제.
Files:
-
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostController.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostDto.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostFacade.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostQueryTest.kt -
미구현 목록, owner 격리, pagination 경계와 관리자 DTO 실패 test를 작성한다.
-
최소 owner-scoped query와
page/size보정으로 focused test를 통과시킨다. -
유료/media private 정보와 public viewer 상태를 부적절하게 노출하지 않는지 검증한다.
-
focused test와
ktlintCheck결과를 Progress에 기록한다. -
Task 5.3: 커뮤니티 게시글 생성 구현
Goal 실행 P5-T3: target creatorMember 작성자로 이미지·오디오·유료 게시글을 기존 검증과 side effect parity로 생성한다.
- 시작 조건:
P5-T1,P5-T2완료. - 완료 증거: 정상/media/paid validation RED/GREEN, writer/owner와 S3/event 결과, Progress 기록.
- 범위 밖: 게시글 수정·고정·soft delete, 구매 기능.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostController.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostDto.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostFacade.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostCreateTest.kt -
정상/image/audio/paid validation과 invalid target 실패 test를 작성한다.
-
해석된 creatorMember를 writer/owner로 사용하는 최소 생성 구현을 통과시킨다.
-
S3, notification, recent-news 호출 순서와 실패 시 부분 저장/no-side-effect를 검증한다.
-
focused/legacy characterization test와
ktlintCheck결과를 Progress에 기록한다. -
Task 5.4: 게시글 수정·고정·soft delete 구현
Goal 실행 P5-T4: owner 게시글만 수정·고정/해제하고 soft delete 시 고정 상태와 시간을 함께 제거한다.
- 시작 조건:
P5-T1,P5-T2완료. - 완료 증거: update/fixed/soft delete/cross-owner RED/GREEN과 transaction 결과 기록.
- 범위 밖: hard delete, 구매·좋아요·댓글 관리.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostController.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostFacade.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostUpdateTest.kt -
수정·고정/해제·soft delete와 cross-owner 실패 test를 작성한다.
-
owner 검증 후 최소 mutation 구현으로 test를 통과시킨다.
-
soft delete가 한 transaction에서
isActive=false,isFixed=false,fixedAt=null을 적용하는지 검증한다. -
focused/legacy characterization test와
ktlintCheck결과를 Progress에 기록한다. -
Task 5.5: 최대 고정 수·동시성·side effect 검증
Goal 실행 P5-T5: 최대 고정 게시글 3개 정책이 동시 요청과 실패에서도 깨지지 않도록 고정한다.
- 시작 조건:
P5-T4완료. - 완료 증거: 세 번째/네 번째 고정, 동시 요청, cross-owner와 DB/S3/event side effect RED/GREEN 기록.
- 범위 밖: 새로운 lock/dependency 도입, 기존 고정 정책 변경.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostRepository.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostFacade.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostConcurrencyTest.kt -
3개 허용·4번째 거부와 동시 고정 요청 실패 test를 작성한다.
-
기존 repository count/update 순서를 유지하는 최소 구현으로 test를 통과시킨다.
-
invalid target/ownership 실패 시 DB/S3/event 0건을 검증한다.
-
concurrency/focused test와
ktlintCheck결과를 Progress에 기록한다. -
Task 5.6: Phase 5 보안·오류·회귀 검증
Goal 실행 P5-T6: 모든 community endpoint의 ADMIN·오류·ownership 계약과 기존 public/legacy 회귀를 고정한다.
- 시작 조건:
P5-T2~P5-T5완료. - 완료 증거: endpoint 권한 매트릭스, 오류 status/key/KO·EN·JA, legacy/public 회귀와 Progress 기록.
- 범위 밖: Phase 6 FanTalk 기능.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/community/AiCharacterAdminCommunityPostContractTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt -
endpoint별 ADMIN 이중 인가와 stale claim을 검증한다.
-
target/post/media/fixed-count 오류의 정확한 status, message key, KO/EN/JA envelope를 확정·검증한다.
-
HUMAN/cross-character 게시글 mutation 거부와 legacy/public community 계약을 검증한다.
-
Phase 5 focused test와
ktlintCheck결과를 Progress에 기록한다.
Phase 5 Gate
Goal 실행 P5-GATE: Phase 5 community 사용자 흐름과 고정·side-effect·회귀 품질을 최종 판정한다.
-
P5-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다. -
시작 조건:
P5-T1~P5-T6완료. -
완료 증거:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.community.*'와./gradlew ktlintCheck성공, Progress 기록. -
범위 밖: 실패와 무관한 Phase 6 구현.
Phase 6: FanTalk 답변 vertical slice
목표
선택한 AI 캐릭터가 자신의 활성 root FanTalk에만 creator reply를 작성하는 v2 관리자 API를 제공한다.
범위와 비범위
- 포함: root FanTalk 존재/활성/owner 검증, creator reply 저장, 언어 감지와 기존 응답 의미 parity.
- 제외: FanTalk 원글 작성, nested reply, 구매/댓글형 기능, AI 캐릭터 일반 사용자 활동.
선행 Phase 및 의존성
- Phase 1 target resolver.
- 기존 FanTalk 저장 엔티티와 응답 DTO 의미 특성화.
API endpoint와 request/response contract
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 검증 후 기존 저장/언어 감지 로직을 필요한 만큼 재사용한다.
DB migration
- 없음.
transaction과 concurrency 고려사항
- root FanTalk 조회와 reply 저장은 같은 transaction에서 수행한다.
- 중복 답변 허용 여부는 기존 domain 정책을 따른다. 기존 정책이 없다면 이번 API는 별도 중복 차단을 추가하지 않는다.
보안 및 개인정보 위험
- 다른 character FanTalk, HUMAN creator FanTalk, inactive FanTalk, nested parent에는 답변하지 않는다.
- AI character Member 로그인/impersonation 없이 writer/creator만 해석된 creatorMember로 저장한다.
acceptance criteria
- target AI character는 자신의 활성 root FanTalk에만 답변할 수 있다.
- cross-character, nested parent, inactive/missing FanTalk는 4xx이며 reply 저장과 이벤트 발행이 없다.
- 저장된 답변의 writer/creator는 해석된 creatorMember와 일관된다.
targeted test
- Characterization:
LegacyFanTalkReplyCharacterizationTest. - V2 RED/GREEN:
AiCharacterAdminFanTalkReplyServiceTest. - Run:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'
전체 회귀 테스트 영향
- 기존 FanTalk 조회/작성 관련 테스트가 통과해야 한다.
rollback 전략
- 신규 FanTalk reply v2 admin route/facade를 제거한다.
- 신규 DDL이 없으므로 schema rollback은 없다.
권장 commit 경계
-
feat: add ai character admin fan talk reply slice -
Task 6.1: 기존 FanTalk reply 의미 특성화 baseline 고정
Goal 실행 P6-T1: 기존 FanTalk의 root 판별, 언어 감지, 응답 의미와 writer/creator 저장 결과를 비교 기준으로 고정한다.
- 시작 조건:
P5-GATE완료. - 완료 증거: production 변경 전 특성화 테스트 통과, 중복 답변 정책과 Progress 기록.
- 범위 밖: 신규 v2 reply production code 구현.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/LegacyFanTalkReplyCharacterizationTest.kt -
valid root reply의 언어 감지, response 의미와 writer/creator 저장 baseline을 작성한다.
-
root/nested/active 판별과 기존 중복 답변 정책을 관찰해 기록한다.
-
Phase 6 domain/client 오류별 정확한 status와 KO/EN/JA message key를 확정해 구현 Goal에 반영한다.
-
production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
-
fixture만 정리하고 test와
ktlintCheck결과를 Progress에 기록한다. -
Task 6.2: FanTalk root reply 저장 구현
Goal 실행 P6-T2: 선택한 AI 캐릭터의 활성 root FanTalk에 creator reply를 저장하고 전용 응답을 반환한다.
- 시작 조건:
P6-T1완료와 Phase 6 오류 계약의 계획 반영. - 완료 증거: 정상 저장·언어 감지·DTO·writer/creator RED/GREEN과 Progress 기록.
- 범위 밖: FanTalk 원글, nested reply, 일반 사용자 대리 작성.
Files:
-
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyController.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyDto.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyFacade.kt -
Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyCreateTest.kt -
미구현 정상 root reply, 언어 감지와 응답 DTO 실패 test를 작성한다.
-
root 조회와 reply 저장을 같은 transaction에서 수행하는 최소 구현을 통과시킨다.
-
writer/creator가 resolver의 creatorMember와 일치하고 principal impersonation이 없음을 검증한다.
-
focused/legacy characterization test와
ktlintCheck결과를 Progress에 기록한다. -
Task 6.3: FanTalk target·root·ownership 거부 구현
Goal 실행 P6-T3: cross-character, nested, inactive, missing FanTalk를 저장·이벤트 없이 거부한다.
- 시작 조건:
P6-T1,P6-T2완료. - 완료 증거: 네 거부 분기의 정확한 오류 계약, DB/event 0건과 Progress 기록.
- 범위 밖: 새로운 중복 답변 차단 정책.
Files:
-
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyFacade.kt -
Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyRepository.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyOwnershipTest.kt -
cross-character/nested/inactive/missing 각각의 실패 test와 의도한 실패를 확인한다.
-
target·root·active·owner를 저장 전에 검증하는 최소 구현을 통과시킨다.
-
각 실패의 정확한 status/message key/KO·EN·JA와 reply insert/event 0건을 검증한다.
-
focused test와
ktlintCheck결과를 Progress에 기록한다. -
Task 6.4: Phase 6 보안·오류·회귀 검증
Goal 실행 P6-T4: FanTalk reply endpoint의 ADMIN·오류 계약과 기존 FanTalk 회귀를 고정한다.
- 시작 조건:
P6-T2,P6-T3완료. - 완료 증거: 권한 매트릭스, request binding/domain 오류, legacy 회귀와 Progress 기록.
- 범위 밖: Phase 7 외 전체 기능 수정.
Files:
-
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkReplyContractTest.kt -
Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/AiCharacterAdminAuthorizationTest.kt -
endpoint의 ADMIN 이중 인가와 stale claim을 검증한다.
-
빈 content/binding/domain 오류의 정확한 status, message key, KO/EN/JA envelope를 확정·검증한다.
-
기존 FanTalk 조회·작성 계약과 AI 로그인/token/impersonation 부재를 확인한다.
-
Phase 6 focused test와
ktlintCheck결과를 Progress에 기록한다.
Phase 6 Gate
Goal 실행 P6-GATE: Phase 6 FanTalk reply의 root·ownership·저장·회귀 품질을 최종 판정한다.
-
P6-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다. -
시작 조건:
P6-T1~P6-T4완료. -
완료 증거:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'와./gradlew ktlintCheck성공, Progress 기록. -
범위 밖: 실패와 무관한 신규 기능.
Phase 7: Final Integration & Quality Gate
목표
신규 AI 캐릭터 관리자 API 전체가 source spec을 충족하고 기존 legacy/public 계약을 회귀시키지 않았음을 검증한다.
범위와 비범위
- 포함: 전체 targeted test, 기존 회귀 테스트, ktlint, dependency/DDL/API contract 점검, 문서 검증 기록 누적.
- 제외: 신규 기능 추가, unrelated refactor.
선행 Phase 및 의존성
- Phase 1~6 완료.
API endpoint와 request/response contract
- Endpoint Contract Summary의 모든 endpoint가 구현되어야 한다.
- 모든 신규 endpoint가 JWT ADMIN + 현재 DB ADMIN 이중 인가와 공통 오류 envelope/i18n을 공유해야 한다.
- legacy/public endpoint URI와 성공·오류 status/body/message diff가 없어야 한다.
entity, repository, service 변경
- 신규 변경 없음. Phase 1~6 변경의 누락 import, unused code, package 의존 방향만 정리한다.
- 신규 v2 application/domain이 기존 controller 또는 v2 API response DTO를 역참조하지 않는지 점검한다.
DB migration
- 없음. 새 DDL/migration 파일이 없는지 확인한다.
transaction과 concurrency 고려사항
- 각 write slice의 transaction 시작부 target/ownership 검증이 유지되는지 점검한다.
- 동시성 관련 targeted test가 실패 없이 통과해야 한다.
보안 및 개인정보 위험
- JWT 또는 현재 DB role이 비ADMIN인 접근, stale ADMIN claim, AI login/token/impersonation, private path 노출, cross-owner write가 없는지 전체 점검한다.
acceptance criteria
- Phase별 targeted test가 모두 통과한다.
- 모든 신규 endpoint에서 stale ADMIN claim은 403이고, 등록된 모든 API 오류 분기는 정확한 비2xx status +
ApiResponse.error+ KO/EN/JA message를 반환한다. Spring CORS 정책 거부 403 body는 envelope 계약의 예외다. - legacy/public 401/403/domain 오류의 status/body/message가 특성화 baseline과 동일하다.
- 전체 회귀 필요성 판정 결과 실행 대상이면
./gradlew test가 통과하고, 생략 대상이면 근거와 대체 targeted/영향 범위 회귀 결과가 기록된다. - Kotlin 파일 변경이 있으면
./gradlew ktlintCheck가 통과한다. - 신규 dependency와 신규 DDL이 없다.
- source spec acceptance criteria 25개를 각 Phase 결과와 대조해 누락이 없다.
targeted test
-
Run:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' ./gradlew ktlintCheck -
Conditional full regression: 공통 경계·여러 Phase 영향 또는 targeted 결과로 영향 범위를 판단할 수 없을 때만
./gradlew test를 추가 실행한다.
전체 회귀 테스트 영향
- legacy
/admin/*,/creator-admin/*, public/api/v2/*관련 테스트 전체가 회귀 범위다. - 기존 성공·오류 request/response 계약 변경이 없음을 controller/DTO diff와 legacy 오류 특성화 테스트로 확인한다.
rollback 전략
- 신규
/api/v2/admin/ai-characterscontroller bean 비활성화 또는 신규 package 제거로 기능 표면을 되돌린다. - DB schema 변경이 없으므로 rollback은 code revert 중심이다.
권장 commit 경계
-
test: verify ai character admin api integration -
Task 7.1: 전체 targeted 및 필요 시 전체 회귀 test 실행
- Goal 실행
P7-T1: Phase 1~6 targeted test를 실행하고 위험 근거에 따라 전체 회귀 필요성을 판정해 결과를 확정한다. - 시작 조건:
P2-GATE~P6-GATE완료와 Phase 1 완료 증거 확인. - 완료 증거: targeted 결과와 전체 회귀 실행 또는 생략 판정·근거를 Progress와 하단 검증 기록에 누적.
- 범위 밖: 실패와 무관한 기능 추가·리팩터링.
- TDD 예외 사유: 구현 완료 후 검증 전용 Task라 신규 실패 테스트를 작성하지 않는다.
- 대체 검증 방법: Phase 1~6 targeted test,
AiCharacterAdminAuthorizationTest,AiCharacterAdminErrorContractTest, 기존 admin security test를 먼저 실행한다. 공통 경계·여러 Phase 영향 또는 targeted 실패로 영향 범위를 판단할 수 없을 때만 전체 회귀를 실행한다. - REFACTOR: 실패가 있으면 관련 Phase Task로 되돌려 최소 수정 후 다시 실행한다.
- Verify: 위 targeted command를 실행하고 전체 회귀 필요성을 판정한다. 실행 시
./gradlew test결과를, 생략 시 근거와 대체 회귀 범위를 이 문서 하단 검증 기록에 남긴다. - Phase 1~6 targeted test를 실행하고 각 결과를 기록한다.
- 공통 경계·여러 Phase 변경과 targeted 결과를 근거로 전체 회귀 필요성을 판정한다.
- 필요하면
./gradlew test의 exit code·실패 수를 기록하고, 불필요하면 생략 근거와 대체 회귀 범위를 기록한다. - 실패가 있으면 소유 Phase에 별도 회귀 수정 Goal을 추가하고
P7-T1을 완료 처리하지 않는다.
- Goal 실행
-
Task 7.2: API contract와 변경 범위 점검
- Goal 실행
P7-T2: API·architecture·dependency·DDL·diff와 문서 추적성을 read-only로 최종 점검한다. - 시작 조건:
P7-T1완료. - 완료 증거: 아래 점검 체크박스,
ktlintCheck, source spec acceptance criteria 추적 결과와 Progress 기록. - 범위 밖: 신규 기능 추가, 확정되지 않은 계약 보정.
- TDD 예외 사유: diff/architecture 검증 전용 Task라 신규 실패 테스트를 작성하지 않는다.
- 대체 검증 방법: legacy/public 성공·오류 status/body/message 특성화 baseline 통과, controller/DTO schema 변경 없음, 신규 dependency 없음, 신규 DDL 없음, 신규 v2 application/domain에서 기존 controller와 v2 response DTO 역참조 없음.
- REFACTOR: 불필요한 import, 역방향 의존, 관련 없는 변경을 제거하고 diff를 다시 확인한다.
- Verify:
git diff --name-only,./gradlew ktlintCheck git diff --name-only와git diff --check로 변경 범위와 문서/코드 오류를 확인한다.build.gradle.kts와 migration/DDL 경로를 확인해 신규 dependency·DDL 0건을 기록한다.- legacy/public controller·DTO 외부 계약 diff와 신규 application/domain의 역방향 import 0건을 확인한다.
- source spec acceptance criteria 25개를 Phase Goal/Gate 완료 증거에 대조한다.
./gradlew ktlintCheck와./gradlew tasks --all결과를 기록한다.
- Goal 실행
Phase 7 Gate
Goal 실행 P7-GATE: 모든 Phase의 완료 증거와 최신 전체 검증을 대조해 AI 캐릭터 관리자 API의 최종 완료 여부를 판정한다.
-
P7-GATE완료: 시작 조건과 완료 증거를 모두 충족하고 문서 상태를구현 완료로 갱신한다. -
시작 조건:
P7-T1,P7-T2완료. -
완료 증거: 미완료 Goal·미처리 review finding·보류 없는 차단 사항 0건, 아래 완료 조건과 최종 Progress 기록.
-
범위 밖: Gate에서 직접 production code 수정, test 삭제·skip·완화.
-
Phase 1~6의 Task/Gate 완료 증거와 하단 검증 기록이 일치한다.
-
모든 확정 review finding이 수정 완료 또는 근거 있는 제외로 종결됐다.
-
최신 targeted·ktlint·문서 명령이 성공했고, 전체 회귀는 필요성 판정에 따라 성공 결과 또는 생략 근거가 기록됐다.
-
남은 항목과 최종 상태를 Progress 및 최종 보고 형식으로 기록한다.
실행 순서와 의존성
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|---|---|---|---|---|
| 1 | P2-R1 |
P2-H1, P2-H2 |
아니요 | 리뷰 후보를 판정하고 계약 미확정이면 Decision Log에 기록 |
| 2 | P2-T3 → P2-T4 → P2-T5 → P2-T6 |
P2-R1 |
아니요 | 실패 소유 Goal에서 수정·검증을 끝낸 뒤 다음 Goal 수행 |
| 3 | P2-GATE |
Phase 2 활성 Goal 전체 | 아니요 | 실패 소유 Task의 회귀 수정 Goal 추가 |
| 4 | P3-R1 |
P2-GATE, P3-H1, P3-H2 |
아니요 | 리뷰 후보를 판정하고 계약 미확정이면 Decision Log에 기록 |
| 5 | P3-T3 → P3-T4 → P3-T5 → P3-T6 → P3-T7 |
P3-R1 |
아니요 | 실패 소유 Goal에서 수정·검증을 끝낸 뒤 다음 Goal 수행 |
| 6 | P3-GATE |
Phase 3 활성 Goal 전체 | 아니요 | 실패 소유 Task의 회귀 수정 Goal 추가 |
| 7 | P2-R2 → P2-R2-GATE |
2차 Phase 2 review | 아니요 | REV-009 소유 Task에서 증거 보강 |
| 8 | P3-R2 → P3-R3 → P3-R2-GATE |
P2-R2-GATE, 2차 Phase 3 review |
아니요 | REV-010~REV-011 소유 Task에서 증거 보강 |
| 9 | P2-R3 → P2-R3-GATE |
4차 Phase 2 review | 아니요 | REV-012 소유 Task에서 증거 보강 |
| 10 | P3-R4 → P3-R3-GATE |
P2-R3-GATE, 4차 Phase 3 review |
아니요 | REV-013~REV-014 소유 Task에서 증거 보강 |
| 11 | P2-R4 → P2-R4-GATE |
5차 Phase 2 review | 아니요 | REV-015 actual transaction 증거 보강 |
| 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 추가 |
변경 금지·중단 규칙
- PRD와 Endpoint Contract Summary를 근거 없이 변경하거나 제공되지 않은 DTO·enum·오류 status/key를 추정하지 않는다.
- 기존 완료 체크박스, 검증 기록과 Progress를 삭제·되돌리거나 덮어쓰지 않는다.
- review 후보는 재현·판정 전 production 수정으로 전환하지 않고, 확정 항목만 관련 Goal 또는 신규 회귀 수정 Goal로 처리한다.
- test를 삭제·skip·완화하거나 관련 없는 refactor·dependency·DDL을 추가해 Gate를 통과시키지 않는다.
- JWT, token, signed URL 전체, private path와 파일 본문을 문서·fixture·log에 기록하지 않는다.
- 일부 체크박스, 일부 test 또는 코드 작성만 끝난 상태에서는 Goal을
complete로 갱신하지 않는다.
Goal Progress
기존 기록을 삭제하거나 덮어쓰지 않고 Goal 실행 결과를 차수별로 누적한다.
P2-R1 실행 준비 — 2026-07-27
- 상태: 대기
- 무엇을: Phase 2~7의 Goal ID, 시작 조건, 완료 증거, 범위 밖, 체크박스와 Gate 구조를 준비했다.
- 왜: 기존 Phase 2·3 체크만으로 심층 리뷰와 최종 완료를 판정할 수 없었기 때문이다.
- 어떻게: PRD, 기존 계획과
docs/sample/sample-plan-task.md,docs/sample/sample-review.md를 대조했다. - 남은 항목:
P2-R1read-only 리뷰 실행. - 다음 행동: 사용자가 goal 실행을 요청하면
P2-R1만create_goal에 등록한다.
P2-R1 완료 — 2026-07-27
- 상태: 완료
- 무엇을: Phase 2 character slice의 PRD Feature B, Endpoint Contract Summary, production/test 구현을 대조했다.
- 왜: 기존
P2-H1,P2-H2완료 이력 이후P2-GATE전에 확정 finding을 소유 Goal에 연결해야 하기 때문이다. - 어떻게:
docs/20260724_AI캐릭터_관리자_API/reviews/phase2-character-review.md를 작성하고./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'를 실행했다. - 결과: focused test는
BUILD SUCCESSFUL in 51s였고,REV-001REV-003,REV-007,REV-008을 확정으로 유지해P2-T3P2-T6에 연결했다. - 남은 항목:
P2-T3캐릭터 목록·검색·상세 보완부터 직렬 실행.
P2-T3 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 캐릭터 목록 응답을 상세 DTO에서 목록 전용 DTO로 분리했다.
- 왜: 목록 API가 계약에 없는 상세 전용
creatorProfileImageUrl,creatorIntroduce,updatedAtUtc를 노출했기 때문이다. - 어떻게: RED로
AiCharacterAdminCharacterControllerTestexact field 비노출 assertion을 추가했고,AiCharacterAdminCharacterListItemResponse와toListItemResponse를 최소 구현했다. - 결과: RED는
AiCharacterAdminCharacterControllerTestline 66 실패로 확인했고, GREEN 후./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerTest와./gradlew ktlintCheck가 모두BUILD SUCCESSFUL이었다. - 남은 항목:
P2-T4캐릭터 생성 흐름 보완.
P2-T4 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 생성 request의 서버 소유 field를 거부하고, 원작 존재를 외부 캐릭터 생성 전에 확인했다. 정상 생성의 원작 연결·AI creatorMember 표시 정보·언어 감지 event 및 중복·외부 API·S3 실패 결과를 v2/legacy test로 고정했다.
- 왜:
REV-002,REV-003,REV-007에서 request 입력 의미와 외부 부작용 전 DB 검증 증거가 부족했기 때문이다. - 어떻게:
DEC-P2-T4-001로 canonical contract와 legacy failure boundary를 고정한 뒤 RED/GREEN test를 추가하고, focused/legacy 및 formatting 검증을 실행했다. - 결과: specified focused/legacy test와
ktlintCheck가 모두BUILD SUCCESSFUL이었다. 외부 API 삭제 endpoint나 신규 DB unique 제약은 기존 external contract·DDL 금지 범위 밖이라 추가하지 않았다. - 남은 항목:
P2-T5캐릭터 수정·비활성화 흐름 보완.
P2-T5 완료 — 2026-07-27
- 상태: 완료
- 무엇을: PUT의 response 전용
externalCharacterId와isActive=false혼합 요청을 거부하고, 원작을 외부 수정 전에 검증했다. 일반 수정의 image 유지·교체, AI creatorMember 표시 정보, 번역 event, soft delete의 Member·콘텐츠 보존과 flush 후updatedAtUtc를 회귀로 고정했다. - 왜:
REV-002,REV-003,REV-007에서 update 입력 의미, S3/외부/DB 실패 경계와 response timestamp 증거가 부족했기 때문이다. - 어떻게:
DEC-P2-T5-001로 PUT contract와 non-compensated external update 경계를 확정한 뒤 RED/GREEN test를 추가하고 지정 focused/legacy 및 formatting 검증을 실행했다. - 결과: 지정 focused/legacy test와
ktlintCheck가 모두BUILD SUCCESSFUL이었다. 외부 update restore API와 기존 image hard delete는 external contract·legacy parity 범위 밖이라 추가하지 않았다. - 남은 항목:
P2-T6Phase 2 보안·오류·회귀 보완.
P2-T6 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 문자열
unmapped-path를 character resource handler에서 제외해 prefix fallback의 404/i18n/CORS 계약으로 보냈고, 실제 목록·상세·생성·수정 endpoint의 ADMIN·binding/domain/multipart·CORS 증거를 보강했다. - 왜: 문자열 path가
Longbinding의 400으로 처리되어REV-001을 위반했고,REV-008의 실제 endpoint matrix 증거가 부족했기 때문이다. - 어떻게: 기존 404 KO/EN/JA·CORS 4건을 RED로 재현하고 GET/PUT path를
[0-9]+로 제한했다. 목록 binding, 상세 target, 생성/수정 multipart KO/EN/JA, 실제 네 endpoint non-ADMIN과 detail preflight를 parameterized/focused test로 확인했다. - 결과: 지정 focused test는
BUILD SUCCESSFUL in 1m 26s,./gradlew ktlintCheck는BUILD SUCCESSFUL in 34s였다.AiCharacterAdminCharacterServiceTest는 존재하지 않는 과거 계획 참조임을 정정 기록으로 보존했고, legacy/public contract와 DTO 의존 방향은 변경하지 않았다. - 남은 항목:
P2-GATE.
P2-GATE 완료 — 2026-07-27
- 상태: 완료
- 무엇을: Phase 2 캐릭터 관리의 PRD 추적성, 정상·실패 흐름, legacy 회귀를 최종 판정했다.
- 왜: Phase 3 실행 전
P2-R1,P2-T3~P2-T6의 완료 증거와 Gate 명령 성공이 필요하기 때문이다. - 어떻게: Gate에 명시된 세 명령과
git diff --check를 실행했다. - 결과: character focused 명령은 병렬 실행 중 XML 결과 파일 write 충돌로 한 번 실패했으나 동일 명령 단독 재실행은
BUILD SUCCESSFUL in 1m 11s였다. authorization/error 명령은BUILD SUCCESSFUL in 1m 30s,ktlintCheck는BUILD SUCCESSFUL이었다.REV-001~REV-003,REV-007,REV-008의 Phase 2 소유 항목은 처리 완료로 판정했다. - 남은 항목:
P3-R1Phase 3 요구사항·계약·코드 리뷰.
P3-T7 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 실제 Phase 3 테마·목록·상세·생성·수정 endpoint의 ADMIN/stale claim, CORS, ownership no-side-effect와 malformed resource path 오류 계약을 독립 focused test로 고정했다.
- 왜: 기존 공통 authorization/error test는 prefix 공통 계약을, 콘텐츠 focused test는 domain ownership을 보장했지만 실제 endpoint가 문자열 식별자를 404 fallback으로 보내는 증거가 없었다.
- 어떻게:
AiCharacterAdminAudioContentOwnershipTest에서 malformedcharacterId/contentIdRED를 먼저 확인하고, controller resource path를[0-9]+로 제한했다. 기존 콘텐츠/authorization/error focused regression과 legacy characterization을 함께 실행했다. - 결과: RED는 6건의 400/415 대 404 불일치로 확인했고, 최소 path 제약 적용 후 ownership focused 18건과 지정 content+authorization+error 회귀가 모두
BUILD SUCCESSFUL이었다.ktlintCheck도 통과했다. - 남은 항목:
P3-GATE.
P3-GATE 완료 — 2026-07-27
- 상태: 완료
- 무엇을: Phase 3 콘텐츠 관리와 signed URL의 PRD 추적성, pipeline 안전성, legacy 회귀를 최종 판정했다.
- 왜: Phase 4 진행 전
P3-R1,P3-T3~P3-T7의 완료 증거와 Gate 명령 성공이 필요하기 때문이다. - 어떻게: Gate에 명시된 content focused, authorization/error,
ktlintCheck세 명령을 fresh 실행했다. - 결과: content focused 명령은
BUILD SUCCESSFUL in 2m 15s, authorization/error 명령은BUILD SUCCESSFUL in 1m 29s,ktlintCheck는BUILD SUCCESSFUL in 1s였다.REV-004~REV-008의 Phase 3 소유 항목은 처리 완료로 판정했다. - 남은 항목:
P4-T1시리즈 요구사항·계약·코드 리뷰.
Phase 2·3 2차 리뷰 완료 — 2026-07-27
- 상태: 리뷰 완료, 후속 수정 Goal 대기
- 무엇을: Phase 2·3 production/test와 기존 review·Gate 완료 기록을 PRD와 다시 대조했다.
- 왜: 기존 focused test 성공뿐 아니라 각 완료 체크박스가 요구한 실제 endpoint·failure-order 증거가 존재하는지 독립 검증하기 위해.
- 어떻게: 두 review 문서에 2차 리뷰를 누적하고 character/content/common test를
--rerun-tasks로 한 번에 실행한 뒤 XML별 test 수와 lint를 확인했다. - 결과: targeted 14개 XML class의 199건은 failure/error/skipped 0으로
BUILD SUCCESSFUL in 9m 23s,ktlintCheck --rerun-tasks는 7개 task가 실행되어BUILD SUCCESSFUL in 27s였다. 기능 실패는 재현되지 않았으나 완료 기록보다 직접 증거가 좁은REV-009~REV-011을 확정했다. - 남은 항목:
P2-R2→P2-R2-GATE→P3-R2→P3-R3→P3-R2-GATE.
P3-R2 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 실제 생성 endpoint의 multipart part별 KO/EN/JA 오류, invalid theme 선검증, cover upload 실패 rollback/event 0회 증거를 보강했다.
- 왜:
REV-010에서 기존 생성 완료 증거가 계획의 직접 증거보다 좁다고 확정됐기 때문이다. - 어떻게:
AiCharacterAdminAudioContentCreateTest를 확장했고, 실패 원인은 production 계약 위반이 아니라 테스트 기대 message와NOT_SUPPORTEDfixture의 transaction 누락임을 확인해 테스트만 최소 정정했다. - 결과: create 단독 명령은
BUILD SUCCESSFUL in 1m 4s, content/common 회귀 명령은BUILD SUCCESSFUL in 2m 41s였다. - 남은 항목:
P3-R3.
P3-R3 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 수정 차집합, cover 유지/교체/실패, ownership/domain no-side-effect 증거를 Phase 3 actual endpoint 범위에서 보강했다.
- 왜:
REV-011에서 기존 수정·ownership 완료 증거가 계획의 직접 증거보다 좁다고 확정됐기 때문이다. - 어떻게: 기존 Phase 3 content test 보강분을 content/common 회귀로 재검증했다.
- 결과: content/common 회귀 명령은
BUILD SUCCESSFUL in 2m 41s였다. - 남은 항목:
P3-R2-GATE.
P3-R2-GATE 완료 — 2026-07-27
- 상태: 완료
- 무엇을: Phase 3 후속 리뷰의
REV-010~REV-011처리를 종결했다. - 왜: 사용자 지시에 따라 Phase 3 후속 보완까지만 진행하고 Phase 4 이후는 시작하지 않기 위해서다.
- 어떻게:
phase3-audio-content-review.md에 3차 후속 검증 기록을 누적하고 content/common 회귀와ktlintCheck를 실행했다. - 결과: content/common 회귀 명령은
BUILD SUCCESSFUL in 2m 41s였다.ktlintCheck결과는 검증 기록에 누적한다. - 남은 항목: 없음. Phase 4는 진행하지 않는다.
Phase 2·3 4차 재리뷰 완료 — 2026-07-27
- 상태: 리뷰 완료, 후속 보완 대기
- 무엇을:
P2-R2,P3-R2~P3-R3반영분과 완료 체크리스트를 production 흐름·test method 단위로 다시 대조했다. - 왜: 회귀 통과만으로
REV-009~REV-011의 선언된 실패·ownership 증거 전체가 충족됐다고 판정할 수 없기 때문이다. - 어떻게: 변경된 test 5개, character/content facade와 legacy service failure order를 확인하고 targeted 전체를
--rerun-tasks로 실행했다. - 결과: 관련 14개 XML class의 228건은 failure/error/skipped 0으로
BUILD SUCCESSFUL in 9m 44s,ktlintCheck --rerun-tasks는BUILD SUCCESSFUL in 41s였다. 적용된 테스트는 유효하지만 DB/event 실패, 생성 후반 S3/event 실패와 ownership no-side-effect matrix가 완료 기록보다 좁아REV-012~REV-014를 확정했다. - 남은 항목:
P2-R3→P2-R3-GATE→P3-R4→P3-R3-GATE.
P2-R3 / P2-R3-GATE 완료 — 2026-07-27
- 상태: 완료
- 무엇을: character 생성·수정의 external/S3 실패 locale matrix와 event publish 실패 경계를 보강했다.
- 왜:
REV-012에서 DB/event 실패 뒤 내부 rollback과 external/S3 잔존 결과 직접 증거가 부족하다고 확정됐기 때문이다. - 어떻게:
AiCharacterAdminCharacterControllerMutationTest에 KO/EN/JA 대표 실패와 facade 직접 event failure characterization을 추가했다. - 결과: mutation focused 명령은
BUILD SUCCESSFUL in 1m 1s였다. - 남은 항목:
P3-R4.
P3-R4 / P3-R3-GATE 완료 — 2026-07-27
- 상태: 완료
- 무엇을: content 생성의 audio upload/event 실패와 실제 네 경로 unknown target no-side-effect matrix를 보강했다.
- 왜:
REV-013~REV-014에서 생성 후반 실패 순서와 ownership/domain 부작용 없음 증거가 부족하다고 확정됐기 때문이다. - 어떻게:
AiCharacterAdminAudioContentCreateTest,AiCharacterAdminAudioContentOwnershipTest를 확장하고 content/common 회귀와ktlintCheck를 실행했다. - 결과: create+ownership focused 명령은
BUILD SUCCESSFUL in 1m 8s, content/common 회귀 명령은BUILD SUCCESSFUL in 2m 20s,ktlintCheck는 import 정리 후BUILD SUCCESSFUL in 17s였다. - 남은 항목: 없음. 사용자 지시에 따라 Phase 4는 진행하지 않는다.
Phase 2·3 5차 재리뷰 완료 — 2026-07-27
- 상태: 리뷰 완료, 후속 수정 Goal 대기
- 무엇을:
P2-R3,P3-R4반영분의 transaction/rollback, multipart binding, ownership/domain 완료 증거를 PRD·production·test method 단위로 다시 대조했다. - 왜: 통과하는 테스트가 기존 finding의 exact exception, actual transaction과 전체 side-effect assertion을 실제로 보장하는지 확인하기 위해서다.
- 어떻게:
phase2-character-review.md,phase3-audio-content-review.md에 5차 리뷰를 누적하고 변경된 핵심 test 세 클래스를--rerun-tasks로 실행한 뒤 XML 수치, lint와 diff check를 확인했다. - 결과: 세 XML 합계 79건은 failure/error/skipped 0으로
BUILD SUCCESSFUL in 7m 50s,ktlintCheck --rerun-tasks는BUILD SUCCESSFUL in 28s,git diff --check는 출력이 없었다. 테스트는 통과했지만REV-015~REV-017의 계약·완료 증거 누락을 확정했다. - 남은 항목:
P2-R4→P2-R4-GATE→P3-R5→P3-R6→P3-R4-GATE.
P3-R5 완료 — 2026-07-27
- 상태: 완료
- 무엇을: 생성 controller와 facade의
coverImage·audioFile을 non-nullMultipartFile로 고정하고, 세 필수 part 누락 KO/EN/JA actual endpoint 9건의 exact MVC binding 계약을 보강했다. - 왜: nullable 파일 part가 facade와 legacy
AudioContentService까지 전달되어MissingServletRequestPartException및common.error.invalid_request계약을 우회했기 때문이다. - 어떻게: RED에서
coverImage·audioFile누락 6건이 legacy content 전용 message로 실패함을 확인한 뒤, non-null binding으로 변경했다. 공통 helper는 각 요청의 400ApiResponse.error, exact exception, DB count 0, S3putObject0회, event no-interaction을 단언한다. - 결과: create/error focused 회귀는
BUILD SUCCESSFUL in 1m 1s, content/common 회귀는BUILD SUCCESSFUL in 2m 46s,ktlintCheck는BUILD SUCCESSFUL이었다. 전체./gradlew test는 controller binding의 직접 영향 범위를 두 targeted 명령이 포함하고 release/Gate 범위가 아니므로 실행하지 않았다. - 남은 항목:
P3-R6및P3-R4-GATE.
P3-R6 완료 — 2026-07-27
- 상태: 완료.
P3-R4-GATE는P2-R4-GATE뒤 실행 대기다. - 무엇을:
AiCharacterAdminAudioContentControllerTest의 cross-owner detail/update, create/update 다른 ownerseriesIds, invalidreleaseDateUtc실제 endpoint를 각각 KO/EN/JA matrix로 확장했다. - 왜: 기존 단일 locale 또는 부분 assertion으로는 ownership/domain 검증이 S3 업로드, DB 변경, event publish보다 앞선다는 완료 증거가 부족했다.
- 어떻게: 테스트 우선으로 exact 400
ApiResponse.errorenvelope, 요청 전후AudioContent·SeriesContentcount와 field/연결 row, S3putObject0회,ApplicationEventPublisherno-interaction을 단언했다. target과 other-owner ID를 분리한 fixture라 owner/domain guard를 제거하면 success/status 또는 state assertion이 실패한다. - 결과: focused characterization은
BUILD SUCCESSFUL in 34s로 기존 production 계약 충족을 확인해 test-only로 종료했다. content/common 회귀는BUILD SUCCESSFUL in 1m 13s,ktlintCheck는BUILD SUCCESSFUL in 12s였다. 전체./gradlew test는 변경이 Phase 3 content endpoint 테스트에 한정되고 focused·content/common 명령이 직접 범위를 포함하므로 실행하지 않았다. - 남은 항목:
P2-R4-GATE후P3-R4-GATE. Phase 4는 진행하지 않는다.
P2-R4-GATE / P3-R4-GATE 완료 — 2026-07-28
- 상태: 완료
- 무엇을: Phase 2·3 5차 리뷰 보완의 최종 Gate를 종결했다.
- 왜:
REV-015actual transaction evidence,REV-016exact multipart binding,REV-017ownership/domain matrix가 모두 보강됐는지 fresh 검증으로 판정하기 위해서다. - 어떻게: character mutation/error와 content create/controller/ownership focused 명령 및
git diff --check를 실행하고 review 문서의 상태를 처리 완료로 갱신했다. - 결과: focused 명령은
BUILD SUCCESSFUL in 52s,git diff --check는 출력이 없었다.P2-R4-GATE,P3-R4-GATE모두 완료했고 Phase 4는 사용자 진행 지시 전까지 시작하지 않는다. - 남은 항목: 없음. 다음 Goal은
P4-T1이지만 사용자 진행 지시가 필요하다.
Phase 2·3 6차 리뷰 완료 — 2026-07-28
- 상태: 완료
- 무엇을: 5차 보완 결과와 Endpoint Contract Summary, multipart empty-file 경계, Phase 3 event no-interaction의 실제 관찰 대상을 다시 대조했다.
- 왜: 통과하는 focused test와 완료된 review ID 외에 client contract 위반, 제한 조건의 파일 손상 가능성 또는 detached mock으로 가려진 증거 공백이 남았는지 확인하기 위해서다.
- 어떻게: character/content production과 관련 test를 정적 대조하고 Phase 2·3 5차 Gate focused 5개 class를
--rerun-tasks로 재실행했다. 두 review 문서에REV-018~REV-020을 누적하고 각 finding을 독립 Task/Gate에 연결했다. - 결과: focused XML 합계 216건은 failure/error/skipped 0으로
BUILD SUCCESSFUL in 4m 16s,ktlintCheck --rerun-tasks는BUILD SUCCESSFUL in 17s, staged/unstagedgit diff --check는 출력이 없었다. 테스트 통과와 별개로 문서 계약 1건, production empty-file 경계 1건, test evidence 1건을 확정했다. - 보완 결과:
P2-R5,P2-R5-GATE,P3-R7,P3-R8,P3-R5-GATE를 완료했다. - 최종 검증: content/common 회귀는
BUILD SUCCESSFUL in 1m 30s,ktlintCheck는BUILD SUCCESSFUL in 17s,git diff --check는 출력이 없었다. - 남은 항목: 없음. Phase 4는 사용자 진행 지시 전까지 시작하지 않는다.
Phase 2·3 6차 보완 재점검 완료 — 2026-07-28
- 상태: 완료
- 무엇을:
P2-R5,P2-R5-GATE,P3-R7,P3-R8,P3-R5-GATE의 실제 코드·테스트·완료 기록을 다시 대조하고 하단 종합 finding 상태를 점검했다. - 왜: 6차 보완의 empty-file·actual publisher·문서 계약 수정이 실제로 유지되는지와 완료된 finding이 미처리 상태로 남아 있지 않은지 확인하기 위해서다.
- 어떻게: Phase 2 character mutation과 Phase 3 create/update/controller/ownership 5개 class를
--rerun-tasks로 실행하고,REV-001~REV-020의 소유 Goal·Gate·Progress 기록을 하단 종합 표와 대조했다. - 결과: 5개 XML 합계 130건은 failure/error/skipped 0으로
BUILD SUCCESSFUL in 4m 19s였다. 6차 보완 범위의 추가 production 결함은 재현되지 않았다. 별도 문서 문제로 종결된REV-001~REV-009가 종합 표에서확정으로 남은 상태 불일치를 확인해처리 완료로 동기화했다. - 남은 항목: 없음. 다음 Goal은
P4-T1이지만 사용자 진행 지시 전까지 시작하지 않는다.
Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|---|---|---|---|---|---|
| 2026-07-27 | DEC-GOAL-001 |
확정 | 기존 Phase 2·3 완료 Task는 이력으로 보존하고 심층 리뷰·세부 보완·Gate Goal을 추가한다. | 기존 검증 이후 후속 보완이 반복됐고 Phase 완료 Gate가 없었다. | P2-R1~P3-GATE |
| 2026-07-27 | DEC-REVIEW-001 |
확정 | 코드 리뷰의 8개 확정 finding은 기존 미실행 범주형 Goal에 REV-001~REV-008로 귀속하고 Phase 안에서 직렬 실행한다. |
새 Goal을 중복 추가하거나 기존 완료 이력을 다시 열지 않으면서 각 finding의 재현·완료 증거를 독립 추적하기 위해. | P2-R1P2-GATE, P3-R1P3-GATE |
| 2026-07-27 | DEC-P2-T4-001 |
확정 | POST 생성은 외부 API 필수 입력인 systemPrompt를 받고, externalCharacterId는 외부 API가 반환하는 response 전용 값이며, isActive는 서버가 true로 생성하는 response 상태다. characterType은 생략 시 Character, 잘못된 값은 외부 부작용 전 400이다. 중복 이름은 legacy와 같은 findByName 선검증만 적용하며 신규 DDL 없이 동시 요청의 DB unique 보장은 추가하지 않는다. 원작·중복·타입 검증은 외부 생성 전에 수행한다. 외부 API 실패는 DB/S3/event를 남기지 않고, S3 실패는 DB transaction과 event를 롤백하지만 legacy에 삭제 API가 없으므로 이미 생성된 외부 캐릭터는 보상하지 않는다. |
Endpoint Contract Summary의 축약 JSON이 외부 API 반환값을 입력처럼 표기하지만, legacy 등록과 v2 external client 모두 systemPrompt로 외부 생성을 요청하고 ID를 응답에서 받는다. PRD의 external API 계약·DDL 변경 금지와 legacy failure order를 유지한다. |
P2-T4, REV-002, REV-003, REV-007 |
| 2026-07-27 | DEC-P2-T5-001 |
확정 | PUT의 externalCharacterId는 response 전용으로 명시 거부한다. isActive=false는 image와 일반 수정 field를 섞지 않는 단독 soft delete다. 일반 수정은 image를 생략하면 기존 경로를 유지하고, 존재하지 않는 originalWorkId는 외부 수정 전에 거부한다. 외부 수정 실패는 S3/DB/event를 남기지 않으며, S3 실패는 DB/event를 롤백하지만 legacy와 같은 external update restore 계약이 없어 성공한 외부 수정은 보상하지 않는다. 응답 updatedAtUtc는 DB flush 후 매핑한다. |
기존 service의 soft delete는 다른 field를 무시해 이미지 업로드 고아를 남겼고, @PreUpdate timestamp는 flush 전에는 이전 값을 반환했다. 외부 API delete/restore 추가와 DDL은 범위 밖이다. |
P2-T5, REV-002, REV-003, REV-007 |
| 2026-07-27 | DEC-REVIEW-002 |
확정 | 기존 Phase 2·3 Task/Gate 완료 이력은 보존하고, 2차 리뷰에서 확인한 검증 증거 누락은 REV-009~REV-011과 새 후속 Task/Gate로 처리한다. |
fresh 199건은 모두 통과했지만 기존 완료 체크리스트의 실제 endpoint·failure-order 범위와 test method가 일치하지 않았다. | P2-R2~P3-R2-GATE, 두 2차 review |
| 2026-07-27 | DEC-REVIEW-003 |
확정 | 3차 수정에서 유효하게 보강된 범위와 기존 완료 이력은 보존하고, 아직 직접 고정되지 않은 실패·ownership 경계만 REV-012~REV-014와 새 Task/Gate로 추적한다. |
fresh 228건은 모두 통과했지만 Task 2.8~Task 3.10의 완료 체크리스트와 실제 failure injection·side-effect assertion 범위가 다시 일치하지 않았다. |
P2-R3~P3-R3-GATE, 두 4차 review |
| 2026-07-27 | DEC-REVIEW-004 |
확정 | 4차 보완의 유효한 테스트와 완료 이력은 보존하고, exact multipart 계약과 actual transaction·ownership/domain 직접 증거 누락은 REV-015~REV-017 및 새 Task/Gate로 추적한다. |
fresh 79건은 통과했지만 nullable file binding, direct facade event test와 unknown target에 한정된 matrix가 기존 완료 조건보다 좁았다. | P2-R4~P3-R4-GATE, 두 5차 review |
| 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, 하단 발견된 문제 표 |
발견된 문제
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|---|---|---|---|---|---|
REV-001 |
Blocker | 처리 완료 | 문자열 미매핑 경로가 /{characterId}에 매핑되어 Phase 1의 404 계약 대신 type mismatch 400을 반환한다. |
P2-R1, P2-T6 |
numeric path mapping과 KO/EN/JA·CORS 회귀를 고정했다. |
REV-002 |
High | 처리 완료 | 캐릭터 생성·수정 DTO의 systemPrompt, externalCharacterId, 생성 isActive와 soft-delete 혼합 의미가 Endpoint Contract Summary와 다르다. |
P2-R1, P2-T4, P2-T5 |
canonical request를 확정하고 문서·actual endpoint 계약을 동기화했다. |
REV-003 |
High | 처리 완료 | 외부 API·S3·DB 실패 사이에 보상 경계가 없고 동시 중복 이름 생성의 원자성 증거가 없다. | P2-R1, P2-T4, P2-T5 |
failure-order·선검증·비보상 경계와 신규 DDL 없는 동시성 정책을 고정했다. |
REV-004 |
High | 처리 완료 | 동일한 seriesIds 수정도 기존 연결을 삭제·재생성해 row ID·orders·createdAt을 소실한다. |
P3-R1, P3-T6 |
교집합 row를 보존하고 차집합만 변경하도록 수정·검증했다. |
REV-005 |
High | 처리 완료 | 콘텐츠 상세의 releaseDate, isOnlyRental, purchaseOption이 legacy 상세 파생 규칙과 다르다. |
P3-R1, P3-T4 |
legacy 조합 matrix와 v2 releaseDateUtc 분리 mapping을 고정했다. |
REV-006 |
High | 처리 완료 | 콘텐츠 생성의 필수 coverImage·audioFile이 nullable binding이라 정확한 missing-part 오류 계약을 우회한다. |
P3-R1, P3-T5, P3-T7 |
non-null binding과 세 part별 exact exception·KO/EN/JA endpoint 회귀를 추가했다. |
REV-007 |
Medium | 처리 완료 | 목록 초과 field, pre-flush timestamp, characterType, legacy/public 중첩 DTO, tags·생성 isActive에서 추가 계약/parity 차이가 있다. |
P2-T3P2-T6, P3-T3P3-T5, P3-T7 |
소유 Goal별 exact schema·field 의미·DTO 경계와 legacy parity를 분리 검증했다. |
REV-008 |
High | 처리 완료 | endpoint별 인가/i18n, failure-order characterization과 계획에 명시된 test 파일·검증 수의 완료 증거가 부족하다. | P2-R1, P2-T6, P3-R1, P3-T5, P3-T7 |
실제 endpoint matrix와 characterization을 보강하고 정정 기록을 누적했다. |
REV-009 |
Medium | 처리 완료 | Phase 2 실제 네 endpoint의 stale claim·허용/거부 CORS·오류와 mutation 실패 경계 직접 증거가 Gate 기록보다 좁다. | P2-R2, P2-R2-GATE |
actual endpoint matrix와 실패 후 DB/S3/external/event 결과를 보강했다. |
REV-010 |
Medium | 처리 완료 | Phase 3 생성의 invalid theme, 실제 multipart KO/EN/JA와 S3/processing/event 실패 순서 직접 증거가 완료 기록보다 좁다. | P3-R2, P3-R2-GATE |
생성 actual endpoint와 legacy failure characterization을 보강했다. |
REV-011 |
Medium | 처리 완료 | Phase 3 수정의 교집합+추가+제거 metadata, cover 성공/실패와 ownership no-side-effect 직접 증거가 완료 기록보다 좁다. | P3-R3, P3-R2-GATE |
update/ownership actual endpoint 회귀를 보강했다. |
REV-012 |
Medium | 처리 완료 | Phase 2의 DB save/flush·event publish 실패 뒤 내부 rollback과 external/S3 잔존 결과를 직접 검증하지 않았다. | P2-R3, P2-R3-GATE |
실제 transaction failure injection과 내부·외부 결과 단언을 보강했다. |
REV-013 |
Medium | 처리 완료 | Phase 3 생성은 첫 cover upload 실패만 검증하고 두 번째 audio upload와 event 실패 결과를 직접 고정하지 않았다. | P3-R4, P3-R3-GATE |
생성 후반 failure order와 비트랜잭션 S3 결과를 특성화했다. |
REV-014 |
Medium | 처리 완료 | Phase 3 실제 네 경로의 ownership/domain KO/EN/JA와 DB/S3/event no-side-effect matrix가 부분적이다. | P3-R4, P3-R3-GATE |
actual endpoint 오류와 요청 전후 부작용 count를 보강했다. |
REV-015 |
Medium | 처리 완료 | Phase 2 event 실패 테스트가 actual endpoint/Spring transaction을 통과하지 않고 DB save/flush·내부 rollback을 직접 단언하지 않는다. | P2-R4, P2-R4-GATE |
actual POST/PUT persistence·event 실패와 transaction 종료 뒤 내부·외부 결과를 고정했다. |
REV-016 |
High | 처리 완료 | Phase 3 생성 필수 coverImage·audioFile이 nullable이라 exact MissingServletRequestPartException 계약을 우회했다. |
P3-R5, P3-R4-GATE |
non-null binding과 세 part별 exact exception·KO/EN/JA·no-side-effect를 복구했다. |
REV-017 |
Medium | 처리 완료 | Phase 3 4차 보강이 unknown target에 한정돼 cross-owner/domain KO/EN/JA와 DB/S3/event matrix가 완료 기록보다 좁다. | P3-R6, P3-R4-GATE |
대표 cross-owner/series/date actual endpoint의 전체 no-side-effect matrix를 보강했다. |
REV-018 |
High | 처리 완료 | 캐릭터 생성 Endpoint Contract Summary가 필수 systemPrompt를 누락하고 request 금지 externalCharacterId, isActive를 포함해 확정 계약과 반대다. |
P2-R5, P2-R5-GATE |
생성 JSON을 DEC-P2-T4-001과 동기화하고 기존 actual endpoint 계약 회귀로 확인했다. |
REV-019 |
Medium | 처리 완료 | 생성·수정의 빈 multipart 파일이 null/non-empty 검사 사이를 통과해 0-byte upload, cover 교체 또는 수정 audioFile 미지원 계약을 우회한다. |
P3-R7, P3-R5-GATE |
v2 facade에서 생성 empty-file 거부, 수정 empty cover 정규화와 모든 audio part 거부를 RED/GREEN으로 고정했다. |
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을 단언했다. |
검증 기록
- 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-018REV-020의 production/test 보완과 Gate 완료 증거가 일치했고 추가 production finding은 확정되지 않았다. 하단 종합 표에서만 미처리로 남은REV-001REV-009를 각 소유 Gate·Progress 판정에 맞춰처리 완료로 동기화했다. P3-R5-GATEcontent/common 회귀(2026-07-28):./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실행 결과 reviewer gate 보완 후 최종BUILD SUCCESSFUL in 2m 21s였다.P3-R5-GATElint/diff 검증(2026-07-28):./gradlew ktlintCheck실행 결과 reviewer gate 보완 후 최종BUILD SUCCESSFUL in 44s였고,git diff --check는 출력이 없었다.P3-R5-GATE전체 회귀 생략(2026-07-28): 변경 범위가 Phase 2 문서와 Phase 3 content v2 facade/test에 한정되고 content/common 회귀가 실제 영향 범위를 포함하므로 전체./gradlew test는 실행하지 않았다.- Phase 2·3 6차 리뷰 fresh 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.AiCharacterAdminErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --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 16s였다. 다섯 XML 합계 216건의 failure/error/skipped는 모두 0이었다. - Phase 2·3 6차 리뷰 lint/diff 검증(2026-07-28):
./gradlew ktlintCheck --rerun-tasks는 7개 task가 실행되어BUILD SUCCESSFUL in 17s였고, staged/unstagedgit diff --check는 출력이 없었다. - Phase 2·3 6차 리뷰 전체 회귀 생략(2026-07-28): production code를 변경하지 않은 read-only review와 문서 후속 Task 등록이며, 5차 보완의 핵심 actual endpoint 216건과 lint로 직접 범위를 확인했으므로 전체
./gradlew test는 실행하지 않았다. 실제P3-R7production 수정 후 content/common 영향 범위 회귀를 실행한다. - Phase 2·3 5차 Gate focused 검증(2026-07-28):
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --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 52s였다. - Phase 2·3 5차 Gate diff 검증(2026-07-28):
git diff --check는 출력이 없었다. P3-R5RED(2026-07-27):./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest실행 결과 14건 중coverImage·audioFile누락 KO/EN/JA 6건이 실패했다. 예를 들어 audio 누락 KO는 기대잘못된 요청입니다.대신 legacy콘텐츠를 선택해 주세요.를 반환해 nullable binding이 MVC 경계를 우회함을 확인했다.P3-R5focused 검증(2026-07-27):./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest실행 결과BUILD SUCCESSFUL in 1m 1s였다. 세 필수 part 누락 KO/EN/JA는 400 generic envelope, exactMissingServletRequestPartException, DB/S3/event 0회를 실제 endpoint에서 확인했다.P3-R5content/common 회귀(2026-07-27):./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실행 결과BUILD SUCCESSFUL in 2m 46s였다.P3-R5lint(2026-07-27):./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL이었다.P3-R5최종 fresh 검증(2026-07-27):./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest는 모든 Gradle task를 재실행해BUILD SUCCESSFUL in 3m 33s였고,./gradlew ktlintCheck --rerun-tasks는BUILD SUCCESSFUL in 25s였다.- Phase 2·3 5차 리뷰 fresh focused 검증(2026-07-27):
./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.AiCharacterAdminAudioContentOwnershipTest실행 결과BUILD SUCCESSFUL in 7m 50s였다. 세 XML 합계 79건의 failure/error/skipped는 모두 0이었다. - Phase 2·3 5차 리뷰 lint/diff 검증(2026-07-27):
./gradlew ktlintCheck --rerun-tasks는 7개 task가 실행되어BUILD SUCCESSFUL in 28s였고,git diff --check는 출력이 없었다. - Phase 2·3 5차 리뷰 전체 회귀 생략(2026-07-27): production code를 수정하지 않은 review/계획 문서 Task 등록이며 변경된 세 핵심 test를 fresh 실행해 finding을 판정했으므로 전체
./gradlew test는 실행하지 않았다. 실제 수정 Goal Gate에서 character/content/common 영향 범위 회귀를 각각 실행한다. - Phase 2·3 5차 리뷰 문서 명령 유효성(2026-07-27):
./gradlew tasks --all실행 결과test,ktlintCheck,tasks가 존재했고BUILD SUCCESSFUL in 1s였다. - Phase 2·3 4차 보완 focused 검증(2026-07-27):
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest실행 결과BUILD SUCCESSFUL in 1m 1s였다../gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentOwnershipTest실행 결과BUILD SUCCESSFUL in 1m 8s였다. - Phase 2·3 4차 보완 content/common 회귀(2026-07-27):
./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실행 결과BUILD SUCCESSFUL in 2m 20s였다. - Phase 2·3 4차 보완 lint(2026-07-27):
./gradlew ktlintCheck는 신규 test import ordering/unused import 3건 실패 후 import만 정리해 재실행했고,BUILD SUCCESSFUL in 17s였다. - Phase 2·3 4차 리뷰 fresh targeted 검증(2026-07-27):
./gradlew test --rerun-tasks --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실행 결과BUILD SUCCESSFUL in 9m 44s였다. 관련 XML 14개 합계 228건의 failure/error/skipped는 모두 0이었다. - Phase 2·3 4차 리뷰 lint(2026-07-27):
./gradlew ktlintCheck --rerun-tasks실행 결과 7개 task가 실행됐고BUILD SUCCESSFUL in 41s였다. - Phase 2·3 4차 리뷰 전체 회귀 생략(2026-07-27): production code를 변경하지 않은 리뷰·문서 Task 등록이며 character/content actual endpoint와 공통 authorization/error를 포함한 fresh 228건으로 직접 영향 범위를 확인했으므로 전체
./gradlew test는 실행하지 않았다. - Phase 2·3 2차 리뷰 fresh targeted 검증(2026-07-27):
./gradlew test --rerun-tasks --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실행 결과BUILD SUCCESSFUL in 9m 23s였다. 생성된 관련 XML 14개 합계 199건의 failure/error/skipped는 모두 0이었다. - Phase 2·3 2차 리뷰 lint(2026-07-27):
./gradlew ktlintCheck --rerun-tasks실행 결과 7개 task가 실행됐고BUILD SUCCESSFUL in 27s였다. - Phase 2·3 2차 리뷰 전체 회귀 생략(2026-07-27): production code를 수정하지 않은 read-only review와 문서 후속 Task 등록이며, character/content actual endpoint와 공통 authorization/error를 포함한 fresh 199건으로 직접 영향 범위를 확인했으므로 전체
./gradlew test는 실행하지 않았다. - Phase 3 후속 보완 content/common 회귀(2026-07-27):
./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실행 결과BUILD SUCCESSFUL in 2m 41s였다. - Phase 3 후속 보완 lint(2026-07-27):
./gradlew ktlintCheck는 import ordering 1건 실패 후AiCharacterAdminAudioContentUpdateTest.ktimport 순서만 정리해 재실행했고,BUILD SUCCESSFUL in 16s였다. - Phase 2·3 코드 리뷰 Task 보완(2026-07-27): 확정 finding 8개를
REV-001REV-008로 등록하고 기존 미실행P2-T3P2-T6,P3-T3~P3-T7에 소유권, 정확한 RED/RED 확인/GREEN/GREEN 확인/REFACTOR, 파일 경로, focused 명령과 Gate 완료 증거를 보강했다. 중복 Goal은 만들지 않고 각 Phase 안에서 직렬 실행하도록 의존성 표를 갱신했다. - Task TDD 샘플 동기화(2026-07-27):
docs/sample/sample-plan-task.md에RED → RED 확인 → GREEN → GREEN 확인 → REFACTOR작성 규칙과 read-only Task의TDD 예외 사유·대체 검증 방법형식을 추가하고 기존 샘플 Task 체크박스를 같은 형식으로 통일했다. - 문서 보완 검증(2026-07-27):
REV-001~REV-008추적 횟수, Phase 2·3 Task/Gate·직렬 Goal 순서, 중복 Goal ID와 미교체 placeholder를rg로 확인했고git diff --check,git diff --cached --check가 통과했다../gradlew tasks --all은 exit code 0,BUILD SUCCESSFUL in 660ms를 확인했다. 문서 전용 변경이며 사용자가 명시적으로 제외했으므로 전체./gradlew test는 실행하지 않았다. - 리뷰 문서 경로 보완(2026-07-27): 여러 리뷰를 범위별 파일로 누적할 수 있도록 공통 저장 경로를
docs/[날짜]_구현할내용한글/reviews/로 정하고, Phase 2·3 리뷰 산출물과 샘플·가이드 경로를 동기화했다. - 전체 회귀 실행 정책 보완(2026-07-27): focused/영향 범위 회귀를 우선하고 공통 경계·여러 Phase 영향 또는 targeted 결과만으로
영향 범위를 판단할 수 없는 경우에만 전체 회귀를 실행하도록 공통 가이드·샘플·
P7-T1을 동기화했다. 생략 시 근거와 대체 검증 명령을 기록하도록 했다. - Goal 실행형 계획 보완(2026-07-27):
docs/sample/sample-plan-task.md와 PRD Feature BF를 대조해 Phase 2·3에는 기존 완료 이력을 보존한 review/세부 보완/Gate Goal을 추가하고, Phase 46은 독립 검토 가능한 기능 단위 Task와 Gate로 분할했다. Phase 7에는 targeted·조건부 전체 회귀, 계약·변경 범위와 최종 판정 Goal을 분리했다. - 샘플 참조 가이드 보완(2026-07-27):
docs/agent-guides/작업절차.md,docs/agent-guides/문서유지보수.md에docs/sample/{sample-prd.md,sample-plan-task.md,sample-review.md}의 용도, Goal 완료·Gate·리뷰 누적 규칙을 반영했다. - 문서 검증(2026-07-27): Goal/Task/Gate heading과 ID, placeholder·구 샘플 경로, 중복 Goal ID를
rg로 확인했고 중복·미교체 placeholder·구 샘플 경로 참조가 없음을 확인했다.git diff --check가 통과했다. - 명령 유효성 검증(2026-07-27): sandbox 실행은 Gradle wrapper lock 권한으로 실패해 승인 범위에서
./gradlew tasks --all을 재실행했고BUILD SUCCESSFUL을 확인했다. - 계획 작성 단계: 코드 변경 없음.
- 문서 규칙 확인:
docs/agent-guides/작업절차.md,docs/agent-guides/문서유지보수.md,docs/agent-guides/테스트스타일.md,docs/sample/sample-prd.md를 확인하고docs/20260724_AI캐릭터_관리자_API/{prd.md,plan-task.md}형식으로 작성했다. - 금지어 확인: 계획 문서 금지어 검색 명령 실행 결과 없음.
- Phase 항목 확인:
rg -n "^### Phase|#### 목표|#### 범위와 비범위|#### 선행 Phase|#### API endpoint|#### entity, repository, service 변경|#### DB migration|#### transaction과 concurrency|#### 보안 및 개인정보|#### acceptance criteria|#### targeted test|#### 전체 회귀 테스트 영향|#### rollback 전략|#### 권장 commit 경계|Final Integration" "docs/20260724_AI캐릭터_관리자_API/plan-task.md"로 Phase 1~7 전체에 필수 항목이 있음을 확인했다. - 명령 유효성 확인:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL. - Markdown diagnostics:
.md확장자용 LSP 서버가 설정되어 있지 않아lsp_diagnostics는 실행 불가. - Endpoint Contract 보강: frontend HTTP 호출 기준으로 query parameter, request body, multipart form fields, response
dataJSON 예시를 추가했다. - Endpoint Contract 보강 검증: 계획 문서 금지어 검색 결과 없음.
.md확장자용 LSP 서버가 없어 diagnostics는 실행 불가. - 문서 동기화 보강: source spec 기준
characterId예외, 시리즈 콘텐츠 검색, endpoint별 ADMIN 권한 테스트, parity RED 범위, 의존 방향 검증, PRD 가드레일, pagination 경계값, RED/GREEN/REFACTOR task 규칙을 반영했다. - 문서 동기화 검증:
Read로prd.md와plan-task.md의 반영 라인을 확인했다.git status --short결과docs/20260724_AI캐릭터_관리자_API/는 현재 untracked 디렉터리로 표시된다../gradlew tasks --all은 이번 세션에서 120초, 300초 제한 모두 초과해 종료 결과를 확인하지 못했다. - 문서 동기화 보강(2차): 기존 legacy/creator-admin 구현을 먼저 통과하는 특성화 baseline과 신규 v2 RED를 분리했고, character/content/series/community/FanTalk 재사용·parity 경계에 반영했다. 시리즈 CRUD·연결·해제·조회·순서 behavior parity와 signed URL 만료 계산식·edge case 특성화를 PRD와 Task에 명시했다.
- 문서 동기화 재검증(4번 제외): source spec acceptance criteria 25개 추적 검사 25/25, actor/scope/non-goal/architecture 가드레일 11/11을 확인했고
git diff --check결과 문제가 없었다. plan 상단의 실행 skill 지침은 사용자 요청에 따라 변경·판정 범위에서 제외했다. - 기존 기록은 보존한다. 각 Task의 실행 명령과 결과 요약은 해당 Task 아래에 누적하고, phase/전체 회귀·전체 빌드·포맷·문서 범위 확인만 이 섹션에 누적한다.
- Phase 1 RED:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest실행 결과AiCharacterAdminTargetResolver와ChatCharacterRepository.findByIdWithCreatorMember미구현으로compileTestKotlin실패를 확인했다. - Phase 1 GREEN:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverTest실행 결과BUILD SUCCESSFUL. - Phase 1 ADMIN 권한 RED/GREEN:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest에서 sample route 권한 실패를 확인한 뒤/api/v2/admin/ai-characters/**ADMIN rule을 적용해BUILD SUCCESSFUL을 확인했다. - Phase 1 targeted 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*'실행 결과BUILD SUCCESSFUL. - Phase 1 lint 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL. - Phase 1 LSP diagnostics: 현재 도구 목록에 LSP diagnostics tool이 노출되어 있지 않아 실행하지 못했고, 대신 Kotlin compile/test와
ktlintCheck로 대체 검증했다. - Phase 1 reviewer gate: 1차 리뷰에서 invalid target 4xx 미충족, production SecurityConfig 미검증, cross-owner fixture 부족을 지적받아 수정했고, 재리뷰 결과 남은 blocking finding 없음으로 승인받았다.
- Phase 1 추가 리뷰 반영: mock 기반 resolver 테스트만으로 실제 repository/ownership 동작을 검증하지 못한다는 지적에 따라
AiCharacterAdminTargetResolverIntegrationTest를 추가했다../gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminTargetResolverIntegrationTest실행 결과BUILD SUCCESSFUL. - Phase 1 no-side-effect 근거 보정: 현재 검증은 resolver 자체가 DB row를 변경하지 않는다는 통합 테스트와 resolver가 S3/외부 API/event 의존성을 갖지 않는다는 구조에 한정한다. Phase 2~6 write vertical slice의 S3, 외부 API, 이벤트 no-side-effect는 각 slice 테스트에서 별도로 검증한다.
- Phase 1 후속 정책 반영 전 한계: 기존 ADMIN smoke는 JWT authority만 검증했고 현재 DB role/stale claim 및 신규 prefix 오류 envelope/i18n을 검증하지 않았다. 2026-07-24 후속 확정 정책은 Task 1.4~1.5에서 RED/GREEN으로 보완한다.
- Phase 1 후속 정책 문서 갱신: JWT ADMIN + 현재 DB ADMIN 이중 인가, stale claim 403, 신규 prefix의 비2xx
ApiResponse.error/KO·EN·JA 계약, legacy 오류 응답 불변 조건을 PRD와 계획에 반영하고 미완료 Task 1.4~1.5를 추가했다. - Phase 1 후속 정책 문서 자체 검토: 금지어/미확정 문구 검색 결과 없음, 요구사항 추적 검색으로 stale claim·오류 envelope·i18n·
legacy fallback·Task 1.4~1.5 반영을 확인했고
git diff --check결과 문제가 없었다. - Phase 1 후속 정책 명령 유효성:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL. - Task 1.4 RED:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest실행 결과 6개 중 stale ADMIN claim과 비MemberAdapterprincipal이 403 기대 대비 200으로 통과해 2개 실패함을 확인했다. - Task 1.4 GREEN: 신규 prefix의 단일 access 식에서 JWT ADMIN,
MemberAdapterprincipal, 현재 DB ADMIN을 AND로 검증한 뒤 동일 테스트 실행 결과BUILD SUCCESSFUL. - Task 1.4 reviewer gate: 별도 read-only 리뷰에서 스펙 준수와 코드 품질 모두 승인됐고 Critical/Important/Minor finding이 없음을 확인했다.
- Task 1.5 최초 RED: legacy 오류 baseline 2개는 통과했고 신규 401/403/400/500 계약은 15개 중 13개가 status/content type/
message 불일치로 실패함을 확인했다. target resolver의 기존
ResponseStatusException을 신규 API 예외로 교체하는 테스트도 새 예외 미구현 상태의compileTestKotlin실패로 RED를 확인했다. - Task 1.5 확장 RED: handler 선택 전 오류와 filter 내부 장애까지 포함해 오류 계약 39개를 실행한 결과 신규 prefix의 404/405/ 415 및 예상하지 못한 JWT filter 오류 500에 해당하는 12개만 실패했고 legacy baseline은 통과했다. 미등록 message key fallback은 44개 중 해당 KO/EN/JA 3개 실패로 별도 RED를 확인했다.
- Task 1.5 GREEN: URI prefix 기반 exception resolver, 낮은 우선순위 404 fallback mapping, prefix 전용 security handler,
known 인증 실패 401/그 외 filter 예외 500 분리, 미등록 message key의 localized unknown fallback을 구현했다.
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest실행 결과 44개 invocation 모두 통과해BUILD SUCCESSFUL을 확인했다. - Task 1.5 기존 slice 회귀: production
SecurityConfig를 import하는 기존 WebMvc test 5개와 신규 authorization/error contract test를 함께 실행한 결과BUILD SUCCESSFUL을 확인했다. 신규 writer/security handler/exception resolver는SecurityConfig의 명시적 bean으로 등록해 slice와 실제 application 구성을 동일하게 유지했다. - Task 1.5 reviewer gate: 최초 read-only 리뷰의 405/415 handler-less 경로, 비인증 filter 예외의 401 오분류, 기존 WebMvc slice 빈 누락, 미매핑 404 지적을 모두 보완했다. 재리뷰 결과 Critical/Important/Minor finding 없이 승인됐다.
- Phase 1 후속 정책 최종 targeted/legacy 회귀:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과BUILD SUCCESSFUL(28초)을 확인했다. - Phase 1 후속 정책 전체 회귀: 최신 작업 트리에서
./gradlew test실행 결과BUILD SUCCESSFUL(4분 22초)을 확인했다. - Phase 1 후속 정책 lint: 최신 작업 트리에서
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL(17초)을 확인했다. - Phase 1 후속 정책 diff 무결성:
git diff --check통과, conflict marker 없음, build 설정·dependency·DDL 변경 없음을 확인했다. - Task 1.5 최종 리뷰 보완 RED/GREEN: JWT parse 사이 만료
JwtException401과 인증 저장소 장애 500을 추가했을 때 50개 중 해당 6개 실패를 확인한 뒤 known credential failure만 401로 분류해 50개 모두 통과했다. 필수 request header 누락 400은 53개 중 해당 KO/EN/JA 3개 실패를 확인한 뒤ServletRequestBindingException을 400으로 분류해 모두 통과했다. - Task 1.5 CORS RED/GREEN: 허용된 관리자 Origin의 404/405/415와 실제
authorization,content-typeheader를 요청하는 미매핑 prefix preflight를 추가했을 때 57개 중 해당 4개 실패를 확인했다. fallback mapping에 기존 전역 설정과 동일한 CORS 설정을 적용한 뒤 57개 모두 통과했다. - Phase 1 no-side-effect 검증 강화:
AiCharacterAdminTargetResolverIntegrationTest에서 Hibernate statistics를 초기화한 뒤 invalid target resolver 호출과 flush 후 entity insert/update/delete가 각각 0건임을 직접 검증했다. resolver 단위·통합 및 오류 계약 테스트를 함께 실행한 결과BUILD SUCCESSFUL(27초)을 확인했다. - Phase 1 최종 reviewer gate: 인증 예외 분류, request binding, fallback CORS, DB no-write, 문서/rollback을 독립 read-only로 재검토한 결과 Critical/Important/Minor finding이 없음을 확인했다.
- Phase 1 최신 targeted/legacy 회귀:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과BUILD SUCCESSFUL(28초)을 확인했다. - Phase 1 최신 전체 회귀: 최종 코드 작업 트리에서
./gradlew test실행 결과BUILD SUCCESSFUL(4분 17초)을 확인했다. - Phase 1 최신 lint: import 순서 1건을 수정한 뒤
./gradlew ktlintCheck재실행 결과BUILD SUCCESSFUL(10초)을 확인했다. - Phase 1 최신 diff 무결성:
git diff --check와git diff --cached --check가 모두 통과했고 conflict marker가 없으며 build 설정, dependency, DDL 변경이 없음을 확인했다. - Phase 1 코드 리뷰 차단 이슈 보완: 리뷰에서 지적된 staged/untracked 누락을 재확인한 결과 최신 작업 트리는 Phase 1 신규 테스트
2개(
AiCharacterAdminAccessDeniedErrorContractTest,AiCharacterAdminLoginJwtIntegrationTest)와AccessDeniedException403 수정이 모두 변경 세트에 포함되어 있음을 확인했다.TokenProvidersubject parsing 보정과 관련 테스트, rollback 범위를 Task 1.4/1.5와 Phase 1 rollback 전략에 반영했다. - Phase 1 코드 리뷰 차단 이슈 재검증:
./gradlew test --rerun-tasks --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests 'kr.co.vividnext.sodalive.jwt.TokenProviderTest'단독 실행 결과BUILD SUCCESSFUL을 확인했다. 이전 QA의 GradleTestOutputStoreEOF는 동시/강제 실행 환경에서 발생한 결과 저장소 문제로 보며, 현재 단독 fresh rerun에서는 재현되지 않았다. - Phase 1 코드 리뷰 차단 이슈 최종 검증: 문서 보정 후
git status --short --untracked-files=all에서 untracked 파일이 없고git diff --name-only결과가 비어 있음을 확인했다.git diff --cached --check,git diff --check,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests 'kr.co.vividnext.sodalive.jwt.TokenProviderTest',./gradlew test --tests 'kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest' --tests 'kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest',./gradlew ktlintCheck실행 결과 모두BUILD SUCCESSFUL을 확인했다. - Task 1.6 CORS 계약 재확인: 현재
WebConfig기준 전용 Originhttp://localhost:8888,https://test-character-admin.sodalive.net,https://character-admin.sodalive.net세 개를 PRD/plan에 명시했다. preflight 테스트는 세 Origin 모두 허용하고 기존 범용 관리자/creator Origin 네 개를 거부하는지 고정했다. - Task 1.6 JWT RED/GREEN:
TokenProviderTest9개 중 추가한 누락·빈 값·빈 분할 항목·비문자열authclaim 7개 invocation이 실패하는 RED를 확인했다. claim을 authority로 변환하기 전 검증해common.error.bad_credentials로 변환한 뒤 9개 모두 통과했다. - Task 1.6 테스트 fixture 격리: 전용 controller 6개를 각 테스트의 nested
@TestComponent로 이동하고@Import로만 등록했다. 초기 nested 이동 후 명시 등록이 누락된 WebMvc 요청 37개가 404로 실패한 것을 확인한 뒤 보정했고, 독립 재리뷰의 component scan 지적을@TestComponent로 해소했다. - Task 1.6 최종 targeted/레거시 회귀:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*'실행 결과 103개 모두 통과했다. 기존AdminAgentReadControllerSecurityTest,AdminContentControllerSecurityTest보안 회귀도BUILD SUCCESSFUL을 확인했다. - Task 1.6 최종 전체 회귀/lint: 최종 소스 상태에서
./gradlew test는BUILD SUCCESSFUL(4분 55초),./gradlew ktlintCheck --rerun-tasks는BUILD SUCCESSFUL(19초)를 확인했다. - Task 1.6 최종 reviewer/diff gate: 독립 read-only 재리뷰 결과 CORS, malformed
auth, fixture scan 격리에 잔여 finding이 없음을 확인했다.git diff --check,git diff --cached --check가 통과했고 conflict marker와 untracked 파일이 없음을 확인했다. - Task 1.6 2차 리뷰 CORS RED/GREEN: 실제 mapped endpoint, 공유
/admin/member/login,/member/logout의 캐릭터 관리자 Origin 요청/preflight를 추가했을 때AiCharacterAdminLoginJwtIntegrationTest6개 중 3개 실패를 확인했다. 두 공유 인증 exact path에만 기존 전역 Origin과 캐릭터 관리자 Origin 합집합을 적용한 뒤, 미등록 Origin 거부까지 포함한 7개가 모두 통과했다. 신규 prefix의 기존 범용 관리자 Origin 거부와 실제 로그인·로그아웃도 함께 검증했다. - Task 1.6 2차 리뷰 HTTP 오류 RED/GREEN: 406, 405
Allow, 415Accept/Accept-Patch,MissingPathVariableException500 계약을 추가해 신규 13개 실패를 확인했다.AiCharacterAdminExceptionHandler에 Spring 기본 HTTP 의미를 보존하는 최소 분기와 header 처리를 추가한 뒤AiCharacterAdminErrorContractTest80개가 모두 통과했다. - Task 1.6 2차 리뷰 최종 targeted/레거시 회귀:
TokenProviderTest와aicharacter.*114개, 기존AdminAgentReadControllerSecurityTest와AdminContentControllerSecurityTest11개를 함께 실행해 총 125개 모두 통과했고BUILD SUCCESSFUL(42초)을 확인했다. - Task 1.6 2차 리뷰 전체 회귀/lint: 최종 소스 상태에서
./gradlew test는BUILD SUCCESSFUL(5분 4초),./gradlew ktlintCheck --rerun-tasks는BUILD SUCCESSFUL(20초)을 확인했다. - Task 1.6 2차 리뷰 최종 gate: 공유 인증 exact path가 전역 fallback보다 먼저 적용되고 신규 prefix/fallback은 전용 Origin을
유지하는지 독립 read-only로 재검토했다.
git diff --check,git diff --cached --check가 통과했고 conflict marker와 untracked 파일이 없음을 확인했다. - Task 1.6 후속 리뷰 mapped write preflight 보완: 기존 신규 prefix write preflight 검증이 fallback
/api/v2/admin/ai-characters/unmapped-path만 타는 한계를 확인했다. 테스트 controller에 실제 mapped/api/v2/admin/ai-characters/error-contract/write-preflight의POST/PUT/PATCH/DELETE매핑을 추가하고, 해당 경로 preflight에서Access-Control-Allow-Origin과Access-Control-Allow-Methods를 함께 검증하도록 보완했다. - Task 1.6 후속 리뷰 fallback write preflight 보완: actual mapping과 fallback이 서로 다른 CORS 설정을 사용하므로,
mapped endpoint 검증과 별도로 fallback
/api/v2/admin/ai-characters/unmapped-path에서도POST/PUT/PATCH/DELETEpreflight의Access-Control-Allow-Origin과Access-Control-Allow-Methods를 검증하도록 보완했다. - Task 1.6 후속 리뷰 추가 계약 보완: wrong-role
USER+AI_CHARACTERtarget은 resolver가 400으로 거부하고 DB insert/update/delete 없이 기존 role/memberKind를 유지하는지 고정했다. 캐릭터 관리자 Origin이 공유 인증 외 legacy/public 경로로 확산되지 않는지 확인했고, 406 Not Acceptable 응답도 허용 Origin에서는 localizedApiResponse와Access-Control-Allow-Origin을 함께 반환하는지 검증했다. - Task 1.6 후속 리뷰 계약 테스트 현황: mapped write preflight 보완 후
AiCharacterAdminErrorContractTest85개,AiCharacterAdminLoginJwtIntegrationTest8개,AiCharacterAdminTargetResolverIntegrationTest4개 기준으로 후속 리뷰 항목을 회귀했다. - Task 1.6 후속 리뷰 최종 targeted/레거시 회귀:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과 targeted+legacy 132개가 모두 통과했고BUILD SUCCESSFUL(40초)을 확인했다. - Task 1.6 후속 리뷰 전체 회귀/lint: mapped write preflight 보완 후 소스 상태에서
./gradlew test는 전체 1,259개 기준BUILD SUCCESSFUL(4분 53초),./gradlew ktlintCheck --rerun-tasks는BUILD SUCCESSFUL(37초)을 확인했다. - Task 1.6 후속 리뷰 fallback 보완 후 계약 테스트 현황: fallback write preflight 보완 후
AiCharacterAdminErrorContractTest89개,AiCharacterAdminLoginJwtIntegrationTest8개,AiCharacterAdminTargetResolverIntegrationTest4개 기준으로 후속 리뷰 항목을 회귀했다. - Task 1.6 후속 리뷰 fallback 보완 후 최종 targeted/레거시 회귀:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과 targeted+legacy 136개가 모두 통과했고BUILD SUCCESSFUL(1분 38초)을 확인했다. - Task 1.6 후속 리뷰 fallback 보완 후 전체 회귀/lint: fallback write preflight 보완 후 최종 소스 상태에서
./gradlew test는 전체 1,263개 기준BUILD SUCCESSFUL(5분 58초),./gradlew ktlintCheck --rerun-tasks는BUILD SUCCESSFUL(35초)을 확인했다. - Task 1.6 추가 코드 리뷰 보완: 실제 logout 후 동일 JWT로 신규 prefix 보호 경로를 호출하면 localized 401을 반환하는지
AiCharacterAdminLoginJwtIntegrationTest에 통합 테스트로 고정했다. 공유/admin/member/login,/member/logoutCORS preflight는WebConfig의 기존 전역 Origin과 캐릭터 관리자 Origin 합집합 전체를 허용하는 parameterized test로 확장했다. Phase 1 targeted Run 명령에는TokenProviderTest를 포함하도록 보정했다. - Task 1.6 추가 코드 리뷰 보완 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminLoginJwtIntegrationTest실행 결과BUILD SUCCESSFUL을 확인했다. - Task 1.6 추가 코드 리뷰 보완 최종 회귀/lint:
./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*'실행 결과BUILD SUCCESSFUL(44초),./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL(25초)을 확인했다. - Phase 1 최신 canonical fresh targeted/legacy 회귀:
./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과 145/145, failure/error 0,BUILD SUCCESSFUL(3분 12초)을 확인했다. - Phase 1 최신 전체 fresh 회귀:
./gradlew test --rerun-tasks실행 결과 1,272/1,272, failure/error 0,BUILD SUCCESSFUL(6분 49초)을 확인했다. - Phase 1 최신 lint:
./gradlew ktlintCheck --rerun-tasks실행 결과BUILD SUCCESSFUL(17초)을 확인했다. - Phase 1 최신 명령 유효성:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL이며test,ktlintChecktask가 존재함을 확인했다. - Phase 1 최종 보강 후 canonical fresh targeted/legacy 회귀:
./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*' --tests kr.co.vividnext.sodalive.admin.partner.agent.read.AdminAgentReadControllerSecurityTest --tests kr.co.vividnext.sodalive.admin.content.AdminContentControllerSecurityTest실행 결과 9개 XML class, 154/154, failure/error/skipped 0,BUILD SUCCESSFUL(5분)을 확인했다. - Phase 1 최종 보강 후 전체 fresh 회귀:
./gradlew test --rerun-tasks실행 결과 243개 XML class, 1,281/1,281, failure/error/skipped 0,BUILD SUCCESSFUL(10분 2초)을 확인했다. - Phase 1 최종 보강 후 lint:
./gradlew ktlintCheck --rerun-tasks실행 결과 7개 task가 실행됐고BUILD SUCCESSFUL(29초)을 확인했다.