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

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 유지
  • 관련 계약: 공통 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-R1P5-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~:262countBy... 후 서로 다른 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-R2P5-R2-GATE를 추가했다. 기존 Task 5.5 완료 이력은 되돌리지 않고 실제 동시성 보완을 새 Goal로 추적한다. P5-R2P5-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 통과 ktlintCheckBUILD 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)를 반환한다.
  • totalCounthasNext는 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를 추가했다. 성공 dataAiCharacterAdminCommunityPostListResponse(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 통과 ktlintCheckBUILD 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이 없다.
  • 조회는 필수 timezonepage, 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 조회 결과의 datecreatedAt.toUtcIso()로 재매핑한 뒤 같은 focused 테스트는 BUILD SUCCESSFUL in 43s였다.
  • Gate: community/common·legacy 영향 범위 회귀는 BUILD SUCCESSFUL in 1m 11s, ktlintCheckBUILD 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 변경 전 AiCharacterAdminCommunityPostCreateTestAiCharacterAdminCommunityPostUpdateTest에 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는 각 @RequestPartrequest 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, ktlintCheckBUILD 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-210AiCharacterAdminCommunityPostUpdateTest.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 요구사항 충족, 추가 수정 없음.

남은 항목: 없음.