Files
sodalive-backend-spring-boot/docs/20260724_AI캐릭터_관리자_API/reviews/phase4-series-review.md

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-R1P4-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
  • 심각도: 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한 뒤 해제하는 상태 전이는 없다.

정적 재현 절차

  1. owner의 active content를 active series에 연결한다.
  2. content를 soft delete해 isActive=false, releaseDate=null로 만든다.
  3. 같은 owner/series/content로 DELETE unlink를 요청한다.
  4. 실제 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 MissingServletRequestPartExceptioncommon.error.invalid_request 대신 legacy creator.admin.series.cover_image_required가 된다. plan의 Phase 4 오류 표가 이 legacy 결과를 기록해 PRD 공통 계약과 충돌한다.

근거

  • 계약: OpenAPI SeriesCreateMultipart의 required image.
  • 요구사항: PRD :183~:184는 필수 part 누락을 exact MissingServletRequestPartException, 400 common.error.invalid_request로 고정한다.
  • 코드: AiCharacterAdminSeriesController.kt:83~:90required=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-R2P4-R2-GATE를 추가했다. DEC-P4-R2-001로 생성 image 누락의 canonical 오류를 확정했으며 기존 완료 이력은 유지한다. P4-R2P4-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-R3P4-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-GATEP4-R3P4-R3-GATE.

11. 3차 후속 수정 및 Gate — 2026-07-29

  • 무엇을: REV-037을 처리했다.
  • 왜: 생성·수정의 빈 image part가 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 점검을 실행했다.
  • 결과: 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 SeriesCreateRequestgenreId, 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 통과 ktlintCheckBUILD 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, Gate P4-R5-GATE
  • 상세 정합화: Task 4.12 / P4-R6, Gate P4-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: AiCharacterAdminSeriesReferenceControllerGET /api/v2/admin/ai-characters/series-genres를 추가하고 facade에서 기존 AdminContentSeriesGenreService.getSeriesGenreList()를 그대로 재사용했다.
    • Gate: focused 장르 테스트, series/common 영향 범위 회귀, ktlintCheck, git diff --check를 fresh 실행했다.
  • 결과: 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 실행한다.
  • 결과: 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의 request part Content-Type을 application/json으로 고정한다.
  • 두 controller는 @RequestPart("request") request: String으로 받아 part 자체의 media type을 검사하지 않는다.
  • 정상 테스트는 JSON media type만 사용하며 미지원/누락 part media type의 415 Accept header와 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 415 ApiResponse.error, Accept: application/json, S3/DB/event no-side-effect로 고정했고, 필수 request part 누락의 기존 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()request media 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를 400 common.error.invalid_request로 거부했다.
  • RED에서 POST·PUT unexpected part 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-117fileMap.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 update genreId에 공통 검사를 호출하지만, :213-215의 구현은 양수인 경우에만 active genre 존재를 확인한다.
  • OpenAPI create schema는 0이 domain validation에서 유효하지 않다고 명시한다. 그러나 0 또는 음수는 legacy service로 전달되고, CreatorAdminContentSeriesGenreRepository.kt:19-26의 QueryDSL fetchFirst() 결과를 Kotlin non-null 반환으로 취급하는 repository 경계에서 NPE가 발생할 수 있다.
  • 기존 compile output의 해당 repository bytecode를 javap로 확인한 결과 fetchFirst() 뒤 Kotlin checkNotNullExpressionValue가 존재했다. 공통 예상 밖 예외는 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-068genreId <= 0 사전 거부 누락을 보완했다.
  • 왜: {image, request} 외 일반 form-field part와 0 이하 장르가 legacy service 호출 전 400으로 고정되어야 하기 때문이다.
  • 어떻게: AiCharacterAdminSeriesMutationTest에 filename 없는 part와 genreId=0/-1 create·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 요구사항 충족, 추가 수정 없음.

남은 항목: 없음.