Files
voiceon-character-admin/docs/20260725_AI캐릭터관리자웹/reviews/review-phase-0-1.md
2026-07-27 23:23:36 +09:00

51 KiB
Raw Blame History

AI 캐릭터 관리자 웹 Phase 0·1 코드 리뷰·QA 리포트

1. 리뷰 정보

항목 내용
리뷰 대상 docs/20260725_AI캐릭터관리자웹/plan-task.md의 Phase 0, Phase 1 및 대응 구현
기준 commit 또는 working tree f8a313031c 기준, 리뷰 시작 시 기존 변경 36개 경로
리뷰 일자 2026-07-27
리뷰어 Codex와 독립 판정 sub-agent
기준 문서 docs/20260725_AI캐릭터관리자웹/prd.md, api-contract.md, plan-task.md
리뷰 기준 docs/agent-guide/review.md, docs/sample/sample-review.md
리뷰 상태 판정 완료, 후속 수정 미실행
최종 결론 수정 goal 필요

리뷰 시작 시 워킹 트리는 사용자 작업을 포함해 이미 변경 상태였다. 기존 변경은 리뷰 기준선으로 보존했으며, 코드와 기존 문서는 수정하지 않았다. 이 파일만 신규 리뷰 산출물로 작성했다.

리뷰 파일은 현재 저장 규칙에 따라 docs/20260725_AI캐릭터관리자웹/reviews/ 아래에 둔다.

2. 리뷰 목적과 범위

2.1 목적

  • Phase 0·1의 완료 체크가 PRD, API 계약, 구현, 테스트 증거와 일치하는지 확인한다.
  • 정적 코드 검토만으로 끝내지 않고 문서에 명시된 Gate와 실제 브라우저 시나리오를 실행한다.
  • 통과한 자동화가 놓치는 계약, 접근 제어, 파일 처리, 접근성 회귀를 확인한다.
  • 확정 문제는 수정하지 않고 재현 가능한 회귀 Task와 goal 초안까지만 남긴다.

2.2 포함 범위

  • 코드: src/app, src/features/auth, src/layouts, src/shared와 관련 구성 파일
  • 테스트: src/app/App.test.tsx, src/shared, src/features/auth, src/layouts, tests/e2e
  • 문서: prd.md, api-contract.md, plan-task.md의 Phase 0·1 완료 조건과 누적 기록
  • 수동 검증: 실제 브라우저의 인증, 파일 처리, modal, keyboard, 접근성 동작

2.3 제외 범위

  • Phase 2 이후 도메인 CRUD 구현
  • 백엔드 내부 구현과 운영 데이터
  • 확정 문제의 코드 수정
  • 기존 PRD, API 계약, 계획 문서의 직접 수정
  • 디자인 정성 평가와 실제 보조기기 수동 테스트

3. 판정 기준

3.1 심각도

심각도 기준
Blocker Phase 완료 판정이나 출고 판단을 무효화하거나 핵심 보호 경계를 신뢰할 수 없게 만드는 문제
High 권한, 핵심 계약, 주요 사용자 흐름 또는 파일 결과를 잘못 처리하는 문제
Medium 접근성, 중복 요청, 컴포넌트 상호작용 등 중요한 품질 계약 위반
Low 현재 영향은 제한적이지만 회귀 방지나 문서·검증 신뢰도를 약화하는 문제

3.2 상태

상태 의미 후속 처리
후보 근거를 발견했지만 아직 재현·판정하지 않음 검증 후 상태 변경
확정 코드, 테스트, 문서 또는 실행으로 문제가 확인됨 회귀 수정 Task·goal 후보
오탐 요구사항이나 실행 결과상 문제가 아님 근거를 남기고 종료
보류 외부 계약·환경·제품 결정이 필요함 담당 주체와 재개 조건 기록
수정 완료 수정과 관련 검증이 완료됨 명령과 결과 연결

4. 검토한 근거

4.1 문서와 코드

  • 요구사항: prd.md의 프로젝트 기반, AUTH, FILE, ERROR, 접근성 및 성공 기준
  • API 계약: api-contract.md의 인증, 관리자 목록 probe, 이미지·음성 파일 계약
  • 계획: plan-task.md의 P0-T1P0-GATE, P1-T1P1-GATE와 누적 구현 기록
  • 코드: src/app, src/features/auth, src/layouts, src/shared 및 Vite·Playwright·test 구성
  • 테스트: App unit, shared·auth·layout unit, smoke·auth·accessibility E2E

4.2 실행 환경

항목
OS macOS 26.0, Darwin 25.0 x86_64
Node.js v24.12.0
npm 11.7.0
Playwright 1.61.1
브라우저 프로젝트 Chromium, Mobile Chrome, WebKit, Mobile Safari
API mode 저장소의 .env.development 및 .env.production

8888 포트에는 리뷰 시작 전부터 같은 저장소의 Vite 서버가 실행 중이었다. 공식 E2E는 Playwright의 기존 서버 재사용 설정으로 수행했고, 별도로 8889 포트에 새 Vite 서버를 띄워 루트 접근, /login 이동, html lang=ko, main landmark 1개, 제목 관리자 로그인을 다시 확인한 뒤 해당 서버만 종료했다.

4.3 실행한 자동 검증

명령 종료 결과
npm ci 0 307 packages 설치
npx playwright install chromium webkit 0 대상 브라우저 설치 확인
npm run typecheck 0 오류 0건, 최종 재실행도 통과
npm run lint 0 오류 0건, 최종 재실행도 통과
npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts 0 2 files, 14 tests 통과
npm run e2e -- tests/e2e/smoke.spec.ts 1 최초 sandbox 실행에서 127.0.0.1:8888 listen EPERM으로 실행 불가
npm run e2e -- tests/e2e/smoke.spec.ts 0 권한 허용 후 4 projects, 4 tests 통과
npm run build:dev 0 157 modules, JS 292.99 kB, gzip 88.18 kB
npm run build:prod 0 157 modules, JS 292.99 kB, gzip 88.18 kB
npm run test:run -- src/shared src/features/auth src/layouts 0 25 files, 79 tests 통과
npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts 0 4 projects, 16 tests 통과
npm run build 0 production build, 157 modules
npm run test:run 0 전체 27 files, 96 tests 통과
npx vitest list src/shared src/features/auth src/layouts 0 79 tests 목록화, src/app/App.test.tsx는 포함되지 않음
npm exec vite -- --host 127.0.0.1 --port 8889 --strictPort 검증 후 종료 새 서버 ready 687ms, 루트 접근 뒤 /login 이동 확인
node --input-type=module -e Playwright 루트 셸 검증 스크립트 0 url=/login, lang=ko, main=1, heading=관리자 로그인
git diff --check 0 공백 오류 없음

Phase 1 접근성 E2E에는 320×640 viewport와 CSS zoom 2 조건, axe critical·serious 위반 0건 확인이 포함돼 있다. 자동화는 모두 최종 통과했지만, 아래 실제 재현 항목은 현재 테스트가 검사하지 않아 통과 결과만으로 완료 조건을 충족했다고 볼 수 없다.

4.4 실제 브라우저·런타임 재현

임시 Playwright 및 브라우저 모듈 스크립트는 파일을 만들지 않고 실행했다.

시나리오 실제 결과 판정 연결
저장된 ADMIN 세션 + 목록 probe를 1.5초 뒤 403으로 응답 403 전 /ai-characters에서 보호 셸과 main이 보였고, 응답 뒤 /access-denied로 이동 REV-P1-002
로그인 400 응답에 한국어 message 제공 화면에는 서버 message 대신 로그인에 실패했습니다. 표시 REV-P1-006
지연된 logout 응답 중 버튼 double click POST /member/logout 2회 발생 REV-P1-007
1200×600 이미지를 1:1, no-upscale로 처리 800×800 결과, 하단 픽셀 alpha 0으로 200px 투명 영역 발생 REV-P1-003
음성 확장자·MIME 불일치 조합 mp3+audio/mp4, aac+audio/mpeg, m4a+audio/aac가 모두 허용됨 REV-P1-004
ConfirmDeactivateDialog를 외부 버튼에서 열기 초기 focus가 외부에 남고 Tab으로 dialog 밖 이동 REV-P1-005
FileField error 렌더링 aria-describedby와 alert는 있으나 aria-invalid는 null REV-P1-008
AdminAudioPlayer 속도 select에서 Enter 재생 토글이 호출됨 REV-P1-009

5. 발견 사항 요약

ID 심각도 상태 제목 소유 Task 후속 goal
REV-P1-001 Blocker 수정 완료 Phase 1 완료 표시가 누락 산출물·통합·Gate 증거와 충돌 R1.1 P1-R1
REV-P1-002 High 수정 완료 stale ADMIN 확인 전 보호 셸 노출과 계약 밖 probe size 사용 R1.3 P1-R3
REV-P1-003 High 수정 완료 이미지 crop이 잘못된 크기·투명 영역을 생성 R1.4 P1-R4
REV-P1-004 High 수정 완료 음성 확장자와 MIME의 허용 조합을 강제하지 않음 R1.4 P1-R4
REV-P1-006 High 수정 완료 로그인 서버 한국어 오류 message를 일반 문구로 폐기 R1.3 P1-R3
REV-P1-010 High 수정 완료 image 10MB의 정확한 byte 계약을 확정하지 않고 완료 처리 R1.2 P1-R2
REV-P1-005 Medium 수정 완료 Phase 1 modal의 focus trap·trigger 복귀 계약 미구현 R1.5 P1-R5
REV-P1-007 Medium 수정 완료 logout 연속 입력으로 요청이 중복 전송됨 R1.3 P1-R3
REV-P1-008 Medium 수정 완료 FileField 오류 상태에 aria-invalid가 없음 R1.5 P1-R5
REV-P1-009 Medium 수정 완료 audio player 하위 control의 key가 재생 토글로 전파됨 R1.5 P1-R5
REV-P0-001 Low 수정 완료 테스트 정리가 stubbed global과 mock 구현을 완전히 복원하지 않음 R0.1 P0-R1
REV-P0-002 Low 수정 완료 루트 셸 unit 회귀 조건 중 main과 root content 검증이 없음 R0.1 P0-R1
REV-P0-003 Low 수정 완료 Playwright URL 주입·문서 링크 체크와 실제 구성·기록이 불일치 R0.1 P0-R1
REV-P0-004 Low 오탐 ignored mise.toml만으로 fresh setup 실패를 확정할 수 없음 없음 없음

수정 후 상태는 수정 완료 13건, 오탐 1건이다. 열린 Blocker·High·Medium 항목은 없다.

6. 발견 사항 상세

REV-P1-001 — Phase 1 완료 표시가 필수 산출물·통합·Gate 증거와 충돌

  • 심각도: Blocker
  • 상태: 확정
  • 관련 요구사항: Phase 1 완료 조건, 공유 UI 산출물·소비 계약
  • 관련 계약: 없음
  • 소유 Task: 신규 R1.1, goal P1-R1
  • 근거:
    • plan-task.md:275-279에서 Phase 1을 완료로 표시한다.
    • 최초 plan-task.md는 src/shared/ui/page-header.tsx를 산출물·소비 관계로 선언했지만 해당 파일은 존재하지 않았다.
    • 최초 plan-task.md는 모든 Task 1.5 컴포넌트를 독립 테스트하고 Admin shell에서 최소 1회 조합했다고 완료 처리했다.
    • 실제 앱 조합 지점인 src/app/admin-pages.tsx:1-35에서는 PageState만 사용하고 나머지 Task 1.5 공유 컴포넌트는 앱·feature·layout에서 소비되지 않는다.
    • Task 1.5 구현 기록과 plan-task.md:438-454의 P1-GATE 종합 실행 기록이 없다.
    • P1-GATE 명령의 대상은 src/shared, src/features/auth, src/layouts라서 보호 route와 셸을 검증하는 src/app/App.test.tsx 11개가 제외된다. 실제 test list에서도 제외를 확인했다.
  • 재현 또는 검증:
    • page-header.tsx 존재 여부와 전체 import를 검색하고, P1-GATE에 App test가 포함되는지 비교한다.
  • 테스트 근거:
    • 최초 Phase 1 명령이 나열한 79 tests에는 src/app/App.test.tsx의 11 tests가 없고, PageHeader 또는 모든 Task 1.5 컴포넌트의 shell 소비를 증명하는 test도 없었다.
  • 영향:
    • Phase 1 완료 여부와 다음 Phase 진입 판단을 신뢰할 수 없다.
    • 누락된 통합 및 보호 route 회귀가 Gate 통과로 오인된다.
  • 권고:
    • 기존 완료 체크를 되돌리지 말고 P1-R1 회귀 Task를 추가한다.
    • PageHeader 계약을 구현하거나 폐기 결정을 문서화하고, 현재 shell에서 실제 소비되는 공유 컴포넌트의 통합 증거를 남긴다.
    • P1-GATE에 src/app 검증을 포함하고 누적 실행 기록을 추가한 뒤에만 회귀 goal을 완료한다.
  • 판정 기록:
    • 2026-07-27 — 파일 존재, import 소비, test list, 누적 기록을 교차 확인해 완료 판정을 무효화하는 Blocker로 확정했다.
    • 2026-07-27 — PageHeader는 구현·소비가 없고 현재 필요한 반복 소비 컴포넌트가 아니므로 신규 구현하지 않고 Phase 1 shared 산출물에서 제거하기로 결정했다. 현재 Admin shell 통합 증거는 실제 소비 중인 PageStatesrc/app/App.test.tsx 포함 Gate로 확인한다.
    • 2026-07-27 — P1-R2 완료 후 closure Gate를 재실행했고 PageHeader 폐기 결정과 PageState shell 소비 증거를 plan-task.md에 누적해 수정 완료로 닫았다.

REV-P1-002 — stale ADMIN 판정 전 보호 셸 노출과 계약 밖 probe size

  • 심각도: High
  • 상태: 확정
  • 관련 요구사항: AUTH-003, AUTH-006, 오류·권한 처리
  • 관련 계약: api-contract.md:52-54, 63-65
  • 소유 Task: 신규 R1.3, goal P1-R3
  • 근거:
    • src/app/App.tsx:172-211은 보호 목록 probe를 비동기로 시작한다.
    • src/app/App.tsx:225-233은 저장된 role이 ADMIN이면 probe 응답 전에 ProtectedAdminShell을 즉시 렌더링한다.
    • 1.5초 지연 403 재현에서 응답 전 보호 heading과 main이 보였고, 응답 후에야 /access-denied로 이동했다.
    • prd.md:160-176, 588-590과 api-contract.md:52-54는 stale ADMIN의 403 차단을 요구한다.
  • 재현 또는 검증:
    • sessionStorage에 ADMIN 세션을 넣고 관리자 목록 요청을 1.5초 뒤 403으로 응답시켜 응답 전후 URL과 보호 DOM을 비교한다.
  • 테스트 근거:
    • src/app/App.test.tsx:139-149는 403 뒤 최종 이동만 확인하며 응답 전 보호 DOM 비노출과 query size를 검사하지 않는다.
  • 영향:
    • 서버가 권한을 거부한 세션에서도 보호 화면이 일시 노출된다. 현재 Phase 1 셸은 빈 상태지만 이후 데이터가 연결되면 노출 범위가 커진다.
  • 추가 관찰:
    • 이 finding의 확정 범위에는 probe가 size=1을 사용해 prd.md:580의 허용 최소 size 20을 위반하는 문제도 포함한다.
  • 추가 영향:
    • 공통 pagination 요청 정책을 우회하고 서버가 size를 최소 20으로 보정하므로 1건만 조회한다는 probe 의도가 성립하지 않는다.
  • 권고:
    • 권한 확인 중에는 보호 셸을 렌더링하지 않는 명시적 pending 상태를 둔다.
    • probe size를 20으로 맞추거나 공통 pagination helper를 사용한다.
    • 지연 403 전후 DOM을 검사하는 회귀 테스트를 먼저 추가한다.
  • 판정 기록:
    • 2026-07-27 — 지연 403 전 보호 셸 노출을 실제 브라우저에서 재현하고 size query를 계약과 대조해 두 문제를 하나의 인증 probe finding으로 확정했다.

REV-P1-003 — 이미지 crop 결과 계산과 렌더링 오류

  • 심각도: High
  • 상태: 확정
  • 관련 요구사항: 이미지 crop·자유 비율·pointer 조작·성공 기준
  • 관련 계약: api-contract.md:121-136
  • 소유 Task: 신규 R1.4, goal P1-R4
  • 근거:
    • src/shared/lib/crop-image.ts:26-29는 sourceWidth만으로 출력 크기를 계산하고 sourceHeight와 선택 crop 영역을 사용하지 않는다.
    • src/shared/lib/crop-image.ts:45-47은 source crop rectangle 없이 원본 전체를 출력 canvas에 그린다.
    • 1200×600 입력을 1:1 no-upscale로 처리하면 올바른 최대 정사각형은 600×600이어야 하나 800×800이 생성됐고 하단 200px이 투명했다.
    • src/shared/ui/image-crop-dialog.tsx:13-17은 숫자 aspect만 받고 자유 비율 선택을 제공하지 않는다.
    • src/shared/ui/image-crop-dialog.tsx:56-84에는 keyboard 조작만 있고 pointer drag·pinch 조작이 없다.
    • prd.md:323-331, 342-344, 476, 701-706과 api-contract.md:121-136의 crop 결과·조작 계약과 충돌한다.
  • 재현 또는 검증:
    • 1200×600 단색 PNG를 1:1, max 800, no-upscale로 처리하고 출력 dimensions와 상·하단 alpha를 읽는다.
  • 테스트 근거:
    • src/shared/lib/crop-image.test.ts:6은 현재 잘못된 800×800 결과를 기대하고 실제 pixel·alpha를 검사하지 않는다.
    • src/shared/ui/tests/image-crop-dialog.test.tsx는 정사각형 입력과 button·keyboard만 다루며 자유 비율과 pointer 조작을 검사하지 않는다.
  • 영향:
    • 업로드 이미지의 실제 내용, 비율, 크기와 투명도가 잘못될 수 있다.
  • 권고:
    • source crop rectangle을 단일 진실 원천으로 계산·렌더링한다.
    • 실제 픽셀과 alpha를 확인하는 비정사각형 이미지 회귀 테스트를 먼저 추가한다.
    • 자유 비율과 pointer 조작을 동일 상태 모델에 연결한다.
  • 판정 기록:
    • 2026-07-27 — 1200×600 입력에서 800×800 출력과 하단 alpha 0을 재현하고 UI 조작 경로를 대조해 확정했다.

REV-P1-004 — 음성 확장자·MIME 허용 조합 미검증

  • 심각도: High
  • 상태: 확정
  • 관련 요구사항: FILE-013
  • 관련 계약: api-contract.md:492-500
  • 소유 Task: 신규 R1.4, goal P1-R4
  • 근거:
    • src/shared/validation/audio-file-policy.ts:4-22는 허용 확장자 집합과 MIME 집합을 독립적으로 검사하고 x-m4a만 별도 처리한다.
    • 실제 실행에서 voice.mp3+audio/mp4, voice.aac+audio/mpeg, voice.m4a+audio/aac가 모두 ok=true였다.
    • prd.md:332와 api-contract.md:492-500은 형식별 확장자·MIME 쌍을 정의한다.
  • 재현 또는 검증:
    • 브라우저 모듈에서 위 세 File 조합을 audio 정책 함수에 전달하고 반환값을 기록한다.
  • 테스트 근거:
    • src/shared/validation/file-media-policy.test.ts:39-54는 정상 조합과 x-m4a 예외만 다루며 canonical MIME이 다른 확장자와 결합된 거부 조건은 없다.
  • 영향:
    • 파일 내용 형식과 이름이 불일치해도 클라이언트 사전 검증을 통과한다.
  • 권고:
    • 명시적 확장자↔MIME 조합표를 단일 정책으로 만들고 허용·거부 table test를 먼저 추가한다.
  • 판정 기록:
    • 2026-07-27 — 계약상 불일치하는 세 조합이 모두 허용되는 것을 런타임 모듈 실행으로 재현해 확정했다.

REV-P1-005 — modal focus trap·trigger 복귀 계약 미구현

  • 심각도: Medium
  • 상태: 확정
  • 관련 요구사항: modal keyboard·focus 접근성
  • 관련 계약: 없음
  • 소유 Task: 신규 R1.5, goal P1-R5
  • 근거:
    • src/shared/ui/confirm-deactivate-dialog.tsx:9-33과 image-crop-dialog.tsx:31-130에는 focus trap과 trigger focus return 처리가 없다.
    • src/shared/ui/unsaved-changes-guard.tsx:21-25는 취소 시 trigger focus 복귀만 부분 구현한다.
    • 실제 ConfirmDeactivateDialog 재현에서 초기 focus가 외부 버튼에 남았고 Tab으로 dialog 밖으로 이동했다.
    • prd.md:509-511은 keyboard-only dialog 사용, focus trap, 닫힌 후 trigger 복귀를 요구한다.
    • initial focus와 Escape close도 현재 구현에 없지만 PRD의 명시 문구가 아니므로 이번 확정 판정의 근거에는 포함하지 않는다.
  • 재현 또는 검증:
    • 외부 trigger에 focus한 상태에서 dialog를 열고 activeElement, Tab 순환과 닫은 뒤 focus를 순서대로 관찰한다.
  • 테스트 근거:
    • confirm-deactivate-dialog.test.tsx는 copy와 click callback만, image-crop-dialog.test.tsx는 control 동작만 검사한다.
    • unsaved-changes-guard.test.tsx의 취소 focus 복귀 외에는 세 modal 공통 focus trap·복귀 test가 없다.
  • 영향:
    • keyboard와 보조기기 사용자가 modal 맥락을 잃거나 배경 UI로 이동할 수 있다.
  • 권고:
    • 세 modal의 focus trap과 trigger 복귀를 테스트로 먼저 고정하고 가장 작은 공통 primitive 또는 각 컴포넌트의 직접 구현으로 충족한다.
  • 판정 기록:
    • 2026-07-27 — 초기 focus와 Tab 이동을 실제 브라우저에서 확인하고 세 구현을 정적으로 대조해 확정했다.

REV-P1-006 — 로그인 서버 오류 message 폐기

  • 심각도: High
  • 상태: 확정
  • 관련 요구사항: 로그인 오류 처리, 서버 한국어 message 우선
  • 관련 계약: 공통 API error envelope
  • 소유 Task: 신규 R1.3, goal P1-R3
  • 근거:
    • src/shared/api/client.ts:103-113은 ApiError의 서버 message를 보존한다.
    • src/features/auth/pages/LoginPage.tsx:65-70은 모든 Error를 로그인에 실패했습니다.로 치환한다.
    • 서버가 이메일 또는 비밀번호가 올바르지 않습니다.를 반환한 400 재현에서도 일반 문구만 표시됐다.
    • prd.md:578, 588은 서버 한국어 message 우선 표시를 요구한다.
  • 재현 또는 검증:
    • login API를 한국어 message가 있는 400으로 intercept하고 화면 alert text를 응답 message와 비교한다.
  • 테스트 근거:
    • src/features/auth/tests/login-page.test.tsx:62-74는 실패 후 button 재활성화만 확인하며 rejection message 표시를 검사하지 않는다.
  • 영향:
    • 사용자가 서버가 제공한 구체적 복구 정보를 받지 못하고 프런트·백엔드 계약이 끊긴다.
  • 권고:
    • ApiError message를 우선 사용하고 message가 없을 때만 안전한 fallback을 사용한다.
  • 판정 기록:
    • 2026-07-27 — 400 응답의 서버 message와 화면 표시를 비교해 소실을 확정했다.

REV-P1-007 — logout 중복 요청

  • 심각도: Medium
  • 상태: 확정
  • 관련 요구사항: logout 단일 요청
  • 관련 계약: api-contract.md:210
  • 소유 Task: 신규 R1.3, goal P1-R3
  • 근거:
    • src/app/App.tsx:130의 logout 버튼에는 pending disable이 없다.
    • src/features/auth/model/auth-session.tsx:40-57에는 in-flight 요청을 합치거나 막는 guard가 없다.
    • 지연 응답 중 double click 재현에서 POST /member/logout이 2회 발생했다.
    • prd.md:659, api-contract.md:210과 plan-task.md:380은 logout API 한 번 호출을 요구한다.
  • 재현 또는 검증:
    • logout 응답을 지연시키고 버튼을 double click한 뒤 route handler 호출 수를 계수한다.
  • 테스트 근거:
    • src/features/auth/tests/auth-session.test.tsx:133-169는 각 응답별 single click 결과만 확인하며 in-flight 연속 입력의 request 수를 검사하지 않는다.
  • 영향:
    • 중복 네트워크 요청과 중복 오류 처리가 발생할 수 있다.
  • 권고:
    • session layer의 단일 in-flight 보장과 버튼 pending 상태를 회귀 테스트로 고정한다.
  • 판정 기록:
    • 2026-07-27 — 지연 응답 중 double click으로 logout POST 2회를 계수해 확정했다.

REV-P1-008 — FileField 오류 상태의 aria-invalid 누락

  • 심각도: Medium
  • 상태: 확정
  • 관련 요구사항: 필드 오류 접근성
  • 관련 계약: 없음
  • 소유 Task: 신규 R1.5, goal P1-R5
  • 근거:
    • src/shared/ui/file-field.tsx:38, 45는 오류 설명 연결과 alert를 제공하지만 input에 aria-invalid를 설정하지 않는다.
    • 실제 오류 렌더링에서 aria-invalid 값은 null이었다.
    • prd.md:490은 필드 오류 시 aria-invalid와 설명 연결을 함께 요구한다.
  • 재현 또는 검증:
    • error prop이 있는 FileField를 렌더링하고 input의 aria-invalid, aria-describedby와 alert를 읽는다.
  • 테스트 근거:
    • src/shared/ui/tests/file-field.test.tsx:6-27은 accessible description을 확인하지만 aria-invalid assertion이 없다.
  • 영향:
    • 보조기기가 파일 입력의 유효하지 않은 상태를 일관되게 전달하지 못한다.
  • 권고:
    • error 존재 여부를 aria-invalid에 연결하고 접근성 이름·설명·상태를 함께 검증한다.
  • 판정 기록:
    • 2026-07-27 — error 상태 input의 aria-invalid=null을 실제 렌더링으로 확인해 확정했다.

REV-P1-009 — audio player 하위 control key 전파

  • 심각도: Medium
  • 상태: 확정
  • 관련 요구사항: keyboard-only 조작과 control event 경계
  • 관련 계약: 없음
  • 소유 Task: 신규 R1.5, goal P1-R5
  • 근거:
    • src/shared/ui/admin-audio-player.tsx:58-63의 group key handler가 event target을 구분하지 않고 Enter·Space를 재생 토글로 처리한다.
    • 속도 select에 focus한 뒤 Enter를 누른 실제 재현에서 play가 1회 호출됐다.
  • 재현 또는 검증:
    • 속도 select에 focus하고 Enter를 누른 뒤 mocked audio play 호출 수를 확인한다.
  • 테스트 근거:
    • src/shared/ui/tests/admin-audio-player.test.tsx:58-77은 group 자체에 Space를 보내며 하위 combobox·button의 key 격리를 검사하지 않는다.
  • 영향:
    • select나 button을 keyboard로 조작할 때 의도하지 않은 재생·정지가 함께 발생한다.
  • 권고:
    • container 자체가 target일 때만 shortcut을 처리하거나 descendant interactive control을 명시적으로 제외한다.
  • 판정 기록:
    • 2026-07-27 — 속도 select의 Enter 입력이 play를 호출하는 것을 실제 브라우저에서 재현해 확정했다.

REV-P1-010 — image 10MB exact byte 계약 미확정 상태로 완료 처리

  • 심각도: High
  • 상태: 확정
  • 관련 요구사항: image 최대 용량과 첫 파일 Phase 계약 확정
  • 관련 계약: api-contract.md:119
  • 소유 Task: 신규 R1.2, goal P1-R2
  • 근거:
    • plan-task.md:101은 첫 파일 Phase에서 모호한 10MB를 정확한 byte 값으로 확정하도록 요구한다.
    • plan-task.md:426은 백엔드와 값을 맞추고 문서·경계 테스트에 기록했다고 완료 처리한다.
    • 최초 prd.md:320과 api-contract.md:119에는 10MB만 있고 10,000,000인지 10,485,760인지 확정 근거가 없었다.
    • src/shared의 10×1024×1024 테스트 값은 주입 예시일 뿐 채택된 백엔드 계약 증거가 아니다.
  • 재현 또는 검증:
    • 계획의 완료 체크와 PRD, API 계약, 구현 상수, 경계 테스트에서 exact byte와 출처를 검색해 서로 비교한다.
  • 테스트 근거:
    • src/shared/validation/file-media-policy.test.ts:27-37의 10×1024×1024는 caller 주입 예시이며 채택된 image domain 상수나 backend 계약 출처를 검증하지 않는다.
  • 영향:
    • 경계 파일에서 프런트와 백엔드의 허용 결과가 달라질 수 있고 완료 체크를 재현할 수 없다.
  • 외부 의존성:
    • 이 finding에서 확정한 문제는 exact byte 근거가 없는데도 완료 처리한 사실이다.
    • 정확한 숫자 값 자체는 이번 판정 대상이 아니며 백엔드 소유자 확인 전 추정하지 않는다. 값이 확인되기 전에는 R1.2를 완료할 수 없다.
  • 권고:
    • 백엔드 exact byte를 확인한 뒤 PRD, API 계약, 구현 상수, 경계 테스트를 같은 Task에서 정렬한다.
  • 판정 기록:
    • 2026-07-27 — 완료 체크와 PRD·API 계약·테스트 근거를 비교해 근거 없는 완료 처리를 확정했다. 실제 숫자 값은 백엔드 확인이 필요한 외부 입력으로 분리했다.
    • 2026-07-27 — 백엔드 exact byte를 10,485,760 bytes로 확정하고 PRD, API 계약, IMAGE_MAX_BYTES, limit-1/limit/limit+1 경계 테스트를 같은 값으로 정렬했다.

REV-P0-001 — 공통 테스트 정리의 global·mock 복원 누락

  • 심각도: Low
  • 상태: 확정
  • 관련 요구사항: Phase 0 test isolation
  • 관련 계약: 없음
  • 소유 Task: 신규 R0.1, goal P0-R1
  • 근거:
    • src/shared/test/setup.ts:11-17은 vi.clearAllMocks()만 호출한다.
    • src/features/auth/tests/auth-session.test.tsx:78은 indexedDB를 stubGlobal로 교체하지만 공통 정리에 unstubAllGlobals가 없다.
    • clearAllMocks는 호출 이력만 지우고 stubbed global이나 mock 구현을 복원하지 않는다.
    • plan-task.md:228은 각 테스트 뒤 DOM, mock, storage가 깨끗해야 한다고 명시한다.
  • 재현 또는 검증:
    • 공통 afterEach 호출과 stubGlobal 사용 지점을 대조하고 후속 테스트 전에 unstub 경로가 있는지 검색한다.
  • 테스트 근거:
    • 전체 96 tests는 통과하지만 stubbed indexedDB가 후속 test 전에 원복되는지를 직접 증명하는 격리 test가 없다.
  • 영향:
    • 같은 파일의 후속 테스트가 이전 global 또는 mock 구현에 의존할 수 있다. 현재 Vitest 파일 격리가 파급을 줄이지만 계약 공백은 남는다.
  • 권고:
    • 누출을 증명하는 실패 테스트를 먼저 만들고 restoreAllMocks·unstubAllGlobals 등 필요한 최소 정리를 추가한다.
  • 판정 기록:
    • 2026-07-27 — 공통 cleanup과 실제 stubGlobal 사용을 대조해 복원 공백을 확정했다.

REV-P0-002 — 루트 셸 unit 회귀 조건 누락

  • 심각도: Low
  • 상태: 확정
  • 관련 요구사항: Phase 0 root shell
  • 관련 계약: 없음
  • 소유 Task: 신규 R0.1, goal P0-R1
  • 근거:
    • plan-task.md:224, 231은 root unit에서 html lang, main landmark, root content를 검증하도록 요구한다.
    • src/app/App.test.tsx:46-50의 root test는 lang만 확인한다.
    • tests/e2e/smoke.spec.ts:3-8도 lang과 login heading은 확인하지만 heading이 main 안에 있는지 검증하지 않는다.
  • 재현 또는 검증:
    • Phase 0 계획의 root assertion 목록을 App root test와 smoke test의 실제 assertion에 하나씩 대응한다.
  • 테스트 근거:
    • src/app/App.test.tsx:46-50은 lang만, tests/e2e/smoke.spec.ts:3-8은 lang과 login heading만 확인해 main 안의 root content 관계가 비어 있다.
  • 영향:
    • 루트 main 또는 콘텐츠 배치가 깨져도 명시된 Phase 0 unit 회귀 조건이 감지하지 못한다.
  • 권고:
    • root render에서 main과 대표 콘텐츠의 포함 관계를 직접 검증한다.
  • 판정 기록:
    • 2026-07-27 — 계획의 세 assertion과 root unit·smoke assertion을 대조해 누락을 확정했다.

REV-P0-003 — Phase 0 Playwright 구성·기록 불일치

  • 심각도: Low
  • 상태: 확정
  • 관련 요구사항: Phase 0 환경·검증 재현성
  • 관련 계약: test API base URL
  • 소유 Task: 신규 R0.1, goal P0-R1
  • 근거:
    • plan-task.md:215는 Playwright webServer가 test API URL을 명시적으로 주입한다고 완료 처리한다.
    • playwright.config.ts:5-9의 command는 npm run dev이며 env를 주입하지 않고 .env.development 로딩에 의존한다.
    • plan-task.md:267은 다시 mode 파일 사용을 현재 전략으로 기록해 같은 계획 안에서도 설명이 다르다.
    • 최초 plan-task.md:265가 연결한 docs/agent-guide/README.md는 현재 존재하지 않았다.
  • 재현 또는 검증:
    • config의 webServer command와 mode 파일을 읽고 계획의 두 기록을 비교한 뒤 링크 대상 파일 존재를 검사한다.
  • 테스트 근거:
    • smoke 4 projects와 dev·prod build는 현재 mode URL 동작만 확인하며 명시 env 주입 또는 문서 링크 존재를 검증하지 않는다.
  • 영향:
    • 체크만 보고는 E2E API 대상과 Phase 0 증거 문서를 재현하기 어렵다. 현재 mode별 URL 자체는 정상이다.
  • 권고:
    • 채택 전략을 하나로 명시하고 실행 가능한 링크·명령과 일치시키는 보정 기록을 기존 완료 체크 아래에 추가한다.
  • 판정 기록:
    • 2026-07-27 — 현재 config, 계획 체크, 후속 기록과 문서 경로를 비교해 문서·증거 정합성 문제로 확정했다.
    • 2026-07-27 — plan-task.md의 존재하지 않는 docs/agent-guide/README.md 참조를 실제 docs/agent-guide/ 세부 문서 관리 방식으로 정정했다.

REV-P0-004 — ignored mise.toml이 fresh setup을 깨뜨린다는 후보

  • 심각도: Low
  • 상태: 오탐
  • 관련 요구사항: Phase 0 fresh setup
  • 관련 계약: 없음
  • 소유 Task: 없음
  • 근거:
    • mise.toml은 ignore 대상이라 저장소만 새로 받은 환경에는 포함되지 않을 수 있다.
    • 그러나 README가 Node.js 24.12.0을 명시하므로 mise.toml 부재만으로 Phase 0 fresh setup 실패를 확정할 수 없다.
  • 재현 또는 검증:
    • ignore 상태와 README의 설치 전제 문서를 함께 확인한다.
  • 테스트 근거:
    • 별도 test 대상은 아니며 README의 Node 24.12.0 설치 전제가 대체 재현 경로다.
  • 결론:
    • 현재 계약 위반으로 확정하지 않으며 회귀 Task를 만들지 않는다.
  • 판정 기록:
    • 2026-07-27 — README의 Node 24.12.0 명시가 대체 재현 경로를 제공하므로 오탐으로 종료했다.

7. 회귀 수정 Task와 goal 초안

아래 내용은 최초 리뷰 당시 plan-task.md에 반영하지 않은 초안이다. 2026-07-27 수정 실행 후 실제 수행·보류 상태는 plan-task.md에 누적했고, 이 절의 체크박스는 해당 실행 결과와 맞춰 정정한다. 기존 완료 체크는 되돌리지 않고, 후속 수정 시 해당 Phase 아래에 회귀 Task와 누적 검증 기록으로 추가한다.

Task R0.1 — 테스트 격리와 Phase 0 증거 정합성 복구

Goal 실행 P0-R1: Phase 0 테스트 격리와 루트 셸·Playwright 증거 정합성을 복구하고 회귀를 방지한다.

  • 연결 리뷰: REV-P0-001, REV-P0-002, REV-P0-003
  • 시작 조건:
    • 세 리뷰 ID와 현재 재현 근거를 Task 본문에 연결한다.
    • 기존 사용자 변경과 리뷰 기준 커밋을 보존한다.
  • 완료 증거:
    • stubbed global 누출을 보여주는 실패 테스트
    • root render의 main과 대표 content 실패 테스트
    • 채택한 Playwright API URL 전략과 실제 config가 일치한다는 검증
    • focused test, P0-GATE, smoke 4 projects 결과와 누적 기록
  • 범위 밖:
    • Phase 1 인증 동작 변경
    • 전체 테스트 프레임워크 교체
  • 실행 체크:
    • 실패하는 격리·루트 셸 테스트를 먼저 추가한다.
    • 공통 cleanup과 root assertion을 최소 변경으로 충족한다.
    • Playwright env 전략과 끊어진 문서 링크를 보정 기록으로 정렬한다.
    • focused test와 P0-GATE를 실행한다.
    • 명령, 종료 코드, 테스트 수를 plan-task.md에 누적 기록한다.

create_goal objective 초안 — P0-R1

  • objective: P0-R1의 확정 review 항목 REV-P0-001, REV-P0-002, REV-P0-003을 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R0.1만 수행한다.
  • 완료 조건: 위 완료 증거가 모두 존재하고 P0-GATE가 통과하며 plan-task.md에 누적 기록이 추가된 상태다.
  • 금지 조건: 기존 완료 체크를 해제하거나 관련 없는 리팩터링을 포함하지 않는다.
  • 중단 조건: 채택할 env 전략이 기존 PRD와 충돌하면 임의 결정하지 않고 문서 소유자 확인 상태를 기록한다.

Task R1.1 — Phase 1 산출물·통합·Gate 완료 증거 복구

Goal 실행 P1-R1: Phase 1 완료 판정을 재현 가능한 산출물·통합·Gate 증거로 복구한다.

  • 연결 리뷰: REV-P1-001
  • 시작 조건:
    • P0-R1과 P1-R2~P1-R5의 수정·검증 기록이 완료돼 Phase 1 closure Gate를 실행할 수 있다.
    • PageHeader 폐기 결정과 공유 UI 소비 계약을 PRD·계획 기준으로 확인한다.
    • 현재 P1-GATE가 src/app을 제외한다는 실패 증거를 보존한다.
  • 완료 증거:
    • PageHeader 계약의 승인된 폐기 결정 기록
    • 선언된 Task 1.5 컴포넌트의 실제 Admin shell 소비 테스트
    • src/app/App.test.tsx를 포함하는 P1-GATE
    • Task 1.5 구현 기록과 P1-GATE 누적 실행 기록
    • REV-P1-001~010의 확정 항목에 열린 Blocker·High·Medium 상태가 없다는 closure 확인
  • 범위 밖:
    • Phase 2 도메인 CRUD
    • 공유 UI의 전면 재설계
  • 실행 체크:
    • P0-R1과 P1-R2~P1-R5의 완료 기록과 잔여 finding을 먼저 확인한다.
    • 누락 산출물·소비·Gate 범위를 드러내는 검증을 수행하고 PageHeader 폐기 결정을 문서화한다.
    • 승인된 계약에 맞춰 PageHeader를 신규 구현하지 않고 계획 산출물·소비 Matrix에서 제거한다.
    • focused test와 보정된 P1-GATE를 실행한다.
    • 320px, zoom 2, axe 결과를 다시 확인한다.
    • 구현·Gate 명령과 결과를 plan-task.md에 누적 기록한다.

create_goal objective 초안 — P1-R1

  • objective: P1-R1의 확정 review 항목 REV-P1-001을 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R1.1만 수행한다.
  • 완료 조건: P0-R1과 P1-R2~P1-R5 완료 후 누락 산출물 결정, 실제 소비 증거, src/app 포함 Gate, 누적 기록과 열린 Blocker·High·Medium 0건이 모두 확인된다.
  • 금지 조건: Phase 2 기능을 선행 구현하거나 기존 완료 체크를 되돌리지 않는다.
  • 중단 조건: PageHeader를 다시 요구하는 제품 결정이 나오면 현재 폐기 결정을 되돌리지 않고 별도 후속 Task로 추가한다.

Task R1.2 — image 최대 용량 exact byte 계약 확정

Goal 실행 P1-R2: image 10MB 표현을 백엔드와 동일한 exact byte 계약으로 확정한다.

  • 연결 리뷰: REV-P1-010
  • 시작 조건:
    • 백엔드 exact byte와 근거를 확보하기 전에는 이 goal을 생성하거나 시작하지 않는다.
    • 백엔드 소유자가 image 최대 byte를 명시적으로 확인한다.
    • 확인 전 10,000,000 또는 10,485,760을 임의 채택하지 않는다.
  • 완료 증거:
    • PRD와 API 계약의 exact byte 값
    • 같은 값을 사용하는 프런트 정책 상수
    • limit-1, limit, limit+1 경계 테스트
    • 실제 백엔드 계약 출처와 누적 검증 기록
  • 범위 밖:
    • 이미지 압축 정책 추가
    • 백엔드 업로드 구현 변경
  • 실행 체크:
    • 백엔드 exact byte와 근거를 확인한다.
    • 현재 모호성을 드러내는 경계 테스트를 먼저 추가한다.
    • 문서, 상수, 테스트를 하나의 값으로 정렬한다.
    • focused test와 P1-GATE를 실행한다.
    • 확인 출처와 실행 결과를 plan-task.md에 기록한다.

create_goal objective 초안 — P1-R2

  • objective: P1-R2의 확정 review 항목 REV-P1-010을 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R1.2만 수행한다.
  • 완료 조건: 소유자 확인, 두 계약 문서, 구현 상수, 세 경계 테스트, Gate 기록이 같은 값을 사용한다.
  • 금지 조건: 외부 확인 없이 값을 추정하거나 audio 용량 계약까지 확장하지 않는다.
  • 중단 조건: 시작 조건이 충족되지 않으면 goal을 생성하지 않고 Task에 외부 의존성·소유자·재개 조건만 기록한다.

Task R1.3 — 인증 보호·오류·logout 회귀 수정

Goal 실행 P1-R3: Phase 1 인증의 보호 경계, 오류 피드백, logout 단일 요청 계약을 복구한다.

  • 연결 리뷰: REV-P1-002, REV-P1-006, REV-P1-007
  • 시작 조건:
    • 지연 403 전 보호 셸 노출, 계약 밖 size=1 probe, 서버 message 소실, logout 2회 호출을 각각 실패 테스트로 재현한다.
  • 완료 증거:
    • stale ADMIN probe pending·403 동안 보호 셸 비노출
    • 목록 계약 범위 안의 probe size와 request assertion
    • 서버 한국어 message 우선 표시와 fallback
    • logout in-flight 중 API 1회 호출
    • focused auth/App test, auth E2E, P1-GATE 실행 기록
  • 범위 밖:
    • 인증 방식 또는 token 저장 방식 교체
    • Phase 2 권한 체계 추가
  • 실행 체크:
    • 네 회귀 조건을 각각 실패 테스트로 먼저 고정한다.
    • 보호 pending 상태, message 전달, 단일 in-flight를 최소 변경으로 구현한다.
    • probe size를 API 계약 범위로 맞춘다.
    • focused test, auth E2E, P1-GATE를 실행한다.
    • 결과를 plan-task.md에 누적 기록한다.

create_goal objective 초안 — P1-R3

  • objective: P1-R3의 확정 review 항목 REV-P1-002, REV-P1-006, REV-P1-007을 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R1.3만 수행한다.
  • 완료 조건: 네 실패 조건의 테스트가 수정 후 통과하고 auth E2E와 P1-GATE가 통과하며 누적 기록이 존재한다.
  • 금지 조건: 세션 아키텍처 전면 교체나 Phase 2 권한 기능을 포함하지 않는다.
  • 중단 조건: 서버 message 노출 정책이 문서와 다르다는 별도 보안 결정이 확인되면 해당 결정을 먼저 문서화한다.

Task R1.4 — crop·음성 파일 정책 회귀 수정

Goal 실행 P1-R4: Phase 1 파일·미디어 입력의 crop 결과와 audio 형식 조합 계약을 복구한다.

  • 연결 리뷰: REV-P1-003, REV-P1-004
  • 시작 조건:
    • 1200×600→1:1 no-upscale 픽셀 실패와 세 MIME 불일치 허용을 테스트로 재현한다.
  • 완료 증거:
    • source crop rectangle 기반 출력
    • 600×600 이하 no-upscale와 투명 영역 없는 픽셀 결과
    • 자유 비율과 pointer drag 조작
    • 확장자↔MIME 허용 조합 table test
    • focused shared test와 P1-GATE 기록
  • 범위 밖:
    • 외부 crop 라이브러리 도입
    • 서버 파일 변환 구현
  • 실행 체크:
    • 출력 크기·픽셀·alpha와 MIME 조합 실패 테스트를 먼저 추가한다.
    • crop 계산과 drawImage source rectangle을 최소 변경으로 정렬한다.
    • 자유 비율과 pointer 조작을 기존 dialog 상태에 연결한다.
    • 명시적 audio 조합표를 적용한다.
    • focused test와 P1-GATE를 실행하고 결과를 누적 기록한다.

create_goal objective 초안 — P1-R4

  • objective: P1-R4의 확정 review 항목 REV-P1-003, REV-P1-004를 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R1.4만 수행한다.
  • 완료 조건: 실제 픽셀 회귀, pointer 조작 회귀, MIME table test, P1-GATE가 모두 통과하고 기록된다.
  • 금지 조건: 새 의존성이나 서버 측 변환을 추가하지 않는다.
  • 중단 조건: browser별 canvas 차이가 발견되면 허용 오차를 추정하지 않고 재현 결과와 결정 필요 항목을 기록한다.

Task R1.5 — 공유 UI keyboard·focus 접근성 회귀 수정

Goal 실행 P1-R5: Phase 1 공유 UI의 modal focus, 필드 오류 상태, keyboard event 경계를 복구한다.

  • 연결 리뷰: REV-P1-005, REV-P1-008, REV-P1-009
  • 시작 조건:
    • modal focus 이탈, FileField aria-invalid 누락, audio child key 전파를 실패 테스트로 재현한다.
  • 완료 증거:
    • 세 modal의 focus trap과 닫힌 뒤 trigger focus return
    • FileField의 aria-invalid와 설명 연결
    • audio player descendant interactive control의 Enter·Space 격리
    • focused component test, 320×640 zoom 2, axe, P1-GATE 기록
  • 범위 밖:
    • 공유 UI 전체 교체
    • 시각 디자인 재작업
  • 실행 체크:
    • keyboard·focus 회귀 테스트를 먼저 추가한다.
    • 필요한 최소 focus trap·복귀 관리와 event target guard를 구현한다.
    • FileField 상태 속성을 보정한다.
    • focused test, 접근성 E2E, P1-GATE를 실행한다.
    • 결과를 plan-task.md에 누적 기록한다.

create_goal objective 초안 — P1-R5

  • objective: P1-R5의 확정 review 항목 REV-P1-005, REV-P1-008, REV-P1-009를 수정하고 회귀를 방지한다. plan-task.md에 추가할 Task R1.5만 수행한다.
  • 완료 조건: 세 회귀 묶음과 접근성 E2E, P1-GATE가 통과하고 plan-task.md 누적 기록이 존재한다.
  • 금지 조건: 접근성 primitive를 이유로 전면 UI 리팩터링을 수행하지 않는다.
  • 중단 조건: 공통 modal primitive 도입이 현재 세 컴포넌트 직접 수정보다 커지면 직접 수정안을 우선 비교하고 결정 근거를 기록한다.

8. 리뷰 종료 판정

판정 항목 결과 근거
리뷰 범위 전체 확인 충족 Phase 0·1 문서, 코드, 테스트와 완료 증거를 대조
후보 항목 판정 완료 충족 확정 13건, 오탐 1건으로 판정
확정 항목 plan 반영 충족 R0.1, R1.1~R1.5 실행 기록과 closure 결과를 plan-task.md와 이 보고서에 누적
외부 조건의 담당·재개 조건 기록 충족 REV-P1-010의 exact byte를 10,485,760 bytes로 확정하고 PRD·API·상수·경계 테스트를 정렬
검증 명령과 결과 기록 충족 exit code, test 수, 실패·불가 사유, 브라우저 관찰과 closure Gate 결과를 기록
Blocker·High 확정 이슈 0건 충족 수정 완료 13건, 오탐 1건이며 열린 Blocker·High·Medium 항목 없음
코드와 기존 문서 정합성 충족 P1-R1/R2 closure에서 코드와 PRD·API·plan·review를 같은 계약으로 정렬

초기 리뷰에서 확인된 계약 위반과 Phase 1 완료 증거 공백은 수정 goal로 정리됐다. Phase 1 완료 판정은 아래 수정 후 검증 기록의 최신 closure Gate를 기준으로 사용한다.

최종 결론: 수정 완료

남은 항목: 없음. Phase 2 이후 구현과 각 도메인의 외부 의존 계약은 기존 계획 범위대로 후속 Phase에서 다룬다.

9. 수정 후 검증 기록

기존 기록을 삭제하거나 덮어쓰지 않고 차수별로 누적한다.

수정 검증 미수행 — 2026-07-27

  • 무엇을: 코드와 기존 문서는 수정하지 않았고 회귀 Task·goal 초안만 작성했다.
  • 왜: 확정 문제를 수정하지 말라는 리뷰 범위 지시를 준수했다.
  • 어떻게:
    • 수정 전 기준 검증은 4절의 명령과 실제 브라우저 재현으로 완료했다.
    • 수정 후 focused test와 Phase Gate는 아직 실행 대상이 아니다.
  • 남은 항목: REV-P0-001003, REV-P1-001010 중 확정 13건과 P0-R1, P1-R1~R5 실행.

수정 검증 1차 — 2026-07-27

  • 무엇을: P0-R1, P1-R3, P1-R4, P1-R5를 구현하고 검증했다. 당시 P1-R2는 백엔드 exact byte 확인 전 시작 금지 조건 때문에 보류로 기록했고, P1-R1 closure는 P1-R2PageHeader 계약 결정이 남아 차단 상태로 기록했다.
  • 코드 수정 연결:
    • REV-P0-001: src/shared/test/setup.ts에 mock/global 복원을 추가하고 src/shared/test/setup-isolation.test.ts로 누출 회귀를 고정했다.
    • REV-P0-002: src/app/App.test.tsx, tests/e2e/smoke.spec.ts에서 root main 안 대표 콘텐츠를 검증한다.
    • REV-P0-003: 현재 Gate 전략을 plan-task.md의 P0-R1 기록에 누적했다.
    • REV-P1-002: stale ADMIN probe pending 동안 보호 shell을 렌더하지 않고 probe query를 size=20으로 보정했다.
    • REV-P1-003: crop output size와 Canvas render를 source crop rectangle 기반으로 고쳤고 자유 비율·pointer drag를 추가했다.
    • REV-P1-004: audio 확장자↔MIME 조합표를 적용했다.
    • REV-P1-005: ConfirmDeactivateDialog, UnsavedChangesGuard, ImageCropDialog에 focus trap과 trigger focus return을 적용했다.
    • REV-P1-006: 로그인 실패 시 서버 한국어 message를 우선 표시한다.
    • REV-P1-007: logout in-flight 중복 호출을 단일 promise로 합쳤다.
    • REV-P1-008: FileField error 상태에 aria-invalid="true"를 연결했다.
    • REV-P1-009: AdminAudioPlayer container shortcut이 descendant control key를 처리하지 않게 했다.
  • RED:
    • npm run test:run -- src/app/App.test.tsx src/features/auth/tests/login-page.test.tsx src/features/auth/tests/auth-session.test.tsx src/shared/lib/crop-image.test.ts src/shared/validation/file-media-policy.test.ts src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/shared/ui/__tests__/unsaved-changes-guard.test.tsx src/shared/ui/__tests__/image-crop-dialog.test.tsx src/shared/test/setup-isolation.test.ts — 기대 실패 확인, 11 files failed / 13 failed / 40 passed.
  • GREEN 및 Gate:
    • 같은 focused command — 성공, 11 files / 55 tests passed.
    • npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts src/shared/test/setup-isolation.test.ts — 성공, 3 files / 18 tests passed.
    • npm run test:run -- src/shared src/features/auth src/layouts src/app/App.test.tsx — 성공, 27 files / 102 tests passed.
    • npm run test:run — 성공, 28 files / 108 tests passed.
    • npm ci — 성공, 307 packages, 0 vulnerabilities.
    • npx playwright install chromium webkit — 성공.
    • npm run typecheck — 성공.
    • npm run lint — 성공.
    • npm run build:dev — 성공.
    • npm run build:prodnpm run build — 성공.
    • npm run e2e -- tests/e2e/smoke.spec.ts — 성공, 4 projects / 4 tests passed.
    • npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts — 성공, 4 projects / 16 tests passed.
    • git diff --check — 성공.
    • LSP diagnostics — src, src/shared/ui, 주요 변경 TS/TSX와 E2E 파일에서 오류 0건. App.tsxtests/e2e/auth.spec.ts 개별 fresh diagnostics는 timeout이 있었고, src directory diagnostics 0건 및 npm run typecheck 성공으로 보완 확인했다.
  • 참고: 첫 Gate 병렬 실행 중 npm cinode_modules를 재설치하는 동안 동시에 실행한 tsc/playwright 명령은 bin 삭제로 실패했다. 재설치 완료 후 같은 Gate 명령을 재실행해 성공 결과만 위에 기록했다.

실행 체크 재검증 및 정정 — 2026-07-27

  • 무엇을: 리뷰 리포트 실행 체크 전체를 재검증해 P0-R1의 문서 링크 공백과 P1-R4의 pixel·alpha 회귀 test 공백을 구현까지 완료했다.
  • 어떻게:
    • 존재하지 않는 docs/agent-guide/README.md 참조를 docs/agent-guide/ 세부 문서 관리 방식으로 정정했다.
    • src/shared/lib/crop-image.test.ts에 1200×600 → 1:1 no-upscale 600×600 출력과 상·하단 alpha 유지 회귀 test를 추가했다.
    • npm run test:run -- src/shared/lib/crop-image.test.ts — 성공, 1 file / 4 tests passed.
    • npm run test:run -- src/shared src/features/auth src/layouts src/app/App.test.tsx — 성공, 27 files / 102 tests passed.
    • npm run test:run — 성공, 28 files / 108 tests passed.
    • npm run e2e -- tests/e2e/smoke.spec.ts — 성공, 4 tests passed.
    • npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts — 성공, 16 tests passed.
  • 당시 결과: P0-R1, P1-R3, P1-R4, P1-R5 실행 체크는 구현·검증 완료였다. P1-R2는 백엔드 exact byte 확인 전 보류, P1-R1P1-R2PageHeader 결정 전 차단 상태였다.
  • 당시 남은 항목:
    • REV-P1-010: 백엔드 exact byte 확인 필요. 확인 전 값을 추정하지 않는다.
    • REV-P1-001: REV-P1-010 보류 해소와 PageHeader 계약 유지·폐기 결정 후 closure Gate 재실행 필요.

P1-R1/R2 closure 점검 — 2026-07-27

  • 무엇을: P1-R2 exact byte를 10,485,760 bytes로 확정하고 P1-R1PageHeader 계약을 폐기 결정으로 정리했다.
  • 코드·문서 수정 연결:
    • REV-P1-010: PRD, API 계약, IMAGE_MAX_BYTES, limit-1/limit/limit+1 경계 테스트를 같은 값으로 정렬했다.
    • REV-P1-001: PageHeader를 Phase 1 shared 산출물·소비처 Matrix에서 제거하고, 현재 shell 통합 증거는 실제 소비 중인 PageState와 App 포함 Gate로 제한했다.
  • RED:
    • npm run test:run -- src/shared/validation/file-media-policy.test.tspolicy.maxBytes 미구현으로 실패 확인.
    • 같은 command — IMAGE_MAX_BYTES export 누락으로 실패 확인.
  • GREEN 및 Gate:
    • npm run test:run -- src/shared/validation/file-media-policy.test.ts — 성공, 1 file / 7 tests passed.
    • npm run test:run -- src/shared/validation/file-media-policy.test.ts src/app/App.test.tsx src/shared/ui/__tests__/page-state.test.tsx — 성공, 3 files / 21 tests passed.
    • npm run test:run -- src/shared src/features/auth src/app/App.test.tsx — 성공, 27 files / 103 tests passed.
    • npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts — 성공, 16 tests passed. 320px·200% zoom, keyboard-contained mobile menu, axe critical·serious 0건을 포함한다.
    • npm run test:run — 성공, 28 files / 109 tests passed.
    • npm run e2e -- tests/e2e/smoke.spec.ts — 성공, 4 tests passed.
    • npm run typecheck, npm run lint, npm run build, git diff --check — 모두 성공.
    • LSP diagnostics — src/shared/validation/file-media-policy.test.ts 오류 0건. src/shared/validation/image-policy.ts 개별 fresh diagnostics는 timeout이 있었고 npm run typechecknpm run build 성공으로 보완 확인했다.
  • 결과: REV-P1-001REV-P1-010을 수정 완료로 닫았다. 열린 Blocker·High·Medium 항목은 없다.