--- kind: CONCEPT slug: adapter-outbound-persistence-jpa-c35 title: 40003이 UNKNOWN으로 강등되면서 조정 경로도 함께 사라진다 topic: state-machines-and-ownership project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: concept:adapter-outbound-persistence-jpa-c35 evidenceCapturedOn: 2026-09-01 assets: - key: adapter-outbound-persistence-jpa-c35 file: ../../../final/evidence/rendered/adapter-outbound-persistence-jpa-c35.svg evidence: - ../../../final/evidence/raw/adapter-outbound-persistence-jpa-c35.txt source: - 원본 분석 절은 final/document.md#a05#L2632 이다. module: adapter-outbound-persistence-jpa --- # 40003이 UNKNOWN으로 강등되면서 조정 경로도 함께 사라진다 `PostgreSqlFailureClassifier`의 SQLSTATE 분류는 맞지만 `PostgreSqlExceptionTranslator.translate()`가 `COMPLETION_UNKNOWN`을 일반 `UNKNOWN`으로 강등한다. ## 본문 `PostgreSqlFailureClassifier`는 PostgreSQL SQLSTATE를 bounded `FailureCategory`로 분류한다. serialization failure, deadlock, lock-not-available, constraint family, timeout, connection failure, schema/data 문제를 문자열 메시지가 아니라 SQLSTATE/structured server field 기준으로 다루는 방향은 적절하다. constraint 이름도 server error field에서 꺼내 catalog로 번역하므로 localized message parsing에 의존하지 않는다. ## PostgreSqlFailureClassifier 참조 위치 :::evidence key="adapter-outbound-persistence-jpa-c35" alt="코드베이스에서 PostgreSqlFailureClassifier 를 검색한 출력 11줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="PostgreSqlFailureClassifier 코드베이스 검색 — 11줄 · exit 0" zoom="true" ::: ## 번역기가 completionUnknown을 항상 false로 만든다 현재 `PostgreSqlExceptionTranslator.translate()`는 classifier 결과가 `COMPLETION_UNKNOWN`이어도 `JpaFailureContext`의 `completionUnknown`을 항상 `false`로 만들고, switch에서 `COMPLETION_UNKNOWN`을 `UNKNOWN`과 함께 일반 `JpaPersistenceException(FailureCategory.UNKNOWN, ...)`으로 강등한다. 직접 probe에서 SQLSTATE `40003`은 다음처럼 변환됐다. ```text type=JpaPersistenceException category=UNKNOWN sqlState=40003 completionUnknown=false retryable=false ``` ## 조정 경로까지 함께 사라진다 여기서 단순 진단 정보만 사라지는 것이 아니다. 현재 `DefaultJpaRetryPolicy`는 `TransactionCompletionUnknownException` 또는 `FailureCategory.COMPLETION_UNKNOWN`을 가장 먼저 검사해 `RECONCILE`로 보낸다. 그런데 실제 translator를 통과시키면 그 분기에 닿지 못한다. 즉 **재실행은 막지만, commit 결과를 확인해야 하는 reconciliation 경로도 잃는다.** fail-closed라는 이유로 안전하다고 볼 수 없는 이유다. commit이 실제로 적용됐는지 알 수 없는 상태를 terminal failure로 바꾸면 caller는 설계된 recovery protocol을 실행할 근거를 잃는다.