chore(atp): W2 work-session 산출물 기록 (sid 20260624-100749)
W2 게임잼 워크스트림 6기능 구현 세션 — 구현/검증 보고·소유권맵·세션 report·회고. orchestrator+implementation/verification-advisor 협업, 6 feat 커밋(ccf1e42~a74bf74), 190/190 GREEN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ae6589b39
commit
ba1a5fe572
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
phase: documentation
|
||||
agent: documentation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T13:10:00+09:00
|
||||
concerns_checked: true
|
||||
concerns:
|
||||
- "docs/index.md 무수정: 카테고리 인덱스 + 빠른 참조만 있고 changelog/최신변경 포인터 슬롯이 없음 → W2 반영 불요(skip, 과잉수정 회피). CLAUDE.md '최신 프로젝트 분석' 포인터는 docs/analysis/2026-06-16 고정 메타라 W2 changes 와 무관."
|
||||
- "work-log frontmatter status:active 유지(변경 안 함): W3 잔여·W4 가 미구현이라 설계 인덱스 문서는 여전히 active 가 정확. 본문 '구현 추적' 섹션에 W2 완료 한 줄 + changes 교차링크만 추가(과잉수정 회피)."
|
||||
- "테스트 카운트 미세 차이는 검증서 concerns 그대로 changes '미적용/이월'에 무해 표기로 이관(W2-2 14 vs 예측 16, W2-5 23 vs 예측 24 — 전부 GREEN·회귀 0). 사실 곡해 없이 검증서 인용."
|
||||
- "dev DB jam_* 미적용(throwaway DB 로만 L2 통과)·L3 E2E 미수행은 needs_user_verification 로 명시. maintenance/ 마이그레이션 절차 문서는 미생성 — orchestrator/사용자가 db/apply-local-ddl.sh 실행 시점 결정. 추후 운영 마이그레이션 가이드 필요 시 maintenance/ 후보."
|
||||
- "design-advisor 와의 충돌 없음: 이번 W2 는 신규 아키텍처 문서(architecture/) 갱신 대상 아님(잼 도메인은 기능 구현 이력 = changes/ 1순위). architecture/ 미접촉."
|
||||
- "backport 해당 없음(N/A): 소비 프로젝트 dogfooding 역이식 아님. self-exclusion grep 불요."
|
||||
---
|
||||
|
||||
# 문서화 보고
|
||||
|
||||
W2 게임잼 워크스트림(W2-1~6) 구현 결과를 기록했다.
|
||||
|
||||
## 작성/수정된 문서
|
||||
| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
|
||||
|---|---|---|---|---|
|
||||
| docs/changes/2026-06-24-w2-jam-platform.md | changes | 신규 | docs/changes/index.md ✅ | 상위 설계요약(work-log 2026-06-23)·검증/구현/설계 work-session·보안 체크리스트 |
|
||||
| docs/changes/index.md | changes | 수정(링크 1줄) | — | — |
|
||||
| docs/work-log/2026-06-23-w2-w4-full-design-summary.md | work-log | 수정(구현 추적 섹션) | — | → changes/2026-06-24-w2-jam-platform.md (설계→구현 추적) |
|
||||
|
||||
## 카테고리 판별 근거
|
||||
- W2-1~6 = 실제 코드/DDL 이 바뀐 구현 변경 이력 → `document-category-classification.md` "빠른 결정 순서 #10" + "changes/ 를 써도 되는 경우"(코드 실제 수정·동작 변화·언제 어떤 구현 변화가 들어갔는지 조회) 충족. 선례 정합: changes/2026-06-18·2026-06-22 (W3-2). 날짜 prefix 2026-06-24.
|
||||
- ADR 미발행: W2-3 동결 단일권위는 이미 직전 changes(2026-06-22 §C3)에서 "W2-3 관련 설계 분기 재발 시 ADR 권고" 로 보류됨. 본 W2 구현은 그 설계를 코드화한 것이라 새 ADR 트리거 아님(되돌리기 어려운 신규 기술결정 0).
|
||||
- architecture/ 미접촉: 기능 구현 이력이지 시스템 경계/레이어 규칙 갱신 아님. design-advisor 충돌 없음.
|
||||
|
||||
## 의사결정 기록 위치
|
||||
- 변경 이력 정본: docs/changes/2026-06-24-w2-jam-platform.md (커밋해시·테이블·게이트·검증 190/190·보안 섹션·이월)
|
||||
- 설계 결정 근거(정석 확정값): work-log/2026-06-23-w2-w4-full-design-summary.md (크로스-W 결정 매트릭스) → 변경 이력에서 역링크
|
||||
- 검증 verdict 세부: .atp/work-session/20260624-100749/verification.md (W2-1 R2 ~ W2-6)
|
||||
|
||||
## 추후 문서화가 필요한 항목
|
||||
- dev DB jam_* 마이그레이션 운영 절차(db/apply-local-ddl.sh) — 실 적용 시 maintenance/ 후보.
|
||||
- W3(잔여)·W4 구현 착수 시 동일 패턴 changes/ 문서 + work-log '구현 추적' 갱신.
|
||||
- W2-3 동결 경계 관련 설계 분기 재발 시 adr/ADR-0001 발행(2026-06-22 §C3 권고 승계).
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T10:07:49+09:00
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 0
|
||||
planned_workers: 0
|
||||
actual_workers: 0
|
||||
fix_round: 1
|
||||
target_defect: L1-compile-FAIL (response(HttpStatus,String) 미정의)
|
||||
---
|
||||
|
||||
# W2-1 보정 보고 (fix round 1)
|
||||
|
||||
## 결함 진단 정정
|
||||
|
||||
verification-advisor 보고는 24 errors(JamAdminController) + 20회 미평가(JamController)로
|
||||
양쪽 결함을 추정했으나, 줄 단위 실측 결과 **결함은 JamAdminController 단일 파일에 한정**된다.
|
||||
|
||||
- **JamAdminController.java** — `response(...)` 호출 24건 존재, 헬퍼 정의 **0건** → 24 errors 의 진원지. (확정 결함)
|
||||
- **JamController.java** — `response(...)` 호출 19건, 헬퍼 정의가 **이미 270-275행에 존재**. javac 가 JamAdminController 에서 먼저 중단되어 "미평가"로 보였을 뿐, 실제로는 컴파일 가능. (결함 아님 — 무수정)
|
||||
|
||||
→ 원인: W2-1 구현 시 JamAdminController worker 가 RecruitController/JamController 의
|
||||
`response(HttpStatus,String)` 응답 패턴을 호출했으나 해당 컨트롤러에 헬퍼 정의를 빠뜨림.
|
||||
|
||||
## 수정 파일 목록
|
||||
|
||||
| 파일 | 변경 유형 | 변경 내용 |
|
||||
|---|---|---|
|
||||
| src/main/java/com/pandoli365/bibimbap/controller/JamAdminController.java | modify | private `response(HttpStatus,String)` 헬퍼 1개 추가 (363-368행) |
|
||||
|
||||
- JamController.java: **무수정** (헬퍼 기존 존재, 결함 없음)
|
||||
- 신규 util 파일: **미생성** (단일 메서드 복제로 충분, 공유 util 추출은 scope 밖 + 리팩토링이라 보류)
|
||||
- 그 외 W2-1 파일: 무수정
|
||||
|
||||
## response() 헬퍼 최종 시그니처 + 정의 위치
|
||||
|
||||
단일 시그니처. 오버로드 **불필요**.
|
||||
|
||||
```java
|
||||
private ResponseEntity<Map<String, Object>> response(HttpStatus status, String message) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("status", status.value());
|
||||
body.put("message", message);
|
||||
return ResponseEntity.status(status).body(body);
|
||||
}
|
||||
```
|
||||
|
||||
- 정의 위치: JamAdminController.java **363-368행** (`requireJamManage` 다음, `sessionUserId` 앞)
|
||||
- import 추가 불필요: `HttpStatus`(15), `ResponseEntity`(16), `LinkedHashMap`(27), `Map`(28) 모두 기존 import 됨.
|
||||
|
||||
## RecruitController 기존 패턴과의 정합 여부 → 복제 (신규정의 아님)
|
||||
|
||||
세 컨트롤러의 헬퍼 본문이 **바이트 단위로 동일**함을 확인:
|
||||
|
||||
| 컨트롤러 | 정의 위치 | 본문 |
|
||||
|---|---|---|
|
||||
| RecruitController | 186-190행 | status.value() + message → ResponseEntity.status(status).body(body) |
|
||||
| JamController | 270-275행 | (동일) |
|
||||
| JamAdminController(추가) | 363-368행 | (동일 — 복제) |
|
||||
|
||||
→ 정본 패턴(쓰기 응답 = `ResponseEntity<Map<String,Object>>{status,message}`)을 **그대로 복제**.
|
||||
W2-1 설계 외부계약(쓰기=ResponseEntity<Map>{status,message,...})과도 정합.
|
||||
|
||||
## 호출부 43건 전수 시그니처 정합 확인 결과
|
||||
|
||||
(verification 보고의 "44건"은 24+20 추정치. 실측 호출 = 24 + 19 = 43건.)
|
||||
|
||||
- JamAdminController: 호출 24건 — 92,96,100,104,108,121,124,158,195,200,204,208,212,216,229,232,270,275,279,282,312,341,355,358행
|
||||
- JamController: 호출 19건 — 116,120,123,127,130,138,144,158,180,184,187,191,220,224,228,231,234,239,241행
|
||||
- **43건 전부 `response(HttpStatus.*, "문자열리터럴")` 2인자 형태** — `rg "response\(" | rg -v "response\(HttpStatus\."` 결과 0건(정의 라인 제외).
|
||||
- 정의 시그니처 `(HttpStatus status, String message)` 와 전수 일치. 오버로드 필요 호출 0건.
|
||||
|
||||
### 추가 데이터를 반환하는 OK 응답은 헬퍼 미사용 (정상)
|
||||
jamId/slug/entryId/jamTeamId/visible 등을 함께 싣는 성공 응답은 모두 인라인
|
||||
`Map<String,Object>` + `ResponseEntity.ok(body)` 로 이미 작성됨 — 헬퍼 호출 아님.
|
||||
따라서 2인자 헬퍼 하나로 43건 전수 충족. 오버로드 불요 재확인.
|
||||
|
||||
## 보존 확인 (scope 엄수)
|
||||
|
||||
- 게이트 `requireJamManage` 6핸들러 정합: 무수정 (AC-T3 PASS 유지)
|
||||
- CSRF 검증 `CsrfTokens.isValid`: 무수정
|
||||
- 매퍼 호출(`#{}` 바인딩): 무수정
|
||||
- 핸들러 비즈니스 로직: 무수정
|
||||
- 변경 = 헬퍼 메서드 1개 추가뿐.
|
||||
|
||||
## 자가 검증 (정적, 실행 안 함)
|
||||
|
||||
1. 산출물 존재: `.atp/work-session/20260624-100749/implementation/W2-1-fix1.md` ✓
|
||||
2. frontmatter 필수 필드(phase/agent/agent_version/generated_at/concerns/concerns_checked) ✓
|
||||
3. concerns 검토 완료(빈 리스트 — 결함 단일·정합 전수 확인) ✓
|
||||
4. 컴파일 가능성: response 호출 43건 전수 정의와 시그니처 일치, 미정의 심볼 0. `git status --short` 로 수정 범위 = JamAdminController 단일 파일 확인.
|
||||
- **L1 컴파일 검증(`mvnw compile`) 실행은 verification-advisor 영역** — 미실행.
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
|
||||
- 재검증 게이트: L1 = `./mvnw -o compile` (BUILD SUCCESS 기대, 직전 24 errors → 0 기대)
|
||||
- 이번 변경 영향 테스트: JamAdminControllerTest.java (response 바디 status/message 키 검증 시 영향)
|
||||
- acceptance criteria: design.md "검증 포인트" 참조 (게이트/CSRF/매퍼는 직전 PASS 유지 기대)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T10:55:00+09:00
|
||||
workstream: W2-1-게임잼 엔티티/라이프사이클
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 21
|
||||
planned_workers: 21
|
||||
actual_workers: 21
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
unused_diagnostics: 0 # 정적 unused-import 스캔 0건 + 각 worker 자가확인. 통합 LSP/린터 실측은 verification 영역.
|
||||
references:
|
||||
design: .atp/work-session/20260623-104307/implementation/W2-1-jam-entity-design.md
|
||||
ownership: .atp/work-session/20260624-100749/implementation/ownership.md
|
||||
---
|
||||
|
||||
# 구현 보고 — W2-1 게임잼 엔티티/라이프사이클
|
||||
|
||||
설계 정본을 그대로 구현. 임의 변경 0(아래 "설계와의 차이"의 2건은 설계 계약을 깨지 않는 구현 디테일 명시).
|
||||
|
||||
## 변경 목록 (설계 파일영향맵 = 19파일, 신규 16 + 수정 3)
|
||||
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| docs/jam-ddl.sql | migration-writer w-001 | 신규. CREATE TABLE 5건, SEQ 5, ADD CONSTRAINT 14, INDEX 8. 멱등(IF NOT EXISTS/DO $$ guard) |
|
||||
| db/schema.sql | migration-writer w-001 | 수정. 371행~ 잼 블록 append. docs/jam-ddl.sql 와 SEQ/CONSTRAINT/INDEX 갯수 동일(동기 사본). 기존 1~368행 무변경 |
|
||||
| data/JamData.java | code-writer w-002 | 신규. jams 18필드 POJO(getIsVisible/getIsDelete) |
|
||||
| data/JamEntryData.java | code-writer w-003 | 신규. jam_entries 8필드 + JOIN 표시 3필드(gameName/thumbnailUrl/entrantName) |
|
||||
| data/JamTeamData.java | code-writer w-004 | 신규. jam_teams 6필드 + memberCount |
|
||||
| jam/JamStatus.java | code-writer w-005 | 신규. enum 4멤버 + isValid/from(unknown→null) |
|
||||
| jam/JamLifecycle.java | code-writer w-006 | 신규. @Component isAllowed(전이그래프) + isPeriodReady(좁은 3인자 + JamData 오버로드) |
|
||||
| jam/JamSlugs.java | code-writer w-006 | 신규. generate(정규화/한글보존/72절단) + withSuffix(충돌재시도) |
|
||||
| mapper/JamsMapper.java | code-writer w-007 | 신규. 9메서드. listVisibleKeyset 은 <script>+row-comparison keyset. ${} 0 |
|
||||
| mapper/JamEntriesMapper.java | code-writer w-008 | 신규. insert/listByJam(JOIN games+COALESCE entrantName)/exists. ${} 0 |
|
||||
| mapper/JamTeamsMapper.java | code-writer w-009 | 신규. insert/getById/listByJam(memberCount 서브쿼리). ${} 0 |
|
||||
| mapper/JamTeamMembersMapper.java | code-writer w-010 | 신규. insert/exists. ${} 0 |
|
||||
| mapper/JamStatusLogMapper.java | code-writer w-011 | 신규. insert(5파라미터 @Param). ${} 0 |
|
||||
| controller/JamAdminController.java | code-writer w-012 | 신규. 6핸들러 전수 requireJamManage(=6) + 5쓰기 CSRF. slug 충돌 재시도 |
|
||||
| config/InterceptorConfig.java | code-writer w-013 | 수정. .excludePathPatterns("/admin/jams/**") 추가(D4-A) |
|
||||
| webapp/.../admin-jam-list.jsp | code-writer w-014 | 신규. 생성폼 + status/visibility/delete 액션. meta _csrf + htmlEscape |
|
||||
| controller/JamController.java | code-writer w-015 | 신규. list(keyset)/detail/submitEntry/submitTeam/addMember. 쓰기 3 CSRF. resolveEntrant 인라인 |
|
||||
| webapp/.../jam-list.jsp | code-writer w-016 | 신규. keyset 더보기(URLEncoder) + htmlEscape |
|
||||
| webapp/.../jam-detail.jsp | code-writer w-017 | 신규. 출품/팀생성 폼(_csrf hidden) + htmlEscape, innerHTML 미사용 |
|
||||
| test/.../BibimbapApplicationTests.java | code-writer w-018 | 수정. 신규 5매퍼 @MockBean 등록(기존 12빈 보존) |
|
||||
| test/.../JamLifecycleTest.java | code-writer w-019 | 신규. 전이그래프 허용/거부/동일/null + 기간정합 8메서드 |
|
||||
| test/.../JamAdminControllerTest.java | code-writer w-020 | 신규. 게이트 401/403/redirect + CSRF + 전이감사 10메서드(순수 Mockito) |
|
||||
| test/.../JamControllerTest.java | code-writer w-021 | 신규. keyset/출품 소유·멤버·중복·상태 + CSRF 14메서드 |
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- 세션 디렉토리 생성: mkdir .atp/work-session/20260624-100749/implementation → OK
|
||||
- 집합 전수 AC 정적 점검(테스트 실행 아님): AC-T1 테이블5/5 · AC-T2 상태4값(enum4=jams_check4=log_check4) · AC-T3 핸들러6=requireJamManage6 · AC-T4 매퍼5 ${} 0 · AC-T5 XOR+type CHECK 존재 · AC-T6 @MockBean Jam 5 → 전부 PASS
|
||||
- DDL 동기 비교: docs/jam-ddl.sql ↔ schema.sql 잼블록 SEQ 5/5, ADD CONSTRAINT 14/14, INDEX 8/8 일치
|
||||
- unused-import 정적 스캔 16 Java 파일 → 0건
|
||||
- git status: 설계 파일영향맵과 정확히 일치(수정3+신규16), 범위 밖 변경 0
|
||||
|
||||
## 설계와의 차이 (계약 미파괴 — 구현 디테일 명시)
|
||||
1. **상태전이 응답 키 `status`→`jamStatus`**: 설계 API 계약 표는 상태전이 200 응답을 `{status:toStatus}`로 적었으나, 동일 응답 Map 에 공통 `status`=HTTP 200(value) 와 잼 상태를 같은 키로 넣으면 후자가 HTTP value 를 덮어쓴다. w-012 가 데이터 손실 방지 위해 잼 상태를 `jamStatus` 키로 분리. **권장 정합**: verification 의 JamAdminControllerTest 가 `jamStatus`=전이상태 + `status`=200 을 검증하도록 작성됨(일관). 클라(admin-jam-list.jsp)는 reload 방식이라 응답 키 의존 없음. 설계 의도(상태 반환)는 충족, 키 이름만 충돌 회피.
|
||||
2. **JamLifecycle.assertPeriodReady → isPeriodReady 좁힘(concern 1 반영)**: 설계 시그니처는 `assertPeriodReady(JamData jam, JamStatus to)`였으나 concern 1(dead parameter 방지)에 따라 ① 좁은 `isPeriodReady(JamStatus to, OffsetDateTime evalStartAt, OffsetDateTime evalEndAt)`(EVAL→start, CLOSED→end 두 인자 실사용) + ② 컨트롤러 편의용 `isPeriodReady(JamStatus to, JamData jam)` 오버로드(jam 의 두 getter 위임 실사용)로 구현. 예외 throw 대신 boolean(컨트롤러 ResponseEntity 패턴 정합). dead parameter 0.
|
||||
|
||||
> 위 2건 외 DDL/엔티티/매퍼 SQL/API 경로/시퀀스/keyset/게이트 enforcement 는 설계 정본 그대로.
|
||||
|
||||
## concerns 처리 결과 (설계 6개 concern)
|
||||
- **concern 1 (시그니처 inflate)**: 해소. JamLifecycle.isPeriodReady 좁힘(위 #2). JamController.resolveEntrant 는 별도 헬퍼 추출 안 하고 submitEntry 인라인 유지(grep resolveEntrant private 0 — dead parameter 회피).
|
||||
- **concern 2 (신규 컨트롤러/매퍼 의존 + @MockBean)**: 해소. BibimbapApplicationTests 에 신규 5매퍼 @MockBean 등록(grep Jam @MockBean = 5). contextLoads 보존. **단, full ./mvnw -o test 실측은 verification 영역(미실행)**.
|
||||
- **concern 3 (DB-방언 L2: snake→camel alias / keyset)**: 매퍼 5개 모두 직접 alias(큰따옴표 0), listVisibleKeyset 은 PostgreSQL row-comparison `(created_at,id) < (...)` + <script> NULL 분기. **dev DB contract 실측은 verification 영역**.
|
||||
- **concern 4 (games↔jam 활성 자연키)**: jam_entries 의 ux_jam_entries_jam_game_active(활성 UNIQUE) DDL 그대로. games 무변경 확인(git status 에 games 관련 변경 0).
|
||||
- **concern 5 (자동전이 스케줄러)**: 본 구현은 수동 전이 enforcement 만. @Scheduled 빈 미도입 → BibimbapApplicationTests context 영향 0(신규 빈은 매퍼 5 + JamLifecycle/PermissionGate 기존). 범위 준수.
|
||||
- **concern 6 (slug 충돌 재시도)**: 해소. JamSlugs.generate + withSuffix, JamAdminController.create 가 DuplicateKeyException catch 시 withSuffix(base,n) 재시도(최대 ~20).
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
- acceptance criteria: design.md §검증 포인트(VP-1~7) + §집합 전수 AC(AC-T1~T6) 참조.
|
||||
- 영향받는 테스트 파일: JamLifecycleTest(VP-2), JamAdminControllerTest(VP-1/2/5), JamControllerTest(VP-3/4/5), BibimbapApplicationTests(VP-7 contextLoads).
|
||||
- **반드시 full `./mvnw -o test`** (verification-strategies §30): 신규 컨트롤러/매퍼 의존 → test-compile 만으로 부족. contextLoads + 신규 테스트 컴파일·실행 판정.
|
||||
- **dev DB contract(L2)** 권장 실측: listVisibleKeyset row-comparison, jam_entries XOR/type CHECK 위반 INSERT 거부, snake→camel alias 키 일치(VP-6).
|
||||
- 미실측 리스크: ① keyset row-comparison SQL 의 PostgreSQL 실행 정합(매퍼 코드는 명세대로) ② JSP 의 게임상세 링크 경로 `/game/{gameId}`(게임 허브 실제 라우트와 대조 — gameId null 시 폴백 처리됨, 깨짐 없음) ③ datetime-local 입력의 OffsetDateTime 파싱(컨트롤러 parseOffset try/catch→422 처리).
|
||||
|
||||
## 미해결/리스크
|
||||
- 없음(설계 범위 내 전부 구현). 위 "Verification 힌트"의 미실측 3건은 verification-advisor 가 full test + dev DB contract 로 판정.
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T00:00:00+09:00
|
||||
fix: W2-2-fix1 (누락 테스트 2건 보완)
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 0
|
||||
planned_workers: 2
|
||||
actual_workers: 0
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
unused_diagnostics: 0
|
||||
test_compile: pass
|
||||
---
|
||||
|
||||
# 구현 보고 — W2-2-fix1 (누락 신규 테스트 2건 작성)
|
||||
|
||||
## 누락 진단 재확인
|
||||
W2-2 설계 파일영향맵 L275-276 의 신규 테스트 2건이 직전 구현에서 누락됨.
|
||||
원인: 소유 태그 `(검증)` 을 verification-advisor 작성으로 오독. 테스트 작성은 구현 산출물(verification 은 실행만, Write 없음). 본 fix 가 이 2 파일만 작성.
|
||||
|
||||
## 변경 목록
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| `src/test/java/com/pandoli365/bibimbap/security/JamRoleGateTest.java` | advisor 직접 | 신규. isJudge 4건 + isOwnEntry 4건 = 8 테스트 |
|
||||
| `src/test/java/com/pandoli365/bibimbap/controller/JamJudgeAdminControllerTest.java` | advisor 직접 | 신규. 지정/해제/조회 게이트·CSRF·404·409·성공 = 16 테스트 |
|
||||
|
||||
## 테스트 케이스 ↔ VP 매핑
|
||||
|
||||
### JamRoleGateTest (VP-2, VP-3)
|
||||
- `isJudgeReturnsTrueForAssignedUser` — VP-2 지정 유저 true
|
||||
- `isJudgeReturnsFalseForUnassignedUser` — VP-2 미지정 false
|
||||
- `isJudgeReturnsFalseWhenUnauthenticated` — VP-2 미인증 false (mapper 미호출 검증)
|
||||
- `isJudgeReturnsFalseForNullSession` — VP-2 보강(null 세션 방어, mapper 미호출)
|
||||
- `isOwnEntryTrueForPersonalEntry` — VP-3 ① 개인출품 true
|
||||
- `isOwnEntryTrueForTeamMemberEntry` — VP-3 ② 팀멤버출품 true
|
||||
- `isOwnEntryFalseForOthersEntry` — VP-3 ③ 타인/타팀 false
|
||||
- `isOwnEntryFalseForInactiveEntry` — VP-3 ④ 비활성(is_delete) false
|
||||
|
||||
### JamJudgeAdminControllerTest (VP-1, VP-4, VP-5, VP-6)
|
||||
- `assignReturns401WhenUnauthenticated` — VP-1 미인증 401
|
||||
- `assignReturns403WhenLacksPermission` — VP-1 무키 403
|
||||
- `assignRejectsMissingCsrfBeforeMapperAccess` — VP-5 지정 CSRF 누락 403 + mapper 미호출
|
||||
- `assignReturns404WhenJamMissing` — 404 잼 없음
|
||||
- `assignReturns404WhenTargetUserMissing` — 404 대상 유저 없음
|
||||
- `assignReturns409WhenAlreadyJudge` — VP-6 멱등 재지정 409 (insert 미호출)
|
||||
- `assignInsertsJudgeWhenAuthorized` — VP-1/VP-4 통과·누구나 지정 성공
|
||||
- `removeReturns403WhenLacksPermission` — VP-1 해제 무키 403
|
||||
- `removeRejectsMissingCsrfBeforeMapperAccess` — VP-5 해제 CSRF 누락 403 + mapper 미호출
|
||||
- `removeReturns404WhenNotAssigned` — 해제 404(지정 안 됨)
|
||||
- `removeDeletesJudgeWhenAuthorized` — 해제 성공 removed:true
|
||||
- `listReturns401WhenUnauthenticated` — VP-1 조회 미인증 401
|
||||
- `listReturns403WhenLacksPermission` — VP-1 조회 무키 403
|
||||
- `listReturnsJudgesWhenAuthorized` — 조회 성공(judges 목록)
|
||||
|
||||
> 게이트 통과의 ADMIN / SUBADMIN+키 구분: 컨트롤러는 PermissionGate 만 소비하므로(role 직접체크 없음, J3-A) `gate.isAuthenticated` + `gate.has(GAME_JAM_MANAGE)` 반환값으로 표현(JamAdminControllerTest 동형). role 분기 L3 스모크는 verification 영역.
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `./mvnw -o -q test-compile` (JAVA_HOME=openjdk@21) → EXIT=0, 에러 0. 두 신규 테스트 컴파일 성공.
|
||||
- `-Dmaven.compiler.showWarnings=true` 재컴파일 → 경고 0건(unused import/변수 없음).
|
||||
- 테스트 **실행은 하지 않음**(verification 영역). test-compile 은 빌드/타입체크라 advisor 영역.
|
||||
|
||||
## 시그니처 정합 자가확인 (정적 전수)
|
||||
| 소비 대상 | 실제 시그니처(실측) | 테스트 정합 |
|
||||
|---|---|---|
|
||||
| `JamRoleGate(JamJudgesMapper, JamEntriesMapper)` 생성자 | 확인 | ✓ |
|
||||
| `JamRoleGate.isJudge(HttpSession, long)` → boolean | 세션 userId attr `"userId"`, null→false | ✓ |
|
||||
| `JamRoleGate.isOwnEntry(long, long, long)` → boolean | jamEntriesMapper 위임 | ✓ |
|
||||
| `JamJudgesMapper.exists/insert/delete/listByJam` | exists(long,long)→bool, insert(long,long,long)→int, delete(long,long)→int, listByJam(long)→List | ✓ |
|
||||
| `JamEntriesMapper.isOwnEntry(long,long,long)` → boolean | @Param 3종 | ✓ |
|
||||
| `JamJudgeAdminController(JamsMapper, UsersMapper, JamJudgesMapper, PermissionGate)` | 확인 | ✓ |
|
||||
| 핸들러 `assign(long,long,HttpServletRequest,HttpSession)` / `remove(long,long,req,session)` / `list(long,session)` | ResponseEntity<Map<String,Object>> 반환 | ✓ |
|
||||
| `PermissionGate.isAuthenticated/has` | isAuthenticated(session), has(session,String) | ✓ |
|
||||
| `UsersMapper.getUser(long)` → UserData | 확인 | ✓ |
|
||||
| `JamsMapper.getById(long)` → JamData | 확인 | ✓ |
|
||||
| `CsrfTokens.HEADER_NAME / SESSION_ATTRIBUTE / errorBody()` | JamAdminControllerTest 동형 사용 | ✓ |
|
||||
| POJO setter: JamData.setId/setTitle/setStatus, UserData.setId, JamJudgeData.setUserId/setDisplayName/setAssignedBy | 전수 존재 | ✓ |
|
||||
| PermissionKeys.GAME_JAM_MANAGE | enum 상수 존재 | ✓ |
|
||||
|
||||
전례 보강: W2-1 1차의 `response()` 누락 컴파일 FAIL 재발 방지 — 컨트롤러 ResponseEntity 반환·헬퍼 흐름을 실측 후 테스트가 동일 흐름 가정. test-compile EXIT=0 으로 정합 입증.
|
||||
|
||||
### MockitoExtension STRICT_STUBS 점검
|
||||
- JamJudgeAdminControllerTest: `@ExtendWith(MockitoExtension.class)` → strict stubbing. 각 테스트의 stub 전수가 핸들러 흐름(requireJamManage→CSRF→getById→getUser→exists→insert / requireJamManage→CSRF→delete / requireJamManage→listByJam)에서 모두 소비되도록 over-stub 0 으로 구성. UnnecessaryStubbingException 위험 0.
|
||||
- JamRoleGateTest: plain `mock()`(MockitoExtension 미사용) → strict stub 비적용, unused stub 무관.
|
||||
|
||||
## 설계와의 차이 (worker 계획 vs 실제)
|
||||
- **전환 사유**: 파일 수 2 < 8 AND 예상 줄수 합산 약 410 < 500 → advisor 직접 실행 선택(병렬 worker 이득 미미). 더 중요한 근거: 두 테스트의 컴파일 정합이 핵심 리스크(W2-1 1차 FAIL 전례)라 시그니처 전수 정합을 advisor 가 직접 통제하는 편이 안전.
|
||||
- `planned_workers: 2`(code-writer 1파일 1worker 원칙상 계획) → `actual_workers: 0`.
|
||||
- 선택 파일: JamRoleGateTest.java, JamJudgeAdminControllerTest.java.
|
||||
|
||||
## BibimbapApplicationTests @MockBean 현황(VP-8/AC-T6 — 본 fix 무수정 확인)
|
||||
- `JamJudgesMapper`(L74), `JamRoleGate`(L89), `JamEntriesMapper`(L71) 전수 이미 @MockBean 등록됨(직전 W2-2 구현이 등록). contextLoads 보존 — 본 fix 가 추가 등록 불요.
|
||||
|
||||
## concerns 검토
|
||||
빈 리스트. 본 fix 는 테스트 2건 작성만이며 설계 VP-1~6 을 그대로 실현. main 코드/스키마/기존 테스트 무수정(제약 준수). 설계와의 충돌·비현실 발견 없음.
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
- acceptance criteria = design.md 검증 포인트 VP-1~8 + AC-T1~T7.
|
||||
- 본 fix 가 채운 VP: VP-1(게이트), VP-2(isJudge), VP-3(isOwnEntry), VP-5(CSRF), VP-6(멱등), VP-4 일부(누구나 지정 성공).
|
||||
- L2(dev DB contract): VP-3 ②팀멤버/④비활성, VP-6 UNIQUE 거부, VP-7 alias 매핑 — isOwnEntry/listByJam SQL 실측은 verification 의 L2 게이트 소관(단위 테스트는 매퍼 위임 정합만 커버).
|
||||
- AC-T 전수 게이트(AC-T1 DDL 제약수, AC-T2 핸들러 게이트수, AC-T3 CSRF수, AC-T4 isOwnEntry 양경로, AC-T5 `${` 0, AC-T6 @MockBean, AC-T7 전역 RBAC 무변경)는 grep/구조 검증 — verification 영역.
|
||||
- 영향받는 테스트 파일(신규): 위 변경 목록 2건. 기존 테스트 무영향(신규 파일 추가만).
|
||||
- full `./mvnw -o test` 의무(설계 concern 2, §30): 신규 매퍼/게이트 @MockBean contextLoads 포함 — verification 이 실행.
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T11:05:00+09:00
|
||||
workstream: W2-2-심사위원 역할 권한(jam_judges + JamRoleGate)
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 7
|
||||
planned_workers: 7
|
||||
actual_workers: 7
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
unused_diagnostics: 0 # -Xlint:all 재컴파일 + 수동 교차검증, W2-2 파일 warning 0
|
||||
test_compile: BUILD SUCCESS # ./mvnw -o test-compile, MAVEN_EXIT=0 (실행 아님)
|
||||
---
|
||||
|
||||
# 구현 보고: W2-2 심사위원 역할 권한 (jam_judges + JamRoleGate)
|
||||
|
||||
권위 설계도: `.atp/work-session/20260623-104307/implementation/W2-2-judge-role-design.md` (오픈질문 0, 정본 그대로 구현, 임의 변경 0).
|
||||
|
||||
## 변경 목록
|
||||
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| docs/jam-judge-ddl.sql (신규) | migration-writer w-001 | 권위 DDL. jam_judges(id/jam_id/user_id/assigned_by/created_at) + FK 3종(jams/users/users) + ux_jam_judges_jam_user UNIQUE + idx_jam_judges_jam. 멱등(SEQUENCE/TABLE IF NOT EXISTS, DO $$ pg_constraint guard, CREATE UNIQUE INDEX IF NOT EXISTS) — jam-ddl 선례 동형 |
|
||||
| db/schema.sql (수정) | migration-writer w-001 | 파일 끝(558~596행) jam_judges 블록 동기 사본 추가. 헤더 주석 game_reviews/jam 블록 선례 동형. docs 본문과 바이트 일치(diff 0). jams 블록(374~) 뒤 → FK 순서 정합 |
|
||||
| data/JamJudgeData.java (신규) | code-writer w-002 | POJO 6필드(id/jamId/userId/assignedBy/createdAt/displayName) + getter/setter. JamEntryData 동형(Lombok 미사용) |
|
||||
| mapper/JamJudgesMapper.java (신규) | code-writer w-003 | @Mapper insert/delete/exists/listByJam. #{} only, §33 alias 가드(POJO 비인용 alias, 큰따옴표 0). exists=EXISTS boolean(UserPermissionsMapper 선례) |
|
||||
| mapper/JamEntriesMapper.java (수정) | code-writer w-004 | isOwnEntry(jamId,gameId,userId) 1건 추가. 기존 3메서드 보존. USER+TEAM 양경로 OR-EXISTS. #{} only |
|
||||
| security/JamRoleGate.java (신규) | code-writer w-005 | @Component 잼 스코프 게이트(별도 축). isJudge(session,jamId)/isOwnEntry(jamId,gameId,judgeUserId). 두 매퍼 생성자 주입. 요청당 직접 조회(epoch/캐시 0). sessionUserId PermissionGate 동형 |
|
||||
| controller/JamJudgeAdminController.java (신규) | code-writer w-006 | @Controller. assign(POST)/remove(POST)/list(GET) 3핸들러. requireJamManage 전수 진입 게이트 + 상태변경 CSRF 2건. DuplicateKeyException catch→409. JamAdminController 헬퍼 동형 |
|
||||
| webapp/.../admin-jam-list.jsp (수정) | code-writer w-007 | 심사위원 관리 섹션 + data-action 위임 핸들러(judges/judge-assign/judge-remove). fetch GET 목록 + post 지정/해제. 클라 렌더 textContent only(innerHTML 0). 기존 폼/스크립트 보존 |
|
||||
| test/.../BibimbapApplicationTests.java (수정) | advisor 직접 | @MockBean 2건 추가(JamJudgesMapper, JamRoleGate). contextLoads 빈 의존 해소 (설계 §VP-8/AC-T6, concern 2) |
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `./mvnw -o test-compile` (JAVA_HOME=/opt/homebrew/opt/openjdk@21/...) → **BUILD SUCCESS, MAVEN_EXIT=0**. main+test 전 소스 컴파일 통과(신규 @MockBean 포함).
|
||||
- `./mvnw -o clean compile -Dmaven.compiler.compilerArgument=-Xlint:all` → **BUILD SUCCESS, 전체 WARNING 0, W2-2 파일 unused 0**. unused 진단 별도 게이트 통과.
|
||||
- AC-T1~T7 + §33 정적 grep 게이트 전수 실행 → 전부 기대값 일치(아래 §검증 echo).
|
||||
- **테스트 실행·DB 적용·git commit 안 함**(verification/수동/orchestrator 영역).
|
||||
|
||||
## 설계와의 차이
|
||||
없음. planned_workers(7) == actual_workers(7). 설계 파일영향맵·시그니처·DDL·API 계약·시퀀스를 정본 그대로 구현.
|
||||
|
||||
세부 결정(설계 범위 내, 임의 변경 아님):
|
||||
- **목록 조회 UI = 클라이언트 fetch GET**: 설계 §외부계약이 목록 조회를 JSON API(`GET /admin/jams/{jamId}/judges`)로 명시했고, W2-1 console() 핸들러(model 에 jams/csrfToken 만 주입)를 수정하면 안 되므로, JSP 는 비동기 fetch 로 목록을 로드(SSR 추가 데이터 0). console() 무수정 = W2-1 검증 동작(6핸들러 게이트) 보존.
|
||||
- **JamRoleGate @MockBean 채택**: 설계 concern 2 가 "@Component 면 @MockBean 또는 실제빈+의존매퍼 MockBean" 명시. @MockBean 채택(W1 PermissionGate 동형). 의존 매퍼(JamJudgesMapper/JamEntriesMapper)도 @MockBean 이라 양립.
|
||||
|
||||
## W2-1 기존 파일 수정 여부 + 근거
|
||||
1. **JamEntriesMapper.java (수정)** — 설계 §파일영향맵 명시(K-MAPPER, crossRefs). isOwnEntry 메서드 **1건 추가만**, 기존 insert/listByJam/exists 3메서드 한 글자도 변경 0(자기출품 충돌 판정 = W2-2 소관). W2-1 호출지점 깨짐 0(신규 메서드).
|
||||
2. **admin-jam-list.jsp (수정)** — 설계 §파일영향맵 명시(K-ADMIN, crossRefs). 심사위원 섹션·버튼·스크립트 핸들러 **추가만**. 기존 생성 폼/목록 테이블/transitionStatus·toggleVisibility·removeJam 스크립트 보존(grep 7매치 확인).
|
||||
3. **BibimbapApplicationTests.java (수정)** — 설계 §파일영향맵 (검증) 명시. @MockBean 2건 추가만(contextLoads 보존 의무).
|
||||
4. **db/schema.sql (수정)** — 설계 §데이터모델 명시. jam_judges 블록 추가만(전역 RBAC/jams/jam_entries 블록 무변경).
|
||||
|
||||
**JamController/JamAdminController/InterceptorConfig/W2-1 5매퍼는 무수정** — 설계 J3-A(InterceptorConfig 의 `/admin/jams/**` exclude 가 `/admin/jams/{id}/judges` 트리 커버 → 추가 수정 불요)에 따라 건드리지 않음. W2-1 검증 동작(requireJamManage·CSRF·#{}) 회귀 0.
|
||||
|
||||
## concerns 처리 결과
|
||||
설계 frontmatter concerns 5건은 본 구현이 모두 해소(설계가 결정으로 고정한 항목):
|
||||
- **concern 1 (시그니처 inflate)**: isJudge(session,jamId)/isOwnEntry(jamId,gameId,judgeUserId)/매퍼 시그니처 전부 최소 인자, dead parameter 0. -Xlint:all unused 0 + 수동 교차검증(JamRoleGate 두 매퍼 7회 사용, Controller 4 주입필드 전부 사용).
|
||||
- **concern 2 (신규 빈 @MockBean / full test)**: JamJudgesMapper + JamRoleGate @MockBean 등록. test-compile BUILD SUCCESS. **full ./mvnw -o test 실행은 verification-advisor 몫**(본 advisor 실행 금지) — VP-8 로 이관.
|
||||
- **concern 3 (§33 alias)**: JamJudgesMapper resultType=POJO(JamJudgeData) → snake→camel 직접 alias, **큰따옴표 alias 0건**(grep 확인). isOwnEntry/exists boolean(EXISTS) 매핑.
|
||||
- **concern 4 (자기출품 충돌 = 계약, enforce=W2-4)**: 본 W2-2 는 JamRoleGate.isOwnEntry + JamEntriesMapper.isOwnEntry **헬퍼·계약만 제공**. 지정 시점 충돌 미차단(J7). W2-4 점수입력 소비점은 verification 시 crossRefs 재확인.
|
||||
- **concern 5 (배포 순서 의존)**: InterceptorConfig 무수정(J3-A). W2-1 의 `/admin/jams/**` exclude 선배포 의존 — 롤아웃 순서 문서화(설계 §롤아웃). 코드 결함 아님.
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
- acceptance criteria 는 design.md 의 "검증 포인트" + "집합 전수 체크 AC" 참조(아래 verbatim echo).
|
||||
- 이번 변경으로 영향받는/신규 테스트 파일(설계 §파일영향맵 (검증) — 본 advisor 미작성, verification 작성 대상):
|
||||
- src/test/.../JamJudgeAdminControllerTest.java (신규 — VP-1/4/5/6)
|
||||
- src/test/.../JamRoleGateTest.java (신규 — VP-2/3)
|
||||
- src/test/.../BibimbapApplicationTests.java (수정 완료 — VP-8/AC-T6 contextLoads)
|
||||
- full `./mvnw -o test` 의무(설계 §30/concern 2). JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home 사용(시스템 java 없음).
|
||||
- DB-방언 L2 실측 권장: isOwnEntry OR-EXISTS, JamJudgesMapper EXISTS boolean, listByJam JOIN users alias (dev DB contract).
|
||||
|
||||
## 자가확인 정적 게이트 결과 (verification echo 근거)
|
||||
- AC-T1: docs ADD CONSTRAINT=3, CREATE UNIQUE INDEX=1 / schema.sql jam_judges ADD CONSTRAINT=3, UNIQUE INDEX=1 → PASS
|
||||
- AC-T2: @Post/@GetMapping 핸들러=3, requireJamManage=4(정의1+호출3) → 전수 게이트 PASS
|
||||
- AC-T3: CsrfTokens.isValid=2(상태변경 2핸들러) → PASS
|
||||
- AC-T4: entrant_type 'USER'=1, 'TEAM'=1 → 양경로 PASS
|
||||
- AC-T5: JamJudgesMapper+JamEntriesMapper `${`=0 → PASS
|
||||
- AC-T6: JamJudgesMapper @MockBean=1, JamRoleGate @MockBean=1 → PASS
|
||||
- AC-T7: docs 전역테이블(users/user_permissions/permissions) ALTER/CREATE=0 → 전역 RBAC 무변경 PASS
|
||||
- §33: JamJudgesMapper 큰따옴표 alias=0 → PASS
|
||||
|
||||
## 미해결 / 리스크
|
||||
- DB 미적용(NOT APPLIED). `db/apply-local-ddl.sh` 로 jam-judge-ddl 적용은 수동 영역(jam-ddl < jam-judge-ddl 알파벳 순 → FK 선행 jams 자동 보장).
|
||||
- 테스트 코드(JamJudgeAdminControllerTest/JamRoleGateTest) 미작성 — verification-advisor 작성·실행 대상.
|
||||
- W2-4 점수입력의 isJudge/isOwnEntry 소비점은 W2-4 워크스트림에서 검증(crossRefs).
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T11:45:00+09:00
|
||||
workstream: W2-3-잼 평가 동결 스키마(심사점수/인기투표/시상집계 + jam_score_stats VIEW)
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 1
|
||||
planned_workers: 1
|
||||
actual_workers: 1
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
test_compile_exit: 0
|
||||
unused_diagnostics: 0 # Java 소스 변경 0건(매퍼/컨트롤러 미생성) → dead param/unused 발생 불가
|
||||
references:
|
||||
design: .atp/work-session/20260623-104307/implementation/W2-3-eval-freeze-design.md
|
||||
ownership: .atp/work-session/20260624-100749/implementation/ownership.md
|
||||
---
|
||||
|
||||
# 구현 보고 — W2-3 잼 평가 동결 스키마
|
||||
|
||||
## 변경 목록
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| docs/jam-eval-ddl.sql (신규, 194줄) | migration-writer w-001 | 권위 DDL. 설계 96~291줄 verbatim. 4테이블+1VIEW+9FK+4CHECK+4UNIQUE+4보조인덱스 |
|
||||
| db/schema.sql (수정, 596→792줄, +196) | migration-writer w-001 | jam_judges 블록 뒤 동기 사본 append. 본문 byte-identical(diff 0) |
|
||||
|
||||
> 설계 파일 영향 맵(441~445줄)이 확정한 본 W2-3 산출물 = **정확히 이 2건**. 매퍼/POJO/컨트롤러/JSP/테스트는 하류 W2-4/5/6 소유(447~453줄) → 본 advisor 생성 0(아래 §설계와의 차이 참조).
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `grep -c 'CREATE TABLE IF NOT EXISTS' docs/jam-eval-ddl.sql` → **4** (AC-T1 PASS)
|
||||
- `grep -c 'CREATE OR REPLACE VIEW' docs/jam-eval-ddl.sql` → **1** (AC-T1 PASS)
|
||||
- `grep -c 'CREATE UNIQUE INDEX' docs/jam-eval-ddl.sql` → **4** (AC-T3 PASS, >=4)
|
||||
- schema.sql 동기 사본: jam_criteria/jam_scores/jam_votes/jam_awards 각 1 + jam_score_stats VIEW 1 → **전수 존재** (AC-T1 동기 누락 검출 PASS)
|
||||
- `grep -cE 'ALTER TABLE .*game_review|CREATE TABLE .*game_review' docs/jam-eval-ddl.sql` → **0** (AC-T5 단방향 write 0 PASS)
|
||||
- verbatim 정합: `diff`(jam-eval-ddl 본문 vs schema.sql 신규 블록) → **0줄**(권위=사본 드리프트 0)
|
||||
- append 경계: 596줄 `ON "jam_judges" ("jam_id");`(W2-2 끝) → 599줄 평가 헤더. 기존 블록 무변경.
|
||||
- `./mvnw -o test-compile` (JAVA_HOME=openjdk@21) → **EXIT=0**, test-classes 산출 확인(기존 코드 회귀 0). 실행 안 함(verification 영역).
|
||||
- `git status --short`: `M db/schema.sql`, `?? docs/jam-eval-ddl.sql` — src/ 변경 0.
|
||||
|
||||
## §33 인용 alias 적용 결과 (W2-3 핵심)
|
||||
- **jam_score_stats VIEW 컬럼 alias = 순수 소문자 snake_case 비인용** (`AS jam_id`, `AS game_id`, `AS weighted_total`, `AS simple_total`, `AS scored_criteria`, `AS judge_count`). camelCase 문자 0 → Postgres 소문자 폴딩이 일어나도 **식별자 불변**(이미 소문자) → 케이스폴딩 버그 **불가능**.
|
||||
- §33의 camelCase 큰따옴표 강제는 **하류 매퍼 SELECT alias 단계**에 적용(설계 337/365줄, VP-7): VIEW 컬럼 snake_case → 매퍼가 `SELECT weighted_total AS "weightedTotal"` 로 인용. 본 W2-3 은 매퍼 미생성 → 이 인용은 W2-4 verification 시점 검사(설계 위임).
|
||||
- 선례 game_review_stats 는 컬럼을 `AS "snake_case"`(인용)로, jam_score_stats 는 비인용 snake_case 로 둠 — **둘 다 소문자라 동작 동일**, §33 위반 아님(인용/비인용 무관, 폴딩 영향 0).
|
||||
- **fan-out 패턴(§33 가드)**: jam_score_stats 는 `WITH per_criterion AS (... GROUP BY jam_id,game_id,criterion_key)` 로 jam_scores 를 **출품작·기준 단위 선집계** 후 `LEFT JOIN jam_criteria`. 부모(평가)에 자식(축점수) 직접 JOIN 안 함 → COUNT/AVG 왜곡 없음. game_review_stats 선집계 선례 동형. 0-division: `NULLIF(SUM(COALESCE(c."weight",1.0)),0)` 가드 그대로. 미채점 criterion 은 per_criterion 행 미생성으로 자동 제외.
|
||||
|
||||
## 동결 자연키/컬럼 정합 (하류 소비 계약 고정)
|
||||
- **평가단위 = (jam_id, game_id) 자연키** (F8). jam_scores/jam_votes/jam_awards 전부 jam_id+game_id 직접 컬럼. FK = game_id→games, jam_id→jams(jam_entries.id surrogate 아님). ux_jam_entries_jam_game_active(schema.sql:521) 가 1:1 보장.
|
||||
- **동결 컬럼 변경 0**: 설계 DDL 블록을 한 글자도 변경 없이 구현(diff 0). jam_criteria(jam_id,criterion_key,display_name,sort_order,weight) / jam_scores(jam_id,game_id,judge_user_id,criterion_key,score) / jam_votes(jam_id,game_id,voter_user_id) / jam_awards(jam_id,game_id,award_track,rank,score_value) — 하류 W2-4/5/6 소비 계약 고정.
|
||||
- **무결성 제약 전수**(AC-T3): ux_jam_scores_jam_game_judge_criterion(1심사위원1기준1점), ux_jam_votes_jam_voter(1인1표), jam_scores_score_check(1~5), jam_awards_track_check(JUDGE/USER_RATING/POPULAR/GRAND) — 전수 존재.
|
||||
- **트랙 4종 정합**(AC-T2): award_track CHECK IN 4종 == 시상 산정 트랙 집합. F4 가드 고정.
|
||||
|
||||
## 설계와의 차이
|
||||
**계획=실제 1 worker, planned==actual. 그러나 orchestrator 지시문 "필수준수 3·4"(신규 Test.java 작성·@MockBean 등록) 미적용 — 사유 기록:**
|
||||
|
||||
- 설계 권위(파일 영향 맵 441~455줄 + 비목표 §43-46 + concern 2)가 본 W2-3 범위를 **스키마+계약 동결만**으로 확정. 매퍼/POJO/컨트롤러/JSP/**테스트**는 전부 "하류 W2-4/5/6 소유 — 본 설계 미생성". 본 advisor 가 Java 신규 생성 시 ① 설계 파일영향맵 위반 ② 하류 소유 침범 ③ 동결 스키마 소비 계약 선점 — orchestrator 금지문("파일영향맵 밖 신규 파일 금지", "동결 변경 금지")과도 충돌.
|
||||
- 따라서 본 W2-3 은 **매퍼/컨트롤러 0개 생성** → 등록할 신규 @MockBean 0, 작성할 *Test.java 0. BibimbapApplicationTests 무변경(18빈 그대로) → contextLoads 회귀 없음.
|
||||
- orchestrator "필수준수 3·4" 는 매퍼/컨트롤러가 생성되는 워크스트림(W2-1/W2-2 류) 전제의 누적 교훈이다. 본 W2-3 처럼 매퍼/컨트롤러 0 산출 워크스트림에는 적용 대상이 없다(빈/테스트 산출물 없음).
|
||||
- 검증은 DB contract(L2 — VP-1~7 dev DB 실측) 차원으로 verification-advisor 가 수행(설계 §검증포인트 519줄: contextLoads @MockBean 은 매퍼 생성 하류 책임으로 명시).
|
||||
- **그 외 동결 설계 대비 차이 0**: DDL/VIEW/제약/alias verbatim, W2-1/W2-2 기존 파일 수정 0(append only, 기존 블록 무변경).
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
- acceptance criteria 는 design.md(W2-3-eval-freeze-design.md) "검증 포인트"(VP-1~7) + "집합 전수 체크 AC"(AC-T1~T6) 참조.
|
||||
- 본 변경으로 영향받는 테스트: **없음**(Java 0 변경). 신규 검증은 dev DB contract(L2) 실측 — jam-eval-ddl 적용 후 무결성/집계 VIEW.
|
||||
- L2 dev DB contract 권장: VP-1(UNIQUE/CHECK 거부), VP-2(jam_score_stats fan-out/NULL/0-division 샘플 실측), VP-5(트랙값 CHECK), VP-6(FK 적용 순서 — jam-ddl 후 jam-eval-ddl), VP-7(하류 매퍼 alias 는 W2-4 시점).
|
||||
- DB 적용 미수행(NOT APPLIED) — verification 시 `db/apply-local-ddl.sh` 실행은 verification-advisor 판단(advisor 는 적용 금지).
|
||||
|
||||
### 설계 "검증 포인트" verbatim echo
|
||||
- **VP-1 (AC-2/3 무결성, L2)**: jam_scores 같은 (jam_id,game_id,judge,criterion) 중복 INSERT → UNIQUE 거부. score 0/6 INSERT → CHECK 거부. jam_votes 같은 (jam_id, voter) 2표 INSERT → UNIQUE 거부(1인1표). dev DB contract 실측.
|
||||
- **VP-2 (AC-9 집계 정합, L2)**: jam_score_stats 가 동일 출품작에 다수 심사위원·다수 criterion 입력 시 ① fan-out 없이 weighted_total 정확(SUM(avg*weight)/SUM(weight)) ② criterion 일부 미채점 시 채점 기준만 반영 ③ weight 전부 0 인 경계에서 0-division 없이 NULL(NULLIF 가드) — 샘플 데이터 실측(game_review_stats BUG-1 fan-out 선례 재발 방지).
|
||||
- **VP-3 (AC-6 단방향, L1)**: 시상 USER_RATING 소비 SQL 이 game_review_stats 를 SELECT 만(write 0), game_reviews 에 jam FK 부재 확인(code 정합). 6축 컬럼 미참조 확인.
|
||||
- **VP-4 (AC-7 NULL/미달, L2)**: 리뷰 0개 출품작 avg_rating NULL → NULLS LAST 정렬 말단 + review_count<3 제외. review_count==3 경계 포함. 샘플 실측.
|
||||
- **VP-5 (AC-5 트랙값, L2)**: jam_awards award_track 에 'JUDGE'/'USER_RATING'/'POPULAR'/'GRAND' 외 값 INSERT → CHECK 거부.
|
||||
- **VP-6 (FK 적용 순서, L2)**: apply-local-ddl.sh 가 jam-ddl(W2-1) 적용 후 jam-eval-ddl 적용 시 FK 생성 성공(jams/jam_entries/games/users 선존재). 단독/역순 적용 시 FK 실패 검출.
|
||||
- **VP-7 (AC-12 alias, L2)**: 하류 jam_score_stats 매퍼 반환 키가 weightedTotal/simpleTotal/scoredCriteria/judgeCount 로 정합(집계 VIEW camelCase 큰따옴표 alias 확인 — GameReviewStatsMapper 케이스폴딩 BUG-2 선례 회피).
|
||||
|
||||
### 설계 "집합 전수 체크 AC" verbatim echo
|
||||
- **AC-T1 잼 평가 신규 테이블 전수 4건 + VIEW 1건** — docs/jam-eval-ddl.sql 의 `CREATE TABLE IF NOT EXISTS` 4건(jam_criteria/jam_scores/jam_votes/jam_awards) AND `CREATE OR REPLACE VIEW` 1건(jam_score_stats): `grep -c 'CREATE TABLE IF NOT EXISTS' docs/jam-eval-ddl.sql` == 4 AND `grep -c 'CREATE OR REPLACE VIEW' docs/jam-eval-ddl.sql` == 1. AND db/schema.sql 에 동일 4테이블+1뷰 전수 존재(동기 사본 누락 검출). 테이블/뷰 추가·삭제 누락을 갯수로 동시 커버. **[advisor 사전확인: ddl 4/1, schema 4/1 — PASS]**
|
||||
- **AC-T2 시상 트랙 전수 4종 정합 불변식** — jam_awards_track_check CHECK 의 IN 목록(JUDGE/USER_RATING/POPULAR/GRAND) 4종 == 시상 산정(W2-6)이 upsert 하는 award_track 집합. 검증: DDL CHECK IN 항목 4 AND (하류 W2-6 구현 시) 3개별 트랙 + GRAND 전수 산정 경로 존재. 트랙 추가·누락을 갯수 1로 커버(F4 핵심 가드). **[advisor 사전확인: CHECK IN 4종 ddl/schema 각 1매치 — PASS, W2-6 산정경로는 하류]**
|
||||
- **AC-T3 점수/투표 무결성 제약 전수** — 동결 핵심 UNIQUE/CHECK 4종 전수 존재: ux_jam_scores_jam_game_judge_criterion(1심사위원1기준1점), ux_jam_votes_jam_voter(1인1표), jam_scores_score_check(1~5), jam_awards_track_check(4트랙). 검증: `grep -c 'CREATE UNIQUE INDEX' docs/jam-eval-ddl.sql` >= 4(criteria/scores/votes/awards 각 1) AND 위 4 제약명 전수 존재. 1건 누락 = 무결성 결함(1인1표/중복채점 우회) → FAIL. **[advisor 사전확인: UNIQUE INDEX 4, 4제약명 전수 존재 — PASS]**
|
||||
- **AC-T4 평가 매퍼 시그니처 계약 전수 `${` 0건(하류 검증)** — 하류 W2-4/5/6 가 본 계약대로 구현한 신규 매퍼 전수에 `${` 매치 0: `grep -rc '\${' <하류 jam-eval 매퍼들>` == 0 (AC-11, `${}` 금지). 부재 검증이라 리터럴 정당. **본 W2-3 은 매퍼 미생성 → 이 AC 는 하류 verification 시점 검사**(계약 위임 명시). **[advisor: 본 W2-3 매퍼 0 → 하류 W2-4/5/6 verification 시점]**
|
||||
- **AC-T5 단방향 무결성(write 0) 불변식** — game_reviews/game_review_axes/game_review_stats 가 본 동결로 인해 변경 0: docs/jam-eval-ddl.sql 에 `game_reviews`/`game_review` 토큰의 ALTER/CREATE/INSERT/UPDATE 0건(읽기 계약뿐 — 주석/SELECT 형태 예시는 무방하나 DDL 변경문 0). 검증: `grep -E 'ALTER TABLE .*game_review|CREATE TABLE .*game_review' docs/jam-eval-ddl.sql` 0건. 단방향 계약(G4) 위반(시상이 리뷰 스키마 손대기) 즉시 검출. **[advisor 사전확인: grep 0건 — PASS]**
|
||||
- **AC-T6 FK 적용 순서 불변식** — jam-eval-ddl 의 FK 가 참조하는 선행 테이블 전수(jams/jam_entries/games/users) 가 apply 시점에 존재: 알파벳 글롭 순서상 jam-ddl/game-reviews-ddl 가 jam-eval-ddl 보다 먼저(공통 prefix 비교) → FK 생성 성공. 검증: apply-local-ddl.sh dry-run 또는 dev DB 전체 적용 후 jam_scores/jam_votes/jam_awards/jam_criteria 의 FK 4종 전수 생성 확인(pg_constraint). 순서 깨짐 시 FK 미생성 검출. **[advisor 사전확인: jam-ddl < jam-eval-ddl < jam-judge-ddl 알파벳 순서 + game-reviews-ddl('g')도 선행 — 정적 PASS, FK 실생성은 L2 dev DB]**
|
||||
|
||||
## grounding 불일치 / concerns / 기존 파일 수정 / 미해결
|
||||
- **grounding 불일치**: 없음. 설계 인용 앵커 전부 실측 정합(game_review_stats fan-out/인용 선례, ux_jam_entries_jam_game_active, GameReviewStatsMapper alias, jam-ddl 멱등 스타일, apply-local-ddl.sh 알파벳 글롭, schema.sql jam_judges 끝 596줄, BibimbapApplicationTests @MockBean 18). ownership.md 에 상세.
|
||||
- **concerns 처리**: 설계 concerns 5건 전부 본 W2-3 구현 범위에서 검토 완료 — (1)평가단위 식별자: (jam_id,game_id) 자연키 채택 확정, jam_entries.id 직접 FK 미사용(설계 F8 그대로). (2)신규 매퍼 @MockBean: 본 W2-3 매퍼 0 → 하류 책임(설계 위임). (3)0-division/alias: NULLIF 가드 + VIEW snake/매퍼 camelCase 인용 2단(§33 정합). (4)최소리뷰수 N=3: 시상 산정 상수(W2-6 위치, 본 동결은 스키마만). (5)jam_entries UNIQUE 동결 유지: schema.sql:521 무변경 확인. **본 advisor 신규 concerns 0**.
|
||||
- **W2-1/W2-2 기존 파일 수정 여부**: db/schema.sql 만 수정 — jam_judges 블록 끝(596줄) 뒤 순수 append, 기존 W2-1(jams/entries/teams) + W2-2(jam_judges) 블록 **무변경**(설계 296줄 "jams/jam_entries 블록 뒤 신설" 근거). docs/*-ddl.sql 기존 파일 무변경. src/ 무변경 → W2-1/2 게이트·CSRF·#{} 동작 회귀 0.
|
||||
- **미해결**: 없음. 동결 스키마 단일권위 확정. 하류 W2-4/5/6 이 본 스키마 변경 없이 소비.
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T12:10:00+09:00
|
||||
workstream: W2-4-심사위원 평가(점수 입력/집계 소비)
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 7
|
||||
planned_workers: 11
|
||||
actual_workers: 7
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
unused_diagnostics: 0
|
||||
test_compile: BUILD SUCCESS
|
||||
---
|
||||
|
||||
# 구현 보고 — W2-4 심사위원 평가
|
||||
|
||||
> 설계 권위: `.atp/work-session/20260623-104307/implementation/W2-4-judge-scoring-design.md` (오픈질문 0, audit PASS). W2-3 동결 스키마(jam_criteria/jam_scores + jam_score_stats VIEW) 소비. 신규 DDL 0.
|
||||
|
||||
## 변경 목록
|
||||
|
||||
| 파일 | 담당 | 결과 요약 |
|
||||
|---|---|---|
|
||||
| `src/main/java/com/pandoli365/bibimbap/data/JamCriterionData.java` | advisor 직접 | 신규. criterionKey/displayName/sortOrder/weight(BigDecimal) POJO |
|
||||
| `src/main/java/com/pandoli365/bibimbap/data/JamScoreData.java` | advisor 직접 | 신규. criterionKey/score/judgeUserId/updatedAt POJO |
|
||||
| `src/main/java/com/pandoli365/bibimbap/jam/JamEvalWindow.java` | advisor 직접 | 신규. static isOpen(JamData, OffsetDateTime) — EVAL+구간 경계포함 판정, NULL=false |
|
||||
| `src/main/java/com/pandoli365/bibimbap/mapper/JamCriteriaMapper.java` | code-writer w-B1 | 신규. listByJam(jamId) SELECT, snake→camel 직접 alias, `${}` 0 |
|
||||
| `src/main/java/com/pandoli365/bibimbap/mapper/JamScoresMapper.java` | code-writer w-B2 | 신규. upsertScore(ON CONFLICT 4키) + listByJudge. snake→camel alias, `${}` 0 |
|
||||
| `src/main/java/com/pandoli365/bibimbap/mapper/JamScoreStatsMapper.java` | code-writer w-B3 | 신규. listStatsByJam VIEW SELECT. **큰따옴표 alias 5건** + 결정론 ORDER BY |
|
||||
| `src/main/java/com/pandoli365/bibimbap/controller/JamScoringController.java` | code-writer w-C1 | 신규. submitScores(POST+PUT)/myScores/summary/scoringForm. 3중게이트+CSRF |
|
||||
| `src/main/webapp/WEB-INF/views/jam-scoring.jsp` | code-writer w-C2 | 신규. 채점 폼. HtmlUtils.htmlEscape 전수 + CSRF(meta/hidden/헤더) + textContent + prefill |
|
||||
| `src/test/java/com/pandoli365/bibimbap/controller/JamScoringControllerTest.java` | code-writer w-D1 | 신규. 19 테스트. 게이트순서 전수 + AC-T5 5분류 + upsert never 10건 |
|
||||
| `src/test/java/com/pandoli365/bibimbap/jam/JamEvalWindowTest.java` | code-writer w-D2 | 신규. 10 테스트. 경계포함/status≠EVAL/NULL 4종 |
|
||||
| `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java` | advisor 직접 | 수정. 신규 3매퍼 @MockBean 등록(JamCriteriaMapper/JamScoresMapper/JamScoreStatsMapper) |
|
||||
|
||||
신규 11파일 중 8 신규 + 1 수정. 신규 DDL 0 (W2-3 동결 소비, AC-T4/AC-15).
|
||||
|
||||
## planned_workers 11 vs actual_workers 6 (worker 계획 vs 실제)
|
||||
- 계획 11(파일 11개 1:1). 실제 spawn = 6 code-writer(w-B1/B2/B3/C1/C2/D1/D2 중 7? 재계산 아래).
|
||||
- **실제 spawn 정정**: code-writer worker = JamCriteriaMapper(B1) + JamScoresMapper(B2) + JamScoreStatsMapper(B3) + JamScoringController(C1) + jam-scoring.jsp(C2) + JamScoringControllerTest(D1) + JamEvalWindowTest(D2) = **7 worker**. frontmatter `actual_workers` 를 7로 정정.
|
||||
- advisor 직접 = JamCriterionData + JamScoreData + JamEvalWindow(배치 A 3) + BibimbapApplicationTests(D3) = 4파일.
|
||||
- **전환 사유(계량 근거)**: 배치 A 3파일은 boilerplate POJO 2(JamEntryData 동형 getter/setter, 각 <50줄) + 순수함수 1메서드(JamEvalWindow, <40줄) → 병렬화 이득 미미·줄수 < 500 → advisor 직접. BibimbapApplicationTests 는 @MockBean 5줄 추가(import+필드) modify 라 직접. 정밀 산출물(매퍼 SQL alias/UPSERT, 컨트롤러 3중게이트, 전수 테스트)만 worker.
|
||||
- 11 - 4(직접) = 7 worker. **actual_workers = 7**.
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `./mvnw -o test-compile` (JAVA_HOME=openjdk@21) → **BUILD SUCCESS (EXIT 0)**. 신규 7 Java + 1 test modify 전부 컴파일 통과. 테스트 실행 안 함(test-compile 라이프사이클).
|
||||
- `./mvnw -o clean test-compile -Dmaven.compiler.showWarnings=true -Dmaven.compiler.compilerArgument=-Xlint:all` → BUILD SUCCESS. **W2-4 신규 파일 관련 경고 0건**(unused/deprecation/this-escape 포함). 전체 100 WARNING 은 기존 코드베이스 경고(W2-4 파일 경로 매치 0). **unused 진단 0 게이트 PASS**(별도 게이트).
|
||||
- grep 게이트: AC-T2(매퍼 `${`=0) PASS / AC-T4(DDL 토큰=0) PASS / AC-T3(VIEW매퍼 큰따옴표 alias 5건, 일반매퍼 큰따옴표 0) PASS / JSP innerHTML=0 PASS.
|
||||
|
||||
## §33 인용 alias 적용 결과 (이번 W 핵심 — W2-3 위임분)
|
||||
- **JamScoreStatsMapper(집계 VIEW 소비)**: camelCase alias 5건 전부 `AS "..."` 큰따옴표 인용 확인 — `AS "gameId"`, `AS "weightedTotal"`, `AS "simpleTotal"`, `AS "scoredCriteria"`, `AS "judgeCount"`. Postgres 케이스폴딩(GameReviewStatsMapper.java:13 BUG-2 선례) 회피. ORDER BY 컬럼(weighted_total/judge_count/game_id)은 VIEW 원본 소문자 컬럼이라 비인용(폴딩 무관). VP-7/AC-T3 PASS.
|
||||
- **JamCriteriaMapper/JamScoresMapper(일반 테이블)**: snake→camel **직접 alias**(큰따옴표 없음) — `criterion_key AS criterionKey` 등. 큰따옴표 alias 0건 확인(일반 매퍼 표준, VP-7).
|
||||
|
||||
## 3중 게이트 구현 위치 (JamScoringController.submitScores, 난제1 순서)
|
||||
| 단계 | 가드 | 라인 | 응답 | 소유 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `CsrfTokens.isValid(request)` | 78 | 403 errorBody | W1 인프라 |
|
||||
| 2 | `sessionUserId(session)` null | 82 | 401 | 본 W2-4 |
|
||||
| 3 | `jamRoleGate.isJudge(session, jamId)` | 87 | 403 "심사 권한 없음" | **W2-2 게이트 호출** |
|
||||
| 4 | `jamsMapper.getById(jamId)` null | 91 | 404 | W2-1 매퍼 소비 |
|
||||
| 5 | `JamEvalWindow.isOpen(jam, now())` | 96 | 422 "평가 기간 아님" | **본 W2-4(W2-3 F6 구현)** |
|
||||
| 6 | `jamEntriesMapper.exists(jamId, gameId)` | 100 | 404 | W2-1 매퍼 소비 |
|
||||
| 7 | `jamRoleGate.isOwnEntry(jamId, gameId, userId)` | 104 | 422 "자기 출품작" | **W2-2 게이트 호출** |
|
||||
| 8 | criterion 화이트리스트 + score 1~5 전수검증 → 통과 후 UPSERT 루프 | 108~140 | 422(미등록/범위/빈)/200 | 본 W2-4(S8) |
|
||||
- 트랜잭션 원자성: validated Map 전수 통과 후에만 upsertScore 루프(부분저장 금지). `@Transactional` + POST/PUT 동일핸들러(`@RequestMapping method={POST,PUT}`).
|
||||
|
||||
## 설계와의 차이
|
||||
없음 — 설계 정본 그대로 구현. concern 처리:
|
||||
- **concern 1(W2-2 게이트 시그니처 재확인)**: 실측 결과 `JamRoleGate.isJudge(session, jamId)` + `isOwnEntry(jamId, gameId, judgeUserId)` **별도 메서드** = 설계 §게이트연동 기본가정(b) 정확 일치. isJudge 통과 후 isOwnEntry 별도 호출(게이트 3·7 분리). 해소.
|
||||
- **concern 4(헬퍼 시그니처 inflate)**: JamEvalWindow.isOpen(JamData jam, OffsetDateTime now) — jam 3필드(status/evalStartAt/evalEndAt) 실사용(dead param 아님). 컨트롤러 7개 주입 의존 전부 실사용(unused 0 확인). 설계 기본 시그니처 유지(좁히지 않음 — getById 반환 그대로 전달이 호출부 단순).
|
||||
- **concern 5(UPSERT inflate)**: JamScoresMapper.upsertScore 단일 `INSERT ... ON CONFLICT (4키) DO UPDATE` 문(exists→update 2쿼리 회피). 동결 ux_jam_scores_jam_game_judge_criterion 타깃.
|
||||
- **concern 6(criterion_key 화이트리스트)**: 앱계층 listByJam → HashSet 화이트리스트, 미등록 422(upsert 미호출). AC-13.
|
||||
- **concern(@MockBean §30)**: BibimbapApplicationTests 에 신규 3매퍼 등록(AC-T6). JamRoleGate/PermissionGate 기등록. contextLoads test-compile 통과.
|
||||
|
||||
## 기존 파일 수정 + 근거
|
||||
- `BibimbapApplicationTests.java`: @MockBean 3매퍼 추가만. **설계 §파일영향맵 명시 수정 대상**(VP-8/AC-T6). 누락 시 NoSuchBeanDefinitionException(contextLoads FAIL). JamController 등 W2-1 검증 동작 파일은 **무수정**(slug 경로 vs JamScoringController 의 jamId 숫자경로 — 매핑 충돌 0).
|
||||
|
||||
## Verification 을 위한 힌트 (verification-advisor 입력)
|
||||
- acceptance criteria 는 설계 §검증 포인트(VP-1~VP-8) + §집합 전수 체크 AC(AC-T1~AC-T6) 참조.
|
||||
- 이번 변경으로 영향받는 테스트 파일: `JamScoringControllerTest`(신규 19), `JamEvalWindowTest`(신규 10), `BibimbapApplicationTests`(contextLoads 회귀 가드).
|
||||
- 본 advisor test-compile 자가확인만 수행(BUILD SUCCESS). **테스트 실행은 verification-advisor 영역**. full `./mvnw -o test` + DB contract(L2) 권장(§30).
|
||||
|
||||
---
|
||||
|
||||
## 검증 포인트 (설계 verbatim echo — verification-advisor 점검 대상)
|
||||
|
||||
> L레벨 매핑: 자격/기간/충돌 게이트·점수 입력 플로우 = L1+L2+L3. 신규 매퍼 SQL/alias·UPSERT ON CONFLICT·집계 VIEW 소비 = L1+L2(dev DB contract). 신규 컨트롤러·매퍼 의존 = full ./mvnw -o test 의무(§30).
|
||||
|
||||
- **VP-1 (AC-2/3/4 3중 게이트, L1+L3)**: JamScoringControllerTest — isJudge 통과/미심사위원 403, EVAL+구간내 통과/기간밖·status≠EVAL 422, 자기출품 충돌 422, 미인증 401. 게이트 순서(CSRF→인증→자격→기간→출품작→충돌→criterion)대로 첫 실패 지점 응답 검증. L3 스모크: 심사위원이 EVAL 잼 출품작 채점 성공.
|
||||
- **VP-2 (AC-7 UPSERT 멱등, L1+L2)**: 같은 (jam,game,judge,criterion) 재입력 시 INSERT 아닌 UPDATE(행 수 불변, score·updated_at 갱신). dev DB contract: ON CONFLICT (4키) 실측 — ux_jam_scores_jam_game_judge_criterion(W2-3 동결) 타깃 정합.
|
||||
- **VP-3 (AC-8/9 집계 정합, L2)**: jam_score_stats 소비 — 다수 심사위원·다수 criterion 입력 시 fan-out 없이 weighted_total 정확, 정렬(weighted_total DESC NULLS LAST, judge_count DESC, game_id ASC) 결정론. (W2-3 가 VIEW 자체 fan-out/0-division 을 검증 — 본 설계는 소비 정렬·alias 정합 검증.)
|
||||
- **VP-4 (AC-11/12 부분입력·미채점, L1+L2)**: criterion 일부만 입력 시 미입력 criterion 이 종합에서 제외(weight 분모 포함), judge_count 가 부분참여 반영. 트랜잭션 원자성: 미등록 criterion 포함 배열은 전체 422(부분저장 0).
|
||||
- **VP-5 (AC-5 CSRF, L1)**: 점수 입력 CSRF 누락 → 403 + mapper 미호출(deleteCommentRejectsMissingCsrfBeforeMapperAccess 패턴 준용, grounding 선례).
|
||||
- **VP-6 (AC-13 화이트리스트, L1)**: jam_criteria 미등록 criterion_key 입력 → 422, jamScoresMapper.upsertScore 미호출.
|
||||
- **VP-7 (DB-방언 계약, L2)**: JamScoreStatsMapper 반환 키가 weightedTotal/simpleTotal/scoredCriteria/judgeCount 로 정합(집계 VIEW camelCase 큰따옴표 alias 확인 — GameReviewStatsMapper.java:13 케이스폴딩 BUG-2 선례 회피). JamCriteriaMapper/JamScoresMapper 는 snake→camel 직접 alias(일반 매퍼 표준).
|
||||
- **VP-8 (contextLoads, L1)**: BibimbapApplicationTests 에 신규 3매퍼 @MockBean 등록 후 PASS(§30). JamScoringController 가 주입하는 JamRoleGate(W2-2)·PermissionGate·JamsMapper·JamEntriesMapper 빈 가용성 확인. 누락 시 NoSuchBeanDefinitionException.
|
||||
|
||||
## 집합 전수 체크 AC (설계 verbatim echo)
|
||||
|
||||
> self-audit(시점): 아래 카운트는 본 W2-4 가 신규 생성하는 정적 산출물(매퍼·게이트·API 핸들러)이며 verification 시점까지 본 워크스트림 외 변경 주체 없음(시점 안정). 동결 스키마(jam_scores/jam_score_stats/jam_criteria)는 W2-3 권위 — 본 설계가 수정 0이므로 그 카운트는 W2-3 검증 소관.
|
||||
> self-audit(표현): 게이트 호출·핸들러 열거는 단일 리터럴 grep 취약성을 피해 메서드 열거 + 진입부 헬퍼 호출 구조적 불변식에 앵커(수동 판정). 매퍼 `${` 0건·신규 DDL 0건은 부재 검증이라 리터럴 정당.
|
||||
|
||||
- **AC-T1 점수 입력 핸들러 3중 게이트 전수** — 점수를 저장하는 핸들러(submitScores: POST+PUT 동일 메서드 1개)가 진입부에서 게이트 3종(isJudge / JamEvalWindow.isOpen / 자기출품충돌) + CSRF 전수 통과: 수동 판정으로 submitScores 진입 시퀀스에 4가드(CSRF+3게이트) 전수 존재 확인. 1건이라도 누락 = 인가/기간/충돌 우회 보안결함 → FAIL. (구현: 라인 78/87/96/104 — 전수 존재.)
|
||||
- **AC-T2 신규 매퍼 전수 3개 `${` 0건** — 신규 매퍼 3파일(JamCriteriaMapper/JamScoresMapper/JamScoreStatsMapper)에 `${` 매치 0: `grep -rc '${' <매퍼 3파일>` == 0 (AC-14, `${}` 동적치환 금지). (advisor 실측: 3파일 모두 0.)
|
||||
- **AC-T3 집계 VIEW 매퍼 alias 큰따옴표 전수** — JamScoreStatsMapper(집계 VIEW 소비)의 camelCase alias 전수가 큰따옴표: jam_score_stats 종합 4컬럼(weightedTotal/simpleTotal/scoredCriteria/judgeCount) 의 alias 가 `AS "..."` 형태(케이스 폴딩 회피, §33). 검증: JamScoreStatsMapper 의 `AS "` 출현이 camelCase alias 수와 일치(일반 매퍼 JamCriteriaMapper/JamScoresMapper 는 snake→camel 직접 alias 라 큰따옴표 불요·있으면 안 됨). (advisor 실측: VIEW매퍼 5건 큰따옴표, 일반매퍼 0건.)
|
||||
- **AC-T4 신규 테이블/VIEW 0건 불변식** — 본 W2-4 가 스키마를 추가/변경하지 않음: 본 워크스트림 파일 영향에 `docs/*-ddl.sql` 신규 0 AND `db/schema.sql` diff 0 AND 신규 매퍼에 `CREATE TABLE`/`ALTER TABLE`/`CREATE VIEW` 토큰 0. 검증: `grep -rE 'CREATE TABLE|ALTER TABLE|CREATE OR REPLACE VIEW' <K-MAPPER 3파일>` == 0(매퍼는 SELECT/INSERT ON CONFLICT 만). (advisor 실측: 0.)
|
||||
- **AC-T5 401/403/422 정책 응답 전수** — 점수 입력 게이트 실패 분기 전수가 정책대로: 미인증→401, 미심사위원→403, CSRF→403, 평가기간외/자기출품/미등록criterion/score범위→422, 출품작없음→404. 검증: JamScoringControllerTest 가 5분류(401/403/404/422 각) 케이스 전수 보유(테스트 메서드 열거) AND 각 응답 status 코드 정합. (구현: 19 테스트 5분류 전수 — 401×2/403×4/404×3/422×5/200×5.)
|
||||
- **AC-T6 신규 매퍼 @MockBean 전수 3건** — BibimbapApplicationTests 에 신규 3매퍼 @MockBean 전수 등록: contextLoads PASS AND 3매퍼(JamCriteriaMapper/JamScoresMapper/JamScoreStatsMapper) 등록 수동 확인. 1건 누락 시 contextLoads FAIL 로 즉시 검출(§30). (구현: 3매퍼 등록 + test-compile BUILD SUCCESS.)
|
||||
|
||||
## grounding 불일치
|
||||
없음. 설계 인용 앵커 전부 실재 확인:
|
||||
- JamRoleGate.isJudge(session, jamId):23 / isOwnEntry(jamId, gameId, judgeUserId):31 — 별도 메서드(설계 기본가정 b 일치).
|
||||
- JamsMapper.getById(jamId):35 → JamData(getStatus/getEvalStartAt/getEvalEndAt = OffsetDateTime).
|
||||
- JamEntriesMapper.exists(@Param jamId, @Param gameId):65.
|
||||
- GameReviewStatsMapper.java:13 — camelCase `AS "gameId"` 큰따옴표 / snake 비인용 선례(§33).
|
||||
- CsrfTokens.isValid(request):35 + errorBody()(403):51 + getOrCreate(session):20.
|
||||
- PermissionGate.has(session, key):22 + PermissionKeys.GAME_JAM_MANAGE 실재.
|
||||
- RecruitController.response(HttpStatus,String):186 / sessionUserId:155 패턴 / JamController.getOrCreate 모델 csrfToken:98.
|
||||
- jam-detail.jsp:1-30 — request.getAttribute("csrfToken") + HtmlUtils.htmlEscape + meta name="_csrf" 패턴.
|
||||
- BibimbapApplicationTests @MockBean: JamRoleGate/PermissionGate 기등록, 신규 3매퍼 추가.
|
||||
- jam-eval-ddl.sql: jam_criteria/jam_scores(ux 4키)/jam_score_stats VIEW(weighted_total/simple_total/scored_criteria/judge_count 소문자 컬럼) 동결 — 변경 0.
|
||||
|
||||
## 미해결
|
||||
없음. 설계 오픈질문 0, concern 전수 처리. 잔여는 verification-advisor 의 full test 실행 + DB contract(L2) 실측(ON CONFLICT/VIEW fan-out·0-division·alias 케이스폴딩) — 본 advisor 영역 밖.
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T12:30:00+09:00
|
||||
workstream: W2-5-인기투표
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 5
|
||||
planned_workers: 5
|
||||
actual_workers: 5
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
unused_diagnostics: 0
|
||||
test_compile: BUILD SUCCESS
|
||||
references:
|
||||
design: .atp/work-session/20260623-104307/implementation/W2-5-popular-vote-design.md
|
||||
frozen_schema: docs/jam-eval-ddl.sql # §3 jam_votes (W2-3 동결, 본 워크 미수정)
|
||||
---
|
||||
|
||||
# 구현 보고: W2-5 인기투표 (잼당 1인1표 / 평가기간 게이트 / 표심 은닉→종료 후 공개)
|
||||
|
||||
## 변경 목록
|
||||
|
||||
| 파일 | worker | 변경 | 결과 요약 |
|
||||
|---|---|---|---|
|
||||
| `src/main/java/com/pandoli365/bibimbap/mapper/JamVotesMapper.java` | w-001 | 신규 | @Mapper 6메서드(castVote/updateVote/deleteVote/findVotedGameId/countByJam/listCountsByJam). `#{}` only, `${}` 0건. listCountsByJam alias 큰따옴표 인용. |
|
||||
| `src/main/java/com/pandoli365/bibimbap/controller/JamVoteController.java` | w-002 | 신규 | @Controller 4핸들러(POST vote / DELETE cancelVote / GET mine / GET results). 게이트 6단, 토글, 노출 게이트. |
|
||||
| `src/main/webapp/WEB-INF/views/jam-detail.jsp` | w-003 | 수정 | 인기투표 섹션 + 결과 영역(종료 후만 렌더) + 내 표 + JS(textContent/CSRF 헤더) 추가. 기존 섹션 무변경. |
|
||||
| `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java` | w-004 | 수정 | JamVotesMapper @MockBean 등록(contextLoads 보존). import 1 + 필드 1. |
|
||||
| `src/test/java/com/pandoli365/bibimbap/controller/JamVoteControllerTest.java` | w-005 | 신규 | plain Mockito 24테스트. VP-1~8/VP-11/AC-T1/AC-T3 전수. |
|
||||
|
||||
신규 DDL 0건(W2-3 jam_votes 동결 소비). schema.sql / docs/*-ddl.sql 무수정.
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `./mvnw -o clean test-compile -Dmaven.compiler.showWarnings=true -Dmaven.compiler.compilerArgument=-Xlint:all` (JAVA_HOME=openjdk@21) → **BUILD SUCCESS (EXIT 0)**. 신규 4 Java + 1 test modify 전부 컴파일 통과. 테스트 실행 안 함(test-compile 라이프사이클 — verification 영역).
|
||||
- **unused 진단 게이트(별도)**: 전체 100 WARNING 중 W2-5 신규 파일(JamVotesMapper/JamVoteController/JamVoteControllerTest) 매치 **0건**. unused/this-escape/unchecked/dead-param 0. 전체 100 은 기존 코드베이스 경고(@MockBean deprecation 등, W2-5 경로 매치 0 — W2-4 보고서와 동일 기존 경고군).
|
||||
- 집합 전수 AC grep 게이트(상세 아래 §집합 전수 체크): AC-T1 핸들러 2 + CSRF 2 + 게이트 2 / AC-T2 `${` 0 / AC-T4 jam_votes DDL 변경문 0 — 전부 PASS.
|
||||
|
||||
## §33 인용 alias 적용 결과 (인기집계 매퍼)
|
||||
- `JamVotesMapper.listCountsByJam` 은 `List<Map<String,Object>>` 반환(집계 GROUP BY COUNT) → **Map resultType 이라 alias 큰따옴표 인용 적용**:
|
||||
`SELECT game_id AS "gameId", COUNT(*) AS "voteCount" FROM jam_votes WHERE jam_id = #{jamId} GROUP BY game_id ORDER BY COUNT(*) DESC, game_id ASC`
|
||||
(선례: `JamScoreStatsMapper` 가 동일하게 `AS "gameId"` 큰따옴표 — Postgres lowercase 폴딩 회피로 Map 키 `voteCount` 보존 → 컨트롤러 `((Number) row.get("voteCount"))` 정상.)
|
||||
- 단일 스칼라 반환 `findVotedGameId`(Long), `countByJam`(long) 은 alias 불요 — 미적용(정상).
|
||||
- 그 외 POJO 직접매핑 매퍼 없음(JamVotesMapper 는 Map 1 + 스칼라 2 + 쓰기 3).
|
||||
|
||||
## 1인1표 / 평가기간 / 종료후공개 게이트 구현 위치
|
||||
- **1인1표(P2/G2/AC-2)**: DB `ux_jam_votes_jam_voter` UNIQUE(jam_id, voter_user_id)(W2-3 동결) + 컨트롤러 토글(`JamVoteController.vote` line 77~87): findVotedGameId → null=castVote(INSERT) / 동일 game=멱등 no-op / 다른 game=updateVote(game_id 교체). 표 추가 INSERT 안 함 → UNIQUE 충돌 없음.
|
||||
- **평가기간 게이트(P4/G4/AC-4)**: `JamVoteController.vote` line 68, `cancelVote` line 120 — `!JamEvalWindow.isOpen(jam, OffsetDateTime.now())` → 422 "투표 기간이 아닙니다.". W2-4 산출 `JamEvalWindow.isOpen`(EVAL + now∈[evalStart,evalEnd]) **재사용**(설계 §게이트연동 4 + JamScoringController 선례 동형). 상태변경 핸들러 2건 전부 보유.
|
||||
- **미로그인 차단(P5/G5/AC-5)**: vote line 58, cancelVote line 110, myVote line 141 — sessionUserId(session) null → 401. (results 는 공개 — session 미수신.)
|
||||
- **CSRF(AC-8)**: vote line 54, cancelVote line 106 — `!CsrfTokens.isValid(request)` → 403 errorBody. 매퍼 접근 전(게이트 1).
|
||||
- **종료후공개(P6/G6/AC-6 밴드왜건 회피)**: `JamVoteController.results` line 170~188 — `publiclyVisible = "CLOSED".equals(status) || (evalEndAt != null && now > evalEndAt)`. true=listCountsByJam(출품작별 count)+total / false=countByJam(총합만)+`results:null, open:true`(표심 은닉). JSP(jam-detail.jsp line 403/447) 동일 게이트 — 진행 중 출품작별 결과 영역 미렌더, 안내문만. **컨트롤러+JSP 2지점 모두 게이트 보유(AC-T3)**.
|
||||
- **본인 표 상시 노출(P6/AC-11)**: myVote line 151 — findVotedGameId(진행 중에도 본인 노출). 토글 응답에도 votedGameId 포함(집계는 미포함).
|
||||
|
||||
## 설계와의 차이 (근거 기록)
|
||||
|
||||
### 차이1 — listCountsByJam alias 큰따옴표 인용 (설계 concern 3 과의 의도적 deviation)
|
||||
- 설계 concern 3 / §W2-6계약 / §시그니처 주석은 "집계 VIEW 아님 → 큰따옴표 alias 불요(`COUNT(*) AS voteCount`)" 라 명시했다.
|
||||
- 그러나 본 구현은 `AS "voteCount"` 큰따옴표 인용을 적용했다.
|
||||
- **근거**: 케이스폴딩 위험의 기준은 "VIEW 여부"가 아니라 "**Map resultType 여부**"다. `listCountsByJam` 은 `List<Map<String,Object>>` 반환 → MyBatis 가 컬럼-프로퍼티 자동 대소문자 매칭을 하지 않고 DB 가 반환한 컬럼 라벨 문자열을 Map 키로 그대로 쓴다. Postgres 는 비인용 alias 를 lowercase 로 폴딩(`voteCount`→`votecount`)하므로 컨트롤러 `row.get("voteCount")` 가 null 이 되어 total 합산이 0 이 되는 결함이 발생한다. POJO 직접매핑(JamScoresMapper.listByJudge 등)은 MyBatis 케이스 무시 매칭으로 안전하지만 Map 은 그렇지 않다.
|
||||
- 프로젝트 동일 선례 `JamScoreStatsMapper`(Map resultType)도 `AS "gameId"` 큰따옴표 인용 → 본 구현이 그 선례 및 누적 교훈 §33(인용 alias)과 일치. 작업 지시 ★1("인기투표 집계 매퍼가 Map/집계면 인용")이 명시적으로 이 경로를 지시 — 작업 지시 > 설계 concern.
|
||||
- 설계 정본의 SQL 의미(컬럼/집계/정렬)는 불변. alias 인용은 L2 DB-방언 계약 안전 강화이며 API 응답 키(gameId/voteCount)도 설계 §외부계약과 동일. **기능/계약 변경 0, 무결성 강화만.**
|
||||
|
||||
### 차이2 (없음) — concern 1 dead method/parameter 재확인 결과
|
||||
- 설계 concern 1 은 castVote/updateVote/findVotedGameId 분리가 ON CONFLICT upsert 채택 시 dead 가능성을 경고했다. 본 구현은 **분기 전략(findVotedGameId 사전조회)을 채택**(upsert 아님)했고, 6개 매퍼 메서드 전부 컨트롤러에서 실사용 확인:
|
||||
- castVote(vote 신규), updateVote(vote 변경), deleteVote(cancelVote), findVotedGameId(vote 토글 분기 + myVote), countByJam(results 진행 중 총합), listCountsByJam(results 종료 후).
|
||||
- dead method 0, dead parameter 0(컴파일 lint 경고 0 으로 교차 확인).
|
||||
- 따라서 concern 1 은 "구현이 분기 전략 채택 + 전메서드 실사용" 으로 해소 — 시그니처 inflate 없음. (settled, 잔여 concern 아님.)
|
||||
|
||||
### 자기표 허용(P8) / 표 변경 허용(P7)
|
||||
- 설계 정본대로 구현. 자기 출품작 투표 422 분기 미추가(P8 허용), 표 변경 updateVote 경로 구현(P7). 설계 일치.
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
|
||||
### acceptance criteria — 설계 §검증 포인트 verbatim echo
|
||||
> L레벨 매핑(verification-strategies): 투표 토글·1인1표·평가기간 게이트·미로그인 플로우 = **L1+L2+L3**. 신규 매퍼 SQL/alias·집계 GROUP BY = **L1+L2(dev DB contract)**. 신규 컨트롤러·매퍼 의존 = full `./mvnw -o test` 의무(§30, @MockBean).
|
||||
|
||||
- **VP-1 (AC-2 1인1표, L1+L2)**: 같은 voter 가 같은 잼에 2회 castVote → 2번째 UNIQUE(jam_id,voter_user_id) 위반(DB 강제, L2 dev contract 실측). 다른 game 으로 POST → updateVote 로 행 1개 유지(changed:true), 표 수 불변.
|
||||
- **VP-2 (AC-4 평가기간 게이트, L1+L3)**: jam.status!='EVAL'(RECRUIT/DEV/CLOSED) 또는 now∉[eval_start,eval_end] 시 POST/DELETE → 422 + mapper 미호출. EVAL+window 내만 200.
|
||||
- **VP-3 (AC-5 미로그인, L1)**: 세션 userId 없음 → POST/DELETE/mine 401. results 는 미로그인도 200(공개 조회).
|
||||
- **VP-4 (AC-6 밴드왜건 은닉, L1)**: 진행 중(EVAL) /vote/results → `results:null, open:true`(출품작별 count 미노출). 종료 후(CLOSED 또는 now>eval_end) → results 배열 노출. JSP 동일 분기 렌더 확인.
|
||||
- **VP-5 (AC-8 CSRF, L1)**: POST/DELETE CSRF 누락 → 403 + mapper 미호출(`deleteCommentRejectsMissingCsrfBeforeMapperAccess` 패턴 준용 — verification §CSRF-before-mapper).
|
||||
- **VP-6 (DB-방언 계약, L2)**: JamVotesMapper 반환 키(votedGameId/voteCount/gameId)가 컨트롤러/JSP 조회 키와 정합. listCountsByJam GROUP BY count 가 샘플 데이터와 일치(snake→camel 직접 alias, 집계 VIEW 아님 → 큰따옴표 미사용 확인).
|
||||
- ※ 구현 정정: 본 구현은 Map resultType 이라 **큰따옴표 인용 적용**(§설계와의 차이 차이1). 검증 시 매퍼 alias 가 `AS "gameId"`/`AS "voteCount"` 큰따옴표임을 확인 — Map 키 케이스폴딩 회피.
|
||||
- **VP-7 (contextLoads, L1)**: BibimbapApplicationTests 에 JamVotesMapper @MockBean 등록 후 PASS(§30). 누락 시 NoSuchBeanDefinitionException.
|
||||
- **VP-8 (AC-7 변경/취소, L1)**: 투표→다른 game POST(changed:true, 표 수 1 유지)→DELETE(votedGameId:null, 행 0)→재투표(changed:false) 시퀀스 정합.
|
||||
|
||||
### 집합 전수 체크 AC — 설계 §집합 전수 체크 AC verbatim echo + 구현 충족 결과
|
||||
> self-audit(시점): 아래 카운트는 **본 W2-5 가 신규 생성하는 정적 산출물**(컨트롤러 상태변경 핸들러·매퍼 메서드)이며 verification 시점까지 본 워크스트림 외 변경 주체 없음(시점 안정). 자기 트리처럼 증가하는 대상 아님. jam_votes 동결 스키마 카운트는 W2-3 verification 소관(본 설계는 소비만 — 중복 검증 회피).
|
||||
> self-audit(표현): 단일 리터럴 grep 취약성을 피해 상태변경 핸들러 집합/게이트 호출 같은 **구조적 불변식**에 앵커. 매퍼 `${` 0건만 리터럴(부재 검증은 리터럴 정당).
|
||||
|
||||
- **AC-T1 투표 상태변경 핸들러 전수 2건 CSRF + 평가기간 게이트** — JamVoteController 의 상태변경 핸들러(POST vote / DELETE cancel) 전수 2건이 ① `CsrfTokens.isValid` 선검증 ② 평가기간 게이트(jam.status=='EVAL' AND now∈window) 둘 다 보유. 검증: 상태변경 핸들러(@PostMapping/@DeleteMapping) 열거 == 2 AND 각 진입부에 CSRF + 게이트 존재(수동 판정 — @PostMapping/@DeleteMapping 핸들러 열거 후 각 본문 확인, 리터럴 grep 단독 의존 회피). GET(/mine, /results)은 상태변경 아님 → 게이트 비대상(읽기). 핸들러 추가 시 게이트 누락 = 평가기간 우회 보안결함 → FAIL. **이 전수 AC 가 P4 게이트의 핵심 가드**.
|
||||
- **구현 충족**: @PostMapping("/jams/{slug}/vote")(line 44) + @DeleteMapping("/jams/{slug}/vote")(line 97) = 2건. `CsrfTokens.isValid` 호출 2회(line 54, 106), `JamEvalWindow.isOpen` 호출 2회(line 68, 120) — 두 핸들러 각 진입부 보유. GET 2건은 게이트 비대상. PASS.
|
||||
- **AC-T2 투표 매퍼 `${` 0건** — JamVotesMapper(1파일)에 `${` 매치 0: `grep -c '\${' JamVotesMapper.java` == 0 (AC-9, `${}` 동적치환 금지). 부재 검증이라 리터럴 정당.
|
||||
- **구현 충족**: `grep -c '\${' JamVotesMapper.java` == **0**. (컨트롤러도 0.) PASS.
|
||||
- **AC-T3 결과 노출 게이트 불변식(밴드왜건 회피)** — `/vote/results` 핸들러와 jam-detail.jsp 결과 영역 **둘 다** 노출 게이트(`status=='CLOSED' OR now()>eval_end_at`)를 보유: 진행 중 출품작별 count 비노출(results:null/JSP 막대 미렌더). 검증: 컨트롤러 results 분기 + JSP 조건 렌더 전수 2지점 모두 게이트 존재(수동 판정 — 시각 비교는 런타임이라 리터럴 grep 부적합, 의미 불변식 점검). 1지점이라도 무조건 count 노출 시 밴드왜건 회피(P6/G6) 위반 → FAIL.
|
||||
- **구현 충족**: 컨트롤러 results line 170~172(`publiclyVisible = "CLOSED".equals(status) || (evalEndAt != null && now>evalEndAt)`) + JSP line 403/447(`resultsPublic = "CLOSED".equals(status) || evalEnded`; resultsPublic 일 때만 `#vote-results` 렌더, 아니면 "평가 종료 후 공개" 안내). JS 측 2차 게이트(line 520/583 `resultsPublic` + line 589 `data.open` 분기). 2지점(+JS) 모두 게이트 보유. PASS.
|
||||
- **AC-T4 jam_votes 무변경 불변식(동결 소비)** — 본 W2-5 산출물에 `jam_votes` 의 DDL 변경문(ALTER TABLE/CREATE INDEX/CREATE TABLE) 0건: 본 설계는 신규 docs/*-ddl.sql 파일을 만들지 않고 schema.sql 도 수정하지 않음. 검증: 본 워크스트림 diff 에 `jam_votes` 대상 ALTER/CREATE 0건(읽기/쓰기 매퍼 SQL 의 INSERT/UPDATE/DELETE/SELECT 는 무방, DDL 변경문만 0). 동결 소비 계약(W2-3) 위반(투표가 스키마 손대기) 즉시 검출.
|
||||
- **구현 충족**: W2-5 산출물에 jam_votes ALTER/CREATE TABLE/CREATE INDEX **0건**(grep 무출력). 매퍼는 INSERT/UPDATE/DELETE/SELECT 만. docs/jam-eval-ddl.sql / schema.sql 무수정. PASS.
|
||||
|
||||
### 이번 변경으로 영향받는 테스트 파일
|
||||
- `src/test/java/com/pandoli365/bibimbap/controller/JamVoteControllerTest.java` (신규, 24테스트)
|
||||
- `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java` (contextLoads — JamVotesMapper @MockBean 등록 후 PASS 필요, VP-7)
|
||||
- full `./mvnw -o test` 권장(§30): 신규 컨트롤러+매퍼 의존 contextLoads + 단위 24 + dev DB contract(L2: listCountsByJam GROUP BY + alias 케이스폴딩) 실측.
|
||||
|
||||
## grounding 불일치 / concerns 처리
|
||||
|
||||
### grounding 실측 결과 (불일치 0)
|
||||
- W2-3 jam_votes 스키마: docs/jam-eval-ddl.sql §3 line 87~117 직접 확인 — id/jam_id/game_id/voter_user_id/created_at + ux_jam_votes_jam_voter UNIQUE + idx_jam_votes_jam_game. 매퍼 컬럼명 일치.
|
||||
- W2-4 JamEvalWindow.isOpen(JamData, OffsetDateTime): jam/JamEvalWindow.java 직접 확인 — EVAL + now∈[evalStart,evalEnd] 경계포함. 평가기간 게이트 재사용 정합.
|
||||
- W2-1 JamController: getBySlug/sessionUserId(line 244~260)/response(line 270~275)/CsrfTokens 패턴/`/jams/{slug}/entries`·`/teams` 매핑 직접 확인 — JamVoteController `/vote*` 경로 충돌 0.
|
||||
- JamEntriesMapper.exists(jamId, gameId): 직접 확인(line 65) — 활성 출품작 검증 정합.
|
||||
- JamData getter: getStatus/getEvalStartAt/getEvalEndAt(OffsetDateTime)/getId/getSlug — JamsMapper alias(eval_end_at AS evalEndAt) + JamScoringController 사용처로 교차 확인. JSP getEvalEndAt() OffsetDateTime 반환 정합(worker 의존경고 해소).
|
||||
- CsrfTokens.isValid/errorBody/HEADER_NAME/SESSION_ATTRIBUTE/getOrCreate: security/CsrfTokens.java 직접 확인.
|
||||
- game_likes 무참조: JamVotesMapper/JamVoteController 에 game_likes 참조 0 — 설계 P3/G3(별개) 준수.
|
||||
- BibimbapApplicationTests @MockBean: 직접 확인(JamScoresMapper 등 기등록 패턴) — JamVotesMapper 추가 정합.
|
||||
|
||||
### concerns 처리 (설계 5개 concern)
|
||||
- concern 1 (시그니처 inflate / dead method): **해소** — 분기 전략 채택, 6메서드 전부 실사용, lint 경고 0. (§설계와의 차이 차이2.)
|
||||
- concern 2 (@MockBean §30): **충족** — BibimbapApplicationTests 에 JamVotesMapper @MockBean 등록(w-004), contextLoads test-compile 통과. VP-7 가드.
|
||||
- concern 3 (집계 alias 큰따옴표 불요 주장): **정정 적용** — Map resultType 이라 큰따옴표 인용 채택(§설계와의 차이 차이1 + ★1). 기능 무변경.
|
||||
- concern 4 (결과 노출 게이트 컨트롤러/JSP 분기): **구현** — 컨트롤러 results + JSP resultsPublic 2지점 게이트(AC-T3 PASS).
|
||||
- concern 5 (자기표 허용): **설계대로** — 자기 출품작 422 분기 미추가(P8 허용).
|
||||
- 신규 concern: **없음(빈 리스트)**. 설계 5 concern 전부 구현 단계에서 해소/정정/충족.
|
||||
|
||||
### 기존 파일 수정 + 근거
|
||||
- jam-detail.jsp(W2-1 산출) 수정 — 설계 §파일영향맵 "수정" 명시 대상. 투표 섹션+JS 추가만, 기존 hero/운영안내/출품작/팀/출품폼/팀폼 무변경(회귀 0). 근거: 설계 G1(읽기=잼 상세 JSP 재사용, 투표 폼/결과 영역 추가).
|
||||
- BibimbapApplicationTests 수정 — 설계 §파일영향맵 "수정(검증)" + concern 2 §30 의무. JamVotesMapper @MockBean 1개 추가만.
|
||||
|
||||
### 미해결
|
||||
- 없음. 설계 오픈질문 0, concern 5개 전부 처리, 집합 전수 AC 4개 PASS, test-compile BUILD SUCCESS, unused 진단 0.
|
||||
- (참고 — verification 영역, 차단 아님) JSP 결과 영역은 게임명 대신 "게임 ID N" 텍스트 표기(/vote/results 응답에 gameName 미포함, W2-6 소비 형태 안정화 전 Map 유지 — 설계 §시그니처 주석과 정합). 막대 그래프 대신 텍스트 count(설계 "1차는 종료 후 count 단순 노출" §비목표와 정합). 후속 개선 후보이며 AC 위반 아님.
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T13:10:00+09:00
|
||||
workstream: W2-6-시상 집계/결과
|
||||
concerns: []
|
||||
concerns_checked: true
|
||||
workers_spawned: 9
|
||||
planned_workers: 12
|
||||
actual_workers: 9
|
||||
self_verification:
|
||||
checklist_passed: true
|
||||
test_compile: PASS # ./mvnw -o test-compile EXIT=0 (openjdk@21)
|
||||
unused_diagnostics: PASS # javac -Xlint:all on 9 new/modified main sources → W2-6 unused 0 (third-party jar warnings only)
|
||||
references:
|
||||
design: .atp/work-session/20260623-104307/implementation/W2-6-award-aggregation-design.md
|
||||
ownership: .atp/work-session/20260624-100749/implementation/W2-6-ownership.md
|
||||
---
|
||||
|
||||
# 구현 보고 — W2-6 시상 집계 / 결과 (W2 종점)
|
||||
|
||||
## 변경 목록
|
||||
|
||||
### 신규 생성 (production)
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| jam/AwardTrack.java | advisor 직접(w-002로 계획) | enum JUDGE/USER_RATING/POPULAR/GRAND + isValid. jam_awards CHECK 4값 1:1 |
|
||||
| data/JamAwardData.java | advisor 직접(w-001로 계획) | jam_awards 행 POJO + 표시조인 필드(gameName/thumbnailUrl/entrantName). scoreValue=BigDecimal |
|
||||
| jam/RankScores.java | advisor 직접 | standardCompetitionRank(1,2,2,4) + rankScore (N-rank+1)/N. byOrder(정렬)+byScore(동점판정) 2비교자 |
|
||||
| mapper/JamAwardsMapper.java | advisor 직접 | insert/deleteByJamTrack/listByJam/listByJamWithGame. 일반 테이블 POJO 직접 alias, `#{}` only |
|
||||
| mapper/JamReviewRatingMapper.java | advisor 직접(w-003로 계획) | USER_RATING — jam_entries LEFT JOIN game_review_stats. review_count>=3 + NULLS LAST. 집계 VIEW → `AS "avgRating"`/`"reviewCount"` 큰따옴표 |
|
||||
| jam/JamAwardService.java | advisor 직접 | 3트랙 독립 랭크 + GRAND 정규화 가중합 + NULL/미달 제외 + 멱등 @Transactional recompute |
|
||||
| controller/JamAwardAdminController.java | w-004(code-writer) | POST /admin/jams/{jamId}/awards/compute. 게이트→CSRF→404→F6 422→recompute→200 awardCounts |
|
||||
| controller/JamAwardController.java | w-005(code-writer) | GET /jams/{slug}/results. 공개, byTrack 그룹핑, "jam-results" 뷰 |
|
||||
| WEB-INF/views/jam-results.jsp | w-006(code-writer) | GRAND+3트랙 랭킹 표. HtmlUtils.htmlEscape 전수. 미산정 "결과 준비 중" |
|
||||
|
||||
### 수정 (기존 — 설계 근거 명시)
|
||||
| 파일 | worker | 결과 요약 + 근거 |
|
||||
|---|---|---|
|
||||
| controller/JamController.java | w-007(code-writer) | detail 모델 `awardsSummary` 추가 + 생성자 JamAwardsMapper 주입. **AW-DETAIL(설계 §파일영향맵 W2-1 협의 수정). 기존 5핸들러·model 키 무변경(추가만)** |
|
||||
| WEB-INF/views/jam-detail.jsp | w-008(code-writer) | 출품작 섹션 앞 "수상 결과" 배지 + 전체 결과 링크. **AW-DETAIL. awardsSummary 빈 가드 → 미산정 잼 회귀 0** |
|
||||
| test/BibimbapApplicationTests.java | w-009(code-writer) | JamAwardsMapper/JamReviewRatingMapper @MockBean 2건 추가(§30 contextLoads) |
|
||||
| test/controller/JamControllerTest.java | advisor 직접(계획외) | **JamController 6번째 생성자 인자(JamAwardsMapper) 추가로 인한 회귀 컴파일 깨짐 수정.** @Mock jamAwardsMapper + 생성자 호출 1줄 갱신. AW-DETAIL 변경의 불가피한 동반 수정(아래 §설계와의 차이) |
|
||||
|
||||
### 신규 테스트 (구현 산출물 — "(검증)" 태그여도 advisor 작성 의무)
|
||||
| 파일 | worker | 결과 요약 |
|
||||
|---|---|---|
|
||||
| test/jam/JamAwardServiceTest.java | w-010(code-writer) | 7 테스트: 3트랙 DESC rank·GRAND 정규화·1트랙 가용 포함·NULL weightedTotal 제외·empty·동점 competition·멱등 delete선행 |
|
||||
| test/controller/JamAwardAdminControllerTest.java | w-011(code-writer) | 7 테스트: 401/403/CSRF403/404/F6 422/CLOSED 200/eval종료 200 |
|
||||
| test/controller/JamAwardControllerTest.java | w-012(code-writer) | 5 테스트: missing/hidden redirect·빈 결과·트랙 그룹핑·익명 접근 |
|
||||
|
||||
## Bash 단계 (advisor 직접)
|
||||
- `./mvnw -o test-compile` (JAVA_HOME=openjdk@21) → **EXIT=0** (1차 회귀 1건: JamControllerTest 구생성자 → 수정 후 GREEN)
|
||||
- `javac -Xlint:all` 신규/수정 main 9파일 (m2 jar 클래스패스) → W2-6 unused 변수/파라미터/import **0** (third-party tomcat-embed-core 소스 + 어노테이션프로세서 warning 4건만 — W2-6 코드 무관)
|
||||
- 집합 전수 AC self-audit grep (아래 §전수 체크)
|
||||
|
||||
## 설계와의 차이 (planned 12 → actual 9 worker, advisor 직접 6파일)
|
||||
|
||||
**worker 전환 사유 (계량 근거)**: 의존 코어 3파일(JamAwardsMapper/RankScores/JamAwardService)은 상호 강결합 + GRAND 정규화 알고리즘(순위점수·가용트랙 가중평균·동점 competition)의 정밀도 비용이 커서 병렬 worker 분산 이득 < 정합 리스크 → advisor 직접. POJO(JamAwardData)·enum(AwardTrack)도 ≤50줄 사소 단일편집이라 직접. 합 6파일을 advisor 직접 작성, 나머지 9파일(컨트롤러2·JSP2·수정2·테스트3)을 code-writer 병렬 spawn.
|
||||
- advisor 직접 작성 파일: AwardTrack.java, JamAwardData.java, RankScores.java, JamAwardsMapper.java, JamReviewRatingMapper.java, JamAwardService.java + (계획외 회귀수정) JamControllerTest.java
|
||||
|
||||
**JamVoteCountMapper 미생성 (설계 concern 해소)**: 설계 §파일영향맵은 "JamVoteCountMapper(POPULAR) — W2-5 JamVotesMapper 와 별개 or 재사용, 구현 시 협의"로 명시. 기존 `JamVotesMapper.listCountsByJam(jamId)`(W2-5)가 이미 `[{gameId,voteCount}]` GROUP BY count + 큰따옴표 alias + @MockBean 등록됨 → **재사용**. 신규 매퍼 3→2, 신규 @MockBean 3→2. AC-T5 카운트는 2(설계 3가정에서 1 감소 — JamVotesMapper 기존 등록분이 POPULAR 소비 충당).
|
||||
|
||||
**JamScoreStatsMapper.listStatsByJam 재사용(JUDGE)**: 설계 명시(W2-4 소유 재사용). 신규 0.
|
||||
|
||||
**JamReviewRating Row POJO 미생성**: 설계 시그니처는 `JamReviewRatingRow` 언급. 기존 집계 소비 패턴(JamScoreStatsMapper/JamVotesMapper)이 `List<Map<String,Object>>`이므로 동형 채택 → row POJO 0(inflate 방지, design concern 1 정합).
|
||||
|
||||
**JamControllerTest 회귀 수정(계획외)**: AW-DETAIL 가 JamController 생성자에 6번째 인자(JamAwardsMapper)를 추가 → 기존 W2-1 테스트 컴파일 깨짐. 설계가 AW-DETAIL 을 "W2-1 협의 수정"으로 승인했으므로 그 동반 테스트 수정은 필수(테스트 작성/수정은 implementation 산출물). @Mock 1 + 생성자 호출 1줄만 변경, 기존 테스트 로직·단언 무변경.
|
||||
|
||||
## 설계 "검증 포인트" verbatim echo (verification-advisor 입력)
|
||||
|
||||
> L레벨 매핑: 산정 알고리즘(3트랙·GRAND·NULL·동점·멱등) = **L1**(JamAwardServiceTest 단위) + 3트랙 join/정렬/NULL = **L2(dev DB contract)**. 인가/게이트/F6 422 = **L1+L3**. 신규 컨트롤러·매퍼 의존 = full `./mvnw -o test` 의무(§30).
|
||||
|
||||
- **VP-1 (AC-1~5 산정 코어, L1)**: JamAwardServiceTest — ① 3트랙 각 정렬·rank 정확(weightedTotal/avgRating/voteCount DESC) ② GRAND rankScore (N-rank+1)/N + 가용트랙 가중합 정확 ③ 1트랙만 가용 출품작이 GRAND 에 포함(분모=그 트랙 weight) ④ 모킹 소스로 결정적.
|
||||
- **VP-2 (AC-6 NULL/미달, L1+L2)**: 리뷰 0개(avg_rating NULL) 출품작 → USER_RATING 트랙 제외(rank 없음). review_count==3 경계 포함, ==2 제외. 채점 0 출품작 JUDGE 제외. 득표 0 출품작 POPULAR rankScore 모집단 제외. GRAND 는 그래도 가용트랙으로 산정. dev DB contract 샘플 실측.
|
||||
- **VP-3 (AC-5 동점, L1)**: weightedTotal 동일 2작품 → 같은 rank(competition 1,2,2,4), tiebreak game_id ASC 결정적. GRAND 동점도 동일. 재산정 시 같은 결과(멱등).
|
||||
- **VP-4 (AC-8 멱등, L1+L2)**: recompute 2회 호출 → jam_awards 행이 중복 누적 아님(deleteByJamTrack 선행). UNIQUE(jam_id,track,game_id) 위반 없음. 부분 실패 시 트랜잭션 롤백(트랙 비는 상태 회피).
|
||||
- **VP-5 (AC-7/9 게이트·F6, L1+L3)**: JamAwardAdminControllerTest — ADMIN 통과 / SUBADMIN+GAME_JAM_MANAGE 통과 / SUBADMIN 무키 403 / 미인증 401 / CLOSED 아님+eval진행중 422 / CSRF 누락 403(mapper 미호출). L3 스모크: CLOSED 잼 산정 → 결과 페이지 노출.
|
||||
- **VP-6 (AC-12/13 alias·단방향, L2)**: JamReviewRatingMapper 반환 키 avgRating/reviewCount 정합(game_review_stats 큰따옴표 케이스폴딩 BUG-2 선례 회피, GameReviewStatsMapper.java:14 `AS "avgRating"` 동형). 산정 SQL 이 game_reviews/jam_scores/jam_votes 에 write 0(SELECT 만). 6축 컬럼 미참조.
|
||||
- **VP-7 (contextLoads, L1)**: BibimbapApplicationTests 에 신규 3매퍼 @MockBean 등록 후 PASS(§30). 누락 시 NoSuchBeanDefinitionException.
|
||||
→ **본 구현 정정**: 신규 매퍼는 **2개**(JamAwardsMapper/JamReviewRatingMapper). POPULAR 는 기존 JamVotesMapper(@MockBean 기등록) 재사용 → 신규 @MockBean 2건. JamScoreStatsMapper 도 기존 @MockBean. contextLoads 보존.
|
||||
|
||||
## 설계 "집합 전수 체크 AC" verbatim echo + 적용 결과
|
||||
|
||||
- **AC-T1 시상 트랙 전수 4종 산정 경로 정합 불변식** — AwardTrack enum 멤버 수 == jam_awards_track_check CHECK IN 항목 수(W2-3 동결) == JamAwardService insert award_track 집합 == 4(JUDGE/USER_RATING/POPULAR/GRAND).
|
||||
→ **PASS**: enum 멤버 grep 4. JamAwardService.recompute 가 insertTrack(JUDGE/USER_RATING/POPULAR) + insertGrand(GRAND) 4 산정 경로 전수 호출(AwardTrack.values() 루프로 delete, 명시적 4 insert 경로).
|
||||
- **AC-T2 산정 소스 매퍼 전수 SELECT-only 단방향 불변식** — 산정 소스에 game_reviews/jam_scores/jam_votes 대상 INSERT/UPDATE/DELETE 0.
|
||||
→ **PASS**: grep `INSERT/UPDATE/DELETE (game_reviews|jam_scores|jam_votes)` in JamReviewRatingMapper/JamAwardsMapper → none. JamAwardsMapper 는 jam_awards 대상 INSERT/DELETE 만(허용). JamScoreStatsMapper/JamVotesMapper.listCountsByJam 은 SELECT/기존(W2-4/5).
|
||||
- **AC-T3 시상 신규 매퍼 `${` 0건** — 신규 매퍼 `${` 매치 0.
|
||||
→ **PASS**: 기능적 `${...}` 동적치환 0. (Javadoc 주석 텍스트 `` `${}` 0 `` 2건은 문서 — 파라미터 바인딩은 전부 `#{...}`.)
|
||||
- **AC-T4 집계 VIEW 소비 매퍼 alias 큰따옴표 전수** — JamReviewRatingMapper(game_review_stats)·JamScoreStatsMapper(jam_score_stats) camelCase alias 큰따옴표.
|
||||
→ **PASS**: JamReviewRatingMapper `AS "gameId"`/`AS "avgRating"`/`AS "reviewCount"`(집계 VIEW 소비). JamScoreStatsMapper(W2-4) `AS "weightedTotal"` 기존. 일반 테이블 매퍼 JamAwardsMapper 는 반대로 비인용 직접 alias(scoreValue) — VIEW vs 테이블 구분 정합(혼용 아님). JamVotesMapper.listCountsByJam 도 큰따옴표(`"voteCount"`).
|
||||
- **AC-T5 신규 시상 매퍼 @MockBean 전수 등록** — contextLoads PASS + 매퍼별 등록.
|
||||
→ **PASS(정정)**: 신규 매퍼 2개 모두 @MockBean 등록(JamAwardsMapper/JamReviewRatingMapper, line 104/107). POPULAR 소비 JamVotesMapper + JUDGE 소비 JamScoreStatsMapper 는 기존 @MockBean. test-compile GREEN.
|
||||
- **AC-T6 jam_awards 단방향 소비 무결성(신규 DDL 0)** — docs/ W2-6 신규 *-ddl.sql 0, db/schema.sql 변경 0.
|
||||
→ **PASS**: `git status docs/ db/` 신규 ddl/schema 0. jam_awards 는 W2-3 docs/jam-eval-ddl.sql 소유, W2-6 변형 0.
|
||||
|
||||
## §33 인용 alias 적용 (3트랙 집계 매퍼) + 멱등 + NULL/임계 제외 + CLOSED 게이트 위치
|
||||
|
||||
- **§33 인용 alias**:
|
||||
- JamReviewRatingMapper(game_review_stats 집계 VIEW, Map resultType) → `AS "gameId"`/`AS "avgRating"`/`AS "reviewCount"` 큰따옴표. (케이스폴딩 회피, GameReviewStatsMapper.java:13-15 선례 동형)
|
||||
- JamScoreStatsMapper(jam_score_stats 집계 VIEW, 기존 W2-4) → `AS "weightedTotal"` 기존.
|
||||
- JamVotesMapper.listCountsByJam(GROUP BY count, Map resultType, 기존 W2-5) → `AS "gameId"`/`AS "voteCount"` 기존.
|
||||
- JamAwardsMapper(일반 테이블, POJO 직접매핑) → 비인용 직접 alias(`AS jamId`/`AS scoreValue`). POJO 직접매핑은 비인용 OK(혼용 아님 — VIEW/Map 만 인용).
|
||||
- **멱등 보장 메커니즘**: JamAwardService.recompute @Transactional 경계. 진입 시 `for (AwardTrack t : values()) deleteByJamTrack(jamId, t.name())` 4트랙 전부 초기화 → 재INSERT. 두 번 돌려도 결과 동일, UNIQUE(jam_id,award_track,game_id) 위반 없음(매 트랙 선삭제). 부분 실패 시 단일 트랜잭션 롤백(트랙 비는 상태 회피).
|
||||
- **NULL/임계 제외**:
|
||||
- JUDGE — JamAwardService.trackPopulation 이 weightedTotal NULL(채점 0) 행 제외(score NULL → continue).
|
||||
- USER_RATING — JamReviewRatingMapper SQL `WHERE st.review_count >= 3`(F5 임계 미달 제외) + 리뷰 0개는 LEFT JOIN NULL → review_count NULL → 임계 제외. NULLS LAST.
|
||||
- POPULAR — JamVotesMapper GROUP BY 결과(득표>0 출품작만; 0표 행 없음).
|
||||
- GRAND — insertGrand 가 가용 트랙만 분자·분모 포함(weightSum 누적), 모집단=최소1트랙 union. 부당 0점 회피.
|
||||
- **CLOSED(F6) 게이트 위치**: JamAwardAdminController.computeAwards — getById 후 `"CLOSED".equals(jam.getStatus()) || (evalEndAt!=null && now().isAfter(evalEndAt))` 아니면 422. (앱계층 런타임 게이트, DB CHECK 강제 아님 — 설계 concern 정합)
|
||||
|
||||
## grounding 불일치 / concerns 처리 / 미해결
|
||||
- **grounding 불일치 0**: jam_awards 스키마(docs/jam-eval-ddl.sql §4 track CHECK 4값·rank>=1·ux UNIQUE·idx), jam_score_stats VIEW 컬럼(weighted_total), game_review_stats VIEW(avg_rating/review_count, line 174-201 `AS "avg_rating"`), jam_votes(W2-5 listCountsByJam), JamLifecycle/JamAdminController.requireJamManage 게이트 헬퍼, PermissionGate.has(2-arg), CsrfTokens, BibimbapApplicationTests @MockBean — 전부 실측 후 인용.
|
||||
- **concerns 처리**: design concerns 5건 전부 검토 — (1)dead parameter: javac -Xlint:all unused 0 확인(award/trackPopulation/RankScores 비교자 전부 사용). (2)신규 매퍼 @MockBean + full test: 2건 등록, test-compile GREEN(full test 는 verification 몫). (3)집계 VIEW alias 큰따옴표: JamReviewRatingMapper 적용. (4)임계 N=3/가중 1/3 상수: JamAwardService 상수로 위치(가변화 후속). (5)F6 422 + @Transactional 멱등: recompute @Transactional deleteByJamTrack 선행. → concerns: [] (반환 차단 사유 없음).
|
||||
- **미해결**: 없음. (후속 비스코프: 자동 산정 스케줄러·잼별 가변 가중치 — 설계 비목표.)
|
||||
|
||||
## Verification 을 위한 힌트
|
||||
- acceptance criteria: design.md "검증 포인트" VP-1~VP-7 + "집합 전수 체크 AC" AC-T1~AC-T6 참조.
|
||||
- 영향받는 테스트 파일(신규): JamAwardServiceTest, JamAwardAdminControllerTest, JamAwardControllerTest.
|
||||
- 회귀 위험 테스트(수정): JamControllerTest(생성자 6arg), BibimbapApplicationTests(@MockBean 2 추가).
|
||||
- full `./mvnw -o test` 실행 시 baseline 171 + W2-6 신규(서비스 7 + admin 7 + public 5 = 19) GREEN 기대. JDK: /opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home (PATH 미등록 — JAVA_HOME export 필요).
|
||||
- L2(dev DB contract): JamReviewRatingMapper 3트랙 join·NULL·review_count>=3 경계·NULLS LAST 실측 권장(집계 VIEW alias 케이스폴딩).
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T12:30:00+09:00
|
||||
workstream: W2-6-시상 집계/결과
|
||||
---
|
||||
|
||||
# 파일 소유권 맵 — W2-6 시상 집계
|
||||
|
||||
## 설계 대비 구체화 결정 (concern 해소)
|
||||
- **JamVoteCountMapper 신규 생성 안 함**(설계 concern "JamVoteCountMapper vs W2-5 JamVotesMapper 재사용 — 구현 시 협의").
|
||||
기존 `JamVotesMapper.listCountsByJam(jamId)`(W2-5, 이미 `SELECT game_id AS "gameId", COUNT(*) AS "voteCount" ... GROUP BY game_id ORDER BY COUNT(*) DESC, game_id ASC` + 큰따옴표 alias + @MockBean 등록됨)를 POPULAR 트랙 소비로 **재사용**.
|
||||
→ 신규 매퍼 2개(JamAwardsMapper/JamReviewRatingMapper), 신규 @MockBean 2건.
|
||||
- **JamScoreStatsMapper.listStatsByJam(jamId)** 기존(W2-4, `weighted_total DESC NULLS LAST` 큰따옴표 alias) 재사용 — JUDGE 트랙. 신규 매퍼 0.
|
||||
- **JamReviewRatingRow/JamVoteCountRow POJO 생성 안 함** — 집계 소비는 기존 패턴(JamScoreStatsMapper/JamVotesMapper)대로 `List<Map<String,Object>>`. inflate 방지.
|
||||
|
||||
## 의존 순서 (배치)
|
||||
1. **배치1 (AW-DOMAIN 기반 — 의존 없음, 병렬)**: JamAwardData POJO, AwardTrack enum, JamReviewRatingMapper(독립 신규 매퍼)
|
||||
2. **배치2 (advisor 직접 — 의존 코어)**: JamAwardsMapper(JamAwardData 의존), RankScores 유틸, JamAwardService(전 의존)
|
||||
3. **배치3 (컨트롤러/JSP/수정/테스트 — 병렬)**: JamAwardAdminController, JamAwardController, jam-results.jsp, JamController(수정), jam-detail.jsp(수정), BibimbapApplicationTests(수정), 3 테스트
|
||||
|
||||
| 파일 | 담당 | id | 변경 | 의존 |
|
||||
|---|---|---|---|---|
|
||||
| data/JamAwardData.java | code-writer | w-001 | create | - |
|
||||
| jam/AwardTrack.java | code-writer | w-002 | create | - |
|
||||
| mapper/JamReviewRatingMapper.java | code-writer | w-003 | create | - |
|
||||
| mapper/JamAwardsMapper.java | advisor 직접 | - | create | w-001 |
|
||||
| jam/RankScores.java | advisor 직접 | - | create | - |
|
||||
| jam/JamAwardService.java | advisor 직접 | - | create | 전 |
|
||||
| controller/JamAwardAdminController.java | code-writer | w-004 | create | service |
|
||||
| controller/JamAwardController.java | code-writer | w-005 | create | mapper |
|
||||
| WEB-INF/views/jam-results.jsp | code-writer | w-006 | create | - |
|
||||
| controller/JamController.java | code-writer | w-007 | modify | mapper |
|
||||
| WEB-INF/views/jam-detail.jsp | code-writer | w-008 | modify | - |
|
||||
| test/BibimbapApplicationTests.java | code-writer | w-009 | modify | mappers |
|
||||
| test/jam/JamAwardServiceTest.java | code-writer | w-010 | create | service |
|
||||
| test/controller/JamAwardAdminControllerTest.java | code-writer | w-011 | create | adminctrl |
|
||||
| test/controller/JamAwardControllerTest.java | code-writer | w-012 | create | pubctrl |
|
||||
|
||||
불변식: 동일 파일 1 worker. 의존 코어(JamAwardsMapper/RankScores/JamAwardService)는 상호 강결합 + 정규화 알고리즘 정밀도가 높아 advisor 직접 작성(병렬 이득 없음·실수 비용 큼). 나머지 12 파일은 의존 해소 후 병렬 worker.
|
||||
</content>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
phase: implementation
|
||||
agent: implementation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-24T13:10:00+09:00
|
||||
workstream: W2-6-시상 집계/결과
|
||||
planned_workers: 12
|
||||
actual_workers: 9
|
||||
supersedes: W2-5-인기투표 (이전 workstream — 아래는 W2-6 활성 맵)
|
||||
detail: ./W2-6-ownership.md
|
||||
---
|
||||
|
||||
> 활성 workstream = W2-6 시상 집계. 상세 파일 소유권 맵은 `W2-6-ownership.md`.
|
||||
> planned 12 → actual 9 (의존 코어 6파일 advisor 직접 — 사유 W2-6-impl.md §설계와의 차이).
|
||||
|
||||
# 파일 소유권 맵 (W2-5 인기투표)
|
||||
|
||||
> 설계 권위: `.atp/work-session/20260623-104307/implementation/W2-5-popular-vote-design.md` §파일영향맵.
|
||||
> 의존 순서: V-MAPPER(시그니처 확정) → V-CONTROLLER(매퍼 시그니처 소비) → {V-VIEW, V-TEST-CTX, V-TEST-CTRL}(컨트롤러/매퍼 확정 후, 서로 독립 병렬).
|
||||
> 신규 DDL 0건(W2-3 jam_votes 동결 소비 — schema.sql/docs/*-ddl.sql 무수정 → migration-writer 불요).
|
||||
|
||||
| 파일 | 담당 worker | worker id | 변경 유형 | 의존 |
|
||||
|---|---|---|---|---|
|
||||
| `src/main/java/com/pandoli365/bibimbap/mapper/JamVotesMapper.java` | code-writer | w-001 | create | - |
|
||||
| `src/main/java/com/pandoli365/bibimbap/controller/JamVoteController.java` | code-writer | w-002 | create | w-001 (매퍼 시그니처) |
|
||||
| `src/main/webapp/WEB-INF/views/jam-detail.jsp` | code-writer | w-003 | modify | w-002 (응답 키/액션 path) |
|
||||
| `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java` | code-writer | w-004 | modify | w-001 (매퍼 타입) |
|
||||
| `src/test/java/com/pandoli365/bibimbap/controller/JamVoteControllerTest.java` | code-writer | w-005 | create | w-002 (컨트롤러 시그니처) |
|
||||
|
||||
## 배치 계획
|
||||
- 배치1 (순차 선행): w-001 V-MAPPER — 1 worker
|
||||
- 배치2 (순차): w-002 V-CONTROLLER — 1 worker (매퍼 시그니처 의존)
|
||||
- 배치3 (병렬): w-003 V-VIEW + w-004 V-TEST-CTX + w-005 V-TEST-CTRL — 3 worker 동시 (서로 독립 파일, 컨트롤러/매퍼 확정 후)
|
||||
|
||||
## 불변식 점검
|
||||
- 동일 파일 1 worker only — PASS (5파일, 1:1).
|
||||
- 의존 병렬 금지 — 배치 간 순차(1→2→3), 배치3 내부 3파일 상호 독립이라 병렬 안전. PASS.
|
||||
- 스키마/마이그레이션 0 → migration-writer 불요. PASS.
|
||||
- §33 인용 alias: V-MAPPER 의 listCountsByJam 은 List<Map<String,Object>> 집계(GROUP BY COUNT) → Map resultType → 큰따옴표 인용 필수(JamScoreStatsMapper 선례). 설계 concern 3("큰따옴표 불요")과 충돌하나 누적 교훈 §33 + 작업 지시 ★1 우선 — report.md "설계와의 차이"에 기록.
|
||||
|
||||
## planned 5 vs actual 5
|
||||
- 전 파일 worker 위임. advisor 직접 처리 0건.
|
||||
- 사유: 매퍼(SQL alias/UPSERT 정밀)·컨트롤러(2상태변경+2조회 4핸들러, 게이트 6단·노출 게이트)·JSP(투표 폼+결과 은닉 분기)·테스트(VP 전수 15+케이스)·@MockBean modify 모두 정밀·독립 작업. 단일 파일 자잘 편집(직접 임계) 미만 아님.
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
schema_version: 2
|
||||
sid: 20260624-100749
|
||||
resumed_from: null
|
||||
started_at: 2026-06-24T10:07:49+09:00
|
||||
ended_at: 2026-06-24T14:30:00+09:00
|
||||
branch: feat/v2
|
||||
user_request: "설계 한것 작업 착수 들어가자 — W2~W4 풀설계(11기능) 구현 착수"
|
||||
init_guard: "pass — docs/development/verification-strategies.md 존재"
|
||||
migrate_check: "skip — CLAUDE.md 에 atp:migrate 마커 없음 (이미 .atp/ 경로)"
|
||||
---
|
||||
|
||||
# ATP Work Session — 20260624-100749
|
||||
|
||||
## Summary
|
||||
W2 전체(W2-1~6) 구현 착수. 크리티컬 패스 순서 W2-1 → W2-2 → W2-3 동결 → (W2-4∥W2-5) → W2-6, 각 W = 구현→검증→커밋.
|
||||
|
||||
- **W2-1 게임잼 엔티티**: DONE, commit ccf1e42. 19파일(신규16+수정3). 1차 검증 L1 컴파일 FAIL(JamAdminController response() 헬퍼 누락) → fix1 보정(헬퍼 1개) → 재검증 PASS. ./mvnw test 97/97 GREEN, L2 contract PASS, 집합전수 AC-T1~6 PASS.
|
||||
|
||||
## Invocations
|
||||
- W2-1: implementation-advisor(21 worker) → verification-advisor(FAIL: 컴파일 response() 누락) → impl fix1 → verification(PASS 97/97) → commit ccf1e42
|
||||
- W2-2: implementation-advisor(7 worker) → [갭: 신규 테스트 2 미작성, "(검증)" 태그 오독] → impl fix1(테스트 24) → verification(PASS 119/119, L2 contract) → commit 71b6b6f
|
||||
- W2-3: implementation-advisor(1 worker, 스키마+VIEW 동결만) → verification(PASS 119 유지, L2 VIEW 집계+fan-out 불변 실측) → commit 2d5601b
|
||||
- W2-4: implementation-advisor(7 worker, 테스트 작성됨) → verification(PASS 148/148, L2 UPSERT·VIEW alias camelCase 보존) → commit 09ed6bc
|
||||
- W2-5: implementation-advisor(5파일, 테스트 작성됨, §33 Map alias 정정) → verification(PASS 171/171, L2 UNIQUE 토글·alias 폴딩) → commit 5ed06d9
|
||||
- W2-6: implementation-advisor(planned 12 → actual 9 worker; 코어 6파일 advisor 직접, JamVoteCountMapper 미생성=JamVotesMapper 재사용, 테스트 3 작성, JamControllerTest 회귀수정) → verification(PASS 190/190, L2 멱등 recompute·3트랙 임계/NULL·alias) → commit a74bf74
|
||||
|
||||
**W2 전체(W2-1~6) 완료. 6 feat 커밋(ccf1e42·71b6b6f·2d5601b·09ed6bc·5ed06d9·a74bf74). 최종 ./mvnw test 190/190 GREEN.**
|
||||
|
||||
## Advisor Invocation Decision Log
|
||||
# 각 advisor 호출/스킵 판단 즉시 1줄 append
|
||||
- advisor: requirements-advisor
|
||||
decision: skip
|
||||
rationale: '설계 11개 풀설계 완료(오픈질문 0). 요구 분해 불필요.'
|
||||
checked_at: 2026-06-24T10:10:00+09:00
|
||||
- advisor: research-advisor
|
||||
decision: skip
|
||||
rationale: 'W2-W4-grounding.md 코드 grounding 완료 + _cross-consistency-audit PASS. 재조사 불필요.'
|
||||
checked_at: 2026-06-24T10:10:00+09:00
|
||||
- advisor: design-advisor
|
||||
decision: skip
|
||||
rationale: '설계 산출물이 파일영향맵+계약+시퀀스 확정적, audit PASS. orchestrator→implementation-advisor 직행.'
|
||||
checked_at: 2026-06-24T10:10:00+09:00
|
||||
- advisor: implementation-advisor (W2-1)
|
||||
decision: call
|
||||
rationale: 'W2-1 잼 엔티티 — 신규 5테이블+5매퍼+2컨트롤러+3JSP+config+테스트. 다파일 worker 분산 필요.'
|
||||
checked_at: 2026-06-24T10:12:00+09:00
|
||||
- advisor: verification-advisor (W2-1)
|
||||
decision: call
|
||||
rationale: '코드 변경 → 스킵 불가. full mvnw test + 집합전수 AC + L2.'
|
||||
checked_at: 2026-06-24T10:35:00+09:00
|
||||
- advisor: implementation-advisor (W2-1 fix1)
|
||||
decision: call
|
||||
rationale: '§2.6 backward — L1 컴파일 FAIL: JamAdminController/JamController 미정의 response() 헬퍼 44회 호출. 발원=구현단계 컨트롤러 worker. 2컨트롤러 전수 보정.'
|
||||
checked_at: 2026-06-24T10:38:00+09:00
|
||||
- advisor: implementation-advisor (W2-2)
|
||||
decision: call
|
||||
rationale: 'W2-2 심사위원 jam_judges — 신규 테이블+JamRoleGate+자기출품 충돌. W2-1 후 착수.'
|
||||
checked_at: 2026-06-24T10:48:00+09:00
|
||||
- advisor: implementation-advisor (W2-2 fix1)
|
||||
decision: call
|
||||
rationale: '구현 갭 — 설계 파일영향맵 신규 테스트 2건(JamJudgeAdminControllerTest/JamRoleGateTest) 미작성. impl-advisor가 "(검증)" 소유태그를 verification 작성으로 오독. 테스트 작성=구현 산출물. [retro 교훈 후보]'
|
||||
checked_at: 2026-06-24T11:05:00+09:00
|
||||
- advisor: implementation-advisor (W2-3)
|
||||
decision: call
|
||||
rationale: 'W2-3 평가 동결 ⚠️클러스터 권위 — jam_criteria/scores/votes/awards + jam_score_stats VIEW. W2-4/5 게이트. VIEW 매퍼 §33 인용 alias 필수.'
|
||||
checked_at: 2026-06-24T11:18:00+09:00
|
||||
- advisor: implementation-advisor (W2-4)
|
||||
decision: call
|
||||
rationale: 'W2-4 심사위원 평가 — criterion UPSERT + 가중집계 + 3중게이트. jam_score_stats VIEW 소비 매퍼 AS "camelCase" 인용 필수(W2-3 위임). 동결 후 착수.'
|
||||
checked_at: 2026-06-24T11:30:00+09:00
|
||||
- advisor: verification-advisor (W2-4)
|
||||
decision: call
|
||||
rationale: '코드 변경 → 스킵 불가. full test + L2(ON CONFLICT UPSERT·VIEW alias 케이스폴딩·3중게이트).'
|
||||
checked_at: 2026-06-24T11:50:00+09:00
|
||||
- advisor: implementation-advisor (W2-5)
|
||||
decision: call
|
||||
rationale: 'W2-5 인기투표 — jam_votes 1인1표 UNIQUE + 평가기간 게이트 + 종료후 공개. W2-3 소비. 동결 후 W2-4와 병렬 가능했으나 C2(동시 advisor 금지)로 순차.'
|
||||
checked_at: 2026-06-24T12:02:00+09:00
|
||||
- advisor: verification-advisor (W2-5)
|
||||
decision: call
|
||||
rationale: '코드 변경 → 스킵 불가. full test + L2(listCountsByJam Map alias 케이스폴딩·1인1표 토글·종료후공개).'
|
||||
checked_at: 2026-06-24T12:20:00+09:00
|
||||
- advisor: implementation-advisor (W2-6)
|
||||
decision: call
|
||||
rationale: 'W2-6 시상 집계(W2 마지막) — 3트랙 개별수상 + 가중 GRAND + NULL/임계 제외 + CLOSED 확정 멱등. jam_awards + jam_score_stats VIEW + game_review_stats + jam_votes 다중 소비. §33 인용 alias + 멱등.'
|
||||
checked_at: 2026-06-24T12:32:00+09:00
|
||||
planned_workers: 12
|
||||
actual_workers: 9
|
||||
worker_note: '의존 코어 6파일(AwardTrack/JamAwardData/RankScores/JamAwardsMapper/JamReviewRatingMapper/JamAwardService) advisor 직접(강결합+GRAND 정규화 정밀도). code-writer 9 spawn(컨트롤러2·JSP2·수정2·테스트3). JamVoteCountMapper 미생성(기존 JamVotesMapper.listCountsByJam 재사용 — 신규 매퍼 3→2, @MockBean 3→2). 회귀: JamControllerTest 6arg 생성자 동반수정. test-compile GREEN, unused 0.'
|
||||
- advisor: verification-advisor (W2-6)
|
||||
decision: call
|
||||
rationale: '코드 변경 → 스킵 불가. full test + L2(멱등 recompute·3트랙 집계 NULL/임계·GRAND 가중·alias).'
|
||||
checked_at: 2026-06-24T12:52:00+09:00
|
||||
- advisor: graph-refresh-checker
|
||||
decision: call
|
||||
rationale: '세션종료 §9.4 — 코드 변경 큼(신규 30+ Java). 판정 fully-stale.'
|
||||
checked_at: 2026-06-24T13:05:00+09:00
|
||||
- advisor: graphify-update-advisor
|
||||
decision: call
|
||||
rationale: 'fully-stale no-defer 처리 — src/docs 재생성 + 메타 갱신.'
|
||||
checked_at: 2026-06-24T13:08:00+09:00
|
||||
|
||||
## Decisions
|
||||
- 범위 = W2 전체(W2-1~6) — 사용자 AskUserQuestion 선택. 크리티컬 패스 순서 W2-1→W2-2→W2-3 동결→W2-4→W2-5→W2-6, 각 W 구현→검증→커밋(롤백 경계 확보).
|
||||
- requirements/research/design advisor 스킵 — 풀설계 11개 완료(오픈질문 0) + _cross-consistency-audit PASS. orchestrator→implementation-advisor 직행.
|
||||
- W2-4/5 병렬 가능했으나 C2(orchestrator 동시 상위 advisor 금지)로 순차 진행.
|
||||
- **§33 케이스폴딩 가드 정밀화(W2-5 정정)**: 케이스폴딩 위험 기준 = "VIEW 여부"가 아니라 **Map resultType 또는 집계 SELECT**. → VIEW/Map/집계 매퍼는 camelCase alias `AS "..."` 인용, POJO 직접매핑은 비인용 OK. W2-4(JamScoreStatsMapper)·W2-5(JamVotesMapper.listCountsByJam)·W2-6(JamReviewRatingMapper) 적용. L2 에서 비인용 대조군 폴딩 재현으로 가드 입증.
|
||||
- DB 적용은 §6 수동 게이트 — L2 검증은 격리 throwaway DB 비파괴 실측(적용→실측→DROP), 실 dev DB(bibimbap) 무접촉. 실 dev DB 적용은 needs_user_verification.
|
||||
|
||||
## user_signals
|
||||
positive: []
|
||||
negative: []
|
||||
# 사용자 발화 = 초기 요청 1건 + 범위선택(W2 전체) 1건. 부정/긍정 시그널 없음(중립 진행).
|
||||
|
||||
## verified_by_me
|
||||
- **L1 (typecheck / unit+regression)**: `./mvnw -o test` 각 W 통과, 최종 **190/190 GREEN** (W2-1~6 누적, 회귀 0). 신규 테스트 누계 ~120(JamLifecycle/JamAdmin/JamController/JamRoleGate/JamJudgeAdmin/JamScoring/JamEvalWindow/JamVote/JamAwardService/JamAwardAdmin/JamAwardController). contextLoads PASS(신규 12매퍼+게이트 @MockBean 전수).
|
||||
- **L2 (contract-dev-db)**: pass — 격리 throwaway DB 비파괴 실측. XOR/status/track CHECK 거부, 활성 UNIQUE(slug/entries/judges/scores/votes) 거부, jam_score_stats VIEW 집계+fan-out 불변(심사위원 2→4 무왜곡), UPSERT ON CONFLICT 멱등, jam_awards recompute 멱등(DELETE→INSERT 2회 count·md5 동일), §33 alias camelCase 보존(비인용 대조군 폴딩 재현), 3트랙 임계/NULL 제외.
|
||||
- **로그 스캔**: clean (전 W. 잔여 WARN 은 Mockito/ByteBuddy agent·Spring static trailing-slash 환경 noise).
|
||||
|
||||
## needs_user_verification
|
||||
- **실 dev DB(bibimbap) jam DDL 미적용** — `db/apply-local-ddl.sh` 로 docs/jam-ddl.sql + jam-eval-ddl.sql + jam-judge-ddl.sql 적용 필요. L2 는 throwaway DB 로 통과했으나 실 dev DB 는 §6 수동 게이트라 미적용. L3 런타임/스모크 전 선행.
|
||||
- **L3 런타임 인가 스모크(WAR 기동 후)**: ① SUBADMIN 무권한 POST /admin/jams* → 403, 미인증 GET → /login redirect ② ADMIN 잼 생성·상태전이 → jam_status_log 적재 ③ 심사위원 지정/채점 200·거부경로(비-isJudge 403·기간외 422·자기출품 422) ④ 인기투표 토글·취소·종료후 결과 공개/진행중 은닉 ⑤ CLOSED 시상 산정 멱등(2회 동일)·트랙별 결과 노출.
|
||||
|
||||
## graph_refresh
|
||||
- 판정: **fully-stale** (graph-refresh-checker). 기준 b9d836d(2일전·16커밋차) — W1 RBAC 레이어도 미반영(이번 W2 외). src 75파일(+10119/-43), docs 16파일.
|
||||
- no-defer 처리 **완료**: orchestrator `/graphify src/`(1152노드/2813엣지/56커뮤니티, AST 101코드 + 이미지3 vision) + `/graphify docs/`(91노드/94엣지/20커뮤니티, semantic 2청크 — DDL FK·결정 rationale·교훈) 재생성. docs/graph/src·docs/ 본체 갱신(gitignore), docs/graph/index.md frontmatter source_commit a74bf74 + Scopes 표 갱신(graphify-update-advisor 선반영). 본체 .gitignore, index.md만 커밋.
|
||||
|
||||
## open_items
|
||||
- §33 alias 가드: W2-1 시점 발견(5매퍼 POJO 비인용 무해) → **하류 전부 반영 완료**. VIEW/Map/집계 매퍼(JamScoreStatsMapper·JamVotesMapper.listCountsByJam·JamReviewRatingMapper) `AS "camelCase"` 인용 적용·L2 입증. 잔여 0.
|
||||
- **회고 교훈 반영 완료(docs-first)**: verification-strategies.md "설계·테스트 단계 체크리스트"에 교훈 3건 append — (1) "(검증) 소유태그 신규 테스트=구현 산출물" (2) "케이스폴딩 기준=Map resultType/집계, VIEW 여부 아님" (3) "신규 컨트롤러 공유 헬퍼 정의 grounding 선확인". + 외부번들 권고 2건(design-advisor owner 표기·impl-advisor 헬퍼 grounding). memory_optional=false(bibimbap memory 미활성 → docs 단독 마감).
|
||||
|
||||
## Retrospective
|
||||
```yaml
|
||||
Retrospective:
|
||||
signals:
|
||||
positive: [] # 부정/긍정 명시 시그널 없음. 사용자 발화 = 초기 요청 1 + 범위선택(W2 전체) 1, 모두 중립.
|
||||
negative: []
|
||||
what_went_well:
|
||||
- 풀설계 audit PASS 활용 — requirements/research/design advisor 3종 스킵하고 orchestrator→implementation-advisor 직행. 오픈질문 0 + _cross-consistency-audit PASS 라는 사전조건을 근거로 한 비자명 판단이 6기능 전체에서 재작업 없이 검증됨.
|
||||
- per-W 구현→검증→커밋 사이클로 롤백 경계 확보 — W2-1~6 각각 독립 feat 커밋. 컴파일 FAIL/테스트 갭이 발생한 W2-1·W2-2 도 해당 W 안에서 backward 보정되어 인접 W 로 오염 전파 없음.
|
||||
- L2 contract 를 격리 throwaway DB 비파괴 실측으로 수행(적용→실측→DROP) — 실 dev DB(bibimbap) 무접촉. §33 alias 폴딩을 비인용 대조군 재현으로 가드 입증, VIEW fan-out 불변(심사위원 2→4 무왜곡)·UPSERT/recompute 멱등(count·md5 동일)까지 손계산 대조.
|
||||
- §33 alias 가드를 W2-1 시점 발견 후 하류(W2-4·W2-5·W2-6) 전부 전파 — open item 을 끝까지 닫고 잔여 0 으로 마감.
|
||||
what_to_improve:
|
||||
- W2-1 신규 컨트롤러(JamAdminController/JamController)가 공유 response() 헬퍼를 44회 호출하나 정의가 어디에도 없어 L1 컴파일 FAIL. test-compile 단계가 아니라 full ./mvnw test 에서야 발현 → grounding 단계에서 호출 심볼 정의 존재를 선확인했어야 함.
|
||||
- W2-2 설계 파일영향맵이 신규 *Test.java(JamJudgeAdminControllerTest/JamRoleGateTest)에 "(검증)" 소유태그를 붙여, impl-advisor 가 "verification-advisor 가 작성"으로 오독 → 테스트 미작성 갭. 테스트 작성은 구현 산출물이고 verification 은 실행만 한다는 경계가 설계 표기에서 흐려짐.
|
||||
- §33 케이스폴딩 위험 기준이 설계 concern3 에 "VIEW 만 인용"으로 부정확하게 기술됨 → W2-5(JamVotesMapper.listCountsByJam, Map resultType 비VIEW)에서 정정. 기준은 VIEW 여부가 아니라 Map resultType/집계 SELECT 여부.
|
||||
memory_candidates:
|
||||
- name: test-ownership-tag-is-impl-artifact
|
||||
type: feedback
|
||||
description: 설계 파일영향맵의 신규 *Test.java "(검증)" 소유태그는 구현 산출물 — verification 은 실행만, 작성 안 함.
|
||||
body_draft: |
|
||||
Rule: 설계 파일 영향맵에서 신규 테스트 파일(*Test.java)에 "(검증)" 류 소유태그를 붙이더라도, 그 태그는 "검증 단계가 작성한다"는 뜻이 아니다. 테스트 코드 작성은 implementation 단계의 산출물이고, verification-advisor 는 작성된 테스트를 실행·판정만 한다(Write 없음).
|
||||
Why: W2-2 에서 impl-advisor 가 "(검증)" 태그를 verification 작성으로 오독해 신규 테스트 2건(JamJudgeAdminControllerTest/JamRoleGateTest)을 미작성한 채 넘김 → backward fix1 으로 테스트 24건 보정. 소유태그가 "이 산출물이 검증을 위한 것"과 "검증 단계가 만든다"를 구분하지 못해 갭 발생.
|
||||
How to apply: (1) 설계 영향맵에서 신규 *Test.java 는 owner=implementation 으로 명시, "(검증)" 은 용도 라벨로만 쓴다. (2) impl-advisor 는 영향맵의 모든 신규 *Test.java 를 작성 대상으로 간주한다. (3) verification 단계 산출물에 Write 가 잡히면 경계 위반으로 본다.
|
||||
rationale_for_saving: 설계 표기 관례에서 비롯한 재현성 있는 단계 경계 오독. 코드/커밋에서 유도 불가, 관찰로만 드러남.
|
||||
signal_source: observation
|
||||
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # "설계·테스트 단계 체크리스트" append-only 섹션
|
||||
memory_optional: false # docs 단독 마감 충분. bibimbap memory 미활성.
|
||||
- name: case-folding-trigger-is-map-resulttype-not-view
|
||||
type: feedback
|
||||
description: §33 alias 케이스폴딩 인용 기준 = Map resultType/집계 SELECT 여부지 VIEW 여부 아님.
|
||||
body_draft: |
|
||||
Rule: MyBatis alias 를 camelCase 로 유지하려 큰따옴표 인용(AS "camelCase")이 필요한 기준은 "VIEW 를 조회하느냐"가 아니라 "resultType=Map 또는 집계 SELECT 결과를 컨트롤러가 camelCase 키로 꺼내느냐"이다. POJO 직접 매핑은 MyBatis 가 setter 매칭으로 흡수하므로 비인용 alias 도 무해.
|
||||
Why: 기존 교훈(verification-strategies.md L33)은 케이스폴딩을 Map 키 일반 문제로 맞게 짚었으나, W2 설계 concern3 은 이를 "VIEW 만 인용"으로 좁혀 부정확. W2-5 JamVotesMapper.listCountsByJam(VIEW 아님·Map resultType)에서 비인용이면 동일 폴딩 발생함을 확인 → 기준 정밀화.
|
||||
How to apply: alias 인용 필요 판정 = (resultType 이 Map 인가?) OR (집계/뷰 SELECT 결과를 camelCase 키로 소비하는가?). 둘 다 아니면(POJO 직접) 비인용 OK. L2 에서 비인용 대조군 폴딩 재현으로 가드.
|
||||
rationale_for_saving: 기존 BUG-2/L33 교훈의 정밀화 — VIEW 한정 오해를 교정. 재발 가능(집계 Map 매퍼마다), 관찰 기반.
|
||||
signal_source: observation
|
||||
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # L33 케이스폴딩 항목 정밀화(append 또는 in-place 보강)
|
||||
memory_optional: false
|
||||
conflicts_with: null # L33 과 상충 아님 — 정밀화/보강
|
||||
- name: new-controller-shared-helper-grounding-check
|
||||
type: feedback
|
||||
description: 신규 컨트롤러가 호출하는 공유 헬퍼는 정의 존재를 grounding 에서 확인 — test-compile 로 안 잡히는 경우가 있다.
|
||||
body_draft: |
|
||||
Rule: 신규 컨트롤러/클래스가 공유 헬퍼 메서드(예: response())를 호출하면, implementation grounding 단계에서 그 헬퍼의 정의가 실제로 존재하는지(상위 클래스·유틸·믹스인 어디든) rg 로 선확인한다. "관례상 있을 것"으로 가정하지 않는다.
|
||||
Why: W2-1 에서 JamAdminController/JamController 가 response() 를 44회 호출했으나 정의가 없어 L1 컴파일 FAIL. test-compile 게이트에서는 통과한 것처럼 보이고 full ./mvnw test 에서야 발현 → backward fix1.
|
||||
How to apply: (1) 신규 컨트롤러 worker 지시에 "호출하는 공유 헬퍼 정의 존재 확인" 체크 포함. (2) test-compile GREEN 을 컴파일 안전 근거로 신뢰하지 말고 full test 까지(이미 L33 의 'full test 의무'와 결합). (3) 헬퍼가 없으면 정의 추가도 같은 W 산출물에 포함.
|
||||
rationale_for_saving: 컴파일 게이트의 사각(test-compile vs full test)에서 비롯, 신규 컨트롤러마다 재발 가능. 기존 L30(매퍼 @MockBean) 교훈과 인접하나 결이 다름(헬퍼 정의 부재 컴파일).
|
||||
signal_source: observation
|
||||
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # L30 'full test 의무' 항목에 헬퍼 정의 grounding 한 줄 보강
|
||||
memory_optional: false
|
||||
protocol_feedback:
|
||||
- "외부 번들 — 미적용: design-advisor 파일영향맵 표기 규약에 '신규 *Test.java 의 owner 는 implementation, (검증)/용도 라벨과 owner 를 분리 표기' 조항 추가 권고. structural — 단계 경계 오독을 표기가 유발."
|
||||
- "외부 번들 — 미적용: implementation-advisor 체크리스트에 '신규 컨트롤러가 호출하는 공유 헬퍼/심볼 정의 존재 grounding 확인(test-compile 비신뢰)' 조항 추가 권고."
|
||||
applied_changes: [] # orchestrator 가 수용 후 채움
|
||||
```
|
||||
|
|
@ -0,0 +1,748 @@
|
|||
---
|
||||
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 |
|
||||
Loading…
Reference in New Issue