diff --git a/.atp/work-session/20260622-170857/report.md b/.atp/work-session/20260622-170857/report.md
new file mode 100644
index 0000000..627e96b
--- /dev/null
+++ b/.atp/work-session/20260622-170857/report.md
@@ -0,0 +1,105 @@
+---
+schema_version: 2
+sid: 20260622-170857
+resumed_from: 20260622-092800
+started_at: 2026-06-22T17:08:57+09:00
+ended_at:
+user_request: |
+ W3-2 댓글/리뷰 고도화 검증 부채 해소 (검증 전용 — 신규 기능 추가 금지).
+ 직전 세션 코드 L1 43/43 GREEN. 미검증분 3가지만 닫는다:
+ (1) DDL 적용 + dev 스키마 4객체 검증
+ (2) L1 회귀 가드 (mvn test 43 GREEN 재확인)
+ (3) L3 브라우저 스모크 (XSS 미실행·재방문 영속·다축평점·페이지네이션·키보드·UX·A2 마스킹)
+ 완료 시 changes 문서 §검증결과 표 + security checklist B3 갱신.
+---
+
+# Summary
+W3-2 댓글/리뷰 고도화 검증 부채(DDL 적용 / L1 회귀 / L3 브라우저 스모크) 전량 해소. 검증 중 서버 집계의 실버그 2건(뷰 fan-out, 매퍼 alias 케이스 폴딩) 발견·최소수정. L3 12개 항목 전수 PASS. 검증 데이터는 세션 종료 시 정리해 원본 시드 복귀.
+
+핵심 결과:
+- STEP1 DDL: dev 4객체 검증 PASS.
+- STEP2 L1: mvn test 43/43 GREEN(버그 수정 후 재실행도 43/43, 무회귀).
+- STEP3 L3: XSS 미실행·재방문 영속·다축6행·SVG레이더·집계·페이지네이션·키보드·C1/C2/C4·B2·A2 전부 PASS.
+- 버그수정: GameReviewStatsMapper.java(alias 3개 quote), docs/game-reviews-ddl.sql + db/schema.sql(뷰 fan-out 수정), 실행 DB CREATE OR REPLACE 적용.
+- 환경: 가동 중이던 stale spring-boot:run(목요일 기동, 구 JSP)을 현재 빌드로 재기동. claude-in-chrome computer 도구는 document_idle 차단으로 javascript_tool/curl/psql 조합 검증.
+
+# Advisor Invocation Decision Log
+# 각 advisor 호출/스킵 판단 즉시 1줄 append
+
+- advisor: requirements-advisor
+ decision: skip
+ rationale: '사용자가 STEP1/2/3 + 완료처리까지 정밀 명세. 요구 모호성 0.'
+ checked_at: 2026-06-22T17:09:00+09:00
+- advisor: graphify-lookup-advisor / research-advisor
+ decision: skip
+ rationale: '검증 전용. 신규 설계·외부조사 불요. 배경문서(changes/local-setup) 직독으로 충분.'
+ checked_at: 2026-06-22T17:09:00+09:00
+- advisor: design-advisor / implementation-advisor
+ decision: skip
+ rationale: '코드 변경 기본 없음(검증 전용). 버그 발견 시에만 최소수정 — 그때 재평가.'
+ checked_at: 2026-06-22T17:09:00+09:00
+- advisor: verification-advisor
+ decision: call
+ rationale: 'STEP1(DDL 멱등적용+4객체 psql 검증) + STEP2(mvn test 43 GREEN) Bash 검증 위임. 출력 verbose → 컨텍스트 격리 가치.'
+ checked_at: 2026-06-22T17:09:30+09:00
+
+# Invocations
+- advisor: verification-advisor
+ phase: STEP1+STEP2
+ model_choice: { tier: medium, rationale: 'Bash 실행+AC 판정, 코드추론 불요' }
+ result: PASS
+ detail: |
+ AC-1 game_review_axes PASS (score CHECK 1-5, axis_key CHECK 6종, UNIQUE는 INDEX ux_game_review_axes_review_axis 로 구현 — constraint 카탈로그 미표기지만 기능동등)
+ AC-2 game_review_stats 뷰 9컬럼 PASS
+ AC-3 game_comments.updated_at timestamptz NOT NULL, mismatched=0/total=3 PASS
+ AC-4 game_reviews.is_rating_manual boolean DEFAULT false NOT NULL PASS
+ AC-5 mvn test: Tests run 43 Failures 0 Errors 0 (GameComment17/GameReview20/Csrf5/App1) PASS
+ concerns: 'AC-1 UNIQUE 구현형태=INDEX(not constraint). 기능동등, 비차단.'
+
+# Bug Findings (L3 실게임 검증 중 발견 — L1 mock 사각)
+## BUG-1 game_review_stats 뷰 fan-out (집계 왜곡)
+- 근거: docs/game-reviews-ddl.sql:171-185 (수정전) — `LEFT JOIN game_review_axes` 직접 조인.
+- 증상: 리뷰에 axes 행이 존재하면 리뷰가 axes 행수(최대 6)만큼 복제 → `COUNT(*)`=11(실제 6), `AVG(rating)`=2.8(실제 3.5). axes 0행일 땐 잠복(이전 "3.8(5)" 정상), 다축 리뷰 입력 순간 발현.
+- 수정: 축 평균을 서브쿼리에서 game 단위 선집계 후 LEFT JOIN(MAX), 리뷰 단위 집계는 game_reviews 단독. docs/game-reviews-ddl.sql + db/schema.sql 동기 + 실행 DB CREATE OR REPLACE 적용.
+- 수정후 검증: 뷰 game3 → review_count=6, avg_rating=3.5 (실제값 일치). PASS.
+
+## BUG-2 GameReviewStatsMapper alias 케이스 폴딩 → summary 항상 null
+- 근거: GameReviewStatsMapper.java:13-15 (수정전) — `AS gameId/avgRating/reviewCount` (따옴표 없음).
+- 증상: Postgres가 따옴표 없는 alias 를 소문자(gameid/avgrating/reviewcount)로 폴딩 → MyBatis Map 키 소문자. buildSummary(GameReviewController.java:372-390) 가 `stats.get("reviewCount")` camelCase 조회 → null → reviewCount=0 → summary=null. JSP(game-detail.jsp:2188 `if(!summary||!summary.reviewCount)`)는 항상 "아직 평가 없음" 오표시. 서버집계 기능 전면 무력.
+- 왜 L1 통과: BibimbapApplicationTests/컨트롤러테스트가 매퍼를 @MockBean 으로 대체 → 실제 Postgres alias 폴딩 미발생. mock-vs-reality 갭.
+- 수정: alias 3개 따옴표(`AS "gameId"/"avgRating"/"reviewCount"`). axis alias 는 소문자=AXIS_KEYS 일치라 유지.
+- 수정후 검증: 앱 재기동 후 curl 재확인(아래 verified_by_me).
+
+# Decisions
+- 버그 발견으로 "검증 전용·코드무수정 기본" 가정 반전 → task 의 "실제 버그 발견 시 최소수정" 사전승인 하에 수정. 수정 범위: 매퍼 alias 3개 + 뷰 정의(2파일) + 실행DB 재적용. 비파괴(CREATE OR REPLACE, alias quote).
+- 회귀테스트: 두 버그 모두 DB-통합 계층(L1 mock 우회). 기존 단위 harness(@MockBean)로 재현 불가 → 자동 회귀 테스트는 별도 L2 contract(dev DB 연동) 인프라 필요. 본 세션은 L3 curl+브라우저 before/after(11/2.8/null → 6/3.5/정상)를 회귀 근거로 삼고, L2 contract 테스트 신설을 open_items 로 권고.
+- DDL 적용은 §6 파괴 게이트 비해당: 멱등 CREATE TABLE/ALTER ADD/CREATE OR REPLACE VIEW (DROP/TRUNCATE/rollback 아님). changes 문서서 이미 일반 DDL 분류. → 사용자 재확인 불요.
+- detail 라우트 = /game/{id} (numeric games.id). dev 가시게임 id=3.
+- L3 브라우저 스모크는 claude-in-chrome 필요 → advisor 미보유 → orchestrator 직접 수행.
+
+# verified_by_me
+- L1: unit+regression — mvn test 43/43 GREEN (GameComment17/GameReview20/Csrf5/App1), 버그수정 후 재실행도 43/43 (verification-advisor 2회 독립 판정).
+- DDL: dev 스키마 4객체 psql 검증 (game_review_axes UNIQUE index+CHECK 6종+score 1~5 / game_review_stats 9컬럼 / game_comments.updated_at timestamptz NOT NULL / game_reviews.is_rating_manual boolean DEFAULT false NOT NULL).
+- L3 (javascript_tool+curl+psql+JSP정적): XSS 미실행(alert 0회, textContent 텍스트노드) / 재방문 영속(쿠키없는 GET 서버데이터) / 다축 6행+overall 자동(false)·수동(true) / SVG레이더+aria 6축 / 집계 3.5·(6) / 페이지네이션 21건 page0=20 hasMore=true page1=1 / 키보드 roving+preventDefault / C1 disabled+aria-busy / C2 상대시각+title / C4 0/200·0/1000 / B2 2자→400 / A2 테스터·탈퇴마스킹.
+- 버그수정 검증: BUG-1 뷰 review_count 11→6, avg 2.8→3.5. BUG-2 summary null→{avgRating:3.5,reviewCount:6,axes}.
+- 로그 스캔: clean (앱 기동 로그 ERROR 0, 환경성 WARN만).
+
+# needs_user_verification
+- (선택) claude-in-chrome computer/screenshot 도구가 본 dev 페이지에서 document_idle 미도달로 차단됨 — 시각적 스크린샷 증빙 없음(검증은 DOM/이벤트/서버계약으로 동치 수행). 원인이 WebGL iframe/page idle 휴리스틱인지 사용자 환경 확인 권장(비차단).
+- 가동 앱: 본 세션이 stale spring-boot:run(구 JSP)을 현재 빌드로 재기동해 8080에서 실행 중. 사용자가 별도 기동 흐름이 있었다면 인지 필요.
+
+# graph_refresh
+fresh → 후속 없음. graph-refresh-checker 판정: 변경분(매퍼 alias 따옴표 + 뷰 내부 집계로직)이 심볼 시그니처·뷰/테이블/컬럼 토폴로지를 바꾸지 않음(구조 시그널 0). 재생성·삭제 불필요. source_commit 메타 갱신은 선택(필수 아님) — 본 세션 미수행.
+
+# ended_at
+2026-06-22T17:50:00+09:00
+
+# open_items
+- L2 contract 테스트 신설 권고: game_review_stats 뷰 집계 정합 + GameReviewStatsMapper Map 키를 dev DB 연동으로 가드. BUG-1/2가 L1 @MockBean 사각으로 누출된 근본원인 차단용. (changes 문서 §범위밖/이월 + security checklist 연계)
+- 미커밋 잔여 0 목표 — 본 작업 단위 커밋으로 마감.
+
+# user_signals
+positive:
+ - quote: "dev 픽스처로 자동 로그인 (Recommended) 수락"
+ note: 제안한 검증 경로/권장안을 1회에 수락(이견 없음).
+negative: []
diff --git a/.atp/work-session/20260622-170857/verification.md b/.atp/work-session/20260622-170857/verification.md
new file mode 100644
index 0000000..0901818
--- /dev/null
+++ b/.atp/work-session/20260622-170857/verification.md
@@ -0,0 +1,61 @@
+---
+phase: verification
+agent: verification-advisor
+agent_version: 1
+generated_at: 2026-06-22T17:48:00+09:00
+concerns: []
+concerns_checked: true
+---
+
+# 검증 결과
+
+## Acceptance Criteria (입력 받은 그대로 인용)
+`export JAVA_HOME=/opt/homebrew/opt/openjdk@21 && ./mvnw -P dev test` 실행 → BUILD SUCCESS + 총 43건 GREEN (Failures 0, Errors 0). "Tests run: N, Failures: F, Errors: E" 합계 라인 인용. 회귀(F/E>0) 시 명확히 FAIL + 실패 테스트명·메시지 인용.
+
+변경 scope(검증 대상 아님, 맥락): GameReviewStatsMapper.java SQL alias 3개 따옴표 추가(gameId/avgRating/reviewCount), game_review_stats 뷰 정의 변경(DDL, Java 무관).
+
+## 실행된 전략
+
+레지스트리(`docs/development/verification-strategies.md`)는 템플릿 상태(실제 `cmd` 미기재)이고 통합 검증 스크립트(`make verify`/`scripts/verify.sh`)가 부재. AC가 직접 지정한 `./mvnw -P dev test`가 L1(typecheck + 단위/회귀) 통합 실행 수단이다 — Maven test phase가 test-compile(타입체크) → surefire(단위/회귀)를 순차 포함.
+
+변경 scope = MyBatis mapper SQL alias + 뷰 DDL. 외부 서비스 live contract(L2) 의존 없음 → L2 해당 없음(skip 아님, scope 미매칭).
+
+| id | cmd | exit | severity | 결과 |
+|---|---|---|---|---|
+| verify-l1 | `export JAVA_HOME=/opt/homebrew/opt/openjdk@21 && ./mvnw -P dev test` | 0 | blocker | pass |
+
+분해 결과:
+
+| 단계 | 결과 |
+|---|---|
+| L1 typecheck (test-compile) | pass (Nothing to compile - all classes up to date; compile error 0) |
+| L1 unit+regression (surefire) | pass (43/43 GREEN) |
+| L2 contract | n/a (변경 scope에 외부 의존 계약 없음) |
+| 로그 스캔 | clean (FAIL/ERROR/assert 없음; Mockito self-attach + JDK agent warning은 환경성 비기능 경고로 테스트 무관) |
+
+합계 라인(원문 인용):
+- `[INFO] Tests run: 43, Failures: 0, Errors: 0, Skipped: 0`
+- `[INFO] BUILD SUCCESS`
+
+클래스별 분해(원문 인용):
+- `GameCommentControllerTest` Tests run: 17, Failures: 0, Errors: 0, Skipped: 0
+- `GameReviewControllerTest` Tests run: 20, Failures: 0, Errors: 0, Skipped: 0
+- `UserControllerCsrfTest` Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
+- `BibimbapApplicationTests` Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
+- 합계 17+20+5+1 = 43
+
+## 실패 상세
+없음 (회귀 0건).
+
+## 종합 판정
+overall: pass
+rollback_signal: none
+
+## Acceptance 매칭
+| criterion | 매칭 전략 | 판정 |
+|---|---|---|
+| BUILD SUCCESS | verify-l1 | pass (`[INFO] BUILD SUCCESS`) |
+| 총 43건 GREEN | verify-l1 | pass (`Tests run: 43`) |
+| Failures 0 | verify-l1 | pass (`Failures: 0`) |
+| Errors 0 | verify-l1 | pass (`Errors: 0`) |
+| 합계 라인 인용 | verify-l1 | pass (`Tests run: 43, Failures: 0, Errors: 0, Skipped: 0`) |
diff --git a/db/schema.sql b/db/schema.sql
index d196992..51253bc 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -199,19 +199,34 @@ COMMENT ON TABLE "game_review_axes" IS '리뷰 다축 평점(6축, 리뷰당 6
COMMENT ON COLUMN "game_review_axes"."axis_key" IS '몰입성 immersion/창의성 creativity/조작성 controls/완성도 completeness/사운드 sound/비주얼 visual';
-- 4) game_review_stats (읽기전용 집계뷰 — 클라 평균계산 폐기 공급원)
+-- fan-out 방지: game_review_axes 직접 JOIN 시 리뷰가 axes 행수만큼 복제되어
+-- COUNT(*)/AVG(rating) 왜곡. 축 평균은 서브쿼리에서 game 단위 선집계 후 LEFT JOIN.
CREATE OR REPLACE VIEW "game_review_stats" AS
SELECT
- r."game_id" AS "game_id",
- ROUND(AVG(r."rating")::numeric, 1) AS "avg_rating",
- COUNT(*) AS "review_count",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='immersion'),1) AS "avg_immersion",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='creativity'),1) AS "avg_creativity",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='controls'),1) AS "avg_controls",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='completeness'),1) AS "avg_completeness",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='sound'),1) AS "avg_sound",
- ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='visual'),1) AS "avg_visual"
+ r."game_id" AS "game_id",
+ ROUND(AVG(r."rating")::numeric, 1) AS "avg_rating",
+ COUNT(*) AS "review_count",
+ MAX(ax."avg_immersion") AS "avg_immersion",
+ MAX(ax."avg_creativity") AS "avg_creativity",
+ MAX(ax."avg_controls") AS "avg_controls",
+ MAX(ax."avg_completeness") AS "avg_completeness",
+ MAX(ax."avg_sound") AS "avg_sound",
+ MAX(ax."avg_visual") AS "avg_visual"
FROM "game_reviews" r
-LEFT JOIN "game_review_axes" a ON a."review_id" = r."id"
+LEFT JOIN (
+ SELECT
+ rv."game_id" AS "game_id",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='immersion'),1) AS "avg_immersion",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='creativity'),1) AS "avg_creativity",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='controls'),1) AS "avg_controls",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='completeness'),1) AS "avg_completeness",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='sound'),1) AS "avg_sound",
+ ROUND(AVG(a."score") FILTER (WHERE a."axis_key"='visual'),1) AS "avg_visual"
+ FROM "game_reviews" rv
+ JOIN "game_review_axes" a ON a."review_id" = rv."id"
+ WHERE rv."is_delete" IS NOT TRUE
+ GROUP BY rv."game_id"
+) ax ON ax."game_id" = r."game_id"
WHERE r."is_delete" IS NOT TRUE
GROUP BY r."game_id";
COMMENT ON VIEW "game_review_stats" IS 'W3-2 일반 집계뷰(W2-3 동결 무관). 게임별 평균별점·리뷰수·6축평균';
diff --git a/docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md b/docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md
index c17001a..85e0519 100644
--- a/docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md
+++ b/docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md
@@ -103,7 +103,7 @@ related_report: "../../.atp/work-session/20260622-092800/report.md"
- `docs/game-reviews-ddl.sql` — 멱등 블록 append(+ :63 보수주석 갱신)
- `db/schema.sql` — 동일 정의 동기화(+ :136-137 주석 갱신)
-**적용 상태**: needs_user_verification (dev: schema.sql 재부트 or ddl 수동 실행)
+**적용 상태**: PASS — dev 스키마 적용·검증 완료(20260622-170857, `db/apply-local-ddl.sh`). game_review_axes(UNIQUE index ux_game_review_axes_review_axis + score CHECK 1~5 + axis_key CHECK 6종) / game_review_stats(9컬럼) / game_comments.updated_at(timestamptz NOT NULL, 기존행 = created_at) / game_reviews.is_rating_manual(boolean DEFAULT false NOT NULL) 전수 확인.
---
@@ -267,13 +267,49 @@ frontend-design 스킬로 처리된 `game-detail.jsp` 변경 요약. 기존 앰
| AC-5 `${}` 0건 | PASS | 신규·변경 매퍼 전수(주석 3건 제외) |
| AC-6 axis_key 6키 정합 | PASS | DDL CHECK·뷰 FILTER·앱 enum·JSP 라벨 4곳 일치 |
| L2 contract-DB | skip | 원격 DB 미기동 |
-| L3 브라우저 스모크 | **needs_user_verification** | DDL 적용 후 dev 실게임 대상 |
-| DDL 적용 | **needs_user_verification** | game_review_axes/stats/updated_at/is_rating_manual |
+| L3 브라우저 스모크 | **PASS (+버그 2건 발견·수정)** | dev 실게임(game id=3) 대상, 세션 20260622-170857. 아래 §L3 결과 참조 |
+| DDL 적용 | **PASS** | game_review_axes/stats/updated_at/is_rating_manual 4객체 dev 스키마 검증 완료(20260622-170857) |
FIX LOOP 상세: `report.md#Invocations` implementation-advisor(FIX LOOP §2.6) — 근본원인=테스트 계약 미갱신(컨트롤러 무버그). body<10자 7곳→10자+ 교체, 행위 assertion 보존, 계약 무약화. main 코드 무수정.
---
+## L3 브라우저 스모크 결과 (세션 20260622-170857)
+
+dev 실게임(game id=3, 더미) + dev 픽스처 계정(`tester@bibimbap.local`, `db/seed-dev.sql`) 로 검증. 브라우저 확장의 computer/screenshot 도구가 해당 페이지에서 `document_idle` 미도달로 차단되어, 검증은 `javascript_tool`(DOM/이벤트 동기 검사)·`curl`(서버 계약)·psql(DB)·JSP 정적분석을 조합해 수행. 검증 후 세션 생성 테스트 데이터(tester=user3)는 전량 삭제해 원본 시드(댓글 3·리뷰 5) 복귀.
+
+| 항목 | 결과 | 근거 |
+|---|---|---|
+| **B3 XSS 미실행** | PASS | 댓글 `
`/`` + 리뷰 본문 저장 → 재렌더 시 isOnlyTextNode=true, img/script 엘리먼트 미생성, alert/confirm/prompt 0회. game-detail.jsp:1840(댓글)/:2136(리뷰) textContent. |
+| **재방문 영속** | PASS | 작성 후 새로고침 유지 + 쿠키 없는 GET이 서버 데이터 반환(localStorage 비의존). |
+| **다축 6행 + overall 자동/수동** | PASS | axes(4,5,3,4,2,5) rating 미전송 → overall=4(avg 3.83 HALF_UP), is_rating_manual=false, game_review_axes 6행. PUT rating=2 → rating=2, is_rating_manual=true, edited=true. |
+| **육각형 SVG 레이더 + aria** | PASS | summary radar svg role="img" aria-label="6축 평가: 몰입성 4, 창의성 5, 조작성 3, 완성도 4, 사운드 2, 비주얼 5". 개별 카드 레이더 동일 aria. axes 0행 리뷰는 "6축 평가 데이터 없음". |
+| **서버 집계 표시** | PASS(수정 후) | summary={avgRating:3.5, reviewCount:6, axes{...}}, UI "3.5"·"(6)" 소수1자리. review_count=0 → buildSummary null → "아직 평가 없음" 코드경로. |
+| **페이지네이션 더보기 20+hasMore** | PASS | 댓글 21건: page0 count=20 hasMore=true, page1 count=1 hasMore=false. |
+| **6축 radiogroup 키보드(C6)** | PASS | roving tabindex [0,-1,-1,-1,-1] → ArrowRight 즉시선택+tabindex 이동, Home=1/End=5, Arrow preventDefault=true(스크롤차단), aria-label "몰입성 N점". |
+| **C1 submit 잠금** | PASS | 제출 클릭 직후 btn.disabled=true, aria-busy=true. |
+| **C2 상대시각** | PASS | "5일 전"/"6일 전"/"7일 전", >7일 절대날짜, `