Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/304-canonical-form-asymmetry.txt
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

58 lines
3.6 KiB
Plaintext

# 주제: 같은 리프 안에서 "정규 형식" 인코딩이 두 가지이며, 그중 하나는
# 바로 옆 파일이 명시적으로 금지한 방식이다
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
# (A) 길이 접두 방식 — ApprovalGrant.canonicalForm() (ApprovalGrant.java:61-87)
# javadoc:
# "Every field is length-prefixed rather than delimited. A delimiter can be smuggled into a
# ticket or an identity to make two different grants render identically; a length prefix
# cannot."
# 구현:
# private static void appendField(StringBuilder canonical, String value) {
# canonical.append(value.length()).append(':').append(value);
# }
# 대상 필드 11개: ticket, approvedBy, requestedBy, operation, source, target, topologyVersion,
# maxImpact, planDigest, approvedAt, validUntil
# (B) 구분자 결합 방식 — 계획 다이제스트 2곳
# ReplayPlan.digest() (ReplayPlan.java:51-64)
# PlanDigest.ofCanonical(String.join("|",
# "REPLAY", replayId, destination, isolatedConsumerGroup, from, to, estimatedMessages,
# topologyVersion, targetsLiveConsumerGroup))
# RedrivePlan.digest() (RedrivePlan.java:50-62)
# PlanDigest.ofCanonical(String.join("|",
# "REDRIVE", redriveId, source, target, batchSize, candidates,
# alreadyRedrivenCandidates, topologyVersion))
# ---- 각 필드가 '|' 를 담을 수 있는가 ----
# command: cat messaging-core-api/.../destination/DestinationName.java
# private static final Pattern VALID = Pattern.compile("[a-z0-9][a-z0-9.-]{0,159}");
# -> destination/source/target 는 '|' 불가.
# replayId/redriveId : UUID.toString() -> 불가
# 불리언 : "true"/"false" -> 불가
# from/to : Instant.toString() -> 불가 ("" 가능)
# 정수/long : 10진수 -> 불가
# topologyVersion : String, 검증은 isBlank() 뿐 -> **가능**
# command: git grep -n "topologyVersion" -- src/messaging/messaging-admin-api/src/main
# ReplayPlan.java:37-38 if (topologyVersion == null || topologyVersion.isBlank()) throw …
# RedrivePlan.java:37-38 동일
# 생산처: BrokerTopologyInspector.topologyVersion() (messaging-admin-runtime, 애플리케이션이 구현하는 SPI)
# -> 형식 제약 없음.
# ---- 현재 충돌 가능성 판정 ----
# 자유 필드가 topologyVersion 하나뿐이고, 그 앞의 모든 필드가 '|' 를 담을 수 없으므로
# 왼쪽에서 '|' 를 세면 앞 필드들의 경계가 확정되고, 마지막 필드(고정 형식)도 오른쪽에서 확정된다.
# 따라서 현재 필드 구성에서는 인코딩이 단사(injective)이며 **충돌을 만들 수 없다**.
#
# 판정: 지금은 악용 가능한 결함이 아니다. 기록하는 이유는 두 가지다.
# 1) 같은 리프의 ApprovalGrant 가 정확히 이 위험("구분자 밀반입")을 이유로 길이 접두를 쓰고
# 그 이유를 javadoc 에 남겼는데, 승인이 서명으로 묶이는 대상인 계획 다이제스트 두 곳은
# 그 규칙을 따르지 않는다.
# 2) 단사성이 "자유 형식 필드가 하나뿐" 이라는 우연한 조건에 의존한다.
# 자유 형식 필드가 하나 더 추가되거나 topologyVersion 의 위치가 바뀌면
# 조용히 깨지며, 깨졌을 때의 결과는 "한 승인이 다른 계획을 인가" 로
# PlanDigest 가 존재하는 이유 자체가 무력화되는 것이다.
# 참고: PlanDigest 자체는 형식을 강하게 검증한다 (PlanDigest.java:23-28)
# if (!value.matches("[0-9a-f]{64}")) throw new IllegalArgumentException(…)