15 KiB
| 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 |
|
구현 보고 — 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 ANDdb/schema.sqldiff 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 영역 밖.