chore(atp): W3/W4 work-session 산출물 기록 (sid 20260629-100849)

잔여 5기능 구현 세션. report.md(invocations·decisions·verified_by_me·needs_user_verification·graph_refresh·회고) + 소유권맵·검증·문서화 산출물.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3FeMrbtxfTScjrwUukyHD
This commit is contained in:
이정수 2026-06-29 12:54:50 +09:00
parent 029b6996d4
commit af298ce8a0
9 changed files with 681 additions and 0 deletions

View File

@ -0,0 +1,49 @@
---
phase: documentation
agent: documentation-advisor
agent_version: 1
generated_at: 2026-06-29T15:10:00+09:00
concerns:
- "design-advisor 가 이번 세션 skip(report.md Advisor Invocation Decision Log) — architecture/ 갱신 주체 부재. 5기능은 신규 런타임 동작 변경(changes/)이 주이며 시스템 경계·레이어 재정의는 아니라 architecture/ 신규문서 불요로 판단. 문서화는 changes/ + work-log 중심 운영."
- "docs/graph/ 무접촉(graphify-update-advisor 영역, 병렬 진행). report.md graph_refresh=fully-stale 는 해당 advisor 가 처리."
- "보안 체크리스트(security/security-remediation-checklist.md) 무수정 — 본 작업 SSRF/zip-slip 신규 게이트는 기존 B1~B4 항목과 직접 매핑 아님. changes 문서 §보안 + needs_user_verification 에 file 근거 동반 기록으로 갈음. 별도 security/ 기준 문서화는 후속 필요 항목으로 이관."
- "ADR 후보 검토 완료 — option-b hostname-connect SSRF 연결방식·평가단위 자연키 등은 이미 W2 단계/설계 단계에서 확정된 결정이며, 이번 세션 신규 '되돌리기 어려운' 아키텍처 결정은 없음(설계 단계 audit PASS 산출물 소비). ADR 신규 불요."
concerns_checked: true
---
# 문서화 보고
세션: 20260629-100849 (W3 잔여 + W4 5기능 구현, 5 feat 35f1dc3~305cc73)
호출 의도: final (구현·검증·커밋 완료 후 결과 기록)
## 작성/수정된 문서
| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
|---|---|---|---|---|
| docs/changes/2026-06-29-w3-w4-features.md | changes | 신규 | changes/index.md (추가) | ← work-log 구현추적·changes/index. → work-log 요약·이전 W2 changes(related_prev_change + 본문 §개요)·verification.md·report.md·security-checklist |
| docs/changes/index.md | changes | 수정(링크 추가) | (자기) | 신규 changes 문서 |
| docs/work-log/2026-06-23-w2-w4-full-design-summary.md | work-log | 수정(구현추적 갱신) | n/a | → 신규 changes 문서(교차링크). "W3(잔여)·W4 설계만(미구현)" → 완료로 갱신 + "풀설계 11기능 전부 구현 완료" 한 줄 |
## 의사결정 기록 위치
- **5기능 런타임 변경 이력**`docs/changes/2026-06-29-w3-w4-features.md` (DDL 5종·컨트롤러·매퍼·보안 게이트·커밋 해시 매핑·검증 결과·needs_user_verification 전부 포함).
- **설계→구현 추적성**`docs/work-log/2026-06-23-w2-w4-full-design-summary.md` §구현 추적 (풀설계 11기능 완결 + changes 교차링크).
- **세션 결정/검증 1차 근거**`.atp/work-session/20260629-100849/report.md` (Decisions·verified_by_me·open_items) + `verification.md` (W4 verification + 직전 W 검증은 per-W) + `implementation/ownership*.md` (파일 소유권·편차).
- **카테고리 선택 근거**: 5기능 모두 코드/DB/런타임 동작 실제 변경 → `changes/`(분류 기준 §"changes 를 써도 되는 경우" 충족). 단일 changes 문서로 묶음(W2 선례 2026-06-24-w2-jam-platform.md 형식 답습 — 워크스트림 단위 통합 changelog).
## 추후 문서화가 필요한 항목
- **실 dev DB DDL 8종 일괄 적용 절차** — 적용 시점에 maintenance/ 운영 절차 문서 또는 changes 후속 한 줄(현재는 changes §미적용/이월에 needs_user_verification 으로 기록). `db/apply-local-ddl.sh` 기반.
- **W4 게임카드 creator 배지 후속 구현** — WebMvcController/SearchController 수정 후 별도 changes(또는 본 changes 보강). 현재 deferral 사유는 changes §미적용/이월 + report.md open_items 에 기록.
- **W3-5 배포 하드닝(저장루트 static 트리 밖 이전 + gameRoot 경로 정합)** — config 결정 동반 작업. 결정 시 ADR 후보(저장 경로 구조)일 수 있음. 현재는 changes needs_user_verification.
- **SSRF/zip-slip 보안 기준 문서** — option-b hostname-connect 방어 모델·zip-slip canonical 경계를 security/ 기준 문서로 승격할지 검토(현재 changes §보안에만 요약). adversarial 감사 산출 가치 보존 차원.
- **docs/graph/ 재생성** — graphify-update-advisor 가 `/graphify src/` + `/graphify docs/` + index.md 메타(source_commit→305cc73) 갱신 예정(본 advisor 영역 아님).
## 자가 검증 (프로토콜 §11.2)
1. 산출물 위치: `.atp/work-session/20260629-100849/documentation.md` 존재 ✅. 신규 문서(changes) 생성 → changes/index.md 링크 추가 완료 ✅.
2. frontmatter 필수 필드: phase·agent·agent_version·generated_at·concerns·concerns_checked 전부 포함 ✅.
3. concerns 의도적 검토 완료 ✅ (design skip·graph 무접촉·security 무수정·ADR 불요 4건 명시).
4. backport: N/A (소비 프로젝트 dogfooding 역이식 아님 — bibimbap 자체 기능 구현 기록).
checklist_passed: true

View File

@ -0,0 +1,42 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T12:10:00+09:00
workstream: W3-3-posting-board-fix1
concerns: []
concerns_checked: true
planned_workers: 0
actual_workers: 0
---
# 파일 소유권 맵 (W3-3 fix1 — backward 보정)
> 발원 = 구현단계(§2.6). verification FAIL + adversarial SSRF 감사 결함 전수 보정.
> 보정 대상 파일이 2개(SsrfSafeFetcher / FeedParser, 보안핵심 + 강결합)이고 두 파일 모두
> 내부 로직이 상호 일관성을 요구하는 보안 수정이라 advisor 직접 편집을 선택(아래 계량).
> 정상 산출물(XSS sanitize·매퍼·컨트롤러·DDL·OgPreviewService) 불변.
## 변경 파일 (결함번호 매핑)
| 파일 | 변경 유형 | 결함 | 담당 |
|---|---|---|---|
| src/main/java/com/pandoli365/bibimbap/security/SsrfSafeFetcher.java | modify | 결함1·2·3 | advisor 직접 |
| src/main/java/com/pandoli365/bibimbap/service/FeedParser.java | modify | 결함4 | advisor 직접 |
| src/test/java/com/pandoli365/bibimbap/security/SsrfSafeFetcherTest.java | modify | 결함5 (+2·3 커버리지) | advisor 직접 |
| Dockerfile | 변경 없음 | 결함1 (불요) | - |
| pom.xml | 변경 없음 | 결함1 (불요) | - |
> 결함4 의 FeedParserTest 픽스처는 DOCTYPE 미포함(정상 RSS) — 픽스처 정정 불요.
> "Content is not allowed in prolog" / "DOCTYPE is disallowed" stderr 는 broken-input/XXE
> 테스트의 정상(graceful empty) 부산물 로그였음. 유일 Failure 는 pubDate→publishedAt null.
## 계량 (advisor 직접 선택 근거)
- planned_workers: 0 / actual_workers: 0 (worker 미spawn)
- 사유: 파일 수 3 < 8 임계 + 결함1/2/3 동일 파일(SsrfSafeFetcher) 강결합 보안 로직
(연결 메커니즘 변경 ↔ 포트 allowlist ↔ IP 차단대역 ↔ 테스트 훅)이라 단일 손에서
일관성 보장 필요 → 병렬화 이득 0, 충돌 위험만 증가. 보안핵심은 분할보다 일관 편집 우선.
## 충돌 회피
- 각 파일 정확히 1 손(advisor). 교차 소유 0.
- 다른 W·정상 산출물 미접근.

View File

@ -0,0 +1,68 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T10:30:00+09:00
workstream: W3-3-posting-board
---
# 파일 소유권 맵 (W3-3 포스팅 보드)
> 불변식: 동일 파일 정확히 1 worker. 파일 충돌 0. SSRF 보안핵심 단독 worker.
> Java 컴파일은 advisor 사후/verification 영역 — worker 는 파일 작성만. 시그니처는 설계서 확정값이라 타 클래스 참조 타입명 정확. 따라서 파일 독립이면 병렬 가능.
## Wave 1 (병렬 spawn, 파일 독립 — 동시 최대 6 준수)
| 파일 | 담당 worker | worker id | U-태그 | 변경 |
|---|---|---|---|---|
| docs/board-ddl.sql | migration-writer | w-schema | U-SCHEMA | create |
| db/schema.sql (append only, line 908 직후) | migration-writer | w-schema | U-SCHEMA | modify |
| data/PostData.java | code-writer | w-domain | U-DOMAIN | create |
| data/PostCategoryData.java | code-writer | w-domain | U-DOMAIN | create |
| data/UnityFeedSourceData.java | code-writer | w-domain | U-DOMAIN | create |
| data/UnityFeedItemData.java | code-writer | w-domain | U-DOMAIN | create |
| mapper/PostsMapper.java | code-writer | w-domain | U-DOMAIN | create |
| mapper/PostCategoriesMapper.java | code-writer | w-domain | U-DOMAIN | create |
| mapper/UnityFeedSourcesMapper.java | code-writer | w-domain | U-DOMAIN | create |
| mapper/UnityFeedItemsMapper.java | code-writer | w-domain | U-DOMAIN | create |
| pom.xml (jsoup 1.17.2 + commonmark 0.22.0) | code-writer | w-markdown | U-MARKDOWN | modify |
| service/PostMarkdownService.java | code-writer | w-markdown | U-MARKDOWN | create |
| test/.../PostMarkdownServiceTest.java | code-writer | w-markdown | U-MARKDOWN | create |
| security/SsrfSafeFetcher.java | code-writer | w-ssrf (보안핵심 단독) | U-SSRF | create |
| service/OgPreviewService.java | code-writer | w-ssrf (보안핵심 단독) | U-SSRF | create |
| test/.../SsrfSafeFetcherTest.java | code-writer | w-ssrf (보안핵심 단독) | U-SSRF | create |
| service/FeedParser.java | code-writer | w-feed | U-FEED | create |
| service/UnityFeedPoller.java | code-writer | w-feed | U-FEED | create |
| config/SchedulingConfig.java | code-writer | w-feed | U-FEED | create |
| test/.../FeedParserTest.java | code-writer | w-feed | U-FEED | create |
| test/.../UnityFeedPollerTest.java | code-writer | w-feed | U-FEED | create |
| controller/PostAdminController.java | code-writer | w-admin | U-ADMIN-CTRL | create |
| controller/UnityFeedAdminController.java | code-writer | w-admin | U-ADMIN-CTRL | create |
| views/admin-post-categories.jsp | code-writer | w-admin | U-ADMIN-CTRL | create |
| views/admin-unity-feeds.jsp | code-writer | w-admin | U-ADMIN-CTRL | create |
## Wave 2 (Wave1 후 — U-POST-CTRL + 테스트 + MockBean)
| 파일 | 담당 worker | worker id | U-태그 | 변경 |
|---|---|---|---|---|
| controller/PostController.java | code-writer | w-postctrl | U-POST-CTRL | create |
| views/posts-list.jsp | code-writer | w-postctrl | U-POST-CTRL | create |
| views/posts-detail.jsp | code-writer | w-postctrl | U-POST-CTRL | create |
| views/posts-form.jsp | code-writer | w-postctrl | U-POST-CTRL | create |
| views/header.jsp (append /posts 링크) | code-writer | w-postctrl | U-POST-CTRL | modify |
| test/.../PostControllerTest.java | code-writer | w-postctrl-test | (검증) | create |
| test/.../BibimbapApplicationTests.java (append @MockBean) | advisor 직접 | - | (검증) | modify |
## 계량 (최종)
- planned_workers: 8 (w-schema, w-domain, w-markdown, w-ssrf, w-feed, w-admin, w-postctrl, w-postctrl-test)
- actual_workers: 9 — 위 8 + JSP JSTL→scriptlet 변환 보정 worker 1 (계획 외 추가, 사유 아래)
- advisor 직접: BibimbapApplicationTests @MockBean append(병렬화 이득 0) + admin-unity-feeds JSP 잔존 점검(worker 가 최종 완료해 advisor 직접편집 불요로 귀결).
## 계획 외 추가 worker 사유 (JSP 변환 보정)
- U-ADMIN-CTRL / U-POST-CTRL worker 가 생성한 5개 JSP 가 `<%@ taglib c %>` + JSTL(`<c:out>` 등)을 사용했으나, 프로젝트에 JSTL 의존성이 전무(pom 0건 + ~/.m2 캐시 0건, 오프라인 빌드라 추가 불가)하여 런타임 JSP 변환 실패 위험.
- grounding 결과 기존 JSP 전부 scriptlet + HtmlUtils.htmlEscape 컨벤션. 설계 의도(출력 escape)는 동일 충족하되 메커니즘을 프로젝트 컨벤션으로 정렬하는 보정이 필요 → 단일 worker 로 5개 JSP 일괄 변환(일관성).
## 충돌 회피 근거
- db/schema.sql / header.jsp / BibimbapApplicationTests 는 기존+W3-1/4 보존 append-only.
- 각 파일 정확히 1 worker. 교차 소유 0.
- U-SSRF 단독 worker(보안핵심): SsrfSafeFetcher + OgPreviewService + Test 한 worker 격리.

View File

@ -0,0 +1,26 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T10:30:00+09:00
workstream: W3-4-main-hub (단계1+단계2 통합)
---
# W3-4 파일 소유권 맵 (충돌 0 — 1파일=1worker)
| 파일 | 담당 worker | worker id | 변경 유형 | H-* | 의존(시그니처 사전제공 → 병렬 가능) |
|---|---|---|---|---|---|
| docs/games-hub-ddl.sql | migration-writer | w-001 | create | H-MAPPER | - |
| db/schema.sql | migration-writer | w-001 | modify | H-MAPPER | games-hub-ddl 사본(같은 worker 동기) |
| src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java | code-writer | w-002 | modify(append) | H-MAPPER | - |
| src/main/java/com/pandoli365/bibimbap/mapper/JamsMapper.java | code-writer | w-003 | modify(append) | H-JAM | - |
| src/main/java/com/pandoli365/bibimbap/controller/WebMvcController.java | code-writer | w-004 | modify | H-CTRL+H-JAM | w-002/w-003 시그니처(프롬프트에 명시) |
| src/main/webapp/WEB-INF/views/index.jsp | code-writer | w-005 | modify | H-VIEW1+H-JAM | w-004 attr 이름(프롬프트에 명시) |
| src/test/java/com/pandoli365/bibimbap/controller/WebMvcControllerTest.java | code-writer | w-006 | create | (검증 산출물) | w-004 시그니처(프롬프트에 명시) |
# 불변식 점검
- 동일 파일 1 worker only: OK (WebMvcController·index.jsp 는 단계1+단계2 통합이라 각 1 worker 가 전 변경 담당 → split 충돌 회피)
- BibimbapApplicationTests.java: **변경 불요** (GamesMapper line44 + JamsMapper line89 @MockBean 기존 등록 — W2-1 재사용). worker 미할당.
- 시그니처 사전 제공으로 병렬 spawn 안전(파일 read 의존 0).
planned_workers: 6

View File

@ -0,0 +1,61 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T13:30:00+09:00
workstream: W3-5 Unity WebGL 업로드 보안 보강
---
# 파일 소유권 맵 — W3-5
설계: `.atp/work-session/20260623-104307/implementation/W3-5-upload-design.md`
파일영향맵 §파일영향맵 U-* 소유권 준수. 1파일 1worker 원칙(강결합 신규 파일군은 동일 worker 로 묶음).
## 의존 그래프 (병렬 wave)
- **Wave 1 (병렬, 독립)**: U-DDL · U-ZIPSEC · U-AUDIT-MAPPER
- **Wave 2 (Wave 1 완료 후)**: U-UPLOAD(ZipSec+Audit+Gate 의존) · U-LIFECYCLE(독립; GameController 단독)
- **Wave 3 (테스트, Wave 2 완료 후)**: TEST-ZIPSEC(U-ZIPSEC만 의존 — Wave 2와 병렬) · TEST-UPLOAD(U-UPLOAD 의존) · TEST-CTX(매퍼/서비스 빈 의존)
## 소유권 테이블
| 파일 | 담당 worker | worker id | 변경 유형 | U-태그 | 의존 |
|---|---|---|---|---|---|
| docs/game-upload-ddl.sql | migration-writer | w-001 | create | U-DDL | - |
| db/schema.sql (append) | migration-writer | w-001 | modify | U-DDL | - |
| security/ZipSecurity.java | code-writer | w-002 | create | U-ZIPSEC | ZipRejectException |
| security/ZipRejectException.java | code-writer | w-002 | create | U-ZIPSEC | - |
| mapper/GameUploadAuditMapper.java | code-writer | w-003 | create | U-AUDIT-MAPPER | GameUploadAudit, GameAssetOwner |
| data/GameUploadAudit.java | code-writer | w-003 | create | U-AUDIT-MAPPER | - |
| data/GameAssetOwner.java | code-writer | w-003 | create | U-AUDIT-MAPPER | - |
| controller/api/GameUploadController.java | code-writer | w-004 | modify | U-UPLOAD | w-002, w-003 |
| service/GameAssetCleanupService.java | code-writer | w-005 | create | U-LIFECYCLE | - |
| controller/api/GameController.java | code-writer | w-005 | modify | U-LIFECYCLE | GameAssetCleanupService |
| test/.../security/ZipSecurityTest.java | code-writer | w-006 | create | (검증) | w-002 |
| test/.../GameUploadControllerSecurityTest.java | code-writer | w-007 | create | (검증) | w-004 |
| test/.../BibimbapApplicationTests.java (append) | advisor 직접 | - | modify | (검증) | w-003, w-005 |
## 불변식 점검
- 동일 파일 단일 worker 할당: 확인 (GameController 는 w-005 단독, GameUploadController 는 w-004 단독).
- 강결합 신규 파일군 동일 worker: ZipSecurity+ZipRejectException(w-002), 매퍼+POJO 2종(w-003), Service+GameController(w-005).
- 의존 있는 건 순차 wave: Wave1 → Wave2 → Wave3.
- BibimbapApplicationTests append 는 단순 @MockBean 2줄 추가 — advisor 직접(병렬화 이득 없음, 다른 워커 산출물 보존하며 append).
## planned_workers: 7 (w-001 ~ w-007)
## actual_workers: 7 (전원 spawn — w-001 migration-writer, w-002~w-007 code-writer)
## 실제 spawn 결과
| worker id | subagent | 산출 | 상태 |
|---|---|---|---|
| w-001 | migration-writer | docs/game-upload-ddl.sql + db/schema.sql append | 완료 (reject_reason 11토큰, schema 1023줄 보존) |
| w-002 | code-writer | ZipSecurity.java + ZipRejectException.java | 완료 (enum 11, getCompressedSize 0, 3중상한, 심링크 전수) |
| w-003 | code-writer | GameUploadAuditMapper + GameUploadAudit + GameAssetOwner | 완료 (#{} only, 컬럼 8 정합, alias 정합) |
| w-004 | code-writer | GameUploadController 보강 | 완료 (게이트 순서, ATOMIC_MOVE, audit, dead code 제거) |
| w-005 | code-writer | GameAssetCleanupService + GameController 훅 | 완료 (UUID 검증+경계, gameRoot 미터치) |
| w-006 | code-writer | ZipSecurityTest (20 @Test) | 완료 (9 거부코드 + 통과 회귀, 심링크 가드) |
| w-007 | code-writer | GameUploadControllerSecurityTest (11 @Test) | 완료 (401/403/413/200/400 + CSRF 3핸들러) |
advisor 직접 처리(병렬화 이득 없는 단일 편집):
- BibimbapApplicationTests.java: @MockBean 2종(GameUploadAuditMapper, GameAssetCleanupService) append.
- ZipRejectException.java: serialVersionUID 1줄 추가([serial] lint 경고 제거).
- 빌드/타입/불변식 게이트(advisor 직접 bash): clean compile + test-compile + javac -Xlint:all(unused 0) + AC-T grep 정합.

View File

@ -0,0 +1,64 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T13:30:00+09:00
workstream: W4-유저 배지/평판
---
# W4 파일 소유권 맵 (충돌 0 — 1파일=1worker, 강결합 그룹만 묶음)
> 의존: B-SCHEMA → B-DOMAIN(data/enum/매퍼) → {B-SERVICE → B-HOOK/B-MANAGE/B-DISPLAY}. B-PERM 은 B-MANAGE 선행.
> 시그니처 사전 제공(설계 §신규 함수 시그니처)으로 wave 간 read 의존 제거 → wave 내 병렬 안전.
## Wave 1 (병렬 — 상호 독립)
| 파일 | worker | id | 유형 | B-* | 의존 |
|---|---|---|---|---|---|
| docs/badge-ddl.sql | migration-writer | w-001 | create | B-SCHEMA | - |
| db/schema.sql | migration-writer | w-001 | modify(append 말미) | B-SCHEMA | badge-ddl 사본(같은 worker 동기) |
| src/main/java/.../security/PermissionKeys.java | code-writer | w-002 | modify(enum +1) | B-PERM | - |
| src/main/java/.../badge/BadgeKeys.java | code-writer | w-003 | create | B-DOMAIN | - |
| src/main/java/.../data/BadgeData.java | code-writer | w-004 | create | B-DOMAIN | - |
| src/main/java/.../data/UserBadgeData.java | code-writer | w-004 | create | B-DOMAIN | - |
| src/main/java/.../data/UserBadgeKeyRow.java | code-writer | w-004 | create | B-DOMAIN | - |
| src/main/java/.../mapper/BadgesMapper.java | code-writer | w-005 | create | B-DOMAIN | - |
| src/main/java/.../mapper/UserBadgesMapper.java | code-writer | w-006 | create | B-DOMAIN | - |
| src/main/java/.../mapper/UserBadgesQueryMapper.java | code-writer | w-007 | create | B-DOMAIN | - |
| src/main/java/.../mapper/ReputationEventsMapper.java | code-writer | w-008 | create | B-DOMAIN | - |
## Wave 2 (B-DOMAIN 시그니처 의존 — 프롬프트에 시그니처 명시 → 병렬)
| 파일 | worker | id | 유형 | B-* | 의존 |
|---|---|---|---|---|---|
| src/main/java/.../badge/BadgeCatalogSeeder.java | code-writer | w-009 | create | B-DOMAIN | BadgeKeys(w-003)·BadgesMapper(w-005) 시그니처 |
| src/main/java/.../badge/ReputationService.java | code-writer | w-010 | create | B-SERVICE | ReputationEventsMapper(w-008)·BadgeService 시그니처 |
| src/main/java/.../badge/BadgeService.java | code-writer | w-011 | create | B-SERVICE | BadgeKeys·ReputationEventsMapper·UserBadgesMapper 시그니처 |
## Wave 3 (B-SERVICE 의존)
| 파일 | worker | id | 유형 | B-* | 의존 |
|---|---|---|---|---|---|
| src/main/java/.../controller/api/GameReviewController.java | code-writer | w-012 | modify(append 훅+배지부착) | B-HOOK+B-DISPLAY | ReputationService·UserBadgesQueryMapper 시그니처 |
| src/main/java/.../controller/api/GameController.java | code-writer | w-013 | modify(append 훅) | B-HOOK | ReputationService 시그니처 |
| src/main/java/.../controller/BadgeManageController.java | code-writer | w-014 | create | B-MANAGE | BadgeService·PermissionGate·PermissionKeys.BADGE_MANAGE 시그니처 |
| src/main/webapp/WEB-INF/views/game-detail.jsp | code-writer | w-015 | modify(JS 리뷰 배지 렌더) | B-DISPLAY | authorBadges 필드명(w-012) |
| src/main/webapp/WEB-INF/views/profile.jsp | code-writer | w-016 | modify(본인 배지 표시) | B-DISPLAY | - (profile 은 자체 조회 추가) |
## Wave 4 (검증 산출물 — 신규 컴포넌트 시그니처 의존)
| 파일 | worker | id | 유형 | 의존 |
|---|---|---|---|---|
| src/test/.../BibimbapApplicationTests.java | code-writer | w-017 | modify(@MockBean +5매퍼·+2서비스) | 신규 빈 이름 |
| src/test/.../service/BadgeServiceTest.java | code-writer | w-018 | create | BadgeService(w-011) |
| src/test/.../controller/BadgeManageControllerTest.java | code-writer | w-019 | create | BadgeManageController(w-014) |
| src/test/.../controller/api/GameReviewControllerTest.java | code-writer | w-020 | modify(훅 회귀 + 본흐름 불변) | GameReviewController(w-012) |
# 불변식 점검
- 동일 파일 1 worker only: OK.
- profile.jsp(w-016) ↔ game-detail.jsp(w-015): 별 파일, 충돌 0.
- GameReviewController(w-012): 훅(B-HOOK) + 배지부착(B-DISPLAY) 한 파일에 통합 → 1 worker 가 전 변경 담당(split 충돌 회피, ownership-w34 선례).
- BadgeData/UserBadgeData/UserBadgeKeyRow(w-004): 같은 data 디렉토리 POJO 3개 강결합 → 1 worker.
- 매퍼 4종은 각 1 worker(w-005~008): SQL 독립, 병렬 이득.
# 설계 편차(grounding 확정)
- **게임 카드 SSR 배지 부착 보류**: 설계 영향맵 line322 "GameController getVisibleGames creator 배지 부착" — 실제 getVisibleGames 호출지점 0(미사용 매퍼). 게임 카드는 WebMvcController hub(listVisibleKeyset/searchVisibleKeyset) + SearchController 가 SSR(index.jsp). 둘 다 W3-4/타 W 산출물 → 작업지시 §금지 "W3-1/3/4/5 산출물 접근 금지". 따라서 게임 카드 배지는 미구현, AC-8/AC-9 는 리뷰 작성자 배지(listReviews JSON+JS) + profile.jsp 로 충족. UserBadgesQueryMapper.listActiveBadgeKeysByUserIds 배치쿼리는 listReviews 에서 N+1 차단 입증.
- B-HOOK 의 GameController createGame 훅은 작업지시 "GameController append 허용" 명시 대상 → 진행.
planned_workers: 20

View File

@ -0,0 +1,53 @@
---
phase: implementation
agent: implementation-advisor
agent_version: 1
generated_at: 2026-06-29T10:30:00+09:00
---
# 파일 소유권 맵 — W3-1 게임 태그 + 검색 확장
설계: `.atp/work-session/20260623-104307/implementation/W3-1-tags-search-design.md`
concern 해소안(orchestrator 확정): TAG_MANAGE 미도입(CONTENT_MODERATE 재사용) / GameTagsMapper AND·OR 분리 / existsRecentView sinceTs 인자 제거 / GameData nullable 박스필드.
## Wave 1 (병렬, 파일 충돌 0 — 신규 인터페이스·POJO·스키마·JSP)
| 파일 | 담당 worker | worker id | 변경 | U-* | 의존 |
|---|---|---|---|---|---|
| docs/tag-ddl.sql | code-writer | w-001 | create | U-TAG-SCHEMA | - |
| db/schema.sql | code-writer | w-001 | modify | U-TAG-SCHEMA | - |
| data/TagData.java | code-writer | w-002 | create | U-TAG-DOMAIN | - |
| mapper/TagsMapper.java | code-writer | w-002 | create | U-TAG-DOMAIN | TagData |
| mapper/GameTagsMapper.java | code-writer | w-002 | create | U-TAG-DOMAIN | TagData |
| mapper/JamTagsMapper.java | code-writer | w-002 | create | U-TAG-DOMAIN | TagData |
| mapper/GameViewsMapper.java | code-writer | w-003 | create | U-VIEW | - |
| controller/api/GameController.java | code-writer | w-003 | modify | U-VIEW | GameViewsMapper |
| util/TagSanitizer.java | code-writer | w-004 | create | U-TAG-ADMIN | - |
| resources/banned-words.txt | code-writer | w-004 | create | U-TAG-ADMIN | - |
| webapp/WEB-INF/views/index.jsp | code-writer | w-005 | modify | U-JSP | - |
## Wave 2 (Wave1 인터페이스 타입 확정 후 — 컨트롤러·검색쿼리)
| 파일 | 담당 worker | worker id | 변경 | U-* | 의존 |
|---|---|---|---|---|---|
| data/GameData.java | code-writer | w-006 | modify | U-SEARCH | - |
| data/SearchCriteria.java | code-writer | w-006 | create | U-SEARCH | - |
| mapper/GamesMapper.java | code-writer | w-006 | modify | U-SEARCH | SearchCriteria, GameData |
| controller/SearchController.java | code-writer | w-006 | create | U-SEARCH | GamesMapper, GameTagsMapper, TagsMapper, SearchCriteria, TagSanitizer |
| controller/TagController.java | code-writer | w-007 | create | U-TAG-ADMIN | TagsMapper, GameTagsMapper, TagSanitizer, PermissionGate, CsrfTokens |
## Wave 3 (테스트 — 구현 산출물)
| 파일 | 담당 worker | worker id | 변경 | 의존 |
|---|---|---|---|---|
| test/.../BibimbapApplicationTests.java | code-writer | w-008 | modify | 신규 매퍼 4종 @MockBean |
| test/.../util/TagSanitizerTest.java | code-writer | w-008 | create | TagSanitizer |
| test/.../controller/TagControllerTest.java | code-writer | w-009 | create | TagController |
| test/.../controller/SearchControllerTest.java | code-writer | w-009 | create | SearchController |
## 불변식 점검
- 동일 파일 1 worker only: 충족(GameData=w-006, GamesMapper=w-006, GameController=w-003 단일).
- 의존 있는 파일 병렬 금지: Wave 분리(인터페이스→소비자→테스트). Wave 내 파일은 상호 독립 또는 동일 worker.
- 스키마: U-TAG-SCHEMA 격리(code-writer; migration-writer 는 ORM 마이그레이션 전용이나 본 프로젝트는 raw SQL DDL → code-writer 로 처리, DB 적용 금지).
</content>
</invoke>

View File

@ -0,0 +1,212 @@
---
schema_version: 2
sid: 20260629-100849
resumed_from: null
started_at: 2026-06-29T10:08:49+09:00
ended_at: 2026-06-29T12:55:00+09:00
branch: feat/v2
user_request: "크리티컬(W2 크리티컬패스) 닫혔으니 다른 작업(W2-W4 풀설계 미구현 잔여) 진행. 열린 질문 없도록 미리 파악 후 착수."
init_guard: "pass — docs/development/verification-strategies.md 존재"
migrate_check: "skip — CLAUDE.md 에 atp:migrate 마커 없음 (이미 .atp/ 경로)"
---
# ATP Work Session — 20260629-100849
## Summary
"크리티컬 닫힘" = W2 크리티컬 패스(W1→W2-1→W2-3 동결→W2-6) 완료(20260624 세션, 6 feat, 190/190 GREEN). "다른 작업" = W2-W4 풀설계 11기능 중 미구현 잔여 = **W3-1·W3-3·W3-4·W3-5·W4 (5기능)**. W3-2(댓글/리뷰)는 이미 완료(21892c8). 설계 오픈질문 0(_cross-consistency-audit PASS), 단 구현 전 선결정/선결조건 식별됨.
## Invocations
- (진행 전 — orchestrator 직접 discovery: work-session 20260624 report·풀설계 요약·구현추적·grounding)
- **W3-1**: implementation-advisor(9 worker, 신규13+수정6, ${} 0, @MockBean 4) → verification-advisor(PASS: L1 235/235 컨테이너·L2 C1~C5 throwaway DB·AC-1/2/4/5·보안VP) → **commit 35f1dc3**. baseline 190→235(+45 신규테스트). 편차 2건(insertTag int+generatedKeys, TagController GamesMapper 주입 소유자확인) 의미보존.
- **W3-4**: implementation-advisor(6 worker, 신규2+수정5, 단계1+2 통합, BibimbapApplicationTests 무변경) → verification-advisor(PASS: L1 243/243·L2 C1~C5 keyset 경계 무중복/무누락 throwaway DB·AC-T2/T3/T4·단계무결성·보안VP) → **commit e28fa60**. 편차 0(listActive limit=1 설계 축소후보 채택).
- **W3-3**: implementation-advisor(9 worker, 신규17 Java+5 JSP+5테스트+DDL+pom) → verification-advisor(FAIL: L1 283/2fail+1err) + **adversarial SSRF 감사**(CRITICAL: Host restricted-header→fetch vacuous + CGNAT 등 범위갭; XSS SOUND) → impl fix1(option-b hostname-connect 전환·범위확장·FeedParser) → 재검증(FAIL: FeedParser parsesRss20 잔존) → **orchestrator 마이크로수정**(FeedParser RFC1123 요일-날짜 불일치 robust) → L1 287/287 GREEN → **commit 6047a39**. 2중검증(verification+adversarial)이 vacuous SSRF(거짓통과) 포착 — 핵심 가치.
- **W3-5**: implementation-advisor(7 worker, 신규 8+수정 3, ZipSecurity 단독, dead code 제거) → verification-advisor(PASS: L1 318/318·L2 C1~C5 audit throwaway DB·AC-T1/3/4/5/6·getCompressedSize 0) + **adversarial zip-slip 감사(SOUND: 심링크 TOCTOU 구조불가·경로탈출/유니코드/zipbomb/LIKE주입 차단)****commit 08d191f**. 편차 0. LOW 2건(split 빈세그먼트·static 트리 .tmp 노출)=open_item.
## Advisor Invocation Decision Log
# 각 advisor 호출/스킵 판단 즉시 1줄 append
- advisor: requirements-advisor
decision: skip
rationale: '풀설계 11기능 산출 완료(오픈질문 0, W1-design 깊이). 요구 분해 불필요 — 잔여 5기능 설계도 확정적.'
checked_at: 2026-06-29T10:12:00+09:00
- advisor: research-advisor
decision: skip
rationale: '풀설계 grounding(.atp/.../20260623-104307/research) + _cross-consistency-audit PASS 존재. orchestrator discovery 로 baseline·prereq 직접 확인 완료.'
checked_at: 2026-06-29T10:12:00+09:00
- advisor: design-advisor
decision: skip
rationale: '잔여 5기능 풀설계 파일영향맵+계약+시퀀스+AC 확정적, audit PASS. orchestrator→implementation-advisor 직행.'
checked_at: 2026-06-29T10:12:00+09:00
- advisor: implementation-advisor (W3-1)
decision: call
rationale: '대규모 다파일(4매퍼+2컨트롤러+POJO+DDL+sanitizer+JSP+테스트) → worker 분산+소유권 충돌방지 필요.'
checked_at: 2026-06-29T10:25:00+09:00
- advisor: verification-advisor (W3-1)
decision: call
rationale: '코드 변경 → 스킵 불가. 신규 매퍼+VIEW alias 소비 → L1+L2(DB-방언 계약).'
checked_at: 2026-06-29T11:00:00+09:00
- advisor: implementation-advisor (W3-4)
decision: call
rationale: 'keyset 페이징 매퍼+컨트롤러+JSP+JamsMapper 확장+테스트 다파일. 단계1+2 통합(W2-1 jams·W3-1 검색라우트 충족).'
checked_at: 2026-06-29T11:10:00+09:00
- advisor: verification-advisor (W3-4)
decision: call
rationale: '코드 변경 → 스킵 불가. keyset 혼합방향 OR 분해 → L1+L2(경계 무중복/무누락 실측).'
checked_at: 2026-06-29T11:40:00+09:00
- advisor: implementation-advisor (W3-3)
decision: call
rationale: '최대 복잡(SSRF 9항목·md sanitize·6매퍼·3컨트롤러·4테이블·scheduling·pom 의존). worker 다분산+보안핵심 단독 worker 필요.'
checked_at: 2026-06-29T11:45:00+09:00
- advisor: verification-advisor (W3-3)
decision: call
rationale: '코드 변경 → 스킵 불가. SSRF/sanitize 보안핵심 → L1+L2+9항목 커버리지.'
checked_at: 2026-06-29T12:30:00+09:00
- advisor: adversarial-ssrf-audit (general-purpose)
decision: call
rationale: '보안핵심 — verification(테스트 실행)이 못 잡는 우회벡터(IP인코딩·IPv6·rebinding·redirect)를 코드추적으로 adversarial 탐색. perspective-diverse verify.'
checked_at: 2026-06-29T12:30:00+09:00
- advisor: implementation-advisor (W3-3 fix1)
decision: call
rationale: '§2.6 backward — 발원=구현. (1)CRITICAL Host restricted-header→fetch 전건 empty+테스트 vacuous (2)CGNAT 100.64/10 등 범위갭 (3)FeedParser RSS prolog (4)테스트 버그 (5)HTTPS SNI 동작우려. 보안핵심 전수 보정.'
checked_at: 2026-06-29T12:50:00+09:00
- advisor: verification-advisor (W3-3 fix1 재검증)
decision: call
rationale: '보정 후 재검증 의무. FAIL(FeedParser parsesRss20 잔존) → orchestrator 마이크로수정으로 해소.'
checked_at: 2026-06-29T13:15:00+09:00
- advisor: (orchestrator 직접 마이크로편집)
decision: call
rationale: 'FeedParser parseRfc1123 단일 메서드 — pubDate 요일-날짜 불일치(픽스처 Tue=실제 Wed) RFC1123 DOW 검증 거부. §5.1 마이크로편집 예외. 선행 요일토큰 제거 robust 파싱. 직접 L1 287/287 검증.'
checked_at: 2026-06-29T13:25:00+09:00
- advisor: implementation-advisor (W3-5)
decision: call
rationale: 'zip-slip/심링크/zipbomb/포맷/권한게이트/생명주기/감사 보안보강 다파일. ZipSecurity 보안핵심 단독 worker.'
checked_at: 2026-06-29T13:30:00+09:00
- advisor: verification-advisor (W3-5) + adversarial-zipslip-audit
decision: call
rationale: '보안핵심 zip-slip → 2중검증(L1+L2 + 우회벡터 코드추적). 감사 SOUND, 우회 0.'
checked_at: 2026-06-29T14:00:00+09:00
- advisor: implementation-advisor (W4)
decision: call
rationale: '배지/평판 3테이블+5매퍼+2서비스+2훅+매니지컨트롤러+표시+BADGE_MANAGE+시더 다파일. worker 분산.'
checked_at: 2026-06-29T14:10:00+09:00
## Decisions
- **범위 = 잔여 5기능 전부 순차** (AskUserQuestion). 순서 W3-1→W3-4→W3-3→W3-5→W4. 각 impl→verify→commit(롤백 경계, 중간 중단 가능).
- **검증 실행 경로 확정 (환경 핵심)**: 호스트 JDK 0(`/usr/bin/java`=실패 스텁). L1 = 컨테이너 실행:
`docker run --rm -v "$PWD":/work -w /work -v "$HOME/.m2":/root/.m2 --entrypoint sh eclipse-temurin:21-jdk -c './mvnw -o test'`
근거: ~/.m2(136M) 채워짐 → 오프라인 `-o` 해소, maven 3.9.14 dist 캐시됨. W2 세션이 쓴 동일 경로(메모리 java-crypto-verify-via-jdk-container-when-no-host-jdk). L2 = compose postgres:16 격리 throwaway(비파괴).
- W3-1 concern 해소(설계 명시): concern1 CONTENT_MODERATE 재사용(신규키 0), concern2/3 GameTags AND/OR 메서드 분리(listGameIdsByTagsAll/Any)+existsRecentView sinceTs 인자제거(dead param 회피), concern4 GameData nullable 박스필드 확장(소비처 영향 최소).
## 잔여 미구현 인벤토리 (풀설계 대비)
| W | 설계 문서 | 의존 | 신규 테이블/뷰 | 선결조건(구현 1보) | 보안핵심 |
|---|---|---|---|---|---|
| W3-1 태그+검색 | W3-1-tags-search-design.md | 독립 | tags, game_tags, jam_tags, game_views (+games.view_count) | DDL | sanitize/금칙어·CONTENT_MODERATE |
| W3-3 포스팅보드 | W3-3-posting-board-design.md | 독립 | post_categories, posts, unity_feed_sources, unity_feed_items | **pom: commonmark+jsoup** + @EnableScheduling(SchedulingConfig) | ★SSRF 9항목·md sanitize·POST_WRITE enforcement 연결 |
| W3-4 메인허브 | W3-4-main-hub-design.md | **W3-1 후** | (없음, GamesMapper 확장) | W3-1 선행 | 파라미터 #{} |
| W3-5 Unity업로드 | W3-5-upload-design.md | 독립 | game_upload_audit_log | DDL | ★zip-slip canonical+심링크거부+zipbomb·권한게이트 |
| W4 배지/평판 | W4-badges-design.md | 독립 | badges, user_badges, reputation_events | **PermissionKeys+BADGE_MANAGE(4번째)** + W1 카탈로그 전수 AC 재측정 | 부여/회수 CSRF+BADGE_MANAGE·/manage/badges 경로 |
## 선결정 (구현 전 — 설계가 이미 정한 사항이나 부수효과 플래그)
1. W3-3: pom.xml 신규 의존 commonmark+jsoup 추가(HTTP=spring-web RestClient 내장, 의존 0) + @EnableScheduling 신규 활성. → 구현 1보 = 의존추가+contextLoads.
2. W4: PermissionKeys enum +BADGE_MANAGE → W1 권한카탈로그 전수 AC 카운트 3→4 동반갱신(회귀 가드).
3. W3-4 는 W3-1 산출(GET /games/search 태그파라미터) 소비 → W3-1 선행 강제.
4. DB DDL: 신규 테이블 다수. W2 패턴 답습 = L2 격리 throwaway DB 비파괴 실측 + 실 dev DB 적용은 §6 수동게이트(needs_user_verification). 실 dev DB 는 W2 jam DDL 도 아직 미적용(carryover).
## user_signals
positive: []
negative: []
## verified_by_me
- **L1 (typecheck / unit+regression)**: 컨테이너 `./mvnw -o test`(eclipse-temurin:21-jdk + ~/.m2 마운트, 오프라인). baseline 190 → 최종 **347/347 GREEN**(누적 +157 신규테스트, 회귀 0). 각 W per-feature 검증: W3-1 235·W3-4 243·W3-3 287(fix1 포함)·W3-5 318·W4 347. contextLoads 전 신규 매퍼/서비스 @MockBean 전수.
- **L2 (contract-dev-db)**: pass — 각 W 격리 throwaway postgres:16 비파괴 실측(적용→실측→DROP). W3-1 alias 케이스폴딩 인용/비인용 대조·NULLS LAST·AND⊆OR·ILIKE·interval dedupe / W3-4 keyset 혼합방향 OR분해 경계 무중복·무누락·tie-break / W3-3 board keyset row-comparison·insertIgnoreDup ON CONFLICT·FK / W3-5 audit insert·outcome CHECK·FK·findGameByWebglUuid alias / W4 자동부여 멱등·회수후 재획득 부분유니크·dedupe·배치 alias·CHECK/FK. **실 dev DB(bibimbap) 무접촉**(전 W).
- **adversarial 보안 감사 2건**: W3-3 SSRF(CRITICAL Host restricted-header→vacuous 포착 → option-b 전환 + CGNAT 등 범위확장; XSS sanitize SOUND), W3-5 zip-slip(SOUND — 심링크 TOCTOU 구조불가·경로탈출/유니코드/zipbomb/LIKE주입 전수 차단).
- **로그 스캔**: clean(전 W. WARN = Mockito/ByteBuddy agent·Spring static trailing-slash 환경 noise).
## needs_user_verification
- **실 dev DB(bibimbap) DDL 미적용**`db/apply-local-ddl.sh` 로 신규 DDL 5종(tag-ddl/games-hub-ddl/board-ddl/game-upload-ddl/badge-ddl) + (W2 carryover) jam DDL 3종 일괄 적용 필요. L2 는 throwaway 로 통과, 실 dev DB 는 §6 수동게이트. L3 선행.
- **L3 런타임 스모크(WAR 기동 후)**: ① 검색 `/games/search` 태그·정렬키·잼필터 + 방문수 dedupe ② 허브 `/` keyset 더보기 + 진행중 잼 배너 ③ 포스팅 작성(POST_WRITE)/sanitize 렌더/OG 미리보기/유니티 피드 폴링 + **★실 HTTPS 외부 fetch 동작**(SsrfSafeFetcher option-b hostname-connect 실 TLS — 단위는 HTTP만) ④ 업로드 zip-slip/포맷 거부 + 정상 Unity 빌드 + replaceUuid 교체 ⑤ 배지 자동부여(리뷰10/업로드3)·수동 grant/revoke(BADGE_MANAGE)·리뷰작성자 칩.
- **W3-3 SSRF 하드닝(선택)**: option-b 잔여 rebinding TOCTOU → `networkaddress.cache.ttl` 설정 검토.
## graph_refresh
- 판정: **fully-stale** (graph-refresh-checker). 기준 a74bf74(W2)→HEAD 305cc73, 9커밋차. src 77파일(+10163/-103, 신규63), docs 10파일(신규6 DDL5). **no-defer 처리 완료**: orchestrator `/graphify src/`(AST 1825노드/4292엣지/79커뮤니티 — 비코드 4건[wordlist·이미지3]은 아키텍처값 0으로 AST 기반) + `/graphify docs/`(semantic 198노드/282엣지/14커뮤니티, 3 subagent chunk — DDL FK·결정 rationale, graph 본체 자기참조 제외) 재생성. docs/graph/src·docs/ 본체 갱신(gitignore), docs/graph/index.md frontmatter(source_commit→305cc73·last_generated 2026-06-29) + Scopes 표 2행 갱신. graphify-update-advisor 가 advisor 경계상 /graphify 직접호출 불가로 orchestrator 가 Skill 실행. src/graphify-out AST 캐시 잔여물 제거.
## 선제 해소 (구현 전 blocker)
- **W3-3 pom 의존 blocker 해소**: commonmark·jsoup `~/.m2` 미존재(오프라인 `-o` 컴파일 불가) → 온라인+프록시 CA(`certs/corporate-proxy-ca.crt`) 주입으로 1회 pre-resolve 완료. **jsoup 1.17.2 + commonmark 0.22.0** 캐시됨(jsoup 1.14+ Safelist API 정합). W3-3 impl 은 이 정확버전 pom 선언. 이후 오프라인 빌드 가능. (online dep 해소 = JDK cacerts 프록시 CA 주입 필요 — 메모리 local-dev-setup-gotchas §1 패턴.)
## open_items
- (W2 carryover) 실 dev DB(bibimbap) jam DDL 미적용 — 신규 W3/W4 DDL 과 함께 일괄 적용 게이트 검토 필요.
- **W3-5 gameRoot() dev 경로 이중중첩**(app.upload.game-storage-path=src/main/resources/static/game → resolve('game') → .../static/game/game): W3-5 설계 명시 스코프-아웃(에스컬레이션). 보안작업 무관 진행(canonical 경계 보장). 별도 config 정합 작업 — dev 자산 위치 영향이라 사용자 결정 필요. needs_user_verification 후보.
- **W3-3 SSRF 잔여 rebinding TOCTOU**(option-b hostname-connect): connect 시 재resolve 창. DNS 캐시로 최소화, 위협모델 LOW(URL 제출자=권한자). 하드닝 옵션: `networkaddress.cache.ttl` 설정. HTTPS 실 TLS = L3 스모크 권고.
## Retrospective
```yaml
Retrospective:
signals:
positive: [] # 이번 세션 발화는 초기 요청 1 + "계속해줘" + config 명령(model/effort/fast). 강한 긍정 발화 없음 — 억지 기재 안 함. 비자명 판단의 가치는 what_went_well + memory_candidates(signal_source: observation)로 분리 기재.
negative: [] # 부정 발화 없음("왜 안했어"/"또야"/"틀렸어" 0건). W3-3 1차 SSRF 갭은 사용자 시그널이 아니라 adversarial 감사가 잡은 내부 관찰 — observation 으로 처리.
what_went_well:
- "착수 전 환경 blocker 선제 해소: 호스트 JDK 0 → 컨테이너 L1 경로 확정(baseline 동결), W3-3 commonmark/jsoup 오프라인 의존 → 프록시 CA 주입으로 1회 pre-resolve. '열린 질문 없도록 미리 파악' 의도를 환경 차원까지 확장해 W3-3 착수 시 컴파일 blocker 0."
- "보안핵심 2중 검증(verification + adversarial 코드감사 병렬): W3-3 SSRF 에서 verification 은 'fetch empty' RED 만 봤으나 adversarial 감사가 근본원인(Host restricted-header → 거부 테스트 vacuous PASS, SSRF 거부가 거짓통과) + 추가 범위갭(CGNAT 100.64/10) 진단. 이대로 커밋했으면 SSRF 무력 배포였음. W3-5 zip-slip 도 동일 패턴(감사 SOUND)으로 우회 0 확정."
- "backward 회귀 단계 판정(§2.6) + 마이크로편집(§5.1) 정확 적용: W3-3 fix1 후 FeedParser 잔존 RED 의 발원이 픽스처 요일-날짜 불일치(RFC1123 DOW 검증 거부)임을 orchestrator 가 직접 진단, 선행 요일토큰 제거 robust 파싱으로 마이크로편집(실 피드 대응) + 직접 L1 287/287 검증. advisor 라운드 추가 없이 단일 메서드 보정으로 해소."
- "advisor 호출/스킵 판단 즉시 로깅: requirements/research/design 3 advisor 를 풀설계 audit PASS 근거로 스킵 결정 + rationale 기록 → 확정적 설계에 불필요 라운드 0. 코드 변경 W 마다 verification 스킵 불가 일관 적용."
- "검증 DB 격리 무파괴 일관 유지: 전 W L2 를 격리 throwaway postgres:16(적용→실측→DROP)으로 실측, 실 dev DB(bibimbap) 무접촉. 실 dev DB DDL 적용은 §6 수동게이트로 needs_user_verification 에 분리."
what_to_improve:
- "W3-3 1차 구현이 SSRF Host restricted-header 를 놓침 + 자체 거부 테스트가 vacuous(전건 empty 반환이라 '거부됨'을 입증한 게 0건). graceful catch 가 예외를 삼켜 정상경로 실패를 '정상 거부'처럼 위장. → '보안 거부 테스트는 정상경로 성공 케이스와 쌍으로 차별 입증(distinguishing assertion)해야 vacuous 회피' 가 핵심 교훈."
- "W4 표시 AC 부분 갭(프로필 myBadges 주입·게임카드 creator 배지 칩): orchestrator scope-fence(W3-1/3/4 산출물 접근금지)가 W4 표시면을 선행기능 컨트롤러에 배선하는 것을 막음. 단일 기능의 표시면이 선행 기능 컨트롤러에 의존할 때, scope-fence 가 표시 배선을 차단하지 않도록 표시-배선 지점을 fence 예외로 사전 식별하는 설계 교훈."
- "option-b SSRF(hostname-connect) 잔여 rebinding TOCTOU: 설계 concern 이 IP핀닝 vs HTTPS-SNI 트레이드오프를 예고했으나 1차 구현이 HTTPS 미검증 IP핀닝을 선택. 보안핵심에서 설계가 명시한 트레이드오프는 구현 1보에 선결정으로 못박아 1차에서 정답 분기를 고르게 한다."
memory_candidates:
- name: security-reject-test-needs-distinguishing-assertion
type: feedback
description: "보안 거부 테스트는 정상경로 성공 케이스와 쌍으로 차별 입증해야 vacuous(거짓통과)를 회피한다."
body_draft: |
What: 보안 거부(SSRF·zip-slip·인가 등) 단위 테스트가 '차단되어 empty/예외'만 확인하면, 정상경로마저 같은 이유로 실패할 때 테스트가 vacuous PASS(거부를 입증한 게 0건)가 된다. graceful catch 가 예외를 삼키면 '정상경로 실패'가 '정상 거부'로 위장된다.
Why: W3-3 1차 구현은 Host restricted-header 때문에 모든 fetch 가 empty 였고(정상 URL 도 fetch 0), SSRF 거부 테스트가 전건 empty 라 거부를 차별 입증하지 못했다. verification 은 RED 만 봤고 adversarial 코드감사가 vacuous 근본원인을 잡았다. 이대로 커밋했으면 SSRF 무력 배포.
How to apply: 보안 거부 테스트는 (a) 차단되어야 하는 입력 → 거부, (b) 허용되어야 하는 정상 입력 → 성공, 두 케이스를 쌍으로 둬 두 분기가 실제로 갈리는지(distinguishing assertion) 본다. graceful catch 경로는 '거부 사유'와 '정상경로 실패'를 구별 가능한 신호로 분리한다. 보안핵심 기능은 verification(테스트 실행)과 adversarial 코드감사를 병렬로 건다.
rationale_for_saving: "재현성 높은 테스트 안티패턴(전 보안 거부 테스트에 적용). 코드/커밋에서 유도 불가 — 관찰로만 드러난 verification 사각."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # "## 설계·테스트 단계 체크리스트" 섹션에 신규 소제목 append (append-only 관례)
memory_optional: false
- name: security-core-parallel-adversarial-audit
type: feedback
description: "보안핵심 기능은 verification(테스트 실행)과 adversarial 코드감사를 병렬로 건다 — 우회 벡터는 테스트 실행이 못 잡는다."
body_draft: |
What: SSRF·zip-slip 등 보안핵심 변경은 verification-advisor(테스트 실행)만으로 부족. 우회 벡터(IP 인코딩·IPv6·DNS rebinding·redirect·심링크 TOCTOU·zipbomb·유니코드 경로)는 테스트가 작성돼 있어야만 잡히는데, 누락 벡터는 테스트 자체가 없어 GREEN 으로 숨는다.
Why: W3-3 SSRF — adversarial 감사가 vacuous PASS(Host restricted-header) + CGNAT 범위갭을 잡아 무력 배포를 막음. W3-5 zip-slip — 감사 SOUND 로 우회 0 확정. 두 케이스 모두 verification 단독이면 누출됐을 사각.
How to apply: 변경 scope 에 SSRF/파일추출/인가/sanitize 등 보안핵심이 포함되면 verification 과 별도로 general-purpose adversarial 감사(코드추적 기반 우회벡터 탐색)를 병렬 호출. 감사 산출은 CRITICAL/SOUND 판정 + 미커버 벡터 목록. CRITICAL 시 §2.6 backward 보정.
rationale_for_saving: "보안핵심 검증 토폴로지의 재현 패턴. 이번 세션이 2건(SSRF·zip-slip)으로 가치를 실증 — verification-strategies 레지스트리에 보안핵심 분류 트리거로 둘 가치."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # "버그 범주 → 적용 L 레벨" 표에 '보안핵심(SSRF/파일추출/인가/sanitize) → L1+L2+adversarial 감사' 행 추가 + 체크리스트 소제목
memory_optional: false
- name: scope-fence-vs-display-wiring
type: feedback
description: "단일 기능의 표시면이 선행 기능 컨트롤러에 의존하면 scope-fence 가 표시 배선을 차단할 수 있다 — 표시-배선 지점을 fence 예외로 사전 식별."
body_draft: |
What: orchestrator scope-fence 는 충돌 방지를 위해 worker 의 선행 기능 산출물 접근을 막는다. 그러나 신규 기능의 '표시면'(예: 배지 칩 렌더)이 선행 기능 컨트롤러(프로필·게임카드)에 주입돼야 하면 fence 가 표시 배선을 막아 표시 AC 부분 갭이 생긴다.
Why: W4 배지/평판 — myBadges 프로필 주입·게임카드 creator 배지 칩 표시가 W3-1/3/4 컨트롤러에 배선돼야 하는데 scope-fence(W3-1/3/4 접근금지)가 막아 표시 AC 부분 미충족.
How to apply: 설계 영향맵에서 신규 기능의 표시면이 선행 기능 컨트롤러/뷰에 배선되는 지점을 'cross-feature display wiring'으로 표시. orchestrator 는 이 지점을 scope-fence 예외(읽기+해당 라인 편집 허용)로 사전 식별하거나, 표시 배선을 별도 후행 단계로 분리한다.
rationale_for_saving: "scope-fence 운영의 재현 가능한 사각. 다기능 순차 구현에서 반복 발생 — orchestrator 토폴로지 교훈으로 docs 가치."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # 체크리스트 섹션에 설계/스코프 교훈으로 append (또는 design 영향맵 규약 — protocol_feedback 과 연동)
memory_optional: false
- name: design-tradeoff-pin-to-implementation-step1
type: feedback
description: "설계 concern 이 명시한 보안 트레이드오프(IP핀닝 vs HTTPS-SNI 등)는 구현 1보 선결정으로 못박아 1차에서 정답 분기를 고르게 한다."
body_draft: |
What: 설계 단계가 'A vs B 트레이드오프, B 권장' 식으로 concern 을 예고해도, 구현이 1차에서 A 를 고르면 backward 보정 라운드가 생긴다. 트레이드오프는 선결정 목록(구현 전)에 결론까지 명시해야 1차 구현이 정답으로 수렴한다.
Why: W3-3 SSRF — 설계 concern 이 IP핀닝 vs HTTPS-SNI 트레이드오프를 예고했으나 1차 구현이 HTTPS 미검증 IP핀닝(option-a 류)을 선택 → fix1 에서 option-b hostname-connect 로 전환. 잔여 rebinding TOCTOU 도 이 분기에서 파생.
How to apply: design-advisor 가 'concern: 트레이드오프'를 남기면 결론(채택 분기 + 이유)을 함께 적고, orchestrator 는 '선결정' 섹션에 결론을 못박아 implementation-advisor 지시에 포함. 결론 미확정 트레이드오프는 구현 착수 전 해소.
rationale_for_saving: "design→implementation 핸드오프의 재현 갭. 보안핵심에서 1차 정답률을 올리는 운영 교훈."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md # 체크리스트 또는 protocol_feedback(design-advisor concern 규약)와 연동
memory_optional: false
protocol_feedback:
- "[verification 전략 레지스트리] '버그 범주 → 적용 L 레벨' 표에 '보안핵심(SSRF/파일추출·zip-slip/인가/sanitize) → L1 + L2 + adversarial 코드감사(병렬)' 행 추가. verification(테스트 실행)이 못 잡는 우회벡터를 코드추적 감사로 병렬 커버하는 패턴을 레지스트리 트리거화. 근거: W3-3 SSRF vacuous 포착 + W3-5 zip-slip SOUND."
- "[verification-advisor 또는 W-TEST worker 체크리스트] '보안 거부 테스트는 정상경로 성공 케이스와 쌍으로 차별 입증(distinguishing assertion)해야 vacuous 회피' 조항 추가. graceful catch 가 정상경로 실패를 거부로 위장하는 안티패턴을 명시. 근거: W3-3 SSRF 거부 테스트 전건 empty vacuous PASS."
- "[orchestrator scope-fence 규약 / design-advisor 영향맵] 신규 기능의 표시면이 선행 기능 컨트롤러/뷰에 배선되는 'cross-feature display wiring' 지점을 영향맵에 표시하고 scope-fence 예외(또는 후행 분리)로 사전 식별하는 조항 추가. structural — 다기능 순차 구현에서 반복 가능. 근거: W4 myBadges/게임카드 배지 표시 AC 부분 갭."
- "[design-advisor concern 규약] '트레이드오프 concern' 은 결론(채택 분기 + 이유)까지 명시하고, orchestrator 는 이를 '선결정'으로 못박아 implementation 지시에 포함하는 조항 추가. 결론 미확정 트레이드오프는 구현 착수 전 해소. 근거: W3-3 SSRF IP핀닝 vs HTTPS-SNI 1차 오선택 → backward 보정."
applied_changes: []
```
## applied_changes (orchestrator — 회고 교훈 docs-first 반영)
- verification-strategies.md "설계·테스트 단계 체크리스트" append 4건: (1) 보안 거부 테스트 차별 입증(vacuous 회피) (2) 보안핵심 verification+adversarial 병렬 (3) 표시면 의존 scope-fence 예외 (4) 설계 트레이드오프 1차 결론 못박기.
- verification-strategies.md "프로토콜 개선 권고" append 5건(보안핵심 L레벨 행·distinguishing assertion·cross-feature display fence·design 트레이드오프). 외부번들 대상 미적용 기록.
- memory: bibimbap memory 미활성 → docs 단독 마감(memory_optional=false 전 후보). 강제 기록 안 함.

View File

@ -0,0 +1,106 @@
---
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 처리 안 함) |