bibimbap/.atp/work-session/20260624-100749/implementation/W2-4-impl.md

15 KiB
Raw Blame History

phase agent agent_version generated_at workstream concerns concerns_checked workers_spawned planned_workers actual_workers self_verification
implementation implementation-advisor 1 2026-06-24T12:10:00+09:00 W2-4-심사위원 평가(점수 입력/집계 소비)
true 7 11 7
checklist_passed unused_diagnostics test_compile
true 0 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 영역 밖.