{ "schema_version": "1.0", "document": "/home/donghyeon/workspace/chat-gpt-container/document-haness/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": 38954, "line": 38954 }, "current_section": { "heading": { "line": 38954, "level": 4, "text": "1. 모듈의 정체와 경계" }, "start_line": 38954, "end_line": 38983, "text": "#### 1. 모듈의 정체와 경계\n\nSpring Cloud Stream 바인딩을 이미 쓰는 서비스가 같은 논리 목적지에 닿게 하는 **상호운용 seam**이다.\n\n```java\n// MessagingBindingBridge.java:8-15\n *
The bridge is an interoperability seam, not a second messaging API. Its whole reason to exist\n * is that a service already has Stream bindings and needs to reach the same destinations without a\n * rewrite.\n *\n *
Binder semantics are never promoted to platform guarantees. Stream's binder has its own retry,\n * its own dead-letter, and its own acknowledgement mode, and they look enough like the platform's\n * to be mistaken for them — so a destination that actually relies on the platform's versions is\n * refused by {@link StreamBridgePolicyGuard} rather than served with the binder's.\n```\n\n**\"look enough like the platform's to be mistaken for them\"**이 이 leaf 전체의 위협 모델이다. 브리지는 기능을 추가하지 않고 **차이를 드러낸다.**\n\n세 층으로 그것을 한다.\n\n| 층 | 무엇을 |\n|---|---|\n| `StreamBridgePolicyGuard` | 플랫폼 보장에 의존하는 목적지를 아예 거절 |\n| `BindingProfileValidator` | 바인더 확장 속성이 프로파일 결정을 덮는 것을 거절 |\n| `BindingCapabilityReport` | 남은 차이를 **문장으로** 기록 |\n\n세 번째가 특이하다 — 거절할 수 없는 차이를 문서화 가능한 값으로 만든다.\n\n---\n" }, "previous_section": { "heading": { "line": 38909, "level": 4, "text": "0. SSOT identity / 커버리지와 숫자 지도" }, "start_line": 38909, "end_line": 38953, "text": "#### 0. SSOT identity / 커버리지와 숫자 지도\n\n- registered leaf id: `messaging-spring-cloud-stream-bridge`\n- canonical state `analysisFile`: §A19-MESSAGING-SPRING-CLOUD-STREAM-BRIDGE\n- source path: `src/messaging/messaging-spring-cloud-stream-bridge`\n- registry `allowed_dependencies`: `[\"messaging-core-api\", \"messaging-policy\", \"messaging-transport-spi\"]`\n- registry `runtime_memberships`: **`[]`** — build-only\n\n##### 숫자\n\n| 항목 | 수 |\n|---|---:|\n| production Java 파일 | 6 |\n| production LOC | 507 |\n| 패키지 | 1 (`dev.caskeleton.messaging.streambridge`) |\n| test 파일 | 2 |\n| test 메서드(실행 확인) | **20** |\n| 선언된 의존 | project 3 + vendor 1 |\n| **실제 import되는 의존** | **project 2** (§12.4) |\n\n여섯 타입:\n\n| 타입 | 종류 | 역할 | leaf 밖 참조 |\n|---|---|---|---:|\n| `MessagingBindingBridge` | interface | 논리 목적지 ↔ Stream 바인딩 | 0 |\n| `SpringCloudStreamPublisherBridge` | class | 발행 측 + 위 인터페이스 구현 | 0 |\n| `SpringCloudStreamConsumerBridge` | class | 수신 측 | 0 |\n| `StreamBridgePolicyGuard` | class | 목적지가 브리지 대상인가 | 0 |\n| `BindingProfileValidator` | class | 바인딩 구성이 일관적인가 | 0 |\n| `BindingCapabilityReport` | record | 무엇을 보장하지 **않는가** | 0 |\n\n##### Coverage ledger\n\n| scope/file group | count | disposition | reason |\n|---|---:|---|---|\n| `src/main/java/**` (6) | 6 | `FULL_READ` | 전 파일 본문 확인 |\n| `src/test/java/**` (2) | 2 | `FULL_READ` | 20개 테스트명·단언 확인 |\n| `build.gradle` | 1 | `FULL_READ` | 9줄 |\n| `gradle.lockfile` | 1 | `STRUCTURAL_ONLY` | 잠금 파일 |\n| `build/**` | — | `EXCLUDED` | 빌드 산출물 |\n\n`UNCLASSIFIED` 0.\n\n---\n" }, "next_section": { "heading": { "line": 38984, "level": 4, "text": "2. 의존성과 런타임 배선" }, "start_line": 38984, "end_line": 39008, "text": "#### 2. 의존성과 런타임 배선\n\n**선언된 것과 쓰이는 것이 다르다.**\n\n| 선언 | scope | 실제 import |\n|---|---|---|\n| `messaging-core-api` | api | **o** — `DestinationName`, `MessagingConfigurationException`, publish 6타입 |\n| `messaging-policy` | api | **o** — `DestinationProfile`, `RetryMode` |\n| `messaging-transport-spi` | api | **x** |\n| `org.springframework:spring-context` | implementation | **x** |\n\n`grep -rn 'import dev.caskeleton.messaging.transport\\|import org.springframework'` → exit 1.\n\n**Spring Cloud Stream 브리지가 Spring을 import하지 않는다.** 바인더 접촉면 전체가 두 함수형 인터페이스로 추상화돼 있다 — `SpringCloudStreamPublisherBridge.ChannelSend`와 `SpringCloudStreamConsumerBridge.BridgedHandler`. javadoc이 그 목적을 적는다 — \"isolated so the bridge is testable without a binder\".\n\n즉 **`spring-context` 의존은 실제 통합 코드가 있어야 필요했을 것**인데 그 코드가 없다. §12.4.\n\n나가는 것: 없다. 어떤 leaf의 `allowed_dependencies`에도 이 leaf가 없고 starter 목록에도 없다.\n\n런타임 배선: 없음. `runtime_memberships: []`. bean 없음.\n\n**소비자 0 · membership `[]` · 조립 0의 삼중 정합** — `messaging-kafka-share-experimental`·`messaging-schema-avro`와 같은 상태다.\n\n---\n" }, "context_range": { "start_line": 38909, "end_line": 39008 }, "context_lines": [ { "line": 38909, "text": "#### 0. SSOT identity / 커버리지와 숫자 지도" }, { "line": 38910, "text": "" }, { "line": 38911, "text": "- registered leaf id: `messaging-spring-cloud-stream-bridge`" }, { "line": 38912, "text": "- canonical state `analysisFile`: §A19-MESSAGING-SPRING-CLOUD-STREAM-BRIDGE" }, { "line": 38913, "text": "- source path: `src/messaging/messaging-spring-cloud-stream-bridge`" }, { "line": 38914, "text": "- registry `allowed_dependencies`: `[\"messaging-core-api\", \"messaging-policy\", \"messaging-transport-spi\"]`" }, { "line": 38915, "text": "- registry `runtime_memberships`: **`[]`** — build-only" }, { "line": 38916, "text": "" }, { "line": 38917, "text": "##### 숫자" }, { "line": 38918, "text": "" }, { "line": 38919, "text": "| 항목 | 수 |" }, { "line": 38920, "text": "|---|---:|" }, { "line": 38921, "text": "| production Java 파일 | 6 |" }, { "line": 38922, "text": "| production LOC | 507 |" }, { "line": 38923, "text": "| 패키지 | 1 (`dev.caskeleton.messaging.streambridge`) |" }, { "line": 38924, "text": "| test 파일 | 2 |" }, { "line": 38925, "text": "| test 메서드(실행 확인) | **20** |" }, { "line": 38926, "text": "| 선언된 의존 | project 3 + vendor 1 |" }, { "line": 38927, "text": "| **실제 import되는 의존** | **project 2** (§12.4) |" }, { "line": 38928, "text": "" }, { "line": 38929, "text": "여섯 타입:" }, { "line": 38930, "text": "" }, { "line": 38931, "text": "| 타입 | 종류 | 역할 | leaf 밖 참조 |" }, { "line": 38932, "text": "|---|---|---|---:|" }, { "line": 38933, "text": "| `MessagingBindingBridge` | interface | 논리 목적지 ↔ Stream 바인딩 | 0 |" }, { "line": 38934, "text": "| `SpringCloudStreamPublisherBridge` | class | 발행 측 + 위 인터페이스 구현 | 0 |" }, { "line": 38935, "text": "| `SpringCloudStreamConsumerBridge` | class | 수신 측 | 0 |" }, { "line": 38936, "text": "| `StreamBridgePolicyGuard` | class | 목적지가 브리지 대상인가 | 0 |" }, { "line": 38937, "text": "| `BindingProfileValidator` | class | 바인딩 구성이 일관적인가 | 0 |" }, { "line": 38938, "text": "| `BindingCapabilityReport` | record | 무엇을 보장하지 **않는가** | 0 |" }, { "line": 38939, "text": "" }, { "line": 38940, "text": "##### Coverage ledger" }, { "line": 38941, "text": "" }, { "line": 38942, "text": "| scope/file group | count | disposition | reason |" }, { "line": 38943, "text": "|---|---:|---|---|" }, { "line": 38944, "text": "| `src/main/java/**` (6) | 6 | `FULL_READ` | 전 파일 본문 확인 |" }, { "line": 38945, "text": "| `src/test/java/**` (2) | 2 | `FULL_READ` | 20개 테스트명·단언 확인 |" }, { "line": 38946, "text": "| `build.gradle` | 1 | `FULL_READ` | 9줄 |" }, { "line": 38947, "text": "| `gradle.lockfile` | 1 | `STRUCTURAL_ONLY` | 잠금 파일 |" }, { "line": 38948, "text": "| `build/**` | — | `EXCLUDED` | 빌드 산출물 |" }, { "line": 38949, "text": "" }, { "line": 38950, "text": "`UNCLASSIFIED` 0." }, { "line": 38951, "text": "" }, { "line": 38952, "text": "---" }, { "line": 38953, "text": "" }, { "line": 38954, "text": "#### 1. 모듈의 정체와 경계" }, { "line": 38955, "text": "" }, { "line": 38956, "text": "Spring Cloud Stream 바인딩을 이미 쓰는 서비스가 같은 논리 목적지에 닿게 하는 **상호운용 seam**이다." }, { "line": 38957, "text": "" }, { "line": 38958, "text": "```java" }, { "line": 38959, "text": "// MessagingBindingBridge.java:8-15" }, { "line": 38960, "text": " *
The bridge is an interoperability seam, not a second messaging API. Its whole reason to exist" }, { "line": 38961, "text": " * is that a service already has Stream bindings and needs to reach the same destinations without a" }, { "line": 38962, "text": " * rewrite." }, { "line": 38963, "text": " *" }, { "line": 38964, "text": " *
Binder semantics are never promoted to platform guarantees. Stream's binder has its own retry," }, { "line": 38965, "text": " * its own dead-letter, and its own acknowledgement mode, and they look enough like the platform's" }, { "line": 38966, "text": " * to be mistaken for them — so a destination that actually relies on the platform's versions is" }, { "line": 38967, "text": " * refused by {@link StreamBridgePolicyGuard} rather than served with the binder's." }, { "line": 38968, "text": "```" }, { "line": 38969, "text": "" }, { "line": 38970, "text": "**\"look enough like the platform's to be mistaken for them\"**이 이 leaf 전체의 위협 모델이다. 브리지는 기능을 추가하지 않고 **차이를 드러낸다.**" }, { "line": 38971, "text": "" }, { "line": 38972, "text": "세 층으로 그것을 한다." }, { "line": 38973, "text": "" }, { "line": 38974, "text": "| 층 | 무엇을 |" }, { "line": 38975, "text": "|---|---|" }, { "line": 38976, "text": "| `StreamBridgePolicyGuard` | 플랫폼 보장에 의존하는 목적지를 아예 거절 |" }, { "line": 38977, "text": "| `BindingProfileValidator` | 바인더 확장 속성이 프로파일 결정을 덮는 것을 거절 |" }, { "line": 38978, "text": "| `BindingCapabilityReport` | 남은 차이를 **문장으로** 기록 |" }, { "line": 38979, "text": "" }, { "line": 38980, "text": "세 번째가 특이하다 — 거절할 수 없는 차이를 문서화 가능한 값으로 만든다." }, { "line": 38981, "text": "" }, { "line": 38982, "text": "---" }, { "line": 38983, "text": "" }, { "line": 38984, "text": "#### 2. 의존성과 런타임 배선" }, { "line": 38985, "text": "" }, { "line": 38986, "text": "**선언된 것과 쓰이는 것이 다르다.**" }, { "line": 38987, "text": "" }, { "line": 38988, "text": "| 선언 | scope | 실제 import |" }, { "line": 38989, "text": "|---|---|---|" }, { "line": 38990, "text": "| `messaging-core-api` | api | **o** — `DestinationName`, `MessagingConfigurationException`, publish 6타입 |" }, { "line": 38991, "text": "| `messaging-policy` | api | **o** — `DestinationProfile`, `RetryMode` |" }, { "line": 38992, "text": "| `messaging-transport-spi` | api | **x** |" }, { "line": 38993, "text": "| `org.springframework:spring-context` | implementation | **x** |" }, { "line": 38994, "text": "" }, { "line": 38995, "text": "`grep -rn 'import dev.caskeleton.messaging.transport\\|import org.springframework'` → exit 1." }, { "line": 38996, "text": "" }, { "line": 38997, "text": "**Spring Cloud Stream 브리지가 Spring을 import하지 않는다.** 바인더 접촉면 전체가 두 함수형 인터페이스로 추상화돼 있다 — `SpringCloudStreamPublisherBridge.ChannelSend`와 `SpringCloudStreamConsumerBridge.BridgedHandler`. javadoc이 그 목적을 적는다 — \"isolated so the bridge is testable without a binder\"." }, { "line": 38998, "text": "" }, { "line": 38999, "text": "즉 **`spring-context` 의존은 실제 통합 코드가 있어야 필요했을 것**인데 그 코드가 없다. §12.4." }, { "line": 39000, "text": "" }, { "line": 39001, "text": "나가는 것: 없다. 어떤 leaf의 `allowed_dependencies`에도 이 leaf가 없고 starter 목록에도 없다." }, { "line": 39002, "text": "" }, { "line": 39003, "text": "런타임 배선: 없음. `runtime_memberships: []`. bean 없음." }, { "line": 39004, "text": "" }, { "line": 39005, "text": "**소비자 0 · membership `[]` · 조립 0의 삼중 정합** — `messaging-kafka-share-experimental`·`messaging-schema-avro`와 같은 상태다." }, { "line": 39006, "text": "" }, { "line": 39007, "text": "---" }, { "line": 39008, "text": "" } ], "numbered_context": "38909 | #### 0. SSOT identity / 커버리지와 숫자 지도\n38910 | \n38911 | - registered leaf id: `messaging-spring-cloud-stream-bridge`\n38912 | - canonical state `analysisFile`: §A19-MESSAGING-SPRING-CLOUD-STREAM-BRIDGE\n38913 | - source path: `src/messaging/messaging-spring-cloud-stream-bridge`\n38914 | - registry `allowed_dependencies`: `[\"messaging-core-api\", \"messaging-policy\", \"messaging-transport-spi\"]`\n38915 | - registry `runtime_memberships`: **`[]`** — build-only\n38916 | \n38917 | ##### 숫자\n38918 | \n38919 | | 항목 | 수 |\n38920 | |---|---:|\n38921 | | production Java 파일 | 6 |\n38922 | | production LOC | 507 |\n38923 | | 패키지 | 1 (`dev.caskeleton.messaging.streambridge`) |\n38924 | | test 파일 | 2 |\n38925 | | test 메서드(실행 확인) | **20** |\n38926 | | 선언된 의존 | project 3 + vendor 1 |\n38927 | | **실제 import되는 의존** | **project 2** (§12.4) |\n38928 | \n38929 | 여섯 타입:\n38930 | \n38931 | | 타입 | 종류 | 역할 | leaf 밖 참조 |\n38932 | |---|---|---|---:|\n38933 | | `MessagingBindingBridge` | interface | 논리 목적지 ↔ Stream 바인딩 | 0 |\n38934 | | `SpringCloudStreamPublisherBridge` | class | 발행 측 + 위 인터페이스 구현 | 0 |\n38935 | | `SpringCloudStreamConsumerBridge` | class | 수신 측 | 0 |\n38936 | | `StreamBridgePolicyGuard` | class | 목적지가 브리지 대상인가 | 0 |\n38937 | | `BindingProfileValidator` | class | 바인딩 구성이 일관적인가 | 0 |\n38938 | | `BindingCapabilityReport` | record | 무엇을 보장하지 **않는가** | 0 |\n38939 | \n38940 | ##### Coverage ledger\n38941 | \n38942 | | scope/file group | count | disposition | reason |\n38943 | |---|---:|---|---|\n38944 | | `src/main/java/**` (6) | 6 | `FULL_READ` | 전 파일 본문 확인 |\n38945 | | `src/test/java/**` (2) | 2 | `FULL_READ` | 20개 테스트명·단언 확인 |\n38946 | | `build.gradle` | 1 | `FULL_READ` | 9줄 |\n38947 | | `gradle.lockfile` | 1 | `STRUCTURAL_ONLY` | 잠금 파일 |\n38948 | | `build/**` | — | `EXCLUDED` | 빌드 산출물 |\n38949 | \n38950 | `UNCLASSIFIED` 0.\n38951 | \n38952 | ---\n38953 | \n38954 | #### 1. 모듈의 정체와 경계\n38955 | \n38956 | Spring Cloud Stream 바인딩을 이미 쓰는 서비스가 같은 논리 목적지에 닿게 하는 **상호운용 seam**이다.\n38957 | \n38958 | ```java\n38959 | // MessagingBindingBridge.java:8-15\n38960 | *
The bridge is an interoperability seam, not a second messaging API. Its whole reason to exist\n38961 | * is that a service already has Stream bindings and needs to reach the same destinations without a\n38962 | * rewrite.\n38963 | *\n38964 | *
Binder semantics are never promoted to platform guarantees. Stream's binder has its own retry,\n38965 | * its own dead-letter, and its own acknowledgement mode, and they look enough like the platform's\n38966 | * to be mistaken for them — so a destination that actually relies on the platform's versions is\n38967 | * refused by {@link StreamBridgePolicyGuard} rather than served with the binder's.\n38968 | ```\n38969 | \n38970 | **\"look enough like the platform's to be mistaken for them\"**이 이 leaf 전체의 위협 모델이다. 브리지는 기능을 추가하지 않고 **차이를 드러낸다.**\n38971 | \n38972 | 세 층으로 그것을 한다.\n38973 | \n38974 | | 층 | 무엇을 |\n38975 | |---|---|\n38976 | | `StreamBridgePolicyGuard` | 플랫폼 보장에 의존하는 목적지를 아예 거절 |\n38977 | | `BindingProfileValidator` | 바인더 확장 속성이 프로파일 결정을 덮는 것을 거절 |\n38978 | | `BindingCapabilityReport` | 남은 차이를 **문장으로** 기록 |\n38979 | \n38980 | 세 번째가 특이하다 — 거절할 수 없는 차이를 문서화 가능한 값으로 만든다.\n38981 | \n38982 | ---\n38983 | \n38984 | #### 2. 의존성과 런타임 배선\n38985 | \n38986 | **선언된 것과 쓰이는 것이 다르다.**\n38987 | \n38988 | | 선언 | scope | 실제 import |\n38989 | |---|---|---|\n38990 | | `messaging-core-api` | api | **o** — `DestinationName`, `MessagingConfigurationException`, publish 6타입 |\n38991 | | `messaging-policy` | api | **o** — `DestinationProfile`, `RetryMode` |\n38992 | | `messaging-transport-spi` | api | **x** |\n38993 | | `org.springframework:spring-context` | implementation | **x** |\n38994 | \n38995 | `grep -rn 'import dev.caskeleton.messaging.transport\\|import org.springframework'` → exit 1.\n38996 | \n38997 | **Spring Cloud Stream 브리지가 Spring을 import하지 않는다.** 바인더 접촉면 전체가 두 함수형 인터페이스로 추상화돼 있다 — `SpringCloudStreamPublisherBridge.ChannelSend`와 `SpringCloudStreamConsumerBridge.BridgedHandler`. javadoc이 그 목적을 적는다 — \"isolated so the bridge is testable without a binder\".\n38998 | \n38999 | 즉 **`spring-context` 의존은 실제 통합 코드가 있어야 필요했을 것**인데 그 코드가 없다. §12.4.\n39000 | \n39001 | 나가는 것: 없다. 어떤 leaf의 `allowed_dependencies`에도 이 leaf가 없고 starter 목록에도 없다.\n39002 | \n39003 | 런타임 배선: 없음. `runtime_memberships: []`. bean 없음.\n39004 | \n39005 | **소비자 0 · membership `[]` · 조립 0의 삼중 정합** — `messaging-kafka-share-experimental`·`messaging-schema-avro`와 같은 상태다.\n39006 | \n39007 | ---\n39008 | ",
"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