Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/capability-declaration-vs-proof/case/case-a05-f006-stable.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

12 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-f006-stable 안정 등급으로 광고한 증거를 만드는 매니저가 어디서도 만들어지지 않는다 capability-declaration-vs-proof clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:a05-f006-stable 2026-09-02
key file
a05-f006-stable ../../../final/evidence/rendered/a05-f006-stable.svg
key file
a05-f006-stable-chain ../../../final/evidence/rendered/a05-f006-stable-chain.svg
../../../final/evidence/raw/a05-f006-stable.txt
../../../final/evidence/raw/a05-f006-stable-chain.txt
분석 문서는 persistence-jpa 편 §23 이고 세부는 §23.1 이다. 능력이 안정으로 보고되는데 매니저 생성이 0 이라는 판정과, 루트의 몫으로 남은 분류기 팩토리에도 소비자가 없다는 관찰이 거기 있다. 같은 문서 §23.5 가 범위를 한정한다.

안정 등급으로 광고한 증거를 만드는 매니저가 어디서도 만들어지지 않는다

완료 증거 능력이 안정 등급으로 보고된다. 그 증거를 만드는 트랜잭션 매니저를 생성하는 코드는 자기 파일의 정적 팩토리뿐이고, 그것을 부르는 곳이 프로덕션에도 테스트에도 없다.

관계

  • 커밋 증거 단계 — NOT_STARTED에서 UNKNOWN까지 이 능력이 만드는 증거 모델이다.
  • @Bean이 있다는 것은 조립 증거가 아니다 이 사례가 그 규칙의 형태다.
  • 지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다 등급과 실제의 거리를 다룬 결정이다.

문제

완료 증거란 커밋 진행 지점을 남길 수 있는가의 문제다. 그 기록이 없으면 커밋 실패를 롤백된 것과 결과 미상으로 나눌 수 없다.

이 능력의 등급은 능력 리포트에 안정으로 올라 있다.

결론

계수는 이렇다. 매니저를 만드는 코드 0, 클래스 이름을 언급하는 다른 프로덕션 파일 1 이고 그것도 javadoc 안이다. 설정 리소스에 FQCN 0, 상속 0, 트랜잭션 매니저 빈을 등록하는 main 코드 0 이다.

그래서 완료 불명 예외는 출하 조립에서 던져질 경로가 없다. 그것을 만드는 프로덕션 코드는 분류기 한 곳이고, 그 분류기를 부르는 프로덕션 코드는 매니저의 커밋 catch 한 곳이며, 그 매니저를 설치하는 코드가 없다.

컴포지션 루트의 javadoc 에는 루트에서 만들면 ORM 타입이 루트의 컴파일 클래스패스에 올라오기 때문에 매니저를 영속성 리프 안에서 만든다고 적혀 있다. 그 설명은 클래스패스에서 사실로 확인된다. 그런데 영속성 리프 쪽에도 그것을 만드는 코드가 없다.

그 javadoc 은 루트가 맡을 것으로 둘을 든다. 설치 여부의 결정, 그리고 거기에 쓸 커밋 실패 분류기다. 팩토리는 있는데 그것을 부르는 코드가 없다. 그 팩토리를 담은 클래스는 스프링 설정이 아니라 평범한 클래스이고, 그것을 쓰는 프로덕션 코드가 부르는 메서드는 실행기와 재시도 코디네이터 둘뿐이다.

프레임이 안 만들어지는 것은 아니다. 실제로 조립되는 실행기는 트랜잭션마다 프레임을 밀어 넣는다. 그 프레임을 커밋 단계로 옮기는 것이 설치되지 않는 매니저뿐이라 프레임은 시작 전 상태로 남는다.

매니저의 javadoc 에는 코드와 맞지 않는 문단도 있다. 증거가 모든 경로에서 지워진다고 적는데, 커밋과 롤백의 finally 는 둘 다 비어 있고 여기서 지우지 않는다고 주석이 달려 있다. 꺼내는 쪽은 실행기의 스코프이고 매니저가 하는 일은 단계 표시다. 두 주인이 꺼내던 시절의 서술이 남은 것이다.

단계를 읽는 접근자도 아무도 부르지 않는다. 분류기가 프레임에서 꺼내는 것은 작업 이름과 시작 시각과 시도 횟수와 조정 키이고, 단계는 보지 않는다. 단계에 민감해지는 것은 검사 때문이 아니라 어디서 부르느냐 때문이다.

그 순서를 실제로 돌리는 테스트도 없다. 이름만 같은 테스트가 정작 그 타입을 건드리지 않고, 순서는 다른 시험이 본다고 자기 javadoc 에 적어 둔다.

범위는 한정된다. 정규 트랜잭션 경로 쪽은 다른 감시자가 커밋 예외를 불확정 결과로 바꿔 놓고 재실행은 하지 않는다. 없는 것은 자동 재시도 안전이 아니라 안정 등급으로 내건 조정 증거 쪽이다.

검증 환경

OpenJDK : 21.0.12 확인 방식 : 생성 지점과 빈 등록의 정적 계수, javadoc 과 코드 대조, 예외 생산·호출 사슬 추적 소스 수정 : x

재현 조건

  1. 능력 리포트에서 이 능력의 등급을 확인한다.
  2. 매니저의 javadoc 과 같은 파일의 doCommit·doRollback 을 나란히 읽는다.
  3. 그 매니저를 만드는 코드를 센다. 자기 파일과 javadoc 을 뺀다.
  4. 설정 리소스의 FQCN 과 상속과 트랜잭션 매니저 빈 등록을 각각 센다.
  5. 루트가 몫이라고 적은 분류기 팩토리의 호출자를 센다.
  6. 완료 불명 예외의 생산 지점과 그것을 부르는 지점을 따라간다.
  7. 단계를 읽는 접근자의 호출자를 센다.

본문

완료 증거는 트랜잭션이 어디까지 갔는지를 기록한다. 커밋 실패를 롤백된 것과 결과를 모르는 것으로 나누려면 그 기록이 있어야 한다.

능력 리포트는 이 능력을 안정 등급으로 보고한다.

javadoc 이 주장하는 것과 코드가 하는 것

:::evidence key="a05-f006-stable" alt="능력 리포트가 완료 증거 능력에 매긴 등급, 그 증거를 만드는 트랜잭션 매니저의 javadoc 과 같은 파일의 커밋·롤백 구현, 그 매니저를 만드는 코드와 자기 파일을 뺀 생성 지점 수, 설정 리소스의 FQCN 과 상속 수, 그리고 트랜잭션 매니저 빈을 등록하는 main 과 test 코드 수를 출력한 터미널 기록." caption="능력 등급은 stable · javadoc 의 정리 규칙과 비어 있는 finally · 자기 파일 밖 생성 0 · 설정 리소스 0 · 상속 0 · 매니저 빈 main 0, test 1 — 56줄 · exit 0" zoom="true" :::

매니저의 javadoc 에는 단계가 제공자 커밋 직전에 표시되고 그 뒤에는 표시되지 않는다고 한 문장으로 적혀 있다. 커밋 안에서 죽으면 마지막으로 기록된 것은 we asked, we do not know 이고, javadoc 은 그것이 롤백으로 오인되어서는 안 되는 상태라고 적는다.

같은 javadoc 에는 증거가 커밋 성공과 실패와 롤백과 정리, 모든 경로에서 지워진다는 정리 규칙도 적혀 있다. 코드는 그렇지 않다.

} finally {
  // Deliberately not cleared here. The executor's scope owns the frame's lifetime; a second
  // owner popping was how an inner REQUIRES_NEW transaction deleted its outer frame.
}

커밋과 롤백의 finally 가 둘 다 비어 있고 같은 주석이 붙어 있다. 프레임을 꺼내는 것은 실행기의 스코프뿐이고 매니저는 단계만 표시한다. 그 javadoc 문단은 주인이 둘이던 시절의 서술이 남은 것이다.

그 매니저를 만드는 코드가 없다

자기 파일 안에 정적 팩토리가 있고, 그것을 부르거나 생성자를 쓰는 코드는 자기 파일 밖에 0 이다. 클래스 이름을 언급하는 다른 프로덕션 파일은 하나뿐이고 그것도 자동설정의 javadoc 안이다.

설정 리소스에 FQCN 이 나오는 곳도 0 이고, 상속하는 코드도 0 이다. 트랜잭션 매니저 빈을 등록하는 main 코드도 0 이다. 테스트에 하나 있는데, 그것은 자동설정 시험이 조건을 만족시키려고 세운 평범한 매니저다.

이 저장소가 등록하는 매니저가 없다는 뜻이고, 매니저가 없다는 뜻은 아니다. 부트의 JPA 자동설정이 평범한 것을 넣고, 그것은 단계를 표시하지 않는다.

루트가 남긴 몫도 비어 있다

:::evidence key="a05-f006-stable-chain" alt="컴포지션 루트가 매니저를 만들지 않는 이유와 루트의 몫으로 지목한 두 가지를 적은 javadoc, 그 분류기 팩토리의 호출자 수, 그 클래스가 스프링 설정이 아니라는 서술과 그것을 쓰는 프로덕션 코드가 부르는 메서드, 완료 불명 예외의 생산 지점과 그것을 부르는 지점, 분류기가 프레임에서 꺼내는 값과 단계 접근자의 호출자 수, 조립되는 실행기가 프레임을 미는 줄, 그리고 같은 이름의 테스트가 그 타입을 참조하는 횟수를 출력한 터미널 기록." caption="루트가 만들지 않는 이유와 남긴 몫 둘 · 분류기 팩토리 호출자 0 · 예외 생산 1곳과 호출 1곳 · 분류기는 단계를 보지 않음 · 단계 접근자 호출자 0 · 실행기는 프레임을 만듦 — 45줄 · exit 0" zoom="true" :::

javadoc 이 루트가 여기서 만들지 않는 이유를 적는다. 매니저는 영속성 리프 안에서 만들어지고, 루트가 만들면 jakarta.persistenceorg.hibernate 가 루트의 컴파일 클래스패스에 올라온다는 것이다.

그 이유는 클래스패스 구성으로 성립한다. 다만 영속성 리프 안에도 그것을 만드는 코드는 없다. javadoc 은 일어난 일이 아니라 일어났어야 할 일을 서술한다.

같은 javadoc 이 루트의 몫으로 둘을 지목한다. 설치할지 말지의 결정과 그것이 쓸 커밋 실패 분류기다.

분류기를 만드는 팩토리는 존재하고, 부르는 코드는 0 이다. 그 팩토리를 담은 클래스는 @Configuration@Bean 도 없는 평범한 클래스이고, 스스로 그렇게 적는다. 그것을 쓰는 유일한 프로덕션 코드가 부르는 메서드는 실행기와 재시도 코디네이터 둘이다.

예외로 가는 길이 한 줄씩 끊긴다

완료 불명 예외를 만드는 프로덕션 코드는 분류기 97행 한 곳이다. 그 분류기의 번역 메서드를 부르는 프로덕션 코드는 매니저 57행의 커밋 catch 한 곳이다. 그 매니저를 설치하는 코드가 0 이다.

분류기가 프레임에서 꺼내는 것은 작업 이름, 시작 시각, 시도 횟수, 조정 키다. 단계는 보지 않는다. 단계 민감성은 검사가 아니라 호출 위치에서 나온다. 단계를 읽는 접근자를 부르는 코드는 저장소 전체에 0 이다.

프레임은 만들어지고, 단계만 오르지 않는다

조립되는 실행기가 트랜잭션마다 프레임을 민다. 그 실행기는 플랫폼 트랜잭션 매니저 빈이 있을 때 붙는 빈이고, 부트가 넣은 매니저가 그 조건을 만족시킨다.

그 프레임을 활성과 커밋 중과 커밋됨으로 옮기는 것은 설치되지 않는 매니저뿐이다. 프레임은 시작 전 상태로 남는다.

순서를 실행하는 테스트도 없다. 같은 이름의 테스트는 그 타입을 한 번도 참조하지 않고 컨텍스트와 분류기를 따로 검증하며, 자기 javadoc 이 실제 순서는 커밋 모호성 계약 시험이 본다고 적는다.

범위

이 사건이 모든 유스케이스가 불확정 커밋을 중복 실행한다는 뜻은 아니다. 애플리케이션의 정규 트랜잭션 경로는 별도의 스프링 동기화 감시자로 커밋 예외를 불확정 결과로 되돌리고 재실행하지 않는다.

빠진 것은 자동 재시도 안전이 아니라, 안정 등급으로 광고한 조정 증거다. 지속되는 기록도 런북 지표도 없고, 애플리케이션이 돌려주는 불확정 결과에도 조정 참조가 비어 있다.

확인하지 못한 것

애플리케이션을 부팅해 어떤 트랜잭션 매니저가 실제로 쓰이는지 관측하지 않았다. 조립 코드에 그것을 만드는 자리가 없다는 것까지만 확인했다.