Command: python3 - <<'PY' import json from pathlib import Path root=Path('src/adapter/outbound/support') for p in sorted(x for x in root.rglob('*') if x.is_file() and 'build/' not in str(x)): print(p) print('--- counts ---') for label,base,pattern in [('prod-java',root/'src/main/java','*.java'),('test-java',root/'src/test/java','*.java')]: files=list(base.rglob(pattern)) if base.exists() else [] print(label,'files=',len(files),'loc=',sum(len(p.read_text(errors='ignore').splitlines()) for p in files)) r=json.load(open('src/config/architecture/modules.json'))['modules'] print('--- registry ---') print(json.dumps(next(m for m in r if m['id']=='adapter-outbound-support'),indent=2)) PY printf '%s\n' '--- build.gradle ---'; cat src/adapter/outbound/support/build.gradle printf '%s\n' '--- README.md ---'; cat src/adapter/outbound/support/README.md printf '%s\n' '--- CLAUDE.md ---'; cat src/adapter/outbound/support/CLAUDE.md Working directory: /shared/codebase/clean-architecture-backend-template Executed at: 2026-08-29T07:19:26Z Source revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5 Observation boundary: Enumerates the support leaf source/build/document files and registry entry at the recorded revision; downstream consumers are not measured here. --- stdout/stderr --- src/adapter/outbound/support/CLAUDE.md src/adapter/outbound/support/README.md src/adapter/outbound/support/build.gradle src/adapter/outbound/support/gradle.lockfile src/adapter/outbound/support/src/main/java/dev/caskeleton/adapter/outbound/package-info.java src/adapter/outbound/support/src/main/java/dev/caskeleton/adapter/outbound/support/FailOpenDependencyLogger.java src/adapter/outbound/support/src/main/java/dev/caskeleton/adapter/outbound/support/OutboundCorrelation.java src/adapter/outbound/support/src/main/java/dev/caskeleton/adapter/outbound/support/OutboundSupportConfig.java src/adapter/outbound/support/src/test/java/dev/caskeleton/adapter/outbound/support/FailOpenDependencyLoggerTest.java --- counts --- prod-java files= 4 loc= 97 test-java files= 1 loc= 89 --- registry --- { "id": "adapter-outbound-support", "gradle_path": ":adapter:outbound:support", "source_path": "src/adapter/outbound/support", "allowed_dependencies": [ "domain-core", "application-core", "shared-contract" ], "runtime_memberships": [ "app-bootstrap" ] } --- build.gradle --- // Shared base for outbound integration adapters: correlation, fail-open dependency // logging, and the @Configuration seam. Depended on by messaging/cache/notification/httpclient. dependencies { implementation 'org.springframework.boot:spring-boot-autoconfigure' implementation 'org.slf4j:slf4j-api' } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } --- README.md --- # adapter:outbound:support — 설계 결정 참조 아웃바운드 기술 어댑터(`:adapter:outbound:messaging`, `:adapter:outbound:cache-redis`, `:adapter:outbound:notification`, `:adapter:outbound:httpclient`)가 공유하는 베이스 모듈. 패키지 루트: `dev.caskeleton.adapter.outbound.support`. 허용/금지 의존 정책은 `src/build.gradle` 의 `allowedProjectDependencies['adapter:outbound:support']` 항목이 SSOT 다(이 모듈은 아직 별도 CLAUDE.md 를 두지 않았다). 이 문서는 코드 주석에서 덜어낸 **설계 결정의 근거**를 모아둔 참조용 기록이다 — 코드를 읽다 "왜 이렇게 했나"가 궁금할 때 본다. ## 왜 별도 모듈인가 옛 `adapter-outbound` 모듈을 기술별(`messaging`/`cache-redis`/`notification`/`httpclient`)로 쪼개면서, 네 모듈이 공통으로 필요로 하는 조각 — correlation 조회, fail-open 의존성 로깅, 공유 `@Configuration` 등록 지점 — 을 이 모듈로 뽑아냈다. 네 기술 모듈 모두 `implementation project(':adapter:outbound:support')` 로 이 모듈에 의존한다. ## FailOpenDependencyLogger — WARN, ERROR 아님 실패를 ERROR 가 아니라 WARN 으로 로깅한다: cache/messaging/notification 선택형 어댑터는 fail-open 이라 의존성 실패가 나도 use case 는 성공한 것이다 — 관측은 하되 escalate 하지 않는다. 이는 호출자에게 직접 노출되는 hard failure 를 ERROR 로 올리는 `httpclient` 모듈의 `OutboundHttpDependencyLogger` 와 명확히 구분된다. 메서드 시그니처가 본문·수신자·페이로드를 받지 않아 PII 가 로그에 닿지 않는다. ## OutboundCorrelation — `"unknown"` 센티넬 MDC 에 correlation id 가 없으면 `null` 이 아니라 `"unknown"` 문자열을 반환해 로그 라인에 필드가 비지 않게 한다. MDC 키는 SSOT(`mdc-keys.yaml`)를 따른다. ## OutboundSupportConfig — 공유 `@Configuration` seam `FailOpenDependencyLogger` 빈을 `@ConditionalOnMissingBean` 으로 무조건 등록하는 단일 `@Configuration` 이다. 네 기술 모듈이 각자 로거 빈을 중복 등록하지 않고 이 모듈에만 의존하면 와이어링되며, 포크는 같은 타입의 빈을 직접 등록해 오버라이드할 수 있다. 이 모듈 자체는 어떤 `@ConditionalOnProperty` 게이팅도 갖지 않는다 — 게이팅은 각 기술 모듈이 자신의 연동 client 단위로 소유한다. --- CLAUDE.md --- # adapter:outbound:support — shared outbound support ## Registered identity - Module ID: `adapter-outbound-support` - Gradle path: `:adapter:outbound:support` - Focused test (derived from Gradle path): `./gradlew :adapter:outbound:support:test --console=plain` - Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0. - Registry SSOT: `src/config/architecture/modules.json`. Package roots: `dev.caskeleton.adapter.outbound` and `dev.caskeleton.adapter.outbound.support`. ## Responsibility - Own reusable outbound correlation, fail-open dependency logging, and auto-configuration seams. - Provide technical support used by registered messaging/cache/notification/httpclient leaves. - Keep feature-specific clients and routing policies in their owning leaf. ## Boundaries - Allowed project dependencies are registry-owned: application, domain, and shared contracts. - No inbound transport, persistence, bootstrap, or sample dependencies. - No business rules or external-system-specific protocol implementation. ## Tests Use focused unit tests for support behavior. Do not start a Spring application context unless the contract being tested is configuration wiring itself. Exit code: 0