31 KiB
Phase 4 시리즈 관리 리뷰
1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 4 / 시리즈 9개 operation |
| 기준 commit 또는 working tree | 2f93e2c9 + 현재 Phase 4~7 working tree |
| 리뷰 일자 | 2026-07-28 |
| 리뷰어 | Codex |
| 기준 문서 | prd.md, plan-task.md, api-contract.openapi.json |
| 리뷰 상태 | 후속 수정 및 Gate 완료 |
2. 리뷰 목적과 범위
목적
- PRD Feature D와 OpenAPI Series 9개 operation을 controller/facade/repository/test에 직접 대조한다.
- 공개 route 수, soft delete 방식, owner 검증과 JSON request schema 경계를 점검한다.
포함 범위
src/main/kotlin/.../aicharacter/series/*src/test/kotlin/.../aicharacter/series/*- OpenAPI Series path/schema와 plan Phase 4
제외 범위
- production 수정, legacy series API 변경, 테스트 실행
3. 판정 기준
| 심각도 | 기준 |
|---|---|
| Blocker | 보안·소유권 우회 또는 데이터 손실 |
| High | OpenAPI operation/path 위반 또는 주요 사용자 흐름 회귀 |
| Medium | request schema·오류 계약의 제한된 위반 |
| Low | 유지보수성 또는 문서 정합성 문제 |
4. 검토한 근거
| 근거 | 판정 |
|---|---|
OpenAPI :247~:389 |
Series는 9개 operation이며 /series/{seriesId}에는 GET/PUT만 존재 |
AiCharacterAdminSeriesController.kt:23~:112 |
10개 route를 노출하며 마지막 DELETE가 계약에 없음 |
AiCharacterAdminSeriesFacade.kt:166~:187 |
별도 DELETE facade가 PUT과 같은 isActive=false를 구성 |
AiCharacterAdminSeriesMutationTest.kt:201 이후 |
계약 밖 DELETE 성공·거부 동작을 테스트가 고정 |
OpenAPI :1099~:1109 |
콘텐츠 추가와 순서 변경 schema는 additionalProperties: false |
controller :55~:82 |
두 request를 기본 @RequestBody DTO binding으로 수신 |
| strict reader 검색 | multipart create/update에만 FAIL_ON_UNKNOWN_PROPERTIES가 있고 두 JSON body에는 없음 |
실행한 검증
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|---|---|---|
| route/schema/호출 정적 대조 | 성공 | OpenAPI 9개 대비 controller 10개, strict JSON 경계 누락 확인 |
| Gradle/컴파일/테스트 | 미실행 | 사용자 요청에 따라 실행하지 않음 |
5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-023 |
High | 처리 완료 | OpenAPI에 없는 시리즈 DELETE route 노출 | Task 4.7 |
P4-R1 |
REV-024 |
Medium | 처리 완료 | 콘텐츠 추가·순서 변경이 미지 JSON 필드를 허용 | Task 4.7 |
P4-R1 |
6. 발견 사항 상세
REV-023 — OpenAPI에 없는 시리즈 DELETE route
- 심각도: High
- 상태: 처리 완료
- 관련 요구사항: PRD Feature D, 공개 API schema 임의 변경 금지
- 관련 계약:
/series/{seriesId}는 GET과 PUT 수정/soft delete만 제공 - 소유 Task:
Task 4.7,P4-R1
관찰 내용
controller는 DELETE /series/{seriesId}를 추가로 노출한다. 같은 soft delete는 계약에 있는 PUT의 isActive=false로 이미
표현되므로 별도 route는 중복이자 공개 API 표면 확장이다.
영향
서버와 OpenAPI 기반 client가 서로 다른 operation 집합을 사용한다. 현재 테스트도 계약 밖 route를 정상 동작으로 고정해 향후 차이를 지속시킨다.
권장 조치
DELETE controller/facade를 제거하고 기존 DELETE 성공 기대를 405 계약으로 교정하며 PUT isActive=false actual endpoint
테스트를 보강한다.
처리 결과
DELETE /series/{seriesId} controller/facade 경로를 제거했고, actual endpoint 테스트를 405와 PUT isActive=false soft delete
계약으로 교정했다.
REV-024 — 두 JSON body의 additionalProperties: false 미적용
- 심각도: Medium
- 상태: 처리 완료
- 관련 요구사항: OpenAPI request schema 준수
- 관련 계약:
SeriesContentAddRequest,SeriesOrderUpdateRequest - 소유 Task:
Task 4.7,P4-R1
관찰 내용
multipart create/update는 facade strict reader를 사용하지만 콘텐츠 추가와 순서 변경은 기본 @RequestBody binding을
사용한다. repository 전역 설정에는 unknown property 실패 설정이 없어 두 schema의 계약 밖 필드를 무시하고 요청을 처리한다.
현재 malformed JSON 테스트는 문법 오류만 확인하고 미지 필드를 확인하지 않는다.
영향
잘못된 client field가 성공으로 처리되어 계약 오류를 조기에 발견할 수 없고 mutation side effect까지 진행될 수 있다.
권장 조치
두 body만 strict parse하고 미지 필드 요청의 400 common.error.invalid_request와 DB/event 0회를 actual endpoint로
고정한다. 전역 ObjectMapper 설정은 legacy/public 영향이 있으므로 변경하지 않는다.
처리 결과
콘텐츠 추가와 순서 변경 body를 facade strict reader로 역직렬화하도록 변경했고, 미지 필드 요청의 400/no-side-effect 테스트를 추가했다.
7. plan·goal 전환
plan-task.md Phase 4의 Task 4.7 / P4-R1과 P4-R1-GATE를 완료 처리했다. 기존 P4-GATE 완료 이력은 유지한다.
8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| OpenAPI operation 대조 | 충족 | Series controller mapping 9개, 계약 밖 DELETE /series/{seriesId} 제거 |
| JSON schema 대조 | 충족 | content add/order body strict parse와 unknown-field 400 회귀 통과 |
| ownership/soft delete 핵심 추적 | 충족 | facade owner 검증과 PUT soft delete 경로 존재 |
| 실행 검증 | 충족 | focused 테스트, series/common 회귀, ktlintCheck, git diff --check 통과 |
최종 결론: 후속 수정 및 Gate 완료
남은 항목: 없음. 다음 Goal은 P5-R1이다.
9. 2차 정적 리뷰 및 판정 — 2026-07-28
리뷰 정보와 검증 범위
- 기준 commit/working tree:
2f93e2c9+ 현재 working tree - 기준 문서: PRD Feature D·API Expectations, plan Phase 4, OpenAPI Series 9개 operation
- 검토 범위: 시리즈 생성 multipart binding, 콘텐츠 연결/해제 facade·repository·legacy service와 관련 테스트
- 검증 방식: controller → facade → repository/legacy service 상태 전이를 정적으로 추적했다. 사용자 요청에 따라 컴파일과 테스트는 실행하지 않았다.
추가 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-031 |
Medium | 처리 완료 | soft-delete된 linked content를 시리즈에서 해제할 수 없음 | Task 4.8 |
P4-R2 |
REV-032 |
Medium | 처리 완료 | 생성 필수 image가 nullable binding으로 공통 오류 계약 우회 |
Task 4.8 |
P4-R2 |
REV-031 — soft-delete 콘텐츠의 기존 link 해제 차단
- 심각도: Medium
- 상태: 처리 완료
- 관련 요구사항: PRD Feature D의 콘텐츠 연결/해제와 동일 owner 검증
- 관련 계약: 존재하는 owner link 해제 성공, 없는/cross-owner link만 400
- 소유 Task:
Task 4.8,P4-R2
관찰 내용
해제 facade가 실제 link 존재 여부뿐 아니라 연결 추가/미연결 검색용 적격성 method를 호출한다. 이 method는
duration != null && (isActive || releaseDate != null)을 요구하므로, 정상 연결 후 콘텐츠가 soft delete되어
isActive=false, releaseDate=null이 되면 link가 존재해도 해제를 400으로 거부한다.
근거
- 코드:
AiCharacterAdminSeriesFacade.kt:109~:120은 해제 전에findEligibleContentByIdAndCreatorMemberId를 요구한다. - 코드:
AiCharacterAdminSeriesRepository.kt:28~:30은 inactive/unreleased 콘텐츠를 제외한다. - 코드: legacy
CreatorAdminContentSeriesService.kt:297~:303은 owner series의 link를 content 상태와 무관하게 제거한다. - 테스트:
AiCharacterAdminSeriesContentTest는 정상 active link 해제와 inactive 콘텐츠 연결 거부를 분리 검증하지만, 이미 연결된 콘텐츠를 soft delete한 뒤 해제하는 상태 전이는 없다.
정적 재현 절차
- owner의 active content를 active series에 연결한다.
- content를 soft delete해
isActive=false,releaseDate=null로 만든다. - 같은 owner/series/content로 DELETE unlink를 요청한다.
- 실제 link는 남아 있지만 적격성 guard가 null을 반환해 400이 된다.
영향
관리자는 삭제된 콘텐츠의 시리즈 연결을 정리할 수 없고 orphan link가 남는다. 콘텐츠 연결 추가 적격성과 기존 link 해제 조건이 불필요하게 결합된 문제다.
권장 조치
해제는 series에 존재하는 link와 link 콘텐츠의 owner만 검증한다. findEligibleContentByIdAndCreatorMemberId는 연결 추가에만
유지하고 soft-delete link 해제 회귀를 추가한다.
처리 결과
해제 경로에서 연결 추가·검색용 active/release/duration 적격성 guard를 제거하고, 실제 series link와 link 콘텐츠의 owner만 검증하도록 변경했다. soft-delete된 owner content의 기존 link 해제 성공 회귀를 추가했다.
REV-032 — 시리즈 생성 필수 image의 nullable binding
- 심각도: Medium
- 상태: 처리 완료
- 관련 요구사항: PRD API Expectations의 필수 multipart 누락 오류
- 관련 계약: OpenAPI
SeriesCreateMultipart.required = ["image", "request"] - 소유 Task:
Task 4.8,P4-R2
관찰 내용
OpenAPI는 생성 image를 필수로 선언하지만 controller와 facade는 nullable로 받아 누락 요청을 legacy service까지
전달한다. 결과는 400이지만 exact MissingServletRequestPartException과 common.error.invalid_request 대신
legacy creator.admin.series.cover_image_required가 된다. plan의 Phase 4 오류 표가 이 legacy 결과를 기록해 PRD 공통 계약과
충돌한다.
근거
- 계약: OpenAPI
SeriesCreateMultipart의 requiredimage. - 요구사항: PRD
:183~:184는 필수 part 누락을 exactMissingServletRequestPartException, 400common.error.invalid_request로 고정한다. - 코드:
AiCharacterAdminSeriesController.kt:83~:90은required=false, nullable image를 사용한다. - 코드: facade create와 legacy service는 null을 받아 domain key로 변환한다.
- 테스트:
AiCharacterAdminSeriesMutationTest는 missing image가 facade/legacy까지 도달하는 동작을 기대한다.
영향
클라이언트는 같은 신규 prefix의 다른 필수 multipart 누락과 다른 message/exception 계약을 받는다. 상태는 400으로 같고 mutation은 시작되지 않으므로 영향은 오류 표면에 제한된다.
권장 조치
PRD/OpenAPI를 우선해 생성 image만 non-null binding으로 변경하고 세 locale의 exact exception/envelope과 facade/DB/S3/event 0회를 검증한다. update image의 optional 계약은 유지한다.
처리 결과
시리즈 생성 image part를 non-null MultipartFile binding으로 변경해 누락 요청을 MissingServletRequestPartException,
400 common.error.invalid_request로 통일했다. KO/EN/JA envelope과 no-side-effect를 actual endpoint로 고정했고 update의 optional
image 계약은 유지했다.
판정 기록
- 2026-07-28 —
REV-031,REV-032모두 코드·문서·테스트 정적 추적으로 확정. 테스트는 미실행.
plan·goal 전환
plan-task.md Phase 4에 Task 4.8 / P4-R2와 P4-R2-GATE를 추가했다.
DEC-P4-R2-001로 생성 image 누락의 canonical 오류를 확정했으며 기존 완료 이력은 유지한다.
P4-R2와 P4-R2-GATE를 완료 처리했다.
2차 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| operation/path 대조 | 충족 | Series 9개 mapping 유지 |
| 상태 전이 대조 | 충족 | soft-delete linked content unlink 성공 회귀 통과 |
| multipart binding | 충족 | required image 누락이 MissingServletRequestPartException 400으로 처리됨 |
| plan 반영 | 충족 | Task 4.8, P4-R2, P4-R2-GATE |
| 실행 검증 | 충족 | focused 테스트, series/common 회귀, ktlintCheck, git diff --check 통과 |
최종 결론: 후속 수정 및 Gate 완료
남은 항목: 없음. 다음 Goal은 P5-R2다.
10. 3차 정적 리뷰 및 판정 — 2026-07-28
리뷰 정보와 범위
- 기준 commit/working tree:
2f93e2c9+ 현재 working tree - 기준 문서: PRD Feature D, plan Phase 4, OpenAPI Series 9개 operation
- 검토 범위: 생성·수정 multipart facade, legacy series service의 S3 upload, mutation 테스트
- 검증 방식: 코드·문서·테스트 정적 추적. 컴파일과 테스트는 실행하지 않았다.
발견 사항
REV-037 — Medium — 시리즈 빈 image의 0-byte upload
- 생성 controller의 non-null binding은 part 누락만 차단하며
MultipartFile.isEmpty는 검증하지 않는다. - facade는 생성 image를 그대로 legacy service로 넘기고, legacy service는 size 0 metadata와 input stream을 S3에 업로드한 뒤 cover를 교체한다.
- 수정의 optional 빈 image도 null이 아니므로 같은 0-byte upload와 기존 cover 교체가 발생한다.
- 현재 테스트는 생성 image 누락과 정상 image 경로를 다루지만 생성·수정의 빈 part는 다루지 않는다.
권장 조치: 생성 빈 image는 legacy 호출 전에 400으로 거부하고, 수정 빈 image는 optional part 생략으로 정규화한다. 정상 upload, 수정 image 생략, 빈 image no-S3/no-cover-change를 actual endpoint로 고정한다.
plan·goal 전환
plan-task.md Phase 4에 Task 4.9 / P4-R3과 P4-R3-GATE를 추가했다.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| operation/path | 충족 | Series 9개 mapping 유지 |
| 생성 empty file | 수정 필요 | 0-byte upload와 DB/event 부작용 발생 |
| 수정 empty file | 수정 필요 | 기존 cover가 0-byte 객체로 교체됨 |
| plan 반영 | 충족 | Task 4.9, P4-R3, P4-R3-GATE 추가 |
| 실행 검증 | 미실행 | 사용자 요청에 따라 컴파일·테스트 미실행 |
최종 결론: Phase 4 후속 수정 필요
남은 항목: P3-R11-GATE 후 P4-R3 → P4-R3-GATE.
11. 3차 후속 수정 및 Gate — 2026-07-29
- 무엇을:
REV-037을 처리했다. - 왜: 생성·수정의 빈
imagepart가 legacy service로 전달되어 0-byte S3 upload와 cover 교체를 유발했기 때문이다. - 어떻게:
- RED:
AiCharacterAdminSeriesMutationTest에 빈 생성 image 400/no-side-effect, 빈 수정 image cover 유지, 빈 image-only no_changes 유지 테스트를 추가했다. focused 실행에서 신규 3건이 실패했다. - GREEN:
AiCharacterAdminSeriesFacade.create는 empty image를 400으로 거부하고,update는 empty image를 null로 정규화해 legacy service에 전달했다. - 검증: focused series mutation test, targeted aicharacter 회귀, 전체
./gradlew test,ktlintCheck, OpenAPI/mapping/diff 점검을 실행했다.
- RED:
- 결과:
REV-037처리 완료. 생성 empty image는 부작용 전에 400이고, 수정 empty image는 생략으로 처리해 기존 cover를 유지한다.
최종 결론: Phase 4 3차 리뷰 종결
남은 항목: 없음.
12. 4차 정적 리뷰 및 판정 — 2026-07-29
리뷰 범위와 방식
- 기준 commit/working tree:
2f93e2c9+ 현재 working tree - 기준 문서: PRD Feature D, plan Phase 4, OpenAPI Series 9개 operation
- 검토 범위: CRUD, 콘텐츠 조회·검색·연결·해제, owner-scoped 순서 변경, 최신 empty image 보완
- 검증 방식: 코드·schema·테스트 정적 대조. 컴파일과 테스트는 실행하지 않았다.
발견 사항과 판정
Series runtime의 신규 확정 finding은 없다. 9개 controller mapping, owner-scoped lock·연결/해제와 생성·수정 empty image 정책은 현재 OpenAPI와 일치한다.
다만 plan Phase 4 endpoint 설명은 콘텐츠 해제를 DELETE /series/{seriesId}/contents와 request body로 적고 있어,
OpenAPI와 실제 DELETE /series/{seriesId}/contents/{contentId} body 없음 route와 달랐다. 이를 REV-039로 확정했고,
Phase 7 Task 7.6 / P7-R4에서 설명을 정정했다. Task 4.9 헤더의 미완료 표기도 REV-038로 함께 동기화했다.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| runtime operation | 충족 | Series controller mapping 9개와 OpenAPI 일치 |
| ownership/순서 | 충족 | active owner ID 전체 검증과 ID 정렬 lock 유지 |
| 연결/해제 | 충족 | owner link 검증 후 path contentId로 해제 |
| 문서 계약 | 충족 | REV-039의 stale DELETE path/body 설명 정정 |
| plan 전환 | 충족 | Task 7.6, P7-R4 완료 |
최종 결론: Phase 4 기능 추가 수정 없음, Phase 7 문서 보완 완료
남은 항목: 없음.
13. 5차 정적 리뷰 및 판정 — 2026-07-29
확인된 문제
REV-042 — 시리즈 생성 primitive의 explicit null 허용 가능성
- 심각도: Medium
- 상태: 처리 완료
- 계약: OpenAPI
SeriesCreateRequest의genreId,isAdult은 nullable이 아니며 각각 생략 기본값0,false를 가진다. - 구현:
CreateSeriesRequest의 두 값은 Kotlin primitive이고 facade strict reader는 미지 필드만 거부한다. - 근거: Jackson Kotlin/databind 2.13.5 기본 설정에서 explicit null은 primitive 기본값으로 보정될 수 있다.
- 영향: 특히
isAdult: null이 계약상 400 대신false인 정상 생성으로 이어져 S3·DB·event mutation이 발생할 수 있다.
보완 결과
| 항목 | 판정 |
|---|---|
| 신규 Task | Task 4.10 / P4-R4 처리 완료 |
| 시작 조건 | P3-R12-GATE 완료 후 실행 |
| Gate | P4-R4-GATE 완료 |
| RED | genreId: null, isAdult: null actual POST가 보완 전 400 기대 실패 |
| GREEN | v2 생성 strict reader에서 explicit null 거부, 필드 생략 기본값 유지 |
| 범위 제한 | 전역 mapper·레거시 service·OpenAPI 변경 없음 |
실행 검증
| 명령 또는 검증 | 결과 | 핵심 증거 |
|---|---|---|
AiCharacterAdminSeriesMutationTest.shouldRejectNullPrimitiveCreateFieldsBeforeSideEffects RED |
실패 확인 | 신규 2개 invocation이 400 기대 실패 |
| 같은 focused RED/GREEN 명령 | 통과 | strict reader 보완 후 BUILD SUCCESSFUL |
AiCharacterAdminSeriesMutationTest |
통과 | 기존 생성 기본값·정상 mutation 회귀 유지 |
| series/common 영향 범위 회귀 | 통과 | BUILD SUCCESSFUL in 1m 12s |
ktlintCheck, git diff --check |
통과 | ktlintCheck는 BUILD SUCCESSFUL in 20s, diff check 출력 없음 |
최종 결론: Phase 4 REV-042 보완 완료
다음 Goal: P5-R3.
14. 장르 참조 API·상세 응답 후속 검토 — 2026-07-29
확인 결과
REV-046/ Medium / 구현 대기: 시리즈 등록용 활성 장르 목록은 레거시 관리자 service/repository에 존재하지만 신규 v2 캐릭터 관리자 route에는 없다. 계약은orders오름차순의id,genre,isAdult직접 배열이다.REV-047/ Medium / 정합화 대기: 현재 시리즈 상세는 레거시 상세 DTO를 반환해 목록 item과publishedDaysOfWeek,genreId,state,isActive필드·타입이 다르다.- 승인된 상세
data는 목록items단일 객체와 동일한 11개 필드이며 기존 상세 전용genre,keywords는 제거한다.
plan 전환
- 장르 목록:
Task 4.11/P4-R5, GateP4-R5-GATE - 상세 정합화:
Task 4.12/P4-R6, GateP4-R6-GATE - 범위 밖: 장르 CRUD, 목록 wrapper 변경, legacy/public 상세 DTO 변경
사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
최종 결론: Phase 4 장르 목록 구현과 시리즈 상세 정합화 필요
다음 Goal: P4-R5.
15. 장르 참조 API 구현 및 Gate — 2026-07-29
- 무엇을:
REV-046을 처리했다. - 왜: 시리즈 등록 화면에서 사용할 활성 장르 목록이 레거시 관리자 API에는 있으나 신규 v2 캐릭터 관리자 route에는 없었기 때문이다.
- 어떻게:
- RED:
AiCharacterAdminSeriesGenreTest에 활성/비활성 장르,orders오름차순, 빈 목록, ADMIN 공통 경계 actual GET 테스트 2건을 추가했고 미구현 route로 실패했다. - GREEN:
AiCharacterAdminSeriesReferenceController에GET /api/v2/admin/ai-characters/series-genres를 추가하고 facade에서 기존AdminContentSeriesGenreService.getSeriesGenreList()를 그대로 재사용했다. - Gate: focused 장르 테스트, series/common 영향 범위 회귀,
ktlintCheck,git diff --check를 fresh 실행했다.
- RED:
- 결과:
REV-046처리 완료. 별도 query, DTO, pagination, 장르 CRUD 변경은 추가하지 않았다.
최종 결론: Phase 4 장르 목록 후속 기능 종결
다음 Goal: P4-R6.
16. 시리즈 상세 정합화 및 Gate — 2026-07-29
- 무엇을:
REV-047을 처리했다. - 왜: 시리즈 상세가 레거시 상세 DTO를 반환해 목록 item과 field/type이 달랐기 때문이다.
- 어떻게:
- RED:
AiCharacterAdminSeriesQueryTest에서 상세data의 11개 목록 item field와genre,keywords부재를 고정했고 레거시 상세 응답 차이로 실패했다. - GREEN: v2 상세 response type을
GetCreatorAdminContentSeriesListItem으로 통일하고 facade에서 owner-scoped series를 동일 필드로 매핑했다. - Gate: focused query/contract, series/common 영향 범위 회귀,
ktlintCheck,git diff --check를 fresh 실행한다.
- RED:
- 결과:
REV-047처리 완료. legacy/public 상세 DTO와 mapper는 변경하지 않았다.
최종 결론: Phase 4 시리즈 상세 정합화 종결
다음 Goal: P5-R5.
17. 6차 통합 정적 리뷰 및 판정 — 2026-07-29
리뷰 범위와 방식
- 기준 문서: PRD Feature D, OpenAPI Series 10개 operation
- 검토 범위: Series/reference controller, facade/repository, JSON·multipart mapping과 목록·상세 DTO
- 기준 상태: 현재 working tree
- 검증 방식: 문서·코드·관련 테스트 정적 대조. 사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
확인 결과
| 항목 | 판정 | 근거 |
|---|---|---|
| route/operation | 충족 | 장르 참조를 포함한 Series 10개 OpenAPI operation과 실제 mapping 유지 |
| request 전체 media type | 충족 | 생성·수정 multipart, 순서 변경·콘텐츠 추가 JSON consumes 선언 일치 |
| query/default | 충족 | 목록·연결 콘텐츠 page/size 기본값과 미연결 검색 필수 query 일치 |
| response/ownership | 충족 | 상세-목록 item schema 정합화와 target owner 경계 유지 |
REV-057 — High — 처리 완료 — Series multipart request part의 JSON media type 미강제
- OpenAPI와 계약 설명은 생성·수정 multipart의
requestpart Content-Type을application/json으로 고정한다. - 두 controller는
@RequestPart("request") request: String으로 받아 part 자체의 media type을 검사하지 않는다. - 정상 테스트는 JSON media type만 사용하며 미지원/누락 part media type의 415
Acceptheader와 S3/DB/event no-side-effect를 고정하지 않는다. - 같은 shared converter와 signature에서 AudioContent의
text/plain성공 테스트가 있어 permissive binding을 정적으로 확인할 수 있다.
처리 결과
AiCharacterAdminSeriesController의 POST·PUT에MultipartHttpServletRequest를 받고 Character·AudioContent와 같은 part header 검사와HttpMediaTypeNotSupportedException을 적용했다. 기존 String strict reader와 facade/domain 로직은 변경하지 않았다.AiCharacterAdminSeriesMutationTest는 POST·PUT 각각의text/plain·Content-Type 누락 request part를 KO/EN/JA 415ApiResponse.error,Accept: application/json, S3/DB/event no-side-effect로 고정했고, 필수requestpart 누락의 기존 400 binding 오류도 확인했다.- RED는 신규 media type 12건이 415 기대와 달리 실패했고, GREEN focused 36건과 Series/common error 영향 범위 회귀,
ktlintCheck, OpenAPI encoding 정적 대조,git diff --check를 통과했다.
최종 결론: REV-057, P4-R7, P4-R7-GATE 처리 완료
다음 Goal: P5-R7 (P4-R7-GATE 완료 후).
18. 7차 통합 정적 리뷰 및 판정 — 2026-07-29
리뷰 범위와 방식
- 기준 문서: PRD Feature D, OpenAPI
SeriesCreateMultipart·SeriesUpdateMultipart - 검토 범위: Series POST·PUT controller의 multipart binding과 image/genre/owner mutation 테스트
- 검증 방식: 현재 working tree의 문서·코드·테스트를 정적으로 대조했다. 사용자 요청에 따라 컴파일과 테스트는 실행하지 않았다.
REV-062 — Medium — image, request 외 multipart part가 무시됨
- 두 Series multipart schema는
additionalProperties: false이며 허용 이름은image,request다. - controller의
requireJsonRequestPart()는requestmedia type만 검사하고 전체 part 이름을 열거하지 않는다. - 정상 part와 미정의 part를 함께 보낸 요청이 facade로 전달될 수 있어 OpenAPI 입력 범위보다 runtime이 넓다.
plan 전환
| 항목 | 내용 |
|---|---|
| 신규 Task | Task 4.14 / P4-R8 |
| Gate | P4-R8-GATE |
| RED | POST·PUT 미정의 part와 S3·DB·event 결과 |
| GREEN | 실제 part 이름을 {image, request}와 비교해 초과 이름 400 |
| 회귀 | 필수/빈 image, request part 415, genre/owner 경계 |
처리 결과 (2026-07-29 / P4-R8):
- Series POST·PUT controller 경계에 multipart part allow-list를 추가해
image,request외 file part를 400common.error.invalid_request로 거부했다. - RED에서 POST·PUT
unexpectedpart KO/EN/JA 테스트 6개가 기존 mutation 경로로 실패함을 확인했고, GREEN 후 focused/영향 범위 회귀,ktlintCheck,git diff --check를 통과했다.
Gate 결과 (2026-07-29 / P4-R8-GATE):
- Focused multipart 회귀, Phase 4 Series 영향 범위 회귀,
ktlintCheck,git diff --check를 fresh 실행했다. - POST·PUT 미정의 part 400/no-side-effect, 정상·필수/빈 image, request part 415, genre/owner 경계가 유지됨을 확인했다.
최종 결론: REV-062 resolved. Phase 4 완료.
다음 Goal: P5-R9.
19. 8차 통합 정적 리뷰 및 판정 — 2026-07-29
리뷰 범위와 방식
- 기준 문서: PRD Feature D, OpenAPI Series create/update multipart 및 장르 계약
- 검토 범위: Series controller/facade, active genre 조회 경계, legacy genre repository와 mutation 테스트
- 기준 상태: 현재 working tree
- 리뷰어/상태: Codex / 판정 완료
- 검증 방식: 문서·코드·테스트 소스와 기존 compile output을 정적으로 대조했다. 사용자 지시에 따라 컴파일과 테스트는 실행하지 않았다.
REV-067 — Medium — 일반 form-field multipart part가 allow-list 우회
AiCharacterAdminSeriesController.kt:114-117은fileMap.keys만{image, request}와 비교한다.- 기존
AiCharacterAdminSeriesMutationTest.kt:664-710은 filename이 있는MockMultipartFile("unexpected", ...)만 검증하므로 filename 없는 일반 form-field part 경계는 빠져 있다. - OpenAPI의 create/update multipart
additionalProperties: false를 우회할 수 있어 Medium으로 확정한다.
REV-068 — Medium — genreId <= 0이 active genre 검사를 우회
AiCharacterAdminSeriesFacade.kt:155-168은 create와 non-null updategenreId에 공통 검사를 호출하지만,:213-215의 구현은 양수인 경우에만 active genre 존재를 확인한다.- OpenAPI create schema는 0이 domain validation에서 유효하지 않다고 명시한다. 그러나 0 또는 음수는 legacy service로
전달되고,
CreatorAdminContentSeriesGenreRepository.kt:19-26의 QueryDSLfetchFirst()결과를 Kotlin non-null 반환으로 취급하는 repository 경계에서 NPE가 발생할 수 있다. - 기존 compile output의 해당 repository bytecode를
javap로 확인한 결과fetchFirst()뒤 KotlincheckNotNullExpressionValue가 존재했다. 공통 예상 밖 예외는 500 경로이므로 요청 오류 400 계약과 다르다.
plan 전환
| finding | 신규 Task / Goal | Gate | 최소 보완 |
|---|---|---|---|
REV-067 |
Task 4.15 / P4-R9 |
P4-R9-GATE |
servlet 전체 part 이름을 {image, request}와 대조 |
REV-068 |
Task 4.16 / P4-R10 |
P4-R10-GATE |
0 이하 또는 비활성·미존재 장르를 legacy 호출 전에 400으로 거부 |
최종 결론: Phase 4 보완 필요 — Medium 2건 확정
다음 Goal: P4-R9 (P3-R18-GATE 완료 후), 이어서 P4-R10.
20. 8차 후속 수정 및 Gate — 2026-07-29
- 무엇을:
REV-067의 Series POST·PUT filename 없는 multipart part 우회와REV-068의genreId <= 0사전 거부 누락을 보완했다. - 왜:
{image, request}외 일반 form-field part와 0 이하 장르가 legacy service 호출 전 400으로 고정되어야 하기 때문이다. - 어떻게:
AiCharacterAdminSeriesMutationTest에 filename 없는 part와genreId=0/-1create·update KO/EN/JA actual endpoint 회귀를 추가하고, controller part 검사와 facade active genre guard를 최소 수정했다. - 결과: RED 묶음에서 신규 multipart/genre 36건 실패를 확인했고, 보완 후 focused GREEN 묶음은
BUILD SUCCESSFUL in 1m 17s였다. 영향 범위 회귀와 lint 결과는P7-R10-GATE에 통합 기록한다.
최종 결론: REV-067, REV-068 처리 완료. Phase 4 후속 Gate 완료.
남은 항목: 없음.
21. 9차 정적 리뷰 및 판정 — 2026-07-29
리뷰 범위와 방식
- 기준 문서: PRD Feature E, OpenAPI Series 10개 operation
- 검토 범위: 목록·상세·생성·수정·연결 콘텐츠·순서·장르, owner/active genre/multipart 경계
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과 테스트는 실행하지 않았다.
판정 및 plan 전환
- Series 10개 operation과 controller/facade의 owner·active genre·exact multipart 경계를 대조했다.
- 기존 완료 finding 이후 신규 확정 finding은 없다.
- Phase 4 신규 Task/Gate 없음.
최종 결론: Phase 4 추가 수정 없음.
남은 항목: Phase 3 보완 뒤 P7-R11 통합 재판정.
22. 10차 정적 리뷰 및 판정 — 2026-07-30
리뷰 범위와 방식
- 기준 문서: PRD Feature D, OpenAPI Series 10개 operation
- 검토 범위: 장르·목록·상세·생성·수정, 연결 콘텐츠 조회·검색·추가·해제와 순서 변경
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과 테스트는 실행하지 않았다.
판정
- Series 10개 operation과 controller mapping, 목록 item과 동일한 상세 schema가 일치한다.
- active target/series·genre와 owner-scoped 콘텐츠 연결/해제, 순서 변경 lock·전체 ID 선검증이 유지된다.
- 생성·수정의 필수/빈 image, strict JSON과 exact multipart part 경계가 OpenAPI와 일치한다.
- 신규 확정 finding이 없어 Phase 4 회귀 수정 Task/Gate를 추가하지 않는다.
최종 결론: Phase 4 요구사항 충족, 추가 수정 없음.
남은 항목: 없음.