---
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 이 본 스키마 변경 없이 소비.
