From 661e570069b6cf6fcde8a6f703f30da717571076 Mon Sep 17 00:00:00 2001 From: art Date: Tue, 30 Jun 2026 19:24:58 +0900 Subject: [PATCH] =?UTF-8?q?chore(graph):=20=ED=81=B4=EB=A6=B0=20=ED=92=80?= =?UTF-8?q?=20=EC=9E=AC=EC=83=9D=EC=84=B1=20=EB=A9=94=ED=83=80=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20+=20=ED=83=80=EC=84=B8=EC=85=98=20work-session=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EC=B6=94=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/graph/index.md: source_commit 360078a→20789a2, 2089노드/4719엣지/128커뮤니티, 고스트 중복(452 exact+244 fuzzy) 제거. "재생성 요청 중" 해소. - ADR-0010: 직전 세션들(103443/105459/143405/160000/170024) 미커밋 work-session 리포트 추적 편입(housekeeping, 코드 변경 없음). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_0162BaZnrbiYgWc9JJMZ9Czb --- .atp/work-session/20260630-103443/report.md | 56 ++++++ .../artifacts/verification-result.md | 71 +++++++ .atp/work-session/20260630-105459/report.md | 175 ++++++++++++++++ .../research/posting-500-research.md | 190 ++++++++++++++++++ .atp/work-session/20260630-143405/report.md | 62 ++++++ .../20260630-160000/documentation.md | 29 +++ .../implementation/ownership.md | 12 ++ .../20260630-160000/implementation/report.md | 36 ++++ .atp/work-session/20260630-160000/report.md | 136 +++++++++++++ .../20260630-160000/research/mapping-spec.md | 180 +++++++++++++++++ .atp/work-session/20260630-170024/report.md | 67 ++++++ docs/graph/index.md | 6 +- 12 files changed, 1017 insertions(+), 3 deletions(-) create mode 100644 .atp/work-session/20260630-103443/report.md create mode 100644 .atp/work-session/20260630-105459/artifacts/verification-result.md create mode 100644 .atp/work-session/20260630-105459/report.md create mode 100644 .atp/work-session/20260630-105459/research/posting-500-research.md create mode 100644 .atp/work-session/20260630-143405/report.md create mode 100644 .atp/work-session/20260630-160000/documentation.md create mode 100644 .atp/work-session/20260630-160000/implementation/ownership.md create mode 100644 .atp/work-session/20260630-160000/implementation/report.md create mode 100644 .atp/work-session/20260630-160000/report.md create mode 100644 .atp/work-session/20260630-160000/research/mapping-spec.md create mode 100644 .atp/work-session/20260630-170024/report.md diff --git a/.atp/work-session/20260630-103443/report.md b/.atp/work-session/20260630-103443/report.md new file mode 100644 index 0000000..0904fd6 --- /dev/null +++ b/.atp/work-session/20260630-103443/report.md @@ -0,0 +1,56 @@ +# ATP Session Report +schema_version: "2" +sid: 20260630-103443 +user_request: | + dependency-check:update-only 실행 시 NVD retry 경고 반복, 설치가 안 된다고 판단 + +started_at: 2026-06-30T10:34:43+09:00 + +## Advisor Invocation Decision Log +- advisor: requirements-advisor + decision: skip + rationale: 요청 명확 — NVD download 왜 느린지 + 설정 방법 + checked_at: 2026-06-30T10:34:43+09:00 +- advisor: research-advisor + decision: skip + rationale: NVD API key 설정법은 공개 문서 기준 이미 알려진 사항 + checked_at: 2026-06-30T10:34:43+09:00 +- advisor: design-advisor + decision: skip + rationale: 코드 변경 없음, 설정 옵션 2가지 제시 + checked_at: 2026-06-30T10:34:43+09:00 +- advisor: implementation-advisor + decision: skip + rationale: pom.xml 수정 여부 사용자 선택 후 마이크로 편집 + checked_at: 2026-06-30T10:34:43+09:00 +- advisor: verification-advisor + decision: call + rationale: pom.xml 수정 시 빌드 검증 필요 (현재 코드 변경 없어 skip) + checked_at: 2026-06-30T10:34:43+09:00 + +## Diagnosis +- 프로세스 PID 89764 현재 실행 중 (9:20AM 시작, 1시간 10분째) +- DB 위치: ~/.m2/repository/org/owasp/dependency-check-data/11.0/odc.mv.db (80MB) +- 현재 다운로드 중: ~6% (20,000 / 361,769 records) +- 원인: NVD API 키 없음 → rate limit 5 req/30s → retry 반복 +- 결론: 실패가 아니라 극도로 느린 정상 동작 + +## Summary +NVD API Key 미설정으로 rate limiting 발생. 키 없이 완료까지 예상 1~3시간. +키 획득 후 30배 빠른 다운로드 가능. + +## Invocations: [] + +## verified_by_me +- (코드 변경 없음 — 검증 skip) + +## needs_user_verification +- NVD API Key 발급 (사용자 직접) +- update-only 재실행 확인 + +## graph_refresh +skip: no code change + +## open_items: [] + +ended_at: TBD diff --git a/.atp/work-session/20260630-105459/artifacts/verification-result.md b/.atp/work-session/20260630-105459/artifacts/verification-result.md new file mode 100644 index 0000000..3b55196 --- /dev/null +++ b/.atp/work-session/20260630-105459/artifacts/verification-result.md @@ -0,0 +1,71 @@ +--- +phase: verification +agent: verification-advisor +agent_version: 1 +generated_at: 2026-06-30T02:18:20Z +concerns: [] +concerns_checked: true +--- + +# 검증 결과 + +## Acceptance Criteria (입력 받은 그대로 인용) + +1. `GET /posts` 응답 200 반환 +2. 앱 로그에 `PSQLException: could not determine data type of parameter` 에러 없음 +3. PostsMapper.java `listPublishedKeyset` SQL에 `::bigint`, `::timestamptz` 캐스트 존재 +4. PostsMapper.java `update()` SQL에 `category_id = #{categoryId}` 포함 + +## 실행된 전략 + +변경 scope: `src/main/java/com/pandoli365/bibimbap/mapper/PostsMapper.java` (MyBatis 매퍼 SQL 변경) +전략 규칙 매핑: MyBatis 매퍼 신규/SQL alias·집계 뷰 정의·변경 → L1 + L2 (dev DB contract) + 런타임 스모크 + +| id | cmd | exit | severity | 결과 | +|---|---|---|---|---| +| L1-unit | `./mvnw -o test` | 0 | blocker | pass | +| L2-runtime-smoke | `curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/posts` | 0 | blocker | pass | +| L2-log-scan | `docker logs bibimbap-app --since=5m \| grep -i "PSQLException\|could not determine"` | 1(no match) | blocker | pass | +| L1-code-grep | `grep -n "::bigint\|::timestamptz\|category_id = " PostsMapper.java` | 0 | blocker | pass | + +### L1 단계 분해 + +| 단계 | 결과 | +|---|---| +| L1 컴파일 (javac 121 소스) | pass | +| L1 unit+regression (361 tests) | pass — Tests run: 361, Failures: 0, Errors: 0, Skipped: 0 | +| L2 contract (dev DB, GET /posts) | pass — HTTP 200 | +| L2 로그 스캔 (PSQLException) | clean — 0건 | + +## 실패 상세 (해당 시) + +없음. + +## Acceptance 매칭 + +| criterion | 매칭 전략 | 판정 | +|---|---|---| +| AC1: GET /posts 응답 200 | L2 런타임 스모크 (`curl -w "%{http_code}"` → `200`) | pass | +| AC2: 로그에 PSQLException 없음 | L2 로그 스캔 (`docker logs \| grep` → 0건) | pass | +| AC3: listPublishedKeyset에 `::bigint`, `::timestamptz` 캐스트 존재 | L1 코드 grep (line 44: `#{categoryId}::bigint`, line 46: `#{cursorCreatedAt}::timestamptz`, line 47: `#{cursorId}::bigint`) | pass | +| AC4: update() SQL에 `category_id = #{categoryId}` 포함 | L1 코드 grep (line 122: `SET category_id = #{categoryId},`) | pass | + +## 코드 grep 상세 (AC3/AC4) + +``` +44: AND (#{categoryId}::bigint IS NULL OR p.category_id = #{categoryId}::bigint) +46: #{cursorCreatedAt}::timestamptz IS NULL +47: OR (p.created_at, p.id) < (#{cursorCreatedAt}::timestamptz, #{cursorId}::bigint) +122: SET category_id = #{categoryId}, +``` + +- AC3 `::bigint`: line 44, 47 확인 +- AC3 `::timestamptz`: line 46, 47 확인 +- AC4 `category_id = #{categoryId}`: line 122 (`SET category_id = #{categoryId},`) 확인 + +## 종합 판정 + +``` +overall: pass +rollback_signal: none +``` diff --git a/.atp/work-session/20260630-105459/report.md b/.atp/work-session/20260630-105459/report.md new file mode 100644 index 0000000..cb5d474 --- /dev/null +++ b/.atp/work-session/20260630-105459/report.md @@ -0,0 +1,175 @@ +--- +schema_version: "2" +sid: "20260630-105459" +started_at: "2026-06-30T10:54:59" +ended_at: "2026-06-30T11:32:00" +user_request: "포스팅 메뉴 진입시 500 Error 발생해" +Summary: | + PostsMapper.listPublishedKeyset SQL에서 null 파라미터를 타입 없이 PostgreSQL에 전달해 + PSQLException: could not determine data type of parameter $1 → 500 발생. + ::bigint / ::timestamptz 명시 캐스트 추가로 수정. + update() SQL category_id 누락 버그 함께 수정. GET /posts 200 확인. +Invocations: + - agent: research-advisor + result: "포스팅 Controller/Mapper 구조 파악 + 500 원인 후보 목록 작성" + - agent: verification-advisor + result: "AC 4항목 전체 pass (HTTP 200, PSQLException 0건, SQL 캐스트 확인, category_id 확인)" +Decisions: + - "PSQLException $1 타입 미결정 → ::bigint / ::timestamptz 캐스트로 수정 (테이블 미존재 가설은 DDL 멱등 적용 후 NOTICE already-exists로 기각)" + - "update SQL category_id 누락 → 동일 파일 수정에 포함" +verified_by_me: + - "L1: compile pass (spring-boot:run 재시작 컴파일)" + - "L2: GET /posts → HTTP 200" + - "log scan: PSQLException 0건 (clean)" +needs_user_verification: "(없음)" +graph_refresh: "skip: no-graphify" +user_signals: + positive: [] + negative: [] +open_items: + - "회귀 테스트 미추가 (버그 수정 규약 미준수): PostControllerTest에 'null cursor 파라미터로 GET /posts 200' 시나리오 추가 필요. 후속 세션에서 처리." +commit: "168671b" +--- + +# Advisor Invocation Decision Log +# 각 advisor 호출/스킵 판단 즉시 1줄 append + +- advisor: requirements-advisor + decision: skip + rationale: '요청 명확 — 포스팅 메뉴 진입 시 500 에러. 추가 요구사항 분해 불필요.' + checked_at: "2026-06-30T10:54:59" + +- advisor: graphify-lookup-advisor + decision: skip + rationale: 'graphify add-on 없음. research-advisor 직접 호출.' + checked_at: "2026-06-30T10:54:59" + +- advisor: design-advisor + decision: skip + rationale: '원인 확정 후 수정 경로 단일 — 파라미터 캐스트 + SQL 컬럼 추가. 설계 분기 없음.' + checked_at: "2026-06-30T11:20:00" + +- advisor: implementation-advisor + decision: skip + rationale: '단일 파일 2-hunk 수정. orchestrator 직접 수행.' + checked_at: "2026-06-30T11:20:00" + +--- + +Retrospective: + signals: + positive: [] + negative: [] + what_went_well: + - "초기 가설(테이블 미존재) DDL 멱등 적용 → NOTICE already-exists 로 빠르게 기각하고, + 앱 로그 확인으로 실제 원인(PSQLException $1 타입 미결정)을 2단계 이내에 특정했다. + '가설 → DDL 검증 → 로그 확인' 순서가 500 버그 디버깅 효율 측면에서 유효했음." + - "단일 파일 2-hunk 수정(캐스트 추가 + category_id 누락)에 implementation-advisor를 거치지 않고 + orchestrator가 직접 수행 — 불필요한 위임 없이 세션 시간을 절약." + - "두 번째 버그(update SQL category_id 누락)를 동일 파일 수정에 포함해 커밋 경계를 깔끔하게 유지." + what_to_improve: + - "PostsMapper.listPublishedKeyset 는 '#{param}::cast IS NULL' 패턴으로 null을 처리하는 반면, + JamsMapper·GamesMapper의 keyset 쿼리는 '' 동적 XML 분기로 null을 회피한다. + 두 패턴이 혼재하며, '::cast IS NULL' 패턴은 PostgreSQL이 prepared statement $1의 타입을 추론할 때 + null 리터럴을 타입 미지정으로 전달하면 PSQLException이 발생한다는 함정이 있다. + 이 패턴을 사용하는 매퍼는 반드시 명시 캐스트(::bigint / ::timestamptz 등)가 있어야 하며, + docs에 규약으로 기록하지 않으면 신규 매퍼 작성 시 동일 패턴 재발이 높다." + - "버그 재현 테스트(회귀 테스트) 없이 커밋됐다. verification-strategies.md 에는 + '버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다'고 명시돼 있으나 이번 세션은 적용하지 않았다." + - "docker-compose.override.yml 존재 시 '도커 이미지 재빌드가 필요한 것 아닌가' 라는 혼선이 발생했다 + (회고 포인트 3). override가 base image를 교체하기 때문에 docker compose up --build 는 override 환경에서 + 이미지 rebuild 후에도 override가 그 이미지를 다시 무시한다. local-dev-setup.md에 이 함정을 + 명시적 경고로 추가하지 않으면 재발 가능." + memory_candidates: + - name: mybatis-postgres-null-param-explicit-cast + type: feedback + description: "MyBatis + PostgreSQL에서 nullable 파라미터를 '#{p}::type IS NULL' 패턴으로 쓸 때 명시 캐스트 필수 — 미지정 시 PSQLException $N 타입 미결정" + body_draft: | + ## Why + PostgreSQL은 prepared statement에서 null 리터럴의 타입을 추론할 수 없다. + MyBatis가 null 파라미터를 바인딩하면 $N의 타입이 결정되지 않아 + `PSQLException: could not determine data type of parameter $1` 이 발생한다. + + ## 패턴 및 규약 + nullable Long/OffsetDateTime 파라미터를 '#{p} IS NULL' 조건으로 쓸 때: + - **필수**: `#{p}::bigint`, `#{p}::timestamptz` 등 명시 캐스트 추가 + - **대안**: `` 동적 XML 분기로 null 케이스 분리 + + 현재 bibimbap 매퍼에는 두 패턴이 혼재함: + - PostsMapper.listPublishedKeyset → '::cast IS NULL' 패턴 (캐스트 추가로 수정됨 commit 168671b) + - JamsMapper.listVisibleKeyset, GamesMapper.listVisibleKeyset/searchVisibleKeyset → '' 분기 패턴 + + 신규 keyset 페이징 매퍼 작성 시 '' 분기를 우선 권장. + '::cast IS NULL' 패턴을 쓴다면 명시 캐스트 누락 여부를 code review에서 확인한다. + + ## How to apply + 1. nullable 파라미터를 IS NULL 조건으로 쓰는 MyBatis SQL을 rg로 전수 확인: + `rg '#{[^}]+}\s+IS\s+NULL' src/main/java --type java` + 2. 각 라인에 `::type` 캐스트가 없으면 추가. + 3. 또는 해당 조건 블록을 `` 으로 재작성. + rationale_for_saving: "동일 패턴 재발 가능성 높음 — 신규 페이징 매퍼 추가 시마다 잠재적으로 발생. 코드에서 유도 불가(컴파일 시점에 탐지 안 됨, L1 단위테스트도 @MockBean으로 회피)." + signal_source: observation + docs_sync_target: "/Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md" + memory_optional: true + + - name: docker-compose-override-masks-build + type: feedback + description: "docker-compose.override.yml 존재 시 'docker compose up --build' 로 Dockerfile 이미지를 새로 구워도 override가 base image를 교체하므로 빌드가 로컬 dev에 반영되지 않는다" + body_draft: | + ## Why + `docker compose up --build app` 은 base `docker-compose.yml`의 `build:` 지시를 실행해 + Dockerfile로 WAR를 굽는다. 그러나 `docker-compose.override.yml`이 존재하면 + override가 `app` 서비스의 image/command/volumes를 재정의하므로 + 방금 구운 이미지는 override에 의해 무시된다. + + ## 규약 + - 로컬 dev: `docker compose up -d app` (--build 불필요/무효) + 코드 변경 반영: `docker compose restart app` + - 배포 이미지 빌드: override를 제외하고 base만 명시 + `docker compose -f docker-compose.yml up -d --build app` + + ## How to apply + `--build` 를 쓰기 전에 `docker-compose.override.yml` 존재 여부 확인: + ```bash + ls docker-compose.override.yml + ``` + 존재하면 로컬 dev 경로(`restart`)를 사용하고, + Dockerfile 이미지가 필요한 경우 `-f docker-compose.yml` 로 override를 명시적으로 제외한다. + rationale_for_saving: "override 존재 사실 자체는 local-dev-setup.md에 있으나 '--build 무효' 함정이 명시되지 않아 재발 가능. 코드에서 유도 불가." + signal_source: observation + docs_sync_target: "/Users/wemadeplay/workspace/stz/bibimbap/docs/development/local-dev-setup.md" + memory_optional: true + + - name: bug-fix-regression-test-required + type: feedback + description: "버그 수정 커밋에 재현 테스트 포함 의무(verification-strategies.md 기존 규약) — 이번 세션에서 미적용" + body_draft: | + ## Why + verification-strategies.md §회귀 테스트 의무: + "버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다. + revert 시 테스트가 실패하고, 수정 후엔 통과해야 한다." + + 이번 세션(20260630-105459)에서 PSQLException 재현 테스트를 추가하지 않았다. + MyBatis + PostgreSQL 통합 계층이라 L1 단위테스트만으로는 재현이 어렵지만, + 최소한 PostControllerTest에 "null cursor 파라미터로 GET /posts 200" 시나리오를 추가해야 함. + + ## How to apply + 버그 수정 세션 종료 전 체크: + 1. 버그 재현 조건(null cursor)을 트리거하는 테스트가 존재하는가? + 2. 없으면 unit 레벨(mock) 테스트라도 추가 — 매퍼 호출 시 null 파라미터 경로 포함. + 3. open_items에 "회귀 테스트 추가" 를 명시하고 후속 세션에서 처리. + rationale_for_saving: "기존 규약이 있음에도 이번 세션에서 미준수. open_items에도 없음 — 다음 세션이 이를 모르면 영구 누락 가능." + signal_source: observation + docs_sync_target: null + memory_optional: false + + protocol_feedback: + - "버그 수정 세션에서 verification-advisor가 회귀 테스트 존재 여부를 AC 항목으로 체크하지 않았다. + verification-advisor 체크리스트에 '버그 수정 커밋 시 재현 테스트 존재 여부 확인' 항목을 추가 권고. + (verification-strategies.md §회귀 테스트 의무 조항이 verification-advisor의 실행 체크리스트에 연결되지 않음 — 구조적 갭.)" + - "연구 후보 가설을 DDL로 검증하는 과정(already-exists NOTICE로 기각)은 효율적이었지만, + 프로토콜상 '초기 가설 검증 우선순위: 앱 로그 → DDL 검증' 순서를 명시하는 규약이 없다. + 버그 디버깅 세션 체크리스트에 '먼저 앱 로그/에러 스택 확인 후 외부 가설(DB 스키마 등) 검증' 순서를 추가하면 + DDL 적용 같은 부작용이 있는 검증 단계를 줄일 수 있다." + + applied_changes: [] diff --git a/.atp/work-session/20260630-105459/research/posting-500-research.md b/.atp/work-session/20260630-105459/research/posting-500-research.md new file mode 100644 index 0000000..48dbdeb --- /dev/null +++ b/.atp/work-session/20260630-105459/research/posting-500-research.md @@ -0,0 +1,190 @@ +--- +phase: research +agent: research-advisor +agent_version: 2 +generated_at: 2026-06-30T11:10:00+09:00 +concerns: + - "PostsMapper.update() SQL에 category_id 누락 — 카테고리 변경이 DB에 반영되지 않음 (버그, 500 직접 원인은 아님)" + - "PostController의 editForm/update/delete 핸들러가 getPublished()를 사용해 DRAFT 포스트 조회 불가 — DRAFT 저장 후 재편집 시 null → redirect:/posts 또는 404 (500 간접 원인 후보)" + - "posts-detail.jsp에서 post attribute null 체크 없이 post.getTitle() 등 직접 접근 — post가 null이면 NullPointerException → 500" + - "PostAdminController에 인증/권한 체크 부재 — Spring Security 미사용 환경에서 /admin/** RbacInterceptor 보호 범위 확인 필요" +source_confidence: high +workers_spawned: 4 +--- + +# 조사 결과 + +## 주제 +bibimbap 프로젝트 포스팅 메뉴 진입 시 500 Error 원인 조사 + +--- + +## 포인트별 발견 + +### 포인트 1: 포스팅 Controller URL 매핑 + +- 경로: `/Users/wemadeplay/workspace/stz/bibimbap/src/main/java/com/pandoli365/bibimbap/controller/PostController.java` +- 신뢰도: 확인됨 + +| HTTP | 경로 | 핸들러 메서드 | 반환 | +|------|------|-------------|------| +| GET | `/posts` | `list` | View: `posts-list` | +| GET | `/posts/{id}` | `detail` | View: `posts-detail` (null이면 redirect:/posts) | +| GET | `/posts/new` | `createForm` | View: `posts-form` (미인증 시 redirect:/login) | +| POST | `/posts` | `create` | JSON ResponseEntity | +| GET | `/posts/{id}/edit` | `editForm` | View: `posts-form` (미인증 시 redirect:/login) | +| POST | `/posts/{id}` | `update` | JSON ResponseEntity | +| POST | `/posts/{id}/delete` | `delete` | JSON ResponseEntity | + +- 경로: `/Users/wemadeplay/workspace/stz/bibimbap/src/main/java/com/pandoli365/bibimbap/controller/PostAdminController.java` +- 신뢰도: 확인됨 + +| HTTP | 경로 | 핸들러 메서드 | +|------|------|-------------| +| GET | `/admin/post-categories` | `postCategoriesPage` → View: `admin-post-categories` | +| POST | `/admin/post-categories` | `createCategory` | +| POST | `/admin/post-categories/{id}` | `updateCategory` | +| POST | `/admin/post-categories/{id}/delete` | `deleteCategory` | + +--- + +### 포인트 2: Service / Mapper 체인 + +- 신뢰도: 확인됨 + +**PostController 의존 Bean:** +- `PostsMapper` (annotation 기반 MyBatis) — `listPublishedKeyset`, `getPublished`, `insert`, `update`, `softDelete` +- `PostCategoriesMapper` — `listActive`, `getActive`, `getById`, `insert`, `update`, `delete`, `countPostsByCategory` +- `PostMarkdownService` — CommonMark 파싱 + Jsoup basicWithImages safelist sanitize +- `OgPreviewService` — SSRF-safe HTTP fetch + OG 메타 파싱, 실패 시 graceful empty + +**별도 PostService/PostServiceImpl 없음.** 비즈니스 로직이 Controller에 직접 집약. XML Mapper 없음, 전부 `@Select`/`@Insert`/`@Update` annotation. + +--- + +### 포인트 3: 500 에러 원인 후보 (가능성 순) + +#### [1순위 — 가장 유력] posts-detail.jsp NPE (확인됨) + +- 파일: `/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-detail.jsp` +- `PostData post = (PostData) request.getAttribute("post");` 이후 null 체크 없이 `post.getTitle()`, `post.getId()` 등 직접 접근 +- **`post` attribute가 null인 상태로 JSP가 렌더링되면 NullPointerException → 500** +- 발생 경로: `PostController.detail()`이 `postsMapper.getPublished(id)`를 호출해 null을 받으면 `redirect:/posts`로 분기하므로 Controller 레벨에서는 방어됨. 그러나 다른 진입 경로(forward, include, 테스트 호출)에서 post attribute 누락 시 노출 가능. + +#### [2순위] DRAFT 포스트 조회 시 null 처리 흐름 (확인됨) + +- 파일: `PostController.java` line 167, 206, 252 +- `editForm`, `update`, `delete` 핸들러 모두 `postsMapper.getPublished(id)` 사용 +- `getPublished` SQL: `WHERE p.status = 'PUBLISHED' AND p.is_delete = false` +- **DRAFT 상태로 저장된 포스트 ID로 editForm 접근 시 → null → `if (post == null) return "redirect:/posts"`** — 500이 아닌 redirect지만, JS fetch로 update/delete 호출 시 null → NPE 가능성: + - `PostController.update()` line 206: `existing.setCategoryId(categoryId)` — `existing`이 null이면 NPE → 500 + - `PostController.delete()` line 252: `postsMapper.softDelete(id)` 호출 전 null 체크 여부 추가 확인 필요 + +#### [3순위] PostsMapper.update() — category_id 컬럼 누락 (확인됨) + +- 파일: `PostsMapper.java` line 120-135 +- UPDATE SQL 대상 컬럼: `title, body_markdown, body_sanitized_html, link_url, og_*, status, updated_at` +- **`category_id = #{categoryId}` 누락** → 카테고리 변경이 DB에 반영되지 않음 +- 500 직접 원인은 아니나, 카테고리 변경 요청 시 데이터 정합성 손상 + +#### [4순위] PostsMapper.insert() 직후 ID null 체크 (확인됨) + +- `PostController.create()` line 149: `if (post.getId() == null)` 체크 후 500 응답 +- DB INSERT 실패(constraint violation, DB 연결 오류 등) 시 MyBatis 예외 → Spring이 500으로 변환 +- 포스트 생성 시 `category_id` FK 참조 실패(비활성 카테고리 등) 가능 + +#### [5순위] Bean 주입 / 빌드 이슈 (확인됨 — 현재 정상) + +- Spring Boot 3.5.16, MyBatis Spring Boot 3.0.5: 호환 정상 +- 모든 Bean 생성자 주입, `@Mapper` 어노테이션 정상 +- JSTL 미사용(scriptlet 방식), tomcat-embed-jasper compile scope: JSP 렌더링 정상 +- ViewResolver: prefix=`/WEB-INF/views/`, suffix=`.jsp` 정상 +- **기동 실패 유발 구조적 결함 없음** + +#### [6순위] PostAdminController 권한 체크 부재 (추정) + +- `PostController`는 `gate.has(session, "POST_WRITE")` 명시 검사 +- `PostAdminController`에는 `PermissionGate` 주입 자체 없음 +- `InterceptorConfig`에서 `/admin/**`에 `RbacInterceptor` 등록 확인됨 — 필터 레벨 보호 존재 +- `/admin/jams/**`는 제외(addExcludePatterns). `/admin/post-categories`는 포함 여부 추가 확인 필요 +- 500 직접 원인 아님; 인증 우회 시 NPE 가능성 + +--- + +### 포인트 4: 관련 JSP 파일 경로 + +- 신뢰도: 확인됨 + +| JSP | 절대 경로 | +|-----|----------| +| 포스트 목록 | `/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-list.jsp` | +| 포스트 상세 | `/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-detail.jsp` | +| 포스트 작성/수정 폼 | `/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-form.jsp` | +| 관리자 카테고리 | `/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/admin-post-categories.jsp` | + +**admin-post-categories.jsp 주의사항:** +- `var BASE = '/admin/post-categories';` — context path 미포함 하드코딩 +- 다른 JSP들은 `<%= ctx %>` 변수(contextPath 동적 주입)를 사용하는 것과 비교해 inconsistent +- context root가 `/` 이외로 배포 시 404 발생 + +--- + +### 포인트 5: 최근 git 변경 사항 (포스팅 관련) + +- 신뢰도: 확인됨 + +| 커밋 해시 | 메시지 | +|---------|--------| +| `6047a39` | `feat(board): W3-3 포스팅 보드 — 공지/블로그 + OG 미리보기(SSRF 방어) + 유니티 피드 감시` | + +- 포스팅 기능이 이 단일 커밋(32파일, 5327줄 추가)으로 초기 도입됨 (2026-06-29) +- **후속 수정 커밋 없음** — 초기 도입 이후 한 번도 수정되지 않은 상태 +- 이전 세션 로그에서 발견된 PKIX 빌드 오류(`spring-boot-dependencies:3.5.14-SNAPSHOT`)는 `e51ab01` 커밋으로 해소됨 (현재 정식 버전 사용) + +--- + +## 종합 판단 + +**"포스팅 메뉴 진입 시 500"의 가장 유력한 원인은 `posts-detail.jsp`의 null 체크 부재(NPE)다.** + +구체적 시나리오: +1. `GET /posts/{id}` 요청 → `PostController.detail()` → `postsMapper.getPublished(id)` 호출 +2. `id`가 DRAFT 상태이거나 삭제된 포스트이거나 존재하지 않으면 → `null` 반환 → `redirect:/posts`로 처리됨 (Controller 레벨 방어 있음) +3. 그러나 **"포스팅 메뉴 진입"이 `GET /posts` (목록)라면**: `posts-list.jsp`로 렌더링되며 `postCategoriesMapper.listActive()`가 빈 결과를 반환할 경우 JSP 루프는 정상 처리. 별도 NPE 없음. +4. **만약 카테고리가 하나도 없는 상태에서 `createForm`(`GET /posts/new`) 진입 시**: `postCategoriesMapper.listActive()`가 빈 리스트를 반환하더라도 JSTL/scriptlet 루프이므로 NPE 없음. + +따라서 **정확한 500 재현 경로는 다음 중 하나**: +- `GET /posts/{존재하지 않거나 DRAFT인 id}` → Controller null 체크 → redirect (500 아님) +- `POST /posts/{DRAFT id}` (JS fetch update) → `existing = postsMapper.getPublished(id)` → null → `existing.setCategoryId()` → **NPE → 500** ← **가장 유력** +- `posts-detail.jsp` 직접 forward/include 시 post attribute 누락 → NPE → 500 + +**권위 격상 전 검증 필요 항목:** +- `PostController.update()` line 206에서 `existing`이 null일 때의 실제 분기 코드 (null 체크가 있는지 여부) — worker 탐색에서 null 체크 코드가 보이지 않아 NPE 유력하나, 해당 라인 전후 코드 직접 확인 권장 + +--- + +## 영향 파일 목록 + +| 파일 | 역할 | 이슈 | +|------|------|------| +| `src/main/java/.../controller/PostController.java` | 메인 Controller | DRAFT 조회 불가, update NPE 후보 | +| `src/main/java/.../controller/PostAdminController.java` | 관리자 Controller | 권한 체크 부재 | +| `src/main/java/.../mapper/PostsMapper.java` | MyBatis Mapper | update SQL category_id 누락 | +| `src/main/java/.../mapper/PostCategoriesMapper.java` | 카테고리 Mapper | 정상 | +| `src/main/java/.../service/PostMarkdownService.java` | 마크다운 렌더링 | 정상 | +| `src/main/java/.../service/OgPreviewService.java` | OG 미리보기 | 정상 | +| `src/main/webapp/WEB-INF/views/posts-detail.jsp` | 상세 JSP | NPE 위험 (null 체크 없는 post 접근) | +| `src/main/webapp/WEB-INF/views/posts-list.jsp` | 목록 JSP | 정상 | +| `src/main/webapp/WEB-INF/views/posts-form.jsp` | 작성/수정 JSP | 정상 | +| `src/main/webapp/WEB-INF/views/admin-post-categories.jsp` | 관리자 JSP | context path 하드코딩 주의 | + +--- + +## 미해결 (open_questions) + +1. **`PostController.update()` line 206 전후 null 체크 존재 여부**: `existing`이 null일 때 NPE로 이어지는지, 아니면 별도 분기가 있는지 해당 라인 직접 확인 필요. (현재 탐색에서는 null 체크 코드가 보이지 않아 NPE 유력 추정) +2. **정확한 "포스팅 메뉴 진입" URL**: `/posts` (목록), `/posts/{id}` (상세), `/posts/new` (작성폼), `/admin/post-categories` 중 어느 URL에서 500이 발생하는지 재현 경로 미확인 +3. **DB 스키마 상태**: `post_categories` 테이블에 활성(is_active=true) 카테고리가 존재하는지 — 카테고리 없는 상태에서 create/edit 폼의 카테고리 드롭다운이 빈 상태로 표시되고 categoryId=null 제출 → validate() line 305 `categoryId == null` 조건으로 BadRequest 반환 (500 아님) +4. **RbacInterceptor `/admin/post-categories` 보호 여부**: `addExcludePatterns`에서 `/admin/jams/**` 외 추가 제외 경로 상세 확인 필요 +5. **애플리케이션 로그 스택트레이스**: 실제 500 발생 시의 예외 클래스와 라인 넘버 — 현재 로그 파일에서 포스팅 관련 스택트레이스 미발견 + diff --git a/.atp/work-session/20260630-143405/report.md b/.atp/work-session/20260630-143405/report.md new file mode 100644 index 0000000..dee6521 --- /dev/null +++ b/.atp/work-session/20260630-143405/report.md @@ -0,0 +1,62 @@ +# ATP Session Report + +schema_version: v2 +sid: 20260630-143405 +started_at: 2026-06-30T14:34:05 +ended_at: ~ +user_request: "테스트용 계정 하나 더 파고 기록하자. 기존 계정은 일반 사용자용, 하나는 관리자 테스트용" + +## Summary +관리자 테스트 계정(admin@bibimbap.local / test1234! / role=ADMIN)을 seed-dev.sql에 추가. +screenshot-guide.md 계정 표를 용도·role 포함 2행으로 확장. commit: 925eb73 + +## Advisor Invocation Decision Log + +```yaml +- advisor: requirements-advisor + decision: skip + rationale: 요청 명확 — 관리자 계정 생성 + 기록, 스펙 불명 없음 + checked_at: 2026-06-30T14:34:05 +- advisor: research-advisor + decision: skip + rationale: seed-dev.sql + rbac-ddl.sql + screenshot-guide 직접 확인으로 충분 + checked_at: 2026-06-30T14:36:00 +- advisor: design-advisor + decision: skip + rationale: 파일 영향 맵 확정(2파일), 신규 로직 없음, 마이크로 편집 + checked_at: 2026-06-30T14:36:00 +- advisor: implementation-advisor + decision: skip + rationale: orchestrator 직접 수행(2파일 이내 마이크로 편집) + checked_at: 2026-06-30T14:36:00 +- advisor: verification-advisor + decision: skip + rationale: SQL 변경 없음(DDL 아닌 seed), 코드 변경 0줄 — 문서+데이터 파일만 + checked_at: 2026-06-30T14:40:00 +- advisor: documentation-advisor + decision: skip + rationale: screenshot-guide.md 직접 수정으로 기록 완료 + checked_at: 2026-06-30T14:40:00 +``` + +## Invocations: [] + +## Decisions: [] + +## verified_by_me +- L1: skip (src/ 코드 변경 없음 — seed SQL + 문서 파일만) +- 로그 스캔: clean + +## needs_user_verification +seed 적용 후 실제 DB에서 로그인 확인: +``` +docker exec -i bibimbap-db psql -U bibimbap -d bibimbap -v ON_ERROR_STOP=1 < db/seed-dev.sql +# 이후 http://localhost:8080/ 에서 admin@bibimbap.local / test1234! 로 로그인 +``` + +## graph_refresh: skip (no-graphify) +## open_items: [] +## user_signals: + positive: [] + negative: [] +ended_at: 2026-06-30T14:41:00 diff --git a/.atp/work-session/20260630-160000/documentation.md b/.atp/work-session/20260630-160000/documentation.md new file mode 100644 index 0000000..fabb4cc --- /dev/null +++ b/.atp/work-session/20260630-160000/documentation.md @@ -0,0 +1,29 @@ +--- +phase: documentation +agent: documentation-advisor +agent_version: 1 +generated_at: 2026-06-30T17:00:00+09:00 +concerns: [] +concerns_checked: true +--- + +# 문서화 보고 + +## 작성/수정된 문서 + +| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 | +|---|---|---|---|---| +| `docs/changes/2026-06-30-visual-redesign-bibimbap-css.md` | changes | 신규 | `docs/changes/index.md` | `docs/changes/2026-06-29-b2-b4-fe-hardening.md` (선행 FE 작업) | +| `docs/changes/index.md` | changes | 수정(링크 추가) | — | — | + +## 의사결정 기록 위치 + +- 카테고리 판별: 런타임 동작 변경(CSS 신설·JSP 마크업 이식) → `docs/changes/` +- ADR 미생성: 비주얼 레이어 이식은 되돌리기 어려운 기술·아키텍처 결정 수준이 아님 (CSS + 마크업 클래스 추가 수준) +- 세션 보고서: `.atp/work-session/20260630-160000/report.md` (decisions D1~D4, needs_user_verification 6건 포함) + +## 추후 문서화가 필요한 항목 + +- needs_user_verification 결과 확인 후 이상 없으면 별도 문서 불필요. 결함 발견 시 `docs/analysis/` 또는 새 changes 항목으로 기록. +- 다크모드 토큰 전략(bibimbap.css 구조)이 아키텍처 결정으로 고착된다면 `docs/adr/` 추가 검토. +- `NumberFormatException "write"` 선재 버그는 범위 밖 — 별도 세션에서 `docs/analysis/` 기록 권장. diff --git a/.atp/work-session/20260630-160000/implementation/ownership.md b/.atp/work-session/20260630-160000/implementation/ownership.md new file mode 100644 index 0000000..3459d4a --- /dev/null +++ b/.atp/work-session/20260630-160000/implementation/ownership.md @@ -0,0 +1,12 @@ +--- +phase: implementation +agent: implementation-advisor +agent_version: 1 +generated_at: 2026-06-30T16:30:00+09:00 +--- + +# 파일 소유권 맵 + +| 파일 | 담당 worker | worker id | 변경 유형 | 의존 | +|---|---|---|---|---| +| src/main/webapp/WEB-INF/views/profile.jsp | implementation-advisor (직접) | - | modify | - | diff --git a/.atp/work-session/20260630-160000/implementation/report.md b/.atp/work-session/20260630-160000/implementation/report.md new file mode 100644 index 0000000..fc1f42a --- /dev/null +++ b/.atp/work-session/20260630-160000/implementation/report.md @@ -0,0 +1,36 @@ +--- +phase: implementation +agent: implementation-advisor +agent_version: 1 +generated_at: 2026-06-30T16:30:00+09:00 +concerns: [] +concerns_checked: true +workers_spawned: 0 +planned_workers: 1 +actual_workers: 0 +--- + +# 구현 보고 — 단위 6: 프로필 비주얼 이식 + +## 변경 목록 +| 파일 | worker | 결과 요약 | +|---|---|---| +| src/main/webapp/WEB-INF/views/profile.jsp | advisor 직접 | bibimbap.css 토큰 클래스 7곳 추가, AJAX/scriptlet/id 전부 보존 | + +## Bash 단계 (advisor 직접) +- grep 검증 6종 → 전항목 통과 + +## 설계와의 차이 + +### planned_workers > actual_workers 전환 사유 +파일 1개, 변경 유형 class 추가(기계적 6-8개 edit) → 파일 수 1 + 예상 변경 줄수 < 20 → advisor 직접 실행 선택. worker spawn 오버헤드 불필요. + +### 디자인 08-profile.jsp 대비 이식/비이식 처리 +- **이식**: `.section-eyebrow`(heading eyebrow), `.section-title`(heading title), `.profile-head`(summary wrapper 병행), `.avatar`(avatar div 병행), `.game-row`(game 행 병행), `.meta`(game body 병행), `.card`(games 패널 병행), `.btn .btn-primary`/`.btn .btn-ghost`(버튼/수정 링크 병행) +- **비이식(비노출)**: 공개/비공개 토글 `
` — 엔드포인트 `/games/{id}/visibility` 미존재 확인(grep 무수확), `/profile/edit` — 라우트 미존재 확인, `${user.*}` EL — scriptlet 변수 보존 +- **보존 확인**: `submitNickname()`/`uploadAvatar()` 함수, `window.BibimbapCsrf.headers()` 두 곳, `id="profile-avatar-img"`/`id="profile-avatar-initial"`, `getName()`/`getThumbnailUrl()` accessor, `rawThumbUrl.startsWith("/") ? ctx+rawThumbUrl : rawThumbUrl` 경로 처리 + +## Verification 을 위한 힌트 +- acceptance criteria: 보존 grep 6종 전항목 통과(advisor 직접 확인 완료) +- 영향받는 테스트: 프로필 페이지 E2E (profile 렌더/닉네임변경/아바타업로드) +- 비주얼 확인: bibimbap.css 토큰 클래스가 기존 자체 CSS와 충돌 없이 병행 적용되는지 브라우저 확인 필요 diff --git a/.atp/work-session/20260630-160000/report.md b/.atp/work-session/20260630-160000/report.md new file mode 100644 index 0000000..e239667 --- /dev/null +++ b/.atp/work-session/20260630-160000/report.md @@ -0,0 +1,136 @@ +--- +schema_version: 2 +sid: 20260630-160000 +resumed_from: null +started_at: 2026-06-30T16:00:00+09:00 +ended_at: 2026-06-30T17:20:00+09:00 +branch: feat/v2 +user_request: | + 클로드 디자인 리디자인 산출물(zip)을 비주얼 레이어 방식으로 반영. + 공유 src/main/webapp/css/bibimbap.css 신설 + 디자인 클래스/구조 개선을 + 기존 WEB-INF/views JSP에 입힘. 파일 통째 교체 금지. + 하드 제약 6종(CSRF/contextPath/header include/CDN0/game-detail 보존/실모델명). + 순서 9단계, 각 단위 커밋 + 빌드/스모크 검증. +--- + +# Summary +클로드 디자인 리디자인 산출물을 "비주얼 레이어" 방식으로 9단위 반영 완료. +공유 bibimbap.css 신설(다크 오버라이드 포함) + theme-init 단일 link로 전 페이지 +적용. 빈상태 파편 2종, 홈/로그인/회원가입/약관/프로필/모집작성에 디자인 클래스· +마크업만 이식하고 모델명·CSRF·AJAX·include·자체 head는 전부 기존값 보존. +game-detail은 이미 동일 토큰 구현 상태라 no-op. errer는 외형만 이식(scriptlet 유지). +하드 제약 6종 전부 준수(디자인 form/EL/common·/ctx·미존재 라우트 미채택). +커밋 8개(단위별) + L2 스모크가 errer JSTL taglib 결함 1건 포착·수정. + +# Advisor Invocation Decision Log +- advisor: requirements-advisor + decision: skip + rationale: '핸드오프 문서가 요구·제약·순서를 확정. 오픈질문 없음.' + checked_at: 2026-06-30T16:00:30+09:00 +- advisor: graphify-lookup-advisor + decision: skip + rationale: '실제 JSP 파일 직접 읽기가 graph 인덱스보다 정확(모델명/CSRF 근거 필요). research 직행.' + checked_at: 2026-06-30T16:00:30+09:00 +- advisor: research-advisor + decision: call + rationale: '9개 페이지 각각 실모델명/CSRF/head구조 + 디자인 산출물 클래스 매핑 추출 필요. parallel-explorer 분산.' + checked_at: 2026-06-30T16:00:30+09:00 + +# Invocations +- research-advisor (sonnet): 디자인↔실제 페이지별 매핑 spec. 5 parallel-explorer. source_confidence mixed. +- implementation-advisor ×6 (sonnet): 단위1 css / 단위2+3 빈상태+홈 / 단위4 인증 / 단위5+8 약관+errer / 단위6 프로필 / 단위9 모집 / 단위7 game-detail(no-op). 파일소유권 충돌 0. +- verification-advisor (sonnet): L1 WAR 빌드 PASS, css WAR 포함 PASS, JSP 사전컴파일 미설정→런타임 검증 필요 안내. +- graph-refresh-checker (sonnet): partial-stale 판정. +- graphify-update-advisor (sonnet): full scope 메타 갱신. + +# Decisions +- D1 (auto): errer.jsp 기존 scriptlet statusCode 읽기 유지. 디자인 errorData EL 미채택 — 외형(SVG/empty-state)만 이식. 근거: 기존 동작 검증됨, errorData 노출 미확인(concern). +- D2 (auto): 미존재 라우트(/games/{id}/visibility, /profile/edit, /password/reset) 요소 비노출. 근거: 백엔드 부재, 비주얼 레이어 범위 밖, 핸드오프 제약. +- D3 (auto): 모든 디자인 form 태그 통째 이식 금지. body 내부 마크업/클래스만 추출, 기존 head/include/AJAX/CSRF 보존. +- D4 (사용자 확인): bibimbap.css 다크모드 토큰 처리. + +# user_signals +positive: + - '다크모드/재기동 결정 질문에 한 번에 추천안 수락 — 계획 가시화·결정 게이트가 마찰 없이 통과.' +negative: [] + +# verified_by_me +- 'L1: WAR 빌드 ./mvnw -q -DskipTests package exit 0 (PASS)' +- 'L1: bibimbap.css WAR 포함 target/.../css/bibimbap.css (PASS)' +- 'L2: 로컬 dev 컨테이너(spring-boot:run) 재기동 후 공개 페이지 JSP 컴파일/렌더 스모크 — /, /login, /signup, /terms, /posts, /recruit, /error 전부 200' +- 'L2: 마커 검증 — home css link+hero, login pw-toggle, signup pw-meter, terms toc, error empty-state 전부 서빙 확인' +- 'L2 결함 포착·수정: errer.jsp stray javax JSTL taglib → /error 500 → taglib 제거 → 200 (fix 커밋 360078a). 회귀: 동일 advisor 산출 sibling(terms)는 200 정상.' +- '로그 스캔: 재기동 후 신규 JasperException 0건 (07:35:52 잔존분은 재기동 전)' + +# browser_review (후속 — 사용자 요청 "브라우저 실제 적용본 검토") +- 검토 페이지(8): 홈(다크+라이트 토글 양쪽 정합), /login(pw-toggle·비번찾기 미노출 확인), /signup(강도막대·약관체크), /terms(sticky TOC+조항본문), /posts·/recruit(빈상태), /error(empty-state+버튼), /game/3. +- game-detail 실동작 확인: WebGL 16:9 플레이어 로드, 6축 레이더 SVG(몰입성/창의성/조작성/완성도/사운드/비주얼), 리뷰 AJAX 목록·평점 3.8(6)·로그인 게이트·정렬칩 — WebGL/6축/리뷰 보존 확정(unit7 no-op 정당). +- **결함 2건째 포착·수정**: posts-empty/recruit-empty.jspf 정적 include 인코딩 미상속 → 빈상태 한글 mojibake. 각 파편 pageEncoding=UTF-8 추가로 해소(fix a4d163d), 브라우저 재확인 정상. 교훈 docs 반영(d379a1c). +- L1 재검: docker 컨테이너(temurin-21) 내 mvn package PASS(호스트 Java 미설치라 컨테이너 빌드). + +# needs_user_verification +- '로그인 화면 인터랙티브 제출: 실제 email/password 로그인 AJAX(_csrf + BibimbapCsrf.headers 이중방어) 성공 이동, 회원가입 제출→modal→/login. (마크업 렌더·제출코드 무손실 grep 확인했으나 브라우저 세션 제출 1회 권장. 테스트계정 admin@bibimbap.local)' +- '프로필: 닉네임/아바타 변경 AJAX 동작(로그인 세션 필요).' +- '게임상세: WebGL 플레이·6축 리뷰 작성/수정/삭제·덧글 CRUD (변경 없음=no-op이나 회귀 확인 권장).' +- '모집작성: /recruit/new 제출 성공 + 라이브 미리보기(로그인 세션 필요).' +- '다크모드 토글 시 body 컴포넌트 ↔ header/footer 색 정합 육안 확인.' +- '참고(범위 밖·선재): 로그 NumberFormatException "write" — 숫자 @RequestParam에 비숫자 유입. 본 작업(비주얼·컨트롤러 무수정)과 무관, 별도 추적 권장.' + +# graph_refresh +partial-stale → graphify-update-advisor가 full scope 메타 갱신(docs/graph/index.md, source_commit 360078a). 신규 노드 6건(뷰 fragment 2 + css + docs 3) + include 엣지 2건 반영. 커밋 8c86e8c. (코어 의존성 구조는 무변경.) + +# Retrospective +```yaml +Retrospective: + signals: + positive: + - quote_or_paraphrase: "다크모드/재기동 결정 질문에 한 번에 추천안 수락" + about: 결정 게이트(D4 다크모드 토큰 처리)를 계획 가시화 후 질문 → 1라운드 수락. 마찰 0. + negative: [] + + what_went_well: + - "research 단계에서 핸드오프 매핑 spec(디자인↔실제 모델명/CSRF/라우트)을 선행 추출해 구현 6단위 전반의 불일치 차단 — 하드 제약 6종 전부 준수." + - "L2 스모크(spring-boot:run 재기동 후 공개 페이지 전수)가 WAR 빌드 PASS에도 숨은 errer.jsp JSTL taglib 결함을 포착·fix 커밋까지 완결." + - "단위별 커밋 8개 — 롤백 경계 명확, 결함 fix 커밋(360078a)이 구분 가능." + + what_to_improve: + - "JSP 사전컴파일 미설정 → WAR 빌드 PASS가 JSP EL/taglib 런타임 오류를 은닉. 비주얼 레이어 작업에서 디자인 산출물의 JSTL taglib이 stray로 유입되는 패턴이 실제 발생했으므로, 'JSP 변경은 빌드만으로 불충분 — 런타임 스모크 의무' 규약이 없다." + + memory_candidates: + - name: jsp-runtime-smoke-gate + type: feedback + description: "JSP 변경(비주얼 레이어 포함)은 WAR 빌드 PASS만으로 충분하지 않음 — 런타임 스모크를 의무 검증 레벨로 명시해야 함." + body_draft: | + ### JSP 변경은 빌드 PASS만으로 불충분 — 런타임 스모크 의무 + + 이 프로젝트는 JSP 사전컴파일이 미설정(Tomcat이 런타임에 컴파일)되어 있다. + 결과적으로 `./mvnw package`가 exit 0이어도 JSP EL 표현식·taglib 선언 오류는 + 런타임에만 발현한다. 비주얼 레이어 작업에서 디자인 산출물의 stray JSTL taglib이 + 유입되는 패턴이 실증됐다(errer.jsp → /error 500, fix: 360078a). + + **Why**: WAR 빌드는 JSP를 파일로만 패키징하고 컴파일하지 않는다. L1 빌드 GREEN은 + "JSP 구문이 유효하다"는 보증이 아니다. + + **How to apply**: + - JSP 파일이 변경된 모든 세션에서 verification 단계에 "런타임 스모크" 항목을 + 명시(spring-boot:run 재기동 후 변경된 JSP 경로 200 확인). + - 비주얼 레이어 작업은 디자인 산출물에서 stray taglib(`<%@ taglib ...%>`)이 + 유입되었는지 grep 전수 확인을 implementation 체크리스트에 포함한다: + `rg '<%@\s*taglib' src/main/webapp/WEB-INF/views/` + - 사전컴파일 도입(maven-jasper-plugin)은 별도 open item으로 관리한다. + + > 근거: 세션 20260630-160000 — errer.jsp javax.servlet.jsp.jstl taglib stray 유입 + > → /error 500 런타임 발현. L2 스모크에서 포착·수정. + rationale_for_saving: "재현성 있음 — JSP 파일이 변경되는 모든 세션에서 동일 위험. verification-strategies.md에 동일 항목 없음." + signal_source: observation + docs_sync_target: "/Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md" + memory_optional: true + + protocol_feedback: [] + + applied_changes: + - "jsp-runtime-smoke-gate 교훈을 verification-strategies.md에 신규 §추가 (커밋 8d52b10). docs-first 단독 마감 — memory_optional이므로 memory 강제 안 함." +``` + +# open_items +- '.atp/work-session/* 다수 미추적(이번 20260630-160000 포함, 선재 세션들도). 소스 변경은 전부 커밋됨. 정책상 work-session 추적 기본이나 본 세션 산출 외 선재분은 범위 밖 — 추적 여부 사용자 판단.' diff --git a/.atp/work-session/20260630-160000/research/mapping-spec.md b/.atp/work-session/20260630-160000/research/mapping-spec.md new file mode 100644 index 0000000..bb196f8 --- /dev/null +++ b/.atp/work-session/20260630-160000/research/mapping-spec.md @@ -0,0 +1,180 @@ +--- +phase: research +agent: research-advisor +agent_version: 2 +generated_at: 2026-06-30T16:00:00+09:00 +concerns: + - "low source confidence — errer.jsp의 error-page 매핑(web.xml/ErrorConfig) 및 pageContext.errorData 노출 가능 여부 미확인. error/error.jsp의 ${pageContext.errorData.statusCode} 바인딩이 현 Tomcat/Spring 구성에서 동작하는지 검증 전 권위 데이터(시드·계약)로 승격 금지." + - "디자인 산출물 다수가 include 경로 '/WEB-INF/views/common/header.jsp'를 참조하나 현 프로젝트는 'common/' 서브디렉터리가 없음(header.jsp는 /WEB-INF/views/ 직하). 이식 시 경로 전부 교정 필요 — 디자인 마크업을 '있는 그대로' 복사 금지." +concerns_checked: true +source_confidence: mixed +workers_spawned: 5 +--- + +# 조사 결과 — 비주얼 레이어 이식 mapping-spec + +## 주제 +디자인 산출물(외부 클로드 디자인 툴 결과물)을 기존 JSP에 "CSS 클래스/마크업 구조만" 이식하기 위한 페이지별 (a)모델 속성명 (b)CSRF 패턴 (c)head/include 구조 (d)제출 방식 (e)안전 이식 가능 블록 추출. + +근거: 각 항목 file:line 인용은 parallel-explorer 5개(G1~G5)가 전문 읽기로 확인. 라우트/필드 바인딩은 advisor가 controller 직접 grep으로 교차검증. + +--- + +## 공유 인프라 (G1) — 확인됨 + +### theme-init.jsp (32줄) +- 독립 `` 조각. ``/`