Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/declaration-and-document-drift/case/case-a05-f019-ssot.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 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>
2026-09-04 22:51:59 +09:00

7.9 KiB

kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, assets, evidence, source
kind slug title topic project status sourceRevision rootTreeNode evidenceCapturedOn assets evidence source
CASE a05-f019-ssot 정본이 어디인지 주석에 적어 두고 정의는 다시 타이핑한다 declaration-and-document-drift clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:a05-f019-ssot 2026-09-02
key file
a05-f019-ssot ../../../final/evidence/rendered/a05-f019-ssot.svg
../../../final/evidence/raw/a05-f019-ssot.txt
원본 분석 절은 `analysis/05-adapter-outbound-persistence-jpa.md` §51 이다. 두 목록의 이름과 차이가 `postgresql` 과 `h2` 라는 것이 §51.1 과 §51.2 에, 리프 목록이 바깥 소비자를 스캔하지 않는다는 것이 §51.3 에 있다. 같은 문서 §55 의 backlog 가 이것을 P2/P3 아키텍처 거버넌스 강화로 분류한다.
사본의 javadoc 이 정본이 어디인지 적어 두고도 그 값을 코드로 읽지 않는다는 것은 여기서 확인했다.

정본이 어디인지 주석에 적어 두고 정의는 다시 타이핑한다

리프가 내보내는 패키지 목록을 선언하고, 컴포지션 루트의 소비자 규칙이 같은 목록을 자기 안에 다시 적는다. 사본의 javadoc 은 정본이 리프 쪽이라고 이름으로 적지만, 그 이름을 코드로 읽는 곳은 없다. 두 목록은 이미 두 항목 다르다.

관계

  • 문서의 수치는 세지 말고 파생하거나 게이트로 붙든다 문서의 수치를 세는 대신 파생하거나 게이트로 붙들라는 규칙이다.
  • 빠뜨림이 통과가 되는 게이트는 게이트가 아니다 한쪽에 항목을 더해도 다른 쪽이 조용한 형태가 같다.
  • 같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다 같은 값을 두 곳이 각자 적고 있어 어느 쪽이 정본인지 정해야 한다.

문제

이 리프에는 jar 하나에 공개 구현 타입이 많이 들어 있다. 그래서 자바 접근 제어와 아키텍처 노출 목록을 각각 따로 둔다.

내보낼 패키지 목록을 두 곳이 각각 들고 있다.

결론

리프의 경계 시험 쪽에는 열 개가 선언돼 있다. springdata 와 querydsl 은 거기 없다.

컴포지션 루트의 아키텍처 시험이 같은 열 개를 자기 안에 다시 적고, 벤더 진입점 둘을 더 넣는다. postgresql 과 h2 다. 이유는 그 자리 주석에 적혀 있다. 벤더 설정이 코어 JPA 설정을 임포트하는 방향이라 컴포지션 루트가 대신 막을 단일 내부 진입점이 없고, 방향을 뒤집으면 패키지 순환이 생겼다는 것이다.

사본의 javadoc 에는 이 목록이 리프의 export 허용 목록을 옮겨 적은 것이고 정의는 리프의 경계 시험에 있다고 적혀 있다.

그 문장이 컴포지션 루트에서 그 클래스를 언급하는 유일한 줄이다. 선언 파일 밖에서 그 목록 상수를 참조하는 자바 코드는 저장소 전체에 0 이다. 어느 쪽이 정본인지는 산문이 말하고, 값은 사람이 옮겨 적는다.

리프 목록은 바깥 소비자를 검사하지도 않는다. 그 목록을 쓰는 시험 둘은 목록에 적힌 패키지가 실제로 있는지, 그 패키지가 카탈로그의 거버넌스 대상인지를 본다. 임포트 관계는 컴포지션 루트 쪽 규칙이 따로 본다.

그래서 두 시험 모두 통과한다. 통과는 각자의 규칙을 만족한다는 뜻이고, 두 목록이 같다는 뜻은 아니다. 지금 이미 두 항목 다르다.

권고는 등록부를 한 곳에 두고 두 검사가 같은 데이터를 보게 하라는 것이다.

검증 환경

OpenJDK : 21.0.12 확인 방식 : 두 목록의 항목 추출과 집합 대조, 정본 참조 계수, 리프 시험의 검사 범위 확인 소스 수정 : x 실행 : 없음. 정적 검색과 집합 연산이다.

재현 조건

  1. 리프 경계 시험의 export 목록을 읽는다.
  2. 컴포지션 루트의 소비자 규칙 안에 있는 같은 이름의 목록을 읽는다.
  3. 두 집합을 뽑아 차집합을 구한다.
  4. 사본의 javadoc 이 정본을 어떻게 지목하는지 읽는다.
  5. 컴포지션 루트에서 그 정본 클래스를 언급하는 줄과, 선언 파일 밖에서 그 상수를 참조하는 코드를 각각 센다.
  6. 리프 목록을 쓰는 시험 둘이 무엇을 확인하는지 읽는다.

본문

이 리프는 하나의 jar 안에 공개 구현 타입이 많다. 그래서 자바의 public 과 아키텍처가 내보내는 패키지를 따로 관리한다.

내보내는 패키지 목록이 두 곳에 있다.

두 목록과 그 차이

:::evidence key="a05-f019-ssot" alt="리프 경계 시험이 선언하는 export 패키지 목록, 컴포지션 루트의 소비자 규칙 안에 다시 적힌 같은 목록과 거기 더해진 벤더 진입점 둘과 그 이유 주석, 두 집합의 크기와 차집합, 사본의 javadoc 이 정본을 지목하는 줄과 그 정본 클래스를 언급하는 줄 수와 선언 파일 밖의 상수 참조 수, 그리고 리프 목록을 쓰는 두 시험이 무엇을 보는지를 출력한 터미널 기록." caption="리프 10개와 루트 12개, 차이는 postgresql 과 h2 · 사본 javadoc 이 정본을 어디라고 적는지 · 그 클래스 언급 1줄은 그 주석뿐 · 상수 참조 0 · 리프 시험 둘은 목록의 자기 정합만 확인 — 65줄 · exit 0" zoom="true" :::

리프의 경계 시험이 열 개를 선언한다.

private static final Set<String> EXPORTED_PACKAGES =
    Set.of(
        "api", "notification.configuration", "transaction", "security",
        "observation", "migration", "hibernate", "fileserver", "failure", "config");

컴포지션 루트의 아키텍처 시험은 같은 열 개에 둘을 더 적는다. 그 자리의 주석이 이유를 적는다.

The two vendor entry points. A vendor configuration imports the core JPA config rather
than the reverse, so there is no single internal entry the composition root could gate
instead — inverting the import to make one produced a package cycle.

집합으로 빼면 차이가 정확히 둘이다.

리프 10개 / 루트 12개
루트에만 있는 것: ['h2', 'postgresql']
리프에만 있는 것: []

사본이 정본을 지목하는 방식

The list is the leaf's export allowlist, mirrored here because this is the consumer side
of the same boundary. JpaModuleBoundaryTest owns the definition.

그 문장이 컴포지션 루트에서 리프 경계 시험을 언급하는 유일한 줄이다. 선언 파일 밖에서 EXPORTED_PACKAGES 를 참조하는 자바 코드는 저장소 전체에 0 이다.

정본을 지목하는 것은 산문이고, 값은 손으로 옮겨져 있다.

리프 목록은 바깥 소비자를 보지 않는다

그 목록을 쓰는 시험은 둘이다. 목록에 적힌 패키지가 소스 트리에 실제로 있는지, 그리고 그 패키지가 카탈로그의 거버넌스 대상인지를 본다.

누가 무엇을 임포트하는지는 컴포지션 루트의 규칙이 따로 본다. split 은 실수가 아니라 역할 분리의 결과이고, 그래서 어느 쪽도 상대를 검사할 이유가 없다.

두 시험이 각각 무엇을 보는가

두 시험의 입력이 겹치지 않는다. 리프 시험은 리프의 소스 트리와 자기 카탈로그만, 루트 시험은 루트의 임포트 그래프와 자기 목록만 읽는다.

한쪽 목록이 늘어도 다른 쪽 단언의 입력은 그대로다. 실패할 근거가 없다.

고칠 방향

내보내는 패키지 등록부를 한 곳으로 옮기고, 리프의 패키지 그래프 검사와 소비자 규칙이 같은 데이터를 읽게 한다. 분석 문서의 권고가 그것이다.

확인하지 못한 것

한쪽 목록에 항목을 더해 다른 쪽이 조용한지 실행으로 확인하지 않았다. 두 선언을 대조하고 참조를 센 것까지가 확인 범위다.