From 21892c8a03367ea4f90345092d0ff8f66d8a9ee1 Mon Sep 17 00:00:00 2001 From: art Date: Mon, 22 Jun 2026 17:54:19 +0900 Subject: [PATCH] =?UTF-8?q?fix(reviews):=20game=5Freview=5Fstats=20?= =?UTF-8?q?=EC=A7=91=EA=B3=84=20=EB=B2=84=EA=B7=B8=202=EA=B1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20+=20W3-2=20L3=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W3-2 댓글/리뷰 고도화 검증 부채(DDL 적용·L1 회귀·L3 브라우저 스모크) 해소 중 서버 집계의 실버그 2건을 발견·수정. 둘 다 L1 @MockBean 사각으로 누출됨. BUG-1 game_review_stats 뷰 fan-out - LEFT JOIN game_review_axes 직접 조인 → 리뷰가 axes 행수(최대 6)만큼 복제, COUNT(*)/AVG(rating) 왜곡(다축 리뷰 1건에 review_count 11/avg 2.8, 실제 6/3.5). - 축 평균은 서브쿼리 game 단위 선집계 후 LEFT JOIN(MAX), 리뷰 단위 집계는 game_reviews 단독 산출로 수정. docs/game-reviews-ddl.sql + db/schema.sql 동기. BUG-2 GameReviewStatsMapper alias 케이스 폴딩 - 따옴표 없는 AS gameId/avgRating/reviewCount → Postgres 소문자 폴딩 → buildSummary의 camelCase Map 조회 null → summary 항상 null("아직 평가 없음" 오표시). - alias 3개 큰따옴표로 케이스 보존. 검증 - mvn test 43/43 GREEN(수정 후 무회귀). - 수정 후 dev 실게임: 뷰 review_count 6/avg 3.5, summary={avgRating:3.5,reviewCount:6,axes}. - L3 스모크 12항목 전수 PASS(XSS 미실행·재방문 영속·다축6행·SVG레이더·집계· 페이지네이션·키보드·C1/C2/C4·B2·A2). changes 문서 §L3 결과 + security checklist B3 갱신. - 권고: game_review_stats L2 contract 테스트 신설(L1 mock 사각 차단) — 이월. Co-Authored-By: Claude Opus 4.8 (1M context) --- .atp/work-session/20260622-170857/report.md | 105 ++++++++++++++++++ .../20260622-170857/verification.md | 61 ++++++++++ db/schema.sql | 35 ++++-- ...06-22-w3-2-comments-reviews-enhancement.md | 43 ++++++- docs/game-reviews-ddl.sql | 36 ++++-- .../security-remediation-checklist.md | 6 +- .../mapper/GameReviewStatsMapper.java | 6 +- 7 files changed, 263 insertions(+), 29 deletions(-) create mode 100644 .atp/work-session/20260622-170857/report.md create mode 100644 .atp/work-session/20260622-170857/verification.md 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일 절대날짜, `