- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
15 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | a19-f020-messaging-admin-api | messaging-admin-api 의 시험 한 파일이 아홉을 담고 만료까지 단언한다 | messaging-and-outbox | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:a19-f020-messaging-admin-api | 2026-09-04 | case-a19-f020-messaging-admin-api.body.md |
|
|
|
messaging-admin-api 의 시험 한 파일이 아홉을 담고 만료까지 단언한다
messaging-admin-api 는 main 25 파일 1,613 줄인데 test 는 DestructiveOperationGuardTest 한 파일 147 줄이다. 그 한 파일이 담은 @Test 는 아홉이고, 만료된 승인에 대한 거부까지 그 안에 있다.
관계
- messaging-reliability-api는 main 13파일 · 817 LOC에 테스트가 0개다 두 기록 모두 같은 가족의 leaf 에서 main 대비 test 파일 수를 세는 데서 출발한다. 그 leaf 는 test 소스 세트 자체가 없어 계수가 그대로 결론이 되지만, 여기서는 시험 파일 하나를 열어 아홉이 무엇을 단언하는지 봐야 했다.
- 브로커 ACL 매니페스트의 자기 점검이 존재하지 않는다
저 기록은
BrokerAclManifest의 검사 메서드가 코드에 있는데 부르는 main 코드가 없는 경우다. 여기는 검증자를 만드는 코드가 시험 네 파일에만 있는데, 왜 그래야 하는지가 그 타입의 자바독에 적혀 있다. - admin 스위치가 가드를 켜고 서비스는 켜지 않는다
저 기록은 관리 스위치를 켜도 서비스 빈이 생기지 않는 것을 빈 목록에서 확인했다. 여기서는 같은 사슬의 가드가
@Bean으로 존재하고 검증자만 test 에서 만들어진다는 것을 소스 세트로 확인했다.
문제
관리 평면의 계약 타입이 messaging-admin-api 에 스물다섯 개 있고 그 leaf 의 시험 파일은 하나다.
그 하나가 무엇을 덮고 나머지 검증이 어디 있는지 확인했다.
결론
원문이 적은 계수는 맞다.
DestructiveOperationGuardTest 가 담은 @Test 는 아홉이다. 승인 요구와 만료 거부와 승인 통과가 각각 :63, :73, :102 에 있는데, 셋 다 단언이 걸리는 대상은 DestructiveOperationGuard 가 내는 예외 코드와 메시지다. 아홉 전부가 승인 객체를 한 헬퍼에서 얻는데, 그 헬퍼의 자바독은 아무나 생성할 수 있던 record 를 서명 없이는 얻을 수 없는 타입으로 바꾼 것이 목적이라고 적는다. 재구동의 자기 대상 금지(:113), 배치 상한(:119), TopologyManifest 의 차이 보고(:128, :140)도 같은 파일이다.
실제 시험은 대부분 messaging-admin-runtime 에 있다. 그 leaf 의 시험 6 파일 1,051 줄이 시험 51 개를 담고, 그중 ApprovalForgeryTest 열 개가 승인 위조 경로를 덮는다.
HmacApprovalVerifier 를 생성하는 시험 파일은 두 leaf 에 넷이고 생성 지점은 다섯 줄이다. 그 타입을 만드는 main 코드는 0 건이다.
그것이 공백은 아니다. HmacApprovalVerifier:50~:53 의 자바독이 그 부재를 설계로 못박는다 — 키 보관자가 곧 발급자이므로 실행 런타임에 두어서는 안 된다는 것이다. 같은 사슬의 DestructiveOperationGuard 는 MessagingAdminAutoConfiguration:37 이 @Bean 으로 만들며 false 를 넘기는데, 그 인자가 같은 경계를 값으로 적은 것이다.
PlanDigest, ApprovalGrant, TopologyManifest 는 그 이름을 건 시험 파일이 모두 0 개다. 세 타입을 참조하는 test 파일은 각각 6, 4, 4 인데 그 시험들은 이름이 가리키는 다른 대상을 단언한다.
판정은 P3 이고 원문과 같다. 원문이 미확인으로 남긴 예 둘 가운데 ApprovalGrant 의 만료는 DestructiveOperationGuardTest:73 과 ApprovalForgeryTest:158 이 단언하고, PlanDigest 의 정규화만 미확인으로 남는다.
검증 환경
확인 방식 : messaging-admin-api 와 messaging-admin-runtime 의 소스 세트 목록과 파일 수·줄 수 계수, 계약 leaf 시험 파일의 @Test 수와 메서드 이름 전수, 소비자 leaf 시험 파일별 @Test 수 계수와 ApprovalForgeryTest 메서드 전수, 계약 leaf 의 main 타입 전수, ApprovalVerifier 의 main 구현과 HmacApprovalVerifier 생성 지점을 leaf·소스 세트별로 분리, 그 타입의 자바독과 MessagingAdminAutoConfiguration 의 가드 빈 확인, 계약 타입 여섯의 main·test 참조 파일 수와 그 이름을 건 시험 파일 검색, PlanDigest 의 test 참조 전수 소스 수정 : x
재현 조건
- messaging-admin-api 와 messaging-admin-runtime 의 소스 세트를 나열하고 각각의 파일 수와 줄 수를 센다.
- 계약 leaf 의 시험 파일을 열고 @Test 수와 메서드 이름을 줄 순서대로 나열한다.
- 각 이름이 무엇을 단언하는지 읽고 계약 타입과 짝짓는다.
- 소비자 leaf 의 시험 파일마다 @Test 수를 세고, 승인 위조 시험의 메서드를 전부 읽는다.
- 계약 leaf 의 main 타입을 전부 나열한다.
- ApprovalVerifier 의 main 구현을 찾고, HmacApprovalVerifier 를 생성하는 자리를 leaf 와 소스 세트로 갈라 센다.
- 그 타입의 자바독에서 main 에 두지 않는 이유가 적혀 있는지 읽는다.
- 같은 사슬의 다른 타입이 자동 설정에서 빈으로 만들어지는지 확인하고 인자를 읽는다.
- 계약 타입 몇 개의 main·test 참조 파일 수를 세고, 그 이름을 건 시험 파일을 *Test·*Tests·*IT 로 넓혀 찾는다.
- 만료를 단언하는 자리를 검색하고, PlanDigest 가 test 에서 어떻게 쓰이는지 전수로 읽는다.
본문
messaging-admin-api 는 관리 평면의 계약 타입을 담는 leaf 다. main 25 파일 1,613 줄에 test 는 DestructiveOperationGuardTest 한 파일 147 줄이다. 그 비율만 보면 승인 사슬이 검증되지 않은 것처럼 읽힌다.
DestructiveOperationGuardTest 한 파일이 담은 시험 아홉
:::evidence key="a19-f020-messaging-admin-api" alt="저장소 루트에서 돌린 정적 검색 출력 95줄. messaging-admin-api 와 messaging-admin-runtime 이 각각 main test 두 소스 세트만 갖고 25/1613·1/147, 12/1253·6/1051 이라는 계수가 먼저 나온다. 계약 leaf 의 유일한 시험 파일이 @Test 아홉을 담고 그 메서드 이름과 줄 번호가 줄 순서대로 나열되며, 이어서 그 시험들이 승인 객체를 얻는 통로인 헬퍼의 자바독과 verify 호출 줄, 각 단언이 기다리는 문자열, 그리고 가드가 그 문자열을 내는 자리가 나온다. 소비자 leaf 시험 여섯의 @Test 개수가 이어지고 그중 승인 위조 시험 열 개의 메서드 이름이 모두 나온다. 계약 leaf 의 main 타입 스물다섯이 이름으로 실리고, ApprovalVerifier 의 main 구현이 하나이며 HmacApprovalVerifier 생성 지점이 다섯 곳 파일 네 개로 전부 test 소스 세트라는 것이 leaf 이름과 함께 나온다. 이어서 그 검증자를 실행 런타임에 두지 않는 이유를 적은 자바독 네 줄과, 같은 사슬의 가드를 @Bean 으로 만드는 자동 설정 일곱 줄이 원문 그대로 실린다. 마지막으로 계약 타입 여섯의 main·test 참조 파일 수와 그 이름을 건 시험 파일이 모두 0 개라는 것, 만료를 고정하는 두 자리, 그리고 PlanDigest 가 test 에서 쓰이는 열두 줄이 나온다. 긴 문자열 리터럴은 가려져 있다." caption="두 leaf 의 소스 세트와 계수 · 계약 leaf 시험 아홉의 이름과 단언 대상 · 승인 객체를 얻는 헬퍼 · 소비자 시험 여섯과 위조 시험 열 · main 타입 스물다섯 · 검증자 생성 지점 다섯 곳 전부 test · 그것이 경계라고 적은 자바독과 main 빈으로 있는 가드 · 타입별 참조와 전용 시험 0 · 만료를 고정하는 두 자리 — 123줄 · exit 0" zoom="true" :::
파일 이름은 DestructiveOperationGuard 만 가리키는데 @Test 는 아홉이다.
이름에 승인이 들어간 셋이 승인 판정을 단언한다. anAdminRuntimeStillNeedsAnApproval(:63)이 승인 없는 관리 런타임을 거부하는 것을, anExpiredApprovalDoesNotAuthorise(:73)가 만료된 승인이 권한을 주지 않는 것을, anApprovedAdminOperationIsAuthorised(:102)가 승인된 연산이 통과하는 것을 단언한다.
셋의 단언이 떨어지는 곳은 모두 DestructiveOperationGuard 다. :69 가 "approval" 을, :87 이 "validity window" 를 기다리는데 그 문자열은 DestructiveOperationGuard:66 의 APPROVAL_REQUIRED 와 :70 의 APPROVAL_EXPIRED 메시지에서 온다. :109 는 예외가 없는 것만 본다.
셋이 검증자를 지나는 정도는 서로 다르다. :63 은 Optional.empty() 를 넘기므로 승인 객체를 만들지 않는다. :102 는 정적 필드 VALID 를 쓰는데 그것은 클래스 로딩 때 한 번 만들어진다. :73 만 자기 시험 안에서 만료 구간을 지정해 승인 객체를 새로 만든다.
승인 객체를 만드는 통로는 verified(...) 헬퍼 하나이고 :47 에서 ISSUER.verify(grant, ISSUER.sign(grant), digest, from) 을 부른다. 그 헬퍼의 자바독(:30~:32)은 가드가 예전에는 아무나 생성할 수 있는 AdminApproval 을 그대로 받았고, 지금은 모든 경우가 실제 서명을 지나야 승인 객체를 얻는다고 적는다. 타입을 바꾼 목적이 그것이라는 것이다.
나머지 여섯은 줄 순서대로 이렇다. anApplicationRuntimeCannotRedrive(:51), aDryRunIsAlwaysPermitted(:91), aRedriveCannotTargetItsOwnSource(:113), aRedriveBatchIsBoundedSoOneOperationCannotFloodTheSource(:119), aTopologyManifestReportsEveryDifference(:128), aMatchingTopologyReportsNoDifferences(:140) 다. 뒤의 둘은 TopologyManifest 가 차이를 전부 보고하는 경우와 일치할 때 하나도 보고하지 않는 경우를 짝으로 단언한다.
messaging-admin-runtime 의 시험 6 파일이 담은 51 개
소비자 leaf 는 messaging-admin-runtime 하나이고 main 12 파일 1,253 줄에 시험 6 파일 1,051 줄이다. @Test 수는 TopologyValidatorTest 13, ApprovedPlanExecutionTest 11, ApprovalForgeryTest 10, AdminOperationJournalTest 8, RedriveResumptionTest 5, TopologyValidationRuntimeTest 4 로 합계 51 이다.
ApprovalForgeryTest 열 개가 승인 사슬의 위조 경로를 덮는다. 검증자 밖에서 VerifiedApproval 을 만들 수 없다는 것(:51), 변조된 grant 가 검증되지 않는 것(:69), 다른 발급자의 서명이 검증되지 않는 것(:84), 한 계획의 승인이 다른 계획을 실행하지 못하는 것(:96), 만료된 grant 가 검증되지 않는 것(:158), 승인자와 운영자가 달라야 한다는 것(:172)이 각각 단언된다.
HmacApprovalVerifier 를 main 에 두지 않는 것은 경계다
ApprovalVerifier 의 main 구현은 HmacApprovalVerifier 하나다. 그것을 생성하는 지점은 다섯 곳이고 파일은 넷이다. 하나는 messaging-admin-api 의 DestructiveOperationGuardTest:21 이고 나머지 셋은 messaging-admin-runtime 의 ApprovalForgeryTest:42·:46, ApprovedPlanExecutionTest:37, RedriveResumptionTest:45 다. main 에는 생성하는 코드가 없다.
그 부재가 검증 공백은 아니다. HmacApprovalVerifier:50~:53 은 이 키를 쥔 쪽이 곧 발급자이며 그것이 연산을 실행하는 런타임에 있어서는 안 된다고 적는다.
같은 사슬의 다른 끝은 main 빈으로 있다. MessagingAdminAutoConfiguration:35~:41 이 @Bean @ConditionalOnMissingBean 으로 DestructiveOperationGuard 를 만들면서 false 를 넘기고, 주석은 애플리케이션 런타임이 관리 자격을 갖지 않으므로 가드가 그런 연산을 거부한다고, 운영자 도구가 이 빈을 true 로 덮어쓴다고 적는다. 생성자에 넘기는 false 가 자바독이 말한 경계를 그대로 인코딩한 값이다.
PlanDigest·ApprovalGrant·TopologyManifest 를 이름으로 건 시험 파일이 0 개다
계약 타입 여섯의 참조를 셌다. PlanDigest main 10 · test 6, VerifiedApproval main 7 · test 4, TopologyManifest main 5 · test 4, ApprovalGrant main 3 · test 4, ApprovedRedrivePlan main 2 · test 2, ApprovedReplayPlan main 2 · test 1 이다.
여섯 모두 그 이름을 건 시험 파일이 0 개다. *Test.java 뿐 아니라 이 저장소가 쓰는 *IT.java 와 *Tests.java 까지 넓혀 세도 0 이다.
만료는 두 자리가 고정한다. DestructiveOperationGuardTest:73 이 가드 쪽에서, ApprovalForgeryTest:158 의 anExpiredGrantDoesNotVerify 가 검증자 쪽에서 단언한다.
PlanDigest 는 다르다. test 에서 나오는 열두 줄이 전부 PlanDigest.ofCanonical(...) 로 다이제스트를 만들거나 파라미터로 받는 자리다. 정규화 자체를 단언하는 줄은 없다.
원문과 갈리는 자리
원문은 계약 leaf 의 test 를 1 (DestructiveOperationGuardTest) 로만 적었다. 파일 이름은 그렇지만 담긴 아홉 중 셋이 승인 판정을, 둘이 TopologyManifest 를 단언한다.
원문은 계약 자체의 경계 조건이 별도로 고정돼 있는지 확인되지 않는다고 적으면서 ApprovalGrant 의 만료를 예로 들었다. 그 예는 DestructiveOperationGuardTest:73 과 ApprovalForgeryTest:158 이 고정한다. PlanDigest 의 정규화만 남는다.
원문 §8.2 는 main 참조가 0 인 다섯 중 넷에는 이유가 적혀 있지 않다고 하면서 HmacApprovalVerifier 를 그 넷에 넣었다. 이 타입에는 이유가 자기 자바독에 적혀 있다.
계약 타입 목록에 대해
원문은 계약 타입을 열 개 들고 등 으로 닫았다. 이 leaf 의 main 타입은 스물다섯이고, 그 열에 없는 AdminOperationLease 와 AdminOperationState 와 DestinationTopology 도 같은 스물다섯에 들어 있다.
확인하지 못한 것
시험을 한 번도 돌리지 않았다. 세고 읽는 데 그쳤다.
아홉을 셋과 여섯으로 나눈 기준은 메서드 이름과 단언 대상이다. 실행 경로를 계측해 가른 것이 아니다.
다른 이름을 단 시험이 PlanDigest 의 정규화를 고정하고 있을 가능성은 test 참조 열두 줄까지 읽고 좁혔다.
두 leaf 의 시험을 실행하지 않았다. 파일과 @Test 를 세고 메서드 이름과 단언 대상을 읽은 데까지다.