docs(dev): 검증 회고 교훈 반영 — DB-방언 계약 L2 + stale 빌드 가드

W3-2 L3 검증 세션(20260622-170857) 회고에서 채택한 재현성 교훈 2건 docs 반영.

- verification-strategies.md: 버그범주→L레벨 표에 "MyBatis 매퍼/SQL alias·집계 뷰
  변경 = L1+L2(dev DB contract)" 행 추가 + mock-vs-reality 노트(alias 케이스 폴딩,
  뷰 fan-out 실증) — L1 @MockBean 이 DB-방언 계약을 구조적으로 우회함을 명시.
- local-setup.md §4.2: L3 검증 진입 전 "가동 앱이 현재 빌드인지" 확인 절차 신설
  (JSP docBase 동결 → stale spring-boot:run 라이브 리로드 불가, 재기동 필수).
- work-session 회고 산출물(report.md Retrospective/applied_changes) 동봉.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
이정수 2026-06-22 17:58:47 +09:00
parent 21892c8a03
commit e0eb8eae15
3 changed files with 111 additions and 0 deletions

View File

@ -103,3 +103,91 @@ positive:
- quote: "dev 픽스처로 자동 로그인 (Recommended) 수락"
note: 제안한 검증 경로/권장안을 1회에 수락(이견 없음).
negative: []
# Retrospective
Retrospective:
signals:
positive:
- quote_or_paraphrase: "dev 픽스처로 자동 로그인 (Recommended) 수락"
about: "L3 브라우저 스모크 진입 전 인증 우회 경로로 'dev 픽스처 자동 로그인' 권장안을 제시 → 사용자가 1회에 수락(이견·재지시 0). 통상 검증 인증 셋업은 왕복 협의가 흔한데 권장안 단독 채택됨."
negative: []
what_went_well:
- "검증 전용 task 였으나 L3 실게임 스모크에서 서버 집계 실버그 2건(뷰 fan-out, 매퍼 alias 케이스 폴딩)을 잡아냄. L1 43/43 GREEN 만 믿지 않고 실제 런타임·DB 까지 내려간 판단이 사용자 노출 직전 차단으로 이어짐."
- "computer/screenshot 도구가 document_idle 미도달로 차단된 상황에서 멈추지 않고 javascript_tool + curl + psql + JSP 정적 조합으로 동치 검증을 구성해 L3 12항목 전수 PASS. 도구 1종 차단을 검증 포기 사유로 삼지 않음."
- "버그 발견 시 task 의 '실버그 발견 시 최소수정' 사전승인 범위 안에서만 비파괴 수정(CREATE OR REPLACE, alias quote)으로 한정. 검증 전용 scope 를 임의 확장하지 않음."
- "advisor 호출/스킵 판단을 즉시 1줄 로그로 남기고(검증 전용 → 설계/구현 advisor skip, verbose Bash 검증만 verification-advisor 위임), 버그 발견 시 'minimal-fix 재평가' 트리거를 사전 명시."
- "수정 후 회귀 근거를 before/after 수치(뷰 11/2.8/null → 6/3.5/정상)로 명시하고, L1 @MockBean 으로 재현 불가한 한계를 인정하며 L2 contract 신설을 open_item 으로 위임. 회귀 가드의 공백을 숨기지 않음."
what_to_improve:
- "BUG-2(매퍼 alias 케이스 폴딩→summary 항상 null)는 서버 집계 기능을 전면 무력화하는 사용자 가시 버그였는데 L1 43건 전부가 매퍼를 @MockBean 으로 대체해 우회했다. DB-통합 계층(매퍼 Map 키, 뷰 집계 정합)에 대한 자동 회귀 가드가 0인 상태가 구조적 사각. L2 contract(dev DB 연동) 부재가 근본원인."
- "stale spring-boot:run(목요일 기동, 구 JSP)이 8080 을 점유한 채로 검증을 시작하면 JSP docBase 가 동결돼 라이브 리로드가 안 되고 현재 빌드가 검증되지 않는다. 검증 진입 전 '가동 중 앱이 현재 빌드인지' 확인하는 절차가 부재했다(재기동으로 해소했으나 절차화 안 됨)."
- "claude-in-chrome computer/screenshot 가 본 dev 페이지(WebGL iframe 추정)에서 document_idle 미도달로 차단되는 원인이 미규명. 시각 증빙이 매번 막히면 L3 효율이 떨어지므로 idle 휴리스틱 우회/대체 증빙 표준이 필요."
memory_candidates:
- name: mock-vs-reality-db-contract-gap
type: feedback
description: "@MockBean 매퍼 대체 단위테스트는 DB-방언 계약 버그(alias 케이스 폴딩·뷰 fan-out)를 구조적으로 놓친다 — DB-통합 계약은 L2 로 가드."
body_draft: |
What: 컨트롤러/애플리케이션 단위테스트가 MyBatis 매퍼를 @MockBean 으로 대체하면,
실제 Postgres 가 일으키는 계약 결함이 전부 우회된다. 검증된 사각 2종:
1. 따옴표 없는 SQL alias(AS gameId)는 Postgres 가 소문자로 폴딩(gameid) → MyBatis Map 키 소문자
→ 컨트롤러의 camelCase get("reviewCount") 이 null → summary 전면 무력.
2. 집계 뷰의 1:N LEFT JOIN fan-out → COUNT/AVG 왜곡(자식행 0일 땐 잠복, 입력 순간 발현).
Why: 두 결함 다 L1 43/43 GREEN 을 통과했다. mock 은 '내가 부른 메서드가 불렸나'만 보고
'DB 가 그 SQL 을 우리 기대대로 해석하나'는 검증하지 못한다. 단위 통과율이 높을수록 오히려
DB-통합 계약 공백이 숨는다.
How to apply:
- 매퍼 메서드를 신규 추가/시그니처 변경하거나 집계 뷰를 정의·수정할 때 L1 GREEN 으로 끝내지 말 것.
- dev DB 연동 L2 contract 로 (a) 매퍼 반환 Map 키가 컨트롤러 조회 키와 정합, (b) 뷰 집계가
샘플 데이터에서 손계산과 일치, 를 가드한다.
- SQL alias 는 camelCase 가 필요하면 반드시 큰따옴표("gameId"). 소문자 폴딩 허용 alias 만 무따옴표.
rationale_for_saving: "동일 패턴(매퍼 추가/뷰 집계 변경) 재발 가능. 코드·git log 로 유도 불가(런타임 DB 방언 동작은 관찰로만 드러남). 기존 verification-strategies '신규 컨트롤러/매퍼 의존 변경 시 full test 의무'는 컨텍스트 로드 회귀만 다루고 DB 방언 계약 사각은 미커버."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md
memory_optional: true
- name: verify-against-current-build-not-stale-server
type: feedback
description: "L3 검증 진입 전 가동 중 앱이 현재 빌드인지 확인 — JSP docBase 동결로 stale spring-boot:run 은 라이브 리로드 불가, 재기동 필수."
body_draft: |
What: bibimbap 직접 실행(spring-boot:run / provided Tomcat)에서 JSP 는 docBase 가 기동 시점에
동결돼 라이브 리로드되지 않는다. 과거 세션에 띄워둔 stale 프로세스가 8080 을 점유한 채 검증을
시작하면 구 JSP 가 응답하고 현재 빌드는 검증되지 않는다(거짓 PASS/FAIL 위험).
Why: 본 세션은 목요일 기동된 구 JSP 프로세스가 떠 있어, 현재 빌드를 검증하려면 명시적으로
재기동해야 했다. flyway/liquibase 부재(local-setup §4)와 같은 결의 함정 — '띄워져 있음'이
'최신임'을 보장하지 않는다.
How to apply:
- L3/브라우저 스모크 진입 전: 8080 점유 프로세스의 기동 시각·아티팩트가 현재 작업 빌드인지 확인.
- 불일치/불확실하면 현재 빌드로 재기동(JSP 변경분은 재기동 없이 반영 불가).
- DDL 변경분은 별도로 db/apply-local-ddl.sh 로 실행 DB 에 적용(local-setup §4.1).
rationale_for_saving: "검증 세션마다 재발 가능한 함정. JSP docBase 동결·재기동 필요는 코드에 안 적혀 있고 런타임 관찰로만 드러남. local-setup §4.1 은 DDL 적용만 다루고 'stale 프로세스 vs 현재 빌드' 가드는 미기재."
signal_source: observation
docs_sync_target: /Users/wemadeplay/workspace/stz/bibimbap/docs/usage/local-setup.md
memory_optional: true
- name: recommended-option-accepted-low-friction
type: feedback
description: "검증 인증 우회 같은 비차단 셋업 결정은 (Recommended) 단독안으로 제시하면 왕복 협의 없이 1회 수락되는 경향."
body_draft: |
What: L3 검증 진입을 막는 비차단 셋업(인증 우회 경로 등)에서 'dev 픽스처 자동 로그인 (Recommended)'을
단일 권장안으로 제시 → 사용자가 즉시 수락(이견·재지시 0).
Why: 검증 진척을 위한 저위험·가역 셋업 결정은 옵션 나열보다 근거 붙인 권장안 단독 제시가 마찰을 줄였다.
본질적 산출물 결정이 아니라 검증 수단 결정이라 사용자 비용을 권장안에 위임하는 게 맞았다.
How to apply:
- 가역적·저위험·검증진척용 셋업 결정은 옵션 매트릭스 대신 (Recommended) 단독안 + 한 줄 근거로 제시.
- 단, 산출물 본질·비가역 결정은 이 패턴을 적용하지 말 것(옵션 제시 유지).
rationale_for_saving: "긍정 시그널이 검증한 비자명한 판단(옵션 나열 대신 권장안 단독). 재현성 있음 — 검증 세션마다 인증/픽스처 셋업 결정이 반복됨. 단 적용 경계(가역·저위험 한정)가 핵심이라 기록 가치."
signal_source: positive
docs_sync_target: null
memory_optional: true
protocol_feedback:
- "검증 사다리(verification-strategies §버그 범주→L 레벨)에 '신규 매퍼 메서드/집계 뷰 정의·변경'은 L1 + L2(dev DB contract) 의무로 명시 권고. 현재 표는 '순수 도메인 로직=L1', '외부 API=L1+L2' 만 있고 내부 DB-방언 계약(매퍼 Map 키·뷰 집계)은 L 레벨 매핑 공백. BUG-1/2 가 이 공백으로 누출됨 (structural)."
- "ATP §2.3 시그널 세탁 경계 관점: 본 세션 user_signals 는 positive 1/negative 0 로 기록됐고 회고에서 재검토 결과 세탁 정황은 없음(사용자가 사실·데이터 오류를 잡아낸 발화 없음, 버그 2건은 orchestrator 자가 발견). 다만 '검증 전용 task 에서 서버 실버그가 나왔는데 negative 시그널 0'은 사용자가 결함을 사후 인지하지 못한 정황일 수 있으므로, 검증 세션 회고에서는 '발견 버그 수 vs negative 시그널 수'를 교차 점검하는 절차를 protocol 에 추가 권고."
- "claude-in-chrome 미보유 advisor 상황에서 orchestrator 가 L3 브라우저 스모크를 직접 수행했고 computer 도구 차단을 javascript_tool 등으로 우회했다. document_idle 미도달 차단의 대체 증빙 표준(JS 이벤트/DOM/서버계약 동치)을 verification-strategies L3 항목에 등재 권고(긍정 패턴 보존)."
applied_changes:
- candidate: mock-vs-reality-db-contract-gap
decision: accepted
docs_applied: docs/development/verification-strategies.md (L레벨 표에 'DB-방언 계약' 행 + 'mock-vs-reality' 노트 추가)
- candidate: verify-against-current-build-not-stale-server
decision: accepted
docs_applied: docs/usage/local-setup.md (§4.2 '가동 앱이 현재 빌드인지 확인' 신설)
- candidate: recommended-option-accepted-low-friction
decision: docs-declined
rationale: 'docs_sync_target=null(소프트 작업흐름 패턴). 회고 기록으로 보존, 별도 docs sink 없음. memory 는 사용자 memory 설정 시에만 보조 — 본 세션 memory 미기록(docs-first 단독 마감).'
orchestrator_note: 'memory 기록은 사용자 memory 활성 신호 부재로 미수행(memory_optional). docs-first 로 #1·#2 반영 완료. protocol_feedback 3건은 외부 atp 번들 대상이라 본 repo 미적용(권고 보존).'

View File

@ -22,12 +22,18 @@
| 외부 서비스 설정 상수 변경 | L1 + L2 |
| 인증/인가/롤 플로우 수정 | L1 + L2 + L3 |
| 순수 도메인 로직 (외부 의존 없음) | L1 |
| MyBatis 매퍼 신규/SQL alias·집계 뷰 정의·변경 (DB-방언 계약) | L1 + L2 (dev DB contract) |
| 인프라 설정 (container/env/compose) | L1 + 수동 스모크 |
**회귀 테스트 의무**: 버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다. revert 시 테스트가 실패하고, 수정 후엔 통과해야 한다.
**신규 컨트롤러/매퍼 의존 변경 시 full test 의무**: 신규 컨트롤러를 추가하거나 컨트롤러의 매퍼 의존을 늘리면 implementation 단계에서 `test-compile` 만으로 끝내지 말고 반드시 full `./mvnw -o test` 를 실행한다. `BibimbapApplicationTests` 는 MyBatis/DataSource autoconfigure 가 exclude 된 컨텍스트라 컨트롤러가 주입하는 매퍼마다 `@MockBean` 을 수동 등록해야 하며, 누락 시 `contextLoads``NoSuchBeanDefinitionException` 으로 실패한다. 이 회귀는 `test-compile` 로는 탐지되지 않는다(W3-2 세션 20260618 실증).
**mock-vs-reality: DB-방언 계약은 L1 으로 못 잡는다**: 컨트롤러/애플리케이션 단위테스트가 MyBatis 매퍼를 `@MockBean` 으로 대체하면 "내가 부른 메서드가 불렸나"만 보고 "실제 Postgres 가 그 SQL 을 우리 기대대로 해석하나"는 검증하지 못한다. L1 통과율이 높을수록 DB-통합 계약 공백이 오히려 숨는다. W3-2 세션 20260622 에서 L1 43/43 GREEN 을 통과한 채 누출된 실버그 2종:
- **SQL alias 케이스 폴딩**: 따옴표 없는 `AS gameId` 는 Postgres 가 소문자(`gameid`)로 폴딩 → MyBatis `Map` 키가 소문자 → 컨트롤러의 camelCase `get("reviewCount")` 가 null → 집계 summary 전면 무력. camelCase 키가 필요하면 alias 를 **반드시 큰따옴표**(`AS "gameId"`)로 감싼다.
- **집계 뷰 1:N LEFT JOIN fan-out**: 부모(리뷰)에 자식(축 평점) 직접 JOIN 시 `COUNT(*)`/`AVG` 가 자식 행수만큼 왜곡(자식 0행일 땐 잠복, 입력 순간 발현). 집계는 자식을 서브쿼리에서 선집계 후 LEFT JOIN 한다.
→ 매퍼 메서드 신규/시그니처·SQL 변경, 집계 뷰 정의·변경 시 L1 GREEN 으로 끝내지 말고 dev DB 연동 L2 contract 로 (a) 매퍼 반환 `Map` 키가 컨트롤러 조회 키와 정합, (b) 뷰 집계가 샘플 데이터 손계산과 일치, 를 가드한다. (현재 L2 harness 미구축 — 신설이 open item.)
### 실행 수단
프로젝트 루트에 통합 검증 스크립트를 둘 것을 권장한다 (예: `scripts/verify.sh`, `make verify`, `cargo xtask verify`). 스크립트는 L1 → L2 → 로그 스캔 순차 실행을 담당.

View File

@ -180,6 +180,23 @@ db/apply-local-ddl.sh docs/game-reviews-ddl.sql # 특정 파일만
> 검증: 적용 후 `docker exec bibimbap-db psql -U bibimbap -d bibimbap -c "\d dev.<테이블>"` 로 컬럼·제약·인덱스를 확인한다.
### 4.2 검증 진입 전: 가동 중 앱이 "현재 빌드"인지 확인
**핵심 함정**: "8080 에 앱이 떠 있음"이 "현재 작업 빌드가 서빙 중"을 보장하지 않는다. 직접 실행(`spring-boot:run` / provided Tomcat)에서 **JSP 는 docBase 가 기동 시점에 동결**되어 파일을 고쳐도 라이브 리로드되지 않는다. 과거 세션에 띄워둔 stale 프로세스가 8080 을 점유한 채 검증을 시작하면 **구 JSP 가 응답**하고 현재 빌드는 검증되지 않는다(거짓 PASS/FAIL). flyway/liquibase 부재(§4)와 같은 결의 함정 — "떠 있음 ≠ 최신".
L3/브라우저 스모크·런타임 검증 진입 전 점검:
```bash
# 8080 점유 프로세스의 기동 시각 확인 (오늘 변경분보다 이전이면 stale)
ps -o lstart,command -p "$(lsof -ti tcp:8080)" 2>/dev/null
# JSP 변경분이 실제 서빙되는지 확인 (예: 신규 마커 토큰 grep)
curl -s http://localhost:8080/<경로> | grep -c '<현재빌드에만-있는-마커>'
```
- JSP/Java 변경분은 **재기동해야** 반영된다(JSP touch 후 재요청해도 docBase 동결로 미반영 확인됨, 20260622).
- DDL 변경분은 앱 재기동과 무관하게 `db/apply-local-ddl.sh` 로 실행 DB 에 별도 적용한다(§4.1). MyBatis annotation 매퍼는 쿼리타임 반영이라 DDL 적용 후 앱 재기동 불요.
## 5. 검증 체크리스트
실제 통과한 항목은 `[x]` 다.