60 KiB
| phase | agent | agent_version | generated_at | concerns | concerns_checked | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| verification | verification-advisor | 1 | 2026-06-24T12:40:00+09:00 |
|
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
- 신규 테스트 전수 통과:
JamControllerTestTests run: 14, F0 E0 S0JamAdminControllerTestTests run: 10, F0 E0 S0JamLifecycleTestTests run: 8, F0 E0 S0BibimbapApplicationTestsTests 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 인가는 자동 미수행(앱 미기동):
- GAME_JAM_MANAGE 무권한 SUBADMIN 세션으로 POST /admin/jams/* → 403 기대
- 미인증 GET /admin/jams → /login redirect 기대
- 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 는 자동 미수행(앱 미기동) — 사용자 수동 단계:
- SUBADMIN(GAME_JAM_MANAGE 무권한) 세션으로 POST /admin/jams/{id}/judges → 403 기대.
- 미인증으로 GET /admin/jams/{id}/judges → 401 또는 /login redirect 기대.
- ADMIN 세션 지정 → jam_judges 행 적재, 동일 (jam,user) 재지정 → 409 기대.
- 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 ANDCREATE.*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 testBUILD 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 무접촉 범위:
- WAR 기동 후 심사위원(isJudge) 로그인 → EVAL 상태·구간내 잼의 타인 출품작 채점 POST/PUT → 200 + jam_scores UPSERT 반영.
- 미심사위원/미인증/CSRF누락 실호출 시 각 403/401/403 + mapper 미호출.
- 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 계약은 자동 검증 완료. 다음 런타임 시나리오는 사용자 수동 확인 권장:
- 실제 세션 로그인 상태에서 EVAL 기간 잼 상세(jam-detail) 투표 → 토글 → 취소 UI 왕복.
- 평가기간 종료(CLOSED 또는 eval_end 경과) 후 결과 영역 출품작별 count 실제 렌더, 진행 중에는 "평가 종료 후 공개" 안내 노출.
- 미로그인/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 테이블 부재 = 미접촉 확인).
- ★멱등 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.
- 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표 제외.
- 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). - 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/FAILED0건.
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 |