Files
llm-wiki/raw/errors/idempotency-column-definition-base-check-failure-2026-07-15.md

74 lines
6.5 KiB
Markdown

---
title: error / idempotency-column-definition-base-check-failure-2026-07-15
source_type: error-note
status: raw
related_branches: [experiment-nplus1-feed-api-replay]
related_projects: [ca-skeleton]
tags: [error, ca-skeleton, architecture, persistence, hibernate, testing, idempotency]
created: 2026-07-15
status_label: open
---
# error: idempotency-column-definition-base-check-failure-2026-07-15
> Layer: `raw/errors/` — N+1 replay branch의 최종 `check`에서 남은 단일 architecture failure를, 랩 기능 실패와 분리해 보존한다. 원본은 raw에 영구 보관한다.
## Parent / 부모
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] — 11개 N+1 replay checkpoint의 최종 검증에서 발견했으며, 수정 소유권은 replay 범위 밖의 persistence base에 있다.
## 증상 / Symptom
- 에러 메시지 (ArchUnit condition이 만드는 원문):
```text
Field dev.caskeleton.adapter.outbound.persistence.idempotency.entity.IdempotencyRecordEntity.requestHash pins vendor SQL columnDefinition='char(64)' in adapter:outbound:persistence-jpa; move the physical type to the vendor migration.
```
- 발생 컨텍스트: `lab/nplus1-api-replay`의 최종 `cd src && ./gradlew check`.
- 발생 시점: 2026-07-15 (최종 검증; 시각은 별도 캡처하지 않음).
- 발생 환경: local Gradle / `app-bootstrap`의 `CleanArchitectureTest`.
- 재현 가능 여부: `always` — 해당 `@Column(columnDefinition = "char(64)")`가 non-PostgreSQL persistence package에 남아 있는 한.
- 범위 구분: 이는 L1의 lazy highlights 재현이나 L12의 CQRS-lite read-model 기능 실패가 아니다. final `check`에서 남은 base architecture failure 하나이며, L1/L12 replay 변경이 이 entity를 수정하거나 도입하지 않았다.
## 재현 절차 / Reproduction
1. `lab/nplus1-api-replay`의 `nplus1-replay-l12` tag에서 `cd src && ./gradlew check`를 실행한다.
2. `CleanArchitectureTest.PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS`가 `IdempotencyRecordEntity.requestHash`의 non-blank `columnDefinition`을 검사한다.
3. 기대 결과: non-PostgreSQL persistence entity에는 vendor SQL `columnDefinition` 문자열이 없다.
4. 실제 결과: `request_hash`에 `columnDefinition = "char(64)"`가 있어 위 Architecture violation으로 `check`가 실패한다.
## 조사 단계 / Investigation log
- 2026-07-15 — final `./gradlew check`의 잔여 failure가 하나뿐임을 [[raw/branch-notes/experiment-nplus1-feed-api-replay]]의 `Full check의 기준선 실패` 기록으로 확인했다.
- 2026-07-15 — `git show 6f0b0d6:src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/idempotency/entity/IdempotencyRecordEntity.java`에서 `requestHash`의 `@Column(... columnDefinition = "char(64)")`를 확인했다.
- 2026-07-15 — `git diff --exit-code 6f0b0d6..nplus1-replay-l12 -- src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/idempotency/entity/IdempotencyRecordEntity.java`가 변경 없음으로 끝났다. base와 replay tag의 해당 file blob은 모두 `4b0f51783a9db312b09b181c0245734599f7ced7`이다.
- 2026-07-15 — `CleanArchitectureTest`의 rule은 `dev.caskeleton.adapter.outbound.persistence.postgresql..` 밖의 `@Column` field에 non-blank `columnDefinition`이 있으면 위 원문을 생성하도록 확인했다. 따라서 failure는 replay의 L1/L12 기능을 대상으로 하지 않는다.
## 근본 원인 / Root cause
- 직접 원인: `IdempotencyRecordEntity.requestHash`가 vendor-neutral persistence package 안에서 `@Column(columnDefinition = "char(64)")`로 물리 SQL type을 고정했다.
- 근본 원인: RDBMS base entity의 portable mapping과 PostgreSQL 물리 schema 소유권을 분리하는 architecture rule이 이미 base commit `6f0b0d6`의 기존 entity 선언과 충돌한다.
- 트리거 조건: full `check`가 `CleanArchitectureTest`를 실행해 non-PostgreSQL package의 모든 `@Column` field를 검사할 때.
## Sources / 근거
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] — `Full check의 기준선 실패`가 final `check`의 유일한 잔여 failure와 replay scope 밖이라는 판단을 기록한다.
- local code evidence: `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java`의 `PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS`와 `notDeclareColumnDefinition()` — violation 조건과 원문을 보유한다.
- local Git evidence: base `6f0b0d6`와 `nplus1-replay-l12`의 `IdempotencyRecordEntity.java` blob SHA가 동일하다. 이는 replay history가 해당 선언을 건드리지 않았다는 근거다.
## 해결 / Resolution
- 적용한 조치: replay branch에서는 수정하지 않았다. `nplus1-replay-l12`가 학습 checkpoint history를 보존해야 하므로, 이 failure의 소유권을 별도 persistence base-fix 작업으로 분리했다.
- 권고 조치 및 소유권: persistence base owner가 entity의 non-empty `columnDefinition`을 제거하고, `char(64)` 물리 type이 PostgreSQL vendor Flyway migration에만 남는지 확인한다. portable `@JdbcTypeCode` 사용 여부는 기존 mapping/integration test와 함께 검토한다.
- 검증 방법: base-fix branch에서 `cd src && ./gradlew check`를 다시 실행하고, idempotency migration 및 persistence integration test로 schema/mapping을 확인한다.
- 잔여 위험 / 후속 작업: migration이 실제 physical type을 충분히 소유하지 않으면 entity annotation만 제거한 뒤 schema와 runtime mapping이 어긋날 수 있다. base-fix가 완료되기 전에는 replay branch의 full `check`를 green이라고 주장할 수 없다.
## 회고 / Lessons
- 빨리 감지하는 신호: `pins vendor SQL columnDefinition=` 또는 `move the physical type to the vendor migration` 메시지가 보이면, 랩 변경 파일부터 추측하지 말고 baseline blob과 replay diff를 먼저 비교한다.
- 예방 체크리스트 항목 후보: vendor-neutral JPA entity에 `@Column(columnDefinition = ...)`를 추가하거나 유지할 때는 architecture test와 vendor migration의 schema ownership을 같은 change에서 확인한다.
- wiki로 끌어올릴 가치가 있는 일반화된 교훈: full-suite failure를 feature regression으로 귀속하기 전에 base/replay diff와 architecture-rule 대상 범위를 대조하는 방법.
## Related / 관련
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] — focused lab suite, L1/L12 replay 검증, 그리고 이 base failure의 범위 구분을 함께 보존한다.