642 lines
32 KiB
Markdown
642 lines
32 KiB
Markdown
# Phase 5 커뮤니티 관리 리뷰
|
|
|
|
## 1. 리뷰 정보
|
|
|
|
| 항목 | 내용 |
|
|
|---|---|
|
|
| 리뷰 대상 | Phase 5 / 커뮤니티 3개 operation |
|
|
| 기준 commit 또는 working tree | `2f93e2c9` + 현재 Phase 5~7 working tree |
|
|
| 리뷰 일자 | 2026-07-28 |
|
|
| 리뷰어 | Codex |
|
|
| 기준 문서 | `prd.md`, `plan-task.md`, `api-contract.openapi.json` |
|
|
| 리뷰 상태 | 후속 수정 및 Gate 완료 |
|
|
|
|
## 2. 리뷰 목적과 범위
|
|
|
|
### 목적
|
|
|
|
- PRD Feature E와 OpenAPI Community 3개 operation을 facade/legacy service/test에 대조한다.
|
|
- multipart JSON 오류, 미지 필드, pagination과 side-effect 차단 순서를 점검한다.
|
|
|
|
### 포함 범위
|
|
|
|
- `AiCharacterAdminCommunityPostController`, `Facade`, `Repository`, DTO
|
|
- 관리자 community 테스트와 재사용하는 `CreatorCommunityService`
|
|
- OpenAPI Community path/schema와 plan Phase 5
|
|
|
|
### 제외 범위
|
|
|
|
- production 수정, legacy/public community 계약 변경, 테스트 실행
|
|
|
|
## 3. 판정 기준
|
|
|
|
| 심각도 | 기준 |
|
|
|---|---|
|
|
| Blocker | 소유권 우회 또는 데이터 손실 |
|
|
| High | 잘못된 요청이 500/side effect로 이어지는 주요 계약 위반 |
|
|
| Medium | pagination 또는 제한된 request schema 위반 |
|
|
| Low | 유지보수성 또는 문서 정합성 문제 |
|
|
|
|
## 4. 검토한 근거
|
|
|
|
| 근거 | 판정 |
|
|
|---|---|
|
|
| `AiCharacterAdminCommunityPostFacade.kt:36`~`:50` | create는 raw JSON을 legacy service에 전달하고 update는 기본 ObjectMapper로 직접 parse |
|
|
| `CreatorCommunityService.kt:73`~`:87` | create JSON을 기본 ObjectMapper로 parse한 뒤 media 검증·side effect 진행 |
|
|
| `AiCharacterAdminExceptionHandler.kt:58`~`:71` | Jackson parse 예외 전용 400 변환이 없고 미분류 예외는 500 |
|
|
| OpenAPI `:1156`~`:1176` | create/update request는 필수 필드와 `additionalProperties: false`를 정의 |
|
|
| `AiCharacterAdminCommunityPostFacade.kt:68`~`:81`, `:141`~`:145` | 목록에 `size in 1..50`을 강제 |
|
|
| OpenAPI `Size` parameter `:519` | minimum 1만 있고 maximum은 없음 |
|
|
| create/update 테스트 | request part 누락은 검증하지만 malformed/missing JSON field/unknown field는 직접 검증하지 않음 |
|
|
|
|
### 실행한 검증
|
|
|
|
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|
|
|---|---|---|
|
|
| parse 흐름·예외 handler·schema 정적 추적 | 성공 | parse 예외의 500 가능성과 unknown-field 허용 경계 확인 |
|
|
| pagination 계약 대조 | 성공 | runtime 최대 50과 OpenAPI maximum 부재 확인 |
|
|
| Gradle/컴파일/테스트 | 미실행 | 사용자 요청에 따라 실행하지 않음 |
|
|
|
|
## 5. 발견 사항 요약
|
|
|
|
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
|
|---|---|---|---|---|---|
|
|
| `REV-025` | High | 처리 완료 | multipart JSON parse 오류가 500이 될 수 있고 미지 필드를 허용 | `Task 5.7` | `P5-R1` |
|
|
| `REV-026` | Medium | 처리 완료 | OpenAPI에 없는 목록 size 50 상한 | `Task 5.7` | `P5-R1` |
|
|
|
|
## 6. 발견 사항 상세
|
|
|
|
### REV-025 — Community JSON 오류 경계 불일치
|
|
|
|
- **심각도:** High
|
|
- **상태:** 처리 완료
|
|
- **관련 요구사항:** PRD Feature E, 공통 오류/side-effect 계약
|
|
- **관련 계약:** 잘못된 request는 400 `common.error.invalid_request`, request schema는 미지 필드 금지
|
|
- **소유 Task:** `Task 5.7`, `P5-R1`
|
|
|
|
**관찰 내용**
|
|
|
|
create는 raw request 문자열을 legacy service에 넘기고 update는 facade에서 기본 ObjectMapper로 읽는다. 두 경로 모두
|
|
`FAIL_ON_UNKNOWN_PROPERTIES`를 활성화하지 않으며 `JsonProcessingException`을 관리자 API 예외로 변환하지 않는다.
|
|
|
|
**영향**
|
|
|
|
malformed JSON이나 필수 non-null 필드 누락이 공통 handler의 500 `common.error.unknown`으로 분류될 수 있다. 미지 필드는
|
|
무시되어 잘못된 요청이 mutation과 S3/event 경로까지 진행될 수 있다.
|
|
|
|
**권장 조치**
|
|
|
|
legacy 호출 전에 create/update DTO를 strict reader로 검증하고 Jackson mapping 오류를
|
|
400 `common.error.invalid_request`로 변환한다. malformed, 필수 필드 누락, 미지 필드의 DB/S3/event 0회를 actual
|
|
endpoint로 고정한다.
|
|
|
|
**처리 결과**
|
|
|
|
create/update `request` part를 legacy service 호출 전에 strict reader로 검증하고 Jackson parse/mapping 오류를
|
|
400 `common.error.invalid_request`로 변환했다. create malformed·필수 field 누락·미지 field, update malformed·미지 field의
|
|
actual endpoint no-side-effect 테스트를 추가했다. OpenAPI상 update request에는 required field가 없어 update 필수 field 누락
|
|
케이스는 계약 밖으로 제외했다.
|
|
|
|
### REV-026 — 계약에 없는 Community size 상한
|
|
|
|
- **심각도:** Medium
|
|
- **상태:** 처리 완료
|
|
- **관련 요구사항:** 레거시 목록 query/pagination 유지
|
|
- **관련 계약:** 공통 `Size` parameter는 default 20, minimum 1이며 maximum 없음
|
|
- **소유 Task:** `Task 5.7`, `P5-R1`
|
|
|
|
**관찰 내용**
|
|
|
|
facade는 `size !in 1..50`을 400으로 거부한다. OpenAPI 단일 원본에는 maximum 50이 없으므로 `size=51`은 계약상 유효하다.
|
|
|
|
**권장 조치**
|
|
|
|
OpenAPI를 임의 변경하지 않고 관리자 facade의 상한 guard만 제거한다. page 음수와 size 1 미만 검증은 유지한다.
|
|
|
|
**처리 결과**
|
|
|
|
목록의 `size <= 50` 상한 guard만 제거하고 `page < 0`, `size < 1` 검증은 유지했다. `size=51` actual endpoint 요청이
|
|
정상 pagination으로 처리되는 테스트를 추가했다.
|
|
|
|
## 7. plan·goal 전환
|
|
|
|
`plan-task.md` Phase 5의 `Task 5.7` / `P5-R1`과 `P5-R1-GATE`를 완료 처리했다. 기존 `P5-GATE` 완료 이력은 유지한다.
|
|
|
|
## 8. 리뷰 종료 판정
|
|
|
|
| 판정 항목 | 결과 | 근거 |
|
|
|---|---|---|
|
|
| operation/path 대조 | 충족 | Community 3개 route 존재 |
|
|
| JSON 오류/schema 대조 | 충족 | strict parse와 parse 예외 400 변환, unknown-field 거부 회귀 통과 |
|
|
| pagination 대조 | 충족 | 계약에 없는 maximum 50 제거, `size=51` 회귀 통과 |
|
|
| ownership 선검증 | 충족 | target/post owner 확인은 mutation 전 수행 |
|
|
| 실행 검증 | 충족 | focused 테스트, community/common 회귀, `ktlintCheck`, `git diff --check` 통과 |
|
|
|
|
**최종 결론:** 후속 수정 및 Gate 완료
|
|
|
|
**남은 항목:** 없음. 다음 Goal은 `P6-R1`이다.
|
|
|
|
## 9. 2차 정적 리뷰 및 판정 — 2026-07-28
|
|
|
|
### 리뷰 정보와 검증 범위
|
|
|
|
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
|
|
- 기준 문서: PRD Feature E, plan Phase 5, OpenAPI Community 3개 operation
|
|
- 검토 범위: fixed update facade/legacy service/repository, transaction·lock 경계와 concurrency test
|
|
- 검증 방식: 두 병렬 transaction의 count/read/update 순서를 코드와 테스트로 정적 추적했다.
|
|
사용자 요청에 따라 컴파일과 테스트는 실행하지 않았다.
|
|
|
|
### 추가 발견 사항 요약
|
|
|
|
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
|
|---|---|---|---|---|---|
|
|
| `REV-033` | High | 처리 완료 | 최대 고정 3개가 실제 병렬 요청에서 보장되지 않음 | `Task 5.8` | `P5-R2` |
|
|
|
|
### REV-033 — lock 없는 count-then-update 경쟁 조건
|
|
|
|
- **심각도:** High
|
|
- **상태:** 처리 완료
|
|
- **관련 요구사항:** PRD Feature E의 최대 고정 게시글 수 3개
|
|
- **관련 계약:** plan transaction/concurrency 고려사항과 `Task 5.5`의 동시 요청 완료 증거
|
|
- **소유 Task:** `Task 5.8`, `P5-R2`
|
|
|
|
**관찰 내용**
|
|
|
|
legacy fixed update는 활성 고정 수를 조회한 뒤 별도 게시글 entity의 `isFixed`를 변경한다. owner 또는 고정 집합을 잠그는
|
|
lock/constraint가 없으므로, 고정 2개 상태에서 서로 다른 게시글을 고정하는 두 transaction이 모두 count 2를 읽고 커밋하면
|
|
최종 고정 수는 4개가 된다.
|
|
|
|
`AiCharacterAdminCommunityPostConcurrencyTest`는 이름과 달리 두 요청을 순서대로 호출한다. plan `Task 5.5`도 실제 병렬
|
|
재현을 하지 못했다고 기록하면서 Task objective·완료 증거와 Phase acceptance를 완료 처리했다.
|
|
|
|
**근거**
|
|
|
|
- 코드: `AiCharacterAdminCommunityPostFacade.kt:62`~`:69`은 lock 없이 legacy fixed update를 호출한다.
|
|
- 코드: `CreatorCommunityService.kt:247`~`:262`는 `countBy...` 후 서로 다른 post를 갱신한다.
|
|
- 테스트: `AiCharacterAdminCommunityPostConcurrencyTest.kt:55`~`:79`는 세 번째 요청 완료 후 네 번째 요청을 실행하는
|
|
순차 시나리오다.
|
|
- 계획: `Task 5.5` objective/완료 증거는 동시 요청을 요구하지만 `:2849`~`:2852`에서 실제 병렬 요청은 미검증이라고
|
|
명시한다.
|
|
- 기존 코드: `MemberRepository.findByIdForUpdate` owner row pessimistic lock을 재사용할 수 있다.
|
|
|
|
**정적 재현 절차**
|
|
|
|
1. 같은 owner에 active fixed post 2개와 미고정 post 2개를 준비한다.
|
|
2. 두 독립 transaction이 서로 다른 미고정 post를 `isFixed=true`로 수정한다.
|
|
3. lock이 없으므로 두 transaction 모두 count 2를 읽을 수 있다.
|
|
4. 서로 다른 row를 갱신해 둘 다 커밋하면 최종 active fixed count는 4가 된다.
|
|
|
|
**영향**
|
|
|
|
PRD의 최대 3개 데이터 불변식이 깨지고 관리자 목록 정렬·운영 정책이 비결정적이 된다. 단순 순차 회귀는 통과하므로
|
|
현재 테스트 통과만으로 문제를 탐지할 수 없다.
|
|
|
|
**권장 조치**
|
|
|
|
신규 DDL이나 dependency 없이 기존 `MemberRepository.findByIdForUpdate`로 fixed/unfixed count·update 전에 owner를 잠근다.
|
|
두 독립 transaction과 barrier/lock probe를 사용하는 결정적 병렬 테스트로 최종 3개, 한 요청 400, 실패 side effect 0건을
|
|
검증하고 sleep·반복 확률 기반 테스트는 사용하지 않는다.
|
|
|
|
**처리 결과**
|
|
|
|
fixed 변경 요청에서 legacy count/update 전에 `MemberRepository.findByIdForUpdate`로 owner row를 잠그도록 변경했다.
|
|
두 병렬 요청이 같은 count 경계에 진입하는 결정적 회귀 테스트를 추가했고, 최종 고정 수 3개와 한 요청 400을 확인했다.
|
|
|
|
**판정 기록**
|
|
|
|
- 2026-07-28 — 코드·plan 완료 증거·테스트 실행 구조로 경쟁 조건을 확정. 테스트는 사용자 요청에 따라 미실행.
|
|
|
|
### plan·goal 전환
|
|
|
|
`plan-task.md` Phase 5에 `Task 5.8` / `P5-R2`와 `P5-R2-GATE`를 추가했다. 기존 `Task 5.5` 완료 이력은
|
|
되돌리지 않고 실제 동시성 보완을 새 Goal로 추적한다.
|
|
`P5-R2`와 `P5-R2-GATE`를 완료 처리했다.
|
|
|
|
### 2차 리뷰 종료 판정
|
|
|
|
| 판정 항목 | 결과 | 근거 |
|
|
|---|---|---|
|
|
| operation/schema 대조 | 충족 | Community 3개 mapping과 JSON 경계 유지 |
|
|
| 순차 fixed 정책 | 충족 | 세 번째 성공·네 번째 거부 테스트 존재 |
|
|
| 실제 동시성 불변식 | 충족 | owner row lock과 결정적 병렬 회귀 통과 |
|
|
| plan 반영 | 충족 | `Task 5.8`, `P5-R2`, `P5-R2-GATE` |
|
|
| 실행 검증 | 충족 | focused concurrency, community/common 회귀, `ktlintCheck`, `git diff --check` 통과 |
|
|
|
|
**최종 결론:** 후속 수정 및 Gate 완료
|
|
|
|
**남은 항목:** 없음. 다음 Goal은 `P7-R2`다.
|
|
|
|
## 10. 3차 정적 리뷰 및 판정 — 2026-07-28
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
|
|
- 기준 문서: PRD Feature E, plan Phase 5, OpenAPI Community 3개 operation
|
|
- 검토 범위: 목록·생성·수정 facade, strict multipart JSON, owner-scoped query, fixed owner lock과 관련 테스트
|
|
- 검증 방식: 코드·문서·테스트 정적 추적. 컴파일과 테스트는 실행하지 않았다.
|
|
|
|
### 발견 사항과 판정
|
|
|
|
확정 발견 사항 없음.
|
|
|
|
| 판정 항목 | 결과 | 근거 |
|
|
|---|---|---|
|
|
| operation/schema | 충족 | Community 3개 mapping과 OpenAPI 경계 유지 |
|
|
| ownership | 충족 | active target과 owner post 검증 유지 |
|
|
| JSON/multipart | 충족 | strict parse와 malformed/unknown-field 400 유지 |
|
|
| fixed 동시성 | 충족 | owner row lock이 count/update 앞에서 수행됨 |
|
|
| plan 전환 | 해당 없음 | Phase 5 신규 Task 불필요 |
|
|
|
|
**최종 결론:** Phase 5 추가 수정 없음
|
|
|
|
**남은 항목:** `P7-R3`에서 Community/common 회귀를 통합 재검증한다.
|
|
|
|
## 11. 4차 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 commit/working tree: `2f93e2c9` + 현재 working tree
|
|
- 기준 문서: PRD Feature E, plan Phase 5, OpenAPI Community 3개 operation
|
|
- 검토 범위: 목록·생성·수정, owner query, strict JSON, 고정 수 lock과 soft delete
|
|
- 검증 방식: 코드·schema·테스트 정적 대조. 컴파일과 테스트는 실행하지 않았다.
|
|
|
|
### 발견 사항과 판정
|
|
|
|
확정 발견 사항 없음.
|
|
|
|
| 판정 항목 | 결과 | 근거 |
|
|
|---|---|---|
|
|
| operation/schema | 충족 | Community 3개 mapping과 OpenAPI 경계 유지 |
|
|
| ownership | 충족 | active target과 owner post를 mutation 전에 확인 |
|
|
| 고정/soft delete | 충족 | owner row lock과 fixed 상태 동시 해제 유지 |
|
|
| 오류/부작용 | 충족 | strict JSON과 target/owner 실패 선검증 유지 |
|
|
| plan 전환 | 해당 없음 | Phase 5 신규 Task 불필요 |
|
|
|
|
**최종 결론:** Phase 5 추가 수정 없음
|
|
|
|
**남은 항목:** 없음.
|
|
|
|
## 12. 5차 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 확인된 문제
|
|
|
|
#### `REV-043` — 커뮤니티 primitive의 required·null 계약 미강제
|
|
|
|
- **심각도:** High
|
|
- **상태:** 처리 완료
|
|
- **계약:** OpenAPI create request는 `isCommentAvailable`, `isAdult`를 required non-null boolean으로 정의하고,
|
|
`price`와 update의 `isFixed`도 nullable로 선언하지 않는다.
|
|
- **구현:** create DTO의 boolean/price는 Kotlin primitive이고 update `isFixed`는 nullable이라, strict reader가
|
|
미지 필드만 거부하면 누락·explicit null을 계약대로 구분하지 못한다.
|
|
- **근거:** Jackson Kotlin/databind 2.13.5 기본 설정에서 create primitive는 false·0으로 보정될 수 있고,
|
|
update `isFixed: null`은 필드 생략과 같은 null로 처리된다.
|
|
- **영향:** 계약상 invalid 요청이 생성 mutation을 진행하거나 성공 no-op update로 처리될 수 있다.
|
|
|
|
### 보완 결과
|
|
|
|
| 항목 | 판정 |
|
|
|---|---|
|
|
| 신규 Task | `Task 5.9` / `P5-R3` 처리 완료 |
|
|
| 시작 조건 | `P4-R4-GATE` 완료 후 실행 |
|
|
| Gate | `P5-R3-GATE` 완료 |
|
|
| RED | required boolean 누락·null, `price: null`, `isFixed: null`이 400 기대 실패 |
|
|
| GREEN | v2 create/update 경계 required/non-null 검증, 생략 의미 유지 |
|
|
| 범위 제한 | 전역 mapper·레거시 service·OpenAPI 변경 없음 |
|
|
|
|
### 실행 검증
|
|
|
|
| 명령 또는 검증 | 결과 | 핵심 증거 |
|
|
|---|---|---|
|
|
| invalid primitive RED focused | 실패 확인 | 신규 6건이 400 기대 실패 |
|
|
| invalid primitive GREEN focused | 통과 | `BUILD SUCCESSFUL in 41s` |
|
|
| create/update focused | 통과 | 리뷰 보완 후 `BUILD SUCCESSFUL in 34s` |
|
|
| community/common 영향 범위 회귀 | 통과 | 리뷰 보완 후 `BUILD SUCCESSFUL in 1m 2s` |
|
|
| `ktlintCheck`, `git diff --check` | 통과 | `ktlintCheck`는 `BUILD SUCCESSFUL in 13s`, diff check 출력 없음 |
|
|
|
|
**최종 결론:** Phase 5 `REV-043` 보완 완료
|
|
|
|
**다음 Goal:** `P5-R4`.
|
|
|
|
## 13. Community 목록 요구사항 변경 판정 — 2026-07-29
|
|
|
|
### 확정 요구사항
|
|
|
|
- **추적 ID:** `DEC-P5-LIST-001`
|
|
- GET 목록에서 실제 응답 생성에 사용하지 않는 `timezone` query를 제거한다.
|
|
- 성공 `data`는 직접 배열 대신
|
|
`AiCharacterAdminCommunityPostListResponse(totalCount, page, size, hasNext, items)`를 반환한다.
|
|
- `totalCount`와 `hasNext`는 target creatorMember 소유 active 게시글만 기준으로 계산한다.
|
|
- `items`의 기존 18개 필드와 고정 우선 정렬, owner/inactive 격리, page/size 오류 정책은 유지한다.
|
|
|
|
### 설계 판정
|
|
|
|
| 항목 | 판정 | 근거 |
|
|
|---|---|---|
|
|
| query | `page`, `size`만 유지 | timezone은 facade에서 유효성 검사 외 사용되지 않음 |
|
|
| response | 전용 pagination wrapper 추가 | UI가 전체 개수와 추가 로딩 필요 여부를 판단해야 함 |
|
|
| count | active owner count query 1개 추가 | totalCount가 필요해 size+1 조회만으로는 충족 불가 |
|
|
| hasNext | `pageable.offset + items.size < totalCount` | 마지막·범위 밖 page를 단순하게 처리 |
|
|
| 기존 item | 변경 없음 | 요청 범위 밖 schema 변경 방지 |
|
|
| 공용 추상화 | 추가하지 않음 | 단일 endpoint 전용 DTO가 최소 변경 |
|
|
|
|
### plan 전환
|
|
|
|
- 신규 Task: `Task 5.10` / `P5-R4`
|
|
- Gate: `P5-R4-GATE`
|
|
- 시작 조건: `P5-R3-GATE` 완료
|
|
- 통합 조건: `P7-R5` 시작 전에 `P5-R4-GATE` 완료
|
|
|
|
### 처리 결과
|
|
|
|
`P5-R4`에서 controller/facade의 `timezone` query와 미사용 검증을 제거하고, repository에 active owner count query를 추가했다.
|
|
성공 `data`는 `AiCharacterAdminCommunityPostListResponse(totalCount, page, size, hasNext, items)`로 반환한다. 기존 item 18개 필드,
|
|
고정 우선 정렬, owner/inactive 격리, `page < 0`·`size < 1` 오류 정책과 문서에 없는 size 상한 부재는 유지했다. OpenAPI
|
|
Community GET status는 `implemented`로 복구했다.
|
|
|
|
### 실행 검증
|
|
|
|
| 명령 또는 검증 | 결과 | 핵심 증거 |
|
|
|---|---|---|
|
|
| query RED focused | 실패 확인 | 신규 3건이 400/직접 배열 응답 차이로 실패 |
|
|
| query GREEN focused | 통과 | `BUILD SUCCESSFUL in 1m 28s` |
|
|
| query+contract focused | 통과 | `BUILD SUCCESSFUL in 37s` |
|
|
| community/common 영향 범위 회귀 | 통과 | `BUILD SUCCESSFUL in 1m 6s` |
|
|
| OpenAPI jq assertion | 통과 | `true` |
|
|
| `ktlintCheck`, `git diff --check` | 통과 | `ktlintCheck`는 `BUILD SUCCESSFUL in 12s`, diff check 출력 없음 |
|
|
|
|
**최종 결론:** Phase 5 `DEC-P5-LIST-001` 목록 계약 정합화 및 Gate 완료
|
|
|
|
**다음 Goal:** `P7-R5`.
|
|
|
|
## 14. 커뮤니티 댓글 후속 검토 — 2026-07-29
|
|
|
|
### 확인 결과
|
|
|
|
- **`REV-048` / High / 구현 대기:** 신규 v2 관리자 경계에 target 소유 커뮤니티 게시글의 댓글·답글
|
|
조회/작성/수정/삭제 5개 operation이 없다.
|
|
- 조회는 필수 `timezone`과 `page`, `size`, 레거시 `totalCount/items`를 유지한다.
|
|
- 작성자는 target `creatorMember`, 수정은 target 작성 활성 row만 허용한다. 삭제는 target 소유 게시글의 row를
|
|
작성자와 관계없이 soft delete하고 cascade하지 않으며 이미 비활성이면 성공 no-op이다.
|
|
- 답글 `parentId`는 같은 게시글의 활성 원댓글이어야 한다.
|
|
|
|
### plan 전환
|
|
|
|
- 신규 Task: `Task 5.11` / `P5-R5`
|
|
- Gate: `P5-R5-GATE`
|
|
- 범위 밖: 캐릭터 직접 댓글, hard delete·cascade, legacy/public endpoint 변경
|
|
|
|
사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
|
|
|
|
**최종 결론:** Phase 5 커뮤니티 댓글 CRUD 구현 필요
|
|
|
|
**다음 Goal:** `P5-R5`.
|
|
|
|
## 15. 커뮤니티 댓글 CRUD 구현 및 Gate — 2026-07-29
|
|
|
|
- 무엇을: `REV-048`을 처리했다.
|
|
- 왜: target AI 소유 커뮤니티 게시글의 댓글·답글 조회/작성/수정/삭제 5개 operation이 신규 v2 관리자 경계에 없었기 때문이다.
|
|
- 어떻게:
|
|
- RED: `AiCharacterAdminCommunityPostCommentTest`에 root/reply 목록, target AI 작성, parent 검증, target 작성자 수정 제한, row-only soft delete, 요청 오류 계약 테스트 7건을 추가했고 미구현 route로 실패했다.
|
|
- GREEN: `AiCharacterAdminCommunityPostController`에 5개 route를 추가하고, facade에서 target/owner/parent/actor를 선검증한 뒤 기존 `CreatorCommunityService` 댓글 조회·작성·수정 의미를 재사용했다.
|
|
- Gate: focused 댓글 테스트, community/common 영향 범위 회귀, `ktlintCheck`, `git diff --check`를 fresh 실행했다.
|
|
- 결과: `REV-048` 처리 완료. 캐릭터 직접 댓글, hard delete, cascade, legacy/public endpoint 변경은 추가하지 않았다.
|
|
|
|
**최종 결론:** Phase 5 커뮤니티 댓글 후속 기능 종결
|
|
|
|
**다음 Goal:** `P6-R2`.
|
|
|
|
## 16. 커뮤니티 댓글 UTC 날짜 계약 변경 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: PRD Feature E, OpenAPI 2.2.0 Community 8개 operation, `DEC-UTC-DATE-001`
|
|
- 검토 범위: 커뮤니티 댓글·답글 GET의 controller/facade/repository와 관련 테스트
|
|
- 검증 방식: 문서·코드·테스트 정적 대조. 사용자 요청에 따라 컴파일과 테스트는 실행하지 않았다.
|
|
|
|
### `REV-051` — High — 처리 완료: 커뮤니티 댓글 2개 GET의 timezone/UTC 계약 불일치
|
|
|
|
- 처리 전 댓글·답글 GET은 필수 `timezone` query를 controller/facade/repository로 전달하고, 각 댓글 `date`를
|
|
요청 timezone에 맞춘 표시 문자열로 반환한다.
|
|
- 승인된 최신 계약은 `timezone` query 없이 `page`, `size`만 받고 기존 `totalCount`, `items`, `date` 필드명을
|
|
유지하되 `date` 값을 ISO-8601 UTC(`Z`)로 반환한다.
|
|
- 댓글 작성·수정·삭제의 actor/owner/parent/soft delete 의미와 커뮤니티 게시글 목록의 pagination wrapper는
|
|
변경 대상이 아니다.
|
|
|
|
### 판정
|
|
|
|
| 항목 | 결과 | 근거 |
|
|
|---|---|---|
|
|
| route 수 | 유지 | Community 8개 operation 자체는 변경 없음 |
|
|
| 댓글·답글 query | 처리 완료 | v2 controller/facade에서 필수 `timezone`을 제거하고 `page`·`size`만 사용 |
|
|
| 댓글 `date` | 처리 완료 | owner-scoped 조회 결과를 `createdAt.toUtcIso()`로 재매핑해 UTC `date-time` 반환 |
|
|
| 기존 댓글 의미 | 유지 | pagination·ownership·block/secret·mutation 정책 변경 없이 영향 범위 회귀 통과 |
|
|
| legacy/public 격리 | 충족 | 기존 community 댓글 timezone service/repository 계약을 변경하지 않음 |
|
|
| OpenAPI 상태 | 처리 완료 | 영향 2개 operation을 `implemented`로 동기화 |
|
|
|
|
### plan·goal 전환
|
|
|
|
- 신규 Task: `Task 5.12` / `P5-R6`
|
|
- Gate: `P5-R6-GATE`
|
|
- 시작 조건: `P3-R14-GATE`
|
|
- 완료 조건: 댓글·답글 actual GET UTC exact JSON, 기존 pagination/ownership/block/secret 의미와
|
|
legacy/public 회귀
|
|
|
|
### `P5-R6` / `P5-R6-GATE` 처리 결과
|
|
|
|
- RED: production 변경 전 `AiCharacterAdminCommunityPostCommentTest`는 timezone 없는 root/reply GET이 400을 반환해 2건 실패했고 `BUILD FAILED in 38s`였다.
|
|
- GREEN: controller/facade의 timezone 입력·검증을 제거하고 legacy 조회 결과의 `date`만 `createdAt.toUtcIso()`로 재매핑한 뒤 같은 focused 테스트는 `BUILD SUCCESSFUL in 43s`였다.
|
|
- Gate: community/common·legacy 영향 범위 회귀는 `BUILD SUCCESSFUL in 1m 11s`, `ktlintCheck`는 `BUILD SUCCESSFUL in 26s`였고, OpenAPI는 36개 `implemented`와 0개 `alignment-required`, `git diff --check`는 출력 없음을 확인했다.
|
|
|
|
**최종 결론:** `REV-051` 처리 완료, Phase 5 UTC 계약 정합화 완료
|
|
|
|
**다음 Goal:** `P7-R7`.
|
|
|
|
## 17. 6차 통합 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: PRD Feature E, OpenAPI Community 8개 operation
|
|
- 검토 범위: Community controller의 multipart/JSON mapping, 댓글 facade와 관련 actual endpoint 테스트
|
|
- 기준 상태: 현재 working tree
|
|
- 검증 방식: 문서·코드·테스트 정적 대조. 사용자 요청에 따라 Gradle, 컴파일, 테스트는 실행하지 않았다.
|
|
|
|
### `REV-053` — High — 처리 완료
|
|
|
|
- OpenAPI는 댓글 POST와 PUT의 requestBody media type을 `application/json` 하나로 정의하고 415 response를 선언한다.
|
|
- 검토 당시 두 controller mapping에는 `consumes = [MediaType.APPLICATION_JSON_VALUE]`가 없었다.
|
|
- body를 `String`으로 받으므로 mapping 단계에서 media type을 제한하지 않으면 `text/plain` 같은 요청이
|
|
`HttpMediaTypeNotSupportedException`으로 차단되지 않고 handler/parser까지 진입할 수 있다.
|
|
- 기존 댓글 테스트는 정상·오류 JSON 요청을 모두 `application/json`으로만 보내 미지원 media type과
|
|
415 `Accept` header/no-side-effect를 고정하지 않는다.
|
|
- 외부 HTTP 요청 수용 범위와 명시된 415가 달라 High로 판정한다.
|
|
|
|
### plan 전환
|
|
|
|
- 신규 Task: `Task 5.13` / `P5-R7`
|
|
- Gate: `P5-R7-GATE`
|
|
- 최소 수정: 댓글 POST·PUT mapping에 JSON `consumes` 추가
|
|
- 완료 조건: 정상 JSON 회귀, 미지원 media type의 KO/EN/JA 415 envelope, `Accept` header,
|
|
작성 insert/event 0회와 수정 row 불변
|
|
- 범위 밖: facade/parser·댓글 actor/owner/parent 의미, OpenAPI·legacy/public API 변경
|
|
|
|
### 처리 결과
|
|
|
|
- 댓글 POST·PUT mapping에 `consumes = [MediaType.APPLICATION_JSON_VALUE]`를 추가했다.
|
|
- actual endpoint 회귀에서 KO/EN/JA `text/plain` 요청의 localized 415 `ApiResponse.error`, `Accept: application/json`,
|
|
작성 insert/event 0회와 수정 row 불변을 확인했다.
|
|
|
|
### `REV-058` — High — 처리 완료
|
|
|
|
- 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을
|
|
정적으로 확인할 수 있다.
|
|
|
|
### 추가 plan 전환
|
|
|
|
- 신규 Task: `Task 5.14` / `P5-R8`
|
|
- Gate: `P5-R8-GATE`
|
|
- 최소 수정: 기존 strict String reader는 유지하고 v2 multipart 경계에서 part-level JSON media type만 강제
|
|
- 완료 조건: POST·PUT 정상 JSON 회귀, 미지원/누락 media type의 KO/EN/JA 415 envelope, `Accept` header,
|
|
S3/DB/event no-side-effect
|
|
|
|
### `P5-R8` / `P5-R8-GATE` 처리 결과
|
|
|
|
- RED: production 변경 전 `AiCharacterAdminCommunityPostCreateTest`와 `AiCharacterAdminCommunityPostUpdateTest`에
|
|
KO/EN/JA `text/plain` 및 Content-Type 누락 `request` part 415 matrix를 추가했고, focused 명령은 12개 invocation이
|
|
415 기대 실패로 `BUILD FAILED in 56s`였다.
|
|
- GREEN: controller POST·PUT 경계에서 `request` part의 JSON 호환 media type만 확인하도록 추가했다. facade strict reader,
|
|
media/fixed/owner 의미, OpenAPI schema는 변경하지 않았다.
|
|
- Gate: 같은 focused 명령은 `BUILD SUCCESSFUL in 59s`, community/common 영향 범위와
|
|
`AiCharacterAdminErrorContractTest` 회귀는 `BUILD SUCCESSFUL in 55s`였다.
|
|
|
|
**최종 결론:** `REV-053`, `REV-058` 처리 완료, Phase 5 HTTP media type 계약 정합화 완료
|
|
|
|
**다음 Goal:** `P6-R3`.
|
|
|
|
## 18. 7차 통합 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: PRD Feature E, OpenAPI `CommunityPostCreateMultipart`·`CommunityPostUpdateMultipart`
|
|
- 검토 범위: Community POST·PUT controller의 multipart binding과 media/fixed/owner mutation 테스트
|
|
- 검증 방식: 현재 working tree의 문서·코드·테스트를 정적으로 대조했다. 사용자 요청에 따라 컴파일과 테스트는
|
|
실행하지 않았다.
|
|
|
|
### `REV-063` — Medium — 생성·수정의 서로 다른 허용 part 집합을 강제하지 않음
|
|
|
|
- OpenAPI는 생성에 `audioFile`, `postImage`, `request`, 수정에 `postImage`, `request`만 허용하고 두 schema 모두
|
|
`additionalProperties: false`다.
|
|
- controller는 각 `@RequestPart`와 `request` media type만 처리하며 전체 part 이름을 검사하지 않는다.
|
|
- 특히 수정 요청에 OpenAPI가 금지한 `audioFile`이나 임의 `unexpected` part를 추가해도 해당 part가 무시된 채 게시글
|
|
mutation이 진행될 수 있다.
|
|
|
|
### plan 전환
|
|
|
|
| 항목 | 내용 |
|
|
|---|---|
|
|
| 신규 Task | `Task 5.15` / `P5-R9` |
|
|
| Gate | `P5-R9-GATE` |
|
|
| RED | 생성·수정 미정의 part, 수정 `audioFile`, S3·DB·event 결과 |
|
|
| GREEN | 생성 `{audioFile, postImage, request}`, 수정 `{postImage, request}` exact allow-list |
|
|
| 회귀 | 정상 media/fixed/owner, request part 누락·415 |
|
|
|
|
### `P5-R9` / `P5-R9-GATE` 처리 결과
|
|
|
|
- RED: production 변경 전 Community POST `unexpected`, PUT `unexpected`·`audioFile` actual endpoint 테스트를 추가했고,
|
|
focused 명령은 3개 케이스 모두 400 기대 실패로 `BUILD FAILED in 3m 23s`였다.
|
|
- GREEN: Community controller POST는 `{audioFile, postImage, request}`, PUT은 `{postImage, request}` exact allow-list를
|
|
적용해 초과 part를 400 `common.error.invalid_request`로 거부한다. media/fixed/owner 의미와 OpenAPI schema는 변경하지 않았다.
|
|
- Gate: focused 명령은 `BUILD SUCCESSFUL in 2m 30s`, community/common 영향 범위 회귀는 `BUILD SUCCESSFUL in 1m 44s`,
|
|
`ktlintCheck`는 `BUILD SUCCESSFUL in 55s`였다.
|
|
|
|
**최종 결론:** `REV-063` 처리 완료, Phase 5 multipart part 이름 계약 정합화 완료
|
|
|
|
**다음 Goal:** `P7-R9`.
|
|
|
|
## 19. 8차 통합 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: OpenAPI Community create/update multipart schema의 operation별 허용 part와
|
|
`additionalProperties: false`
|
|
- 검토 범위: Community POST·PUT controller와 미정의 part·media/fixed/owner 회귀 테스트
|
|
- 기준 상태: 현재 working tree
|
|
- 리뷰어/상태: Codex / 판정 완료
|
|
- 검증 방식: 문서·코드·테스트 소스 정적 대조. 사용자 지시에 따라 컴파일과 테스트는 실행하지 않았다.
|
|
|
|
### `REV-069` — Medium — 일반 form-field multipart part가 allow-list 우회
|
|
|
|
- `AiCharacterAdminCommunityPostController.kt:63-69`는 operation별 allow-list를 받지만 실제 검사는
|
|
`fileMap.keys`에 한정한다.
|
|
- 기존 create/update 회귀는 `AiCharacterAdminCommunityPostCreateTest.kt:202-210`과
|
|
`AiCharacterAdminCommunityPostUpdateTest.kt:256-268`에서 filename이 있는 `MockMultipartFile`만 사용한다.
|
|
- filename 없는 일반 form-field `unexpected`는 생성 `{audioFile, postImage, request}`, 수정
|
|
`{postImage, request}` 계약을 우회할 수 있어 Medium으로 확정한다.
|
|
|
|
### plan 전환
|
|
|
|
| 항목 | 내용 |
|
|
|---|---|
|
|
| 신규 Task | `Task 5.16` / `P5-R10` |
|
|
| Gate | `P5-R10-GATE` |
|
|
| RED | filename 없는 미정의 part의 POST·PUT 400과 S3·DB·event no-side-effect |
|
|
| GREEN | servlet 전체 part 이름을 operation별 allow-list와 비교 |
|
|
| 범위 제한 | media/fixed/concurrency·OpenAPI·전역 resolver·legacy/public 변경 없음 |
|
|
|
|
**최종 결론:** Phase 5 보완 필요 — `REV-069` 확정
|
|
|
|
**다음 Goal:** `P5-R10` (`P4-R10-GATE` 완료 후).
|
|
|
|
## 20. 8차 후속 수정 및 Gate — 2026-07-29
|
|
|
|
- 무엇을: `REV-069`의 Community POST·PUT filename 없는 일반 form-field multipart part 우회를 보완했다.
|
|
- 왜: 생성 `{audioFile, postImage, request}`, 수정 `{postImage, request}` 외 일반 form-field part가 기존 파일 map 검사만으로는 mutation 전 거부되지 않았기 때문이다.
|
|
- 어떻게: create/update focused test에 filename 없는 `unexpected` part KO/EN/JA actual endpoint 회귀를 추가하고, controller가 `fileMap.keys`와 servlet `parts` 이름을 모두 operation별 allow-list와 비교하게 했다.
|
|
- 결과: RED 묶음에서 신규 multipart/genre 36건 실패를 확인했고, 보완 후 focused GREEN 묶음은 `BUILD SUCCESSFUL in 1m 17s`였다. 영향 범위 회귀와 lint 결과는 `P7-R10-GATE`에 통합 기록한다.
|
|
|
|
**최종 결론:** `REV-069` 처리 완료. Phase 5 후속 Gate 완료.
|
|
|
|
**남은 항목:** 없음.
|
|
|
|
## 21. 9차 정적 리뷰 및 판정 — 2026-07-29
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: PRD Feature F의 Community 요구사항, OpenAPI Community 8개 operation
|
|
- 검토 범위: 게시글 목록·생성·수정, 댓글 CRUD, owner/actor/parent, multipart·concurrency 경계
|
|
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과
|
|
테스트는 실행하지 않았다.
|
|
|
|
### 판정 및 plan 전환
|
|
|
|
- Community 8개 operation과 target owner, 댓글 actor/parent, exact multipart, 고정 제한 동시성 경계를 대조했다.
|
|
- 기존 완료 finding 이후 신규 확정 finding은 없다.
|
|
- Phase 5 신규 Task/Gate 없음.
|
|
|
|
**최종 결론:** Phase 5 추가 수정 없음.
|
|
|
|
**남은 항목:** Phase 3 보완 뒤 `P7-R11` 통합 재판정.
|
|
|
|
## 22. 10차 정적 리뷰 및 판정 — 2026-07-30
|
|
|
|
### 리뷰 범위와 방식
|
|
|
|
- 기준 문서: PRD Feature E, OpenAPI Community 8개 operation
|
|
- 검토 범위: 게시글 목록·생성·수정, 고정 동시성, 댓글 CRUD, owner/actor/parent와 multipart 경계
|
|
- 검증 방식: 현재 working tree의 문서·production·test 소스를 정적으로 대조했다. 사용자 지시에 따라 컴파일과
|
|
테스트는 실행하지 않았다.
|
|
|
|
### 판정
|
|
|
|
- Community 8개 operation과 controller mapping, active owner pagination wrapper·고정 우선 정렬이 일치한다.
|
|
- 게시글 생성·수정의 strict request와 operation별 multipart part, 최대 고정 3개 owner lock·soft delete 정리가 유지된다.
|
|
- 댓글의 target AI 작성/수정, 동일 게시글 활성 원댓글, row-only soft delete와 UTC 응답 계약이 유지된다.
|
|
- 신규 확정 finding이 없어 Phase 5 회귀 수정 Task/Gate를 추가하지 않는다.
|
|
|
|
**최종 결론:** Phase 5 요구사항 충족, 추가 수정 없음.
|
|
|
|
**남은 항목:** 없음.
|