docs: ATP 문서 체계 도입 및 프로젝트 종합 분석 추가
docs/ 카테고리 트리(adr·analysis·architecture 등)와 ATP 워크플로 문서를 신규 추가하고, 4축(아키텍처·보안·품질·도메인) read-only 종합 분석을 docs/analysis/2026-06-16-project-analysis.md 로 기록한다. - 신규 분석 문서 + docs/analysis/index.md 링크 - 기존 docs 2건(db-update-query-generator, user-signup-schema)에 교차링크 추가 - CLAUDE.md 프로젝트 지침, .atp/work-session 분석 세션 기록, .serena 프로젝트 설정 포함 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
40bde12c6d
commit
33a4465e0e
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
phase: documentation
|
||||
agent: documentation-advisor
|
||||
agent_version: 1
|
||||
generated_at: 2026-06-16T11:17:11Z
|
||||
concerns:
|
||||
- "프로젝트 docs/development/documentation-guidelines.md 는 소비 프로젝트에 복사되지 않음(번들 캐시 참조). atp 플러그인 캐시(2.1.0) 의 권위 가이드를 직접 읽어 analysis frontmatter 스키마(kind/perspective/valid_starting_point_for/superseded_note)를 따랐다."
|
||||
- "기존 docs/analysis/ 에 선행 문서가 없어 매칭할 명명 패턴 부재. 루트 docs 의 서술형 kebab-case 관례 + analysis 스냅샷 날짜접두 관례를 조합해 2026-06-16-project-analysis.md 로 명명했다."
|
||||
- "원본 analysis.md 의 추정/미확인 마커(세션쿠키 Secure/SameSite, SNAPSHOT 의도, 소셜 확장, CVE 미스캔)를 사실로 격상하지 않고 그대로 보존했다."
|
||||
concerns_checked: true
|
||||
---
|
||||
|
||||
# 문서화 보고
|
||||
|
||||
## 작성/수정된 문서
|
||||
|
||||
| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
|
||||
|---|---|---|---|---|
|
||||
| docs/analysis/2026-06-16-project-analysis.md | analysis | 신규(정본 분석) | docs/analysis/index.md (목록 1줄 추가) | 아웃바운드 → db-update-query-generator.md, user-signup-schema.md, development/document-category-classification.md / 인바운드 ← 동일 2개 문서의 "관련 문서" |
|
||||
| docs/analysis/index.md | analysis | 수정(목록) | (자기 자신) | — |
|
||||
| docs/db-update-query-generator.md | (루트) | 수정(관련 문서 링크) | — | → 신규 분석 인바운드 |
|
||||
| docs/user-signup-schema.md | (루트) | 수정(관련 문서 링크) | — | → 신규 분석 인바운드 |
|
||||
|
||||
## 의사결정 기록 위치
|
||||
|
||||
- 정본 분석: `docs/analysis/2026-06-16-project-analysis.md` — D1~D4 + 주요 발견 Top10 + 종합 판단 + open_questions 전부 보존. 모든 file:line 인용 유지, 추정/미확인 마커 보존.
|
||||
- 출처(원본): `.atp/work-session/20260616-111711/research/analysis.md`
|
||||
- frontmatter 적용 필드: `kind, title, description, perspective(neutral), valid_starting_point_for, owner, stability(snapshot), last_reviewed, source, superseded_note`
|
||||
|
||||
## 따른 규약
|
||||
|
||||
- 읽은 가이드: `docs/index.md`, `docs/analysis/index.md`, `docs/development/index.md`, `docs/development/document-category-classification.md`, 그리고 atp 플러그인 캐시의 권위 `documentation-guidelines.md`(2.1.0).
|
||||
- 카테고리 판별: 코드/흐름/리스크 분석(수정 없음) → `analysis/` (분류 기준 §6, §빠른결정순서 6번).
|
||||
- 링크 강제 규칙: 신규 문서 → 카테고리 index.md 1줄 링크 추가 완료 + 기존 관련 문서 2건에 인바운드 교차 링크 추가 완료.
|
||||
|
||||
## 추후 문서화가 필요한 항목
|
||||
|
||||
- 보안 미결 결정(login/signup CSRF, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정)이 design 단계에서 확정되면 → `adr/`(되돌리기 어려운 결정) 또는 `security/`(기준 문서)로 분리 기록 필요.
|
||||
- game_comments/game_likes 서버 영속화의 "의도된 미완성 vs 폐기" 판정이 내려지면 → `backlog/`(보류) 또는 `changes/`(구현 시) 기록.
|
||||
- D2 보안 발견을 독립 `security/` 기준 문서로 승격할지 검토(현재는 analysis 한 문서에 통합).
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
schema_version: 1
|
||||
sid: 20260616-111711
|
||||
started_at: 2026-06-16T11:17:11+09:00
|
||||
ended_at: null
|
||||
user_request: "프로젝트 분석"
|
||||
resumed_from: null
|
||||
---
|
||||
|
||||
# Report — 프로젝트 분석
|
||||
|
||||
## Summary
|
||||
bibimbap = Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰 웹앱(게임 공유 + 팀원 모집). 4축 read-only 종합 분석 완료.
|
||||
- 보안 기초 견고(SQLi 0, PBKDF2 210k, CSRF 더블서밋, zip-slip/XSS 방어 일관)
|
||||
- 부채는 프로토타입 화석 코드 + 서비스 레이어 부재 + 테스트 전무에 집중
|
||||
- research-advisor 2회 디스패치(첫 호출 async worker await 중 TaskOutput 조기판독 → 재디스패치). 두 산출 상호검증되어 결과 신뢰도↑. 산출물: research/analysis.md (198줄)
|
||||
|
||||
## Orientation (사전 파악)
|
||||
- Stack: Spring MVC + MyBatis, Java/Maven (pom.xml, .mvn)
|
||||
- Package: com.pandoli365.bibimbap — Java 30 files
|
||||
- Layers: abstracts / config / controller(+api) / data / game / mapper / security
|
||||
- View: JSP (src/main/webapp/WEB-INF/jsp, views), static resources
|
||||
- Config: src/main/resources/{dev,live}/db.properties.example, application.properties
|
||||
- Domain: 팀원 모집 + 프로젝트 목록 웹앱 (recent commits)
|
||||
- graph: no-graph (docs/graph/index.md scopes 비어있음)
|
||||
- docs/: 대부분 ATP/graphify 템플릿 스캐폴딩 (owner: template-maintainer) — 프로젝트별 architecture 문서 없음
|
||||
|
||||
## Invocations
|
||||
- inv-001:
|
||||
advisor: research-advisor
|
||||
status: completed (interim-misread, see notes)
|
||||
model_choice: { phase: analyze, dispatch_size: parallel, tier: large, effort: high, resolved_model: inherit(opus), capped: false }
|
||||
output: research/analysis.md (171줄, 4 parallel-explorer)
|
||||
notes: "async worker await 중 orchestrator 가 TaskOutput 을 조기 판독해 standby 메시지를 종료로 오인 → inv-002 재디스패치 유발. 실제로는 정상 완료."
|
||||
- inv-002:
|
||||
advisor: research-advisor
|
||||
status: completed
|
||||
model_choice: { phase: analyze, dispatch_size: l-batch, tier: large, effort: high, resolved_model: inherit(opus), capped: false }
|
||||
output: research/analysis.md (198줄, 직접 탐색, 디스크 잔존본)
|
||||
notes: "no-async 규칙 명시 재디스패치. inv-001 과 핵심 발견 전부 일치(교차검증)."
|
||||
|
||||
# Advisor Invocation Decision Log
|
||||
# 각 advisor 호출/스킵 판단 즉시 1줄 append
|
||||
- advisor: graphify-lookup-advisor
|
||||
decision: skip
|
||||
rationale: 'graph/index.md scopes 비어있음(no-graph) 확인 — 조회 대상 없음, 직접 src 탐색으로 대체'
|
||||
checked_at: 2026-06-16T11:17:11+09:00
|
||||
- advisor: requirements-advisor
|
||||
decision: skip
|
||||
rationale: '분석 축 AskUserQuestion 으로 직접 확정 — 4축 전부 선택(아키텍처/보안/품질/도메인). 마이크로 요구해석, advisor 불필요'
|
||||
checked_at: 2026-06-16T11:18:00+09:00
|
||||
- advisor: research-advisor
|
||||
decision: call
|
||||
rationale: '4축 종합 분석 = 탐색적 다파일 교차 추론. Tier-3 로 parallel-explorer 4 fan-out. tier=large(opus, self-cap 내), effort=high'
|
||||
checked_at: 2026-06-16T11:18:00+09:00
|
||||
- advisor: documentation-advisor
|
||||
decision: call
|
||||
rationale: '사용자 A 선택 — analysis.md 를 docs/analysis/ 영구 문서로 보존 + index 링크. docs-sync phase, tier=medium'
|
||||
checked_at: 2026-06-16T11:33:00+09:00
|
||||
|
||||
## Decisions
|
||||
- 본 세션은 코드 변경 0 (읽기 전용 분석). design/implementation/verification advisor 비적용.
|
||||
|
||||
## user_signals
|
||||
positive: []
|
||||
negative: []
|
||||
|
||||
## graph_refresh
|
||||
skip: no-scope-change — 코드 변경 0(read-only 분석). graph-refresh-checker 미호출. (no-graph 상태이나 분석 세션이라 생성 유예; 필요 시 후속 `/graphify src` 권장)
|
||||
|
||||
## verified_by_me
|
||||
- L1: skip — 코드 변경 0 (read-only 분석, typecheck/test 대상 없음)
|
||||
- L2: skip — 외부 의존 변경 없음
|
||||
- 산출물 무결성: research/analysis.md 디스크 존재 확인(198줄), 모든 사실 항목 file:line 근거 + 불확실 항목 마커(추정/미확인) 보존 확인. 2개 독립 산출 교차검증 일치.
|
||||
|
||||
## needs_user_verification
|
||||
- 분석 자체는 검증 불요. 단 analysis.md open_questions 1~5 는 코드 밖 의도/배포설정 의존 → 사용자 답변 필요(특히 likes/comments 영속화 의도, 세션쿠키 Secure/SameSite, SNAPSHOT 고정 사유).
|
||||
|
||||
## open_items
|
||||
- .atp/work-session/20260616-111711/ (report.md, research/analysis.md, documentation.md) — 미커밋.
|
||||
- A 완료: docs/analysis/2026-06-16-project-analysis.md(신규 218줄) + docs/analysis/index.md(링크) + docs/db-update-query-generator.md·docs/user-signup-schema.md(교차링크) — 미커밋. 커밋은 사용자 지시 대기(base 규칙: 커밋은 요청 시에만).
|
||||
- src/·pom.xml 무변경 확인(read-only 분석 준수).
|
||||
|
||||
## Invocations (추가)
|
||||
- inv-003:
|
||||
advisor: documentation-advisor
|
||||
status: completed
|
||||
model_choice: { phase: docs, dispatch_size: s-batch, tier: medium, effort: low, resolved_model: inherit(opus), capped: false }
|
||||
output: docs/analysis/2026-06-16-project-analysis.md + index + 교차링크 2건; work-session documentation.md
|
||||
notes: "분석 영구 보존(A). 산출 디스크 존재로 완료 판정(inv-001 교훈 적용)."
|
||||
|
||||
## retrospective (orchestrator 직접 — read-only 세션, advisor 미호출)
|
||||
- skip: retrospective-advisor — 코드 변경 0 + 사용자 negative 시그널 없음. 단 1개 프로세스 교훈 직접 기록:
|
||||
- LESSON(b-pattern 후보): async 서브에이전트(worker fan-out)는 부모가 worker await 중 "rest" 상태로 보고될 수 있음. TaskOutput status=completed + thin standby 메시지면 종료 단정 말고 산출물 디스크 존재로 완료 판정할 것. 본 세션서 이 오판으로 inv-002 중복 디스패치(~66k 토큰 낭비).
|
||||
- memory_candidate: { type: feedback, signal_source: process, text: "research-advisor 등 worker-fanout advisor 의 완료 판정은 반환 메시지가 아닌 산출 파일 존재로 한다", docs_sync_target: null }
|
||||
|
||||
ended_at: 2026-06-16T11:32:00+09:00
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
---
|
||||
phase: research
|
||||
agent: research-advisor
|
||||
agent_version: 2
|
||||
generated_at: 2026-06-16T11:17:11Z
|
||||
concerns:
|
||||
- "abstracts 패키지(Service/Request/Result/ErrorResult)는 전부 dead code이며 세션키('id' vs 'userId')가 활성 코드와 불일치 — 향후 재사용 시 인증 우회 함정. requirements 전제(레이어 구조)와 충돌 가능: 실제 서비스 레이어는 존재하지 않음(컨트롤러가 매퍼 직접 호출)."
|
||||
- "POST /login, POST /signup 에 CSRF 검증 부재(다른 모든 mutation 엔드포인트는 검증함) — login CSRF 가능. design 단계로 넘기기 전 보안 결정 필요."
|
||||
concerns_checked: true
|
||||
source_confidence: high
|
||||
workers_spawned: 0
|
||||
---
|
||||
|
||||
# 조사 결과 — bibimbap 종합 분석 (D1~D4)
|
||||
|
||||
> 분석 방식: 30개 Java 파일 + 16개 JSP + 6개 MyBatis 매퍼 전수 직접 읽기(Read/Grep/Bash). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 명시.
|
||||
|
||||
## 주제
|
||||
Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4).
|
||||
|
||||
---
|
||||
|
||||
## 주요 발견 Top 10
|
||||
|
||||
| # | 심각도 | 발견 | 위치 |
|
||||
|---|--------|------|------|
|
||||
| 1 | INFO(긍정) | **SQL 인젝션 표면 0** — 6개 매퍼 전 쿼리가 파라미터 바인딩 `#{}` 만 사용. `${}` 문자열 보간 단 1건도 없음(java/xml 전수 grep). 검색어도 `ILIKE CONCAT('%', #{query}, '%')` 로 안전. | mapper/*.java 전체 |
|
||||
| 2 | INFO(긍정) | **비밀번호 해싱 견고** — PBKDF2WithHmacSHA256, 210,000 iterations, 16B salt, 256bit, `MessageDigest.isEqual`(상수시간 비교). 형식 `pbkdf2_sha256$iter$salt$hash`. | UserController.java:542-576 |
|
||||
| 3 | MED | **POST /login, /signup CSRF 미검증** — logout/profile/game/recruit 모든 mutation은 `CsrfTokens.isValid()` 검사하나 login·signup은 누락 → login-CSRF 가능. | UserController.java:118-160(login), 65-116(signup) |
|
||||
| 4 | MED(부채) | **abstracts 패키지 전체 dead code** — `Service`/`Request`/`Result`/`ErrorResult` 어디서도 상속·호출 안 됨. `Service.ChackService`는 세션키 `"id"`(line 12) 사용, 활성 코드는 `"userId"` 사용 → 재사용 시 인증 우회 함정. | abstracts/Service.java:12 |
|
||||
| 5 | MED(부채) | **orphan 깨진 JSP fragment** — `WEB-INF/jsp/fragments/header.jspf` 가 Spring Security 태그(`<sec:authorize>`, `${_csrf}`)와 JSTL 사용하나 (a)어디서도 include 안 됨 (b)Spring Security 의존성 자체가 pom에 없음 (c)라우트(`/games/new`,`/register`)·브랜딩('비빔밥')이 활성앱(`/game/new`,`/signup`,'bibimbap')과 불일치. 이전 프로토타입 잔재. | jsp/fragments/header.jspf:1-25 |
|
||||
| 6 | MED(부채) | **GameCatalog 완전 stub** — 모든 배열이 `{}` 빈 배열, `COUNT=0`. `GameController.gameDetail` 의 폴백 분기(line 111-128)는 영구 도달 불가(`isValidId`는 항상 false). 죽은 분기 + 죽은 클래스. | game/GameCatalog.java:8-22, controller/api/GameController.java:111-128 |
|
||||
| 7 | MED(부채) | **게임 댓글: DB 스키마·매퍼 존재하나 미연결** — `game_comments` 테이블, `GameCommentsMapper`(get/add/update), `GameCommentData` 모두 존재하지만 **댓글 작성/조회 컨트롤러 엔드포인트 없음**. 실제 댓글은 JSP 내 localStorage 클라이언트 전용(서버 미저장). `softDeleteGameComments`만 게임 삭제 시 호출됨. | mapper/GameCommentsMapper.java, views/game-detail.jsp:907-1000 |
|
||||
| 8 | LOW | **WebApplicationFirewall/인증필터 부재 — 세션 기반 임시방편 인증** — Spring Security 미사용. 각 컨트롤러가 수동으로 `session.getAttribute("userId")` 체크. 인가는 리소스 소유권 체크(`userId.equals(game.getUserId())`)로 일관 처리됨(양호)하나 중앙 집중 필터 없음 → 신규 엔드포인트에서 체크 누락 위험. | 전 컨트롤러 |
|
||||
| 9 | LOW | **생성 산출물 git 추적** — `src/test/db/dev-to-live-update.sql`(테스트가 생성하는 dev→live 마이그레이션 진단)이 커밋됨. 실제 비밀값은 없고(`-- DATA DIFF ... password_hash` 주석뿐) 비밀 누출 아님. 단 생성물은 비추적이 적절. | src/test/db/dev-to-live-update.sql:54 |
|
||||
| 10 | INFO | **XSS 방어 일관 적용(비관습적)** — JSTL `<c:out>` 0건이나, 모든 JSP가 Java scriptlet 변수 할당 시점에 `HtmlUtils.htmlEscape()` 적용 후 출력. 사용자 콘텐츠 sink(게임명/제작자/모집글/닉네임/이메일/창작노트/검색어) 전수 확인 결과 escape 누락 없음. CSP·X-Content-Type-Options 도 WebGL asset 응답에 설정. | views/*.jsp 전체, GameAssetController.java:60-66 |
|
||||
|
||||
---
|
||||
|
||||
## D1 — 아키텍처/구조
|
||||
|
||||
### 패키지별 책임 (1줄)
|
||||
|
||||
| 패키지 | 역할 |
|
||||
|--------|------|
|
||||
| (root) | `BibimbapApplication`(@SpringBootApplication main), `ServletInitializer`(WAR 배포용 SpringBootServletInitializer) |
|
||||
| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) |
|
||||
| config | `UploadResourceConfig` — 업로드 경로의 `/profile/**` 정적 리소스 핸들러 등록 |
|
||||
| controller | 페이지(뷰) 컨트롤러 — `WebMvcController`(공통 페이지+에러), `RecruitController`(모집 페이지+API 혼합) |
|
||||
| controller/api | API/mutation 컨트롤러 — Game CRUD, GameUpload(zip/썸네일/파일), GameAsset(WebGL 서빙), User(인증/프로필), 예외 핸들러 |
|
||||
| data | 순수 POJO DTO 6종 (Lombok 미사용 — 수동 getter/setter) |
|
||||
| game | `GameCatalog` — **stub(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) |
|
||||
| mapper | MyBatis `@Mapper` 인터페이스 6종 — 어노테이션 기반 SQL(XML 매퍼 0) |
|
||||
| security | `CsrfTokens` — 세션 기반 CSRF 토큰 발급/검증 유틸(static) |
|
||||
|
||||
### 요청 흐름
|
||||
- **페이지 렌더**: Controller → (서비스 레이어 없음) → Mapper → DB → ModelAndView/Model → `/WEB-INF/views/*.jsp` (ViewResolver prefix/suffix는 application.properties).
|
||||
- **API mutation**: `@PostMapping`/`@DeleteMapping` → CSRF 검증 → 세션 인증 → 입력 검증 → Mapper → JSON ResponseEntity.
|
||||
- **서비스 레이어 부재**(확인됨): 컨트롤러가 매퍼를 직접 주입·호출. `@Transactional`은 컨트롤러 메서드에 부착. → requirements가 "service 레이어 존재"를 전제했다면 충돌(concern 기재).
|
||||
- **JSP 렌더 방식**(확인됨): JSTL 미사용. Java scriptlet(`<%%>`/`<%=%>`)으로 `request.getAttribute()` 직접 캐스팅 + `HtmlUtils.htmlEscape()`.
|
||||
|
||||
### 레이어별 핵심 클래스
|
||||
|
||||
| 레이어 | 클래스 | 역할(1줄) |
|
||||
|--------|--------|----------|
|
||||
| 진입 | BibimbapApplication / ServletInitializer | main 부트 / WAR 배포 부트 |
|
||||
| 페이지 | WebMvcController | `/`,`/{pageName}`(화이트리스트), `/error`, 프로필·게임등록 뷰 게이팅 |
|
||||
| 페이지+API | RecruitController | `/recruit`(목록), `/recruit/new`(폼+생성 POST), `/recruit/{id}`(상세) |
|
||||
| API | GameController | 게임 생성/수정/삭제(soft) + 상세/편집 뷰 + 소유권 인가 |
|
||||
| API | GameUploadController | WebGL zip 업로드(zip-slip 방어), 썸네일·일반파일 업로드 |
|
||||
| API | GameAssetController | `/game/{uuid}/**` WebGL 정적 서빙 + CSP/인코딩 헤더 |
|
||||
| API | UserController | signup/login/logout/프로필(닉네임·아바타) + PBKDF2 해싱 |
|
||||
| API | ApiExceptionControllerAdvice | 업로드 초과·일반 예외 → JSON 에러(스택 비노출) |
|
||||
| 매퍼 | Games/RecruitPosts/Users/UserAuthIdentities/GameComments/GameLikes Mapper | 어노테이션 SQL CRUD |
|
||||
|
||||
### Spring/MyBatis 배선·프로파일
|
||||
- `@SpringBootApplication` 단일. MyBatis는 `mybatis-spring-boot-starter` 자동 배선 + `@Mapper` 스캔(별도 @MapperScan 불필요, 확인됨).
|
||||
- 프로파일: pom `dev`/`live` 프로파일이 `app.profile` 치환 → `application.properties`의 `spring.profiles.active=@app.profile@`(빌드 필터링) → `spring.config.import`로 `${profile}/db.properties` 로드. dev/live는 PostgreSQL 동일 호스트·다른 schema(`currentSchema=dev|live`).
|
||||
- DB props: `db.properties.example`만 git 추적, 실제 `db.properties`는 .gitignore 처리(확인됨: gitignore `### Local secrets ###`).
|
||||
- 앱 진입: embedded Tomcat(`spring-boot-starter-web`) + WAR(`tomcat-embed-jasper` for JSP, `spring-boot-starter-tomcat` provided). web.xml 없음(Servlet 3+ initializer).
|
||||
|
||||
---
|
||||
|
||||
## D2 — 보안 (최우선)
|
||||
|
||||
### 인증/인가
|
||||
- **메커니즘**(확인됨): Spring Security 미사용(의존성 없음). `HttpSession` 속성 기반 커스텀. login 시 `request.changeSessionId()`로 세션 고정 공격 방어(UserController.java:152). remember 옵션으로 세션 타임아웃 30분/30일 분기.
|
||||
- **인가**(확인됨): 리소스 mutation마다 `userId.equals(resource.getUserId())` 소유권 체크 일관 적용(GameController updateGame:183, deleteGame:239, editView:142). role 기반 인가는 미구현(role 컬럼은 저장만, 'USER' 고정).
|
||||
|
||||
### 자격증명/시크릿
|
||||
- 하드코딩 시크릿 **없음**(확인됨): tracked 파일은 `*.example`(placeholder `your_username/your_password`)뿐. 실제 db.properties는 gitignore. application.properties에 비밀값 없음.
|
||||
- 비밀번호 저장: PBKDF2-SHA256 210k iter(상기 Top#2). 평문·약한해시 없음.
|
||||
|
||||
### SQL 인젝션 (전수 — `${` vs `#{`)
|
||||
- **전 매퍼 `${}` 보간 0건**(확인됨, grep 전수). 모든 동적값은 `#{}` 또는 `@Param`+`#{}`. 검색: `searchVisibleGames`도 `ILIKE CONCAT('%', #{query}, '%')`로 안전(GamesMapper.java:85-87).
|
||||
- 단, **테스트 코드** `DbUpdateQueryGeneratorTest`는 `String.formatted()`로 schema명을 SQL에 직접 삽입(line 333,369) + `quoteIdentifier`/`escapeSql`로 수동 이스케이프. 이는 프로덕션 경로 아님(테스트 전용, surefire에서 제외됨)이나 패턴상 주의(LOW, 입력이 상수 'dev'/'live'라 실위험 없음 — `확인됨`).
|
||||
|
||||
### JSP XSS / CSRF
|
||||
- **XSS**(확인됨): `<c:out>` 0건이나 모든 사용자 콘텐츠 sink가 `HtmlUtils.htmlEscape()` 처리(index.jsp:506-507, recruit-detail.jsp:전 필드, recruit-list.jsp:286-292, profile.jsp:16-18, header.jsp:11, game-detail.jsp:7-21, game-register.jsp:13-17, theme-init.jsp:7). raw `<%=` sink들도 추적 결과 모두 escape된 변수 또는 안전값(ctx, id, String.format)임을 확인.
|
||||
- 게임 댓글: 클라이언트 JS가 `textContent`로 렌더(game-detail.jsp:970,983) → DOM XSS 안전. 단 서버 미저장.
|
||||
- **CSRF**(확인됨): 커스텀 더블서밋 — `theme-init.jsp`가 `<meta name="csrf-token">` + `window.BibimbapCsrf.headers()`로 `X-CSRF-Token` 헤더 주입. 검증은 `CsrfTokens.isValid()`(헤더 또는 `_csrf` 파라미터, 세션값과 `.equals` 비교). **단 login/signup 미적용**(Top#3, MED).
|
||||
- 세션/쿠키: 기본 JSESSIONID. `Secure`/`SameSite` 명시 설정 없음(application.properties 미존재 → 컨테이너 기본값. 프로덕션 HTTPS에서 Secure 미설정이면 LOW~MED 위험 — **추정**: 배포 톰캣 설정 미확인이라 코드만으로 단정 불가).
|
||||
|
||||
### 파일 업로드 보안 (양호)
|
||||
- zip-slip 방어: `target.startsWith(targetDir)` 정규화 검증(GameUploadController.java:128,200,266-269).
|
||||
- zip bomb 방어: entry 수 8000 / 압축해제 512MB 상한(line 40-41,262,297).
|
||||
- path traversal: 프로필 아바타·에셋 서빙 모두 `normalize()`+`startsWith(root)` 검증(UserController.java:248-263, GameAssetController.java:101-104). UUID 검증으로 게임 디렉토리 화이트리스트.
|
||||
- 콘텐츠 타입: 확장자 화이트리스트(png/jpg/webp/gif). **추정 한계**: MIME/확장자만 검사, 매직바이트 미검증 → polyglot 업로드 가능성(LOW, WebGL asset은 nosniff+CSP로 완화).
|
||||
|
||||
### 정정 (orientation 대비)
|
||||
- orientation은 "JSP under WEB-INF/jsp(+fragments)"를 활성 뷰처럼 기재했으나, 실제 활성 뷰는 `WEB-INF/views/*.jsp`이고 `WEB-INF/jsp/fragments/header.jspf`는 **orphan dead 잔재**(Top#5)임을 확인.
|
||||
|
||||
---
|
||||
|
||||
## D3 — 코드 품질/기술부채
|
||||
|
||||
| 항목 | 발견 | 위치 |
|
||||
|------|------|------|
|
||||
| Dead code | abstracts 패키지 4클래스 전부 미사용; GameCatalog 빈 stub+도달불가 분기; header.jspf orphan | Top#4,5,6 |
|
||||
| 미완성 기능 | game_comments 스키마·매퍼·DTO 존재하나 컨트롤러 미연결(localStorage 임시) | Top#7 |
|
||||
| 레이어 위반 | 서비스 레이어 부재 → 컨트롤러가 비즈니스 로직+매퍼 직접 호출(fat controller). UserController 629라인(해싱·파일IO·세션관리·검증 혼재) | UserController.java 전체 |
|
||||
| 중복 | `sessionUserId()` 동일 메서드가 4개 컨트롤러에 복붙(RecruitController:155, GameController:291, GameUploadController:222, UserController:325, WebMvcController:118). `trimToNull/trimToEmpty`, `imageExtension/profileImageExtension`, UUID 정규화도 중복 | 다수 |
|
||||
| 미사용 의존성 | Lombok이 pom+컴파일러 플러그인에 선언되었으나 data 클래스는 수동 getter/setter(Lombok 미활용) | pom.xml:74-79, data/*.java |
|
||||
| 오타/네이밍 | 뷰 파일명 `errer.jsp`(error 오타), 메서드명 `ChackService`(Check 오타), 필드 `is_login`(자바 네이밍 컨벤션 위반 snake_case) | views/errer.jsp, abstracts/Service.java:7,11 |
|
||||
| 미사용 코드 | `WebMvcController.isMobileDevice()` 정의만 되고 호출 없음(line 130) | WebMvcController.java:130 |
|
||||
| 에러 처리 | API는 일관(ResponseEntity+JSON, 스택 비노출). 단 `Result` 생성자의 `System.out.println("잘못된 status")`(line 21)는 로깅이 아닌 stdout | abstracts/Result.java:21 |
|
||||
| 매직값 | 역할/참여유형/상태 한글 문자열 하드코딩 Set(RecruitController:25-27); status 1000("NULL USERS") 같은 비표준 코드 | RecruitController.java:25-27 |
|
||||
| 설정 위험 | 업로드 1GB 상한(application.properties), `max-swallow-size=-1`(무제한) → DoS 표면(**추정**: 인증 게이트 뒤라 완화되나 큰 값) | application.properties |
|
||||
|
||||
### 테스트
|
||||
- `BibimbapApplicationTests.contextLoads()` — 스모크만(빈 본문).
|
||||
- `DbUpdateQueryGeneratorTest` — 실제 단위테스트 아님. dev/live DB 직접 연결해 schema/data diff SQL을 `src/test/db/dev-to-live-update.sql`로 출력하는 **운영 도구**(surefire에서 제외, pom 명시). 실DB 필요 → CI 불가.
|
||||
- **커버리지 갭**: 컨트롤러·매퍼·인증·업로드·CSRF 로직 단위/통합 테스트 전무. 보안 핵심(PBKDF2 검증, zip-slip, 소유권 인가) 미검증.
|
||||
|
||||
### pom 위생
|
||||
- **spring-boot 3.5.14-SNAPSHOT**(확인됨) — SNAPSHOT은 비재현·비프로덕션. spring-snapshots 저장소 의존. 안정 릴리스로 고정 필요(MED 부채, **추정**: 보안패치 추적 어려움).
|
||||
- Java 21, MyBatis starter 3.0.5, PostgreSQL(runtime), 의존성 수 적고 깔끔. CVE 직접 스캔 미수행(외부 조회 안 함 — `미확인`).
|
||||
|
||||
---
|
||||
|
||||
## D4 — 도메인/기능
|
||||
|
||||
### 기능 인벤토리 (기능 → 엔드포인트 / 매퍼 / 뷰 / 데이터)
|
||||
|
||||
| 기능 | 엔드포인트 | 매퍼 | 뷰 | 데이터/테이블 |
|
||||
|------|-----------|------|-----|--------------|
|
||||
| 게임 목록/검색 | GET `/`(q 검색) | GamesMapper.getVisibleGames/searchVisibleGames | index.jsp | games, users |
|
||||
| 게임 상세 | GET `/game/{id}` | getGame | game-detail.jsp | games, users |
|
||||
| 게임 등록 | GET `/game/new`(뷰), POST `/game/new` | addGame, nextSortOrder | game-register.jsp | games |
|
||||
| 게임 수정/삭제 | GET/POST `/game/{id}/edit`, DELETE `/game/{id}` | updateGame, softDeleteGame, softDeleteGameComments, deleteGameLikes | game-register.jsp(edit) | games, game_comments, game_likes |
|
||||
| WebGL 업로드 | POST `/api/game-files`, `/webgl-zip`, `/thumbnail`, GET `/ping` | — (파일시스템) | (AJAX) | 업로드 디렉토리 |
|
||||
| WebGL 서빙 | GET `/game/{uuid}/**` | — | (iframe) | 파일시스템 |
|
||||
| 팀원 모집 목록/상세 | GET `/recruit`, `/recruit/{id}` | getVisibleRecruitPosts, getRecruitPost | recruit-list.jsp, recruit-detail.jsp | recruit_posts, users |
|
||||
| 모집글 작성 | GET/POST `/recruit/new` | addRecruitPost, nextSortOrder | recruit-form.jsp | recruit_posts |
|
||||
| 회원가입/로그인/로그아웃 | POST `/signup`,`/login`,`/logout` | Users/UserAuthIdentities add/get/update | signup.jsp, login.jsp | users, user_auth_identities |
|
||||
| 프로필 | GET `/profile`(뷰), POST `/profile/nickname`,`/profile/avatar` | getUser, updateUser, getGamesByUserId | profile.jsp | users, user_auth_identities |
|
||||
| 정적 페이지 | GET `/terms`,`/operation-policy` | — | terms.jsp, operation-policy.jsp | — |
|
||||
| 게임 댓글 | **(엔드포인트 없음)** | GameCommentsMapper(미연결) | game-detail.jsp(localStorage) | game_comments(미사용) |
|
||||
| 게임 좋아요 | **(엔드포인트 없음)** | GameLikesMapper(deleteGameLikes만 호출) | game-detail.jsp(JS baseLikes) | game_likes(쓰기경로 없음) |
|
||||
|
||||
### 데이터 모델 (매퍼 SQL에서 역추출 — `확인됨`)
|
||||
- **users**(id, display_name, canonical_email, avatar_url, role, status, last_login_at, created_at, updated_at, is_delete)
|
||||
- **user_auth_identities**(id, user_id, provider, provider_user_id, email, password_hash, display_name, avatar_url, last_login_at, created_at, updated_at, is_delete) — provider='email' 고정, 소셜 확장 의도된 스키마(provider/provider_user_id)이나 email만 구현(**추정**)
|
||||
- **games**(id, user_id, name, creator_note, git_url, webgl_path, thumbnail_url, like_count, is_visible, sort_order, created_at, updated_at, is_delete)
|
||||
- **recruit_posts**(id, user_id, project_name, genre, summary, role, project_status, participation_type, expected_period, team_members, contact, description, reference_url, deadline_at, is_visible, sort_order, created_at, updated_at, is_delete)
|
||||
- **game_comments**(id, game_id, nickname, content, created_at, deleted_at, is_delete) — 매퍼는 완성, 미연결
|
||||
- **game_likes**(id, game_id, user_key, created_at)
|
||||
- 공통 패턴: `is_delete` soft-delete + `is_visible` 노출제어 + `sort_order` 정렬. 모든 조회가 `is_delete IS NOT TRUE` 필터(확인됨, 일관).
|
||||
|
||||
### 사용자 플로우
|
||||
1. 비로그인: 홈에서 게임 탐색/검색 → 게임 상세(WebGL 플레이, 댓글은 로컬), 모집 목록/상세 열람.
|
||||
2. 가입(이메일+비번 8자+약관) → 로그인(세션, remember) → 프로필(닉네임/아바타 변경).
|
||||
3. 로그인 사용자: 게임 등록(zip 업로드→썸네일→메타 저장), 본인 게임 수정/삭제, 모집글 작성.
|
||||
4. 인가: 게임 수정/삭제는 작성자만(소유권 체크).
|
||||
|
||||
---
|
||||
|
||||
## 종합 판단 (상위 패턴 · 충돌 · 갭)
|
||||
|
||||
**강점**: 보안 기초가 의외로 탄탄하다 — SQLi 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관, 세션 고정 방어, 소유권 인가 일관. 코드가 "보안을 의식하고 작성"된 흔적이 뚜렷.
|
||||
|
||||
**핵심 부채 패턴**: 프로토타입 잔재가 곳곳에 화석으로 남음 — (a)abstracts 미사용 프레임워크 시도, (b)GameCatalog 정적 카탈로그→DB 마이그레이션 후 stub만 잔존, (c)Spring Security 기반 header.jspf orphan, (d)game_comments/game_likes 서버 기능 미완성(스키마만). 이들은 "구현되다 만/대체된" 코드로, 신규 개발자에게 혼란·인증함정(abstracts의 'id' 키)을 유발.
|
||||
|
||||
**아키텍처 갭**: 서비스 레이어 부재로 fat controller(특히 UserController 629줄). 중복 헬퍼(sessionUserId 5중복) → 공통 유틸/베이스 추출 여지. 테스트 거의 전무가 가장 큰 리스크(보안 로직 회귀 무방비).
|
||||
|
||||
**보안 미결 결정**: login/signup CSRF 적용 여부, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정 — design 단계 보안 결정 필요.
|
||||
|
||||
**requirements 충돌 가능**: "레이어드 아키텍처(controller/service/mapper)" 전제가 있었다면, 실제로 service 레이어는 없음(abstracts는 dead). → concern 기재.
|
||||
|
||||
---
|
||||
|
||||
## 미해결 / open_questions
|
||||
|
||||
1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`).
|
||||
2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요.
|
||||
3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`.
|
||||
4. spring-boot 3.5.14-SNAPSHOT을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`.
|
||||
5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/cache
|
||||
/project.local.yml
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
# the name by which the project can be referenced within Serena
|
||||
project_name: "bibimbap"
|
||||
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al angular ansible bash clojure
|
||||
# cpp cpp_ccls crystal csharp csharp_omnisharp
|
||||
# dart elixir elm erlang fortran
|
||||
# fsharp go groovy haskell haxe
|
||||
# hlsl html java json julia
|
||||
# kotlin lean4 lua luau markdown
|
||||
# matlab msl nix ocaml pascal
|
||||
# perl php php_phpactor powershell python
|
||||
# python_jedi python_ty r rego ruby
|
||||
# ruby_solargraph rust scala scss solidity
|
||||
# svelte swift systemverilog terraform toml
|
||||
# typescript typescript_vts vue yaml zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
# Note:
|
||||
# - For C, use cpp
|
||||
# - For JavaScript, use typescript
|
||||
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
|
||||
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
|
||||
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
|
||||
# - For Free Pascal/Lazarus, use pascal
|
||||
# Special requirements:
|
||||
# Some languages require additional setup/installations.
|
||||
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||
# The first language is the default language and the respective language server will be used as a fallback.
|
||||
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||
languages:
|
||||
- java
|
||||
|
||||
# the encoding used by text files in the project
|
||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||
encoding: "utf-8"
|
||||
|
||||
# line ending convention to use when writing source files.
|
||||
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||
line_ending:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
# whether to use project's .gitignore files to ignore files
|
||||
ignore_all_files_in_gitignore: true
|
||||
|
||||
# advanced configuration option allowing to configure language server-specific options.
|
||||
# Maps the language key to the options.
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||
# No documentation on options means no options are available.
|
||||
ls_specific_settings: {}
|
||||
|
||||
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
|
||||
# Paths can be absolute or relative to the project root.
|
||||
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
|
||||
# symbols and references across package boundaries.
|
||||
# Currently supported for: TypeScript.
|
||||
# Example:
|
||||
# additional_workspace_folders:
|
||||
# - ../sibling-package
|
||||
# - ../shared-lib
|
||||
additional_workspace_folders: []
|
||||
|
||||
# list of additional paths to ignore in this project.
|
||||
# Same syntax as gitignore, so you can use * and **.
|
||||
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# list of tool names to exclude.
|
||||
# This extends the existing exclusions (e.g. from the global configuration)
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||
# This extends the existing inclusions (e.g. from the global configuration).
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||
fixed_tools: []
|
||||
|
||||
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
||||
# for this project.
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
default_modes:
|
||||
|
||||
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
||||
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||
added_modes:
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||
# such as docstrings or parameter information.
|
||||
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||
# If null or missing, use the setting from the global configuration.
|
||||
symbol_info_budget:
|
||||
|
||||
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
read_only_memory_patterns: []
|
||||
|
||||
# list of regex patterns for memories to completely ignore.
|
||||
# Matching memories will not appear in list_memories or activate_project output
|
||||
# and cannot be accessed via read_memory or write_memory.
|
||||
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
# Example: ["_archive/.*", "_episodes/.*"]
|
||||
ignored_memory_patterns: []
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<!-- atp:begin -->
|
||||
## 문서화 정책 (docs-first)
|
||||
작업 시작 전 `docs/index.md` → 카테고리 `index.md` → 구체 문서 순으로 읽는다.
|
||||
## 에이전트 팀 운영
|
||||
`/atp:task [요청]` 으로 Orchestrator+Advisor+Worker 팀 모드 진입(명시 호출 전용).
|
||||
위임 토폴로지는 호스트 capability 자가판정(Tier A/A-flat/B)을 따른다 — 번들 `docs/development/platform-adapters.md`.
|
||||
권위 레퍼런스: atp 플러그인 번들 `docs/development/agent-team-protocol.md`.
|
||||
<!-- atp:end -->
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# ADR 인덱스
|
||||
|
||||
**카테고리 용도**: 기술 선택·아키텍처 원칙에 대한 되돌리기 어려운 결정. 불변(append-only). 결정을 뒤집을 때는 기존 ADR 을 수정하지 않고 새 ADR 을 발행하여 "supersedes ADR-NNNN" 명시.
|
||||
|
||||
파일명 규칙: `ADR-NNNN-kebab-case-title.md` (NNNN 은 0001 부터 순차 증가)
|
||||
|
||||
## 결정 목록
|
||||
|
||||
| ADR | 제목 | 상태 | 날짜 |
|
||||
|---|---|---|---|
|
||||
| *(아직 등록된 ADR 없음)* | | | |
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
---
|
||||
kind: analysis
|
||||
title: bibimbap 종합 코드 분석 (아키텍처/보안/품질/도메인)
|
||||
description: Spring Boot 3 MVC + MyBatis + PostgreSQL + JSP 웹앱의 4차원(D1~D4) 전면 read-only 분석. 보안 기초·기술부채·도메인 인벤토리·미결 결정 정리.
|
||||
perspective: neutral
|
||||
valid_starting_point_for: 현황 파악, 보안 결정 입력(login/signup CSRF·세션 쿠키·SNAPSHOT 고정), 기술부채 정리(dead code 제거·서비스 레이어 추출), 미완성 기능(게임 댓글/좋아요) 방향 결정
|
||||
owner: art
|
||||
stability: snapshot
|
||||
last_reviewed: 2026-06-16
|
||||
source: work-session 20260616-111711 (research-advisor, analysis.md) 에서 distill
|
||||
superseded_note: 후속 결정으로 전제가 바뀌면 이 줄을 갱신한다 — 현재 없음
|
||||
---
|
||||
|
||||
# bibimbap 종합 코드 분석 (D1~D4)
|
||||
|
||||
> 분석 방식: 30개 Java 파일 + 16개 JSP + 6개 MyBatis 매퍼 전수 직접 읽기(Read/Grep/Bash). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 `추정`/`미확인` 으로 명시했으며 사실로 격상하지 않았다.
|
||||
>
|
||||
> 출처: work-session `20260616-111711` 의 `research/analysis.md` 를 정제한 정본 기록.
|
||||
|
||||
## 요약
|
||||
|
||||
Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석. 4개 차원으로 정리한다: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4).
|
||||
|
||||
상위 결론: **보안 기초는 의외로 탄탄**(SQLi 표면 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관)하나, **프로토타입 잔재성 dead code 와 미완성 기능이 화석으로 남아** 신규 개발자에게 혼란·인증 함정을 유발한다. 서비스 레이어 부재로 fat controller 가 발생했고, 테스트가 거의 전무하다.
|
||||
|
||||
## 주요 발견 Top 10
|
||||
|
||||
| # | 심각도 | 발견 | 위치 |
|
||||
|---|--------|------|------|
|
||||
| 1 | INFO(긍정) | **SQL 인젝션 표면 0** — 6개 매퍼 전 쿼리가 파라미터 바인딩 `#{}` 만 사용. `${}` 문자열 보간 단 1건도 없음(java/xml 전수 grep). 검색어도 `ILIKE CONCAT('%', #{query}, '%')` 로 안전. | mapper/*.java 전체 |
|
||||
| 2 | INFO(긍정) | **비밀번호 해싱 견고** — PBKDF2WithHmacSHA256, 210,000 iterations, 16B salt, 256bit, `MessageDigest.isEqual`(상수시간 비교). 형식 `pbkdf2_sha256$iter$salt$hash`. | UserController.java:542-576 |
|
||||
| 3 | MED | **POST /login, /signup CSRF 미검증** — logout/profile/game/recruit 모든 mutation 은 `CsrfTokens.isValid()` 검사하나 login·signup 은 누락 → login-CSRF 가능. | UserController.java:118-160(login), 65-116(signup) |
|
||||
| 4 | MED(부채) | **abstracts 패키지 전체 dead code** — `Service`/`Request`/`Result`/`ErrorResult` 어디서도 상속·호출 안 됨. `Service.ChackService` 는 세션키 `"id"`(line 12) 사용, 활성 코드는 `"userId"` 사용 → 재사용 시 인증 우회 함정. | abstracts/Service.java:12 |
|
||||
| 5 | MED(부채) | **orphan 깨진 JSP fragment** — `WEB-INF/jsp/fragments/header.jspf` 가 Spring Security 태그(`<sec:authorize>`, `${_csrf}`)와 JSTL 사용하나 (a)어디서도 include 안 됨 (b)Spring Security 의존성 자체가 pom 에 없음 (c)라우트(`/games/new`,`/register`)·브랜딩('비빔밥')이 활성앱(`/game/new`,`/signup`,'bibimbap')과 불일치. 이전 프로토타입 잔재. | jsp/fragments/header.jspf:1-25 |
|
||||
| 6 | MED(부채) | **GameCatalog 완전 stub** — 모든 배열이 `{}` 빈 배열, `COUNT=0`. `GameController.gameDetail` 의 폴백 분기(line 111-128)는 영구 도달 불가(`isValidId` 는 항상 false). 죽은 분기 + 죽은 클래스. | game/GameCatalog.java:8-22, controller/api/GameController.java:111-128 |
|
||||
| 7 | MED(부채) | **게임 댓글: DB 스키마·매퍼 존재하나 미연결** — `game_comments` 테이블, `GameCommentsMapper`(get/add/update), `GameCommentData` 모두 존재하지만 **댓글 작성/조회 컨트롤러 엔드포인트 없음**. 실제 댓글은 JSP 내 localStorage 클라이언트 전용(서버 미저장). `softDeleteGameComments` 만 게임 삭제 시 호출됨. | mapper/GameCommentsMapper.java, views/game-detail.jsp:907-1000 |
|
||||
| 8 | LOW | **WebApplicationFirewall/인증필터 부재 — 세션 기반 임시방편 인증** — Spring Security 미사용. 각 컨트롤러가 수동으로 `session.getAttribute("userId")` 체크. 인가는 리소스 소유권 체크(`userId.equals(game.getUserId())`)로 일관 처리됨(양호)하나 중앙 집중 필터 없음 → 신규 엔드포인트에서 체크 누락 위험. | 전 컨트롤러 |
|
||||
| 9 | LOW | **생성 산출물 git 추적** — `src/test/db/dev-to-live-update.sql`(테스트가 생성하는 dev→live 마이그레이션 진단)이 커밋됨. 실제 비밀값은 없고(`-- DATA DIFF ... password_hash` 주석뿐) 비밀 누출 아님. 단 생성물은 비추적이 적절. | src/test/db/dev-to-live-update.sql:54 |
|
||||
| 10 | INFO | **XSS 방어 일관 적용(비관습적)** — JSTL `<c:out>` 0건이나, 모든 JSP 가 Java scriptlet 변수 할당 시점에 `HtmlUtils.htmlEscape()` 적용 후 출력. 사용자 콘텐츠 sink(게임명/제작자/모집글/닉네임/이메일/창작노트/검색어) 전수 확인 결과 escape 누락 없음. CSP·X-Content-Type-Options 도 WebGL asset 응답에 설정. | views/*.jsp 전체, GameAssetController.java:60-66 |
|
||||
|
||||
---
|
||||
|
||||
## D1 — 아키텍처/구조
|
||||
|
||||
### 패키지별 책임 (1줄)
|
||||
|
||||
| 패키지 | 역할 |
|
||||
|--------|------|
|
||||
| (root) | `BibimbapApplication`(@SpringBootApplication main), `ServletInitializer`(WAR 배포용 SpringBootServletInitializer) |
|
||||
| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) |
|
||||
| config | `UploadResourceConfig` — 업로드 경로의 `/profile/**` 정적 리소스 핸들러 등록 |
|
||||
| controller | 페이지(뷰) 컨트롤러 — `WebMvcController`(공통 페이지+에러), `RecruitController`(모집 페이지+API 혼합) |
|
||||
| controller/api | API/mutation 컨트롤러 — Game CRUD, GameUpload(zip/썸네일/파일), GameAsset(WebGL 서빙), User(인증/프로필), 예외 핸들러 |
|
||||
| data | 순수 POJO DTO 6종 (Lombok 미사용 — 수동 getter/setter) |
|
||||
| game | `GameCatalog` — **stub(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) |
|
||||
| mapper | MyBatis `@Mapper` 인터페이스 6종 — 어노테이션 기반 SQL(XML 매퍼 0) |
|
||||
| security | `CsrfTokens` — 세션 기반 CSRF 토큰 발급/검증 유틸(static) |
|
||||
|
||||
### 요청 흐름
|
||||
|
||||
- **페이지 렌더**: Controller → (서비스 레이어 없음) → Mapper → DB → ModelAndView/Model → `/WEB-INF/views/*.jsp` (ViewResolver prefix/suffix 는 application.properties).
|
||||
- **API mutation**: `@PostMapping`/`@DeleteMapping` → CSRF 검증 → 세션 인증 → 입력 검증 → Mapper → JSON ResponseEntity.
|
||||
- **서비스 레이어 부재**(확인됨): 컨트롤러가 매퍼를 직접 주입·호출. `@Transactional` 은 컨트롤러 메서드에 부착. → requirements 가 "service 레이어 존재" 를 전제했다면 충돌(아래 종합 판단 참조).
|
||||
- **JSP 렌더 방식**(확인됨): JSTL 미사용. Java scriptlet(`<%%>`/`<%=%>`)으로 `request.getAttribute()` 직접 캐스팅 + `HtmlUtils.htmlEscape()`.
|
||||
|
||||
### 레이어별 핵심 클래스
|
||||
|
||||
| 레이어 | 클래스 | 역할(1줄) |
|
||||
|--------|--------|----------|
|
||||
| 진입 | BibimbapApplication / ServletInitializer | main 부트 / WAR 배포 부트 |
|
||||
| 페이지 | WebMvcController | `/`,`/{pageName}`(화이트리스트), `/error`, 프로필·게임등록 뷰 게이팅 |
|
||||
| 페이지+API | RecruitController | `/recruit`(목록), `/recruit/new`(폼+생성 POST), `/recruit/{id}`(상세) |
|
||||
| API | GameController | 게임 생성/수정/삭제(soft) + 상세/편집 뷰 + 소유권 인가 |
|
||||
| API | GameUploadController | WebGL zip 업로드(zip-slip 방어), 썸네일·일반파일 업로드 |
|
||||
| API | GameAssetController | `/game/{uuid}/**` WebGL 정적 서빙 + CSP/인코딩 헤더 |
|
||||
| API | UserController | signup/login/logout/프로필(닉네임·아바타) + PBKDF2 해싱 |
|
||||
| API | ApiExceptionControllerAdvice | 업로드 초과·일반 예외 → JSON 에러(스택 비노출) |
|
||||
| 매퍼 | Games/RecruitPosts/Users/UserAuthIdentities/GameComments/GameLikes Mapper | 어노테이션 SQL CRUD |
|
||||
|
||||
### Spring/MyBatis 배선·프로파일
|
||||
|
||||
- `@SpringBootApplication` 단일. MyBatis 는 `mybatis-spring-boot-starter` 자동 배선 + `@Mapper` 스캔(별도 @MapperScan 불필요, 확인됨).
|
||||
- 프로파일: pom `dev`/`live` 프로파일이 `app.profile` 치환 → `application.properties` 의 `spring.profiles.active=@app.profile@`(빌드 필터링) → `spring.config.import` 로 `${profile}/db.properties` 로드. dev/live 는 PostgreSQL 동일 호스트·다른 schema(`currentSchema=dev|live`).
|
||||
- DB props: `db.properties.example` 만 git 추적, 실제 `db.properties` 는 .gitignore 처리(확인됨: gitignore `### Local secrets ###`).
|
||||
- 앱 진입: embedded Tomcat(`spring-boot-starter-web`) + WAR(`tomcat-embed-jasper` for JSP, `spring-boot-starter-tomcat` provided). web.xml 없음(Servlet 3+ initializer).
|
||||
|
||||
---
|
||||
|
||||
## D2 — 보안 (최우선)
|
||||
|
||||
### 인증/인가
|
||||
|
||||
- **메커니즘**(확인됨): Spring Security 미사용(의존성 없음). `HttpSession` 속성 기반 커스텀. login 시 `request.changeSessionId()` 로 세션 고정 공격 방어(UserController.java:152). remember 옵션으로 세션 타임아웃 30분/30일 분기.
|
||||
- **인가**(확인됨): 리소스 mutation 마다 `userId.equals(resource.getUserId())` 소유권 체크 일관 적용(GameController updateGame:183, deleteGame:239, editView:142). role 기반 인가는 미구현(role 컬럼은 저장만, 'USER' 고정).
|
||||
|
||||
### 자격증명/시크릿
|
||||
|
||||
- 하드코딩 시크릿 **없음**(확인됨): tracked 파일은 `*.example`(placeholder `your_username/your_password`)뿐. 실제 db.properties 는 gitignore. application.properties 에 비밀값 없음.
|
||||
- 비밀번호 저장: PBKDF2-SHA256 210k iter(상기 Top#2). 평문·약한해시 없음.
|
||||
|
||||
### SQL 인젝션 (전수 — `${` vs `#{`)
|
||||
|
||||
- **전 매퍼 `${}` 보간 0건**(확인됨, grep 전수). 모든 동적값은 `#{}` 또는 `@Param`+`#{}`. 검색: `searchVisibleGames` 도 `ILIKE CONCAT('%', #{query}, '%')` 로 안전(GamesMapper.java:85-87).
|
||||
- 단, **테스트 코드** `DbUpdateQueryGeneratorTest` 는 `String.formatted()` 로 schema 명을 SQL 에 직접 삽입(line 333,369) + `quoteIdentifier`/`escapeSql` 로 수동 이스케이프. 이는 프로덕션 경로 아님(테스트 전용, surefire 에서 제외됨)이나 패턴상 주의(LOW, 입력이 상수 'dev'/'live' 라 실위험 없음 — `확인됨`).
|
||||
|
||||
### JSP XSS / CSRF
|
||||
|
||||
- **XSS**(확인됨): `<c:out>` 0건이나 모든 사용자 콘텐츠 sink 가 `HtmlUtils.htmlEscape()` 처리(index.jsp:506-507, recruit-detail.jsp:전 필드, recruit-list.jsp:286-292, profile.jsp:16-18, header.jsp:11, game-detail.jsp:7-21, game-register.jsp:13-17, theme-init.jsp:7). raw `<%=` sink 들도 추적 결과 모두 escape 된 변수 또는 안전값(ctx, id, String.format)임을 확인.
|
||||
- 게임 댓글: 클라이언트 JS 가 `textContent` 로 렌더(game-detail.jsp:970,983) → DOM XSS 안전. 단 서버 미저장.
|
||||
- **CSRF**(확인됨): 커스텀 더블서밋 — `theme-init.jsp` 가 `<meta name="csrf-token">` + `window.BibimbapCsrf.headers()` 로 `X-CSRF-Token` 헤더 주입. 검증은 `CsrfTokens.isValid()`(헤더 또는 `_csrf` 파라미터, 세션값과 `.equals` 비교). **단 login/signup 미적용**(Top#3, MED).
|
||||
- 세션/쿠키: 기본 JSESSIONID. `Secure`/`SameSite` 명시 설정 없음(application.properties 미존재 → 컨테이너 기본값. 프로덕션 HTTPS 에서 Secure 미설정이면 LOW~MED 위험 — **추정**: 배포 톰캣 설정 미확인이라 코드만으로 단정 불가).
|
||||
|
||||
### 파일 업로드 보안 (양호)
|
||||
|
||||
- zip-slip 방어: `target.startsWith(targetDir)` 정규화 검증(GameUploadController.java:128,200,266-269).
|
||||
- zip bomb 방어: entry 수 8000 / 압축해제 512MB 상한(line 40-41,262,297).
|
||||
- path traversal: 프로필 아바타·에셋 서빙 모두 `normalize()`+`startsWith(root)` 검증(UserController.java:248-263, GameAssetController.java:101-104). UUID 검증으로 게임 디렉토리 화이트리스트.
|
||||
- 콘텐츠 타입: 확장자 화이트리스트(png/jpg/webp/gif). **추정 한계**: MIME/확장자만 검사, 매직바이트 미검증 → polyglot 업로드 가능성(LOW, WebGL asset 은 nosniff+CSP 로 완화).
|
||||
|
||||
### 정정 (orientation 대비)
|
||||
|
||||
- orientation 은 "JSP under WEB-INF/jsp(+fragments)" 를 활성 뷰처럼 기재했으나, 실제 활성 뷰는 `WEB-INF/views/*.jsp` 이고 `WEB-INF/jsp/fragments/header.jspf` 는 **orphan dead 잔재**(Top#5)임을 확인.
|
||||
|
||||
---
|
||||
|
||||
## D3 — 코드 품질/기술부채
|
||||
|
||||
| 항목 | 발견 | 위치 |
|
||||
|------|------|------|
|
||||
| Dead code | abstracts 패키지 4클래스 전부 미사용; GameCatalog 빈 stub+도달불가 분기; header.jspf orphan | Top#4,5,6 |
|
||||
| 미완성 기능 | game_comments 스키마·매퍼·DTO 존재하나 컨트롤러 미연결(localStorage 임시) | Top#7 |
|
||||
| 레이어 위반 | 서비스 레이어 부재 → 컨트롤러가 비즈니스 로직+매퍼 직접 호출(fat controller). UserController 629라인(해싱·파일IO·세션관리·검증 혼재) | UserController.java 전체 |
|
||||
| 중복 | `sessionUserId()` 동일 메서드가 4개 컨트롤러에 복붙(RecruitController:155, GameController:291, GameUploadController:222, UserController:325, WebMvcController:118). `trimToNull/trimToEmpty`, `imageExtension/profileImageExtension`, UUID 정규화도 중복 | 다수 |
|
||||
| 미사용 의존성 | Lombok 이 pom+컴파일러 플러그인에 선언되었으나 data 클래스는 수동 getter/setter(Lombok 미활용) | pom.xml:74-79, data/*.java |
|
||||
| 오타/네이밍 | 뷰 파일명 `errer.jsp`(error 오타), 메서드명 `ChackService`(Check 오타), 필드 `is_login`(자바 네이밍 컨벤션 위반 snake_case) | views/errer.jsp, abstracts/Service.java:7,11 |
|
||||
| 미사용 코드 | `WebMvcController.isMobileDevice()` 정의만 되고 호출 없음(line 130) | WebMvcController.java:130 |
|
||||
| 에러 처리 | API 는 일관(ResponseEntity+JSON, 스택 비노출). 단 `Result` 생성자의 `System.out.println("잘못된 status")`(line 21)는 로깅이 아닌 stdout | abstracts/Result.java:21 |
|
||||
| 매직값 | 역할/참여유형/상태 한글 문자열 하드코딩 Set(RecruitController:25-27); status 1000("NULL USERS") 같은 비표준 코드 | RecruitController.java:25-27 |
|
||||
| 설정 위험 | 업로드 1GB 상한(application.properties), `max-swallow-size=-1`(무제한) → DoS 표면(**추정**: 인증 게이트 뒤라 완화되나 큰 값) | application.properties |
|
||||
|
||||
### 테스트
|
||||
|
||||
- `BibimbapApplicationTests.contextLoads()` — 스모크만(빈 본문).
|
||||
- `DbUpdateQueryGeneratorTest` — 실제 단위테스트 아님. dev/live DB 직접 연결해 schema/data diff SQL 을 `src/test/db/dev-to-live-update.sql` 로 출력하는 **운영 도구**(surefire 에서 제외, pom 명시). 실DB 필요 → CI 불가. (참조: [db-update-query-generator.md](../db-update-query-generator.md))
|
||||
- **커버리지 갭**: 컨트롤러·매퍼·인증·업로드·CSRF 로직 단위/통합 테스트 전무. 보안 핵심(PBKDF2 검증, zip-slip, 소유권 인가) 미검증.
|
||||
|
||||
### pom 위생
|
||||
|
||||
- **spring-boot 3.5.14-SNAPSHOT**(확인됨) — SNAPSHOT 은 비재현·비프로덕션. spring-snapshots 저장소 의존. 안정 릴리스로 고정 필요(MED 부채, **추정**: 보안패치 추적 어려움).
|
||||
- Java 21, MyBatis starter 3.0.5, PostgreSQL(runtime), 의존성 수 적고 깔끔. CVE 직접 스캔 미수행(외부 조회 안 함 — `미확인`).
|
||||
|
||||
---
|
||||
|
||||
## D4 — 도메인/기능
|
||||
|
||||
### 기능 인벤토리 (기능 → 엔드포인트 / 매퍼 / 뷰 / 데이터)
|
||||
|
||||
| 기능 | 엔드포인트 | 매퍼 | 뷰 | 데이터/테이블 |
|
||||
|------|-----------|------|-----|--------------|
|
||||
| 게임 목록/검색 | GET `/`(q 검색) | GamesMapper.getVisibleGames/searchVisibleGames | index.jsp | games, users |
|
||||
| 게임 상세 | GET `/game/{id}` | getGame | game-detail.jsp | games, users |
|
||||
| 게임 등록 | GET `/game/new`(뷰), POST `/game/new` | addGame, nextSortOrder | game-register.jsp | games |
|
||||
| 게임 수정/삭제 | GET/POST `/game/{id}/edit`, DELETE `/game/{id}` | updateGame, softDeleteGame, softDeleteGameComments, deleteGameLikes | game-register.jsp(edit) | games, game_comments, game_likes |
|
||||
| WebGL 업로드 | POST `/api/game-files`, `/webgl-zip`, `/thumbnail`, GET `/ping` | — (파일시스템) | (AJAX) | 업로드 디렉토리 |
|
||||
| WebGL 서빙 | GET `/game/{uuid}/**` | — | (iframe) | 파일시스템 |
|
||||
| 팀원 모집 목록/상세 | GET `/recruit`, `/recruit/{id}` | getVisibleRecruitPosts, getRecruitPost | recruit-list.jsp, recruit-detail.jsp | recruit_posts, users |
|
||||
| 모집글 작성 | GET/POST `/recruit/new` | addRecruitPost, nextSortOrder | recruit-form.jsp | recruit_posts |
|
||||
| 회원가입/로그인/로그아웃 | POST `/signup`,`/login`,`/logout` | Users/UserAuthIdentities add/get/update | signup.jsp, login.jsp | users, user_auth_identities |
|
||||
| 프로필 | GET `/profile`(뷰), POST `/profile/nickname`,`/profile/avatar` | getUser, updateUser, getGamesByUserId | profile.jsp | users, user_auth_identities |
|
||||
| 정적 페이지 | GET `/terms`,`/operation-policy` | — | terms.jsp, operation-policy.jsp | — |
|
||||
| 게임 댓글 | **(엔드포인트 없음)** | GameCommentsMapper(미연결) | game-detail.jsp(localStorage) | game_comments(미사용) |
|
||||
| 게임 좋아요 | **(엔드포인트 없음)** | GameLikesMapper(deleteGameLikes 만 호출) | game-detail.jsp(JS baseLikes) | game_likes(쓰기경로 없음) |
|
||||
|
||||
### 데이터 모델 (매퍼 SQL 에서 역추출 — `확인됨`)
|
||||
|
||||
- **users**(id, display_name, canonical_email, avatar_url, role, status, last_login_at, created_at, updated_at, is_delete)
|
||||
- **user_auth_identities**(id, user_id, provider, provider_user_id, email, password_hash, display_name, avatar_url, last_login_at, created_at, updated_at, is_delete) — provider='email' 고정, 소셜 확장 의도된 스키마(provider/provider_user_id)이나 email 만 구현(**추정**). 상세: [user-signup-schema.md](../user-signup-schema.md)
|
||||
- **games**(id, user_id, name, creator_note, git_url, webgl_path, thumbnail_url, like_count, is_visible, sort_order, created_at, updated_at, is_delete)
|
||||
- **recruit_posts**(id, user_id, project_name, genre, summary, role, project_status, participation_type, expected_period, team_members, contact, description, reference_url, deadline_at, is_visible, sort_order, created_at, updated_at, is_delete)
|
||||
- **game_comments**(id, game_id, nickname, content, created_at, deleted_at, is_delete) — 매퍼는 완성, 미연결
|
||||
- **game_likes**(id, game_id, user_key, created_at)
|
||||
- 공통 패턴: `is_delete` soft-delete + `is_visible` 노출제어 + `sort_order` 정렬. 모든 조회가 `is_delete IS NOT TRUE` 필터(확인됨, 일관).
|
||||
|
||||
### 사용자 플로우
|
||||
|
||||
1. 비로그인: 홈에서 게임 탐색/검색 → 게임 상세(WebGL 플레이, 댓글은 로컬), 모집 목록/상세 열람.
|
||||
2. 가입(이메일+비번 8자+약관) → 로그인(세션, remember) → 프로필(닉네임/아바타 변경).
|
||||
3. 로그인 사용자: 게임 등록(zip 업로드→썸네일→메타 저장), 본인 게임 수정/삭제, 모집글 작성.
|
||||
4. 인가: 게임 수정/삭제는 작성자만(소유권 체크).
|
||||
|
||||
---
|
||||
|
||||
## 종합 판단 (상위 패턴 · 충돌 · 갭)
|
||||
|
||||
**강점**: 보안 기초가 의외로 탄탄하다 — SQLi 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관, 세션 고정 방어, 소유권 인가 일관. 코드가 "보안을 의식하고 작성" 된 흔적이 뚜렷.
|
||||
|
||||
**핵심 부채 패턴**: 프로토타입 잔재가 곳곳에 화석으로 남음 — (a)abstracts 미사용 프레임워크 시도, (b)GameCatalog 정적 카탈로그→DB 마이그레이션 후 stub 만 잔존, (c)Spring Security 기반 header.jspf orphan, (d)game_comments/game_likes 서버 기능 미완성(스키마만). 이들은 "구현되다 만/대체된" 코드로, 신규 개발자에게 혼란·인증함정(abstracts 의 'id' 키)을 유발.
|
||||
|
||||
**아키텍처 갭**: 서비스 레이어 부재로 fat controller(특히 UserController 629줄). 중복 헬퍼(sessionUserId 5중복) → 공통 유틸/베이스 추출 여지. 테스트 거의 전무가 가장 큰 리스크(보안 로직 회귀 무방비).
|
||||
|
||||
**보안 미결 결정**: login/signup CSRF 적용 여부, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정 — design 단계 보안 결정 필요.
|
||||
|
||||
**requirements 충돌 가능**: "레이어드 아키텍처(controller/service/mapper)" 전제가 있었다면, 실제로 service 레이어는 없음(abstracts 는 dead). → 후속 작업에서 전제 재확인 필요.
|
||||
|
||||
## 미해결 / open_questions
|
||||
|
||||
1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`).
|
||||
2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요.
|
||||
3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`.
|
||||
4. spring-boot 3.5.14-SNAPSHOT 을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`.
|
||||
5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장.
|
||||
|
||||
## 관련 링크 (References)
|
||||
|
||||
- 출처(work-session): `.atp/work-session/20260616-111711/research/analysis.md`
|
||||
- [db-update-query-generator.md](../db-update-query-generator.md) — `DbUpdateQueryGeneratorTest` 운영 도구(D3 테스트 항목 관련)
|
||||
- [user-signup-schema.md](../user-signup-schema.md) — `users`/`user_auth_identities` 스키마 상세(D4 데이터 모델 관련)
|
||||
- 카테고리 분류 기준: [development/document-category-classification.md](../development/document-category-classification.md)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Analysis — 코드/흐름/성능/리스크 분석
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
- [2026-06-16-project-analysis.md](./2026-06-16-project-analysis.md) — bibimbap 전면 코드 분석(D1 아키텍처/D2 보안/D3 품질·기술부채/D4 도메인). 보안 기초는 견고(SQLi 0·PBKDF2·CSRF 더블서밋)하나 프로토타입 잔재 dead code·미완성 기능·서비스 레이어 부재가 주요 부채. `perspective: neutral`, work-session 20260616-111711 distill.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
kind: architecture
|
||||
title: Architecture 카테고리 인덱스
|
||||
description: 시스템 경계·저장 구조·구성 파일 맵 등 아키텍처 문서 인덱스.
|
||||
owner: template-maintainer
|
||||
stability: stable
|
||||
last_reviewed: 2026-05-07
|
||||
---
|
||||
|
||||
# Architecture — 시스템 경계 / 저장 구조 / 구성 파일 맵
|
||||
|
||||
이 카테고리는 본 템플릿의 **오래 유지될 구조 설명** 을 모은다. 런타임 동작 변경 이력은 `changes/`, 기술 선택 결정은 `adr/` 로 분리.
|
||||
|
||||
## 목록
|
||||
|
||||
- [file-map.md](./file-map.md) — 템플릿 루트의 구성 파일 트리 + 파일별 역할 + 런타임 생성 디렉토리 (원 위치: 과거 README §3)
|
||||
|
||||
## 관련 카테고리
|
||||
|
||||
- 기술/운영 결정의 **불변 레코드** 는 `../adr/`
|
||||
- 재사용 가능한 **개발 규칙** 은 `../development/`
|
||||
- graphify 산출물 메타는 `../graph/`
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Backlog — 미채택 아이디어, 재검토 트리거
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Changes — 실제 동작이 바뀐 구현 변경 이력
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Contracts — 외부/내부 계약 스펙
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -95,3 +95,7 @@ $env:JAVA_HOME='C:\Users\acst0\.jdks\azul-21.0.10'
|
|||
- 따라서 SQL을 실행할 때는 반드시 `live` 스키마가 기본 스키마로 잡힌 연결에서 실행해야 한다.
|
||||
- `dev` DB 접속 정보는 `src/main/resources/dev/db.properties`를 사용한다.
|
||||
- `live` DB 접속 정보는 `src/main/resources/live/db.properties`를 사용한다.
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [analysis/2026-06-16-project-analysis.md](./analysis/2026-06-16-project-analysis.md) — 이 유틸을 "운영 도구(실DB 필요·CI 불가, surefire 제외)" 로 분류한 D3 테스트 분석 참조.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
# 문서 카테고리 분류 기준
|
||||
|
||||
## 목적
|
||||
|
||||
`changes/` 남용을 줄이고, 문서의 주된 목적에 따라 카테고리를 일관되게 선택하기 위한 기준이다.
|
||||
|
||||
> 프로젝트 성격에 따라 아래 카테고리 목록에서 해당 없는 것은 삭제하고, 필요한 카테고리는 추가한다. 예: 웹 UI 가 있는 프로젝트는 `uiux-qa-report/` 를 추가할 수 있다.
|
||||
|
||||
## 핵심 원칙
|
||||
|
||||
- 카테고리는 "무엇이 바뀌었는가" 보다 **"이 문서를 나중에 왜 다시 찾는가"** 를 기준으로 선택한다.
|
||||
- 한 문서에는 하나의 주 목적만 둔다.
|
||||
- 문서가 두 목적을 동시에 가지면, 오래 참조할 기준 문서를 우선 만들고 필요할 때만 보조 문서를 추가한다.
|
||||
|
||||
## 카테고리 선택 기준
|
||||
|
||||
| 카테고리 | 이럴 때 사용 | 이럴 때는 사용하지 않음 |
|
||||
| --- | --- | --- |
|
||||
| `adr/` | 기술 선택/아키텍처 원칙에 대한 **불변 결정 레코드**. ORM/프레임워크/배포 방식/레이어 경계 등 되돌리기 어려운 결정 | 단건 구현 변경, 일회성 분석 |
|
||||
| `analysis/` | 코드/데이터 흐름/성능/리스크를 이해하거나 원인을 추적하는 문서 | 수정 결과 changelog, 장기 운영 기준, 계약 스펙 |
|
||||
| `architecture/` | 시스템 경계, 레이어 규칙, 저장 구조, 데이터 흐름, 스케줄러 설계처럼 오래 유지될 구조 설명 | 단건 변경 이력, 일회성 조사, ADR 에 가까운 결정(→ `adr/`) |
|
||||
| `backlog/` | 미채택 자동화/기능 아이디어, 재검토 트리거 조건 기록. 구현되지 않았지만 흔적 남길 가치 있는 것 | 확정된 로드맵, 완료된 작업 기록 |
|
||||
| `changes/` | 이미 반영된 구현 변경의 이력(changelog). 런타임 동작·스케줄러 룰·DB 스키마·외부 클라이언트 계약이 실제로 바뀐 경우 | 분석만 한 경우, 문서만 동기화한 경우, 재사용 가이드/절차, 장애 조사만 있고 수정이 없는 경우 |
|
||||
| `contracts/` | 외부/내부 **계약 기준 문서**. HTTP 엔드포인트 계약, CLI/커맨드 스펙, 내부 모듈 간 인터페이스 | 특정 날짜의 변경 이력, 조사 메모 |
|
||||
| `development/` | 반복해서 재사용할 개발 규칙, 절차, 워크플로우, 툴 운영 기준. 이 문서 자체도 여기 속함 | 특정 구현 변경 이력, 단건 장애 보고서 |
|
||||
| `domain/` | 프로젝트가 다루는 **도메인 지식** (업계 규칙, 용어 정의, 비즈니스 룰 등) | 구현 변경 이력, 내부 구조 |
|
||||
| `issues/` | 운영 이슈, 장애, 재현 조건, 영향 범위, 원인, 대응 내역 | 일반 기능 변경 기록, 장기 가이드 |
|
||||
| `maintenance/` | 운영자가 따라야 하는 점검, 수동 조치, 배치, 데이터 정리, 유지보수 절차 | 개발 규칙, 분석 보고서 |
|
||||
| `security/` | 인증/인가, 입력 검증, 비밀 값 관리, 보안 정책/패턴을 설명하는 기준 문서 | 특정 기능 수정 이력 자체 |
|
||||
| `usage/` | 이식자·사용자 관점 운영 가이드, FAQ, 체크리스트 | 내부 구조 설명(→ `architecture/`), 개발 규칙(→ `development/`) |
|
||||
| `work-log/` | 작업 중간 기록, 세션 간 handoff 메모, 시점성 있는 진행 로그 | 최종 기준 문서, 장기 참조용 설명 |
|
||||
| `graph/` | **자동 생성** (graphify). 사람이 손으로 편집하지 않음. `index.md` 만 메타로 커밋 | 사람이 작성하는 아키텍처 설명(→ `architecture/`) |
|
||||
|
||||
## `changes/` 를 써도 되는 경우
|
||||
|
||||
- 코드가 실제로 수정되었다.
|
||||
- 또는 코드가 소비하는 설정/정적 데이터가 바뀌어 실제 동작이 달라졌다.
|
||||
- 문서의 중심이 "현재 구조 설명" 이 아니라 "이번 작업에서 무엇이 왜 바뀌었는지" 다.
|
||||
- 읽는 사람이 "언제 어떤 구현 변화가 들어갔는지" 를 확인하려고 이 문서를 찾는다.
|
||||
|
||||
## `changes/` 로 보내면 안 되는 경우
|
||||
|
||||
- 구현은 그대로고 설명만 보강했다.
|
||||
- README / CLAUDE.md 와 docs/ 를 동기화했다.
|
||||
- 재사용 가능한 작업 절차나 작성 규칙을 정리했다(→ `development/`).
|
||||
- 버그 원인/구조를 분석했지만 수정은 아직 없다(→ `analysis/`).
|
||||
- 운영 장애를 복기하고 재현/영향 범위를 남기는 것이 핵심이다(→ `issues/`).
|
||||
|
||||
## 빠른 결정 순서
|
||||
|
||||
1. 기술 선택/아키텍처 원칙에 대한 되돌리기 어려운 결정인가? → `adr/`.
|
||||
2. 운영 장애나 이슈 대응 기록인가? → `issues/`.
|
||||
3. 프로젝트의 업무 도메인 지식인가? → `domain/`.
|
||||
4. 외부/내부 계약 스펙인가? → `contracts/`.
|
||||
5. 현재 기준의 구조/가이드를 설명하는 문서인가? → `architecture/`, `development/`, `security/` 중 하나.
|
||||
6. 조사, 비교, 원인 추적, 리스크 파악이 핵심인가? → `analysis/`.
|
||||
7. 수동 운영 절차나 유지보수 실행 가이드인가? → `maintenance/`.
|
||||
8. 진행 중인 작업 메모인가? → `work-log/`.
|
||||
9. 미채택 아이디어/흔적인가? → `backlog/`.
|
||||
10. 위가 아니고 실제 구현 변경 이력을 남기는 문서인가? 그때만 → `changes/`.
|
||||
|
||||
## 함께 작성하는 규칙
|
||||
|
||||
- 구현 변경과 장기 기준 문서가 동시에 필요하면, 기준 문서는 해당 도메인 카테고리(`contracts/`, `architecture/`, `domain/` 등) 에 두고 `changes/` 에는 변경 요약만 남긴다.
|
||||
- 문서 동기화만 했다면 원칙적으로 `changes/` 를 만들지 말고, 대상 기준 문서를 직접 갱신한다.
|
||||
- 장애 수정이 있었더라도 핵심 가치가 장애 원인과 대응 이력 보존이라면 `issues/` 를 우선하고, 필요할 때만 관련 `changes/` 를 추가한다.
|
||||
- ADR 에 해당하는 결정이면 `changes/` 대신 `adr/` 에 불변 레코드로 남긴다.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Development — 개발 규칙 / 절차 / 툴 운영
|
||||
|
||||
반복해서 재사용할 규칙/워크플로우/툴 사용법을 둔다. 이 카테고리에 문서를 추가할 때는 `./document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
- [verification-strategies.md](./verification-strategies.md) — `verification-advisor` 가 읽는 검증 전략 레지스트리 (프로젝트별 `cmd` 를 채워 사용)
|
||||
- [document-category-classification.md](./document-category-classification.md) — 카테고리 분류 기준 (불필요한 카테고리는 프로젝트에 맞게 정리)
|
||||
|
||||
> atp 플러그인 번들 레퍼런스(`agent-team-protocol.md`, `agent-catalog.md`, `documentation-guidelines.md`, `search-tool-matrix.md`)는 플러그인 캐시에 있으며 이 프로젝트로 복사되지 않는다. 에이전트가 `${CLAUDE_PLUGIN_ROOT}/docs/...` 로 직접 참조한다.
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# Verification Strategies Registry
|
||||
|
||||
`verification-advisor` 가 읽는 단일 레지스트리. 이 파일에 등록된 전략만 검증 대상이 된다. 전략 추가/수정 시 이 파일만 변경하면 에이전트 구조(tier) 는 건드릴 필요 없다.
|
||||
|
||||
## 검증 사다리 (L1 / L2 / L3)
|
||||
|
||||
프로젝트의 검증은 3단으로 나뉜다. 코드 변경의 성격에 따라 **적용할 최소 L 레벨** 이 결정된다.
|
||||
|
||||
| 레벨 | 대상 | 신뢰 범위 |
|
||||
|---|---|---|
|
||||
| **L1** | 단위·회귀 (타입체크 + 단위/회귀 테스트) | "내가 고친 라인이 깨지지 않음" + "과거 버그가 재발하지 않음" |
|
||||
| **L2** | 원격/외부 의존 계약 (live contract 테스트) | "외부 서비스 스키마·필드가 우리 기대와 정합" |
|
||||
| **L3** | End-to-end (실제 런타임·DB·외부 서비스 전부 도는 시나리오) | "사용자 시나리오가 실제 스택에서 끝까지 동작" |
|
||||
|
||||
### 버그 범주 → 적용 L 레벨 (예시 · 프로젝트 맞춤)
|
||||
|
||||
프로젝트 특성에 맞게 아래 표를 채운다.
|
||||
|
||||
| 버그/변경 범주 | 의무 레벨 |
|
||||
|---|---|
|
||||
| 외부 API 스키마/응답 파싱 수정 | L1 + L2 |
|
||||
| 외부 서비스 설정 상수 변경 | L1 + L2 |
|
||||
| 인증/인가/롤 플로우 수정 | L1 + L2 + L3 |
|
||||
| 순수 도메인 로직 (외부 의존 없음) | L1 |
|
||||
| 인프라 설정 (container/env/compose) | L1 + 수동 스모크 |
|
||||
|
||||
**회귀 테스트 의무**: 버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다. revert 시 테스트가 실패하고, 수정 후엔 통과해야 한다.
|
||||
|
||||
### 실행 수단
|
||||
|
||||
프로젝트 루트에 통합 검증 스크립트를 둘 것을 권장한다 (예: `scripts/verify.sh`, `make verify`, `cargo xtask verify`). 스크립트는 L1 → L2 → 로그 스캔 순차 실행을 담당.
|
||||
|
||||
- L2 를 조건부로 비활성화하는 환경 변수를 제공 (원격 꺼진 CI 대비). 예: `SKIP_LIVE_CONTRACT=1`.
|
||||
- 원격 테스트의 seed/live URL 미지정 시 L2 는 자동 skip 처리되도록 구성.
|
||||
|
||||
## 사용 규약
|
||||
|
||||
- `verification-advisor` 는 호출 시 변경 scope 를 받아 매칭되는 전략만 실행한다.
|
||||
- 전략 개수에 따라 tier 가 자동 결정된다:
|
||||
- 1개 → advisor 가 직접 실행 (tier 2)
|
||||
- 2개 이상 → `worker:` 필드가 있는 전략은 해당 worker spawn (tier 3), 나머지는 advisor 순차 실행
|
||||
- 신규 worker 도입은 [agent-team-protocol.md §9](./agent-team-protocol.md) 의 확장 트리거 참조.
|
||||
|
||||
### 집합 전수 AC 실행 지침
|
||||
|
||||
design.md 의 "검증 포인트" 에 다음 형식의 AC 가 있으면 "집합 전수 체크" 유형으로 분류한다.
|
||||
|
||||
```
|
||||
AC-N: <집합명> 전수 N건 ... (grep -c ... == N)
|
||||
```
|
||||
|
||||
실행 방법:
|
||||
|
||||
1. AC 에 명시된 `grep -c` 명령을 그대로 실행한다.
|
||||
2. 반환값이 AC 에 명시된 기대 수와 다르면 **blocker FAIL** 로 판정한다.
|
||||
3. 실패 시 출력: 실제 매치 수 + 누락된 항목을 diff 또는 목록으로 제시.
|
||||
|
||||
집합 전수 AC 는 특성상 "부분 통과"가 없다 — 기대 수에서 1 이라도 어긋나면 전체 FAIL. 규약 근거는 프로토콜 §4.3 + `design-advisor.md` "집합 전수 체크 AC 패턴" 섹션.
|
||||
|
||||
## 전략 (템플릿)
|
||||
|
||||
아래는 YAML 스켈레톤이다. 프로젝트 기술 스택에 맞게 `cmd` 를 교체한다 (`pnpm` / `yarn` / `npm` / `cargo` / `go test` / `pytest` / `mvn test` 등).
|
||||
|
||||
```yaml
|
||||
strategies:
|
||||
# ── L1 ────────────────────────────────────────
|
||||
- id: typecheck
|
||||
cmd: <프로젝트 타입체크 명령> # e.g. pnpm typecheck / cargo check / mypy .
|
||||
scope: all
|
||||
timeout_s: 180
|
||||
failure_severity: blocker
|
||||
|
||||
- id: unit
|
||||
cmd: <프로젝트 단위 테스트 명령> # e.g. pnpm test / cargo test / pytest
|
||||
scope: <src glob> # e.g. src/**
|
||||
timeout_s: 600
|
||||
failure_severity: blocker
|
||||
|
||||
# ── L2 ────────────────────────────────────────
|
||||
- id: contract-<external>
|
||||
cmd: <계약 테스트 명령>
|
||||
scope:
|
||||
- <외부 의존이 집중된 경로>
|
||||
timeout_s: 60
|
||||
failure_severity: blocker
|
||||
preconditions:
|
||||
- "<외부 서비스 기동 확인 방법>"
|
||||
- "<필요 환경 변수>"
|
||||
note: |
|
||||
원격 미기동 또는 seed 미지정 시 runtime 에서 skip 처리.
|
||||
blocker 로 두는 이유는 "원격이 떠 있는데 계약 위반" 이 사용자 장애로 직결되기 때문.
|
||||
|
||||
# ── 통합 ──────────────────────────────────────
|
||||
- id: verify-all
|
||||
cmd: <통합 검증 스크립트> # e.g. pnpm verify / make verify
|
||||
scope: all
|
||||
timeout_s: 900
|
||||
failure_severity: blocker
|
||||
note: "L1 + L2 + 로그 스캔 통합. ATP task 세션 종료 전 의무."
|
||||
```
|
||||
|
||||
## 필드 정의
|
||||
|
||||
| 필드 | 의미 |
|
||||
|---|---|
|
||||
| `id` | 전략 식별자 (unique) |
|
||||
| `cmd` | 실행 명령 |
|
||||
| `scope` | glob. 이 패턴에 변경이 걸칠 때만 실행 |
|
||||
| `timeout_s` | 초 단위 타임아웃 |
|
||||
| `failure_severity` | `blocker` (실패 시 전체 검증 실패) \| `warning` (기록만) |
|
||||
| `worker` (optional) | spawn 할 worker 이름. 미지정 시 advisor 직접 실행 |
|
||||
|
||||
## 확장 시
|
||||
|
||||
- 새 전략 추가: 위 YAML 블록에 항목 추가.
|
||||
- Worker 분리가 필요한 수준에 도달 (브라우저 테스트, 장시간 E2E 등): `worker:` 필드 붙이고 해당 worker 파일 신설 + `verification-advisor.md` 의 tools 에 `Agent` 추가.
|
||||
- 기준은 [agent-team-protocol.md §9 확장 트리거 레지스트리](./agent-team-protocol.md#9-확장-트리거-레지스트리).
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Domain — 도메인 지식
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Feedback — 검토·수정 요청 캡처 inbox
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# graphify 산출물 본체는 재생성 가능하므로 커밋하지 않는다.
|
||||
# index.md 와 본 .gitignore 만 커밋 대상.
|
||||
|
||||
*
|
||||
!index.md
|
||||
!.gitignore
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
kind: graphify-meta
|
||||
last_generated_at: null
|
||||
source_commit: null
|
||||
scopes: []
|
||||
---
|
||||
|
||||
# Graph — graphify 산출물 메타
|
||||
|
||||
이 디렉토리는 `/graphify` 가 생성하는 지식 그래프 산출물의 **메타 정보만** 커밋한다. HTML/JSON/audit 본체는 `.gitignore` 대상이며 재생성 가능하다.
|
||||
|
||||
> 설치·적용 가이드는 `atp-graphify` add-on 번들의 `graphify-usage.md` 참조.
|
||||
|
||||
## 엔트리포인트 사용법
|
||||
|
||||
- 구조 탐색이 필요한 작업 전에 이 파일의 `last_generated_at` 과 `source_commit` 을 확인한다.
|
||||
- 메인 브랜치 기준 마지막 소스 커밋보다 뒤쳐져 있다면 `graph-refresh-checker` 서브에이전트를 호출해 staleness 판정을 받는다.
|
||||
- 판정 결과가 `partial-stale` / `fully-stale` 이면 메인 에이전트가 `/graphify` 를 호출해 재생성하고, 이 파일을 갱신한다.
|
||||
- 더 이상 관련 없는 scope (제거된 모듈 등) 의 산출물은 재생성 전 **삭제** 한다.
|
||||
|
||||
## 산출물 레이아웃
|
||||
|
||||
```
|
||||
docs/graph/
|
||||
├── index.md # 이 파일 — 메타, 커밋 대상
|
||||
├── .gitignore # 본체 무시
|
||||
└── <scope>/ # scope 별 산출물 디렉토리 (무시됨)
|
||||
├── graph.html
|
||||
├── graph.json
|
||||
└── audit.md
|
||||
```
|
||||
|
||||
scope 예시: `src`, `src-features`, `docs`, `full` 등. 한 번에 여러 scope 를 운용할 수 있다.
|
||||
|
||||
## Scopes
|
||||
|
||||
_현재 생성된 그래프 없음._
|
||||
|
||||
| scope | 마지막 생성 | 소스 커밋 | 대상 경로 | 요약 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
|
||||
## 갱신 시 체크리스트
|
||||
|
||||
- [ ] frontmatter 의 `last_generated_at`, `source_commit`, `scopes` 갱신
|
||||
- [ ] 아래 "Scopes" 표에 한 줄 추가/갱신
|
||||
- [ ] 폐기된 scope 가 있다면 해당 디렉토리 `rm -rf` + 표에서 제거
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# 문서 인덱스
|
||||
|
||||
**docs-first 워크플로우**: 어떤 작업을 시작하기 전이든 이 파일을 먼저 읽고, 해당 카테고리의 `index.md` 를 거쳐 관련 문서를 확인한 뒤 구현에 착수한다.
|
||||
|
||||
> 작성/갱신 규칙과 카테고리 분류 기준은 `development/documentation-guidelines.md` + `development/document-category-classification.md` 를 따른다.
|
||||
|
||||
## 카테고리
|
||||
|
||||
| 경로 | 용도 | 인덱스 |
|
||||
| --- | --- | --- |
|
||||
| [adr/](./adr/index.md) | 되돌리기 어려운 아키텍처/기술 결정 (불변, 번호제) | adr/index.md |
|
||||
| [analysis/](./analysis/index.md) | 코드/흐름/성능/리스크 분석 | analysis/index.md |
|
||||
| [architecture/](./architecture/index.md) | 시스템 경계, 레이어 규칙, 저장 구조 | architecture/index.md |
|
||||
| [backlog/](./backlog/index.md) | 미채택 아이디어, 재검토 트리거 | backlog/index.md |
|
||||
| [changes/](./changes/index.md) | 실제 동작이 바뀐 구현 변경 이력 | changes/index.md |
|
||||
| [contracts/](./contracts/index.md) | 외부/내부 계약 스펙 | contracts/index.md |
|
||||
| [development/](./development/index.md) | 재사용 가능한 개발 규칙/절차/툴 운영 | development/index.md |
|
||||
| [domain/](./domain/index.md) | 도메인 지식 (프로젝트가 다루는 업무 영역의 규칙·개념) | domain/index.md |
|
||||
| [issues/](./issues/index.md) | 운영 장애/이슈 대응 기록 | issues/index.md |
|
||||
| [maintenance/](./maintenance/index.md) | 수동 운영 절차 (마이그레이션/복구/배치) | maintenance/index.md |
|
||||
| [security/](./security/index.md) | 인증/인가, 입력 검증, 비밀 값 관리 | security/index.md |
|
||||
| [usage/](./usage/index.md) | 이식자용 사용 가이드·FAQ·체크리스트 | usage/index.md |
|
||||
| [work-log/](./work-log/index.md) | 세션 간 handoff, 시점성 있는 작업 메모 | work-log/index.md |
|
||||
| [feedback/](./feedback/index.md) | 검토·수정 요청 캡처 inbox (선택) | feedback/index.md |
|
||||
| [graph/](./graph/index.md) | **자동 생성** — graphify 산출물 메타 | graph/index.md |
|
||||
|
||||
> 프로젝트 성격에 따라 불필요한 카테고리는 제거하거나 새 카테고리를 추가한다. 기준은 `development/document-category-classification.md`.
|
||||
|
||||
## 빠른 참조
|
||||
|
||||
- **아키텍처 / 구조 파악**: `architecture/` → 부족하면 `graph/index.md` 확인 후 필요 시 `/graphify` 호출
|
||||
- **이식 후 설정**: `usage/setup-checklist.md` → 문제 발생 시 `usage/faq.md`
|
||||
- **기술 선택 근거**: `adr/`
|
||||
- **외부/내부 계약 스펙**: `contracts/`
|
||||
- **도메인 규칙**: `domain/`
|
||||
- **장애 이력**: `issues/`
|
||||
|
||||
## 작업 시작 체크리스트
|
||||
|
||||
- [ ] 이 인덱스에서 관련 카테고리를 식별했는가?
|
||||
- [ ] 해당 카테고리 `index.md` 를 읽었는가?
|
||||
- [ ] 구조 탐색이 필요한 작업이라면 `graph/index.md` 의 `last_generated_at` 이 최근인지 확인했는가?
|
||||
- [ ] 작업 결과로 새 문서가 필요한가? (분류 기준은 `development/document-category-classification.md`)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Issues — 운영 장애/이슈 대응 기록
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Maintenance — 수동 운영 절차 (마이그레이션/복구/배치)
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Security — 인증/인가, 입력 검증, 비밀 값 관리
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
kind: usage
|
||||
title: Usage 카테고리 인덱스
|
||||
description: 이식자·운영자 대상 사용 가이드(설정 체크리스트·FAQ 등) 인덱스.
|
||||
owner: template-maintainer
|
||||
stability: living
|
||||
last_reviewed: 2026-05-07
|
||||
---
|
||||
|
||||
# Usage — 이식자 사용 가이드
|
||||
|
||||
이 카테고리는 본 템플릿을 cp-R 로 **이식한 사용자 관점** 의 운영 문서를 모은다. 내부 구조 설명은 `architecture/`, 개발 규칙은 `development/` 로 분리.
|
||||
|
||||
## 목록
|
||||
|
||||
- [setup-checklist.md](./setup-checklist.md) — 이식 후 30분 내 완료 설정 체크리스트 (복사 제외 재확인 → CLAUDE.md 병합 → 검증 명령 교체 → 카테고리 조정 → graphify 도입 여부)
|
||||
- [faq.md](./faq.md) — 문제 해결 / FAQ + 이식자 실수 카탈로그 (M1~M8)
|
||||
|
||||
## 먼저 읽을 순서
|
||||
|
||||
1. `../../README.md` §3 (설치) — cp-R 기본 절차
|
||||
2. [setup-checklist.md](./setup-checklist.md) — 복사 직후 30분 내 정리
|
||||
3. 문제 발생 시 [faq.md](./faq.md)
|
||||
|
|
@ -181,3 +181,7 @@ Content-Type: application/json
|
|||
```
|
||||
|
||||
실제 구현에서는 클라이언트가 넘긴 `providerUserId`를 그대로 신뢰하기보다, 가능하면 서버에서 소셜 로그인 토큰을 검증한 뒤 provider 사용자 ID를 확정하는 방식을 권장한다.
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [analysis/2026-06-16-project-analysis.md](./analysis/2026-06-16-project-analysis.md) — D4 데이터 모델 분석. 현재 코드는 `provider='email'` 만 구현(소셜 확장은 `추정`)이라는 현황 확인 참조.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# Work Log — 세션 간 handoff, 시점성 작업 메모
|
||||
|
||||
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||
|
||||
## 목록
|
||||
|
||||
_(아직 문서 없음)_
|
||||
Loading…
Reference in New Issue