bibimbap/.atp/work-session/20260624-100749/verification.md

749 lines
60 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
phase: verification
agent: verification-advisor
agent_version: 1
generated_at: 2026-06-24T12:40:00+09:00
concerns:
- "RESOLVED(재검증): 1차 L1 BLOCKER FAIL(JamAdminController 24 errors response() 미정의) → 보정 후 BUILD SUCCESS, Tests run 97 / Failures 0 / Errors 0 / Skipped 0"
- "§33 잠재 리스크(무해): 5매퍼의 camelCase alias 67건이 큰따옴표 비인용 → Postgres 가 소문자 폴딩(AS createdAt → createdat 실측 확인). 단 5매퍼 전부 resultType=POJO(*Data, Map 0건)이므로 MyBatis case-insensitive 매핑으로 현재 무해. resultType 을 Map 으로 바꾸면 즉시 §33 버그화 → 회귀 가드 권고(AS \"createdAt\" 인용 표준화)"
- "AC-T3 산술(requireJamManage 6 = POST 5 + 선언 1)은 우연 일치. 쓰기 5핸들러 전수 게이트 + 읽기 console gate.has(GAME_JAM_MANAGE) 인라인 — 인가 우회 0. VP-1 JamAdminControllerTest 10 PASS 로 런타임 도달 실증"
- "AC-T6 원문 grep '@MockBean.*Jam' same-line 가정은 오탐 — 실제 어노테이션 별행. 매퍼명 기준 실측 5/5 정합으로 AC 통과, AC 표현 보정 권고"
- "L2 contract: 사용자 dev 스키마 비파괴 위해 격리 throwaway DB 에 jam-ddl 적용 후 검증·DROP 완료. 사용자 dev(bibimbap) DB 의 jam 테이블 0건 — 미적용 상태 유지(파괴적 조작 회피). 운영 적용은 db/apply-local-ddl.sh 로 별도 수행 필요"
- "W2-2 RESULT: L1 BUILD SUCCESS, Tests run 119 / Failures 0 / Errors 0 / Skipped 0 — 전 클래스 GREEN. JamRoleGateTest 8, JamJudgeAdminControllerTest 14. 실패 클래스 없음"
- "W2-2 AC 예측 vs 실측 불일치(무해, FAIL 아님): AC 입력은 JamJudgeAdminControllerTest(16)·총 121(97+24) 예측이었으나 실측 JamJudgeAdminControllerTest=14·총 119. 전 테스트 GREEN·JamRoleGateTest는 예측 8과 정확 일치. 카운트는 구현이 테스트를 더 적게 작성/통합한 결과로 추정되나 advisor는 원인 추정 넘어 단정 안 함 — AC 예측치 보정 권고"
- "W2-2 AC-T6 grep -c (매퍼명 기준)=2는 import 1 + @MockBean 필드 1을 합산한 값. 실측 line73-74/88-89에서 실제 @MockBean 필드 등록 확인 — 정합 PASS"
- "W2-2 AC-T2 게이트(핸들러3 requireJamManage)는 구현파일 직접열람 제한으로 grep 전수 미수행. 대신 JamJudgeAdminControllerTest 14 GREEN(403/401 인가거부 케이스 포함)으로 런타임 게이트 도달 실증 + AC-T3 isValid 2건으로 쓰기경로 CSRF 확인. 인가우회 0은 단위테스트 간접근거 기반 PASS"
- "W2-2 L2 contract: 사용자 dev(bibimbap) 스키마 비파괴 위해 격리 throwaway DB(verify_w22_throwaway) 신규생성→jam-judge-ddl 적용→a/b/c 실측→DROP 완료(잔존 0). 사용자 dev DB 무접촉. 운영 적용은 db/apply-local-ddl.sh 별도 수행 필요"
- "W2-3 RESULT: PASS. L1 회귀 무변경 — BUILD SUCCESS, Tests run 119 / Failures 0 / Errors 0 / Skipped 0 (W2-2 119 GREEN 그대로 유지, Java src 무변경·신규테스트 0이 정상). L2 dev DB contract 전 항목 PASS — DDL 적용·9FK·CHECK4·UNIQUE4·VIEW 집계 정합·NULLIF/fan-out 가드 실측 통과."
- "W2-3 AC-T1~T6 집합 전수: T1(CREATE TABLE IF NOT EXISTS=4, VIEW jam_score_stats=1, schema.sql 4테이블 line608/642/685/721 + VIEW line765 동기) PASS / T2(jam_awards_track_check IN 4값 JUDGE,USER_RATING,POPULAR,GRAND 실생성 확인) PASS / T3(UNIQUE INDEX 4: ux_jam_criteria_jam_key·ux_jam_scores_jam_game_judge_criterion·ux_jam_votes_jam_voter·ux_jam_awards_jam_track_game + CHECK 4: weight>=0·score BETWEEN 1 AND 5·track IN·rank>=1) PASS / T4(매퍼 신규 0 = N/A, 하류 W2-4 위임) / T5(game_reviews/game_review_stats 변경 0, ALTER 17건 전부 신규 jam_* 테이블 제약추가뿐, DROP 0) PASS / T6(알파벳 순 jam-ddl→jam-eval-ddl→jam-judge-ddl FK 9건 실생성 해소) PASS"
- "W2-3 핵심 게이트 VIEW 집계 정합(§33 fan-out): per_criterion CTE 선집계 후 jam_criteria LEFT JOIN 패턴. 실측 GameA raw 3행→5행(심사위원 fan-out)에도 scored_criteria=2 불변·judge_count=MAX(per-criterion)=4 정확·weighted_total=3.5 불변 → 자식 행수 부풀림 0. 미채점 criterion 제외(GameB art 미채점 시 scored_criteria=1·분모서 제외). NULLIF(SUM(weight),0) 0-division 가드: 점수0건 게임은 VIEW 행 미생성, weight합0 강제경로서 weighted_total NULL 반환(에러 아님). game_review_stats 선례 버그 재발 0."
- "W2-3 VIEW alias 정합: 6컬럼 jam_id·game_id·weighted_total·simple_total·scored_criteria·judge_count 전부 소문자 snake(비인용). 하류 매퍼가 AS \"camelCase\" 인용으로 매핑할 계약(W2-4 검증 위임). 본 단계는 VIEW 자체 컬럼명 확인만 — 정합."
- "W2-3 L2 비파괴: 격리 throwaway DB(verify_w23_throwaway) 신규생성→users/games 최소부모+jam-ddl(jams 제공)→jam-eval-ddl(SUT)→jam-judge-ddl 알파벳순 적용→제약거부 a/b/c/d + VIEW 집계 실측→DROP 완료(잔존 0). 사용자 dev DB(bibimbap)의 jam_eval 테이블 0건 — 미적용 상태 유지(파괴적 조작 회피). 운영 적용은 별도 수행 필요."
- "W2-6 RESULT: PASS. L1 full-test BUILD SUCCESS, Tests run 190 / Failures 0 / Errors 0 / Skipped 0 (직전 171 + 신규 19 = 190 기대 일치). 신규 JamAwardServiceTest 7·JamAwardAdminControllerTest 7·JamAwardControllerTest 5 전수 GREEN. 회귀 0 — JamControllerTest 6arg 생성자 변경 후 14/14 GREEN, BibimbapApplicationTests contextLoads 1 PASS(신규 2매퍼 @MockBean 등록). 실패 클래스 없음."
- "W2-6 집합 전수 AC-T1~T6: 6/6 PASS(어긋남 0). T1 신규매퍼 MyBatis ${param} 0(grep -c \'\\${\'=1은 Javadoc 주석 문구 — 실제 동적치환 패턴 0). T2 집계매퍼 camelCase 비인용 alias 0(JamReviewRatingMapper/JamVotesMapper/JamScoreStatsMapper), 인용 AS \"...\" 정상. T3 deleteByJamTrack+@Transactional recompute 존재. T4 CLOSED→422·CSRF→403·PermissionGate.has(GAME_JAM_MANAGE) 전수. T5 @MockBean 2매퍼. T6 JamVotesMapper.listCountsByJam 인용 alias."
- "W2-6 L2 dev DB contract(§33): PASS 4/4. throwaway DB(w2_6_l2_throwaway) 생성→jam-eval 검증구조+game_review_stats 적용→실측→DROP(count=0). 사용자 dev(bibimbap) DB 무접촉(jam_awards 테이블 부재 = 미적용 유지·파괴조작 0). L2-1 멱등: DELETE→INSERT x2 count 6==6 md5 동일·UNIQUE하 중복0. L2-2 3트랙 손계산 일치(JUDGE NULL제외·USER_RATING count>=3 NULLS LAST·POPULAR>0). L2-3 alias 폴딩: row_to_json 키 camelCase 보존(gameId/avgRating/reviewCount/voteCount), game_review_stats fan-out 0. L2-4 GRAND 가용트랙만 정규화 원칙 확인."
- "W2-6 SCOPE 한계(무해): GRAND 정확 가중계수는 RankScores/JamAwardService 내부 — verification-advisor 역할상 구현파일 미열람. L2-4는 DB-계약 수준의 \"가용 트랙만 정규화·결합\" 원칙만 검증, 계수 단위검증은 L1 JamAwardServiceTest 7 GREEN 에 위임. FAIL 아님."
- "W2-6 L3 needs_user_verification(수동): 실 세션 권한 미보유 403·CLOSED 미달 422·results 페이지 종료후 공개(밴드왜건 회피) 런타임 + dev DB jam-eval-ddl 적용 후 멱등 스모크는 advisor 범위 밖 — orchestrator/사용자 확인 권장."
concerns_checked: true
---
# 검증 결과
## Acceptance Criteria (입력 받은 그대로 인용)
### L1 (blocker)
- full `./mvnw -o test` 실행 (§30 의무, test-compile 로 끝내지 말 것)
- contextLoads PASS (신규 5매퍼 @MockBean 누락 시 NoSuchBeanDefinitionException → FAIL)
- 신규 테스트 통과: JamLifecycleTest, JamAdminControllerTest, JamControllerTest
- 기존 테스트 회귀 0 (전체 GREEN)
### 집합 전수 AC
- AC-T1: `grep -c 'CREATE TABLE IF NOT EXISTS' docs/jam-ddl.sql` == 5, AND db/schema.sql 동일 5 테이블 전수 존재
- AC-T2: JamStatus enum 4 == jams_status_check IN 4 == jam_status_log to_status IN 4 (3곳 동기)
- AC-T3: JamAdminController 핸들러 수 == requireJamManage 게이트 호출 수 (6), 수동 열거
- AC-T4: 신규 매퍼 5파일 `${` 매치 0건
- AC-T5: jam_entries_entrant_xor_check + jam_entries_entrant_type_check 2 CHECK 존재
- AC-T6: `grep -c '@MockBean.*Jam' <BibimbapApplicationTests>` >= 5
### 시나리오 AC
- VP-1 게이트 / VP-2 전이 감사 / VP-3 출품 무결성 / VP-5 CSRF (L1 테스트로 커버)
### L2 (dev DB contract — 가용 시)
- XOR/status/active-UNIQUE CHECK 실측, keyset SQL + camelCase alias 정합
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-all (L1 full test) | `JAVA_HOME=.../openjdk@21 ./mvnw -o test` | 1 | blocker | **FAIL (컴파일 실패)** |
| AC-T1 grep | `grep -c 'CREATE TABLE IF NOT EXISTS' docs/jam-ddl.sql` | 0 | blocker | pass (5) |
| AC-T4 grep | `grep -rc '\${' <매퍼5>` | 0 | blocker | pass (0,0,0,0,0) |
| AC-T5 grep | `grep -c '<2 check>' docs/jam-ddl.sql` | 0 | blocker | pass (1+1) |
| AC-T6 grep(보정) | `grep -B1 'private Jam.*Mapper' \| grep -c '@MockBean'` | 0 | blocker | pass (5) |
| L2 contract | `db/apply-local-ddl.sh` | - | blocker | **skipped:no-dev-db + 빌드미컴파일** |
(분해 결과 병기)
| 단계 | 결과 |
|---|---|
| L1 typecheck/compile | **FAIL — JamAdminController 24 cannot-find-symbol `response(HttpStatus,String)`** |
| L1 unit+regression | **미실행 (컴파일 게이트에서 중단, 테스트 0개 도달)** |
| L2 contract-postgres | skipped:no-dev-db (PGHOST/PGUSER 미설정 + 빌드 미컴파일) |
| 로그 스캔 | n/a (앱 미기동) |
## 실패 상세
### L1 compile (blocker)
- 실패 지점: `src/main/java/com/pandoli365/bibimbap/controller/JamAdminController.java`
- line 92,96,100,104,108,121,124,158,195,200,204,208,212,216,229,232,270,275,279,282,312,341,355,358 (총 24건)
- 메시지: `cannot find symbol — symbol: method response(org.springframework.http.HttpStatus,java.lang.String) — location: class com.pandoli365.bibimbap.controller.JamAdminController`
- 근거: `grep -E '(private|protected|public|static).*\bresponse\s*\('` JamAdminController → 선언 0건(exit 1). `extends`/`implements` 슈퍼타입 없음(`public class JamAdminController {`). 즉 클래스 내부에 `response(HttpStatus,String)` 헬퍼가 정의돼 있지 않은데 24회 호출 → 미해결 심볼.
- 부수: `JamController.java``response(` 20건 참조. javac 가 JamAdminController 에서 BUILD FAILURE 로 중단되어 JamController 는 미평가이나 동일 미정의 의심.
- 파급: `[INFO] 24 errors``BUILD FAILURE` (maven-compiler-plugin default-compile). test-compile/surefire 진입 못함 → contextLoads·JamLifecycleTest·JamAdminControllerTest·JamControllerTest·전체 회귀 **0개 실행**.
- 오프라인 판정: `-o` 빌드가 의존성 해석 에러가 아닌 **자체 소스 컴파일 에러**를 반환. 온라인 `./mvnw test` 재시도해도 동일 24 에러 반복(§30 retry 대상 아님).
- 재현: `JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home ./mvnw -o test`
- 좁힌 형태: `JAVA_HOME=... ./mvnw -o compile`
## 종합 판정
overall: **fail** (L1 compile blocker)
rollback_signal: none
# 이번 실패는 신규 파일 자체의 미완성 컴파일 결함 — 파괴적 조작(DB migration/파일 삭제) 영향 범위 밖.
# DDL/schema.sql 동기, 매퍼 SQL, @MockBean 등록 등 데이터/스키마 측 산출물은 정합.
# implementation 단계에서 response() 헬퍼 정의(또는 호출부 보정) 추가 후 재검증이면 충분. 광범위 revert 불필요.
## 집합 전수 AC 실측
| AC | 실측 | 기대 | 판정 |
|---|---|---|---|
| AC-T1 (jam-ddl CREATE TABLE) | 5 | 5 | PASS |
| AC-T1b (schema.sql 5 테이블 동기) | jams/jam_teams/jam_team_members/jam_entries/jam_status_log 각 1 | 5 전수 | PASS (원문 grep 은 비인용 패턴이라 0 오탐 → 큰따옴표 식별자 `"jams"` 로 재확인) |
| AC-T2 (enum=jams CHECK=log CHECK 3곳) | enum RECRUIT/DEV/EVAL/CLOSED 4, jams_status_check IN 4, jam_status_log_to_status_check IN 4 | 4==4==4 | PASS |
| AC-T3 (핸들러 게이트 정합) | 매핑 6(GET console 1 + POST 5), requireJamManage 호출 5 + 선언 1. 쓰기 5핸들러 진입부 전수 게이트(82/186/261/303/332), 읽기 console 은 inline gate.has(GAME_JAM_MANAGE). 인가 우회 없음 | 무우회 | PASS (산술 6==6 은 우연; 실질 전수 게이트 OK) |
| AC-T4 (매퍼5 `${` 0건) | 0,0,0,0,0 | 0 | PASS |
| AC-T5 (jam_entries 2 CHECK) | xor 1 + entrant_type 1 | 2 | PASS |
| AC-T6 (@MockBean Jam >=5) | 5 (JamsMapper/JamEntriesMapper/JamTeamsMapper/JamTeamMembersMapper/JamStatusLogMapper, line 66/69/72/75/78) | >=5 | PASS (원문 same-line grep 은 0 오탐, AC 표현 보정 권고) |
## 시나리오 AC 커버
| VP | 커버 | 비고 |
|---|---|---|
| VP-1 게이트 (ADMIN/SUBADMIN+키/무키403/미인증401) | **미커버** | L1 미실행. JamAdminControllerTest 도달 못함 |
| VP-2 전이 감사 (JamLifecycle 허용/거부 + status_log insert) | **미커버** | JamLifecycleTest 미실행 |
| VP-3 출품 무결성 (소유/멤버/중복409/비소유422/비멤버422/EVAL422) | **미커버** | JamControllerTest 미실행 |
| VP-5 CSRF (쓰기 전수 누락→403 + mapper 미호출) | **미커버** | 미실행 |
## L3 (런타임 인가 플로우)
- 자동 실행: **불가** — 빌드 미컴파일로 앱 기동 자체 불가.
- needs_user_verification (컴파일 복구 후): WAR 기동 → (1) GAME_JAM_MANAGE 무권한 SUBADMIN 세션으로 POST /admin/jams/* 호출 시 403, (2) 미인증으로 GET /admin/jams 시 /login redirect, (3) ADMIN 세션 전이 후 jam_status_log 적재 확인.
## L2 (dev DB contract)
- skipped:no-dev-db — PGHOST/PGPORT/PGDATABASE/PGUSER 전부 미설정, 그리고 빌드 미컴파일로 contract 검증 대상(매퍼 반환 Map alias 정합 등) 부재. db/apply-local-ddl.sh + psql 바이너리는 존재. 원격/seed 지정 후 재호출 시 (a)XOR/status/active-UNIQUE CHECK 거부 (b)keyset SQL camelCase alias 정합 실측 가능.
## 로그 스캔
- n/a — 애플리케이션 미기동 (빌드 FAIL).
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| L1 full test 통과 | verify-all (mvnw -o test) | **FAIL (compile)** |
| contextLoads / 신규3테스트 / 회귀0 | surefire | 미실행 (게이트 차단) |
| AC-T1~T6 집합 전수 | grep cmd | 전 PASS |
| VP-1/2/3/5 시나리오 | L1 테스트 | 미커버 (L1 미실행) |
| L2 DB contract | apply-local-ddl.sh | skipped:no-dev-db |
---
# 재검증 (R2 — 보정 후) — 2026-06-24T11:05:00+09:00
> 1차 검증(상단)에서 L1 컴파일 BLOCKER FAIL(JamAdminController 24 errors, `response(HttpStatus,String)` 헬퍼 미정의) 판정.
> implementation 보정 완료 후 본 R2 재검증 수행. 1차 검증서는 보존(상단), 본 섹션 append.
## R2 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-all (L1 full test) | `JAVA_HOME=.../openjdk@21 ./mvnw -o test` | 0 | blocker | **PASS** |
| AC-T1 grep | `grep -c 'CREATE TABLE IF NOT EXISTS' docs/jam-ddl.sql` | 0 | blocker | pass (5) |
| AC-T1b schema 동기 | `grep -iE 'CREATE TABLE.*<t>' db/schema.sql` x5 | 0 | blocker | pass (5/5) |
| AC-T2 enum/CHECK 동기 | `grep` JamStatus enum + jams/ log CHECK IN | 0 | blocker | pass (4==4==4, schema 동기) |
| AC-T3 게이트 열거 | `grep -c requireJamManage / @PostMapping / gate.has` | 0 | blocker | pass (무우회) |
| AC-T4 매퍼5 `${` | `grep -c '\${' <매퍼5>` | 0 | blocker | pass (0,0,0,0,0) |
| AC-T5 entries 2 CHECK | `grep` xor + entrant_type (ddl+schema) | 0 | blocker | pass (2, 양측 동기) |
| AC-T6 @MockBean Jam | `grep -A1 '@MockBean' \| grep -c 'Jam.*Mapper'` | 0 | blocker | pass (5) |
| L2 contract-postgres | 격리 임시DB + jam-ddl 적용 + CHECK/UNIQUE/keyset 실측 | 0 | blocker | **pass** |
(분해 결과 병기)
| 단계 | 결과 |
|---|---|
| L1 typecheck/compile | **PASS** — BUILD SUCCESS, 컴파일 에러 0 (1차 24 errors 완전 해소) |
| L1 unit+regression | **PASS** — Tests run: 97, Failures: 0, Errors: 0, Skipped: 0 |
| L2 contract-postgres | **PASS** (격리 DB 실측: CHECK/UNIQUE/keyset 전수 정상) |
| 로그 스캔 | clean (benign WARN 1건 — static resource trailing slash, W2-1 무관) |
## R2 L1 테스트 카운트 (실측 인용)
```
[INFO] Tests run: 97, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
```
- 신규 테스트 전수 통과:
- `JamControllerTest` Tests run: 14, F0 E0 S0
- `JamAdminControllerTest` Tests run: 10, F0 E0 S0
- `JamLifecycleTest` Tests run: 8, F0 E0 S0
- `BibimbapApplicationTests` Tests run: 1, F0 E0 S0 (contextLoads — 신규 5매퍼 @MockBean 정합)
- 회귀 0: PermissionGateTest(7)/AdminConsoleControllerTest(13)/GameCommentControllerTest(18)/GameReviewControllerTest(21)/UserControllerCsrfTest(5) 전부 GREEN.
- 실패 클래스: **없음**.
## R2 집합 전수 AC 실측 (보정 grep)
| AC | 실측 | 기대 | 판정 |
|---|---|---|---|
| AC-T1 (jam-ddl CREATE TABLE) | 5 (line 8/49/77/104/157) | 5 | PASS |
| AC-T1b (schema.sql 5 테이블 동기, 큰따옴표 식별자) | jams/jam_teams/jam_team_members/jam_entries/jam_status_log 각 1 (line 381/422/450/477/530) | 5 전수 | PASS |
| AC-T2 (enum 4 == jams CHECK 4 == log to_status CHECK 4) | enum RECRUIT/DEV/EVAL/CLOSED(4), jams_status_check IN 4, jam_status_log_to_status_check IN 4. schema.sql 도 동기(408/549) | 4==4==4 | PASS |
| AC-T3 (쓰기핸들러 게이트 전수 + console inline) | requireJamManage 6(POST5+선언1), GET console gate.has(GAME_JAM_MANAGE). 인가 우회 0 | 무우회 | PASS |
| AC-T4 (매퍼5 `${` 0건) | 0,0,0,0,0 | 0 | PASS |
| AC-T5 (jam_entries 2 CHECK) | xor 1 + entrant_type 1 (ddl 135/139, schema 508/512) | 2 | PASS |
| AC-T6 (@MockBean Jam 매퍼) | 5 (JamsMapper/JamEntriesMapper/JamTeamsMapper/JamTeamMembersMapper/JamStatusLogMapper, line 66/69/72/75/78) | 5 (>=5) | PASS |
## R2 시나리오 AC 커버 (이번엔 실제 도달 — L1 테스트 GREEN)
| VP | 커버 | 근거 테스트 |
|---|---|---|
| VP-1 게이트 (ADMIN/SUBADMIN+키 통과·무키403·미인증401) | **커버 PASS** | JamAdminControllerTest 10 PASS (런타임 도달) |
| VP-2 전이 감사 (허용/거부 전이 + status_log insert) | **커버 PASS** | JamLifecycleTest 8 PASS |
| VP-3 출품 무결성 (소유/멤버/409/422) | **커버 PASS** | JamControllerTest 14 PASS |
| VP-5 CSRF (쓰기 누락→403 + mapper 미호출) | **커버 PASS** | JamAdminControllerTest / JamControllerTest 내 CSRF 케이스 (전체 GREEN) |
## R2 L2 (dev DB contract) — 실측 PASS
`bibimbap-db` 컨테이너(Up healthy) 가용. **사용자 dev 스키마 비파괴 원칙**상 격리 throwaway DB(`jam_l2_verify_*`)에 users/games stub + `docs/jam-ddl.sql` 전체 적용(ON_ERROR_STOP=1, 에러 0) 후 contract 실측, 검증 후 DROP. 사용자 dev(bibimbap) DB 의 jam 테이블은 0건 유지(미적용).
| contract | 케이스 | 결과 |
|---|---|---|
| status CHECK | jams.status='BOGUS' INSERT | **거부** (jams_status_check) |
| XOR CHECK | USER+양쪽채움 / USER+양쪽NULL / TEAM+user_id | **3종 전부 거부** (jam_entries_entrant_xor_check) |
| XOR 정상 | USER+user_id only | 통과(OK) |
| 활성 UNIQUE | 동일(jam_id,game_id) active 2건 | **거부** (ux_jam_entries_jam_game_active) |
| 활성 UNIQUE soft-delete | 1건 is_delete=true 후 동일키 재등록 | 허용(OK) — partial UNIQUE WHERE is_delete IS NOT TRUE 정상 |
| slug 활성 UNIQUE | 동일 slug active 중복 | **거부** (ux_jams_slug_active) |
| keyset row-comparison | `(created_at,id) < (cursor)` ORDER BY created_at DESC, id DESC | **실행 정상** — cursor 기준 더 오래된 행만 반환, idx_jams_visible_keyset 정합 |
| camelCase alias | `AS createdAt` 폴딩 실측 → `createdat` (비인용 소문자 폴딩 §33 패턴) | 5매퍼 전부 resultType=POJO(*Data, Map 0건) → MyBatis case-insensitive 매핑으로 **현재 무해** (concerns 기록) |
## R2 로그 스캔
clean — 실패 신호 0. 유일 WARN: `ResourceHandlerUtils: Appended trailing slash to static resource location` (Spring 정적 리소스 기동 경고, W2-1 변경과 무관한 기존 부트 동작).
## R2 종합 판정
overall: **PASS** (L1 BUILD SUCCESS + 97/97 GREEN + 집합전수 AC-T1~6 전 PASS + L2 contract 실측 PASS)
rollback_signal: none (실패 없음)
### L3 런타임 인가 (자동 불가 → needs_user_verification)
L1/L2 GREEN 으로 컴파일·DB계약·단위 인가는 커버. 실제 WAR 기동 E2E 인가는 자동 미수행(앱 미기동):
1. GAME_JAM_MANAGE 무권한 SUBADMIN 세션으로 POST /admin/jams/* → 403 기대
2. 미인증 GET /admin/jams → /login redirect 기대
3. ADMIN 세션 상태 전이 후 jam_status_log 적재 확인
## R2 Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| L1 full test 통과 | verify-all (mvnw -o test) | **PASS (97/97)** |
| contextLoads / 신규3테스트 / 회귀0 | surefire | **PASS** |
| AC-T1~T6 집합 전수 | grep cmd (보정) | **전 PASS** |
| VP-1/2/3/5 시나리오 | L1 테스트 (런타임 도달) | **커버 PASS** |
| L2 DB contract | 격리DB + jam-ddl 실측 | **PASS** |
| L3 런타임 인가 E2E | WAR 기동 | needs_user_verification |
---
# W2-2 검증 결과 (심사위원 역할 / jam_judges) — append 2026-06-24T11:12:00+09:00
## Acceptance Criteria (입력 받은 그대로 인용)
- VP-1 지정게이트: ADMIN/SUBADMIN+GAME_JAM_MANAGE 통과·SUBADMIN무키 403·미인증 401. 지정/해제/조회.
- VP-2 isJudge: 지정 true·미지정 false·미인증 false.
- VP-3 ★자기출품 충돌 isOwnEntry: 개인출품 true·팀멤버출품 true·타인/타팀 false·비활성 false.
- VP-4 누구나 지정: USER role 대상 지정 200(전역 role 검사 없음)·미존재 userId 404.
- VP-5 CSRF: 지정/해제 CSRF 누락 → 403 + mapper 미호출.
- VP-6 멱등: 이미 지정 (jamId,userId) 재지정 → 409.
- AC-T1~T7 집합 전수 (grep -c 그대로).
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-all (L1 full) | `JAVA_HOME=…openjdk@21… ./mvnw -o test` | 0 | blocker | **pass** |
| L2 contract-postgres | 격리 throwaway DB + jam-ddl + jam-judge-ddl 실측 | 0 | blocker | **pass** |
| AC-T1~T7 집합전수 | `grep -c …` (원문) | 0 | blocker | **전 pass** |
분해:
| 단계 | 결과 |
|---|---|
| L1 typecheck (BUILD) | **pass** — BUILD SUCCESS |
| L1 unit+regression | **pass** — Tests run 119 / Failures 0 / Errors 0 / Skipped 0 |
| L1 contextLoads (@MockBean JamJudgesMapper+JamRoleGate) | **pass** — NoSuchBeanDefinitionException 없음 |
| L1 신규 JamRoleGateTest | **pass** — 8/8 |
| L1 신규 JamJudgeAdminControllerTest | **pass** — 14/14 (※AC 예측 16, 실측 14 — 아래 concern) |
| L2 contract-postgres (a)(b)(c) | **pass** (격리DB, 검증 후 DROP) |
| 로그 스캔 | clean — 실패신호 0, WARN 5건 전부 ByteBuddy agent + Spring 정적리소스 (W2-2 무관) |
## L1 상세 (전 클래스 GREEN)
- BUILD SUCCESS. 집계: **Tests run: 119, Failures: 0, Errors: 0, Skipped: 0**. (직전 W2-1 재검증 97 → +22)
- 클래스별 (잼 관련): JamRoleGateTest 8/8, JamJudgeAdminControllerTest 14/14, JamAdminControllerTest 10/10, JamControllerTest 14/14, JamLifecycleTest 8/8, PermissionGateTest 7/7, BibimbapApplicationTests 1/1(contextLoads).
- 실패 클래스: **없음**.
## 시나리오 VP-1~6 커버 (L1 테스트 도달 + L2 보강)
| VP | 커버 경로 | 판정 |
|---|---|---|
| VP-1 지정게이트(403/401/통과, 지정/해제/조회) | JamRoleGateTest 8 + JamJudgeAdminControllerTest 14 GREEN | **PASS (커버)** |
| VP-2 isJudge(t/f/미인증f) | JamRoleGateTest GREEN | **PASS (커버)** |
| VP-3 isOwnEntry(개인t/팀멤버t/타인f/타팀f/비활성f) | JamJudgeAdminControllerTest GREEN + **L2(b) DB 실측 6/6 정합** | **PASS (커버+DB실증)** |
| VP-4 누구나 지정(USER 200 / 미존재 404) | JamJudgeAdminControllerTest GREEN | **PASS (커버)** |
| VP-5 CSRF 누락 403 + mapper 미호출 | JamJudgeAdminControllerTest GREEN (AC-T3 isValid 2건 실증) | **PASS (커버)** |
| VP-6 멱등 재지정 409 | JamJudgeAdminControllerTest GREEN + **L2(a) UNIQUE 중복 INSERT 거부 실측** | **PASS (커버+DB실증)** |
## 집합 전수 AC-T1~T7 (실측 vs 기대)
| AC | grep 실측 | 기대 | 판정 |
|---|---|---|---|
| AC-T1 ddl ADD CONSTRAINT | 3 | 3 | **PASS** |
| AC-T1 ddl CREATE UNIQUE INDEX | 1 | 1 | **PASS** |
| AC-T1 schema.sql 동기 | jam_judges + 3 FK(jam/user/assigned_by) + ux_jam_judges_jam_user UNIQUE 존재 (db/schema.sql:566-594) | 동일 제약 | **PASS** |
| AC-T2 핸들러 3 requireJamManage 진입(인가우회 0) | JamJudgeAdminController CsrfTokens.isValid 2(쓰기) + GET 조회 — 게이트 진입 실증은 JamJudgeAdminControllerTest 14 GREEN(403/401 케이스) | 3 게이트 / 우회 0 | **PASS** |
| AC-T3 CsrfTokens.isValid | 2 | 2 (지정/해제, GET 제외) | **PASS** |
| AC-T4 isOwnEntry entrant_type USER/TEAM 양경로 | USER=1, TEAM=1 | 각 1 | **PASS** |
| AC-T5 `${` JamJudgesMapper, JamEntriesMapper | 0, 0 | 0, 0 | **PASS** |
| AC-T6 @MockBean JamJudgesMapper+JamRoleGate 등록 | line73-74 `@MockBean private JamJudgesMapper`, line88-89 `@MockBean private JamRoleGate` 실측 정합 | 양 매퍼 등록 | **PASS** |
| AC-T7 전역 RBAC 테이블 무변경 | 0 | 0 | **PASS** |
## L2 DB Contract (격리 throwaway DB — 비파괴, 검증 후 DROP 완료)
방식: bibimbap-db 컨테이너 내 `verify_w22_throwaway` 신규 DB → 최소 parent(users/games/jams/jam_teams/jam_team_members/jam_entries) + 실파일 docs/jam-judge-ddl.sql 적용 → contract 실행 → DROP(잔존 0 확인). 사용자 dev(bibimbap) 스키마 무접촉.
- **(a) ux_jam_judges UNIQUE**: (jam_id=1000,user_id=10) 1차 INSERT OK → 동일 키 2차 INSERT `ERROR: duplicate key value violates unique constraint "ux_jam_judges_jam_user"` (멱등 가드 = VP-6 409 DB 근거). DELETE 후 재INSERT OK(역할 회수→재지정 모델 실증). **PASS**
- **(b) isOwnEntry OR-EXISTS**: 개인(9001,u1)=t, 팀멤버(9002,u2)=t, 팀오너(9002,u1)=t, 타인개인(9001,u3)=f, 비멤버(9002,u3)=f, 삭제건(9003,u3)=f → 6/6 기대 정합. **PASS**
- **(c) alias snake→camel**: jam_judges 컬럼 전부 순수 snake_case(id/jam_id/user_id/assigned_by/created_at), 큰따옴표 camel alias 0 → §33 case-folding 해당 없음, POJO resultType 안전. **PASS**
## 로그 스캔
clean — Exception/stacktrace 0. WARN 5건: ByteBuddy/Mockito Java-agent dynamic-load 안내 4 + Spring `ResourceHandlerUtils: Appended trailing slash` 정적리소스 기동 경고 1. 전부 프레임워크 기존 노이즈, W2-2 변경 무관.
## 종합 판정
overall: **PASS** (L1 BUILD SUCCESS + 119/119 GREEN + 집합전수 AC-T1~T7 전 PASS + L2 contract a/b/c 실측 PASS)
rollback_signal: none (실패 없음)
### L3 런타임 인가 (자동 불가 → needs_user_verification)
L1(단위 인가)+L2(DB 계약) GREEN 으로 컴파일·DB제약·단위 인가는 커버. 실제 WAR 기동 E2E 는 자동 미수행(앱 미기동) — 사용자 수동 단계:
1. SUBADMIN(GAME_JAM_MANAGE 무권한) 세션으로 POST /admin/jams/{id}/judges → **403** 기대.
2. 미인증으로 GET /admin/jams/{id}/judges → 401 또는 /login redirect 기대.
3. ADMIN 세션 지정 → jam_judges 행 적재, 동일 (jam,user) 재지정 → 409 기대.
4. USER role 대상 지정 200(전역 role 검사 없음), 미존재 userId → 404 기대.
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| L1 full test 통과 | verify-all (mvnw -o test) | **PASS (119/119)** |
| contextLoads / 신규2테스트 / 회귀0 | surefire | **PASS** (JamRoleGateTest 8, JamJudgeAdminControllerTest 14) |
| AC-T1~T7 집합 전수 | grep cmd (원문) | **전 PASS** |
| VP-1~6 시나리오 | L1 테스트(런타임 도달) + L2 보강(VP-3,6) | **커버 PASS** |
| L2 DB contract (a)(b)(c) | 격리 throwaway DB 실측 | **PASS** |
| L3 런타임 인가 E2E | WAR 기동 | needs_user_verification |
---
# W2-3 검증 결과 (잼 평가 동결 스키마 — ⚠️클러스터 권위)
검증 시각: 2026-06-24T11:35:00+09:00 · scope: docs/jam-eval-ddl.sql(신규) + db/schema.sql(동기 사본 append) · Java 코드 0 · 신규 매퍼/컨트롤러/테스트 0(하류 W2-4/5/6 소관, 비목표)
## Acceptance Criteria (입력 받은 그대로 인용)
- AC-T1: jam-eval-ddl `grep -c 'CREATE TABLE IF NOT EXISTS'`==4 AND `CREATE.*VIEW`/jam_score_stats VIEW 1건 AND schema.sql 동기.
- AC-T2: 트랙 CHECK 4종(jam_awards track 또는 동등 CHECK IN 항목) 존재.
- AC-T3: UNIQUE 4건(1심사위원1기준1점·1인1표 등) + 무결성 CHECK(score 1~5 등) 존재.
- AC-T4: 매퍼 `${` — W2-3 신규 매퍼 0이므로 N/A(하류 검사).
- AC-T5: game_reviews/game_review_stats 쓰기·변경 0(기존 리뷰 인프라 무변경). ALTER/DROP 부재 확인.
- AC-T6: FK 적용 순서 — alphabetical glob 에서 jam-ddl < jam-eval-ddl < jam-judge-ddl 순서로 FK 생성 가능(L2 실생성 확인).
- L1: full `./mvnw -o test` BUILD SUCCESS, 119 GREEN 유지(신규 0, 회귀 0).
- L2(핵심): 격리 throwaway DB DDL 적용 / 제약 거부 a~d / VIEW 집계 정합(fan-out 가드) / alias snake 확인.
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-all(L1) | `JAVA_HOME=... ./mvnw -o test` | 0 | blocker | **pass** (119 GREEN) |
| set-exhaustive AC-T1~6 | `grep -c ...` (원문) | 0 | blocker | **전 PASS** |
| §33 contract-postgres (L2) | 격리 throwaway DB 알파벳순 DDL 적용+실측+DROP | 0 | blocker | **pass** |
(분해 결과)
| 단계 | 결과 |
|---|---|
| L1 회귀(typecheck+unit) | **pass** BUILD SUCCESS, Tests run 119 / F0 / E0 / S0 (W2-2 119 그대로, Java src 무변경) |
| L2 DDL 적용(4테이블+VIEW+9FK, ON_ERROR_STOP=1) | **pass** 에러 0, 알파벳순 FK 해소 |
| L2 제약 거부 (a)score범위 (b)scores UNIQUE (c)votes 1인1표 (d)awards track CHECK | **pass** 4종 전부 거부 실측 |
| L2 VIEW 집계 정합33 fan-out 가드) | **pass** 손계산 일치·미채점 제외·NULLIF·fan-out 불변 |
| L2 alias snake 확인 | **pass** 6컬럼 소문자 비인용 |
| 로그 스캔 | **clean** ON_ERROR_STOP DDL ERROR 0, VIEW 쿼리 WARNING 0 (의도된 제약거부 ERROR는 검증목적) |
## L1 회귀 (테스트 카운트)
BUILD SUCCESS · **Tests run 119 / Failures 0 / Errors 0 / Skipped 0** W2-2 검증서 119 GREEN 그대로 유지. Java src 무변경이라 카운트 동일(예상 일치). 클래스별: PermissionGateTest 7 · JamRoleGateTest 8 · AdminConsoleControllerTest 13 · JamControllerTest 14 · GameCommentControllerTest 18 · GameReviewControllerTest 21 · UserControllerCsrfTest 5 · JamJudgeAdminControllerTest 14 · JamAdminControllerTest 10 · BibimbapApplicationTests 1 · JamLifecycleTest 8. 실패 클래스 없음.
## 집합 전수 AC-T1~6 실측 vs 기대
| AC | 기대 | 실측 | 판정 |
|---|---|---|---|
| T1 | CREATE TABLE IF NOT EXISTS=4, VIEW 1, schema.sql 동기 | ddl: 4테이블(jam_criteria/jam_scores/jam_votes/jam_awards)+VIEW jam_score_stats / schema.sql line608·642·685·721 4테이블 + line765 VIEW 동기 | **PASS** |
| T2 | 트랙 CHECK 4종 | jam_awards_track_check = `award_track IN ('JUDGE','USER_RATING','POPULAR','GRAND')` 트랙 enum 4값. DB 실생성 확인 | **PASS** |
| T3 | UNIQUE 4 + 무결성 CHECK | UNIQUE INDEX 4: ux_jam_criteria_jam_key·ux_jam_scores_jam_game_judge_criterion(1심사위원1기준1점ux_jam_votes_jam_voter(1인1표ux_jam_awards_jam_track_game / CHECK 4: weight>=0·score BETWEEN 1 AND 5·track IN·rank>=1 | **PASS** |
| T4 | 매퍼 `${` (신규 매퍼 0 → N/A) | 신규 매퍼 0 — 하류 W2-4 위임. N/A 명시 | **N/A** |
| T5 | review 인프라 변경 0, ALTER/DROP 부재 | game_reviews/game_review_stats refs 3건 전부 주석(무변경 단언) / ALTER 17건 전부 신규 jam_* 제약추가·시퀀스 OWNED / **DROP 0** | **PASS** |
| T6 | 알파벳순 FK 생성 가능 | jam-ddl(jams 제공)→jam-eval-ddl(9FK: jams5·games3·users3 참조)→jam-judge-ddl 순 적용, FK 9건 실생성·에러 0 | **PASS** |
## L2 DB contract 실측 (★W2-3 실질 게이트)
환경: bibimbap-db(postgres:16, Up healthy) 격리 throwaway DB `verify_w23_throwaway` — users/games 최소부모 + jam-ddl + jam-eval-ddl(SUT) + jam-judge-ddl 알파벳순 적용, 실측 후 DROP. 사용자 dev DB(bibimbap) 무접촉(검증 후 jam_eval 테이블 0건 재확인). 잔존 throwaway 0.
### 1. DDL 적용 (pass)
4테이블 + VIEW 생성, FK 9건 실생성(pg_constraint 확인: jam_criteria 1 · jam_scores 3 · jam_votes 3 · jam_awards 2), CHECK 4종 실생성, UNIQUE INDEX 4 + PK 4. ON_ERROR_STOP=1 에러 0.
### 2. 제약 거부 실측 (pass — 4종 전부 거부)
- (a) jam_scores score=0 / score=6 → `violates check constraint "jam_scores_score_check"` 거부 (score=5 정상 INSERT)
- (b) jam_scores 중복 (jam=50,game=100,judge=1,criterion='fun') → `duplicate key ... "ux_jam_scores_jam_game_judge_criterion"` 거부
- (c) jam_votes 동일 voter(3) 동일 jam 재투표 → `duplicate key ... "ux_jam_votes_jam_voter"` 거부 (1인1표)
- (d) jam_awards track='BOGUS' → `violates check constraint "jam_awards_track_check"` 거부 (track='GRAND' 정상) + rank=0 → `jam_awards_rank_check` 거부
### 3. ★jam_score_stats VIEW 집계 정합 — fan-out 가드 (pass)
샘플: criteria fun(weight 3.0)·art(weight 1.0). GameA(100) raw 3행 → 손계산 대조:
- per_criterion: fun avg=(4+2)/2=3.0, art avg=5/1=5.0
- weighted_total=(3.0×3+5.0×1)/4=**3.5** = VIEW 3.500 ✓ / simple_total=AVG(3,5)=**4.0**=4.000 ✓ / scored_criteria=**2** ✓ / judge_count=MAX(2,1)=**2** ✓
- (i) 가중합/단순합 정확 ✓
- (ii) 미채점 criterion 제외: GameB(200) fun만 채점·art 미채점 → weighted=(4×3)/3=4.0·scored_criteria=1(art 분모서 제외) ✓
- (iii) NULLIF 0-division: GameB 점수 전삭제 → VIEW 행 미생성·에러 0·div0 0 / weight합=0 강제경로 → weighted_total NULL 반환(에러 아님) ✓
- **fan-out 불변성**: GameA fun 심사위원 2→4명(raw 3→5행)에도 scored_criteria=2 불변·judge_count=MAX(4,1)=4 정확·weighted_total=3.5 불변 → 자식 행수만큼 COUNT/AVG 부풀지 않음. **game_review_stats 선례 fan-out 버그 재발 0**.
### 4. alias 정합 (pass)
VIEW 컬럼 6개 전부 소문자 snake 비인용: `jam_id`·`game_id`·`weighted_total`·`simple_total`·`scored_criteria`·`judge_count`. 하류 매퍼가 `AS "camelCase"` 인용으로 매핑할 계약(W2-4 검증 위임). 본 단계는 VIEW 자체 컬럼명 확인만.
## 실패 상세
해당 없음 (전 항목 PASS).
## 종합 판정
**overall: PASS**
verdict: **PASS** — L1 회귀 무변경(119 GREEN 유지) + 집합 전수 AC-T1~6 전 PASS(T4 N/A) + L2 dev DB contract 전 항목 PASS(DDL/제약거부/VIEW집계 fan-out 가드/alias). W2-3 동결 스키마는 PASS이므로 **W2-4/5/6 착수 가능**. VIEW 집계 정합을 손계산·fan-out·NULLIF·미채점 제외 4축으로 엄격 검증했고 game_review_stats 선례 버그 재발 0 확인.
rollback_signal: none (전 PASS — 파괴적 조작 영향 없음. throwaway DB 격리 적용·DROP 완료, 사용자 dev DB 무접촉.)
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| AC-T1 4테이블+VIEW+schema 동기 | grep-c + schema.sql line 확인 + L2 DDL 적용 | **PASS** |
| AC-T2 track CHECK 4값 | grep + L2 pg_constraint 실생성 | **PASS** |
| AC-T3 UNIQUE 4 + CHECK | grep + L2 인덱스/제약 실생성 + 거부 실측 | **PASS** |
| AC-T4 매퍼 ${ | 신규 매퍼 0 → N/A(하류 위임) | **N/A** |
| AC-T5 review 인프라 무변경 | grep ALTER/DROP/game_review* | **PASS** |
| AC-T6 알파벳순 FK 생성 | L2 순차 적용·FK 9건 실생성 | **PASS** |
| L1 회귀 무변경 119 | `./mvnw -o test` | **PASS** |
| L2 VIEW 집계 fan-out 가드 | throwaway DB 손계산 대조 | **PASS** |
---
# 검증 결과 — W2-4 (심사위원 평가) · append 2026-06-24
> verification-advisor v1 · 제한 컨텍스트(설계·diff·구현경로 무접촉, AC + 실행결과만). bibimbap-db 컨테이너 가용(Up healthy). L2 는 throwaway DB(`bibimbap_l2_w24`)에 `db/schema.sql`(W2-3/W2-4 동결 스키마 통합 권위본) 적용 후 contract → 검증 → DROP. 사용자 dev DB(`bibimbap`) 무접촉 확인.
## Acceptance Criteria (입력 받은 그대로 인용)
- **VP 3중게이트**: CSRF누락 403 / 미인증 401 / 비-isJudge 403 / 잼없음 404 / 평가기간 외 422 / 출품작없음 404 / 자기출품 422 / 정상 200. 순서대로 도달.
- **VP UPSERT 멱등**: 동일 (심사위원,기준,평가단위) 재채점 → update(중복행 0).
- **VP criterion 화이트리스트**: 잼 소속 아닌 criterion / score 범위 밖 → 422.
- **VP CSRF**: 쓰기 CSRF 누락 → 403 + mapper 미호출.
- **AC-T1~T6** (impl echo, grep 그대로 1 어긋나면 FAIL): 신규 매퍼 3파일 `${` 0 / JamScoreStatsMapper camelCase alias 전부 `AS "..."` 큰따옴표 / submitScores 진입부 4가드(CSRF+3게이트) / 401·403·422 정책응답 전수 / BibimbapApplicationTests 신규 3매퍼 @MockBean.
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| L1-full-test | `JAVA_HOME=…/openjdk@21… ./mvnw -o test` | 0 | blocker | **pass** |
| AC-T2 grep | `grep -rc '${' <매퍼 3파일>` | 0 | blocker | **pass** (3파일 모두 0) |
| AC-T3 grep | `grep -oE 'AS "…"' JamScoreStatsMapper` + 일반매퍼 비인용 | 0 | blocker | **pass** (VIEW매퍼 5건 큰따옴표 / 일반매퍼 0) |
| AC-T4 grep | `grep -rEc 'CREATE TABLE\|ALTER TABLE\|CREATE…VIEW' <매퍼 3파일>` + git status ddl | 0 | blocker | **pass** (DDL 토큰 0, ddl/schema diff 0) |
| AC-T5 열거 | `grep isEqualTo(HttpStatus.*)` 분포 | 0 | blocker | **pass** (401×2/403×4/404×3/422×5/200×5) |
| AC-T6 열거 | `grep @MockBean … (3매퍼)` | 0 | blocker | **pass** (라인 88-95 인접 등록) |
| L2-1 UPSERT | throwaway DB INSERT…ON CONFLICT 2회 동일4키 | 0 | blocker | **pass** (행수 1 유지, score 3→5 갱신) |
| L2-2 VIEW alias 케이스폴딩(§33) | 큰따옴표 alias SELECT → 반환 컬럼명 실측 | 0 | blocker | **pass** (camelCase 보존, 폴딩 0) |
| L2-3 가중집계 정합 | VIEW 손계산 대조 | 0 | blocker | **pass** (전 값 일치, fan-out 0) |
### 분해 결과
| 단계 | 결과 |
|---|---|
| L1 typecheck (compile) | pass (BUILD SUCCESS) |
| L1 unit+regression | pass — **148/148** (Failures 0, Errors 0, Skipped 0) |
| L1 신규 JamScoringControllerTest | pass — 19/19 |
| L1 신규 JamEvalWindowTest | pass — 10/10 |
| L1 contextLoads (BibimbapApplicationTests, 3매퍼 @MockBean) | pass — 1/1 (NoSuchBeanDefinitionException 0) |
| L2 UPSERT ON CONFLICT (jam_scores 4키) | pass |
| L2 contract VIEW alias 케이스폴딩 | pass |
| L2 가중집계 정합 | pass |
| 로그 스캔 | clean (WARN 5건 전부 환경성 noise: JVM Sharing/ByteBuddy agent/Spring static trailing-slash — blocker 아님) |
## L1 카운트
- BUILD SUCCESS. **148 tests run, 0 failures, 0 errors, 0 skipped.**
- 회귀 정합: 직전 119 + 신규 29 = 148 ✓ (예상 일치).
- 신규: JamScoringControllerTest 19/19, JamEvalWindowTest 10/10.
- 실패 클래스: **없음**.
## 시나리오 VP 커버 (L1 — JamScoringControllerTest 19 메서드 열거로 확인)
| VP | 커버 테스트 (메서드명 실측) | 판정 |
|---|---|---|
| 3중게이트 CSRF누락 403 | `submitRejectsMissingCsrf` | pass |
| 미인증 401 | `submitReturns401WhenUnauthenticated` (+myScores/summary 미인증) | pass |
| 비-isJudge 403 | `submitReturns403WhenNotJudge` (+myScores 403) | pass |
| 잼없음 404 | `submitReturns404WhenJamMissing` (+summary 404) | pass |
| 평가기간 외 422 | `submitReturns422WhenEvalClosed` | pass |
| 출품작없음 404 | `submitReturns404WhenEntryMissing` | pass |
| 자기출품 422 | `submitReturns422WhenOwnEntry` | pass |
| 정상 200 | `submitUpsertsWhenAllGatesPass` / `submitReturns200OnIdempotentResubmit` | pass |
| UPSERT 멱등 | `submitReturns200OnIdempotentResubmit` (savedCount=1) + L2-1 DB 실측 | pass |
| criterion 화이트리스트 422 | `submitReturns422WhenUnknownCriterion` | pass |
| score 범위 밖 422 | `submitReturns422WhenScoreOutOfRange` | pass |
| 빈 점수 422 | `submitReturns422WhenScoresEmpty` | pass |
- 게이트 순서(CSRF→인증→자격→기간→출품작→충돌→criterion) 첫 실패지점 응답은 위 메서드들이 각 분기를 단독 트리거하는 형태로 커버됨(L1 통과로 간접 확인).
## 집합 전수 AC-T1~T6 실측 vs 기대
| AC | 기대 (impl echo) | 실측 | 판정 |
|---|---|---|---|
| AC-T1 submitScores 진입부 4가드(CSRF+3게이트) | 전수 존재 (impl: 라인 78/87/96/104) | **구현코드 직접확인은 advisor 권한 밖** → L1 VP 게이트 테스트 12종 전부 PASS 로 동작 보증. 정적 라인검증은 needs_user_verification | **pass(간접)** |
| AC-T2 신규 매퍼 3파일 `${` 0건 | 0 / 0 / 0 | 0 / 0 / 0 | **pass** |
| AC-T3 VIEW매퍼 camelCase alias 큰따옴표 전수 | 5건 `AS "…"` / 일반매퍼 0 | gameId·judgeCount·scoredCriteria·simpleTotal·weightedTotal 5건 `AS "…"`; 비인용 camelCase 0; 일반매퍼 JamCriteriaMapper/JamScoresMapper 각 0 | **pass** |
| AC-T4 신규 테이블/VIEW 0건 | 매퍼 DDL토큰 0 + ddl/schema diff 0 | 매퍼 3파일 DDL토큰 0; git status ddl.sql/schema.sql 변경 0 | **pass** |
| AC-T5 401/403/422 정책응답 전수 | 401×2/403×4/404×3/422×5/200×5 | `isEqualTo(HttpStatus.*)` 분포 = 401×2 / 403×4 / 404×3 / 422×5 / 200×5 | **pass** |
| AC-T6 신규 3매퍼 @MockBean | 3건 + contextLoads PASS | 라인 88/91/94 `@MockBean` → 89/92/95 jamCriteriaMapper/jamScoresMapper/jamScoreStatsMapper 인접; contextLoads 1/1 PASS | **pass** |
## L2 상세 (dev DB contract — throwaway `bibimbap_l2_w24`, schema.sql 적용 후 DROP)
**L2-1 UPSERT ON CONFLICT 멱등**: `ux_jam_scores_jam_game_judge_criterion (jam_id,game_id,judge_user_id,criterion_key)` UNIQUE 타깃 정합 확인. 동일 4키 2회 INSERT…ON CONFLICT → 1차 후 rows=1 score=3, 2차(score=5) 후 **rows=1 유지(중복 0) score=5 갱신**. → PASS.
**L2-2 VIEW alias 케이스폴딩(§33 핵심)**: 매퍼가 발행하는 큰따옴표 alias SELECT 를 Postgres 직접 실행 → 반환 컬럼명 `jamId|gameId|weightedTotal|simpleTotal|scoredCriteria|judgeCount` (camelCase 보존, 폴딩 0). 대조군(비인용 `AS weightedTotal`) → `weightedtotal|judgecount` 소문자 폴딩 재현. **GameReviewStatsMapper BUG-2(비인용→소문자→null) 메커니즘 확인 + 본 매퍼 큰따옴표로 회피 입증**. → PASS.
**L2-3 가중집계 정합**: VIEW 정의 = per_criterion(criterion별 avg + count distinct judge) 선집계 후 LEFT JOIN(fan-out 회피). game500(criterion weight 상이 fun2.0/art1.0/tech0.5, judge 2명) weighted_total=3.786 [손계산 (4.0×2+4.5×1+1.5×0.5)/3.5=3.7857] / simple_total=3.333 / scored_criteria=3 / judge_count=2 — **전 값 일치**. game501(부분입력 fun만) weighted=4.000/simple=4.000/scored=1/judge=1 — 미채점 criterion 분모 제외·judge_count 부분참여 반영 일치. 정렬 weighted DESC NULLS LAST → 501>500 결정론 정합. → PASS.
## L3 런타임 인가 (needs_user_verification — 수동 단계)
- 인증/인가/롤 플로우 = L1+L2+L3 의무(전략표 line 23). L1/L2 GREEN 으로 단위·계약은 보증되나, **실제 런타임 스택 E2E 는 advisor 무접촉 범위**:
1. WAR 기동 후 심사위원(isJudge) 로그인 → EVAL 상태·구간내 잼의 타인 출품작 채점 POST/PUT → 200 + jam_scores UPSERT 반영.
2. 미심사위원/미인증/CSRF누락 실호출 시 각 403/401/403 + mapper 미호출.
3. AC-T1 정적 라인검증(submitScores 진입부 4가드 라인 78/87/96/104) — 구현코드 직접 grep 은 사용자/orchestrator 가 확인.
## 종합 판정
**verdict: PASS** (L1 148/148 GREEN + 집합 전수 AC-T1~T6 전건 일치 + L2 contract 3종 PASS + 로그 clean)
overall: **pass**
rollback_signal: none (전 PASS — 파괴적 조작 없음. L2 는 throwaway DB 격리 적용·DROP 완료, 사용자 dev DB `bibimbap` 무접촉 확인됨)
## FAIL 항목 실제 출력 인용
- 해당 없음 (FAIL 0건).
## Acceptance 매칭 (요약)
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| VP 3중게이트 순서 | JamScoringControllerTest 12 게이트 메서드 (L1) | **PASS** |
| VP UPSERT 멱등 | `submitReturns200OnIdempotentResubmit` (L1) + L2-1 DB 실측 | **PASS** |
| VP criterion 화이트리스트 | `submitReturns422WhenUnknownCriterion`/`…ScoreOutOfRange` (L1) | **PASS** |
| VP CSRF 누락 403 | `submitRejectsMissingCsrf` (L1) | **PASS** |
| AC-T1 submitScores 4가드 | L1 VP 12종 PASS (정적 라인 = needs_user_verification) | **PASS(간접)** |
| AC-T2 매퍼 `${` 0 | grep | **PASS** |
| AC-T3 VIEW alias 큰따옴표 | grep + L2-2 케이스폴딩 실측 | **PASS** |
| AC-T4 신규 DDL 0 | grep + git status | **PASS** |
| AC-T5 정책 응답 전수 | grep HttpStatus 분포 | **PASS** |
| AC-T6 3매퍼 @MockBean | grep + contextLoads PASS | **PASS** |
| L2 VIEW 가중집계 정합 | throwaway DB 손계산 대조 | **PASS** |
---
# 검증 결과 — W2-5 인기투표 (append)
> generated_at: 2026-06-24T12:20:00+09:00 · agent: verification-advisor v1 · phase: verification
> concerns_checked: true
## Acceptance Criteria (입력 받은 그대로 인용)
### 시나리오 AC (VP)
- 1인1표 토글: 첫 투표 cast / 재투표 update(중복행 0, no-op or 변경) / DB UNIQUE(jam_id,voter_user_id).
- 평가기간 게이트: vote/cancelVote 평가기간 외 → 422. CSRF 누락 → 403. 미로그인 → 401(results 는 공개).
- 종료후 공개: results + jam-detail JSP 2지점 — CLOSED 또는 eval_end 경과 시만 count 노출, 진행 중 은닉(밴드왜건 회피).
### 집합 전수 AC (AC-T1~T4 — grep 그대로, 1 어긋나면 FAIL)
- AC-T1: 상태변경 핸들러 2(vote/cancelVote) + CsrfTokens.isValid 2 + 평가기간 게이트(JamEvalWindow) 2.
- AC-T2: JamVotesMapper `${` 0건.
- AC-T3: 노출 게이트 2지점(JamVoteController.results + jam-detail.jsp) CLOSED/eval_end 조건.
- AC-T4: jam_votes DDL 변경문 0(docs/*-ddl.sql, schema.sql diff 0).
- listCountsByJam camelCase alias 전부 `AS "..."` 인용(gameId/voteCount), 비인용 0.
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-L1-fulltest | `JAVA_HOME=…openjdk@21 ./mvnw -o test` | 0 | blocker | pass |
| AC-T1 grep (handlers/CSRF/gate) | `grep -nE '@PostMapping\|@DeleteMapping' …` + `grep -c CsrfTokens.isValid` + `grep -c JamEvalWindow.isOpen` | 0 | blocker | pass |
| AC-T2 grep (`${` 0) | `grep -c '\${' JamVotesMapper.java` | 0 | blocker | pass |
| AC-T3 grep (노출게이트 2지점) | controller results + jam-detail.jsp resultsPublic | 0 | blocker | pass |
| AC-T4 (jam_votes DDL diff 0) | `git status --short` + DDL grep | 0 | blocker | pass |
| alias 인용 (gameId/voteCount) | `grep -nE 'AS "..."'` + 비인용 대조 | 0 | blocker | pass |
| L2-1 UNIQUE toggle (throwaway DB) | psql 5433 / throwaway / DROP | 0 | blocker | pass |
| L2-2 alias 케이스폴딩 §33 (throwaway DB) | psql quoted vs non-quoted | 0 | blocker | pass |
| 로그 스캔 | grep surefire-reports FAIL/ERROR | 0 | warning | clean |
(분해 결과 병기)
| 단계 | 결과 |
|---|---|
| L1 typecheck/compile | pass (BUILD SUCCESS, Nothing to compile — 컴파일 산출물 최신) |
| L1 unit+regression | pass — 171/171 (Failures 0 / Errors 0 / Skipped 0) |
| L1 contextLoads (JamVotesMapper @MockBean) | pass — BibimbapApplicationTests 1/1 (NoSuchBeanDefinitionException 없음) |
| L2 contract-UNIQUE | pass (duplicate key 거부 실측) |
| L2 contract-alias §33 | pass (quoted=gameId/voteCount 보존, non-quoted 대조=gameid/votecount 폴딩) |
| 로그 스캔 | clean (FAIL/ERROR/Exception 0, 잔여는 Mockito self-attach/JDK agent 경고 = 기존 noise) |
## L1 카운트 + 실패 클래스
- BUILD SUCCESS. Tests run: 171, Failures: 0, Errors: 0, Skipped: 0.
- 신규 JamVoteControllerTest: **23** PASS (작업지시 기대 24 와 -1 불일치 — 전부 PASS 이므로 회귀 아님, 카운트 어긋남만 concern 기록).
- 회귀 baseline: 직전 148 + 신규 23 = **171** (작업지시 기대 172 와 -1, 원인은 신규 테스트 24→23 차이 1건; 기존 148 회귀 0 유지).
- 실패 클래스: 없음.
## 시나리오 VP 커버 (L1 단위테스트 + L2 contract)
| VP | 내용 | 커버 |
|---|---|---|
| VP-1 1인1표 | 2차 INSERT UNIQUE 거부 / 토글 UPDATE 1행 유지 | L1(JamVoteControllerTest) + **L2 실측 PASS** |
| VP-2 평가기간 게이트 | 평가기간 외 vote/cancelVote → 422, mapper 미호출 | L1 covered (게이트 호출 2 grep + 단위테스트) |
| VP-3 미로그인 | vote/cancelVote/mine 401, results 공개 200 | L1 covered |
| VP-4 밴드왜건 은닉 | 진행 중 results:null / 종료 후 노출, JSP 동일 | L1 + 정적 게이트 2지점 확인 |
| VP-5 CSRF | POST/DELETE CSRF 누락 403 mapper 전 차단 | L1 covered (CsrfTokens.isValid 2 grep) |
| VP-6 DB-방언 계약 | Map 키 gameId/voteCount 정합, GROUP BY count | **L2 실측 PASS** |
| VP-7 contextLoads | JamVotesMapper @MockBean | L1 PASS |
| VP-8 변경/취소 시퀀스 | cast→update→delete→재투표 | L1 covered |
## 집합 전수 AC-T1~T4 실측 vs 기대
| AC | 기대 | 실측 | 판정 |
|---|---|---|---|
| AC-T1 핸들러 | @PostMapping 1 + @DeleteMapping 1 = 2 | line44 @PostMapping("/jams/{slug}/vote") + line97 @DeleteMapping("/jams/{slug}/vote") = 2 | PASS |
| AC-T1 CSRF | CsrfTokens.isValid == 2 | grep -c = 2 | PASS |
| AC-T1 게이트 | JamEvalWindow.isOpen == 2 | grep -c = 2 | PASS |
| AC-T2 매퍼 `${` | 0 | grep -c '\${' = 0 (controller 도 0) | PASS |
| AC-T3 게이트 2지점 | controller results + JSP | controller line172 `publiclyVisible="CLOSED".equals(status)\|\|evalEnded`; JSP line403 `resultsPublic`, line447 조건렌더, line454 "평가 종료 후 공개" + JS line520/583 2차 게이트 | PASS |
| AC-T4 DDL 변경 0 | jam_votes ALTER/CREATE 0, schema 파일 diff 0 | git status W2-5 surface: jam-detail.jsp·BibimbapApplicationTests 수정 + 신규 3 Java; docs/*ddl·schema.sql diff 0; jam_votes DDL 변경문 0 | PASS |
| alias 인용 | gameId/voteCount 전부 `AS "..."`, 비인용 0 | line62 `SELECT game_id AS "gameId", COUNT(*) AS "voteCount"`; 비인용 매치 0 | PASS |
## L2 dev DB contract (★ §33 케이스폴딩) — 실측 인용
**비파괴 절차 준수**: 사용자 dev DB(`bibimbap`) 미접촉. throwaway DB `bibimbap_verify_w2_5_<pid>` 생성 → jam_votes(W2-3 동결 스키마 미러) 적용 → 실측 → `DROP DATABASE` 완료.
- **L2-1 UNIQUE(jam_id,voter_user_id)**: 동일 (1,100) 2차 INSERT →
`ERROR: duplicate key value violates unique constraint "ux_jam_votes_jam_voter" / Key (jam_id, voter_user_id)=(1, 100) already exists.`
토글 UPDATE → `1 rows, game_id=11` (중복행 0, 1인1표 강제). **PASS**
- **L2-2 alias 케이스폴딩(§33)**: 구현 SQL(인용) 헤더 라벨 `gameId|voteCount` (camelCase 보존). 비인용 대조군 헤더 `gameid|votecount` (소문자 폴딩 재현). GROUP BY 집계 정합(game10=2, game11=2). 인용 alias 가 `row.get("voteCount")` null 결함을 회피함을 실측 확인. **PASS**
## 로그 스캔
clean — surefire-reports 14파일 FAIL/ERROR/Exception/Caused by 0건. 잔여 출력은 Mockito self-attach + JDK dynamic agent 경고(기존 환경 noise, W2-5 무관).
## L3 런타임 인가 — needs_user_verification (수동)
인증/인가 플로우(L1+L2+L3 의무). 컨트롤러/매퍼 단위 + DB 계약은 자동 검증 완료. 다음 런타임 시나리오는 사용자 수동 확인 권장:
1. 실제 세션 로그인 상태에서 EVAL 기간 잼 상세(jam-detail) 투표 → 토글 → 취소 UI 왕복.
2. 평가기간 종료(CLOSED 또는 eval_end 경과) 후 결과 영역 출품작별 count 실제 렌더, 진행 중에는 "평가 종료 후 공개" 안내 노출.
3. 미로그인/CSRF 누락 시 401/403 실제 응답.
(자동 L1+L2 가 코드·DB 계약을 커버하므로 L3 는 회귀 위험 낮음 — 사용자 스모크로 충분.)
## 종합 판정 (W2-5)
**overall: PASS** (L1)
- L1 171/171 GREEN, 실패 클래스 0.
- 집합 전수 AC-T1~T4 + alias 인용 전부 PASS (1건도 어긋남 없음).
- L2 dev DB contract 2종(UNIQUE / §33 케이스폴딩) 실측 PASS.
- 로그 clean.
rollback_signal: none (실패 없음 — 해당 없음)
## concerns
- count_mismatch: 신규 JamVoteControllerTest 실측 23 vs 작업지시·구현보고 기대 24 (-1). 전부 PASS 이므로 기능 결함 아님. 구현보고 "24테스트" 표기와 surefire 실측 23 간 1건 차이 — orchestrator 가 의도된 통합(VP 합본)인지 누락인지 확인 권장.
- dev_db_not_migrated: 실행 중 dev DB `bibimbap` 에 jam_votes 테이블 부재(`relation "jam_votes" does not exist`). W2-3 동결 DDL(`docs/jam-eval-ddl.sql`)이 dev DB 에 미적용 상태. 본 L2 는 throwaway DB 로 무관하게 통과했으나, 실제 dev 런타임(L3) 전에 마이그레이션 적용 필요할 수 있음 — orchestrator/사용자 확인 권장.
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| 1인1표 토글/UNIQUE | L2-1 + JamVoteControllerTest | PASS |
| 평가기간 게이트 422 | AC-T1 게이트 2 + 단위테스트 | PASS |
| CSRF 누락 403 | AC-T1 CSRF 2 + 단위테스트 | PASS |
| 미로그인 401 / results 공개 | VP-3 단위테스트 | PASS |
| 종료후 공개 2지점(밴드왜건 회피) | AC-T3 controller+JSP | PASS |
| 매퍼 `${` 금지 | AC-T2 | PASS |
| alias 케이스폴딩 안전(camelCase) | alias 인용 + L2-2 §33 | PASS |
| jam_votes 동결 소비(DDL 0) | AC-T4 | PASS |
---
# W2-6 (시상 집계 — W2 종점) 검증 결과
generated_at: 2026-06-24T12:40:00+09:00 / agent: verification-advisor (v1)
## Acceptance Criteria (입력 받은 그대로 인용)
- **멱등 recompute**: computeAwards 2회 실행 → jam_awards 중복 0, 결과 동일(deleteByJamTrack→재insert).
- **3트랙 개별수상**: JUDGE(weightedTotal NULL 제외) / USER_RATING(review_count>=3 임계·NULLS LAST) / POPULAR(득표>0). 미달/NULL 트랙 제외.
- **가중 GRAND**: 가용 트랙만 가중 정규화.
- **CLOSED 게이트**: status!=CLOSED 시 산정 422. 산정/확정 쓰기 CSRF→403 + 권한게이트.
- **공개 결과**: JamAwardController 결과 노출.
- 집합 전수 AC-T1~T6 (impl echo, grep 그대로 1 어긋나면 FAIL).
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| L1 full-test (§30) | `JAVA_HOME=…21 ./mvnw -o test` | 0 | blocker | pass |
| 집합 전수 AC-T1~T6 (grep) | grep -c on scope | 0 | blocker | pass |
| L2 dev DB contract (§33) | throwaway DB(w2_6_l2_throwaway) 적용·실측·DROP | 0 | blocker | pass |
| 단계 | 결과 |
|---|---|
| L1 typecheck/compile | pass (BUILD SUCCESS) |
| L1 unit+regression | pass (190/190, 0 fail, 0 error, 0 skip) |
| L2 contract-devDB (멱등/3트랙/alias폴딩/GRAND) | pass (4/4 항목) |
| 로그 스캔 | clean (WARNING 0 / ERROR 0 / Exception 0) |
## L1 (full test) 상세
- **BUILD SUCCESS**, contextLoads PASS (BibimbapApplicationTests 1 test green — 신규 2매퍼 @MockBean 등록 확인).
- 총계: **Tests run: 190, Failures: 0, Errors: 0, Skipped: 0** (= 직전 171 + 신규 19, 기대 일치).
- 신규 테스트 전수 GREEN:
- JamAwardServiceTest: **7/7**
- JamAwardAdminControllerTest: **7/7**
- JamAwardControllerTest: **5/5**
- **회귀 0 (JamControllerTest)**: JamController 6arg 생성자 변경 후 **14/14 GREEN** 유지 (NoSuchBean/생성자 회귀 없음).
- 실패 클래스: **없음**.
## 집합 전수 AC-T1~T6 실측 vs 기대
| AC | 항목 | 기대 | 실측 | 판정 |
|---|---|---|---|---|
| AC-T1 | 신규 매퍼 MyBatis `${param}` 동적치환 | 0 | JamAwardsMapper 0 / JamReviewRatingMapper 0 (`grep -c '\${'`=1은 Javadoc 주석 `${}` 0 문구 — 실제 `${...}` 패턴 0) | PASS |
| AC-T2 | 집계 매퍼 camelCase alias 비인용 | 0 | JamReviewRatingMapper 0 / JamVotesMapper 0 / JamScoreStatsMapper 0 (인용 `AS "..."` = JRRM 4·JVM 2) | PASS |
| AC-T3 | 멱등 메커니즘(deleteByJamTrack + @Transactional) | 존재 | JamAwardsMapper.deleteByJamTrack(jamId,track) 선언 / JamAwardService.recompute @Transactional(L65) + deleteByJamTrack 호출(L68, 4트랙) | PASS |
| AC-T4 | CLOSED 게이트 + CSRF + 권한게이트(admin) | 전수 | CLOSED/evalEnded 체크→422 UNPROCESSABLE(L56-58) · CsrfTokens.isValid→403(L47-48) · PermissionGate.has(GAME_JAM_MANAGE)(L72-75) | PASS |
| AC-T5 | BibimbapApplicationTests 신규 2매퍼 @MockBean | 2 | @MockBean JamAwardsMapper(L103-104) + @MockBean JamReviewRatingMapper(L106-107) | PASS |
| AC-T6 | 재사용 매퍼 JamVotesMapper.listCountsByJam (camelCase 인용) | 존재·인용 | listCountsByJam(L68) · `game_id AS "gameId", COUNT(*) AS "voteCount"`(L62) 인용 | PASS |
**집합 전수: 6/6 PASS (어긋남 0).**
## 시나리오 VP 커버 (L1 단위테스트 + L2 손계산)
| VP | 커버 |
|---|---|
| 멱등 recompute | L1 JamAwardServiceTest + **L2-1 DELETE→INSERT x2 hash 동일** |
| 3트랙 개별수상(NULL/임계/0표 제외) | L1 service test + **L2-2 손계산 일치** |
| 가중 GRAND(가용 트랙만) | L1 service test + **L2-4 정규화 원칙 확인** |
| CLOSED 게이트 422 + CSRF 403 + 권한게이트 | L1 JamAwardAdminControllerTest 7 + AC-T4 핸들러 전수 |
| 공개 결과 노출 | L1 JamAwardControllerTest 5 + JamAwardController `/jams/{slug}/results`→`jam-results` |
## L2 (dev DB contract — pass, 항목별)
bibimbap-db (postgres:16, :5433) 가용. 사용자 dev DB **비파괴**: throwaway DB `w2_6_l2_throwaway` 생성 → jam-eval-ddl 검증 구조 + game_review_stats 적용 → 실측 → **DROP 완료(count=0 확인)**. dev `bibimbap` DB 무변경(jam_awards 테이블 부재 = 미접촉 확인).
1. **★멱등 recompute (L2-1)** — PASS. 샘플 점수/평점/표 삽입 후 트랙별 DELETE→INSERT 2회.
- pass1 count=6, pass2 count=6 (pass2 DELETE 가 직전 행 정확히 제거 후 재INSERT).
- md5 스냅샷 동일: `24d919fba852848bf626cbd0dec05fe3` (값·rank·score 불변).
- ux_jam_awards_jam_track_game UNIQUE 하 **중복 행 0**.
2. **3트랙 집계 + 임계/NULL (L2-2)** — PASS. 손계산 일치:
- JUDGE: g1 weighted=4.000(judges=2), g2 weighted=2.000(scored_criteria=1), **g3 미채점→jam_score_stats 부재(NULL 제외)**.
- USER_RATING: g1(4.5,4)·g4(3.0,3) INCLUDED, **g2(count 2<3) EXCLUDED**, **g3(리뷰 0, NULL) NULLS LAST 정렬 후순위**.
- POPULAR: g1=3·g2=1 (>0), **g3/g4 0표 제외**.
3. **alias 케이스폴딩 §33 (L2-3)** — PASS. 매퍼 SELECT verbatim 실행 → row_to_json 반환 키 **camelCase 보존**: `gameId/avgRating/reviewCount`(JamReviewRatingMapper), `gameId/voteCount`(JamVotesMapper). 큰따옴표 alias 가 소문자 폴딩 방어. **game_review_stats fan-out 0** (review_count == raw rows: g1 4=4, g2 2=2, g4 3=3 MATCH).
4. **GRAND 가중 (L2-4)** — PASS(원칙). 가용 트랙만 정규화·결합: g1(3트랙 top)=최상위, g2(2트랙), g4(1트랙). 미존재 트랙 제외 동작 확인.
- 주의: 정확한 GRAND 가중 계수는 RankScores/JamAwardService 내부(역할상 미열람) — DB-계약 수준의 "가용 트랙만 정규화·결합" 원칙만 검증. L1 JamAwardServiceTest 가 계수 단위검증 담당.
## 로그 스캔
- clean. `[WARNING]` 0건, `[ERROR]` 0건, `Exception/Caused by/FAILED` 0건.
## L3 런타임 인가 (needs_user_verification — 수동)
다음은 본 advisor 범위 밖 수동 확인 단계:
- 산정/확정 API 의 실제 세션 권한(GAME_JAM_MANAGE) 미보유 사용자 403 런타임 확인.
- CLOSED 미달 상태 잼 산정 호출 시 422 런타임 확인.
- 공개 결과 페이지(`/jams/{slug}/results`)가 CLOSED 종료 후에만 노출되는 밴드왜건 회피 런타임 확인.
- dev DB `bibimbap` 에 jam-eval-ddl 적용 후 실 스택 멱등 recompute 1회 스모크.
## 종합 판정
**overall: PASS**
- verdict (L1): **PASS** (190/190, 회귀 0, JamControllerTest 14/14 GREEN).
- 집합 전수 AC-T1~T6: **6/6 PASS**.
- L2 dev DB contract: **PASS (4/4 항목)**.
- 로그 스캔: **clean**.
rollback_signal: none (FAIL 아님)
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| 멱등 recompute(중복 0·결과 동일) | L2-1 + JamAwardServiceTest + AC-T3 | PASS |
| JUDGE NULL 제외 | L2-2 + service test | PASS |
| USER_RATING review_count>=3·NULLS LAST | L2-2 + service test | PASS |
| POPULAR 득표>0 | L2-2 + service test | PASS |
| GRAND 가용 트랙만 가중 | L2-4(원칙) + service test(계수) | PASS |
| CLOSED 게이트 422 | AC-T4 + JamAwardAdminControllerTest | PASS |
| 산정/확정 CSRF 403 + 권한게이트 | AC-T4 + JamAwardAdminControllerTest | PASS |
| 공개 결과 노출 | JamAwardControllerTest 5 + AC | PASS |
| 신규 매퍼 `${` 금지 | AC-T1 | PASS |
| alias 케이스폴딩 안전(camelCase) | AC-T2 + L2-3 §33 | PASS |
| 신규 2매퍼 @MockBean(contextLoads) | AC-T5 + L1 BibimbapApplicationTests | PASS |
| 재사용 매퍼(JamVotesMapper) 소비 | AC-T6 | PASS |