--- 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' ` >= 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.*' 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_` 생성 → 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 |