{ "schema_version": "1.0", "document": "docs/clean-architecture-backend-template/final/document.md", "document_sha256": "8071fe71b3359d9cf60b95909c26c7b50653ce2f22bbc5fcf6988719bb91236d", "line_count": 47035, "line_number_space": "canonical-source-with-managed-blocks-collapsed", "anchor": { "kind": "line", "value": 40325, "line": 40325 }, "current_section": { "heading": { "line": 40323, "level": 5, "text": "12.4 Documentation / measured-count drift" }, "start_line": 40323, "end_line": 40387, "text": "##### 12.4 Documentation / measured-count drift\n\n**(a) `BrokerFailureMatrix` 클래스 javadoc 이 강제되지 않는 규칙을 선언한다.** (`EVD-300`)\n\n```java\n// BrokerFailureMatrix.java:18-20\n *
A Stable adapter must cover every scenario. That rule is enforced by a test rather than\n * documented, because a promotion to Stable is exactly the moment the gap would otherwise be\n * overlooked.\n```\n\n측정:\n\n```\ngit grep -n \"isComplete\" -- src (messaging-testkit 범위)\n BrokerFailureMatrix.java:95 public boolean isComplete(String adapter) {\n CrossBrokerContractSuite.java:110 assertThat(matrix.isComplete(\"messaging-pulsar-experimental\"))\n```\n\n`isComplete` 의 호출부는 1곳이고 그것은 **Experimental** 어댑터가 불완전함을 단언한다. Stable 어댑터에 `isComplete` 를 거는 테스트는 없다.\n\n그리고 실제로 Stable 인 `messaging-kafka` 는 gap 을 가진 채 통과한다 — 그 사실이 같은 모듈에서 **명시적으로 단언되어 있다**.\n\n```java\n// CertifiedEvidenceTest.java:52-55\nassertThat(CertifiedEvidence.knownGaps(\"messaging-kafka\"))\n .as(\"a Kafka producer buffers before it learns a connection exists, so this stays unproven\")\n .contains(NetworkFaultScenario.CONNECTION_REFUSED);\n```\n\n코드는 \"정직한 gap 열거\"로 바뀌었고 그 결정이 테스트 본문 주석에 남아 있다.\n\n```java\n// CrossBrokerContractSuite.java:44-47\nvoid everyStableAdapterCoversEveryFaultScenario() {\n // The gaps are named rather than asserted empty. A Stable adapter with unrun scenarios is the\n // current, honest state; asserting emptiness here would only reinstate the self-declaration.\n```\n\n**바뀌지 않은 것은 두 가지다**: `BrokerFailureMatrix` 의 클래스 javadoc 과, 저 테스트 메서드 이름(`everyStableAdapterCoversEveryFaultScenario` — 본문은 covers 를 검사하지 않는다). 이 리프의 나머지 javadoc 들이 자기 이력을 정확히 갱신해 온 것과 대비되어 눈에 띈다.\n\n**(b) 선언된 project 의존 4개 중 2개가 import 0건.**\n\n```\nmessaging-core-api -> 사용 O\nmessaging-schema-api -> 사용 O (EncodedMessage)\nmessaging-policy -> import 0건\nmessaging-transport-spi -> import 0건\n```\n\n`messaging-spring-cloud-stream-bridge`, `messaging-kafka-share-experimental` 에서 이미 본 것과 같은 형태다. 다만 여기는 §12.3(b) 때문에 성격이 다르다 — policy 를 **안 쓰는 게 아니라 써야 하는데 리터럴로 우회**하고 있다.\n\n**(c) 증거의 커밋이 현재 트리가 아니다.**\n\n```\n현재 HEAD : 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916\n매니페스트의 gitCommit : e98b56eb03ecab588c21fd1e7dbcaa493c1d8645 (히스토리에 존재)\n```\n\n이는 결함이 아니다. 게이트가 `gitCommit`/`observedAt` 을 비교에서 제외하는 것이 명시적 설계이며 그 이유가 주석에 있다. 다만 `BrokerCertificationEvidence` javadoc 이 \"the commit are here because 'certified' is a claim about … a specific source tree; without them the evidence cannot be checked against anything later\" 라고 쓴 것에 비해, 실제로 그 필드를 **읽어서 무언가를 판정하는 코드는 없다**. 기록은 되고 활용은 되지 않는다.\n\n**(d) 지원 문서 9개 존재·내용 검사는 통과.** `MessagingDocumentationContractTest` 8건 전부 통과(`EVD-301`). 단, 이 검사는 `docs/messaging/support-matrix.md` 의 **등급 표기**만 본다. 같은 문서 23줄의 `runtime_memberships` 관련 서술 드리프트는 이 검사의 사정권 밖이며 §A19 에서 다룬다.\n\n---\n" }, "previous_section": { "heading": { "line": 40282, "level": 5, "text": "12.3 Duplicate mechanism sweep" }, "start_line": 40282, "end_line": 40322, "text": "##### 12.3 Duplicate mechanism sweep\n\n**(a) `Faults` 내부클래스 3중복 — 바이트 동일.** (`EVD-299`)\n\n```\nKafkaContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86\nRabbitContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86\nInMemoryMessagingHarness.java: 57줄 sha256[0:16]=3028b4591144fe86\ndiff kafka vs rabbit -> IDENTICAL\ndiff kafka vs inmemory -> IDENTICAL\n```\n\n`private static final class Faults implements FaultController` 57줄이 3개 모듈에 완전히 동일하게 존재한다. 총 171줄. 4개 불리언 필드 + 5개 오버라이드 + 4개 consume/query 메서드. 이 리프의 `src/main` 에 `DefaultFaultController` 하나만 두면 3벌이 1벌이 된다. 세 하니스가 `faults` 필드 타입만 공유하면 되므로 API 변경도 필요 없다.\n\n**(b) 1 MiB 한도 리터럴 8중복.** `PayloadPolicy.DEFAULT_MAX_BYTES = 1_048_576` 이 있는데도 같은 값이 리터럴로 다시 선언된다.\n\n```\nmessaging-policy/PayloadPolicy.java:17 DEFAULT_MAX_BYTES = 1_048_576 <- 정본\nmessaging-schema-api/RawBytesMessageCodec.java:21 DEFAULT_MAX_BYTES = 1_048_576\nmessaging-schema-json/JacksonMessageCodec.java:42 DEFAULT_MAX_BYTES = 1_048_576\nmessaging-schema-avro/AvroMessageCodec.java:46 DEFAULT_MAX_BYTES = 1_048_576\nmessaging-schema-protobuf/ProtobufMessageCodec.java:35 DEFAULT_MAX_BYTES = 1_048_576\nmessaging-claim-check/…RetentionValidatorTest.java:47 PORTABLE_PAYLOAD_LIMIT_BYTES\nmessaging-rabbit/RabbitContractHarness.java:40 MAX_PAYLOAD_BYTES\nmessaging-testkit/InMemoryMessagingHarness.java:31 MAX_PAYLOAD_BYTES <- 이 리프\nmessaging-testkit/ContractMessage.java:50 new byte[1_048_577] <- 이 리프\nmessaging-spring-boot-starter/DestinationSettings.java:175 @DefaultValue(\"1048576\")\n```\n\n`messaging-testkit` 은 `api project(':messaging:messaging-policy')` 를 선언하고 있으므로 `PayloadPolicy.DEFAULT_MAX_BYTES` 를 그냥 참조할 수 있다. §12.4 의 \"policy 미사용\" 과 합치면, 유일하게 policy 를 써야 할 자리에서 쓰지 않고 있는 셈이다.\n\n**(c) `hasLiveBrokerCertification` 대조 테스트의 항등식.**\n\n```java\n// CompatibilityMatrixTest.java:48-60 aCertificationClaimCannotBeMadeWithoutEvidence\nassertThat(entry.hasLiveBrokerCertification())\n .isEqualTo(BrokerFailureMatrix.from(CertifiedEvidence.recorded()).hasLiveBrokerCoverage(entry.adapter()));\n```\n\n`Entry.hasLiveBrokerCertification()` 의 본문이 정확히 우변과 같다(`CompatibilityMatrix.java:60-62`). 이 단언은 항상 참인 항등식이며, 어떤 회귀도 잡지 못한다. 같은 파일의 `everyStableAdapterIsCertifiedAgainstALiveBroker`(`:97-109`)와 `noExperimentalAdapterClaimsLiveBrokerCertification`(`:111-116`)이 실질 검사를 하고 있어 커버리지 손실은 없지만, 이름이 약속하는 것(\"증거 없이 인증 주장 불가\")을 이 테스트 자체는 검사하지 않는다.\n" }, "next_section": { "heading": { "line": 40388, "level": 4, "text": "13. Git/설계 문서에서 확인한 변화와 실패 기록" }, "start_line": 40388, "end_line": 40413, "text": "#### 13. Git/설계 문서에서 확인한 변화와 실패 기록\n\n```\na24ece9c feat: web, websocket 어댑터 추가 구현\n01372634 refactor: 각 어댑터터별 리펙토링 진행\n2f5d2fc2 feat: jpa, messaging, notification, mongo, graphql 어댑터터 구현체 추가\nd646c2f1 feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가\n```\n\n커밋 메시지는 정보가 거의 없다. 그러나 이 리프는 **코드 주석이 커밋 로그를 대신하는 드문 사례**다. 세 개의 javadoc 이 각각 \"무엇이 틀렸었고 왜 지금 형태인가\" 를 남겼다.\n\n| 위치 | 기록된 과거 결함 |\n|---|---|\n| `BrokerCertificationEvidence.java:11-15` | \"Coverage used to be a constant: every scenario marked live-broker for both brokers, with the suite that 'verified' the matrix asserting the contents of that same map.\" |\n| `CertifiedEvidence.java:12-16` | \"It was the list: an author added a line and the compatibility matrix, the cross-broker suite and the support matrix all believed it, on a machine that may never have started a container.\" |\n| `CompatibilityMatrix.Entry.hasLiveBrokerCertification` javadoc `:52-57` | \"As a field it was a boolean an author set next to the tier, and RabbitMQ carried `true` while no fault scenario had ever been executed against it.\" |\n| `BrokerFailureMatrix.from` javadoc `:111-114` | \"A hard-coded matrix marked every scenario as live-broker coverage for both brokers, and the suite that checked it asserted the same constants, so the whole claim passed on a machine with no Docker.\" |\n| `CompatibilityMatrix` ENTRIES 주석 `:80-84` | \"the matrix and the support document agreed with each other and with nothing that had executed.\" |\n| `CrossBrokerContractSuite` 필드 javadoc `:22-32` | \"A hard-coded map … would leave the assertions below comparing that map to itself\" |\n\n여섯 곳이 같은 결함의 여섯 얼굴이다: **자기 자신을 검증하는 상수**. 그리고 여섯 곳 모두 지금은 매니페스트를 가리킨다.\n\n`messaging-rabbit` 이 Stable 에서 Experimental 로 **강등된 흔적**도 남아 있다: `CompatibilityMatrixTest.theStableSetIsExactlyWhatALaneHasCertified` 의 `.as(\"RabbitMQ passes the shared contract, but no fault scenario has been run against it\")`. 강등의 근거가 \"계약은 통과하지만 결함 증거가 없다\" 로 정확히 적혀 있다.\n\n---\n" }, "context_range": { "start_line": 40282, "end_line": 40413 }, "context_lines": [ { "line": 40282, "text": "##### 12.3 Duplicate mechanism sweep" }, { "line": 40283, "text": "" }, { "line": 40284, "text": "**(a) `Faults` 내부클래스 3중복 — 바이트 동일.** (`EVD-299`)" }, { "line": 40285, "text": "" }, { "line": 40286, "text": "```" }, { "line": 40287, "text": "KafkaContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86" }, { "line": 40288, "text": "RabbitContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86" }, { "line": 40289, "text": "InMemoryMessagingHarness.java: 57줄 sha256[0:16]=3028b4591144fe86" }, { "line": 40290, "text": "diff kafka vs rabbit -> IDENTICAL" }, { "line": 40291, "text": "diff kafka vs inmemory -> IDENTICAL" }, { "line": 40292, "text": "```" }, { "line": 40293, "text": "" }, { "line": 40294, "text": "`private static final class Faults implements FaultController` 57줄이 3개 모듈에 완전히 동일하게 존재한다. 총 171줄. 4개 불리언 필드 + 5개 오버라이드 + 4개 consume/query 메서드. 이 리프의 `src/main` 에 `DefaultFaultController` 하나만 두면 3벌이 1벌이 된다. 세 하니스가 `faults` 필드 타입만 공유하면 되므로 API 변경도 필요 없다." }, { "line": 40295, "text": "" }, { "line": 40296, "text": "**(b) 1 MiB 한도 리터럴 8중복.** `PayloadPolicy.DEFAULT_MAX_BYTES = 1_048_576` 이 있는데도 같은 값이 리터럴로 다시 선언된다." }, { "line": 40297, "text": "" }, { "line": 40298, "text": "```" }, { "line": 40299, "text": "messaging-policy/PayloadPolicy.java:17 DEFAULT_MAX_BYTES = 1_048_576 <- 정본" }, { "line": 40300, "text": "messaging-schema-api/RawBytesMessageCodec.java:21 DEFAULT_MAX_BYTES = 1_048_576" }, { "line": 40301, "text": "messaging-schema-json/JacksonMessageCodec.java:42 DEFAULT_MAX_BYTES = 1_048_576" }, { "line": 40302, "text": "messaging-schema-avro/AvroMessageCodec.java:46 DEFAULT_MAX_BYTES = 1_048_576" }, { "line": 40303, "text": "messaging-schema-protobuf/ProtobufMessageCodec.java:35 DEFAULT_MAX_BYTES = 1_048_576" }, { "line": 40304, "text": "messaging-claim-check/…RetentionValidatorTest.java:47 PORTABLE_PAYLOAD_LIMIT_BYTES" }, { "line": 40305, "text": "messaging-rabbit/RabbitContractHarness.java:40 MAX_PAYLOAD_BYTES" }, { "line": 40306, "text": "messaging-testkit/InMemoryMessagingHarness.java:31 MAX_PAYLOAD_BYTES <- 이 리프" }, { "line": 40307, "text": "messaging-testkit/ContractMessage.java:50 new byte[1_048_577] <- 이 리프" }, { "line": 40308, "text": "messaging-spring-boot-starter/DestinationSettings.java:175 @DefaultValue(\"1048576\")" }, { "line": 40309, "text": "```" }, { "line": 40310, "text": "" }, { "line": 40311, "text": "`messaging-testkit` 은 `api project(':messaging:messaging-policy')` 를 선언하고 있으므로 `PayloadPolicy.DEFAULT_MAX_BYTES` 를 그냥 참조할 수 있다. §12.4 의 \"policy 미사용\" 과 합치면, 유일하게 policy 를 써야 할 자리에서 쓰지 않고 있는 셈이다." }, { "line": 40312, "text": "" }, { "line": 40313, "text": "**(c) `hasLiveBrokerCertification` 대조 테스트의 항등식.**" }, { "line": 40314, "text": "" }, { "line": 40315, "text": "```java" }, { "line": 40316, "text": "// CompatibilityMatrixTest.java:48-60 aCertificationClaimCannotBeMadeWithoutEvidence" }, { "line": 40317, "text": "assertThat(entry.hasLiveBrokerCertification())" }, { "line": 40318, "text": " .isEqualTo(BrokerFailureMatrix.from(CertifiedEvidence.recorded()).hasLiveBrokerCoverage(entry.adapter()));" }, { "line": 40319, "text": "```" }, { "line": 40320, "text": "" }, { "line": 40321, "text": "`Entry.hasLiveBrokerCertification()` 의 본문이 정확히 우변과 같다(`CompatibilityMatrix.java:60-62`). 이 단언은 항상 참인 항등식이며, 어떤 회귀도 잡지 못한다. 같은 파일의 `everyStableAdapterIsCertifiedAgainstALiveBroker`(`:97-109`)와 `noExperimentalAdapterClaimsLiveBrokerCertification`(`:111-116`)이 실질 검사를 하고 있어 커버리지 손실은 없지만, 이름이 약속하는 것(\"증거 없이 인증 주장 불가\")을 이 테스트 자체는 검사하지 않는다." }, { "line": 40322, "text": "" }, { "line": 40323, "text": "##### 12.4 Documentation / measured-count drift" }, { "line": 40324, "text": "" }, { "line": 40325, "text": "**(a) `BrokerFailureMatrix` 클래스 javadoc 이 강제되지 않는 규칙을 선언한다.** (`EVD-300`)" }, { "line": 40326, "text": "" }, { "line": 40327, "text": "```java" }, { "line": 40328, "text": "// BrokerFailureMatrix.java:18-20" }, { "line": 40329, "text": " *
A Stable adapter must cover every scenario. That rule is enforced by a test rather than" }, { "line": 40330, "text": " * documented, because a promotion to Stable is exactly the moment the gap would otherwise be" }, { "line": 40331, "text": " * overlooked." }, { "line": 40332, "text": "```" }, { "line": 40333, "text": "" }, { "line": 40334, "text": "측정:" }, { "line": 40335, "text": "" }, { "line": 40336, "text": "```" }, { "line": 40337, "text": "git grep -n \"isComplete\" -- src (messaging-testkit 범위)" }, { "line": 40338, "text": " BrokerFailureMatrix.java:95 public boolean isComplete(String adapter) {" }, { "line": 40339, "text": " CrossBrokerContractSuite.java:110 assertThat(matrix.isComplete(\"messaging-pulsar-experimental\"))" }, { "line": 40340, "text": "```" }, { "line": 40341, "text": "" }, { "line": 40342, "text": "`isComplete` 의 호출부는 1곳이고 그것은 **Experimental** 어댑터가 불완전함을 단언한다. Stable 어댑터에 `isComplete` 를 거는 테스트는 없다." }, { "line": 40343, "text": "" }, { "line": 40344, "text": "그리고 실제로 Stable 인 `messaging-kafka` 는 gap 을 가진 채 통과한다 — 그 사실이 같은 모듈에서 **명시적으로 단언되어 있다**." }, { "line": 40345, "text": "" }, { "line": 40346, "text": "```java" }, { "line": 40347, "text": "// CertifiedEvidenceTest.java:52-55" }, { "line": 40348, "text": "assertThat(CertifiedEvidence.knownGaps(\"messaging-kafka\"))" }, { "line": 40349, "text": " .as(\"a Kafka producer buffers before it learns a connection exists, so this stays unproven\")" }, { "line": 40350, "text": " .contains(NetworkFaultScenario.CONNECTION_REFUSED);" }, { "line": 40351, "text": "```" }, { "line": 40352, "text": "" }, { "line": 40353, "text": "코드는 \"정직한 gap 열거\"로 바뀌었고 그 결정이 테스트 본문 주석에 남아 있다." }, { "line": 40354, "text": "" }, { "line": 40355, "text": "```java" }, { "line": 40356, "text": "// CrossBrokerContractSuite.java:44-47" }, { "line": 40357, "text": "void everyStableAdapterCoversEveryFaultScenario() {" }, { "line": 40358, "text": " // The gaps are named rather than asserted empty. A Stable adapter with unrun scenarios is the" }, { "line": 40359, "text": " // current, honest state; asserting emptiness here would only reinstate the self-declaration." }, { "line": 40360, "text": "```" }, { "line": 40361, "text": "" }, { "line": 40362, "text": "**바뀌지 않은 것은 두 가지다**: `BrokerFailureMatrix` 의 클래스 javadoc 과, 저 테스트 메서드 이름(`everyStableAdapterCoversEveryFaultScenario` — 본문은 covers 를 검사하지 않는다). 이 리프의 나머지 javadoc 들이 자기 이력을 정확히 갱신해 온 것과 대비되어 눈에 띈다." }, { "line": 40363, "text": "" }, { "line": 40364, "text": "**(b) 선언된 project 의존 4개 중 2개가 import 0건.**" }, { "line": 40365, "text": "" }, { "line": 40366, "text": "```" }, { "line": 40367, "text": "messaging-core-api -> 사용 O" }, { "line": 40368, "text": "messaging-schema-api -> 사용 O (EncodedMessage)" }, { "line": 40369, "text": "messaging-policy -> import 0건" }, { "line": 40370, "text": "messaging-transport-spi -> import 0건" }, { "line": 40371, "text": "```" }, { "line": 40372, "text": "" }, { "line": 40373, "text": "`messaging-spring-cloud-stream-bridge`, `messaging-kafka-share-experimental` 에서 이미 본 것과 같은 형태다. 다만 여기는 §12.3(b) 때문에 성격이 다르다 — policy 를 **안 쓰는 게 아니라 써야 하는데 리터럴로 우회**하고 있다." }, { "line": 40374, "text": "" }, { "line": 40375, "text": "**(c) 증거의 커밋이 현재 트리가 아니다.**" }, { "line": 40376, "text": "" }, { "line": 40377, "text": "```" }, { "line": 40378, "text": "현재 HEAD : 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916" }, { "line": 40379, "text": "매니페스트의 gitCommit : e98b56eb03ecab588c21fd1e7dbcaa493c1d8645 (히스토리에 존재)" }, { "line": 40380, "text": "```" }, { "line": 40381, "text": "" }, { "line": 40382, "text": "이는 결함이 아니다. 게이트가 `gitCommit`/`observedAt` 을 비교에서 제외하는 것이 명시적 설계이며 그 이유가 주석에 있다. 다만 `BrokerCertificationEvidence` javadoc 이 \"the commit are here because 'certified' is a claim about … a specific source tree; without them the evidence cannot be checked against anything later\" 라고 쓴 것에 비해, 실제로 그 필드를 **읽어서 무언가를 판정하는 코드는 없다**. 기록은 되고 활용은 되지 않는다." }, { "line": 40383, "text": "" }, { "line": 40384, "text": "**(d) 지원 문서 9개 존재·내용 검사는 통과.** `MessagingDocumentationContractTest` 8건 전부 통과(`EVD-301`). 단, 이 검사는 `docs/messaging/support-matrix.md` 의 **등급 표기**만 본다. 같은 문서 23줄의 `runtime_memberships` 관련 서술 드리프트는 이 검사의 사정권 밖이며 §A19 에서 다룬다." }, { "line": 40385, "text": "" }, { "line": 40386, "text": "---" }, { "line": 40387, "text": "" }, { "line": 40388, "text": "#### 13. Git/설계 문서에서 확인한 변화와 실패 기록" }, { "line": 40389, "text": "" }, { "line": 40390, "text": "```" }, { "line": 40391, "text": "a24ece9c feat: web, websocket 어댑터 추가 구현" }, { "line": 40392, "text": "01372634 refactor: 각 어댑터터별 리펙토링 진행" }, { "line": 40393, "text": "2f5d2fc2 feat: jpa, messaging, notification, mongo, graphql 어댑터터 구현체 추가" }, { "line": 40394, "text": "d646c2f1 feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가" }, { "line": 40395, "text": "```" }, { "line": 40396, "text": "" }, { "line": 40397, "text": "커밋 메시지는 정보가 거의 없다. 그러나 이 리프는 **코드 주석이 커밋 로그를 대신하는 드문 사례**다. 세 개의 javadoc 이 각각 \"무엇이 틀렸었고 왜 지금 형태인가\" 를 남겼다." }, { "line": 40398, "text": "" }, { "line": 40399, "text": "| 위치 | 기록된 과거 결함 |" }, { "line": 40400, "text": "|---|---|" }, { "line": 40401, "text": "| `BrokerCertificationEvidence.java:11-15` | \"Coverage used to be a constant: every scenario marked live-broker for both brokers, with the suite that 'verified' the matrix asserting the contents of that same map.\" |" }, { "line": 40402, "text": "| `CertifiedEvidence.java:12-16` | \"It was the list: an author added a line and the compatibility matrix, the cross-broker suite and the support matrix all believed it, on a machine that may never have started a container.\" |" }, { "line": 40403, "text": "| `CompatibilityMatrix.Entry.hasLiveBrokerCertification` javadoc `:52-57` | \"As a field it was a boolean an author set next to the tier, and RabbitMQ carried `true` while no fault scenario had ever been executed against it.\" |" }, { "line": 40404, "text": "| `BrokerFailureMatrix.from` javadoc `:111-114` | \"A hard-coded matrix marked every scenario as live-broker coverage for both brokers, and the suite that checked it asserted the same constants, so the whole claim passed on a machine with no Docker.\" |" }, { "line": 40405, "text": "| `CompatibilityMatrix` ENTRIES 주석 `:80-84` | \"the matrix and the support document agreed with each other and with nothing that had executed.\" |" }, { "line": 40406, "text": "| `CrossBrokerContractSuite` 필드 javadoc `:22-32` | \"A hard-coded map … would leave the assertions below comparing that map to itself\" |" }, { "line": 40407, "text": "" }, { "line": 40408, "text": "여섯 곳이 같은 결함의 여섯 얼굴이다: **자기 자신을 검증하는 상수**. 그리고 여섯 곳 모두 지금은 매니페스트를 가리킨다." }, { "line": 40409, "text": "" }, { "line": 40410, "text": "`messaging-rabbit` 이 Stable 에서 Experimental 로 **강등된 흔적**도 남아 있다: `CompatibilityMatrixTest.theStableSetIsExactlyWhatALaneHasCertified` 의 `.as(\"RabbitMQ passes the shared contract, but no fault scenario has been run against it\")`. 강등의 근거가 \"계약은 통과하지만 결함 증거가 없다\" 로 정확히 적혀 있다." }, { "line": 40411, "text": "" }, { "line": 40412, "text": "---" }, { "line": 40413, "text": "" } ], "numbered_context": "40282 | ##### 12.3 Duplicate mechanism sweep\n40283 | \n40284 | **(a) `Faults` 내부클래스 3중복 — 바이트 동일.** (`EVD-299`)\n40285 | \n40286 | ```\n40287 | KafkaContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86\n40288 | RabbitContractHarness.java : 57줄 sha256[0:16]=3028b4591144fe86\n40289 | InMemoryMessagingHarness.java: 57줄 sha256[0:16]=3028b4591144fe86\n40290 | diff kafka vs rabbit -> IDENTICAL\n40291 | diff kafka vs inmemory -> IDENTICAL\n40292 | ```\n40293 | \n40294 | `private static final class Faults implements FaultController` 57줄이 3개 모듈에 완전히 동일하게 존재한다. 총 171줄. 4개 불리언 필드 + 5개 오버라이드 + 4개 consume/query 메서드. 이 리프의 `src/main` 에 `DefaultFaultController` 하나만 두면 3벌이 1벌이 된다. 세 하니스가 `faults` 필드 타입만 공유하면 되므로 API 변경도 필요 없다.\n40295 | \n40296 | **(b) 1 MiB 한도 리터럴 8중복.** `PayloadPolicy.DEFAULT_MAX_BYTES = 1_048_576` 이 있는데도 같은 값이 리터럴로 다시 선언된다.\n40297 | \n40298 | ```\n40299 | messaging-policy/PayloadPolicy.java:17 DEFAULT_MAX_BYTES = 1_048_576 <- 정본\n40300 | messaging-schema-api/RawBytesMessageCodec.java:21 DEFAULT_MAX_BYTES = 1_048_576\n40301 | messaging-schema-json/JacksonMessageCodec.java:42 DEFAULT_MAX_BYTES = 1_048_576\n40302 | messaging-schema-avro/AvroMessageCodec.java:46 DEFAULT_MAX_BYTES = 1_048_576\n40303 | messaging-schema-protobuf/ProtobufMessageCodec.java:35 DEFAULT_MAX_BYTES = 1_048_576\n40304 | messaging-claim-check/…RetentionValidatorTest.java:47 PORTABLE_PAYLOAD_LIMIT_BYTES\n40305 | messaging-rabbit/RabbitContractHarness.java:40 MAX_PAYLOAD_BYTES\n40306 | messaging-testkit/InMemoryMessagingHarness.java:31 MAX_PAYLOAD_BYTES <- 이 리프\n40307 | messaging-testkit/ContractMessage.java:50 new byte[1_048_577] <- 이 리프\n40308 | messaging-spring-boot-starter/DestinationSettings.java:175 @DefaultValue(\"1048576\")\n40309 | ```\n40310 | \n40311 | `messaging-testkit` 은 `api project(':messaging:messaging-policy')` 를 선언하고 있으므로 `PayloadPolicy.DEFAULT_MAX_BYTES` 를 그냥 참조할 수 있다. §12.4 의 \"policy 미사용\" 과 합치면, 유일하게 policy 를 써야 할 자리에서 쓰지 않고 있는 셈이다.\n40312 | \n40313 | **(c) `hasLiveBrokerCertification` 대조 테스트의 항등식.**\n40314 | \n40315 | ```java\n40316 | // CompatibilityMatrixTest.java:48-60 aCertificationClaimCannotBeMadeWithoutEvidence\n40317 | assertThat(entry.hasLiveBrokerCertification())\n40318 | .isEqualTo(BrokerFailureMatrix.from(CertifiedEvidence.recorded()).hasLiveBrokerCoverage(entry.adapter()));\n40319 | ```\n40320 | \n40321 | `Entry.hasLiveBrokerCertification()` 의 본문이 정확히 우변과 같다(`CompatibilityMatrix.java:60-62`). 이 단언은 항상 참인 항등식이며, 어떤 회귀도 잡지 못한다. 같은 파일의 `everyStableAdapterIsCertifiedAgainstALiveBroker`(`:97-109`)와 `noExperimentalAdapterClaimsLiveBrokerCertification`(`:111-116`)이 실질 검사를 하고 있어 커버리지 손실은 없지만, 이름이 약속하는 것(\"증거 없이 인증 주장 불가\")을 이 테스트 자체는 검사하지 않는다.\n40322 | \n40323 | ##### 12.4 Documentation / measured-count drift\n40324 | \n40325 | **(a) `BrokerFailureMatrix` 클래스 javadoc 이 강제되지 않는 규칙을 선언한다.** (`EVD-300`)\n40326 | \n40327 | ```java\n40328 | // BrokerFailureMatrix.java:18-20\n40329 | *
A Stable adapter must cover every scenario. That rule is enforced by a test rather than\n40330 | * documented, because a promotion to Stable is exactly the moment the gap would otherwise be\n40331 | * overlooked.\n40332 | ```\n40333 | \n40334 | 측정:\n40335 | \n40336 | ```\n40337 | git grep -n \"isComplete\" -- src (messaging-testkit 범위)\n40338 | BrokerFailureMatrix.java:95 public boolean isComplete(String adapter) {\n40339 | CrossBrokerContractSuite.java:110 assertThat(matrix.isComplete(\"messaging-pulsar-experimental\"))\n40340 | ```\n40341 | \n40342 | `isComplete` 의 호출부는 1곳이고 그것은 **Experimental** 어댑터가 불완전함을 단언한다. Stable 어댑터에 `isComplete` 를 거는 테스트는 없다.\n40343 | \n40344 | 그리고 실제로 Stable 인 `messaging-kafka` 는 gap 을 가진 채 통과한다 — 그 사실이 같은 모듈에서 **명시적으로 단언되어 있다**.\n40345 | \n40346 | ```java\n40347 | // CertifiedEvidenceTest.java:52-55\n40348 | assertThat(CertifiedEvidence.knownGaps(\"messaging-kafka\"))\n40349 | .as(\"a Kafka producer buffers before it learns a connection exists, so this stays unproven\")\n40350 | .contains(NetworkFaultScenario.CONNECTION_REFUSED);\n40351 | ```\n40352 | \n40353 | 코드는 \"정직한 gap 열거\"로 바뀌었고 그 결정이 테스트 본문 주석에 남아 있다.\n40354 | \n40355 | ```java\n40356 | // CrossBrokerContractSuite.java:44-47\n40357 | void everyStableAdapterCoversEveryFaultScenario() {\n40358 | // The gaps are named rather than asserted empty. A Stable adapter with unrun scenarios is the\n40359 | // current, honest state; asserting emptiness here would only reinstate the self-declaration.\n40360 | ```\n40361 | \n40362 | **바뀌지 않은 것은 두 가지다**: `BrokerFailureMatrix` 의 클래스 javadoc 과, 저 테스트 메서드 이름(`everyStableAdapterCoversEveryFaultScenario` — 본문은 covers 를 검사하지 않는다). 이 리프의 나머지 javadoc 들이 자기 이력을 정확히 갱신해 온 것과 대비되어 눈에 띈다.\n40363 | \n40364 | **(b) 선언된 project 의존 4개 중 2개가 import 0건.**\n40365 | \n40366 | ```\n40367 | messaging-core-api -> 사용 O\n40368 | messaging-schema-api -> 사용 O (EncodedMessage)\n40369 | messaging-policy -> import 0건\n40370 | messaging-transport-spi -> import 0건\n40371 | ```\n40372 | \n40373 | `messaging-spring-cloud-stream-bridge`, `messaging-kafka-share-experimental` 에서 이미 본 것과 같은 형태다. 다만 여기는 §12.3(b) 때문에 성격이 다르다 — policy 를 **안 쓰는 게 아니라 써야 하는데 리터럴로 우회**하고 있다.\n40374 | \n40375 | **(c) 증거의 커밋이 현재 트리가 아니다.**\n40376 | \n40377 | ```\n40378 | 현재 HEAD : 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916\n40379 | 매니페스트의 gitCommit : e98b56eb03ecab588c21fd1e7dbcaa493c1d8645 (히스토리에 존재)\n40380 | ```\n40381 | \n40382 | 이는 결함이 아니다. 게이트가 `gitCommit`/`observedAt` 을 비교에서 제외하는 것이 명시적 설계이며 그 이유가 주석에 있다. 다만 `BrokerCertificationEvidence` javadoc 이 \"the commit are here because 'certified' is a claim about … a specific source tree; without them the evidence cannot be checked against anything later\" 라고 쓴 것에 비해, 실제로 그 필드를 **읽어서 무언가를 판정하는 코드는 없다**. 기록은 되고 활용은 되지 않는다.\n40383 | \n40384 | **(d) 지원 문서 9개 존재·내용 검사는 통과.** `MessagingDocumentationContractTest` 8건 전부 통과(`EVD-301`). 단, 이 검사는 `docs/messaging/support-matrix.md` 의 **등급 표기**만 본다. 같은 문서 23줄의 `runtime_memberships` 관련 서술 드리프트는 이 검사의 사정권 밖이며 §A19 에서 다룬다.\n40385 | \n40386 | ---\n40387 | \n40388 | #### 13. Git/설계 문서에서 확인한 변화와 실패 기록\n40389 | \n40390 | ```\n40391 | a24ece9c feat: web, websocket 어댑터 추가 구현\n40392 | 01372634 refactor: 각 어댑터터별 리펙토링 진행\n40393 | 2f5d2fc2 feat: jpa, messaging, notification, mongo, graphql 어댑터터 구현체 추가\n40394 | d646c2f1 feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가\n40395 | ```\n40396 | \n40397 | 커밋 메시지는 정보가 거의 없다. 그러나 이 리프는 **코드 주석이 커밋 로그를 대신하는 드문 사례**다. 세 개의 javadoc 이 각각 \"무엇이 틀렸었고 왜 지금 형태인가\" 를 남겼다.\n40398 | \n40399 | | 위치 | 기록된 과거 결함 |\n40400 | |---|---|\n40401 | | `BrokerCertificationEvidence.java:11-15` | \"Coverage used to be a constant: every scenario marked live-broker for both brokers, with the suite that 'verified' the matrix asserting the contents of that same map.\" |\n40402 | | `CertifiedEvidence.java:12-16` | \"It was the list: an author added a line and the compatibility matrix, the cross-broker suite and the support matrix all believed it, on a machine that may never have started a container.\" |\n40403 | | `CompatibilityMatrix.Entry.hasLiveBrokerCertification` javadoc `:52-57` | \"As a field it was a boolean an author set next to the tier, and RabbitMQ carried `true` while no fault scenario had ever been executed against it.\" |\n40404 | | `BrokerFailureMatrix.from` javadoc `:111-114` | \"A hard-coded matrix marked every scenario as live-broker coverage for both brokers, and the suite that checked it asserted the same constants, so the whole claim passed on a machine with no Docker.\" |\n40405 | | `CompatibilityMatrix` ENTRIES 주석 `:80-84` | \"the matrix and the support document agreed with each other and with nothing that had executed.\" |\n40406 | | `CrossBrokerContractSuite` 필드 javadoc `:22-32` | \"A hard-coded map … would leave the assertions below comparing that map to itself\" |\n40407 | \n40408 | 여섯 곳이 같은 결함의 여섯 얼굴이다: **자기 자신을 검증하는 상수**. 그리고 여섯 곳 모두 지금은 매니페스트를 가리킨다.\n40409 | \n40410 | `messaging-rabbit` 이 Stable 에서 Experimental 로 **강등된 흔적**도 남아 있다: `CompatibilityMatrixTest.theStableSetIsExactlyWhatALaneHasCertified` 의 `.as(\"RabbitMQ passes the shared contract, but no fault scenario has been run against it\")`. 강등의 근거가 \"계약은 통과하지만 결함 증거가 없다\" 로 정확히 적혀 있다.\n40411 | \n40412 | ---\n40413 | ",
"headings": [
{
"line": 1,
"level": 1,
"text": "clean-architecture-backend-template — 상세 분석 (통합 정본)"
},
{
"line": 40,
"level": 2,
"text": "0. 이 문서를 읽는 법"
},
{
"line": 60,
"level": 2,
"text": "1. Project map — 숫자로 먼저"
},
{
"line": 62,
"level": 3,
"text": "1.1 빌드와 레지스트리"
},
{
"line": 81,
"level": 3,
"text": "1.2 가족별 분모와 출하 여부"
},
{
"line": 94,
"level": 3,
"text": "1.3 leaf별 규모 (main Java 기준 상위)"
},
{
"line": 119,
"level": 3,
"text": "1.4 이 표에서 읽어야 할 것"
},
{
"line": 168,
"level": 2,
"text": "2. Architectural boundaries — 무엇이 경계를 강제하는가"
},
{
"line": 173,
"level": 3,
"text": "2.1 강제 장치 목록"
},
{
"line": 189,
"level": 3,
"text": "2.2 `CleanArchitectureTest`의 규칙 14종"
},
{
"line": 212,
"level": 3,
"text": "2.3 검증된 경계 — 실제로 성립하는 것"
},
{
"line": 266,
"level": 3,
"text": "2.4 경계가 열려 있는 지점"
},
{
"line": 300,
"level": 2,
"text": "3. Representative execution paths"
},
{
"line": 302,
"level": 3,
"text": "3.1 HTTP 요청 — 출하 경로"
},
{
"line": 364,
"level": 3,
"text": "3.2 트랜잭션 — `application-core` 포트에서 PostgreSQL local timeout까지"
},
{
"line": 453,
"level": 3,
"text": "3.3 메시지 발행 — messaging 플랫폼"
},
{
"line": 494,
"level": 3,
"text": "3.4 gRPC — 채택 시점 경로"
},
{
"line": 518,
"level": 3,
"text": "3.5 알림 발송 — 논리적 수락과 provider 불확실성"
},
{
"line": 539,
"level": 2,
"text": "4. Data and state"
},
{
"line": 541,
"level": 3,
"text": "4.1 관계형 — `persistence-jpa` (605 파일 / main 350 / 27,744 LOC)"
},
{
"line": 654,
"level": 3,
"text": "4.2 문서형 — `persistence-mongo` (497 파일 / main 351 / 22,924 LOC)"
},
{
"line": 705,
"level": 3,
"text": "4.3 messaging 신뢰성 저장소 (`19` §7)"
},
{
"line": 757,
"level": 3,
"text": "4.4 fileserver / objectstorage / cache-redis"
},
{
"line": 788,
"level": 2,
"text": "5. Failure and operational behavior"
},
{
"line": 790,
"level": 3,
"text": "5.1 실패 분류 — 세 개의 계층"
},
{
"line": 824,
"level": 3,
"text": "5.2 관측 — 태그를 유한하게, 그리고 그 대가"
},
{
"line": 854,
"level": 3,
"text": "5.3 시작 검증기 — 법칙과 그 예외"
},
{
"line": 903,
"level": 3,
"text": "5.4 admin plane — 가장 잘 조립된 게이트"
},
{
"line": 939,
"level": 3,
"text": "5.5 gRPC 구현 층의 원자성 (`20` §7)"
},
{
"line": 1011,
"level": 2,
"text": "6. Tests and verification coverage"
},
{
"line": 1013,
"level": 3,
"text": "6.1 실행한 것"
},
{
"line": 1025,
"level": 3,
"text": "6.2 실행하지 않은 것과 그 이유"
},
{
"line": 1047,
"level": 3,
"text": "6.3 fail-closed 레인 규약"
},
{
"line": 1071,
"level": 3,
"text": "6.4 완전히 닫힌 게이트 하나 — messaging 인증 체인"
},
{
"line": 1111,
"level": 3,
"text": "6.5 evidence manifest — JPA의 R1/R2 분리"
},
{
"line": 1125,
"level": 3,
"text": "6.6 게이트가 통과하면서 아무것도 증명하지 않는 경우 — 14건"
},
{
"line": 1156,
"level": 2,
"text": "7. 이 저장소에서 반복된 네 가지 형태"
},
{
"line": 1160,
"level": 3,
"text": "7.1 형태 A — 판정하는 코드는 있고, 부르는 코드가 없다"
},
{
"line": 1203,
"level": 3,
"text": "7.2 형태 B — 게이트가 통과하면서 아무것도 증명하지 않는다"
},
{
"line": 1214,
"level": 3,
"text": "7.3 형태 C — 중복 장치에서 조립된 쪽이 약한 쪽이다"
},
{
"line": 1239,
"level": 3,
"text": "7.4 형태 D — 문서 드리프트, 그리고 그 방향"
},
{
"line": 1274,
"level": 3,
"text": "7.5 공시 스펙트럼 — 자기 미완성을 얼마나 말했는가"
},
{
"line": 1289,
"level": 3,
"text": "7.6 학습 전이 — messaging → grpc"
},
{
"line": 1308,
"level": 2,
"text": "8. Confirmed problems"
},
{
"line": 1310,
"level": 3,
"text": "8.1 P1 — 지금 출하되는 아티팩트에서 틀린 동작"
},
{
"line": 1349,
"level": 3,
"text": "8.2 P2 — 명확한 실패 시나리오를 가진 실질적 공백"
},
{
"line": 1392,
"level": 3,
"text": "8.3 심각도가 등급 때문에 낮아진 것"
},
{
"line": 1403,
"level": 2,
"text": "9. Reusable criteria and rules"
},
{
"line": 1452,
"level": 2,
"text": "10. Explicit project decisions"
},
{
"line": 1457,
"level": 3,
"text": "10.1 계약과 경계"
},
{
"line": 1468,
"level": 3,
"text": "10.2 실패와 불확실성"
},
{
"line": 1480,
"level": 3,
"text": "10.3 조립과 활성화"
},
{
"line": 1492,
"level": 3,
"text": "10.4 데이터와 경계값"
},
{
"line": 1506,
"level": 3,
"text": "10.5 증거와 게이트"
},
{
"line": 1523,
"level": 2,
"text": "11. Unresolved questions"
},
{
"line": 1564,
"level": 2,
"text": "12. Evidence index"
},
{
"line": 1581,
"level": 2,
"text": "13. Limits of this analysis"
},
{
"line": 1632,
"level": 2,
"text": "14. 사이클 2 — 18개 리프 재검증과 23개 리프 전수 통독"
},
{
"line": 1634,
"level": 3,
"text": "14.1 18개 리프 재검증"
},
{
"line": 1668,
"level": 3,
"text": "14.2 23개 리프 전수 통독"
},
{
"line": 1747,
"level": 2,
"text": "부록 A. 모듈 문서 지도"
},
{
"line": 1779,
"level": 2,
"text": "부록 B. 자주 쓸 명령"
},
{
"line": 1825,
"level": 2,
"text": "부록 C. 다시 읽는다면 이 순서"
},
{
"line": 1839,
"level": 1,
"text": "제2부 — 모듈 분석 전문"
},
{
"line": 1845,
"level": 2,
"text": "A00. project-overview"
},
{
"line": 1849,
"level": 3,
"text": "Project Overview"
},
{
"line": 1856,
"level": 4,
"text": "분석 기준 revision"
},
{
"line": 1867,
"level": 4,
"text": "최종 커버리지"
},
{
"line": 1884,
"level": 4,
"text": "Build and module map"
},
{
"line": 1939,
"level": 4,
"text": "Dependency direction"
},
{
"line": 1945,
"level": 4,
"text": "Runtime entry points"
},
{
"line": 1951,
"level": 4,
"text": "Persistence / messaging / external systems"
},
{
"line": 1955,
"level": 4,
"text": "Test topology"
},
{
"line": 1960,
"level": 4,
"text": "Configuration and operational surfaces"
},
{
"line": 1964,
"level": 4,
"text": "분석할 bounded scopes (계획 — 실제 문서 배치는 위 \"최종 커버리지\" 참조)"
},
{
"line": 1977,
"level": 4,
"text": "아직 단정하지 않는 것 (분석 시작 시점의 목록)"
},
{
"line": 1993,
"level": 2,
"text": "A01. domain-core"
},
{
"line": 1997,
"level": 3,
"text": "domain-core 상세 분석"
},
{
"line": 2000,
"level": 4,
"text": "SSOT identity — 2026-08-31 재검증"
},
{
"line": 2015,
"level": 4,
"text": "분석 범위와 결론 상태"
},
{
"line": 2026,
"level": 4,
"text": "1. Quantified scope map"
},
{
"line": 2028,
"level": 5,
"text": "Owned source"
},
{
"line": 2042,
"level": 4,
"text": "2. Coverage ledger"
},
{
"line": 2062,
"level": 4,
"text": "3. 이 모듈이 실제로 소유하는 것"
},
{
"line": 2064,
"level": 5,
"text": "관찰: 재사용 가능한 도메인 “내용”보다 도메인 모델링 계약을 소유한다"
},
{
"line": 2073,
"level": 4,
"text": "4. Identifier contract"
},
{
"line": 2075,
"level": 5,
"text": "`ResourceId