--- kind: CONCEPT slug: messaging-core-api-c04 title: 거절과 결과 모름을 하나로 합치면 중복 주문이 생긴다 topic: delivery-and-settlement-models project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: concept:messaging-core-api-c04 evidenceCapturedOn: 2026-09-01 assets: - key: messaging-core-api-c04 file: ../../../final/evidence/rendered/messaging-core-api-c04.svg - key: messaging-core-api-c04-diagram file: ../../../final/assets/diagrams/messaging-core-api-c04.svg evidence: - ../../../final/evidence/raw/messaging-core-api-c04.txt source: - 원본 분석 절은 analysis/messaging/messaging-core-api.md#L180 이다. module: messaging-core-api --- # 거절과 결과 모름을 하나로 합치면 중복 주문이 생긴다 이 leaf의 실질은 여기 있다. 표현할 수 없는 상태를 생성자에서 거절하는 것이 설계의 축이다. ## 본문 이 leaf의 실질은 여기 있다. **표현할 수 없는 상태를 생성자에서 거절하는 것**이 설계의 축이다. ## 발행 결과 세 상태 :::evidence key="messaging-core-api-c04-diagram" alt="발행 시도에서 CONFIRMED 와 REJECTED 와 AMBIGUOUS 세 갈래가 나온다" caption="발행 결과 세 상태" zoom="false" ::: `PublishCompletion`은 boolean이 아니라 3상태다. enum javadoc이 왜 셋인지 적는다 — "Collapsing 'the broker refused this' and 'we never learned what the broker did' into one failure is what produces duplicate orders"(`publish/PublishCompletion.java:6-8`). `AMBIGUOUS`인 호출자는 **같은 `messageId`로만** 재발행할 수 있다. ## PublishCompletion 참조 위치 :::evidence key="messaging-core-api-c04" alt="코드베이스에서 PublishCompletion 를 검색한 출력 29줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="PublishCompletion 코드베이스 검색 — 29줄 · exit 0" zoom="true" ::: ## 생성자가 거절하는 열두 조합 `PublishResult` 생성자(`publish/PublishResult.java:39-101`)가 12가지를 거절한다. 11번과 14번에는 코드 주석이 직접 달려 있다. record가 public이고 모든 adapter가 이것을 만들기 때문에 호출부를 믿지 않고 여기서 검증한다는 것도 javadoc에 적혀 있다(`PublishResult.java:18-20`). ## 증거가 결론보다 먼저 기록된다 `PublishEvidence`(`publish/PublishEvidence.java`)는 `queuedLocally`, `transmission`, `brokerAccepted`, `confirmationLevel` 넷을 갖고, javadoc이 순서를 못 박는다 — "Evidence is recorded before a completion is chosen, not derived from it. That ordering is what lets an operator answer 'could the broker be holding this message?' from a stored result." `TransmissionEvidence`가 3상태(`NOT_TRANSMITTED` / `MAY_HAVE_BEEN_TRANSMITTED` / `TRANSMITTED`)인 것이 그 순서를 가능하게 한다. ## 정산 쪽도 같은 형태다 `SettlementResult`(`settlement/SettlementResult.java:23-36`)는 `SETTLED`인데 `!brokerConfirmed`이면 거절하고, `SETTLED`인데 `redeliveryPossible`이면 거절한다.