32 KiB
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 유지
- 관련 계약: 공통
Sizeparameter는 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.5objective/완료 증거는 동시 요청을 요구하지만:2849~:2852에서 실제 병렬 요청은 미검증이라고 명시한다. - 기존 코드:
MemberRepository.findByIdForUpdateowner row pessimistic lock을 재사용할 수 있다.
정적 재현 절차
- 같은 owner에 active fixed post 2개와 미고정 post 2개를 준비한다.
- 두 독립 transaction이 서로 다른 미고정 post를
isFixed=true로 수정한다. - lock이 없으므로 두 transaction 모두 count 2를 읽을 수 있다.
- 서로 다른 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 목록에서 실제 응답 생성에 사용하지 않는
timezonequery를 제거한다. - 성공
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 실행했다.
- RED:
- 결과:
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은 필수
timezonequery를 controller/facade/repository로 전달하고, 각 댓글date를 요청 timezone에 맞춘 표시 문자열로 반환한다. - 승인된 최신 계약은
timezonequery 없이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과 415Acceptheader/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,
Acceptheader, 작성 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 415ApiResponse.error,Accept: application/json, 작성 insert/event 0회와 수정 row 불변을 확인했다.
REV-058 — High — 처리 완료
- OpenAPI와 계약 설명은 게시글 생성·수정 multipart의
requestpart Content-Type을application/json으로 고정한다. - 두 controller는
@RequestPart("request") request: String으로 받아 part 자체의 media type을 검사하지 않는다. - 정상 테스트는 JSON media type만 사용하며 미지원/누락 part media type의 415
Acceptheader와 S3/DB/event no-side-effect를 고정하지 않는다. - 같은 shared converter와 signature에서 AudioContent의
text/plain성공 테스트가 있어 permissive binding을 정적으로 확인할 수 있다.
추가 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,
Acceptheader, S3/DB/event no-side-effect
P5-R8 / P5-R8-GATE 처리 결과
- RED: production 변경 전
AiCharacterAdminCommunityPostCreateTest와AiCharacterAdminCommunityPostUpdateTest에 KO/EN/JAtext/plain및 Content-Type 누락requestpart 415 matrix를 추가했고, focused 명령은 12개 invocation이 415 기대 실패로BUILD FAILED in 56s였다. - GREEN: controller POST·PUT 경계에서
requestpart의 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와requestmedia type만 처리하며 전체 part 이름을 검사하지 않는다. - 특히 수정 요청에 OpenAPI가 금지한
audioFile이나 임의unexpectedpart를 추가해도 해당 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, PUTunexpected·audioFileactual endpoint 테스트를 추가했고, focused 명령은 3개 케이스 모두 400 기대 실패로BUILD FAILED in 3m 23s였다. - GREEN: Community controller POST는
{audioFile, postImage, request}, PUT은{postImage, request}exact allow-list를 적용해 초과 part를 400common.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 없는
unexpectedpart KO/EN/JA actual endpoint 회귀를 추가하고, controller가fileMap.keys와 servletparts이름을 모두 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 요구사항 충족, 추가 수정 없음.
남은 항목: 없음.