38 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-runtime-context-propagation-contract | branch-note | raw | feature-runtime-context-propagation-contract |
|
|
|
2026-06-09 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-056 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-056 |
|
|
1 | c4b5f766a11a0331d04ca0649fd795aa293d04ef0f05fb0e90b569a921481053 |
branch: feature-runtime-context-propagation-contract
Layer:
raw/branch-notes/— 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는/ingest로wiki/projects/에 추출. 원본은 raw에 영구 보관.status_label:in-progress|review|merged|abandoned계층 표기: project 의 직접 자식 branch 는parent_branch:를 비워두고related_projects만 채움.
부모 (필수)
이 branch 는 ca-skeleton 운영 계약 project 의 직접 자식 branch (project 분해표 §8.0 E영역 row 7).
parent_branch:비어있음.
- Project 의 직접 자식 branch: raw/project-notes/ca-skeleton-operational-contract (§8.0 E영역 priority 7:
feature-runtime-context-propagation-contract— "virtual thread 활성화 + 도메인 context 전파 요구 시점 / Java 21 Scoped Values — boundary B6 의 도메인 확장")
이 branch 가 확장 하는 형제 branch (B6 baseline 의 도메인 확장이므로 강결합):
- raw/branch-notes/feature-boundary-validation-mapping-contract — B6 (virtual-thread MDC propagation) baseline 의 owner (D13). 본 branch 는 그 도메인 확장.
본 branch 가 결정을 위임 하는 형제 branch (Out of scope, §범위 참조):
- raw/branch-notes/feature-operational-error-observability-foundation — MDC key 카탈로그 + ID 의미 SSOT (D6/D8/D11/D19)
- raw/branch-notes/feature-distributed-tracing-contract — W3C trace context 전파 (D5/D7/D8)
- raw/branch-notes/feature-background-job-async-contract —
@Asyncexecutor TaskDecorator MDC-copy (D5/D6) - raw/branch-notes/feature-tenant-context-policy —
tenant_idlifecycle/policy
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: runtime context capture·propagation·cleanup과 architecture/contract test가 명시된다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1 |
Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-LANGUAGE-001@1 |
application language는 Java 21 LTS다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1 |
framework는 Spring Boot 3.5.14다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1 |
test framework는 JUnit 5다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-ARCHTEST-001@1 |
architecture test는 archunit-junit5 1.3.0을 사용한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
ca-skeleton 의 진단(diagnostic) context 전파 — request_id / trace_id / correlation_id 를 inbound filter 에서 MDC 에 심고 virtual thread 위에서 application layer 까지 전달 — 은 이미 B6 (boundary-validation D13) 에서 구현 완료(actually-implemented: VirtualThreadMdcPropagationTest, VirtualThreadMdcE2ETest, no_inheritable_thread_local ArchUnit rule).
본 branch 는 그 도메인 확장이다: 진단용 6개 MDC 키를 넘어서는 도메인/비즈니스 context(예: 도메인 식별자)를 virtual thread + structured concurrency(StructuredTaskScope.fork()) 경계에서 전파하는 기본 구현 + 스왑 가능 추상화를 제공한다.
2026-06-09 설계 전환 (baseline=nothing → 기본 구현 + 스왑): 초안은 "트리거 전까지 아무것도 선박 안 함(baseline=nothing)"이었으나, 이 skeleton 자신의 rate-limit 선례(
RateLimiter포트 +FixedWindowRateLimiter기본 +RateLimitAlgorithm/Factory스왑)에 비춰 과소(under-ambitious)로 판정. rate-limit 의 교훈 = 메커니즘과 값을 분리 — 포트는 값(도메인 key)을 몰라도 추상화 가능. 따라서 메커니즘(경계 넘어 capture/restore)을 기본 구현(plain ThreadLocal)으로 선박하고, *값(key)*만 도메인이 등록하도록 전환. 불가능한 부분(ScopedValue기본 — preview,--enable-preview부재)과 도메인 고유 부분(어떤 key)만 deferred.
- 기본 제공:
DomainContextPropagator포트 +ThreadLocalDomainContextPropagator기본 구현 +DomainContextStrategyenum +DomainContextPropagatorFactory+DomainContextProperties(ca-skeleton.domain-context.strategy, 기본THREAD_LOCAL). rate-limit 구조 1:1 미러. - 스왑 가능:
MICROMETER(stable, 다중 key/Reactor)·SCOPED_VALUE(preview,--enable-preview시) 는 enum 주석 + factory 확장점으로 예약. - 트리거(값 활성화): 도메인 코드가 비즈니스 식별자를 async/fork 경계 너머로 요구하는 시점 (project note L2080) — 그때
DomainContextKey상수를 도메인이 선언. seam 은 그 전까지 동작하지만 전파할 값이 없음(라우트 없는RateLimitKeyResolver와 동일). - 이슈:
- PR:
범위
포함 범위
- (S1) 통합 cross-boundary 전파 메커니즘 메타-계약 — virtual thread 활성화(
spring.threads.virtual.enabled=true) 시 어느 경계에서 어느 메커니즘이 적용되는지 의 단일 위임 맵. 특히 background-job 의ThreadPoolTaskExecutor+TaskDecorator모델(pool)과 B6 의SimpleAsyncTaskExecutor(virtual) 전환의 정합 — 현재 어느 형제도 소유하지 않는 seam. - (S2) 도메인 context 전파 메커니즘 추상화 + 기본 구현 —
DomainContextPropagator포트 +ThreadLocalDomainContextPropagator기본(default) +DomainContextStrategyenum + factory 스왑. ✅ 구현됨(2026-06-09).ScopedValue/Micrometer 는 예약 strategy. - (S3) fork 경계 명시적 capture/rebind 규칙 — 도메인 context 는 thread/
StructuredTaskScopefork 마다 명시적으로 재확립해야 한다(묵시적 상속 금지).InheritableThreadLocal금지의 도메인-context 판본. ✅ 구현됨:wrap(Runnable/Callable)+capture()/restore()API + no-silent-inheritance 테스트. - (S4) ScopedValue 전용 신규 ArchUnit enforcement(활성화 시) — 기존
no_inheritable_thread_local(B6 소유)을 cross-cite 하되, Scoped-Value 오용 차단 룰만 신규 도입. deferred(rule shape 미정, UNSUPPORTED). 단domain_context_propagation_primitives_stay_unshipped가드(preview API 차단)는 선박됨.
제외 범위
의도적으로 제외 — 각각 형제 branch 가 SSOT. 본 branch 의 §구현 가이드에 재결정 하지 않고 cross-cite 만 한다(CLAUDE.md §15.5 R3).
- ID 의미 + MDC snake_case 키 카탈로그 + snake↔camel↔kebab 투영 → raw/branch-notes/feature-operational-error-observability-foundation D6/D8/D11/D19.
- W3C
traceparent/tracestate전파, baggage allowlist, B3-forbidden, sampling/exporter → raw/branch-notes/feature-distributed-tracing-contract D5/D7/D8. @Async/executorTaskDecoratorMDC-copy(4키) + pool sizing + graceful shutdown → raw/branch-notes/feature-background-job-async-contract D5/D6/D7/D8.- B6 baseline(virtual-thread filter/MDC 안전성 probe) +
no_inheritable_thread_localArchUnit rule → raw/branch-notes/feature-boundary-validation-mapping-contract D13(CleanArchitectureTest.java:623+InheritableThreadLocalFixture). tenant_idlifecycle/policy →feature-tenant-context-policy. 본 branch 는tenant_id를 consumer/예시 로만 다룸.
근거 (필수, 최소 1개+)
도메인 context 메커니즘 결정(S2)의 근거가 되는 외부 자료.
/branch-spec자동조사(wiki-decision-researcher)가 N=3 alternatives × 공식문서+기술블로그로 생성.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/scoped-value-jep-446-506-openjdk | D2(ScopedValue) 공식 명세 — immutability / bounded lifetime / StructuredTaskScope inheritance |
| raw/company-tech-blogs/scoped-value-structured-concurrency-softwaremill | D2(ScopedValue) production 패턴 사례 (SoftwareMill, 2025-09) |
| raw/official-docs/micrometer-context-propagation-official | D3(Micrometer ContextSnapshot) 공식 API — capture/restore + ThreadLocalAccessor 등록 |
| raw/company-tech-blogs/micrometer-context-propagation-line-be-hase | D3(Micrometer) production 사례 (LINE / Ryosuke Hasebe, 2025-02) |
| raw/official-docs/threadlocal-virtual-threads-java21-oracle | D4(plain ThreadLocal) virtual thread 안전성 — per-virtual-thread 독립 copy |
| raw/company-tech-blogs/threadlocal-capture-restore-att-israel | D4(plain ThreadLocal) TaskDecorator capture-restore 패턴 사례 (AT&T Israel, 2022-04) |
형제 branch 결정(foundation D11, distributed-tracing D5/D7, background-job D5/D6, boundary-validation D13)은 외부 Source 가 아니라 cross-contract 의존 이므로 §엣지·실패·의존 + Decision Evidence Map 의 Supporting Claims 에 기재.
TODO
각 항목 옆에 증거 등급 표기.
- (S2) 도메인 context 메커니즘 추상화 + 기본 구현 — 등급:
locally-verified(shared-contractDomainContextPropagator/ThreadLocalDomainContextPropagator/DomainContextStrategy/DomainContextPropagatorFactory/DomainContextSnapshot+app-bootstrapDomainContextProperties/DomainContextConfig.:shared-contract:test --tests '*DomainContext*'11/11 green) - (S3) fork 경계 명시적 capture/rebind — 등급:
locally-verified(wrap()/capture()/restore(); virtual-thread 전파 + no-silent-inheritance + finally-revert 테스트 통과) - (S1) 통합 boundary→mechanism 위임 맵 (cross-cite siblings, reference-only) — 등급:
documented-only(package-info.java§S1) - (S4) ScopedValue 전용 ArchUnit rule 신규 작성 (활성화 시) — 등급:
planned(UNSUPPORTED_IMPL_DECISION — rule shape 미정, 지어내지 않음) - (신규) preview-primitive 가드 —
SCOPED_VALUE전략 미선박 강제 — 등급:locally-verified(domain_context_propagation_primitives_stay_unshippedArchUnit rule,CleanArchitectureTest47/47 green; production 이ScopedValue/StructuredTaskScope참조 시 fail) - 트리거 시점 결정: 도메인 context key 집합 명세 (tenantId? userId? …) — 등급:
needs-confirmation(도메인이DomainContextKey상수 선언 시) - B6 baseline(virtual-thread MDC propagation) 사전확인 — 등급:
actually-implemented(boundary-validation D13 소유, 본 branch 범위 밖)
진행 중 메모
- 2026-06-09 설계 전환 + 구현 (선택지 B → 기본구현+스왑, SUPERSEDES 아래 baseline=nothing 메모): rate-limit 선례 대조에서 baseline=nothing 이 과소로 판정 → 기본 구현 + 스왑 추상화로 승급 구현. 선박물:
shared-contract/.../concurrency/에DomainContextKey·DomainContextPropagator·DomainContextSnapshot·DomainContextStrategy·DomainContextPropagatorFactory·ThreadLocalDomainContextPropagator(기본) +app-bootstrap/.../concurrency/에DomainContextProperties·DomainContextConfig. 테스트::shared-contract:test --tests '*DomainContext*'11/11 green(set/get/clear, snapshot 불변, restore revert, virtual-threadwrap()전파, no-silent-inheritance, finally-revert),:app-bootstrap:test --tests '*CleanArchitectureTest'47/47 green(shared-contract 순수성 + preview-primitive 가드 유지).MICROMETER/SCOPED_VALUE는 예약 strategy(enum 주석+factory 확장점).package-info는 baseline=nothing 서술에서 default+swap 서술로 재작성. - 2026-06-09 구현(선택지 B 채택, 위 메모로 대체됨): 사용자 지시("문서대로 구현, 하나도 빠짐없이")를 future-activation contract 의
baseline=nothing(D1)과 양립시키기 위해 문서 계약 artifact + baseline 가드 테스트만 선박. (1)src/shared-contract/src/main/java/dev/caskeleton/shared/concurrency/package-info.java— S1 위임 맵 + S2 선택 결정표 + S3 fork rebind 규칙 + S4 deferred 표기를 in-repo Javadoc 으로 인코딩(어노테이션 없는 package-info →.class미생성, source-only 의도와 일치). (2)CleanArchitectureTest에domain_context_propagation_primitives_stay_unshipped신규 ArchUnit rule — D1 강제(production 이ScopedValue/StructuredTaskScope참조 금지). D2/D3/D4 메커니즘·S4 활성화 룰은 여전히 미선박(planned/UNSUPPORTED). 검증::shared-contract:clean compileJava,:app-bootstrap:test --tests '*CleanArchitectureTest',verifyCleanArchitectureDependencies모두 green. - 2026-06-09 검증 함정 기록: 첫 arch 테스트 실행이
:shared-contract:compileJavaUP-TO-DATE 로 새 package-info 가 classpath 에 없어 stale green 이었음.:shared-contract:clean후 재실행으로 true green 확보. (preview API 라 위반 fixture 컴파일 불가 → fixture 대신 dormant defence-in-depth rule + 컴파일 게이트 이중방어로 문서화.) - 2026-06-09:
ScopedValue는 ca-tmpl src 에 0건 — 도메인 context 전파는 전적으로 greenfield/미선박. B6(진단 MDC)만 구현됨. - 2026-06-09 빌드 사실(C1 해소): ca-tmpl 빌드(
src/build.gradle, Java 21 toolchainJavaLanguageVersion.of(21))에--enable-preview없음.StructuredTaskScope도 0건. → 현재 D2(ScopedValue) 는 빌드 정책 변경 전까지 unavailable; 트리거 도착 시 기본 후보는 D3/D4. no_inheritable_thread_localrule 은CleanArchitectureTest.java:623에 존재하고 본문에서 명시적으로 "feature-boundary-validation-mapping-contract B6" 를 cite — 본 branch 는 재소유 금지, cross-cite.- background-job D5(
TaskDecorator)는 src/main 에 미구현(planned). 즉 pool-vs-virtual 정합(S1 seam)은 아직 코드로 충돌하지 않은 미래 정합 대상.
결정 사항
각 결정 근거는 위 Sources 또는 형제 branch 결정을 가리킴. 2026-06-09 재구성: D1 이 baseline=nothing → 기본구현+스왑으로 전환. D2/D3/D4 는 트리거 시 택1 이 아니라 seam 뒤 strategy 옵션 — D4(ThreadLocal)가 선박된 기본, D2/D3 는 예약.
- 2026-06-09: (D1) 도메인 context 전파의 기본 구현 + 스왑 추상화를 선박(SUPERSEDES baseline=nothing). / 이유: rate-limit 선례(메커니즘과 값 분리) — 포트는 도메인 값을 몰라도 추상화 가능하므로 메커니즘은 기본 구현(ThreadLocal)으로 선박하고 *값(key)*만 도메인이 등록. / 근거: raw/project-notes/ca-skeleton-operational-contract L2080(트리거는 이제 값 활성화에만 적용) + rate-limit 구조 선례(
RateLimiter/RateLimitAlgorithm/Factory). - 2026-06-09: (D4) plain ThreadLocal capture-restore 를 기본(default) strategy 로 선박 —
THREAD_LOCAL. / 이유: virtual-thread 안전(per-thread copy) + zero dep +InheritableThreadLocal-free + 단순. / 근거: raw/official-docs/threadlocal-virtual-threads-java21-oracle, raw/company-tech-blogs/threadlocal-capture-restore-att-israel. - 2026-06-09: (D3) Micrometer Context Propagation 을 예약 strategy(
MICROMETER)로 — 스왑 조건: stable-API + 다중 key/Reactor 확장. (enum 주석 + factory 확장점, 미선박. C7: virtual-thread 보장 확인 후 활성화.) / 근거: raw/official-docs/micrometer-context-propagation-official, raw/company-tech-blogs/micrometer-context-propagation-line-be-hase. - 2026-06-09: (D2) ScopedValue 를 예약 strategy(
SCOPED_VALUE)로 — 스왑 조건:--enable-preview수용(현재 부재, C1) + StructuredTaskScope 중심 + immutability. (preview API,domain_context_propagation_primitives_stay_unshipped가드로 production 진입 차단.) / 근거: raw/official-docs/scoped-value-jep-446-506-openjdk, raw/company-tech-blogs/scoped-value-structured-concurrency-softwaremill. - 2026-06-09: (D5) 통합 boundary→mechanism 위임 맵 — 각 경계의 전파는 형제 branch 가 소유; 본 branch 는 consolidation view 만 제공(reference-only). / 근거: foundation D11, distributed-tracing D5/D7, background-job D5/D6, boundary-validation D13.
- 2026-06-09: (D6) fork 경계 명시적 capture/rebind 규칙 — 도메인 context 는 thread/
StructuredTaskScopefork 마다 명시적 재확립; 묵시적 상속 금지(InheritableThreadLocalban 의 도메인 판본). / 근거: boundary-validation D13(no-silent-inheritance) +scoped-value-jep-446-506-openjdk#SV-C2(StructuredTaskScope 내 자동 상속은 scope 안 에 한정). - 2026-06-09: (D7) ScopedValue 전용 신규 ArchUnit enforcement — 활성화 시. 구체적 rule shape 는 근거 없음(
UNSUPPORTED_DECISION). / 기존no_inheritable_thread_local(B6) cross-cite.
결정-근거 매핑
Supporting Claims: 외부 raw 는
raw/<slug>.md#<ClaimID>, cross-contract 의존은 형제 branch 의D<n>.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 도메인 context 기본 구현 + 스왑 추상화 선박 (포트+default+factory) — ✅ 구현됨 | 항상(기본 제공). 트리거는 값(도메인 key) 활성화에만 적용 — 도메인이 DomainContextKey 선언 시. |
raw/project-notes/ca-skeleton-operational-contract L2080(값 트리거) + rate-limit 구조 선례(RateLimiter/Factory) |
governing + repo-precedent |
seam 은 동작하나 도메인 key 0개면 전파 값 없음(라우트 없는 rate-limit 와 동일, 정상) |
| D4 | plain ThreadLocal capture-restore = 선박된 기본 strategy(THREAD_LOCAL) — ✅ 구현됨 |
기본값. 다중 key/Reactor 면 D3, preview 수용 시 D2 로 스왑. | raw/official-docs/threadlocal-virtual-threads-java21-oracle.md#TL-VT-C1, raw/company-tech-blogs/threadlocal-capture-restore-att-israel.md#ATT-TL-C1 |
official-vendor-doc + company-case-study + locally-verified(11 tests) |
finally-clear 는 wrap()/restore() 가 try-with-resources 로 강제(규율 위험 해소). key 증가 시 D3 권고 |
| D3 | Micrometer Context Propagation = 예약 strategy(MICROMETER) — 미선박(enum 주석+factory 확장점) |
스왑: stable-API + 다중 key/Reactor 확장. | raw/official-docs/micrometer-context-propagation-official.md#MCP-C3, #MCP-C4, raw/company-tech-blogs/micrometer-context-propagation-line-be-hase.md#LN-MCP-C1 |
official-vendor-doc + company-case-study |
공식 문서가 virtual thread 명시 보장 없음(C7 — 활성화 전 확인); 추가 의존성 |
| D2 | ScopedValue = 예약 strategy(SCOPED_VALUE) — 미선박(preview 차단) |
스왑: --enable-preview 수용(현재 부재 C1) + StructuredTaskScope 중심 + immutability. |
raw/official-docs/scoped-value-jep-446-506-openjdk.md#SV-C1, #SV-C2, raw/company-tech-blogs/scoped-value-structured-concurrency-softwaremill.md#SM-SV-C1 |
official-standard + company-case-study |
Java 21 preview(--enable-preview 필요); domain_context_propagation_primitives_stay_unshipped 가드가 production 진입 차단 |
| D5 | 통합 boundary→mechanism 위임 맵 (consolidation only, reference-only) | 항상 — 본 branch 는 경계별 전파를 재결정 안 하고 위임 맵만 제공 | foundation D11, distributed-tracing D5/D7, background-job D5/D6, boundary-validation D13 |
cross-contract (sibling decisions) |
background-job TaskDecorator(pool) ↔ B6 SimpleAsyncTaskExecutor(virtual) 정합 seam 미소유 — S1 핵심 리스크 |
| D6 | fork 경계 명시적 capture/rebind 규칙 (묵시 상속 금지) | 항상 (도메인 context 활성화 시) | boundary-validation D13 (no-silent-inheritance), raw/official-docs/scoped-value-jep-446-506-openjdk.md#SV-C2 |
cross-contract + official-standard |
StructuredTaskScope 안 자동상속과 밖 수동재확립의 경계가 개발자에게 혼동 가능 |
| D7 | ScopedValue 전용 신규 ArchUnit rule (활성화 시) | 도메인 context 활성화 + ScopedValue(D2) 채택 시 | UNSUPPORTED_DECISION — 구체 rule shape 권고하는 raw 없음. 기존 no_inheritable_thread_local(boundary-validation D13) cross-cite |
none (unsupported) |
rule 부재 시 미래 개발자가 도메인 context 를 ThreadLocal 로 오용/누수 |
구현 가이드
본 branch 는 기본 구현 + 스왑 추상화(rate-limit 패턴)를 선박한다(2026-06-09). §2 의 포트/기본구현/factory 는
locally-verified(11 tests); 도메인 key·MICROMETER/SCOPED_VALUEstrategy·S4 활성화 룰만planned/예약.
1. Boundary → Mechanism 위임 맵 (D5 — REFERENCE ONLY)
Trace: D5. 각 행의 실제 호스팅 = sibling branch. 본 branch 는 consolidation view 만 — 코드 위치는 sibling.
- UNSUPPORTED_IMPL_DECISION: (1)
domain-context행의 메커니즘은 D2/D3/D4 트리거 선택에 종속 — 트리거 전까지 미정(trade-off: 조기 확정 시 YAGNI 위반). (2)pool↔virtual 전환 정합seam 행은 mechanism·owner 모두 미정 — 의도적 deferred: background-job D5(TaskDecorator) 구현 완료 + virtual executor 전환이 동시 성립할 때만 활성화(trade-off: 지금 정하면 미구현 D5 에 대한 근거 없는 가정).
| 경계 (boundary) | 전파 대상 | 메커니즘 | 소유 branch (actual location) | 본 branch 관계 |
|---|---|---|---|---|
| inbound HTTP filter | request_id/correlation_id/trace_id MDC |
SLF4J 2.0+ MDC (virtual-thread aware) | boundary-validation D13 (RequestLoggingFilter.java) |
cross-cite (Out of scope) |
| outbound HTTP / message | W3C traceparent/tracestate, baggage(tenant_id,request_id) |
Micrometer Tracing | distributed-tracing D5/D7/D8 | cross-cite (Out of scope) |
@Async ThreadPoolTaskExecutor (pool) |
MDC 4키(request_id/trace_id/correlation_id/tenant_id) |
TaskDecorator copy (planned, 미구현) |
background-job D5/D6 | cross-cite (Out of scope) |
virtual-thread carrier (SimpleAsyncTaskExecutor) |
진단 MDC | SLF4J 2.0+ MDC, InheritableThreadLocal 금지 |
boundary-validation D13 (no_inheritable_thread_local CleanArchitectureTest.java:623) |
cross-cite (Out of scope) |
StructuredTaskScope.fork() / thread handoff |
도메인 context | DomainContextPropagator.wrap()/capture() (기본 THREAD_LOCAL) ✅ 선박 |
본 branch (S2/S3) | in scope — 구현됨 |
pool↔virtual 전환 정합 (TaskDecorator semantics when executor is not a pool) |
— | — | 미소유 seam | 본 branch (S1) 신규 |
2. 도메인 context 추상화 + 기본 구현 (D1/D4 ✅ 구현
Trace: D1(기본구현+스왑) + D4(
THREAD_LOCAL기본). Supporting:TL-VT-C1,ATT-TL-C1+ rate-limit 선례. 예약 strategy 근거:SV-C1/SV-C2(D2),MCP-C3/MCP-C4(D3).선박된 코드 (
:shared-contract:test --tests '*DomainContext*'11/11 green):
요소 클래스 위치 포트 DomainContextPropagatorshared-contract/.../concurrency/기본 구현 ThreadLocalDomainContextPropagator(plain ThreadLocal)〃 strategy enum DomainContextStrategy(THREAD_LOCAL기본;MICROMETER/SCOPED_VALUE주석)〃 factory(확장점) DomainContextPropagatorFactory(switch)〃 키(도메인 확장점) DomainContextKey<T>〃 hand-off DomainContextSnapshot+wrap()/capture()/restore()〃 Spring 와이어링 DomainContextProperties(ca-skeleton.domain-context.strategy) +DomainContextConfigapp-bootstrap/.../concurrency/
- 여전히 planned/예약: 도메인이 선언할
DomainContextKey상수(C5),MICROMETERstrategy(C7 확인 후),SCOPED_VALUEstrategy(--enable-preview시 C1), S4 활성화 룰(UNSUPPORTED).
strategy 스왑 규칙 (DomainContextStrategy / factory):
IF (build 가 --enable-preview 수용) AND (StructuredTaskScope 중심) AND (context immutable)
THEN ScopedValue # D2 — fork 자동상속(scope 내) + immutability
ELIF (stable-API only) AND (Reactor 확장 가능성 OR 다중 domain key)
THEN Micrometer ContextSnapshot # D3 — ThreadLocalAccessor 등록 1회 + captureAll()
ELSE plain ThreadLocal + capture-restore wrapper # D4 — 1~2 key, 최소 추상화
# 2026-06-09 빌드 사실(C1): ca-tmpl 빌드에 --enable-preview 없음 + StructuredTaskScope 0건
# → 현재 IF(D2) 가지는 빌드 정책 변경 전까지 dead. 트리거 시 ELIF/ELSE 부터 평가.
# C5(domain key 수) 미정 시 폴백 순서: 기본 D4(plain TL, 1~2 key) → key 증가/Reactor 도입 시 D3.
3. fork 경계 명시적 capture/rebind 규칙 (D6 — planned)
Trace: D6. Supporting: boundary-validation D13(no-silent-inheritance,
actually-implemented) +scoped-value-jep-446-506-openjdk.md#SV-C2.
- 도메인 context 는 thread/
StructuredTaskScopefork 를 넘을 때 명시적으로 재확립한다. 묵시적 상속(InheritableThreadLocal)은 금지 — B6 가 이미no_inheritable_thread_local(CleanArchitectureTest.java:623)로 차단(cross-cite, 재작성 금지). - 단 ScopedValue(D2)는
StructuredTaskScopescope 안 fork 에서는 자동 상속(SV-C2) — 이 한 경우만 예외이며 scope 밖 fork 는 여전히 명시적 재확립 필요. - 실패 동작: capture/rebind 누락 시 도메인 context 유실 → 계약 위반(테스트로 감지, S4).
4. ScopedValue 전용 ArchUnit enforcement (D7 — UNSUPPORTED_IMPL_DECISION
Trace: D7. UNSUPPORTED_IMPL_DECISION: 구체적 rule shape(무엇을 noClasses/should 로 차단할지)를 권고하는 raw 없음. 활성화 시 신규 작성 대상이며, 그 전까지 기존
no_inheritable_thread_local(B6, boundary-validation D13)만 유효. trade-off: 지금 rule 을 지어내면 근거 없는 결정.
- REFERENCE ONLY:
no_inheritable_thread_local(actual location:app-bootstrapCleanArchitectureTest.java:623, owner=boundary-validation D13). - 신규(활성화 시 본 branch host): 도메인 context 를
ThreadLocal로 오용/누수 차단하는 rule — 형식 미정.
엣지·실패·의존
- 실패·엣지 경로:
--enable-preview가 CI/build 정책에서 거부됨 → D2(ScopedValue) 불가, D3/D4 로 강등.- ca-tmpl 이
StructuredTaskScope를 전혀 사용하지 않음(현재 grep 0건) → D2 의 fork 자동상속 이점 소멸, D3/D4 와 동등. - D4 의
finally-clear 누락 → 같은 virtual thread 내 후속 단계에서 stale 도메인 context 읽기. - ScopedValue ↔ OTel
ContextStorage(attach/detach) 비호환 → distributed-tracing 의 trace context 와 도메인 context 공존 시 충돌 가능. ⚠️ 근거 raw 미등록 — 자동조사 시 secondary 로만 언급된 별도 SoftwareMill OTel 아티클. 활성화(D2 채택) 전raw/company-tech-blogs/에 정식 등록 후 이 엣지를 설계 제약으로 승격할 것(미등록 상태로 설계 판단에 사용 금지).
- 다른 계약 의존:
- raw/branch-notes/feature-boundary-validation-mapping-contract 의
D13(B6 baseline +no_inheritable_thread_local) 에 의존 — 본 branch 는 그 위에 도메인 확장만 얹음. B6 가 바뀌면(예: MDC 위임 대상 변경) 본 branch S3 규칙 영향. - raw/branch-notes/feature-operational-error-observability-foundation 의
D11(MDC 키 카탈로그) 에 의존 — 도메인 context 는 이 6키와 별도 채널 임을 전제. - raw/branch-notes/feature-background-job-async-contract 의
D5/D6(TaskDecorator, pool) 와 seam — pool↔virtual 전환 정합(S1)이 본 branch 신규 결정 영역. - raw/branch-notes/feature-distributed-tracing-contract 의
D8(baggage allowlist=tenant_id,request_id) 에 의존 — 도메인 context 를 baggage 로 전파하려면 이 allowlist 와 충돌하지 않아야 함. - raw/branch-notes/feature-tenant-context-policy —
tenant_id는 그 branch 소유. 본 branch 는 consumer/예시로만.
- raw/branch-notes/feature-boundary-validation-mapping-contract 의
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
(C1) Java 21 LTS 에서 ScopedValue 는 --enable-preview 없이 컴파일 불가 |
preview API 여부가 빌드 정책을 좌우(D2 선결조건) | ca-tmpl build.gradle.kts compileJava options + JEP 446/506 직접 확인 |
needs-confirmation |
(C2) ca-tmpl 이 StructuredTaskScope 를 도메인 경로에서 사용/계획 |
D2 fork 자동상속 이점의 전제 | grep -r StructuredTaskScope src (현재 0건) + 도메인 온보딩 계획 확인 |
planned |
(C3) io.micrometer:context-propagation 이 Spring Boot 3.5.x starter 로 classpath 에 transitive 존재 |
D3 의 추가 의존성 여부 | ./gradlew dependencies 의존성 트리 grep |
needs-confirmation |
(C4) plain ThreadLocal + TaskDecorator 가 SimpleAsyncTaskExecutor(virtual) 에서 동작 |
AT&T 사례는 2022(Loom GA 이전) — virtual 미검증 | D4 채택 결정 전 사전 spike 의무: virtual thread executor 통합 테스트 작성 | planned |
| (C5) 트리거 시점의 도메인 context key 집합(tenantId? userId? …) | 미정 — key 수가 D3 vs D4 선택을 가름 | 도메인 온보딩 시 use case 별 필요 식별자 명세 | needs-confirmation |
(C6) pool(TaskDecorator)↔virtual(SimpleAsyncTaskExecutor) 전환 시 context-copy semantics 정합(S1 seam) |
어느 형제도 미소유; background-job D5 미구현 | background-job TaskDecorator 구현 후 virtual 전환 통합 테스트 | planned |
(C7) Micrometer ContextSnapshot captureAll()/setThreadLocals() 가 virtual thread 환경에서 안전 |
공식 문서가 virtual thread 명시 보장 없음(plain TL 간접 지지뿐) | Spring Boot 3.3+ 릴리즈 노트 / Micrometer CHANGELOG 의 virtual thread 호환성 명시 raw 등록, 또는 D3 채택 전 통합 테스트 | needs-confirmation |
관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
/coverage가 채우는 생성물(손유지 금지, 실행 시마다 재생성). governing 문서(raw/project-notes/ca-skeleton-operational-contract.md)가 요구하는 관심사 커버리지. 기준:rules/coverage-gate.md. 2026-06-09 coverage-auditor 판정: Covered (Blocking 0 / Should-fix 0 / Advisory 1).
| 관심사 (governing doc 출처) | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| 도메인 context 전파 메커니즘 선택 계약 (§8.0 E row 7) | covered-here | — | — | D1 + D2/D3/D4 조건부 + §구현 §2 선택표 |
| virtual thread + fork 경계 명시적 capture/rebind 규칙 (§8 async boundary, §15) | covered-here | — | — | D6 |
| 통합 boundary→mechanism 위임 맵 (§8 전 경계 전파) | covered-here | — | — | D5 (§구현 §1 표) |
| 기본 구현 + 스왑 추상화 (§8.0 E row 7, rate-limit 패턴) | covered-here | — | — | D1 (포트+default+factory, L2080 값 트리거) |
| pool↔virtual 전환 정합 seam (§15/§18 미소유 신규) | covered-here | — | — | D5 S1 + C6 |
| ScopedValue 전용 ArchUnit enforcement (§15) | covered-here | — | Advisory | D7 (UNSUPPORTED_DECISION 라벨) |
| MDC key 카탈로그 + snake↔camel↔kebab (§8/§21) | delegated | raw/branch-notes/feature-operational-error-observability-foundation (D6/D8/D11/D19) | OK | §범위 Out of scope + §엣지 의존 |
| W3C traceparent/baggage/sampling (§8 Distributed Tracing) | delegated | raw/branch-notes/feature-distributed-tracing-contract (D5/D7/D8) | OK | §범위 Out of scope + §엣지 의존 |
| @Async TaskDecorator MDC-copy + pool sizing (§15/§18) | delegated | raw/branch-notes/feature-background-job-async-contract (D5/D6/D7/D8) | OK | §범위 Out of scope + §구현 §1 표 |
| B6 baseline virtual-thread MDC + no_inheritable_thread_local rule (§15) | delegated | raw/branch-notes/feature-boundary-validation-mapping-contract (D13) | OK | §목표 + 코드 CleanArchitectureTest.java:623 |
| tenant_id lifecycle/policy (§19 Tenant Policy) | delegated | raw/branch-notes/feature-tenant-context-policy | OK | §범위 Out of scope + §엣지 의존 |
| ScopedValue↔OTel ContextStorage 비호환 (§8 공존) | covered-here | — | Advisory | §엣지 open risk (근거 raw 미등록 — 활성화 전 등록 의무) |
마주친 문제
- (없음)
묶음 (이 branch에서 파생된 자료)
- raw/company-tech-blogs/micrometer-context-propagation-line-be-hase
- raw/company-tech-blogs/scoped-value-structured-concurrency-softwaremill
- raw/company-tech-blogs/threadlocal-capture-restore-att-israel
- raw/official-docs/micrometer-context-propagation-official
- raw/official-docs/scoped-value-jep-446-506-openjdk
- raw/official-docs/threadlocal-virtual-threads-java21-oracle
Sub-branches (세부 작업)
- (없음)
오류 기록 (이 branch 작업 중 발생)
- (별도 raw/errors 파일 불필요 — 진행 중 메모에 인라인 기록) 2026-06-09 "stale green": Gradle
:shared-contract:compileJavaUP-TO-DATE 로 새 package-info 가 ArchUnit classpath 에 미반영되어 첫 실행이 가짜 green. 교훈 = 새 소스 추가 후 arch 테스트는 해당 모듈clean후 재실행. 재사용 가치 낮아 derived 파일 생성 안 함.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (후보) "virtual thread 에서 MDC/context 가 왜 안 깨지는가, InheritableThreadLocal 은 왜 금지했는가" — B6 + 본 branch 도메인 확장
강의 (이 작업을 위해 학습한 강의)
- (없음)
job-posting tie-ins (이 작업에서 파생된 글감)
- (후보) "Java 21 ScopedValue vs Micrometer Context Propagation vs ThreadLocal — virtual thread 시대의 context 전파 선택"
- derived blog: 생성 전
외부 근거 자료 (Sources — 자동조사 생성)
- raw/official-docs/scoped-value-jep-446-506-openjdk
- raw/company-tech-blogs/scoped-value-structured-concurrency-softwaremill
- raw/official-docs/micrometer-context-propagation-official
- raw/company-tech-blogs/micrometer-context-propagation-line-be-hase
- raw/official-docs/threadlocal-virtual-threads-java21-oracle
- raw/company-tech-blogs/threadlocal-capture-restore-att-israel
관련 일일 노트
- (없음)
완료 후 정리
머지/종료 시점에 채움.
/ingest가 이 섹션 기준으로 wiki/projects/에 추출.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- 머지 결과 / 배포 환경: 로컬 검증 완료(
:shared-contract:test --tests '*DomainContext*'11/11,:app-bootstrap:test --tests '*CleanArchitectureTest'47/47). prod 미배포. - wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: B6 baseline 은 boundary-validation 소유(본 branch 추출 대상 아님)locally-verified항목: (D1) 도메인 context 기본구현+스왑 추상화 —DomainContextPropagator포트 +ThreadLocalDomainContextPropagator기본 +DomainContextStrategy/Factory스왑 +wrap()/capture()/restore()(S3) + Spring 와이어링 +domain_context_propagation_primitives_stay_unshipped가드. rate-limit 패턴 미러.prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned): 도메인
DomainContextKey상수(C5, 도메인 몫),MICROMETER/SCOPED_VALUE예약 strategy(D3/D2 미선박), S4 활성화 룰(D7 UNSUPPORTED), D5(reference-only consolidation)