--- 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: []