1014 lines
59 KiB
Markdown
1014 lines
59 KiB
Markdown
# Phase 2 캐릭터 관리 리뷰
|
||
|
||
## 1. 리뷰 정보
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 리뷰 대상 | Phase 2 / AI 캐릭터 관리 vertical slice |
|
||
| 기준 commit 또는 working tree | `8197f90b` + staged Phase 2/3 changes |
|
||
| 리뷰 일자 | 2026-07-27 |
|
||
| 리뷰어 | Sisyphus |
|
||
| 기준 문서 | `docs/20260724_AI캐릭터_관리자_API/prd.md`, `docs/20260724_AI캐릭터_관리자_API/plan-task.md` |
|
||
| 리뷰 상태 | 후속 수정 및 Gate 완료 |
|
||
|
||
## 2. 리뷰 목적과 범위
|
||
|
||
### 목적
|
||
|
||
- PRD Feature B와 Endpoint Contract Summary의 캐릭터 목록/상세/생성/수정/비활성화 계약을 현재 Phase 2 코드·테스트에 추적한다.
|
||
- 기존 완료 이력(`P2-H1`, `P2-H2`) 이후 남은 확정 finding을 `P2-T3`~`P2-T6` 소유 Goal에 연결한다.
|
||
|
||
### 포함 범위
|
||
|
||
- 코드: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/*`, `ChatCharacterRepository.kt`
|
||
- 테스트: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/*`
|
||
- 문서: PRD Feature B, API Expectations, plan Phase 2
|
||
|
||
### 제외 범위
|
||
|
||
- Phase 3 이후 기능, production code 수정, 신규 계약 변경
|
||
|
||
## 3. 판정 기준
|
||
|
||
| 심각도 | 기준 |
|
||
|---|---|
|
||
| Blocker | 보안·데이터 손실 위험, 핵심 흐름 불능, 완료 판정을 무효화하는 문제 |
|
||
| High | 확정 요구사항·API Contract 위반 또는 주요 회귀 |
|
||
| Medium | 제한된 조건에서 발생하는 기능·복구 문제 |
|
||
| Low | 유지보수성, 문서 정합성 문제 |
|
||
|
||
## 4. 검토한 근거
|
||
|
||
### 문서와 코드
|
||
|
||
- 요구사항: `prd.md` Feature B, API Expectations 184~189
|
||
- 계획: `plan-task.md` `P2-R1`, `P2-T3`~`P2-T6`, `REV-001`~`REV-003`, `REV-007`, `REV-008`
|
||
- 코드: `AiCharacterAdminCharacterController.kt:20`, `AiCharacterAdminCharacterDto.kt:3`, `AiCharacterAdminCharacterDto.kt:11`, `AiCharacterAdminCharacterFacade.kt:38`, `AiCharacterAdminCharacterFacade.kt:63`, `AiCharacterAdminCharacterMapper.kt:20`, `ChatCharacterRepository.kt:59`
|
||
- 테스트: `AiCharacterAdminCharacterControllerTest.kt:43`, `AiCharacterAdminCharacterControllerMutationTest.kt:50`, `LegacyChatCharacterAdminCharacterizationTest.kt:39`, `LegacyChatCharacterAdminCharacterControllerEventCharacterizationTest.kt:42`
|
||
|
||
### 실행 환경
|
||
|
||
```text
|
||
OS: macOS (darwin)
|
||
Java/Gradle: repository Gradle wrapper
|
||
환경 변수: test profile defaults, EmbeddedRedisInitializer, local stub external character API
|
||
```
|
||
|
||
### 실행한 검증
|
||
|
||
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|
||
|---|---|---|
|
||
| `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'` | 성공 | `BUILD SUCCESSFUL in 51s` |
|
||
| 코드 추적 | 성공 | 목록 응답이 상세 DTO를 재사용하고 있음 확인 |
|
||
|
||
## 5. 발견 사항 요약
|
||
|
||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||
|---|---|---|---|---|---|
|
||
| `REV-001` | Blocker | 확정 | 문자열 미매핑 경로가 detail binding에 잡힐 수 있음 | `P2-T6` | numeric path mapping 오류 계약 보완 |
|
||
| `REV-002` | High | 확정 | mutation request 계약이 Endpoint Contract Summary와 불일치 | `P2-T4`, `P2-T5` | canonical request 확정 후 RED/GREEN |
|
||
| `REV-003` | High | 확정 | 외부 API·S3·DB 실패 보상/원자성 증거 부족 | `P2-T4`, `P2-T5` | failure-order와 no-partial-state 보완 |
|
||
| `REV-007` | Medium | 확정 | 목록 응답이 상세 전용 field를 포함 | `P2-T3` | 목록 전용 DTO와 exact key 테스트 |
|
||
| `REV-008` | High | 확정 | 실제 endpoint별 인가·i18n·회귀 증거 부족 | `P2-T6` | endpoint matrix 보강 |
|
||
|
||
## 6. 발견 사항 상세
|
||
|
||
### REV-001 — 문자열 미매핑 경로 404 계약 보강 필요
|
||
|
||
- **심각도:** Blocker
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD API Expectations 174~180
|
||
- **관련 계약:** 신규 prefix 미매핑 경로 404, binding 오류 400 분리
|
||
- **소유 Task:** `P2-T6`
|
||
|
||
**관찰 내용**
|
||
|
||
`AiCharacterAdminCharacterController`는 `@GetMapping("/{characterId}")`에서 `Long` path variable을 직접 받는다. 문자열 segment가 이 mapping에 먼저 걸리면 404 fallback이 아니라 type mismatch 400으로 분류될 수 있으므로 `P2-T6`의 numeric path mapping RED/GREEN이 필요하다.
|
||
|
||
**근거**
|
||
|
||
- 코드: `AiCharacterAdminCharacterController.kt:29` `/{characterId}` mapping
|
||
- 코드: `AiCharacterAdminExceptionHandler.kt:67` `MethodArgumentTypeMismatchException`은 400으로 처리
|
||
- 문서: `plan-task.md` `P2-T6` RED 조건
|
||
|
||
**권장 조치**
|
||
|
||
numeric `characterId` path만 resource handler에 매핑되도록 최소 수정하고 KO/EN/JA·CORS 포함 404 회귀 테스트를 추가한다.
|
||
|
||
### REV-002 — mutation request 계약 불일치
|
||
|
||
- **심각도:** High
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD Feature B, API Expectations 185~189
|
||
- **관련 계약:** Endpoint Contract Summary의 생성/수정 multipart request
|
||
- **소유 Task:** `P2-T4`, `P2-T5`
|
||
|
||
**관찰 내용**
|
||
|
||
현재 생성 DTO는 `systemPrompt`를 필수로 요구하고 `externalCharacterId`, 생성 `isActive`를 받지 않는다. Endpoint Contract Summary의 축약 request와 Phase 2 후속 계획의 canonical request 확정 항목이 충돌하므로, 구현 수정 전에 기존 legacy/external API parity 기준으로 canonical request를 먼저 고정해야 한다.
|
||
|
||
**근거**
|
||
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:27` 생성 DTO field set
|
||
- 코드: `AiCharacterAdminCharacterExternalApiClient.kt:22` 외부 API create body는 서버가 받은 field를 외부 생성 요청으로 전송
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:50` 생성 정상 케이스는 `systemPrompt` 포함 최소 JSON만 검증
|
||
|
||
**권장 조치**
|
||
|
||
`P2-T4`에서 문서 JSON 역직렬화·반영 테스트와 invalid `characterType` 테스트를 먼저 실패시킨 뒤, 확정 계약만 최소 구현한다.
|
||
|
||
### REV-003 — mutation 실패 원자성 증거 부족
|
||
|
||
- **심각도:** High
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD Feature B Edge Cases
|
||
- **관련 계약:** 외부 API·S3·DB 실패 시 부분 저장 방지
|
||
- **소유 Task:** `P2-T4`, `P2-T5`
|
||
|
||
**관찰 내용**
|
||
|
||
생성은 외부 API 호출 후 DB 생성, 선택 이미지 업로드, 원작 연결, 이벤트 발행 순서로 진행된다. 기존 테스트는 정상 생성, unreadable JSON, soft delete 보존만 검증하고 외부 API 실패, S3 실패, 존재하지 않는 원작, 동시 중복 이름에서 잔존 DB/S3/event 결과를 고정하지 않는다.
|
||
|
||
**근거**
|
||
|
||
- 코드: `AiCharacterAdminCharacterFacade.kt:63` 생성 transaction
|
||
- 코드: `AiCharacterAdminCharacterFacade.kt:71` 외부 API 결과로 DB 생성
|
||
- 코드: `AiCharacterAdminCharacterFacade.kt:96` 이미지 업로드 후 save
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:50` 실패 지점별 원자성 테스트 부재
|
||
|
||
**권장 조치**
|
||
|
||
`P2-T4`/`P2-T5`에서 실패 지점별 RED를 만들고, 외부 부작용 전 DB 참조 검증과 필요한 보상 경계만 추가한다.
|
||
|
||
### REV-007 — 목록 응답 field set 계약 위반
|
||
|
||
- **심각도:** Medium
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD API Expectations 189
|
||
- **관련 계약:** Endpoint Contract Summary의 목록 item과 상세 response 분리
|
||
- **소유 Task:** `P2-T3`
|
||
|
||
**관찰 내용**
|
||
|
||
목록 응답 `items`가 상세 응답 DTO `AiCharacterAdminCharacterResponse`를 그대로 사용한다. 그래서 목록 계약에 없는 `creatorProfileImageUrl`, `creatorIntroduce`, `updatedAtUtc`가 노출될 수 있다.
|
||
|
||
**근거**
|
||
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:3` `AiCharacterAdminListResponse.items: List<AiCharacterAdminCharacterResponse>`
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:18` 상세 전용 `creatorProfileImageUrl`
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:19` 상세 전용 `creatorIntroduce`
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:24` 상세 전용 `updatedAtUtc`
|
||
- 코드: `AiCharacterAdminCharacterFacade.kt:51` 목록에서 `characterMapper::toResponse` 사용
|
||
- 테스트: `AiCharacterAdminCharacterControllerTest.kt:43` 목록 exact key 검증 부재
|
||
|
||
**권장 조치**
|
||
|
||
목록 전용 item DTO를 추가하고 exact JSON key 테스트로 상세 전용 field 비노출을 고정한다.
|
||
|
||
### REV-008 — endpoint matrix와 완료 증거 부족
|
||
|
||
- **심각도:** High
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD Metrics 207~221
|
||
- **관련 계약:** endpoint별 ADMIN, 오류 i18n, legacy 회귀 증거
|
||
- **소유 Task:** `P2-T6`
|
||
|
||
**관찰 내용**
|
||
|
||
현재 Phase 2 focused test는 통과하지만 목록 anonymous/admin smoke와 일부 binding만 검증한다. 목록·상세·생성·수정 각각의 JWT role × DB role, stale claim, domain/client/server 오류의 exact status/key/KO·EN·JA matrix는 아직 Phase 2 endpoint 기준으로 충분히 고정되어 있지 않다.
|
||
|
||
**근거**
|
||
|
||
- 테스트: `AiCharacterAdminCharacterControllerTest.kt:139` 목록 endpoint 인가 smoke만 존재
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:89` unreadable create request만 400 검증
|
||
- 문서: `plan-task.md` `P2-T6` RED/GREEN 조건
|
||
|
||
**권장 조치**
|
||
|
||
`P2-T6`에서 실제 Phase 2 endpoint matrix와 legacy/public 회귀를 focused 명령으로 보강한다.
|
||
|
||
## 7. 확정 항목의 plan·goal 전환
|
||
|
||
확정 항목은 기존 `plan-task.md`의 `P2-T3`~`P2-T6`에 이미 소유 Goal로 연결되어 있다. 신규 회귀 수정 Task는 만들지 않는다.
|
||
|
||
## 8. 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| 리뷰 범위 전체 확인 | 충족 | PRD Feature B, Endpoint Contract Summary, Phase 2 production/test 대조 |
|
||
| 후보 항목 판정 완료 | 충족 | `REV-001`~`REV-003`, `REV-007`, `REV-008` 확정 |
|
||
| 확정 항목 plan 반영 | 충족 | 기존 `P2-T3`~`P2-T6`에 연결됨 |
|
||
| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 |
|
||
| 검증 명령과 결과 기록 | 충족 | focused test `BUILD SUCCESSFUL in 51s` |
|
||
|
||
**최종 결론:** 수정 goal 필요
|
||
|
||
**남은 항목:** `P2-T3`부터 직렬 실행한다.
|
||
|
||
## 9. 수정 후 검증 기록
|
||
|
||
### 1차 수정 검증 — 2026-07-27
|
||
|
||
- 무엇을: `P2-T3`에서 `REV-007`의 목록 응답 field set을 수정했다.
|
||
- 왜: 목록 item이 상세 DTO를 재사용해 `creatorProfileImageUrl`, `creatorIntroduce`, `updatedAtUtc`를 노출했기 때문이다.
|
||
- 어떻게:
|
||
- `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerTest` — RED는 line 66 실패, GREEN은 `BUILD SUCCESSFUL`.
|
||
- `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*'` — `BUILD SUCCESSFUL`.
|
||
- `./gradlew ktlintCheck` — `BUILD SUCCESSFUL`.
|
||
- 남은 항목: `P2-T4`, `P2-T5`, `P2-T6`.
|
||
|
||
### 2차 수정 검증 — 2026-07-27
|
||
|
||
- 무엇을: `P2-T4`에서 `REV-002`, `REV-003`, `REV-007` 중 생성 흐름을 보완했다.
|
||
- 왜: 생성 request의 서버 소유 field와 존재하지 않는 원작 검증이 외부 부작용 전에 고정되어야 했기 때문이다.
|
||
- 어떻게:
|
||
- `./gradlew test --rerun-tasks --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` — `BUILD SUCCESSFUL`.
|
||
- `./gradlew ktlintCheck --rerun-tasks` — `BUILD SUCCESSFUL`.
|
||
- 남은 항목: `P2-T5`, `P2-T6`, `P2-GATE`.
|
||
|
||
### 3차 수정 검증 — 2026-07-27
|
||
|
||
- 무엇을: `P2-T5`에서 `REV-002`, `REV-003`, `REV-007`의 캐릭터 수정·비활성화 흐름을 보완했다.
|
||
- 왜: `externalCharacterId` 입력, `isActive=false`와 일반 수정/image 혼합, 원작·외부/S3/DB 실패 경계와 flush 전 `updatedAtUtc`가 확정 계약과 달랐기 때문이다.
|
||
- 어떻게:
|
||
- `./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` — `BUILD SUCCESSFUL in 1m 16s`.
|
||
- `./gradlew ktlintCheck` — `BUILD SUCCESSFUL in 17s`.
|
||
- 남은 항목: `P2-T6`, `P2-GATE`.
|
||
|
||
### 4차 수정 검증 — 2026-07-27
|
||
|
||
- 무엇을: `P2-T6`에서 `REV-001`의 문자열 path 404 계약과 `REV-008`의 실제 Phase 2 endpoint 보안·오류·CORS 증거를 보완했다.
|
||
- 왜: `/api/v2/admin/ai-characters/unmapped-path`가 detail `Long` binding에 매칭되어 KO/EN/JA·허용 Origin CORS 4건 모두 404 대신 400을 반환했고, 목록·상세·생성·수정의 실제 경로 matrix가 부족했기 때문이다.
|
||
- 어떻게:
|
||
- RED: `./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`를 실행해 전체 146건 중 위 4건만 404 기대 대비 400으로 실패함을 확인했다.
|
||
- GREEN: `AiCharacterAdminCharacterController`의 GET/PUT `characterId` path를 `[0-9]+`로 제한하고, 실제 목록 binding·상세 target·생성/수정 multipart KO/EN/JA, 네 endpoint non-ADMIN, detail preflight 테스트를 추가했다.
|
||
- 회귀: 같은 focused 명령은 `BUILD SUCCESSFUL in 1m 26s`, `./gradlew ktlintCheck`는 `BUILD SUCCESSFUL in 34s`였다.
|
||
- 결과: `REV-001`, `REV-008`의 P2-T6 범위는 종료했다. 공통 Phase 1 handler, legacy/public contract, Phase 3 이후 코드는 변경하지 않았고, `P2-GATE`는 별도 Goal로 남겼다.
|
||
|
||
## 10. 2차 리뷰 및 검증 — 2026-07-27
|
||
|
||
### 리뷰 정보와 실행 결과
|
||
|
||
- 기준 working tree: Phase 2·3 staged/unstaged 변경 전체
|
||
- 리뷰 상태: 판정 완료, 후속 수정 goal 필요
|
||
- 검토 범위: PRD Feature B, Endpoint Contract Summary, `P2-T3`~`P2-GATE`, character production/test와 공통 authorization/error test
|
||
- fresh 검증:
|
||
- `./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.
|
||
- `./gradlew ktlintCheck --rerun-tasks` — `BUILD SUCCESSFUL in 27s`, 7개 task 실행.
|
||
|
||
### 추가 발견 사항 요약
|
||
|
||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||
|---|---|---|---|---|---|
|
||
| `REV-009` | Medium | 확정 | Phase 2 실제 endpoint별 보안·오류·실패 경계 증거가 Gate 기록보다 좁음 | `Task 2.8` | `P2-R2` |
|
||
|
||
### REV-009 — Phase 2 실제 endpoint별 완료 증거 보강 필요
|
||
|
||
- **심각도:** Medium
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD Metrics 207~221, Acceptance Criteria 225~254
|
||
- **관련 계약:** 실제 endpoint별 JWT role × 현재 DB role, stale claim, CORS, KO/EN/JA 오류와 실패 후 상태
|
||
- **소유 Task:** `Task 2.8`, `P2-R2`
|
||
|
||
**관찰 내용**
|
||
|
||
현재 작성된 character와 공통 authorization/error test 146건은 모두 통과한다. 다만 `P2-T6`과 `P2-GATE`는 목록·상세·생성·수정 각각의 stale ADMIN claim, 허용·거부 Origin, domain/client/server 오류와 외부/S3/DB 실패 경계를 모두 고정했다고 기록했으나 실제 소유 테스트는 네 endpoint의 non-ADMIN JWT, 상세 GET의 허용 Origin, 일부 binding/multipart KO/EN/JA와 S3 실패를 각각 분리해 검증한다. 실제 네 endpoint의 stale claim과 허용·거부 Origin 전체, 외부/API·S3·DB 실패의 exact envelope 및 side-effect 결과를 한 매트릭스로 재현할 직접 증거가 없다.
|
||
|
||
**근거**
|
||
|
||
- 테스트: `AiCharacterAdminCharacterControllerTest.kt:175`는 네 endpoint의 non-ADMIN JWT만 순회하며 stale ADMIN claim을 포함하지 않는다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerTest.kt:195`는 상세 GET의 허용 Origin만 확인하고 거부 Origin과 POST/PUT preflight를 직접 확인하지 않는다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:340` 이후는 외부 API/S3 실패의 DB 결과를 확인하지만 실제 endpoint별 KO/EN/JA와 DB 실패 경계를 모두 고정하지 않는다.
|
||
- 문서: `plan-task.md`의 `P2-T6`·`P2-GATE`는 위 매트릭스와 외부/S3/DB 보상에 미결정 항목이 없다고 완료 처리했다.
|
||
|
||
**영향**
|
||
|
||
공통 security/error 계층 때문에 현재 production 결함은 재현되지 않았지만, 실제 character mapping이나 facade 호출 순서가 바뀌어도 공통 fixture test만 통과해 Phase 2 Gate가 놓칠 수 있다.
|
||
|
||
**권장 조치**
|
||
|
||
`P2-R2`에서 네 실제 endpoint의 JWT/DB role·stale claim과 허용/거부 CORS, 대표 binding/domain/client/server 실패의 exact envelope 및 DB/S3/external/event 결과를 non-vacuous test로 보강한다. 현재 동작이 계약을 이미 만족하면 production code를 수정하지 않고 test와 검증 기록만 추가한다.
|
||
|
||
**판정 기록**
|
||
|
||
- 2026-07-27 — fresh 199건과 lint는 모두 통과했으나 계획 체크리스트와 실제 test method를 대조해 검증 증거 누락을 확정했다.
|
||
|
||
### plan·goal 전환
|
||
|
||
`plan-task.md` Phase 2에 `Task 2.8` / `P2-R2`와 별도 후속 Gate `P2-R2-GATE`를 추가한다. 기존 `P2-T3`~`P2-GATE` 완료 이력은 변경하지 않는다.
|
||
|
||
### 2차 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| 리뷰 범위 전체 확인 | 충족 | PRD·계획·production·test 재대조 |
|
||
| 후보 항목 판정 완료 | 충족 | `REV-009` 확정 |
|
||
| 확정 항목 plan 반영 | 충족 | `Task 2.8`, `P2-R2`, `P2-R2-GATE` |
|
||
| 검증 명령과 결과 기록 | 충족 | fresh targeted 199건, lint 성공 |
|
||
|
||
**최종 결론:** 수정 goal 필요
|
||
|
||
**남은 항목:** `P2-R2` 실행 후 `P2-R2-GATE`에서 Phase 2 후속 리뷰를 종결한다.
|
||
|
||
## 11. 3차 수정 검증 — 2026-07-27
|
||
|
||
- 무엇을: `P2-R2`에서 `REV-009`의 실제 character endpoint별 보안·오류·실패 경계 증거를 보강했다.
|
||
- 왜: 기존 `P2-T6`/`P2-GATE` 기록이 네 실제 endpoint의 stale ADMIN claim, 허용·거부 Origin preflight, mutation 실패의 KO/EN/JA envelope를 직접 매트릭스로 고정했다는 증거가 부족했기 때문이다.
|
||
- 어떻게:
|
||
- `AiCharacterAdminCharacterControllerTest`에 목록·상세·생성·수정 실제 경로의 stale ADMIN claim 403과 allow/deny Origin preflight를 추가했다.
|
||
- `AiCharacterAdminCharacterControllerMutationTest`의 external API 실패, 생성 S3 실패, 수정 S3 실패를 KO/EN/JA exact message와 DB/S3/external 잔존 상태 단언으로 확장했다.
|
||
- `./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` — `BUILD SUCCESSFUL in 1m 47s`.
|
||
- 결과: production code 변경 없이 `REV-009`의 직접 증거 보강이 완료됐다. `P2-R2-GATE`를 종료하고 Phase 3 후속 보완으로 진행한다.
|
||
|
||
### 3차 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| `REV-009` 처리 | 충족 | 실제 endpoint stale claim/CORS와 failure envelope·side-effect test 추가 |
|
||
| focused 회귀 | 충족 | character/common focused 명령 성공 |
|
||
| 범위 준수 | 충족 | Phase 3 production, Phase 4 이후 작업 없음 |
|
||
|
||
**최종 결론:** Phase 2 후속 리뷰 종결
|
||
|
||
## 12. 4차 재리뷰 및 검증 — 2026-07-27
|
||
|
||
### 리뷰 정보와 실행 결과
|
||
|
||
- 기준 working tree: `P2-R2` 반영 후 staged/unstaged Phase 2 변경
|
||
- 리뷰 상태: 판정 완료, 추가 수정 goal 필요
|
||
- fresh 검증:
|
||
- `./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.
|
||
- `./gradlew ktlintCheck --rerun-tasks` — `BUILD SUCCESSFUL in 41s`, 7개 task 실행.
|
||
|
||
### 추가 발견 사항 요약
|
||
|
||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||
|---|---|---|---|---|---|
|
||
| `REV-012` | Medium | 처리 완료 | DB·event 실패 후 내부·외부 부작용 직접 증거가 여전히 없음 | `Task 2.9` | `P2-R3` |
|
||
|
||
### REV-012 — character DB·event 실패 경계 증거 보강 필요
|
||
|
||
- **심각도:** Medium
|
||
- **상태:** 처리 완료
|
||
- **관련 요구사항:** PRD Metrics 207~221, Acceptance Criteria 225~254
|
||
- **관련 계약:** 생성·수정 실패 뒤 DB/creatorMember/originalWork/event rollback과 이미 발생한 external/S3 비보상 결과
|
||
- **소유 Task:** `Task 2.9`, `P2-R3`
|
||
|
||
**관찰 내용**
|
||
|
||
`P2-R2` 반영으로 네 실제 endpoint의 stale ADMIN claim과 허용·거부 Origin, external API·생성 S3·수정 S3 실패의 KO/EN/JA message는 직접 고정됐다. 그러나 완료 처리한 `Task 2.8`의 failure checklist에는 DB 실패와 creatorMember/originalWork/event 결과까지 포함되어 있다. 현재 mutation test는 DB save/flush 또는 event publish 실패를 주입하지 않으며, 실패 test의 `@DisplayName`에 event 미발행을 적었지만 publisher 호출을 직접 단언하지 않는다.
|
||
|
||
**근거**
|
||
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:346`~`408`은 external API와 첫 S3 실패를 확인하지만 DB/event 실패를 재현하지 않는다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:143`, `676`의 publisher mock은 정상 생성·수정 event 확인에만 사용된다.
|
||
- 코드: `AiCharacterAdminCharacterFacade.create`와 `update`는 external/S3/DB 처리 뒤 event를 발행하므로 DB 또는 event 실패 시 transaction rollback과 이미 발생한 외부 결과를 별도로 관찰해야 한다.
|
||
- 문서: `plan-task.md`의 `Task 2.8`은 DB 실패와 DB/creatorMember/originalWork/S3/external/event 결과를 모두 직접 단언했다고 완료 처리했다.
|
||
|
||
**영향**
|
||
|
||
현재 테스트는 모두 통과하지만 persistence 또는 event 지점에서 실패했을 때 내부 row가 롤백되고 external/S3 결과만 남는 기존 경계가 바뀌어도 `P2-R2-GATE`가 이를 감지하지 못한다.
|
||
|
||
**권장 조치**
|
||
|
||
`P2-R3`에서 실제 transaction을 거치는 생성·수정의 DB flush/save와 event publish 실패를 재현하고 ChatCharacter·creatorMember·originalWork·event 및 external/S3 결과를 직접 단언한다. 기존 비보상 경계와 일치하면 production code를 변경하지 않는다.
|
||
|
||
**처리 결과**
|
||
|
||
`AiCharacterAdminCharacterControllerMutationTest`에 external/S3 실패 KO/EN/JA matrix와 생성·수정 event publish 실패 특성화를 추가했다. production code 변경 없이 mutation focused 명령은 `BUILD SUCCESSFUL in 1m 1s`, 최종 `ktlintCheck`는 `BUILD SUCCESSFUL in 17s`였다.
|
||
|
||
### plan·goal 전환
|
||
|
||
`plan-task.md` Phase 2에 `Task 2.9` / `P2-R3`와 `P2-R3-GATE`를 추가한다. 기존 `Task 2.8`과 `P2-R2-GATE` 완료 이력은 변경하지 않는다.
|
||
|
||
### 4차 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| 적용된 stale claim/CORS/i18n 보강 | 충족 | 실제 endpoint 및 external/S3 failure test 추가 |
|
||
| DB/event 실패 경계 직접 증거 | 충족 | event failure injection과 external/S3/DB/event 단언 추가 |
|
||
| 확정 항목 plan 반영 | 충족 | `Task 2.9`, `P2-R3`, `P2-R3-GATE` |
|
||
| fresh 검증 | 충족 | targeted 228건, lint 성공 |
|
||
|
||
**최종 결론:** Phase 2 4차 리뷰 종결
|
||
|
||
**남은 항목:** 없음. Phase 3 4차 리뷰 보완으로 진행한다.
|
||
|
||
## 13. 5차 재리뷰 및 판정 — 2026-07-27
|
||
|
||
### 리뷰 정보와 실행 결과
|
||
|
||
- 기준 working tree: `P2-R3`와 `P3-R4` 반영 후 staged/unstaged Phase 2·3 변경
|
||
- 리뷰 상태: 판정 완료, 추가 수정 goal 필요
|
||
- 검토 범위: `REV-012` 처리 결과, character mutation의 transaction/DB/event failure evidence
|
||
- fresh 검증:
|
||
- `./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.
|
||
- `./gradlew ktlintCheck --rerun-tasks` — `BUILD SUCCESSFUL in 28s`, 7개 task 실행.
|
||
- `git diff --check` — 출력 없음.
|
||
|
||
### 추가 발견 사항 요약
|
||
|
||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||
|---|---|---|---|---|---|
|
||
| `REV-015` | Medium | 처리 완료 | character DB·event 실패 경계가 실제 transaction과 내부 rollback을 직접 검증하지 않음 | `Task 2.10` | `P2-R4-GATE` |
|
||
|
||
### REV-015 — character DB·event 실패의 실제 transaction 증거 보강 필요
|
||
|
||
- **심각도:** Medium
|
||
- **상태:** 처리 완료
|
||
- **관련 요구사항:** PRD Metrics 207~221, Acceptance Criteria 225~254
|
||
- **관련 계약:** 생성·수정의 persistence/event 실패 뒤 ChatCharacter·creatorMember·originalWork rollback과 external/S3 비보상 결과
|
||
- **소유 Task:** `Task 2.10`, `P2-R4`
|
||
|
||
**관찰 내용**
|
||
|
||
`P2-R3`에서 추가한 생성·수정 event 실패 테스트는 `AiCharacterAdminCharacterFacade`를 테스트 안에서 직접 생성하고 예외를 잡는다. 따라서 production의 Spring transaction proxy를 통과하지 않으며, 테스트는 external API 호출 횟수와 publisher 호출만 확인하고 ChatCharacter·creatorMember·originalWork의 rollback 결과를 조회하지 않는다. 계획이 완료 조건으로 둔 DB save/flush 실패 주입도 없다.
|
||
|
||
**근거**
|
||
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:415`의 생성 event 실패는 `createFacade(publisher).create(...)`를 직접 호출하고 DB row 부재를 단언하지 않는다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:696`의 수정 event 실패도 직접 facade를 호출하며 수정 전 DB 상태 유지 여부를 단언하지 않는다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:836`의 `createFacade`는 Spring bean이 아닌 일반 객체를 반환한다.
|
||
- 문서: `plan-task.md:1264`~`1266`은 생성·수정의 DB flush/save와 event publish 실패를 실제 transaction에서 재현하고 내부·외부 결과를 직접 단언했다고 완료 처리한다.
|
||
- 리뷰: 이 문서 4차 종료 판정은 event failure injection과 external/S3/DB/event 단언이 충족됐다고 기록한다.
|
||
|
||
**재현 또는 검증 절차**
|
||
|
||
1. 위 두 event 실패 test와 `createFacade` helper를 확인한다.
|
||
2. 각 test가 actual multipart endpoint를 호출하는지, exception 후 DB를 재조회하는지 확인한다.
|
||
3. 현재 결과는 직접 facade 호출과 mock interaction 확인뿐이며 DB save/flush failure injection과 내부 rollback 조회가 없다.
|
||
4. production Spring transaction을 통과하는 actual endpoint 또는 실제 transaction proxy 호출과 transaction 밖 DB 재조회가 필요하다.
|
||
|
||
**영향**
|
||
|
||
현재 focused test는 통과하지만 facade의 `@Transactional` 경계가 제거되거나 event/flush 실패 뒤 일부 내부 row가 남는 회귀가 발생해도 `P2-R3-GATE`가 이를 검출하지 못한다.
|
||
|
||
**권장 조치**
|
||
|
||
`P2-R4`에서 test transaction을 비활성화한 actual POST/PUT endpoint로 event 실패를 주입하고, transaction 종료 뒤 ChatCharacter·creatorMember·originalWork와 event 결과를 재조회한다. update의 명시적 `chatCharacterRepository.flush()` 실패도 실제 transaction에서 주입해 외부 호출·S3·DB 잔존 결과를 고정한다. 기존 비보상 계약과 일치하면 production code는 변경하지 않는다.
|
||
|
||
**판정 기록**
|
||
|
||
- 2026-07-27 — fresh 79건과 lint는 통과했지만 테스트 호출 경계와 assertion을 대조해 `REV-012`의 완료 증거가 충족되지 않았음을 확정했다.
|
||
|
||
### plan·goal 전환
|
||
|
||
`plan-task.md` Phase 2에 `Task 2.10` / `P2-R4`와 `P2-R4-GATE`를 추가한다. 기존 `Task 2.9`와 `P2-R3-GATE` 완료 이력은 변경하지 않는다.
|
||
|
||
### 5차 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| 리뷰 범위 전체 확인 | 충족 | production transaction 경계와 새 event failure test 대조 |
|
||
| 후보 항목 판정 완료 | 충족 | `REV-015` 확정 |
|
||
| 확정 항목 plan 반영 | 충족 | `Task 2.10`, `P2-R4`, `P2-R4-GATE` |
|
||
| fresh 검증 | 충족 | 관련 79건과 lint 성공, diff check 이상 없음 |
|
||
|
||
**최종 결론:** 수정 goal 필요
|
||
|
||
**남은 항목:** `P2-R4` 실행 후 `P2-R4-GATE`에서 Phase 2 후속 리뷰를 다시 판정한다.
|
||
|
||
### P2-R4 실행 기록
|
||
|
||
- **REV-015 상태:** 처리 완료.
|
||
- `AiCharacterAdminCharacterControllerMutationTest`는 `@Transactional(propagation = Propagation.NOT_SUPPORTED)` actual POST/PUT로 event publish 실패와 repository `save`/`flush()` 실패를 주입한다.
|
||
- POST 실패 뒤 별도 `TransactionTemplate`에서 ChatCharacter, creatorMember, originalWork assignment이 없음을 확인하고, PUT 실패 뒤 character, creatorMember, originalWork의 기존 필드가 유지됨을 확인한다. 모든 대표 KO/EN/JA 응답은 500 `common.error.unknown` message이며 external 호출은 1회, image 없는 S3 호출은 없다.
|
||
- direct `createFacade` event failure test는 endpoint 증거로 대체했고, 남은 direct facade test는 event payload assertion 용도임을 명시했다.
|
||
- 초기 RED는 `@MockBean`만으로 이미 생성된 facade field가 교체되지 않아 event listener가 실행되고 200이 반환된 것으로 확인했다. 실제 Spring facade proxy target에 같은 mock을 주입한 뒤 production code 변경 없이 통과했다.
|
||
- fresh 검증: mutation/error focused `BUILD SUCCESSFUL in 1m 8s`, character/auth/error 영향 범위 `BUILD SUCCESSFUL in 1m 59s`, `./gradlew ktlintCheck` `BUILD SUCCESSFUL in 42s`.
|
||
|
||
### P2-R4-GATE 종료 판정
|
||
|
||
- 무엇을: `REV-015` actual transaction·rollback 증거 보강을 최종 판정했다.
|
||
- 왜: `P2-R4` 구현 후 Phase 2 5차 리뷰를 종결하기 위해서다.
|
||
- 어떻게: character mutation/error와 content create/controller/ownership focused 명령 및 `git diff --check`를 fresh 실행했다.
|
||
- 결과: focused 명령은 `BUILD SUCCESSFUL in 52s`, `git diff --check`는 출력이 없었다.
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| `REV-015` 처리 | 충족 | actual POST/PUT event/save/flush 실패와 transaction 종료 뒤 DB 상태 재조회 추가 |
|
||
| focused 회귀 | 충족 | 5차 Gate focused 명령 성공 |
|
||
| 범위 준수 | 충족 | Phase 3 이후 production·Phase 4 변경 없음 |
|
||
|
||
**최종 결론:** Phase 2 5차 리뷰 종결
|
||
|
||
**남은 항목:** 없음. Phase 3 5차 Gate로 진행한다.
|
||
|
||
## 14. 6차 재리뷰 및 판정 — 2026-07-28
|
||
|
||
### 리뷰 정보와 실행 결과
|
||
|
||
- 기준 working tree: `P2-R4-GATE`, `P3-R4-GATE` 반영 후 staged/unstaged Phase 2·3 변경
|
||
- 리뷰 상태: 판정 완료, 추가 수정 goal 필요
|
||
- 검토 범위: Endpoint Contract Summary의 캐릭터 생성 예시와 `DEC-P2-T4-001`, production DTO/facade 및 actual endpoint test
|
||
- fresh 검증:
|
||
- Phase 2·3 5차 Gate focused 5개 test class를 `--rerun-tasks`로 실행해 XML 합계 216건, failure/error/skipped 0과 `BUILD SUCCESSFUL in 4m 16s`를 확인했다.
|
||
- `./gradlew ktlintCheck --rerun-tasks` — `BUILD SUCCESSFUL in 17s`, 7개 task 실행.
|
||
- staged/unstaged `git diff --check` — 출력 없음.
|
||
|
||
### 추가 발견 사항 요약
|
||
|
||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||
|---|---|---|---|---|---|
|
||
| `REV-018` | High | 확정 | 캐릭터 생성 Endpoint Contract Summary가 확정 request 계약과 불일치 | `Task 2.11` | `P2-R5` |
|
||
|
||
### REV-018 — 캐릭터 생성 Endpoint Contract Summary 불일치
|
||
|
||
- **심각도:** High
|
||
- **상태:** 확정
|
||
- **관련 요구사항:** PRD Feature B, Endpoint Contract Summary, `DEC-P2-T4-001`
|
||
- **관련 계약:** POST 생성은 필수 `systemPrompt`를 받고 `externalCharacterId`와 `isActive`는 client request로 받지 않는다.
|
||
- **소유 Task:** `Task 2.11`, `P2-R5`
|
||
|
||
**관찰 내용**
|
||
|
||
Endpoint Contract Summary의 POST 생성 예시는 필수 `systemPrompt`를 누락하고, 외부 API response 전용
|
||
`externalCharacterId`와 서버가 `true`로 생성하는 `isActive`를 request에 포함한다. 같은 문서의 `DEC-P2-T4-001`과
|
||
production DTO/facade는 반대 계약을 사용한다.
|
||
|
||
**근거**
|
||
|
||
- 문서: `plan-task.md:189`~`196`의 생성 JSON은 `systemPrompt`가 없고 `externalCharacterId`, `isActive`를 포함한다.
|
||
- 결정: `plan-task.md:2831`의 `DEC-P2-T4-001`은 `systemPrompt` 필수, `externalCharacterId` response 전용,
|
||
`isActive` 서버 소유를 확정한다.
|
||
- 코드: `AiCharacterAdminCharacterDto.kt:40`~`45`는 `systemPrompt`를 non-null로 받고 두 서버 소유 field를 명시적으로
|
||
수신해 facade에서 거부할 수 있게 한다.
|
||
- 코드: `AiCharacterAdminCharacterFacade.kt:64`~`68`은 두 서버 소유 field가 존재하면 400으로 거부한다.
|
||
- 테스트: `AiCharacterAdminCharacterControllerMutationTest.kt:103`~`141`은 `systemPrompt`를 포함한 정상 생성과
|
||
response의 외부 ID·활성 상태를 확인하고, 같은 파일 `:239` 이후는 서버 소유 field 입력을 거부한다.
|
||
|
||
**영향**
|
||
|
||
Endpoint Contract Summary를 source of truth로 사용하는 client는 제공 예시 그대로 요청하면 400을 받는다. production 동작은
|
||
확정 결정과 일치하지만 공개 request 예시가 반대라 Phase 2 API 계약 완료 판정이 문서 수준에서 성립하지 않는다.
|
||
|
||
**권장 조치**
|
||
|
||
`P2-R5`에서 생성 예시에 `systemPrompt`를 추가하고 `externalCharacterId`, `isActive`를 제거한다. production 변경 없이 기존
|
||
정상 생성·서버 소유 field 거부 actual endpoint test와 문서 문자열 검사를 실행해 예시와 `DEC-P2-T4-001`을 동기화한다.
|
||
|
||
### plan·goal 전환
|
||
|
||
`plan-task.md` Phase 2에 `Task 2.11` / `P2-R5`와 `P2-R5-GATE`를 추가한다. 기존 `Task 2.10`과
|
||
`P2-R4-GATE` 완료 이력은 변경하지 않는다.
|
||
|
||
### 6차 리뷰 종료 판정
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| 리뷰 범위 전체 확인 | 충족 | Endpoint Contract Summary·Decision Log·production·actual endpoint test 대조 |
|
||
| 후보 항목 판정 완료 | 충족 | `REV-018` 확정 |
|
||
| 확정 항목 plan 반영 | 충족 | `Task 2.11`, `P2-R5`, `P2-R5-GATE` |
|
||
| fresh 검증 | 충족 | 관련 216건과 lint 성공, staged/unstaged diff check 이상 없음 |
|
||
|
||
**최종 결론:** 수정 goal 필요
|
||
|
||
**남은 항목:** `P2-R5` 실행 후 `P2-R5-GATE`에서 Phase 2 후속 리뷰를 다시 판정한다.
|
||
|
||
### P2-R5-GATE 종료 판정 — 2026-07-28
|
||
|
||
- 무엇을: `REV-018`의 캐릭터 생성 Endpoint Contract Summary 예시를 최종 판정했다.
|
||
- 왜: 공개 예시와 `DEC-P2-T4-001`/production request 계약의 불일치를 해소했는지 확인하기 위해서다.
|
||
- 어떻게: `plan-task.md` 생성 예시를 확인하고 `AiCharacterAdminCharacterControllerMutationTest`를 실행했다.
|
||
- 결과: 생성 예시는 필수 `systemPrompt`를 포함하고 서버 소유 `externalCharacterId`, `isActive`를 제외했다. focused test는 `BUILD SUCCESSFUL in 30s`였다.
|
||
|
||
| 판정 항목 | 결과 | 근거 |
|
||
|---|---|---|
|
||
| `REV-018` 처리 | 충족 | Endpoint Contract Summary 생성 예시와 production DTO/facade 계약 일치 |
|
||
| actual endpoint 회귀 | 충족 | 정상 생성·서버 소유 field 거부 focused test 성공 |
|
||
| 범위 준수 | 충족 | production code 변경 없음, Phase 3 이후 기능 변경 없음 |
|
||
|
||
**최종 결론:** 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 요구사항 충족, 추가 수정 없음.
|
||
|
||
**남은 항목:** 없음.
|