Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/duplicate-mechanisms/case/case-the-same-repository-bound-a-decision-once-and-not-the-other-time.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

5.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 the-same-repository-bound-a-decision-once-and-not-the-other-time 같은 저장소가 "결정을 그 결정이 판정한 대상에 묶는 것"을 한 번은 맞게, 한 번은 틀리게 썼다 duplicate-mechanisms clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:the-same-repository-bound-a-decision-once-and-not-the-other-time 2026-09-01
key file
the-same-repository-bound-a-decision-once-and-not-the-other-time ../../../final/evidence/rendered/the-same-repository-bound-a-decision-once-and-not-the-other-time.svg
../../../final/evidence/raw/the-same-repository-bound-a-decision-once-and-not-the-other-time.txt
원본 분석 절은 analysis/grpc/grpc-codegen.md §17.4 · analysis/grpc/grpc-advanced-bootstrap.md(확인된 설계) 이다.

같은 저장소가 "결정을 그 결정이 판정한 대상에 묶는 것"을 한 번은 맞게, 한 번은 틀리게 썼다

두 리프가 판정과 기록을 두 호출로 나눈다. 한쪽은 결정이 자기가 밟고 선 상태를 들고 있어 적용 시점에 대조하고, 다른 쪽은 결정이 무엇을 판정했는지 들고 있지 않아 짝이 어긋날 수 있다.

관계

  • 중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다 같은 형태의 장치가 둘일 때 쓰는 규칙이다.
  • 같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다 같은 저장소가 같은 형태를 두 번 다르게 쓴 다른 사례다.
  • 검증기는 발행이 아니라 주입이 강제다 판정이 실제로 걸리는 지점을 묻는 계열의 규칙이다.

문제

판정과 기록을 나누면 그 사이를 무엇이 묶는지가 문제가 된다.

스키마 산출물 발행자는 후보와 소비자 픽스처 목록을 받아 발행 가능 여부를 결정으로 돌려준다. 그리고 그 결정을 들고 다시 오면 이력에 기록한다. 발행 결정은 허용 여부와 차단 사유 목록으로 되어 있다.

승격 지원 매트릭스도 같은 형태다. 승격 게이트가 증거와 시작 등급과 목표 등급을 받아 결정을 돌려주고, 매트릭스가 그 결정을 받아 적용한다.

결론

한쪽만 짝을 확인한다.

승격 매트릭스는 결정의 시작 등급이 현재 등급과 다르면 던진다. 예외 메시지가 그 이유를 적는다. 두 승격이 경합했거나 하나가 재생된 경우라는 것이다. 결정이 자기가 밟고 선 상태를 들고 있고 적용 시점에 그것을 대조한다.

스키마 발행자는 결정이 허용인지만 본다. 그 결정이 지금 발행하려는 후보를 판정한 것인지 확인하지 않는다. 발행 결정 객체가 허용 여부와 차단 사유만 갖고 있어서 확인할 재료도 없다.

그래서 무해한 후보를 평가한 결정으로 다른 후보를 발행할 수 있다. 그 후보는 어떤 소비자 픽스처와도 대조되지 않고, 이미 발행된 버전인지도 확인되지 않은 채 이력에 들어간다. 이 클래스가 존재하는 이유인 두 규칙, 즉 소비자 컴파일 게이트와 릴리스 버전 불변성이 인자 짝 하나로 무력해진다.

테스트의 차이도 같다. 발행자 테스트는 평가와 발행을 한 줄에 겹쳐 써서 규율을 지킨다. 그 규율을 코드가 강제하지 않는다. 매트릭스 쪽에는 어긋난 짝을 넣어 거부를 확인하는 테스트가 따로 있다.

이 저장소는 올바른 형태를 알고 있고 한 곳에서 쓰지 않았다.

검증 환경

OpenJDK : 21.0.12 Gradle : 9.0.0 확인 방식 : 두 적용 메서드의 본문 대조와 결정 객체의 성분 확인 소스 수정 : x

재현 조건

  1. 스키마 발행자의 평가 메서드와 발행 메서드가 각각 무엇을 검사하는지 적는다.
  2. 발행 결정 객체의 성분을 나열한다.
  3. 승격 매트릭스의 적용 메서드가 결정의 어떤 성분을 현재 상태와 대조하는지 확인한다.
  4. 두 테스트가 각각 짝을 어떻게 맞추는지 확인한다.

본문

두 리프가 같은 문제를 푼다 — 판정과 기록이 두 호출로 나뉠 때 그 사이를 무엇이 묶는가.

PublishDecision 참조 위치

:::evidence key="the-same-repository-bound-a-decision-once-and-not-the-other-time" alt="코드베이스에서 PublishDecision 를 검색한 출력 4줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="PublishDecision 코드베이스 검색 — 4줄 · exit 0" zoom="true" :::

묶지 않은 쪽

GrpcSchemaArtifactPublisher.publish(candidate, decision)decision.allowed() 만 보고 기록한다. PublishDecision(boolean, List<String>) 뿐이라 자기가 무엇을 판정했는지 들고 있지 않으므로, A 를 평가한 결정으로 B 를 발행할 수 있고 그러면 소비자 컴파일 게이트와 릴리스 버전 불변성을 둘 다 우회한다. 이 클래스가 존재하는 이유인 두 규칙이 인자 짝 하나로 무력해진다.

묶은 쪽

GrpcAdvancedSupportMatrix.apply(decision) 는 결정의 from 이 현재 등급과 다르면 던지고, 그 이유를 "두 승격이 경합했거나 하나가 재생된 경우" 라고 적는다. 결정이 자기가 밟고 선 상태를 들고 있고 적용 시점에 대조하는 형태다.

두 테스트의 차이도 같다

전자의 테스트는 publish(artifact, evaluate(artifact, …)) 로 한 줄에서 짝을 맞춰 규율을 지키지만 코드가 그것을 강제하지 않고, 후자는 어긋난 짝을 넣는 테스트가 따로 있다.

확인하지 못한 것

어긋난 짝을 실제로 실행해 보지 않았다. 판정은 발행 메서드 본문에 대조 코드가 없다는 것에 근거한다.