bibimbap/.atp/work-session/20260629-100849/verification.md

107 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
phase: verification
agent: verification-advisor
agent_version: 1
generated_at: 2026-06-29T03:45:00Z
concerns:
- "AC-T6 소스 표시 아티팩트: event_type 문자열 리터럴이 소스 트리에서 'n' 으로 난독화 표시됨(BadgeKeys/PermissionGate→ln 동일 패턴). L1 347 GREEN + L2 C6 CHECK 거부 동작으로 런타임 emit 값이 유효 enum(REVIEW_WRITTEN/GAME_UPLOADED)임을 간접 검증. 판정에 영향 없음(PASS) — orchestrator 인지용."
concerns_checked: true
---
# 검증 결과 — W4 유저 배지/평판
## Acceptance Criteria (입력 받은 그대로 인용)
- **L1**: BUILD SUCCESS, Failures:0 Errors:0. N ≥ 318 + 신규(BadgeServiceTest 12 + BadgeManageControllerTest 14 + GameReviewControllerTest 회귀 3) ≈ 347+. 회귀 0. ★W1 권한카탈로그 회귀(concern5): PermissionKeys BADGE_MANAGE 4번째 추가, W1 테스트가 하드코딩 카운트(==3)로 FAIL 안 함. contextLoads GREEN(신규 @MockBean 6).
- **L2 (격리 throwaway postgres:16)**: C1 자동부여 멱등 / C2 회수후 재획득 / C3 reputation dedupe / C4 countActive 집계 / C5 배치 alias camelCase / C6 CHECK·FK.
- **AC-T2**: badge 테이블 3 (DDL + schema.sql 동기).
- **AC-T3**: 4매퍼 `${}` == 0.
- **AC-T4**: insertIgnore ON CONFLICT DO NOTHING (부분유니크 대응) × 2매퍼.
- **AC-T5**: PermissionKeys BADGE_MANAGE 존재 + verifier values() 순회(하드코딩 리스트 아님).
- **AC-T6**: event_type CHECK 2종 == 코드 상수 == 훅 2곳.
- **AC-9 (N+1)**: listReviews 루프 밖 1회 배치 호출 + 테스트 times(1).
- **AC-4/11 (게이트)**: BadgeManageController grant/revoke = permissionGate.has(BADGE_MANAGE) + CSRF 선검증(미인증 401 / 미인가 403 / CSRF 403).
- **구현 편차(orchestrator 인지)**: 표시 AC 중 리뷰작성자 배지 경로만 구현. 프로필 myBadges·게임카드 creator 배지 미구현 = 의도된 deferral, FAIL 처리하지 않음.
## 실행된 전략
| id | cmd | exit | severity | 결과 |
|---|---|---|---|---|
| verify-all | (L1 docker mvnw -o test) + (L2 throwaway pg C1~C6) + 로그 스캔 | 0 | blocker | pass |
분해 결과:
| 단계 | 결과 |
|---|---|
| L1 typecheck (컴파일) | pass (BUILD SUCCESS) |
| L1 unit+regression | pass (347 tests, Failures:0 Errors:0 Skipped:0) |
| L2 contract-postgres (C1~C6, 격리 컨테이너) | pass (전 항목 PASS, 컨테이너 DROP 확인) |
| 로그 스캔 | clean (badge 무관 잡음만: test-fixture RuntimeException:boom, @MockBean deprecation WARN, FeedParser XXE-fixture fatal — 모두 Errors:0 동반) |
## L1 상세
- 명령: `docker run --rm -v "$PWD":/work -w /work -v "$HOME/.m2":/root/.m2 --entrypoint sh eclipse-temurin:21-jdk -c './mvnw -o test'`
- 집계: **Tests run: 347, Failures: 0, Errors: 0, Skipped: 0****BUILD SUCCESS** (19.351s).
- 신규/회귀 테스트 GREEN:
- `service.BadgeServiceTest` — 12 (기대 12) ✅
- `controller.BadgeManageControllerTest` — 14 (기대 14) ✅
- `controller.api.GameReviewControllerTest` — 24 (직전 21 + 회귀 3) ✅
- **contextLoads GREEN**: `BibimbapApplicationTests` 1 test pass. 신규 @MockBean 6 (BadgesMapper, UserBadgesMapper, UserBadgesQueryMapper, ReputationEventsMapper, BadgeService, ReputationService) 전부 wiring 됨 — NoSuchBeanDefinitionException 없음.
- **★W1 권한카탈로그 회귀(concern5) — 회귀 없음**:
- 런타임 로그: `[RBAC] 권한 카탈로그 시드 완료: 4 키` — 카탈로그가 3→4 로 증가했으나 시드 정상.
- 카운트 가정 가능 테스트 전부 GREEN: `PermissionGateTest` 7, `AdminConsoleControllerTest` 13, `JamRoleGateTest` 8 — **어느 것도 하드코딩 ==3 으로 FAIL 하지 않음**.
- AC-T5b: `PermissionCatalogVerifier``PermissionKeys.values()` 순회(line 30/35/47), 하드코딩 리스트 아님 → 키 추가에 자동 적응.
## L2 상세 (격리 throwaway `bbm-l2-w4`, postgres:16, 비파괴)
셋업: 컨테이너 기동 → `CREATE SCHEMA dev` + `SET search_path=dev` + `db/schema.sql` 전체 적용(에러 0, 4테이블 생성: users/badges/user_badges/reputation_events) → users(1,2) 시드. 실 dev DB 무접촉(독립 컨테이너). 종료 시 `docker rm -f bbm-l2-w4` 완료 확인.
| 계약 | 결과 | psql 근거 |
|---|---|---|
| **C1 자동부여 멱등** | PASS | 동일 (1,'REVIEWER') insertIgnore 2회 → 2번째 `INSERT 0 0`; total_rows=1, active_rows=1 (부분유니크 ux_user_badges_active) |
| **C2 회수후 재획득** | PASS | revoke(`UPDATE 1`) → 동일키 재insert(`INSERT 0 1`); total=2, active=1, revoked=1 (부분유니크가 revoked 행 제외) |
| **C3 reputation dedupe** | PASS | 동일 (1,'REVIEW_WRITTEN','review:99') 2회 → 2번째 `INSERT 0 0`, dedup_rows=1. source_ref NULL 2회 → null_ref_rows=2 (부분유니크 WHERE source_ref IS NOT NULL 제외 → 중복 허용) |
| **C4 countActive 집계** | PASS | REVIEW_WRITTEN 3건(review:99/100/101) → COUNT(*)=3 |
| **C5 배치 alias camelCase** | PASS | listActiveBadgeKeysByUserIds IN(1,2): userId=1 REVIEWER, userId=2 TECHNICIAN(활성만, revoked 제외). information_schema.columns 가 `userId`/`badgeKey` 인용 보존 확인(소문자 폴딩 없음 — W2 케이스폴딩 교훈 적용 정합) |
| **C6 CHECK·FK** | PASS | badge_type='BOGUS' → `badges_type_check` 위반 거부; event_type='X' → `reputation_events_type_check` 위반 거부; user_id=999 → user_badges/reputation_events 양쪽 FK 위반 거부; valid 'REVIEWER' 대조군 accept |
## AC 정적 상세
- **AC-T2 (badge 테이블 3)**: PASS. `docs/badge-ddl.sql` `CREATE TABLE IF NOT EXISTS` 3 (badges/user_badges/reputation_events). `db/schema.sql` 동기 3 (line 1075/1100/1127). CHECK·FK·부분유니크 모두 동기.
- **AC-T3 (`${}` 0)**: PASS. 4매퍼(BadgesMapper, ReputationEventsMapper, UserBadgesMapper, UserBadgesQueryMapper) `rg '\${'` == 0.
- **AC-T4 (멱등 가드)**: PASS. UserBadgesMapper.insertIgnore `ON CONFLICT (user_id, badge_key) WHERE revoked_at IS NULL DO NOTHING`; ReputationEventsMapper.insertIgnore `ON CONFLICT (user_id, event_type, source_ref) WHERE source_ref IS NOT NULL DO NOTHING`. 부분유니크 타깃 정합(L2 C1/C3 가 실증).
- **AC-T5 (PermissionKeys + verifier)**: PASS. enum 4상수(GAME_JAM_MANAGE/POST_WRITE/CONTENT_MODERATE/BADGE_MANAGE), BADGE_MANAGE 존재. verifier values() 순회.
- **AC-T6 (event_type CHECK 정합)**: PASS(주의 1건). DDL CHECK IN 2종(REVIEW_WRITTEN/GAME_UPLOADED) == L2 C6 거부 동작 == 훅 2곳(GameReviewController.listReviews 경로 reputationService.record + GameController 게임업로드 경로 reputationService.record). 훅 카운트 2 정합. ⚠ 소스 트리에서 event_type 문자열 리터럴이 `"n"` 으로 표시됨(코드 난독화/표시 아티팩트 — BadgeKeys 도 `"n"`, PermissionGate→`ln` 동일 패턴). L1 347 GREEN + L2 CHECK 거부 동작으로 런타임 emit 값이 유효 enum 임을 간접 확인. concerns 에 기록.
- **AC-9 (N+1 배치)**: PASS. GameReviewController.listReviews line 115 에서 `userBadgesQueryMapper.listActiveBadgeKeysByUserIds(userIds)` 단 1회 호출(for 는 결과 row 순회이지 per-user 쿼리 아님). 테스트 line 475 `verify(..., times(1))` 검증, L1 GREEN.
- **AC-4/11 (BADGE_MANAGE 게이트)**: PASS. BadgeManageController grant(line 35)·revoke(line 76) 모두 진입부 순서: (1) `CsrfTokens.isValid` 실패 → 403, (2) 미인증 → 401, (3) `permissionGate.has(session, BADGE_MANAGE.name())` 실패 → 403. BadgeManageControllerTest 14 가 grant/revoke 각각 CSRF누락(403)/미인증(401)/미인가(403) 전 경로 커버, L1 GREEN.
## 실패 상세
없음.
## 종합 판정
overall: **pass**
verdict: **PASS**
rollback_signal: none
## Acceptance 매칭
| criterion | 매칭 전략 | 판정 |
|---|---|---|
| L1 BUILD/Tests/회귀0/N≈347 | L1 docker mvnw test | pass (347/0/0) |
| L1 W1 카탈로그 회귀(concern5) | PermissionGateTest/AdminConsoleControllerTest/JamRoleGateTest GREEN + 로그 "4 키" | pass (카운트 가정 FAIL 없음) |
| L1 contextLoads @MockBean 6 | BibimbapApplicationTests | pass |
| C1 자동부여 멱등 | L2 contract | pass |
| C2 회수후 재획득 | L2 contract | pass |
| C3 reputation dedupe | L2 contract | pass |
| C4 countActive 집계 | L2 contract | pass |
| C5 배치 alias camelCase | L2 contract (information_schema) | pass |
| C6 CHECK·FK | L2 contract | pass |
| AC-T2 badge 테이블 3 | static (DDL + schema.sql) | pass |
| AC-T3 매퍼 ${} 0 | static rg | pass |
| AC-T4 멱등 가드 | static + L2 C1/C3 | pass |
| AC-T5 PermissionKeys + verifier values() | static rg | pass |
| AC-T6 event_type CHECK 정합 | static + L2 C6 | pass (난독화 표시 주의 → concerns) |
| AC-9 N+1 배치 | static + L1 times(1) | pass |
| AC-4/11 BADGE_MANAGE 게이트 | static + L1 BadgeManageControllerTest 14 | pass |
| 프로필 myBadges / 게임카드 creator 배지 | (의도된 deferral) | N/A (FAIL 처리 안 함) |