check_evidence --repo 이 clean-architecture-backend-template 에서 141건을 세고 있었다.
124건 전부를 고정 리비전 21234e38 에 대조했더니 날조는 0 이었다 — 인용은 맞고 없던
쪽이 SSOT 였다. 그래서 SSOT 를 보강한다.
넣는 것은 기록이 옮겨 적은 문장이 아니라 저장소 원문이다. 기록을 복사해 넣으면
검사기는 초록이 되지만 옮겨 적기가 어긋나도 더는 못 잡는다. 원문을 넣으면 어긋난
기록은 계속 걸린다.
- 코드 124건 → 리프 절 일곱 곳에 원문 30조각(300줄). 자리는 기록의 source 앵커와
소스 파일의 모듈을 교차시켜 정했고, 둘이 갈린 다섯은 모듈을 따르고 그 사실을 적었다
- 식별자 13건 → spring.factories · MethodSecurityConfig 원문과 줄여 적힌 경로의 전체 경로
- source 앵커 4건 → 계약이 이미 적고 있던 SSOT 앵커를 기록 frontmatter 에 맞췄다.
맨 앵커를 한 줄씩 앞에 둔다 — `_record_sources` 가 `- <공백 없는 한 덩어리>` 만 잇달아 읽는다
- 인용 4건 → concept-signed-cursor-structure 의 `{ ... }` 생략을 저장소 원문 형태로 고쳤다.
생략한 자리는 `…` 로 남기고 무엇을 줄였는지 본문에 적는다
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
139 lines
9.1 KiB
Markdown
139 lines
9.1 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a-release-gate-with-no-evidence-producer
|
|
title: 릴리스 게이트가 읽는 증거를 아무도 생산하지 않는다
|
|
topic: what-a-gate-does-not-prove
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:a-release-gate-with-no-evidence-producer
|
|
evidenceCapturedOn: 2026-09-02
|
|
assets:
|
|
- key: a-release-gate-with-no-evidence-producer
|
|
file: ../../../final/evidence/rendered/a-release-gate-with-no-evidence-producer.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a-release-gate-with-no-evidence-producer.txt
|
|
- ../../../final/evidence/raw/tl-grpc-release-gate-no-producer.txt
|
|
source:
|
|
- final/document.md#7-6
|
|
- final/document.md#a20
|
|
- 분석 문서는 gRPC 플랫폼 편 §3.2 다. 그 절이 지원 매트릭스의 현재 시제 문장과 게이트의 설계 근거를 인용하고, 생성 지점 넷이 전부 테스트라는 것과 messaging 이 그것을 세 층으로 닫았다는 대비를 적는다.
|
|
- 같은 절이 근거로 든 검색은 리프의 build.gradle 안에서 태스크 등록 문자열을 세는 것이라 0 이 나온다. 이 저장소는 등록을 컨벤션 플러그인으로 옮겨 두었으므로 그 수는 등록된 태스크 수가 아니다. 위 터미널 출력의 레인 목록이 그것을 보여 준다.
|
|
---
|
|
|
|
# 릴리스 게이트가 읽는 증거를 아무도 생산하지 않는다
|
|
|
|
gRPC 안정 릴리스 게이트는 증거 객체를 읽어 판정한다. 그 객체를 만드는 곳은 게이트 자신의 단위 테스트뿐이다. 그 테스트는 매 PR 의 check 를 타고 돌지만, 거기서 게이트가 읽는 것은 테스트가 손으로 채운 값이다.
|
|
|
|
## 관계
|
|
|
|
- **아무도 돌리지 않는 레인의 게이트는 마지막으로 돌린 사람이 본 것을 보고한다**
|
|
이 사례에서 끌어낸 규칙이다.
|
|
- **빠뜨림이 통과가 되는 게이트는 게이트가 아니다**
|
|
증거를 만드는 쪽이 없을 때 게이트가 통과로 끝나면 안 된다는 규칙이다.
|
|
- **시작 검증기 13개 규칙이 유일한 조립 지점에서 호출되지 않는다**
|
|
같은 블록에서 나왔고, 둘 다 검증 코드는 있는데 그것을 부르는 자리가 없다.
|
|
|
|
## 문제
|
|
|
|
게이트는 증거 객체를 입력으로 받아 안정 등급 승격을 판정한다. 판정 로직은 존재하고 테스트되어 있다.
|
|
|
|
증거 객체의 다섯 성분은 전부 호출자가 넘기는 값이다. 등급과 능력이 둘이고, 문서 셋의 존재 여부가 나머지다. 어느 것도 파일 시스템이나 테스트 리포트를 보지 않는다.
|
|
|
|
## 결론
|
|
|
|
증거 객체를 생성하는 곳은 게이트 자신의 테스트 네 군데뿐이다.
|
|
|
|
등급 넷에 대응하는 증거 레인 넷은 존재한다. 리프의 build.gradle 이 아니라 컨벤션 플러그인이 등록하고, 지원 매트릭스가 등급마다 그 이름을 적는다.
|
|
|
|
없는 것은 둘이다. 레인이 낸 결과를 증거 객체로 바꾸는 코드가 없고, 그 레인 넷을 check 나 워크플로에 붙이는 배선이 없다.
|
|
|
|
messaging 쪽은 그 둘을 세 층으로 닫았다. 레인이 증거 파일을 쓰고, Gradle 태스크가 실행 산출물과 커밋본을 대조하고, 워크플로가 실제 브로커를 띄워 그 태스크를 돌린다.
|
|
|
|
gRPC 블록이 어떤 런타임 컴포지션에도 속하지 않는다는 것은 지원 매트릭스가 스스로 적고 있으며 결함이 아니다. 남는 것은 게이트의 입력이다.
|
|
|
|
## 검증 환경
|
|
|
|
OpenJDK : 21.0.12
|
|
Gradle : 9.0.0
|
|
확인 방식 : 정적 도달성 확인과 태스크 그래프 확인
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
1. 증거 레코드의 성분을 읽고, 그중 파일 시스템이나 리포트를 보는 것이 있는지 확인한다.
|
|
2. 그 객체를 생성하는 코드를 저장소 전체에서 찾고 main 과 test 를 구분한다.
|
|
3. 게이트의 테스트에 태그가 붙어 있는지 확인하고, check 그래프에 그 테스트가 들어 있는지 확인한다.
|
|
4. grpc-testkit 에 등록된 증거 레인을 나열하고, 그중 몇 개가 check 그래프에 있는지 센다.
|
|
5. CI 워크플로 중 grpc 를 담은 것의 수를 세고, root check 를 도는 워크플로를 확인한다.
|
|
6. 대비를 위해 messaging 쪽의 증거 파일과 대조 태스크와 워크플로를 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
`docs/compatibility/grpc-support-matrix.md` 는 안정 릴리스 게이트를 현재 시제로 적는다. 인증된 레인에 결과가 없거나 실패하면 릴리스를 막는다는 것이다.
|
|
|
|
게이트는 그 판정을 증거 객체 하나에서 읽는다.
|
|
|
|
## 다섯 성분은 전부 호출자가 넘긴다
|
|
|
|
:::evidence key="a-release-gate-with-no-evidence-producer" alt="코드베이스에서 게이트가 읽는 증거 레코드의 다섯 성분과 그 객체를 만드는 네 지점, 그 테스트에 태그가 없어 check 그래프 안에 있다는 것과 그 check 를 도는 워크플로 줄, 등급 넷에 대응해 컨벤션이 등록한 증거 레인 넷과 그중 check 그래프에 들어 있는 수와 grpc 를 담은 워크플로 수, 그리고 messaging 쪽에서 레인이 쓰는 증거 파일과 그것을 대조하는 태스크와 그 태스크를 돌리는 워크플로 단계를 뽑은 출력 39줄. 게이트의 테스트는 check 안에 있고 증거 레인 넷은 밖에 있다는 것이 그 출력에 보인다." caption="증거의 다섯 성분 · 생성 지점 넷 전부 테스트 · 그 테스트는 check 안 · 레인 넷은 check 밖 · messaging 의 세 층 — 39줄" zoom="true"
|
|
:::
|
|
|
|
증거 레코드는 실행한 등급 집합과 인증된 능력 집합, 그리고 런북과 결정 기록과 지원 매트릭스의 존재 여부 셋을 담는다.
|
|
|
|
셋은 불리언이다. 파일이 있는지 보고 채우는 코드가 아니라, 넘겨받은 값을 그대로 담는 자리다. 앞의 두 집합도 호출자가 만든다.
|
|
|
|
## 그 객체를 만드는 곳은 게이트의 테스트뿐이다
|
|
|
|
생성 지점이 넷이고 전부 같은 파일, 게이트 자신의 단위 테스트다.
|
|
|
|
그 테스트에는 태그가 붙어 있지 않다. 그래서 기본 test 태스크에 들어가고, check 그래프 안에 있고, 매 PR 마다 도는 워크플로가 그 check 를 돌린다.
|
|
|
|
판정 로직은 그러니까 실제로 돈다. 그때 게이트가 읽는 다섯 성분이 테스트가 손으로 채운 값일 뿐이다.
|
|
|
|
## 레인은 등록돼 있는데 아무 게이트에도 붙어 있지 않다
|
|
|
|
증거 등급이 넷이고, 그 넷에 대응하는 레인이 넷 등록되어 있다. 인프로세스 계약, 실제 Netty, 결함 주입, 성능이다.
|
|
|
|
등록은 리프의 build.gradle 이 아니라 컨벤션 플러그인이 한다. 지원 매트릭스가 등급마다 이 이름들을 적어 둔다.
|
|
|
|
그 레인 넷 중 check 그래프에 들어 있는 것은 0 이다. 28개 워크플로 중 grpc 를 이름이나 내용에 담은 것도 0 이다.
|
|
|
|
레인은 손으로 부르면 돈다. 부르는 곳이 없을 뿐이다.
|
|
|
|
## 없는 것은 레인과 증거 사이의 코드다
|
|
|
|
레인이 낸 결과를 증거 객체의 다섯 성분으로 바꾸는 코드가 없다.
|
|
|
|
그래서 게이트가 읽는 값은 레인이 실제로 무엇을 했는지와 연결되어 있지 않다. 블록이 출하되기 시작해도 이 연결은 저절로 생기지 않는다.
|
|
|
|
## 같은 저장소에 그 사슬을 이어 놓은 예가 있다
|
|
|
|
messaging 쪽은 세 층이다.
|
|
|
|
레인이 실행 결과를 증거 파일로 쓴다. Gradle 태스크가 그 산출물과 커밋된 매니페스트를 대조하고, 커밋본이 레인이 내지 않은 시나리오를 주장하면 실패한다. 워크플로가 실제 브로커를 띄워 그 태스크를 돌리고 결과를 산출물로 올린다.
|
|
|
|
그 태스크에는 캐시를 끄는 한 줄과 이유가 붙어 있다. 이전 실행의 결과로 답할 수 있는 게이트는 그 실행에 대한 증거라는 것이다.
|
|
|
|
워크플로 단계에도 한 줄이 있다. 커밋 해시를 레인이 읽어 모든 증거 줄에 적는다는 것이고, 인증이 하나의 소스 트리에 대한 주장이기 때문이라는 것이다.
|
|
|
|
## 게이트의 설계가 차단 사유를 만드는 방식
|
|
|
|
게이트는 빠진 결과와 빠진 등급과 스키마 판정과 문서 존재를 합쳐 차단 사유 목록을 만든다.
|
|
|
|
문서를 후속 과제가 아니라 차단 사유로 둔 근거도 적혀 있다. 동작을 먼저 출하하고 런북을 나중에 쓰면, 그것을 처음 만나는 사람이 새벽 세 시에 스스로 알아내야 한다는 것이다.
|
|
|
|
## 출하되지 않는다는 사실은 결함이 아니다
|
|
|
|
같은 지원 매트릭스가 그 상태를 적는다. grpc 리프는 전부 런타임 소속이 비어 있어서 이 플랫폼은 빌드 전용이다. 컴파일되고 레인이 돌지만 배포되는 산출물이 담지 않는다.
|
|
|
|
같은 문서가 릴리스까지 남은 게이트 입력 두 가지도 적는다. 성능 기준선과 스키마 코드 생성이다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
gRPC 블록이 어떤 배포에도 들어가지 않아서 런타임에서 볼 방법이 없다. 레인 넷을 실제로 돌려 결과를 보지는 않았다.
|
|
|
|
<!-- body:end -->
|