# 계약 leaf 와 그 소비자 leaf 의 소스 규모
  messaging-admin-api 의 소스 세트 : main test 
    main 25 파일 · 1613 줄
    test  1 파일 ·  147 줄
  messaging-admin-runtime 의 소스 세트 : main test 
    main 12 파일 · 1253 줄
    test  6 파일 · 1051 줄

# 계약 leaf 의 유일한 시험 파일이 담은 것
    파일 : DestructiveOperationGuardTest.java · @Test 9 개
    :51  void anApplicationRuntimeCannotRedrive() {
    :63  void anAdminRuntimeStillNeedsAnApproval() {
    :73  void anExpiredApprovalDoesNotAuthorise() {
    :91  void aDryRunIsAlwaysPermitted() {
    :102  void anApprovedAdminOperationIsAuthorised() {
    :113  void aRedriveCannotTargetItsOwnSource() {
    :119  void aRedriveBatchIsBoundedSoOneOperationCannotFloodTheSource() {
    :128  void aTopologyManifestReportsEveryDifference() {
    :140  void aMatchingTopologyReportsNoDifferences() {
  그 시험들이 승인 객체를 어떻게 얻는가 :
    DestructiveOperationGuardTest.java:27   /**
    DestructiveOperationGuardTest.java:28    * A grant the issuer signed, verified the way the runtime verifies it.
    DestructiveOperationGuardTest.java:29    *
    DestructiveOperationGuardTest.java:30    * <p>The guard used to take a plain {@code AdminApproval} record, which any caller could
    DestructiveOperationGuardTest.java:31    * construct. Every case below now has to go through a real signature to get an approval object at
    DestructiveOperationGuardTest.java:32    * all, which is the property the type change was made for.
    DestructiveOperationGuardTest.java:33    */
    DestructiveOperationGuardTest.java:45             Long.MAX_VALUE,
    DestructiveOperationGuardTest.java:46             digest);
    DestructiveOperationGuardTest.java:47     return ISSUER.verify(grant, ISSUER.sign(grant), digest, from);
    DestructiveOperationGuardTest.java:48   }
  그 셋의 단언이 무엇에 떨어지는가 :
    DestructiveOperationGuardTest.java:43  Optional.empty(),
    DestructiveOperationGuardTest.java:57  DestructiveOperation.REDRIVE, ORDERS, Optional.of(VALID), false, NOW))
    DestructiveOperationGuardTest.java:59  .hasMessageContaining("<가림>");
    DestructiveOperationGuardTest.java:67  () -> guard.authorize(DestructiveOperation.PURGE, ORDERS, Optional.empty(), false, NOW))
    DestructiveOperationGuardTest.java:69  .hasMessageContaining("approval");
    DestructiveOperationGuardTest.java:85  DestructiveOperation.OFFSET_RESET, ORDERS, Optional.of(expired), false, NOW))
    DestructiveOperationGuardTest.java:87  .hasMessageContaining("validity window");
    DestructiveOperationGuardTest.java:97  DestructiveOperation.DELETE_DESTINATION, ORDERS, Optional.empty(), true, NOW))
    DestructiveOperationGuardTest.java:98  .doesNotThrowAnyException();
    DestructiveOperationGuardTest.java:108  DestructiveOperation.REPLAY, ORDERS, Optional.of(VALID), false, NOW))
    DestructiveOperationGuardTest.java:109  .doesNotThrowAnyException();
  가드가 그 메시지를 내는 자리 :
    DestructiveOperationGuard.java:66  "APPROVAL_REQUIRED",
    DestructiveOperationGuard.java:68  if (!granted.isValidAt(now)) {
    DestructiveOperationGuard.java:70  "APPROVAL_EXPIRED", "approval " + granted.ticket() + " is outside its validity window");

# 소비자 leaf 의 시험 여섯
    AdminOperationJournalTest.java     @Test 8 개
    ApprovalForgeryTest.java           @Test 10 개
    ApprovedPlanExecutionTest.java     @Test 11 개
    RedriveResumptionTest.java         @Test 5 개
    TopologyValidationRuntimeTest.java @Test 4 개
    TopologyValidatorTest.java         @Test 13 개
  그중 승인 위조 시험이 단언하는 것 :
    ApprovalForgeryTest.java:51  void aVerifiedApprovalCannotBeConstructedOutsideTheVerifier() {
    ApprovalForgeryTest.java:69  void anAlteredGrantDoesNotVerify() {
    ApprovalForgeryTest.java:84  void aSignatureFromAnotherIssuerDoesNotVerify() {
    ApprovalForgeryTest.java:96  void anApprovalForOnePlanCannotExecuteAnother() {
    ApprovalForgeryTest.java:110  void aVerifiedApprovalForTheWrongSourceCannotBeAttached() {
    ApprovalForgeryTest.java:133  void aPlanBeyondTheApprovedImpactCeilingCannotExecute() {
    ApprovalForgeryTest.java:146  void anApprovalForAReplayCannotAuthoriseARedrive() {
    ApprovalForgeryTest.java:158  void anExpiredGrantDoesNotVerify() {
    ApprovalForgeryTest.java:172  void theApproverAndTheOperatorMustBeDifferentPeople() {
    ApprovalForgeryTest.java:192  void aCorrectlyIssuedApprovalExecutes() {

# 계약 leaf 가 담은 main 타입 스물다섯
    AdminApproval AdminOperationJournal AdminOperationLease AdminOperationRecord 
    AdminOperationState ApprovalGrant ApprovalVerifier ApprovedRedrivePlan ApprovedReplayPlan 
    DestinationTopology DestructiveOperation DestructiveOperationGuard HmacApprovalVerifier 
    PlanDigest RedrivePlan RedriveRequest RedriveResult ReplayPlan ReplayRequest ReplayResult 
    TopologyIssue TopologyManagementMode TopologyManifest TopologyValidationReport 
    VerifiedApproval 
# 승인 검증자는 어디서 만들어지는가
  main 구현 :
  main · messaging-admin-api · HmacApprovalVerifier.java:26 public final class HmacApprovalVerifier implements ApprovalVerifier {
  생성 지점 :
  test · messaging-admin-api · DestructiveOperationGuardTest.java:21       new HmacApprovalVerifier(
  test · messaging-admin-runtime · ApprovalForgeryTest.java:42       new HmacApprovalVerifier(
  test · messaging-admin-runtime · ApprovalForgeryTest.java:46       new HmacApprovalVerifier(
  test · messaging-admin-runtime · ApprovedPlanExecutionTest.java:37   private static final HmacApprovalVerifier VERIFIER = new HmacApprovalVerifier(ISSUING_KEY);
  test · messaging-admin-runtime · RedriveResumptionTest.java:45       new HmacApprovalVerifier(
    생성 지점 5 곳 · 파일 4 개

# main 에 검증자를 두지 않는 것이 공백인가 경계인가
  자기 자바독이 적은 이유 :
    HmacApprovalVerifier.java:50    * <p>Present because the issuer and the verifier must agree on the canonical form exactly, and a
    HmacApprovalVerifier.java:51    * second implementation of that agreement is a second thing to drift. Holding this key is what
    HmacApprovalVerifier.java:52    * makes a caller an issuer — it is not, and must not become, available to the runtime that
    HmacApprovalVerifier.java:53    * executes operations.
  같은 사슬의 가드는 main 빈으로 있다 :
    MessagingAdminAutoConfiguration.java:35   @Bean
    MessagingAdminAutoConfiguration.java:36   @ConditionalOnMissingBean
    MessagingAdminAutoConfiguration.java:37   public DestructiveOperationGuard destructiveOperationGuard() {
    MessagingAdminAutoConfiguration.java:38     // false: an application runtime never holds an admin credential, so the guard refuses the
    MessagingAdminAutoConfiguration.java:39     // operations that would need one. An operator tool overrides this bean with true.
    MessagingAdminAutoConfiguration.java:40     return new DestructiveOperationGuard(false);
    MessagingAdminAutoConfiguration.java:41   }

# 계약 타입의 경계 조건이 따로 고정돼 있는가
    PlanDigest             main 10 파일 · test 6 파일 · 그 이름을 건 시험 파일 0 개
    ApprovalGrant          main 3 파일 · test 4 파일 · 그 이름을 건 시험 파일 0 개
    VerifiedApproval       main 7 파일 · test 4 파일 · 그 이름을 건 시험 파일 0 개
    ApprovedRedrivePlan    main 2 파일 · test 2 파일 · 그 이름을 건 시험 파일 0 개
    ApprovedReplayPlan     main 2 파일 · test 1 파일 · 그 이름을 건 시험 파일 0 개
    TopologyManifest       main 5 파일 · test 4 파일 · 그 이름을 건 시험 파일 0 개
  만료를 직접 고정하는 자리 :
  test · messaging-admin-api · DestructiveOperationGuardTest.java:73   void anExpiredApprovalDoesNotAuthorise() {
  test · messaging-admin-runtime · ApprovalForgeryTest.java:158   void anExpiredGrantDoesNotVerify() {
  PlanDigest 가 test 에서 어떻게 쓰이는가 :
  test · messaging-admin-api · DestructiveOperationGuardTest.java:36     PlanDigest digest = PlanDigest.ofCanonical(operation + "|" + source.value());
  test · messaging-admin-runtime · AdminOperationJournalTest.java:9 import dev.caskeleton.messaging.admin.PlanDigest;
  test · messaging-admin-runtime · AdminOperationJournalTest.java:29   private static final PlanDigest DIGEST = PlanDigest.ofCanonical("<가림>");
  test · messaging-admin-runtime · AdminOperationJournalTest.java:30   private static final PlanDigest OTHER_DIGEST = PlanDigest.ofCanonical("<가림>");
  test · messaging-admin-runtime · ApprovedPlanExecutionTest.java:13 import dev.caskeleton.messaging.admin.PlanDigest;
  test · messaging-admin-runtime · ApprovedPlanExecutionTest.java:45       PlanDigest digest,
  test · messaging-admin-runtime · RedriveResumptionTest.java:10 import dev.caskeleton.messaging.admin.PlanDigest;
  test · messaging-admin-runtime · RedriveResumptionTest.java:146     PlanDigest digest = PlanDigest.ofCanonical("<가림>");
  test · messaging-outbox-jdbc-postgresql · AdminOperationJournalPostgresIT.java:9 import dev.caskeleton.messaging.admin.PlanDigest;
  test · messaging-outbox-jdbc-postgresql · AdminOperationJournalPostgresIT.java:46   private static final PlanDigest DIGEST = PlanDigest.ofCanonical("<가림>");
  test · messaging-spring-boot-starter · MessagingAutoConfigurationTest.java:432         dev.caskeleton.messaging.admin.PlanDigest planDigest,
  test · messaging-spring-boot-starter · MessagingAutoConfigurationTest.java:468         String approvalTicket, dev.caskeleton.messaging.admin.PlanDigest planDigest) {
