docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: adapter-inbound-web-c01
|
||||
title: 상호배타성이 프로퍼티가 아니라 타입에서 온다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:adapter-inbound-web-c01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: adapter-inbound-web-c01
|
||||
file: ../../../final/evidence/rendered/adapter-inbound-web-c01.svg
|
||||
- key: adapter-inbound-web-c01-diagram
|
||||
file: ../../../final/assets/diagrams/adapter-inbound-web-c01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/adapter-inbound-web-c01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/14-adapter-inbound-web.md#L116 이다.
|
||||
module: adapter-inbound-web
|
||||
---
|
||||
|
||||
# 상호배타성이 프로퍼티가 아니라 타입에서 온다
|
||||
|
||||
MVC와 WebFlux 자동설정 둘 다 `matchIfMissing = true`로 기본 켜짐이다. 두 아티팩트가 클래스패스에 함께 있어도 하나만 활성화되는 이유는 프로퍼티가 아니라 `@ConditionalOnWebApplication`의 타입 조건이다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
두 자동설정의 게이트는 이렇게 붙어 있다.
|
||||
|
||||
```text
|
||||
MVC: @ConditionalOnWebApplication(SERVLET) + @ConditionalOnProperty(backend.web.mvc.enabled, matchIfMissing = true)
|
||||
WebFlux: @ConditionalOnWebApplication(REACTIVE) + @ConditionalOnProperty(backend.web.webflux.enabled, matchIfMissing = true)
|
||||
```
|
||||
|
||||
둘 다 `matchIfMissing = true` — **기본 켜짐**이다. notification·messaging·cache-redis가 전부 `matchIfMissing = false`(옵트인)인 것과 반대인데, 이유가 다르다: 저쪽은 선택적 능력이고 이쪽은 웹 애플리케이션의 본체다.
|
||||
|
||||
## 활성화를 가르는 것
|
||||
|
||||
:::evidence key="adapter-inbound-web-c01-diagram" alt="웹 애플리케이션 타입에서 MVC 자동설정과 WebFlux 자동설정으로 각각 화살표가 나가고 화살표에 SERVLET 과 REACTIVE 가 붙은 구조" caption="타입이 고르는 자동설정" zoom="false"
|
||||
:::
|
||||
|
||||
상호배타성은 프로퍼티가 아니라 `@ConditionalOnWebApplication`의 타입 수준에서 온다 — "a reactive application cannot accidentally activate the servlet filters even if both artifacts are on the classpath."
|
||||
|
||||
## 등록되는 빈 수
|
||||
|
||||
두 자동설정이 등록하는 빈은 MVC 12개, WebFlux 11개다. `AutoConfiguration.imports`에는 이 둘만 있다.
|
||||
|
||||
## 분석 원문의 게이트 비교
|
||||
|
||||
:::evidence key="adapter-inbound-web-c01" alt="분석 문서 analysis/14-adapter-inbound-web.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/14-adapter-inbound-web.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
<!-- body:end -->
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: adapter-inbound-web-c17
|
||||
title: ndjson 스위치 하나가 두 능력을 함께 켠다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:adapter-inbound-web-c17
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: adapter-inbound-web-c17
|
||||
file: ../../../final/evidence/rendered/adapter-inbound-web-c17.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/adapter-inbound-web-c17.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/14-adapter-inbound-web.md#L1255 이다.
|
||||
module: adapter-inbound-web
|
||||
---
|
||||
|
||||
# ndjson 스위치 하나가 두 능력을 함께 켠다
|
||||
|
||||
`NDJSON`과 `JSON_SEQUENCE`는 enum에서 서로 다른 상수이고 각자 프로퍼티 이름을 갖는데, 실제 조건은 `ndjson` 하나뿐이라 둘이 함께 켜진다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
조건이 붙은 자리는 하나다.
|
||||
|
||||
```java
|
||||
// advanced/mvc/MvcStreamingExecutorConfiguration.java:14-15, 36-39
|
||||
/** Wires servlet-side record streaming: NDJSON and RFC 7464 JSON text sequences. */
|
||||
@ConditionalOnProperty(prefix = "backend.web.advanced.ndjson", name = "enabled", havingValue = "true")
|
||||
```
|
||||
|
||||
`NDJSON`과 `JSON_SEQUENCE`는 enum에서 서로 다른 상수이고 각자 프로퍼티 이름을 갖는데, 실제로는 `ndjson` 스위치 하나가 둘을 함께 켠다.
|
||||
|
||||
## javadoc이 금지한 형태
|
||||
|
||||
`WebAdvancedFeature`의 javadoc이 그 형태를 금지한다 — "A single switch would make those one decision."
|
||||
|
||||
## WebAdvancedFeature 참조 위치
|
||||
|
||||
:::evidence key="adapter-inbound-web-c17" alt="코드베이스에서 WebAdvancedFeature 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="WebAdvancedFeature 코드베이스 검색 — 5줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
<!-- body:end -->
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: adapter-outbound-objectstorage-c01
|
||||
title: 컴파일이 전부 끝난 뒤에야 생성이 시작된다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:adapter-outbound-objectstorage-c01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: adapter-outbound-objectstorage-c01
|
||||
file: ../../../final/evidence/rendered/adapter-outbound-objectstorage-c01.svg
|
||||
- key: adapter-outbound-objectstorage-c01-diagram
|
||||
file: ../../../final/assets/diagrams/adapter-outbound-objectstorage-c01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/adapter-outbound-objectstorage-c01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/09-adapter-outbound-objectstorage.md#L63 이다.
|
||||
module: adapter-outbound-objectstorage
|
||||
---
|
||||
|
||||
# 컴파일이 전부 끝난 뒤에야 생성이 시작된다
|
||||
|
||||
`ObjectStorageProviderContribution`이 `describe`와 `create`의 계약을 나누고, `ObjectStorageCapabilityAssembler.assemble`이 그 순서를 코드 구조로 지킨다. 컴파일러 자체가 fail-closed다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`ObjectStorageProviderContribution`이 두 메서드의 계약을 나눈다.
|
||||
|
||||
> `describe` **must not resolve credentials, create files, clients, threads, or schedulers**. `create` owns cleanup of every partial allocation before it throws; after a successful return the assembler owns the returned lifecycle exactly once.
|
||||
|
||||
## 컴파일과 생성의 순서
|
||||
|
||||
:::evidence key="adapter-outbound-objectstorage-c01-diagram" alt="설정 컴파일에서 provider 생성으로, 다시 조립된 능력으로 이어지는 왼쪽에서 오른쪽 흐름. 화살표에 검증된 바인딩과 수명주기 소유권이 붙어 있다" caption="컴파일과 생성의 순서" zoom="false"
|
||||
:::
|
||||
|
||||
`ObjectStorageCapabilityAssembler.assemble`이 그 순서를 지킨다 — `compiler.compile(settings)`가 **전부** 끝난 뒤(`:25`)에야 선택된 destination을 돌며 `contribution.create(provider)`를 부른다(`:48`). 그리고 도중에 실패하면 이미 만든 것을 **역순으로** 닫는다(`:53–56`). `AssembledCapability.close()`도 역순이고 `AtomicBoolean`으로 정확히 한 번만 실행된다. README의 "Settings compile fully before any selected provider creates a directory, client, thread, scheduler, or credential lookup"이 코드 구조로 성립한다.
|
||||
|
||||
## ObjectStorageProviderContribution 참조 위치
|
||||
|
||||
:::evidence key="adapter-outbound-objectstorage-c01" alt="코드베이스에서 ObjectStorageProviderContribution 를 검색한 출력 39줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="ObjectStorageProviderContribution 코드베이스 검색 — 39줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 컴파일러가 거부하는 것
|
||||
|
||||
컴파일러 자체가 fail-closed다. 비활성이면 빈 바인딩을 돌려주고, 활성인데 provider·destination·default destination 중 하나라도 비면 거부한다. provider마다 `describe`가 돌려준 서술자와 설정을 **대조**한다 — providerType 일치, version 일치, `maximumObjectBytes`가 서술자 상한 이하, `chunkBytes`가 서술자 상한 이하. chunk는 추가로 `1 ≤ chunk ≤ min(maxObject, 16 MiB)`이고 `Integer.MAX_VALUE`를 넘지 못한다.
|
||||
|
||||
destination은 route token 중복을 거부하고, 요구한 capability를 provider가 `SUPPORTED`로 신고하지 않으면 거부하며, `SCAN_CLEAN`을 요구하는데 scanner seam이 없으면 이름을 대며 거부한다.
|
||||
|
||||
## 식별자 검증의 범위
|
||||
|
||||
`canonicalId`는 64자 이내, `[a-z0-9][a-z0-9_-]*`, 소문자, 그리고 **0x20–0x7e 밖 문자를 전부 거부**한다.
|
||||
|
||||
<!-- body:end -->
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: adapter-outbound-objectstorage-c08
|
||||
title: 레지스트리가 덮는 것은 문서 주장이고 설정 경로가 아니다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:adapter-outbound-objectstorage-c08
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: adapter-outbound-objectstorage-c08
|
||||
file: ../../../final/evidence/rendered/adapter-outbound-objectstorage-c08.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/adapter-outbound-objectstorage-c08.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/09-adapter-outbound-objectstorage.md#L560 이다.
|
||||
module: adapter-outbound-objectstorage
|
||||
---
|
||||
|
||||
# 레지스트리가 덮는 것은 문서 주장이고 설정 경로가 아니다
|
||||
|
||||
§41에서 "R0 경계가 문서에만 있다"고 적었다. §47을 반영해 정확히 다시 말하면, R0 경계는 문서 주장에 대해서는 기계 검사되지만 런타임 설정 경로는 그 검사를 거치지 않는다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
§41에서 "R0 경계가 문서에만 있다"고 적었다. §47을 반영해 정확히 다시 말한다.
|
||||
|
||||
## 기계 검사의 대상이 무엇인가
|
||||
|
||||
R0 경계는 **문서 주장에 대해서는** 기계 검사된다(§47). 그러나 그 검사의 대상은 `docs/registries/object-storage-readiness.yaml`이고, `KNOWN_PROVIDERS`는 `filesystem-local-dev` 하나다.
|
||||
|
||||
## S3ProviderBinding 참조 위치
|
||||
|
||||
:::evidence key="adapter-outbound-objectstorage-c08" alt="코드베이스에서 S3ProviderBinding 를 검색한 출력 19줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="S3ProviderBinding 코드베이스 검색 — 19줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 레지스트리를 거치지 않는 경로
|
||||
|
||||
운영자가 `app.object-storage` 설정에 AWS provider용 qualification profile을 쓰면서 `DIRECT_UPLOAD` capability를 주장하는 경로는 이 레지스트리를 **거치지 않는다**. `S3ProviderBinding.compileProfiles`가 그 주장을 MinIO에 대해서만 거부하므로, AWS + DIRECT_* 조합은 여전히 compile을 통과하고 presigner를 할당한다(§41).
|
||||
|
||||
따라서 §41의 판정은 유지되고 오히려 선명해진다 — 이 저장소에는 "이 카드는 R0"를 강제하는 장치가 이미 있는데, 런타임 설정 경로가 그 장치의 사정권 밖에 있다.
|
||||
|
||||
<!-- body:end -->
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: adapter-outbound-persistence-jpa-c24
|
||||
title: 질의 계층은 프레임워크가 아니라 세 단계의 정책층이다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:adapter-outbound-persistence-jpa-c24
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: adapter-outbound-persistence-jpa-c24
|
||||
file: ../../../final/evidence/rendered/adapter-outbound-persistence-jpa-c24.svg
|
||||
- key: adapter-outbound-persistence-jpa-c24-diagram
|
||||
file: ../../../final/assets/diagrams/adapter-outbound-persistence-jpa-c24.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/adapter-outbound-persistence-jpa-c24.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/05-adapter-outbound-persistence-jpa.md#L1610 이다.
|
||||
module: adapter-outbound-persistence-jpa
|
||||
---
|
||||
|
||||
# 질의 계층은 프레임워크가 아니라 세 단계의 정책층이다
|
||||
|
||||
`springdata`와 `querydsl`은 application-core의 repository contract를 대체하는 generic CRUD layer가 아니다. `JpaRepositoryFragmentSupport`에는 범용 `save/findAll/delete`가 없다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
현재 code shape는 대략 다음처럼 읽는 것이 맞다. `springdata`와 `querydsl`이 application-core의 repository contract를 대체하는 generic CRUD layer가 아니다.
|
||||
|
||||
## 질의 계층의 세 단계
|
||||
|
||||
:::evidence key="adapter-outbound-persistence-jpa-c24-diagram" alt="응용 계층 질의 계약과 springdata 와 hibernate 가 위에서 아래로 쌓이고 위임 방향 화살표가 아래로 그려진 구조" caption="질의 계층의 세 단계" zoom="false"
|
||||
:::
|
||||
|
||||
`springdata/**`는 allowlisted sort, keyset assembly/predicate, fetch-plan catalog, bounded stream lifetime, Specification safety를 맡는다. `hibernate/**`는 provider/version facts, 실제 Statistics/JDBC batch evidence, statement naming, batch/bulk/stateless provider optimization을 맡는다.
|
||||
|
||||
## JpaRepositoryFragmentSupport 참조 위치
|
||||
|
||||
:::evidence key="adapter-outbound-persistence-jpa-c24" alt="코드베이스에서 JpaRepositoryFragmentSupport 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="JpaRepositoryFragmentSupport 코드베이스 검색 — 5줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 범용 CRUD가 없는 이유
|
||||
|
||||
`JpaRepositoryFragmentSupport`에는 범용 `save/findAll/delete`가 없고, domain-owned adapter가 필요한 query mechanism만 조합하게 설계돼 있다. 이 방향은 support matrix의 "platform-owned generic CRUD repository는 unsupported"와 일치한다.
|
||||
|
||||
<!-- body:end -->
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: app-bootstrap-c01
|
||||
title: 세 환경 검증기의 관심사가 서로 다르다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:app-bootstrap-c01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: app-bootstrap-c01
|
||||
file: ../../../final/evidence/rendered/app-bootstrap-c01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/app-bootstrap-c01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/18-app-bootstrap.md#L179 이다.
|
||||
module: app-bootstrap
|
||||
---
|
||||
|
||||
# 세 환경 검증기의 관심사가 서로 다르다
|
||||
|
||||
`EnvironmentPostProcessor`가 셋 있지만 각각 스위치 값 문법, 프로파일, 능력 간 의존이라는 다른 관심사를 본다. 중복 아님.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`EnvironmentPostProcessor`가 셋이다.
|
||||
|
||||
| 검증기 | 보는 것 |
|
||||
|---|---|
|
||||
| `MasterSwitchEnvironmentPostProcessor` | 스위치 값 문법 |
|
||||
| `RuntimeEnvironmentProfileValidator` (93) | 프로파일 |
|
||||
| `CapabilityDependencyEnvironmentValidator` (62 → `CapabilityDependencyValidator` 156) | 능력 간 의존 |
|
||||
|
||||
## MasterSwitchEnvironmentPostProcessor 참조 위치
|
||||
|
||||
:::evidence key="app-bootstrap-c01" alt="코드베이스에서 MasterSwitchEnvironmentPostProcessor 를 검색한 출력 3줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="MasterSwitchEnvironmentPostProcessor 코드베이스 검색 — 3줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 중복으로 보지 않은 이유
|
||||
|
||||
셋 다 `EnvironmentPostProcessor`라는 확장 지점을 공유할 뿐 관심사가 다르다. 중복 아님.
|
||||
|
||||
<!-- body:end -->
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: app-bootstrap-c02
|
||||
title: 런타임 멤버십이 결정하는 어댑터 활성화 스위치 범위
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:app-bootstrap-c02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: app-bootstrap-c02
|
||||
file: ../../../final/evidence/rendered/app-bootstrap-c02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/app-bootstrap-c02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/18-app-bootstrap.md#L185 이다.
|
||||
module: app-bootstrap
|
||||
---
|
||||
|
||||
# 런타임 멤버십이 결정하는 어댑터 활성화 스위치 범위
|
||||
|
||||
`src/config/architecture/modules.json`의 `runtime_memberships`가 인바운드 어댑터의 런타임 포함 여부를 결정한다. gRPC와 WebSocket은 build-only leaf라 런타임 멤버십이 없고, `MasterSwitch`·`env-keys.yaml`·`AdapterActivationReport`에 활성화 스위치가 없는 상태가 이 모델과 일치한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## build-only 전송의 런타임 멤버십
|
||||
|
||||
`adapter-inbound-grpc`와 `adapter-inbound-websocket`의 `runtime_memberships`는 비어 있다. `ConditionalTransportCompositionContractTest`도 두 leaf가 어떤 런타임에도 올라가지 않아야 한다는 계약을 강제한다.
|
||||
|
||||
## MasterSwitch 참조 위치
|
||||
|
||||
:::evidence key="app-bootstrap-c02" alt="코드베이스에서 MasterSwitch 를 검색한 출력 17줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="MasterSwitch 코드베이스 검색 — 17줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 멤버십과 활성화 스위치의 관계
|
||||
|
||||
런타임에 포함되지 않는 build-only 어댑터에는 운영자가 켜고 끌 활성화 스위치가 필요하지 않다. 따라서 gRPC와 WebSocket이 `MasterSwitch`·`env-keys.yaml`·`AdapterActivationReport`에 없는 것은 누락이 아니라 런타임 멤버십 모델과 일치한다.
|
||||
|
||||
## 별도로 남는 web 경계
|
||||
|
||||
`adapter-inbound-web`은 `["app-bootstrap", "sample-portfolio"]` 두 런타임에 포함된다. `backend.web.mvc.enabled`, `backend.web.webflux.enabled`, `backend.web.budgets.enabled`, `app.web-platform.durable-operations.enabled`는 `MasterSwitch`와 `env-keys.yaml` 341개 키에 포함되지 않는다. 이 경계는 같은 분석의 §4.1b에서 별도 finding으로 다룬다.
|
||||
|
||||
<!-- body:end -->
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: app-bootstrap-c03
|
||||
title: .imports 여섯 줄 중 다섯이 능력 루트다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:app-bootstrap-c03
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: app-bootstrap-c03
|
||||
file: ../../../final/evidence/rendered/app-bootstrap-c03.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/app-bootstrap-c03.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/18-app-bootstrap.md#L264 이다.
|
||||
module: app-bootstrap
|
||||
---
|
||||
|
||||
# .imports 여섯 줄 중 다섯이 능력 루트다
|
||||
|
||||
`.imports`의 여섯 항목 중 다섯이 능력 루트이고 하나(`AdapterActivationAutoConfiguration`)가 자기 액추에이터다. `MasterSwitch`의 다섯과 일치하지만 `PERSISTENCE_MONGO`만 루트가 없다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`.imports`의 여섯 항목 중 다섯이 능력 루트이고 하나(`AdapterActivationAutoConfiguration`)가 자기 액추에이터다. `MasterSwitch`의 다섯과 일치한다.
|
||||
|
||||
## AdapterActivationAutoConfiguration 참조 위치
|
||||
|
||||
:::evidence key="app-bootstrap-c03" alt="코드베이스에서 AdapterActivationAutoConfiguration 를 검색한 출력 2줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="AdapterActivationAutoConfiguration 코드베이스 검색 — 2줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## PERSISTENCE_MONGO만 루트가 없다
|
||||
|
||||
`PERSISTENCE_MONGO`는 `.imports`에 루트가 없고 컴포넌트 스캔 제외 정규식(`adapter\.outbound\.mongo\..*`)으로만 관리된다. §7.1.
|
||||
|
||||
<!-- body:end -->
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: grpc-advanced-diagnostics-c02
|
||||
title: 진단 열람은 망 게이트와 역할 게이트를 함께 요구한다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:grpc-advanced-diagnostics-c02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-advanced-diagnostics-c02
|
||||
file: ../../../final/evidence/rendered/grpc-advanced-diagnostics-c02.svg
|
||||
- key: grpc-advanced-diagnostics-c02-diagram
|
||||
file: ../../../final/assets/diagrams/grpc-advanced-diagnostics-c02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-advanced-diagnostics-c02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-advanced-diagnostics.md#L51 이다.
|
||||
module: grpc-advanced-diagnostics
|
||||
---
|
||||
|
||||
# 진단 열람은 망 게이트와 역할 게이트를 함께 요구한다
|
||||
|
||||
`GrpcChannelDiagnosticsPolicy`는 네트워크와 역할 두 게이트를 모두 요구하고, 하나라도 비면 생성자가 거부한다. CSDS는 그 위에 xDS 사용 여부라는 조건이 더 붙는다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`GrpcChannelDiagnosticsPolicy` 는 네트워크와 역할 두 게이트를 모두 요구하고, 하나라도 비면 생성자가 거부한다.
|
||||
|
||||
> "diagnostics need both a network and a role gate; Channelz holds every socket's peer and security detail, so either gate alone is the whole surface"
|
||||
|
||||
## 열람을 여는 조건
|
||||
|
||||
:::evidence key="grpc-advanced-diagnostics-c02-diagram" alt="정책 경계 안에 네트워크 게이트와 역할 게이트가 나란히 들어 있고 CSDS 는 경계 밖에 점선 상자로 놓인 구조" caption="열람을 여는 조건" zoom="false"
|
||||
:::
|
||||
|
||||
## GrpcChannelDiagnosticsPolicy 참조 위치
|
||||
|
||||
:::evidence key="grpc-advanced-diagnostics-c02" alt="코드베이스에서 GrpcChannelDiagnosticsPolicy 를 검색한 출력 12줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="GrpcChannelDiagnosticsPolicy 코드베이스 검색 — 12줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## CSDS가 등록되는 조건
|
||||
|
||||
등록 판정이 능력 깃발에 걸려 있다. CSDS 는 Channelz 가 켜져 있고 xDS 도 켜져 있을 때만 등록된다.
|
||||
|
||||
> "A CSDS service on a deployment that does not use xDS answers every query with nothing, which is harmless, and advertises a control-plane surface that does not exist, which is not."
|
||||
|
||||
<!-- body:end -->
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: messaging-kafka-share-experimental-c07
|
||||
title: 이전 결함 대신 막으려는 것 셋을 적는다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:messaging-kafka-share-experimental-c07
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-kafka-share-experimental-c07
|
||||
file: ../../../final/evidence/rendered/messaging-kafka-share-experimental-c07.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-kafka-share-experimental-c07.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-kafka-share-experimental.md#L399 이다.
|
||||
module: messaging-kafka-share-experimental
|
||||
---
|
||||
|
||||
# 이전 결함 대신 막으려는 것 셋을 적는다
|
||||
|
||||
이 leaf의 javadoc에는 이전 결함 서술이 없다. 다른 messaging leaf 대부분이 "X used to …" 형태의 기록을 갖는 것과 대비되며, 대신 막으려는 것을 셋 적는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **허용 의존 목록은 상한이므로 미사용을 잡지 않는다 — 그것을 잡으려면 별도 검사가 필요하다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **leaf의 각 public 클래스는 자기 레인에 테스트를 갖는다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **에러 메시지가 지시하는 설정은 그 설정을 읽는 코드와 함께 존재해야 한다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **구성 오류는 한 예외 타입과 안정 코드로 보고한다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
이 leaf의 javadoc에 **이전 결함 서술이 없다.** 다른 messaging leaf 대부분이 "X used to …" 형태의 기록을 갖는 것과 대비된다. 대신 **막으려는 것**을 셋 적는다.
|
||||
|
||||
| 위치 | 막으려는 것 |
|
||||
|---|---|
|
||||
| `KafkaShareProfileValidator` | 순서 목적지를 share group에 설정 → 브로커가 주지 않는 보장을 광고 |
|
||||
| 같은 곳 | experimental이 기본 켜져 Stable 배포로 drift |
|
||||
| `KafkaShareGroupRegistrar` | pause를 조용히 무시 → pause에 의존하는 retry 정책이 동작하는 것처럼 보이며 아무것도 하지 않음 |
|
||||
|
||||
## MessagingCapabilityUnavailableException 참조 위치
|
||||
|
||||
:::evidence key="messaging-kafka-share-experimental-c07" alt="코드베이스에서 MessagingCapabilityUnavailableException 를 검색한 출력 37줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="MessagingCapabilityUnavailableException 코드베이스 검색 — 37줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 거절이 무시보다 낫다는 원칙이 빠진 자리
|
||||
|
||||
세 번째가 이 leaf에서 가장 성숙한 판단이다 — **거절이 무시보다 낫다**는 원칙이고, `messaging-core-api`의 `MessagingCapabilityUnavailableException` javadoc과 같은 계열이다. 역설적으로 **그 원칙이 `register(...)`에는 적용되지 않았다** — spec을 받아 무시하고 성공을 반환한다(§17).
|
||||
|
||||
<!-- body:end -->
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: messaging-testkit-c05
|
||||
title: 등급을 판정하는 경로가 증거를 만드는 경로 없이도 돈다
|
||||
topic: capability-and-disclosure-models
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:messaging-testkit-c05
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-testkit-c05
|
||||
file: ../../../final/evidence/rendered/messaging-testkit-c05.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-testkit-c05.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-testkit.md#L452 이다.
|
||||
module: messaging-testkit
|
||||
---
|
||||
|
||||
# 등급을 판정하는 경로가 증거를 만드는 경로 없이도 돈다
|
||||
|
||||
실행 경로가 셋이다. 등급 판정(경로 C)은 컨테이너 없이 매 빌드 돌고, 그 판정이 읽는 증거를 만드는 경로 B는 컨테이너를 요구해 `test`에서 제외돼 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
실행 경로가 셋이고 컨테이너 요구가 서로 다르다.
|
||||
|
||||
**경로 A — 어댑터 계약 실행 (컨테이너 불필요, 항상 실행)** `MessagingAdapterHarness extends AutoCloseable` 이고 `close()` 가 checked exception 을 던지지 않도록 재선언되어 있다(`MessagingAdapterHarness.java:71-72`). 7개 테스트 전부 `try (…)` 로 감싸므로 하니스 누수 경로가 없다.
|
||||
|
||||
**경로 B — 인증 증거 생산 (컨테이너 필요, `test` 에서 제외)**
|
||||
|
||||
**경로 C — 등급 판정 (컨테이너 불필요, 매 빌드)**
|
||||
|
||||
## 이 기록이 다루는 파일 범위
|
||||
|
||||
:::evidence key="messaging-testkit-c05" alt="코드베이스에서 파일 목록을 만든 출력 13줄. 이 기록이 다루는 범위가 그 목록이다." caption="코드베이스 파일 목록 — 13줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 경로 C가 경로 B 없이 도는 것이 설계의 핵심이다
|
||||
|
||||
경로 C 가 경로 B 없이도 돌고, 경로 B 가 없으면 매니페스트가 비어 등급 주장이 무너진다.
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user