bibimbap/.atp/work-session/20260623-104307/research/W2-W4-grounding.md

17 KiB

phase agent agent_version generated_at session_id concerns concerns_checked source_confidence workers_spawned
research research-advisor 2 2026-06-23T10:43:00+09:00 20260623-104307
low source confidence — game_likes 테이블/제약(R-D): db/schema.sql 의 game_likes 는 schema.sql:8,248 에서 '비권위 복원본'으로 명시됨. UNIQUE(game_id,user_key) 부재·user_key=varchar 는 schema.sql 근거이나 운영 DB 실제 제약은 미확인. W2-5 인기투표 1인1표 전제로 승격 전 운영 DB 제약 검증 필요.
scope-axis 미수렴(W2-2): user_permissions 가 (user_id, permission_key) 전역 모델임은 code-fact 로 확정되나, '잼 회차별 역할'이 현 모델에 안 얹힌다는 판단은 해석. design 단계에서 스코프 확장 vs 별도 테이블 결정 필요 — 본 research 는 결정하지 않음.
true mixed 6

W2(게임잼 6서브) + W4(유저 배지/평판) 골자 grounding — code surface

조사만. 설계·구현·미결질문 해소 없음. 각 사실은 code-fact(파일:라인) / 해석(설계판단) 으로 태깅.

주제

"남은 W 워크스트림" 설계 세션 진입 전, W2/W4 골자 카탈로그 "결합/의존" 절을 code-fact 정확도로 채우기 위한 코드 surface grounding. 특히 ★STALE1(W1 RBAC), ★STALE2(W3-2 리뷰) 의 실재 확정.


★요약 (최상단 — 설계 전제)

★STALE1 — W1 RBAC: 확정·실재 (code-fact)

W1 RBAC 인터셉터/권한게이트/카탈로그가 전부 실재하며 동작 surface 가 명확하다.

  • PermissionGate(has/canModerate/isAuthenticated/isAdmin/require), RbacInterceptor(401 vs 403 분기), PermissionKeys(enum 3종), Roles(ADMIN/SUBADMIN/USER), CsrfTokens, InterceptorConfig, PermissionCatalogVerifier, UserPermissionsMapper 전부 존재.
  • users 에 role(CHECK ADMIN/SUBADMIN/USER) + permissions_epoch 컬럼 실재. 세션 epoch 전파(refreshIfStale) 동작.
  • 단, enforcement 갭(code-fact): 인터셉터는 /admin/** 에만 등록 + isAdmin 만 검사(SUBADMIN/권한키 분기 없음). 권한 키 CONTENT_MODERATE 만 컨트롤러(canModify)에서 소비되고, POST_WRITE·GAME_JAM_MANAGE 는 enum 선언만 있고 소비처 0건.

★STALE2 — W3-2 리뷰: 확정·실재 (code-fact)

game_reviews + game_review_axes + game_review_stats(VIEW) + 4개 매퍼 전부 실재.

  • 평점은 하이브리드: 단일 overall(game_reviews.rating smallint CHECK 1~5) + 6축 다축(game_review_axes, 리뷰당 6행, axis_key 6종). is_rating_manual 로 overall 출처 구분(true=직접, false=6축 자동평균).
  • game_review_stats테이블이 아니라 VIEW(읽기전용 집계, 9컬럼: avg_rating/review_count/6축평균). 커밋 21892c8 = 이 뷰의 fan-out 버그 + 매퍼 alias 케이스폴딩 버그 2건 수정.

★W2-2 권한 스코프 갭 (해석 — code 근거 제시)

  • code-fact: user_permissions 컬럼 = id/user_id/permission_key/granted_by/created_at. UNIQUE = (user_id, permission_key). scope/resource_id/jam_id 컬럼 부재. PermissionGate.has(session, permissionKey) 도 리소스 인자 없음 → 권한은 전역(글로벌) 모델.
  • 해석: W2-2 "심사위원 = 잼 회차별 역할" 은 현 글로벌 권한 모델 위에 그대로 안 얹힌다(스코프 갭). 잼별 권한을 표현하려면 스키마·게이트 시그니처 확장 또는 별도 잼-역할 테이블이 필요 — 이건 설계 결정, research 는 결정 안 함.

신규성 확정 (code-fact)

  • jams 테이블 전무: schema.sql / docs/*.sql / src 전체 jam_id·CREATE TABLE jam 0 hit (PermissionKeys 의 "게임잼" 은 한글 라벨일 뿐). → W2 잼 엔티티 전부 신규.
  • 배지/평판 구조 전무: badge/reputation/trust_level/karma/honor 도메인 테이블·컬럼·클래스 0 hit (hit 는 전부 JSP CSS 클래스명). → W4 전부 신규.

버킷별 발견

R-A. W1 RBAC 소비 surface (★STALE1)

신뢰도: 확인됨 (전 항목 1차 파일:라인 확인)

항목 사실 근거 태그
PermissionGate 메서드 boolean has(HttpSession, String) / canModerate(HttpSession)→has(CONTENT_MODERATE) / isAuthenticated / isAdmin / require(이름과 달리 throw 없이 has 위임) PermissionGate.java:22,40,47,55,64 code-fact
epoch 동기화 refreshIfStaleusersMapper.getPermissionsEpoch 와 세션 permsEpoch 비교 후 role/permissions/permsEpoch 재설정 PermissionGate.java:86-104 code-fact
PermissionKeys enum 3종: GAME_JAM_MANAGE/POST_WRITE/CONTENT_MODERATE (그 외 없음) PermissionKeys.java:4-6 code-fact
Roles String 상수 ADMIN/SUBADMIN/USER (enum 아님) Roles.java:5-7 code-fact
RbacInterceptor preHandle: 미인증→page면 /login redirect 아니면 401 JSON / 미인가(!isAdmin)→403 JSON RbacInterceptor.java:20-40 code-fact
인터셉터 등록 addPathPatterns("/admin/**") 단 하나, exclude 없음 InterceptorConfig.java:18-20 code-fact
PermissionCatalogVerifier 부팅 시 enum→DB upsert 멱등 시드 + DB-only 키 log.warn PermissionCatalogVerifier.java:28-48 code-fact
UserPermissionsMapper listKeys/exists/insert/delete/deleteAllByUser, 전부 #{} UserPermissionsMapper.java:14-48 code-fact
게이트 소비처 CONTENT_MODERATE(canModerate) 만 소비: GameCommentController.java:203, GameReviewController.java:422. POST_WRITE·GAME_JAM_MANAGE 소비처 0건 rg POST_WRITE|GAME_JAM_MANAGE → PermissionKeys.java 만 hit code-fact
user_permissions scope 컬럼 id/user_id/permission_key/granted_by/created_at, UNIQUE(user_id,permission_key), scope 컬럼 부재 → 전역 권한 db/schema.sql:326-347, docs/rbac-ddl.sql:37-58 code-fact
  • W2 결합 함의(1줄): W2-2 잼 회차별 심사위원 역할은 현 전역 권한 모델 + 미연결 GAME_JAM_MANAGE 키 위에서 스코프 확장 결정이 선행돼야 한다(해석).

R-B. 리뷰/평점 surface (★STALE2)

신뢰도: 확인됨

항목 사실 근거 태그
game_reviews rating smallint CHECK 1~5, body text, user_id nullable, nickname 스냅샷, is_rating_manual(true=직접/false=6축자동평균) db/schema.sql:130-148,162-164 code-fact
활성 유니크 ux_game_reviews_game_user_active (game_id,user_id) WHERE is_delete IS NOT TRUE → 게임당 유저 1리뷰 db/schema.sql:145-146 code-fact
game_review_axes review_id/axis_key(varchar20)/score(smallint CHECK 1~5), axis_key CHECK 6종(immersion/creativity/controls/completeness/sound/visual), UNIQUE(review_id,axis_key) → 리뷰당 6행 db/schema.sql:168-186,201 code-fact
game_review_stats VIEW(테이블 아님). 9컬럼: game_id/avg_rating/review_count/avg_immersion..avg_visual. fan-out 방지: 축평균 서브쿼리 game단위 선집계 후 LEFT JOIN db/schema.sql:212-243 code-fact
StatsMapper alias 리뷰단위 3컬럼 큰따옴표 alias(AS "avgRating" 등, case 보존), 6축은 따옴표 없는 단축명 GameReviewStatsMapper.java:13-30 code-fact
AxesMapper addReviewAxes(배치INSERT)/deleteReviewAxes/listByReviewIds, 수정 시 전체삭제→재삽입 GameReviewAxesMapper.java:17-28 code-fact
컨트롤러 GET/POST/PUT/DELETE /game/{id}/reviews, summary 는 stats 뷰 사용, rating 미입력 시 6축평균 반올림 GameReviewController.java:38,159-170,375-394 code-fact
커밋 21892c8 BUG-1 뷰 fan-out(COUNT/AVG 왜곡) + BUG-2 매퍼 alias 케이스폴딩(summary 항상 null) 수정 docs/changes/2026-06-22-...enhancement.md:300-311 (1차 changes 문서 교차검증) code-fact
game_comments 리뷰와 별개 테이블(rating 없음), 동일 game_id 공유뿐 FK 없음 GameCommentsMapper.java code-fact
  • W2/W4 결합 함의(1줄): W2-3 동결계약·W2-6 유저평점·W3-1 정렬·W4 배지가 모두 이 하이브리드(overall+6축) + VIEW 집계를 소비 — 단일 평균 가정 금지, axes 0행 리뷰는 6축평균 NULL(백필 미적용 code-fact, enhancement.md:322).

R-C. games 엔티티 surface (W2-1)

신뢰도: 확인됨

항목 사실 근거 태그
games 컬럼(13) id/user_id(FK users)/name/creator_note/git_url/webgl_path/thumbnail_url/like_count(int default0)/is_visible/sort_order/created_at/updated_at/is_delete db/schema.sql:88-100 code-fact
jam_id 부재 (games 13컬럼에 없음, jam_id grep 0 hit) db/schema.sql:88-100 code-fact
조회/방문수 visit_count/view_count 부재. 인기지표는 like_count 만 db/schema.sql:88-100 code-fact
팀 구분 team_id 부재. user_id 단일 FK → 1게임=1유저 db/schema.sql:89 code-fact
getVisibleGames is_visible IS NOT FALSE AND is_delete IS NOT TRUE, ORDER BY sort_order ASC, created_at DESC, id DESC GamesMapper.java:62 code-fact
searchVisibleGames ILIKE 부분일치 3컬럼(name/users.display_name/creator_note) GamesMapper.java:85-87 code-fact
생성 경로 메타: GameController @PostMapping("/game/new") @Transactional createGame; 파일: GameUploadController uploadGameFiles/webgl-zip/thumbnail. jam 파라미터 없음 GameController.java:50, GameUploadController.java:52 code-fact
삭제 연쇄 GamesMapper: softDeleteGameComments/softDeleteGameReviews/deleteGameLikes(hard)/softDeleteGame GamesMapper.java:173-199 code-fact
  • W2 결합 함의(1줄): W2-1 잼 엔티티가 games 를 재사용하려면 jam 연결(jam_id 또는 조인테이블)·방문수·팀출품 구분이 모두 신규 추가 대상(해석: 재사용 가능하나 확장 필요).

R-D. game_likes surface (W2-5)

신뢰도: 추정 (테이블/매퍼 실재는 직접 1차 확인 = 확인됨, 그러나 제약은 비권위 복원본 근거)

항목 사실 근거 태그
GameLikesMapper 실재 getGameLike(id)/addGameLike(INSERT)/updateGameLike(id기준). DELETE·토글·(game_id,user_key)조회 메서드 없음 GameLikesMapper.java:13-43 (advisor 직접 1차 확인) code-fact
game_likes 테이블 컬럼 id(PK)/game_id(FK games)/user_key varchar(200)/created_at. PRIMARY KEY(id) 만 db/schema.sql:251-257 (advisor 직접 1차 확인) code-fact
1인1표 UNIQUE (game_id,user_key) UNIQUE 부재 (schema.sql 상). 비권위 복원본이라 운영 DB 제약 미확인 db/schema.sql:8,248,251-257 추정
user_key 의미 user_id(bigint) 아님 — varchar 식별자. 채움값(로그인 user_id vs 익명)은 호출부 영역 미확인 db/schema.sql:254 code-fact(컬럼)/미확인(값)
토글/카운터 매퍼에 토글·games.like_count 갱신 SQL 없음. 게임 삭제 시 GamesMapper.deleteGameLikes hard delete 만 GameLikesMapper.java / GamesMapper.java:186-189, GameController.java:261, seed-dev-teardown.sql:25 code-fact
  • W2 결합 함의(1줄): W2-5 인기투표는 game_likes 와 별개로 봐야 하며, game_likes 자체가 1인1표 미보장·user_key varchar·토글 부재 상태라 인기투표 1인1표 전제는 game_likes 재활용으로 자동 충족되지 않음(해석; 운영 DB 제약 검증이 concern).

R-D worker 중간 출력 정정: worker 의 첫 응답은 "GameLikesMapper/game_likes 부재"였으나 오류. advisor 가 1차 직접 확인(ls, rg game_likes db/schema.sql)으로 둘 다 실재 확정. worker 최종 응답도 실재로 정정됨. 이 문서는 직접 확인 결과를 채택.

R-E. users/identity + 배지 surface (W4)

신뢰도: 확인됨

항목 사실 근거 태그
users 컬럼 id/display_name/canonical_email/avatar_url/role(default USER, CHECK ADMIN/SUBADMIN/USER)/status/last_login_at/created_at/updated_at/is_delete + permissions_epoch(bigint default0) db/schema.sql:31-56 code-fact
role/epoch 출처 role·permissions_epoch 는 W1 RBAC 산출물(W4 신규 아님). perms_epoch 명칭은 0 hit db/schema.sql:48-56 code-fact
UserData(10필드) id/displayName/canonicalEmail/avatarUrl/role/permissionsEpoch/status/lastLoginAt/createdAt/updatedAt (is_delete 미매핑) UserData.java:7-16 code-fact
UsersMapper getUser/addUser/updateUser/getPermissionsEpoch/bumpPermissionsEpoch/updateRole/listOperators UsersMapper.java:17-104 code-fact
user_auth_identities provider/provider_user_id/email/password_hash/display_name/avatar_url, users 1:N, active-unique(provider,provider_user_id) db/schema.sql:62-81 code-fact
표시명 출처 users.display_name 단일 출처(nickname 은 game_comments/리뷰 스냅샷이지 users 컬럼 아님). 세션 attr displayName db/schema.sql:32, UserController.java:511 code-fact
배지/평판 badge/reputation/trust_level/karma/honor 도메인 테이블·컬럼·클래스 0 hit(hit 는 전부 JSP CSS) rg 0 hit (db/docs/src) code-fact(부재)
  • W4 결합 함의(1줄): W4 배지/평판은 전부 신규(스토리지 0). 단 평판 신호의 원천(리뷰=R-B 6축, 좋아요=R-D game_likes, 역할=R-A role)은 모두 실재 — 신규 배지 테이블이 이들을 집계 소비하는 구조(해석).

R-F. 관례/DDL 워크플로 (전 W 공통)

신뢰도: 확인됨

항목 사실 근거 태그
게시판 패턴 RecruitPostsMapper: get/getVisible(users JOIN)/add(useGeneratedKeys)/nextSortOrder. 페이징 없음(전건), ORDER BY sort_order ASC,created_at DESC,id DESC RecruitPostsMapper.java:70,107,110 code-fact
컨트롤러 혼합 읽기=JSP 뷰이름 반환(return "recruit-list"), 상태변경 POST=ResponseEntity JSON + CsrfTokens.isValid + 세션 userId + 화이트리스트 RecruitController.java:36,65,145 code-fact
POST 권한게이트 RecruitController POST /recruit/new 는 CSRF+세션userId만, PermissionGate/POST_WRITE 미적용 → 작성은 모든 로그인 유저 개방 RecruitController.java:65,159 (advisor 직접 교차확인) code-fact
로그인 세션 attr id/userId/displayName/email/avatarUrl/role/status/authProvider/authIdentityId/lastLoginAt/account(Map)/permissions(Set)/permsEpoch(long). changeSessionId()로 세션고정방어 UserController.java(controller/api/):166,508-537 code-fact
DDL 권위 권위 원본 = docs/*-ddl.sql. apply-local-ddl.sh 가 docs/*-ddl.sql glob 순(알파벳) 멱등 적용, ON_ERROR_STOP, search_path=dev. schema.sql = 컨테이너 최초1회 부트스트랩(신규테이블은 docs 동기, legacy users/games/game_comments/game_likes 는 비권위 복원본) apply-local-ddl.sh:4-64, schema.sql:5-18,45,128,261 code-fact
JSP/MyBatis 관례 views/ 평면배치 kebab-case, 컨트롤러는 확장자없는 뷰이름 반환. 매퍼 전부 #{}, ${} 회피(주석 명시). 큰따옴표 alias 는 일반매퍼 0 hit — snake→camel 직접 alias(r.created_at AS createdAt)가 표준, 큰따옴표는 집계뷰 case-folding 회피용만 GameReviewsMapper.java:21-29,92; RecruitPostsMapper.java:34 code-fact
  • 전 W 결합 함의(1줄): W2 잼 목록/상세·W2/W3 게시판류는 RecruitController 패턴(읽기=JSP뷰, 쓰기=JSON+CSRF) 선례를 따르고, 신규 DDL 은 docs/*-ddl.sql 을 권위로 두는 선례(W1-design 의 docs/rbac-ddl.sql 권위와 동일)를 따른다(해석).

종합 판단

상위 패턴

  1. 권한 카탈로그 vs enforcement 갭 (code-fact): RBAC 인프라(게이트/인터셉터/카탈로그/epoch)는 완비됐으나, 실제 enforcement 는 /admin/** ADMIN 게이트 + CONTENT_MODERATE canModify 2곳뿐. POST_WRITE·GAME_JAM_MANAGE 는 선언만 있고 미연결 → W2(잼관리)·W3(포스팅) 가 이 키를 enforcement 에 연결하는 작업이 신규.
  2. 잼·배지 스토리지 전무 (code-fact): jams 테이블 0, 배지/평판 0 → W2 엔티티·W4 도메인은 전부 신규 DDL. 단 평점(하이브리드 6축)·좋아요·역할 등 소비 원천은 실재.
  3. 권위 DDL = docs/*-ddl.sql (code-fact): 신규 W2/W4 테이블도 docs/*-ddl.sql 권위 + schema.sql 동기 + apply-local-ddl.sh 멱등 적용 선례를 따른다.

충돌·갭

  • 충돌 1 (해소됨): R-D worker 중간 출력이 game_likes 부재라 했으나 advisor 1차 직접 확인으로 실재 확정. 이 문서는 실재를 채택.
  • 갭 1 (해석, W2-2): 전역 권한 모델 ↔ 잼 회차별 역할 스코프 미수렴 → design 결정 필요 (concern 기록).
  • 갭 2 (추정, W2-5): game_likes 1인1표 제약은 비권위 복원본 근거뿐 → 운영 DB 제약 검증 필요 (concern 기록).

권위 격상 전 검증 필요 항목 (source_confidence: mixed 사유)

  • game_likes UNIQUE(game_id,user_key) 부재 → 추정. 운영 DB 제약 확인 전 W2-5 1인1표 전제로 승격 금지.
  • game_likes user_key 채움값(로그인 user_id vs 익명) → 미확인 (호출부 미조사).

미해결 (research 로도 미해소 — 골자 단계 비해소가 사용자 제약)

  • game_likes 운영 DB 실제 UNIQUE 제약 (schema.sql 비권위).
  • game_likes user_key 가 무엇으로 채워지는가 (서비스/컨트롤러 호출부 미조사).
  • W2-2 잼 스코프 권한을 현 모델 확장 vs 별도 테이블로 풀지 (설계 결정 — 의도적 비해소).