docs(ai-character): 관리자 API 계약과 검증 기록을 갱신한다

This commit is contained in:
2026-07-30 00:35:34 +09:00
parent d86a9fb059
commit 5a90fbf98e
11 changed files with 9179 additions and 287 deletions

View File

@@ -9,7 +9,7 @@
| 리뷰 일자 | 2026-07-27 |
| 리뷰어 | Sisyphus |
| 기준 문서 | `docs/20260724_AI캐릭터_관리자_API/prd.md`, `docs/20260724_AI캐릭터_관리자_API/plan-task.md` |
| 리뷰 상태 | 판정 완료 |
| 리뷰 상태 | 후속 수정 및 Gate 완료 |
## 2. 리뷰 목적과 범위
@@ -581,3 +581,433 @@ Endpoint Contract Summary를 source of truth로 사용하는 client는 제공
**최종 결론:** Phase 2 6차 리뷰 종결
**남은 항목:** `P3-R7``P3-R8``P3-R5-GATE`. Phase 4는 진행하지 않는다.
## 15. 7차 정적 리뷰 및 판정 — 2026-07-28
### 리뷰 정보와 검증 범위
- 기준 commit/working tree: `2f93e2c9` + 현재 Phase 2~7 working tree
- 기준 문서: PRD Feature B, `plan-task.md`, `api-contract.openapi.json`
- 리뷰 상태: 판정 완료, 후속 수정 goal 필요
- 검증 방식: controller/facade/mapper/test 호출을 정적으로 추적했다. 사용자 요청에 따라 Gradle, 컴파일, 테스트는
실행하지 않았다.
### 추가 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-021` | Low | 처리 완료 | mutation에서 사용하지 않는 전체 response mapping 수행 | `Task 2.12` | `P2-R6` |
### REV-021 — mutation의 미사용 response mapping
- **심각도:** Low
- **상태:** 처리 완료
- **관련 요구사항:** PRD Feature B, 레거시 mutation 성공 응답 유지
- **관련 계약:** Character POST/PUT의 성공 `data``null`
- **소유 Task:** `Task 2.12`, `P2-R6`
**관찰 내용**
controller는 create/update facade 반환값을 사용하지 않고 항상 `ApiResponse.ok(null)`을 반환한다. 그러나 facade는 두
mutation 마지막에 전체 `AiCharacterAdminCharacterResponse`를 생성한다. 외부 계약에 필요 없는 객체 그래프 mapping이
write 성공 뒤 추가 실패 지점과 유지보수 비용을 만든다.
**근거**
- 코드: `AiCharacterAdminCharacterController.kt:34`~`50`은 facade 호출 뒤 exact `data: null`을 반환한다.
- 코드: `AiCharacterAdminCharacterFacade.kt:62`, `:116`은 response DTO 반환형을 선언한다.
- 코드: 같은 파일 `:112`, `:163`은 controller가 버리는 `characterMapper.toResponse(...)`를 수행한다.
- 계약: OpenAPI Character mutation은 `NullSuccess`를 사용한다.
**권장 조치**
`P2-R6`에서 create/update facade 반환형을 `Unit`으로 축소하고 두 mapper 호출만 제거한다. controller와 공개 schema,
business pipeline은 변경하지 않고 기존 mutation exact `data: null` 회귀로 동작 불변을 확인한다.
### plan·goal 전환
`plan-task.md` Phase 2에 `Task 2.12` / `P2-R6``P2-R6-GATE`를 추가했다. 이전 완료 Task/Gate는 다시 열지 않는다.
### 7차 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 문서·구현 대조 | 충족 | Character 4개 operation과 controller/facade 정적 추적 |
| 후보 판정 | 충족 | `REV-021` 확정 |
| plan 반영 | 충족 | `Task 2.12`, `P2-R6`, `P2-R6-GATE` 추가 |
| 실행 검증 | 미실행 | 사용자 요청에 따라 compile/test 미실행 |
**최종 결론:** 수정 goal 필요
**남은 항목:** `P2-R6` 실행 후 `P2-R6-GATE`에서 Phase 2를 재판정한다.
### P2-R6-GATE 종료 판정 — 2026-07-28
- 무엇을: `REV-021`의 캐릭터 POST/PUT 미사용 response mapping 제거를 최종 판정했다.
- 왜: mutation 성공 응답은 `data: null`인데 facade가 controller가 사용하지 않는 상세 DTO를 만들고 있었기 때문이다.
- 어떻게: `AiCharacterAdminCharacterFacade.create/update` 반환형을 `Unit`으로 축소하고 마지막 `characterMapper.toResponse(...)` 호출만 제거했다. 이후 mutation focused, character/common 회귀, `ktlintCheck`, `git diff --check`를 실행했다.
- 결과: mutation focused는 `BUILD SUCCESSFUL in 1m 54s`, character/common 회귀는 `BUILD SUCCESSFUL in 1m 57s`, `ktlintCheck``BUILD SUCCESSFUL in 33s`, `git diff --check`는 출력 없음이었다.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| `REV-021` 처리 | 충족 | facade 반환형 축소와 미사용 mapper 호출 제거 |
| actual endpoint 회귀 | 충족 | POST/PUT mutation `data: null` focused test 성공 |
| 영향 범위 회귀 | 충족 | character/common 회귀, lint, diff check 성공 |
| 범위 준수 | 충족 | business pipeline, controller response, schema 변경 없음 |
**최종 결론:** Phase 2 7차 리뷰 종결
**남은 항목:** `P3-R9` 실행 후 `P3-R9-GATE`에서 Phase 3을 재판정한다.
## 16. 8차 정적 리뷰 및 판정 — 2026-07-28
### 리뷰 정보와 범위
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
- 기준 문서: PRD Feature B, plan Phase 2, OpenAPI Character 4개 operation
- 검토 범위: controller/facade/DTO/mapper, 외부 API·S3·DB/event 순서와 관련 actual endpoint 테스트
- 검증 방식: 정적 호출·schema 대조. 사용자 요청에 따라 컴파일과 테스트는 실행하지 않았다.
### 발견 사항과 판정
확정 발견 사항 없음.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| operation/schema | 충족 | Character 4개 mapping과 OpenAPI request/response 형태 일치 |
| target/ownership | 충족 | detail/update가 공통 resolver와 character target을 사용 |
| mutation 응답 | 충족 | POST/PUT `data: null`, 미사용 상세 mapping 제거 상태 유지 |
| 외부 부작용 경계 | 신규 finding 없음 | 기존 선검증·rollback/비보상 결정과 테스트 존재 |
| plan 전환 | 해당 없음 | Phase 2 신규 Task 불필요 |
**최종 결론:** Phase 2 추가 수정 없음
**남은 항목:** 없음. `P7-R2` 통합 재판정에서 기존 Character/common 회귀만 확인한다.
## 17. 9차 정적 리뷰 및 판정 — 2026-07-28
### 리뷰 정보와 범위
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
- 기준 문서: PRD Feature B, plan Phase 2, OpenAPI Character 4개 operation
- 검토 범위: controller/facade/mapper, multipart 생성, optional update field와 레거시 controller/service parity
- 검증 방식: 정적 호출·schema·테스트 대조. 컴파일과 테스트는 실행하지 않았다.
### 발견 사항
#### `REV-034` — Medium — 캐릭터 생성의 빈 필수 image 허용
- OpenAPI `CharacterCreateMultipart``image`를 required로 선언한다.
- controller는 non-null part 존재까지만 강제하고, facade는 `image?.isEmpty == false`일 때만 upload한다.
- 빈 part는 외부 API 생성과 DB 저장을 먼저 수행한 뒤 image upload를 건너뛰므로 이미지 없는 캐릭터와 관련 부작용을 남긴다.
- 기존 mutation 테스트는 image part 누락은 다루지만 빈 part를 다루지 않는다.
**권장 조치:** facade 진입 직후 빈 image를 400 `common.error.invalid_request`로 거부하고 외부 API, DB, S3,
event 0회를 actual endpoint로 고정한다.
#### `REV-035` — Medium — `isActive=true` 단독 수정의 레거시 parity 위반
- OpenAPI `CharacterUpdateRequest.isActive`는 nullable boolean이며 `true`도 유효하다.
- 레거시 `AdminChatCharacterController.hasChanges``isActive != null`을 변경 요청으로 인정해 200
`data: null` pipeline을 수행한다.
- v2 mapper는 `request.isActive == false`만 external change로 인정하므로 `{"isActive":true}` 단독 요청이
facade no-change guard에서 400이 된다.
**권장 조치:** non-null `isActive`를 변경 요청으로 인정하되 기존 `false` 비활성화 의미와 일반 update pipeline은
변경하지 않는다.
### plan·goal 전환
`plan-task.md` Phase 2에 `Task 2.13` / `P2-R7``P2-R7-GATE`를 추가했다. 두 finding은 같은 facade/mapper와
mutation test 범위이므로 하나의 최소 보완 Task로 묶었다.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| operation/schema | route 유지 | Character 4개 mapping은 유지되나 두 runtime 의미 불일치 존재 |
| multipart create | 수정 필요 | 빈 required image가 외부·DB 부작용 뒤 무시됨 |
| update parity | 수정 필요 | `isActive=true` 단독 요청이 legacy와 다른 400 |
| plan 반영 | 충족 | `Task 2.13`, `P2-R7`, `P2-R7-GATE` 추가 |
| 실행 검증 | 미실행 | 사용자 요청에 따라 컴파일·테스트 미실행 |
**최종 결론:** Phase 2 후속 수정 필요
**남은 항목:** `P2-R7``P2-R7-GATE`.
## 18. 9차 후속 수정 및 Gate — 2026-07-29
- 무엇을: `REV-034`, `REV-035`를 처리했다.
- 왜: 빈 필수 `image`가 외부 생성·DB 저장 뒤 무시되고, `isActive=true` 단독 수정이 레거시와 달리 400으로 거부됐기 때문이다.
- 어떻게:
- RED: `AiCharacterAdminCharacterControllerMutationTest`에 빈 image POST 400/no-side-effect와 `isActive=true` PUT 200 `data:null` actual endpoint 테스트를 추가했다. focused 실행에서 신규 2건이 실패했다.
- GREEN: create facade가 empty image를 400으로 거부하고, mapper가 non-null `isActive`를 변경 요청으로 인정하도록 최소 수정했다.
- 검증: focused mutation test, `./gradlew test --tests kr.co.vividnext.sodalive.jwt.TokenProviderTest --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.*'`, 전체 `./gradlew test`, `./gradlew ktlintCheck`, OpenAPI/mapping/diff 점검을 실행했다.
- 결과: `REV-034`, `REV-035` 처리 완료. 빈 image는 외부 API·DB·S3·event 전에 400으로 종료되고, `isActive=true` 단독 PUT은 200 `data:null`로 통과한다.
**최종 결론:** Phase 2 9차 리뷰 종결
**남은 항목:** 없음.
## 19. 10차 정적 리뷰 및 판정 — 2026-07-29
### 리뷰 범위와 방식
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
- 기준 문서: PRD Feature B, plan Phase 2, OpenAPI Character 4개 operation
- 검토 범위: 목록·상세·생성·수정 controller/facade/mapper, 레거시 parity와 최신 `REV-034`~`REV-035` 보완
- 검증 방식: 코드·schema·테스트 정적 대조. 컴파일과 테스트는 실행하지 않았다.
### 발견 사항과 판정
Character runtime의 신규 확정 finding은 없다. 빈 필수 image는 외부·DB·S3·event 전에 거부되고,
`isActive=true` 단독 수정은 레거시 200 `data: null` 의미를 유지한다.
`Task 2.13` 헤더가 `[ ]`로 남은 문제는 기능 문제가 아닌 전체 완료 상태 기록 불일치였고,
Phase 7 `REV-038` / `Task 7.6`에서 완료 상태로 동기화했다.
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| operation/schema | 충족 | Character 4개 route와 OpenAPI field·multipart 경계 일치 |
| ownership/인가 | 충족 | 목록 외 target route와 공통 ADMIN 이중 인가 유지 |
| mutation parity | 충족 | create/update/soft delete와 최신 empty/no-op 보완 유지 |
| Phase 2 기능 Task | 해당 없음 | 신규 production 수정 불필요 |
| 문서 상태 | 충족 | `REV-038`, `P7-R4`에서 완료 헤더 동기화 |
**최종 결론:** Phase 2 기능 추가 수정 없음
**남은 항목:** 없음.
## 20. 11차 정적 리뷰 및 판정 — 2026-07-29
### 확인된 문제
#### `REV-040` — 필수 관계 정수의 누락·null이 기본값으로 보정될 수 있음
- **심각도:** Medium
- **상태:** 처리 완료
- **계약:** OpenAPI `CharacterRelationship``importance`를 required non-null integer로 정의한다.
- **구현:** `ChatCharacterRelationshipRequest.importance`는 Kotlin `Int`이고, v2 character facade의 strict reader는
`FAIL_ON_UNKNOWN_PROPERTIES``FAIL_ON_NULL_FOR_PRIMITIVES`를 활성화한다.
- **근거:** 사용 중인 Jackson Kotlin/databind 2.13.5에서 `FAIL_ON_NULL_FOR_PRIMITIVES` 기본값은 비활성화되어
Kotlin/JVM primitive의 누락·null이 `0`으로 역직렬화될 수 있다.
- **영향:** 잘못된 관계 입력이 400으로 거부되지 않고 외부 캐릭터 생성·DB mutation으로 이어질 수 있다.
### 보완 계획
| 항목 | 판정 |
|---|---|
| 신규 Task | `Task 2.14` / `P2-R8` |
| Gate | `P2-R8-GATE` |
| RED | `importance` 누락·null actual POST와 외부 API·DB·S3·event 결과 |
| GREEN | v2 생성 경계의 primitive null/누락 400 변환 완료 |
| 범위 제한 | 전역 mapper·레거시 DTO·OpenAPI 변경 없음 |
### 처리 결과
- `AiCharacterAdminCharacterControllerMutationTest`에 관계 `importance` 누락·null actual POST와 외부 API·DB·S3·event no-side-effect 회귀를 추가했다.
- `AiCharacterAdminCharacterFacade.readRequest()``FAIL_ON_NULL_FOR_PRIMITIVES`를 추가해 전역 mapper·레거시 DTO·OpenAPI 변경 없이 v2 경계에서 400으로 변환했다.
- RED: 신규 2건은 보완 전 `status().isBadRequest` 기대에서 실패했다.
- GREEN/GATE: 보완 후 focused, character/common 영향 범위, `ktlintCheck`, `git diff --check`를 fresh 실행했다.
**최종 결론:** Phase 2는 `REV-040` 처리 완료
**다음 Goal:** `P3-R12`.
## 21. 등록 참조 API 후속 검토 — 2026-07-29
### 확인 결과
- **`REV-044` / High / 구현 대기:** 캐릭터 등록용 원작 검색은 레거시
`AdminOriginalWorkController.search``AdminOriginalWorkService.searchOriginalWorksAll`에 존재하지만 신규 v2
캐릭터 관리자 route에는 없다.
- 검색 계약은 필수 `searchTerm`, 제목·콘텐츠 타입·카테고리 부분 검색, soft delete 제외, 무페이징
`OriginalWorkResponse` 직접 배열로 확정됐다.
- target 없는 reference endpoint지만 신규 prefix의 ADMIN 이중 인가·오류·CORS 경계는 동일하게 적용한다.
### plan 전환
- 신규 Task: `Task 2.15` / `P2-R9`
- Gate: `P2-R9-GATE`
- 범위 밖: 원작 CRUD, pagination·정렬 추가, 레거시 endpoint 변경
사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
**최종 결론:** Phase 2 원작 검색 구현 필요
**다음 Goal:** `P2-R9`.
## 22. 12차 통합 정적 리뷰 및 판정 — 2026-07-29
### 리뷰 범위와 방식
- 기준 문서: PRD Feature B, OpenAPI Character 5개 operation
- 검토 범위: Character controller/facade/strict request reader, 원작 검색, 외부 API·S3·DB 선검증 경계
- 기준 상태: 현재 working tree
- 검증 방식: 문서·코드·관련 테스트 정적 대조. 사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
### 확인 결과
| 항목 | 판정 | 근거 |
|---|---|---|
| route/operation | 충족 | Character 5개 OpenAPI operation과 실제 mapping 유지 |
| request 전체 media type | 충족 | 생성·수정 mapping은 multipart/form-data로 제한 |
| pagination/reference query | 충족 | 목록 기본값과 원작 검색 필수 `searchTerm` 계약 일치 |
| mutation 경계 | 충족 | strict JSON, 필수 image, target/원작 검증과 기존 외부/S3/DB 순서 유지 |
### `REV-055` — High — 처리 완료
- OpenAPI와 계약 설명은 생성·수정 multipart의 `request` part Content-Type을 `application/json`으로 고정한다.
- controller는 기존 `@RequestPart("request") request: String` strict reader 전달을 유지하면서 multipart part header에서
`application/json` 호환 여부를 확인한다.
- `text/plain`과 content type 누락은 `HttpMediaTypeNotSupportedException`으로 공통 415 오류 계약에 연결해
localized `ApiResponse.error``Accept: application/json`을 반환한다.
- POST·PUT actual endpoint의 KO/EN/JA 12개 case는 external API·S3·DB·event 부작용 없이 415를 반환하고,
request part 누락은 기존 400으로 유지한다.
### plan 전환
- 신규 Task: `Task 2.16` / `P2-R10`
- Gate: `P2-R10-GATE`
- 최소 수정: 기존 strict String reader는 유지하고 v2 multipart 경계에서 part-level JSON media type만 강제
- 완료 조건: POST·PUT 정상 JSON 회귀, 미지원/누락 media type의 KO/EN/JA 415 envelope, `Accept` header,
external/S3/DB/event no-side-effect
### `P2-R10` / `P2-R10-GATE` 완료 판정 — 2026-07-29
- RED: focused mutation test에서 새 `text/plain`·content type 누락 POST·PUT 12개 415 기대 case가 수정 전 실패했다.
- GREEN: controller의 part-level JSON compatibility 확인 뒤 기존 String payload를 facade strict reader에 그대로 전달했다.
- GATE: focused mutation test는 `BUILD SUCCESSFUL in 31s`, Character/common 영향 범위 회귀는 `BUILD SUCCESSFUL in 1m 2s`, `ktlintCheck``BUILD SUCCESSFUL in 18s`, `git diff --check`는 출력 없이 성공했다.
- 전체 `./gradlew test`는 controller part 경계와 직접 영향 Character/common 회귀를 실행했으므로 생략했다.
**최종 결론:** `REV-055` 처리 완료, Phase 2 완료.
**다음 Goal:** `P3-R15`.
## 23. 13차 통합 정적 리뷰 및 판정 — 2026-07-29
### 리뷰 범위와 방식
- 기준 문서: PRD Feature B, OpenAPI `CharacterCreateMultipart`·`CharacterUpdateMultipart`
- 검토 범위: Character POST·PUT controller의 multipart binding·part media type 검사와 mutation 테스트
- 검증 방식: 현재 working tree의 문서·코드·테스트를 정적으로 대조했다. 사용자 요청에 따라 컴파일과 테스트는
실행하지 않았다.
### `REV-060` — Medium — 미정의 multipart part를 무시하고 mutation 진행
- OpenAPI의 두 Character multipart schema는 `additionalProperties: false`이고 허용 이름을 `image`, `request`
한정한다.
- controller는 선언된 `@RequestPart`를 binding하고 `request``application/json` 여부만 확인한다.
`MultipartHttpServletRequest`의 전체 part 이름 집합은 검사하지 않는다.
- 따라서 정상 `image`·`request``unexpected` part를 함께 보내도 추가 part는 무시되고 create/update facade가
실행될 수 있다.
- 잘못된 입력이 성공 mutation으로 이어지므로 계약 정합성 문제로 확정하되, 추가 part 자체를 사용하거나 저장하지는
않으므로 심각도는 Medium으로 판정한다.
### plan 전환
| 항목 | 내용 |
|---|---|
| 신규 Task | `Task 2.17` / `P2-R11` |
| Gate | `P2-R11-GATE` |
| RED | POST·PUT의 미정의 file/text part와 외부 API·S3·DB·event 결과 |
| GREEN | 실제 part 이름을 `{image, request}`와 비교해 초과 이름을 공통 400으로 거부 |
| 범위 제한 | OpenAPI·legacy/public·전역 multipart resolver 변경 없음 |
### `P2-R11` / `P2-R11-GATE` 처리 결과 — 2026-07-29
- RED: `AiCharacterAdminCharacterControllerMutationTest`에 Character POST·PUT의 `unexpected` multipart part KO/EN/JA 400/no-side-effect actual endpoint test를 추가했고, 기존 구현은 6개 invocation 모두 400 기대 대비 200으로 실패했다.
- GREEN: `AiCharacterAdminCharacterController`에서 `MultipartHttpServletRequest.fileMap.keys``{image, request}`의 부분집합인지 확인하고 초과 part를 400 `common.error.invalid_request`로 거부했다.
- Gate: undefined part, 기존 request part 415, request part 누락 400 focused 회귀는 `BUILD SUCCESSFUL in 1m 41s`, Character/common 영향 범위 회귀는 `BUILD SUCCESSFUL in 1m 36s`, `ktlintCheck``BUILD SUCCESSFUL in 30s`, `git diff --check`는 출력 없이 성공했다.
**최종 결론:** `REV-060` 처리 완료, Phase 2 13차 리뷰 종결.
**다음 Goal:** `P3-R17`.
## 24. 14차 통합 정적 리뷰 및 판정 — 2026-07-29
### 리뷰 범위와 방식
- 기준 문서: OpenAPI `CharacterCreateMultipart`·`CharacterUpdateMultipart`
`additionalProperties: false`, 허용 part `{image, request}`
- 검토 범위: Character POST·PUT controller의 part allow-list와 미정의 part 회귀 테스트
- 기준 상태: 현재 working tree
- 리뷰어/상태: Codex / 판정 완료
- 검증 방식: 문서·코드·테스트 소스 정적 대조. 사용자 지시에 따라 컴파일과 테스트는 실행하지 않았다.
### `REV-065` — Medium — 일반 form-field multipart part가 allow-list 우회
- `AiCharacterAdminCharacterController.kt:71-74``MultipartHttpServletRequest.fileMap.keys`만 검사한다.
filename 없는 일반 form-field part는 file map 대상이 아니므로 `{image, request}` 외 이름을 검출하지 못한다.
- 로컬 의존성 Spring Web 5.3.29의 `StandardMultipartHttpServletRequest.parseRequest` bytecode도 filename이 있는
part만 multipart file map에 넣고, filename이 없는 part 이름은 별도 parameter 집합에 넣음을 확인했다.
- 기존 `AiCharacterAdminCharacterControllerMutationTest.kt:424-469`는 filename이 있는
`MockMultipartFile("unexpected", ...)`만 사용해 이 경계를 고정하지 않는다.
- 따라서 `additionalProperties: false` 계약을 일반 form-field part가 우회해 facade mutation으로 진행할 수 있다.
입력 자체를 저장하지는 않지만 계약 위반과 부작용 가능성이 있어 Medium으로 판정한다.
### plan 전환
| 항목 | 내용 |
|---|---|
| 신규 Task | `Task 2.18` / `P2-R12` |
| Gate | `P2-R12-GATE` |
| RED | filename 없는 `unexpected` part의 POST·PUT 400/no-side-effect |
| GREEN | servlet 전체 part 이름을 `{image, request}`와 비교 |
| 범위 제한 | Character controller/test만 최소 변경, OpenAPI·전역 resolver·legacy/public 변경 없음 |
**최종 결론:** Phase 2 보완 필요 — `REV-065` 확정
**다음 Goal:** `P2-R12`.
## 24. 8차 후속 수정 및 Gate — 2026-07-29
- 무엇을: `REV-065`의 Character POST·PUT filename 없는 일반 form-field multipart part 우회를 보완했다.
- 왜: 기존 `fileMap.keys` 검사만으로는 `{image, request}` 외 일반 form-field part를 mutation 전 거부하지 못했기 때문이다.
- 어떻게: `AiCharacterAdminCharacterControllerMutationTest``shouldRejectFilenameLessUndefinedMultipartPartBeforeSideEffects` KO/EN/JA POST·PUT actual endpoint 회귀를 추가하고, controller가 `fileMap.keys`와 servlet `parts` 이름을 모두 검사하게 했다.
- 결과: RED 묶음에서 신규 multipart/genre 36건 실패를 확인했고, 보완 후 focused GREEN 묶음은 `BUILD SUCCESSFUL in 1m 17s`였다. 영향 범위 회귀와 lint 결과는 `P7-R10-GATE`에 통합 기록한다.
**최종 결론:** `REV-065` 처리 완료. Phase 2 후속 Gate 완료.
**남은 항목:** 없음.
## 25. 15차 정적 리뷰 및 판정 — 2026-07-29
### 리뷰 범위와 방식
- 기준 문서: PRD Feature C, OpenAPI Character 5개 operation
- 검토 범위: 목록·원작 검색·상세·생성·수정, target/owner, multipart·strict JSON, external/S3/event 순서
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과
테스트는 실행하지 않았다.
### 판정
- Character 5개 OpenAPI operation과 controller mapping, target/owner 선검증, strict multipart/JSON 경계를 대조했다.
- 기존 완료 finding 이후 새 계약 불일치나 확정 가능한 production 결함은 확인되지 않았다.
- Phase 3의 `REV-072`는 Character 경로에 영향을 주지 않는다.
### plan 전환
- 신규 Phase 2 finding 및 Task/Gate 없음.
**최종 결론:** Phase 2 추가 수정 없음.
**남은 항목:** Phase 3 보완 뒤 `P7-R11` 통합 재판정.
## 26. 16차 정적 리뷰 및 판정 — 2026-07-30
### 리뷰 범위와 방식
- 기준 문서: PRD Feature B, OpenAPI Character 5개 operation
- 검토 범위: 목록·원작 검색·상세·생성·수정, AI target, strict multipart/JSON, external API·S3·DB·event 경계
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과
테스트는 실행하지 않았다.
### 판정
- Character 5개 operation과 controller mapping, active AI 목록 필터와 target 없는 원작 검색 계약이 일치한다.
- 생성·수정은 exact multipart part와 JSON media type, 필수/nullable·미지 필드, 원작·타입·중복 선검증을 유지한다.
- external API, S3, DB와 event의 기존 호출·rollback/비보상 경계가 계획의 특성화 결과와 일치한다.
- 신규 확정 finding이 없어 Phase 2 회귀 수정 Task/Gate를 추가하지 않는다.
**최종 결론:** Phase 2 요구사항 충족, 추가 수정 없음.
**남은 항목:** 없음.