--- kind: PROJECT_DECISION slug: three-axes-of-evidence title: 전송·업무·스트림 증거는 세 축이고 서로를 함의하지 않는다 topic: commit-ambiguity-as-a-result project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: decision:three-axes-of-evidence decisionStatus: ADOPTED decidedOn: 2026-08-30 source: - docs/adr/ADR-GRPC-003-three-axis-execution-evidence.md — Status accepted, Date 2026-08-30, Scope grpc-core-api, grpc-policy, grpc-testkit - analysis/20-grpc-platform.md - analysis/grpc/grpc-core-api.md --- # 전송·업무·스트림 증거는 세 축이고 서로를 함의하지 않는다 ## 결정문 RPC 에서 일어난 일을 전송 증거와 업무 증거와 스트림 증거 세 축으로 나누어 기록하고, 축 사이의 추론을 거부한다. ## 판단 이유 ADR 의 출발점은 상태 코드가 호출자의 실제 질문에 대한 답이 아니라는 관찰이다. 변경 연산에 대한 DEADLINE_EXCEEDED 는 그 변경이 일어났는지 말해 주지 않는다. 요청이 전송된 뒤의 UNAVAILABLE 은 서버가 그것을 보지 않았다는 뜻이 아니다. 응답 헤더가 도착했다는 것은 트랜잭션이 커밋됐다는 뜻이 아니다. 셋 다 그럴듯한 추론이 중복 쓰기나 손실을 만드는 지점이고, 정상 경로만 검사하는 테스트에서는 어느 것도 보이지 않는다. 그래서 세 축을 독립적으로 두고 각 축이 자기 질문에만 답하게 한다. 전송 증거는 클라이언트가 회선에서 관측한 것을 기록하고, 클라이언트가 자기 전송 실패를 본 NOT_SENT 와 그 외 아무것도 알 수 없는 UNOBSERVED 를 구별한다. 업무 증거는 애플리케이션이 확인한 것을 기록하고, COMMIT_UNKNOWN 을 자리 표시자가 아니라 실제 상태로 둔다. 스트림 증거는 sealed 계층이며, 비어 있지 않은 경우는 전부 위치를 함께 들고 다닌다. 마지막 시퀀스 없는 부분 상태는 재개할 수도 조정할 수도 없기 때문이다. 그리고 관측 불가능한 조합은 실행 증거 타입이 생성 시점에 거부한다. 응답 헤더를 확정 커밋으로 승격하려면 한 메서드를 고쳐야만 가능하다. ## 영향 감수하는 것 값이 세 개로 늘어난다. 호출자가 세 축을 각각 읽어야 하고, 하나만 보고 판단하면 이 결정이 막으려던 추론을 다시 하게 된다. 세 축을 모두 채우는 책임이 전송 계층에 붙는다. 새 전송 구현마다 같은 품질로 채워야 한다. 조합 검증 때문에 표현할 수 없는 값이 생긴다. 테스트 픽스처가 편의상 만들던 조합 중 일부는 더 이상 만들 수 없다. 얻는 것 상태 코드에서 결과를 추론하는 경로가 타입 수준에서 닫힌다. 같은 원칙이 HTTP 쪽 전송 증거와 JPA 쪽 커밋 증거와 한 어휘를 이룬다. ## 근거 - **모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다** 세 축 각각이 이 규칙을 따른다. - **위험한 조합은 정책이 아니라 생성자가 거부하게 만든다** 축 사이의 추론을 막는 구현 방법이다. - **completion-unknown은 자동으로도 수동으로도 재시도하지 않는다** 같은 원칙이 JPA 쪽에서 채택된 결정이다.