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>
138 lines
10 KiB
Plaintext
138 lines
10 KiB
Plaintext
# 클래스 자바독과 목록 선언 (93 줄)
|
|
GrpcCompletionReconciler.java:8
|
|
GrpcCompletionReconciler.java:9 /**
|
|
GrpcCompletionReconciler.java:10 * Turns an unknown completion into a decision, or hands it on.
|
|
GrpcCompletionReconciler.java:11 *
|
|
GrpcCompletionReconciler.java:12 * <p>Two sources, consulted in a fixed order: the ledger first, then — only when the ledger has
|
|
GrpcCompletionReconciler.java:13 * nothing and the method offers one — a business-resource probe. The order matters because they
|
|
GrpcCompletionReconciler.java:14 * answer different questions. The ledger knows whether this exact operation was claimed; the
|
|
GrpcCompletionReconciler.java:15 * resource only knows whether something that looks like the result exists, which is a weaker
|
|
GrpcCompletionReconciler.java:16 * statement and can be satisfied by a different caller's write.
|
|
GrpcCompletionReconciler.java:17 *
|
|
GrpcCompletionReconciler.java:18 * <p>When neither is conclusive, the resolution stays UNKNOWN and the case is queued. Guessing is
|
|
GrpcCompletionReconciler.java:19 * the failure mode this class exists to make impossible: a reconciler that resolves an ambiguous
|
|
GrpcCompletionReconciler.java:20 * case by re-issuing has converted an unknown into a duplicate.
|
|
GrpcCompletionReconciler.java:21 */
|
|
GrpcCompletionReconciler.java:22 public final class GrpcCompletionReconciler {
|
|
GrpcCompletionReconciler.java:23
|
|
GrpcCompletionReconciler.java:24 private final GrpcOperationStatusQuery statusQuery;
|
|
GrpcCompletionReconciler.java:25 private final List<PendingCase> pending = new ArrayList<>();
|
|
GrpcCompletionReconciler.java:26
|
|
|
|
# pending 이 나오는 줄 전부
|
|
GrpcCompletionReconciler.java:25 private final List<PendingCase> pending = new ArrayList<>();
|
|
GrpcCompletionReconciler.java:34 throw new IllegalArgumentException("a pending case identifies its operation");
|
|
GrpcCompletionReconciler.java:37 throw new IllegalArgumentException("a pending case says why it is pending");
|
|
GrpcCompletionReconciler.java:79 pending.add(new PendingCase(method, callerFingerprint, idempotencyKey, fromLedger.reason()));
|
|
GrpcCompletionReconciler.java:85 public List<PendingCase> pendingCases() {
|
|
GrpcCompletionReconciler.java:86 return List.copyOf(pending);
|
|
GrpcCompletionReconciler.java:91 pending.remove(resolved);
|
|
|
|
# 동기화 표시자 — 같은 검색을 같은 리프의 짝에도 건다
|
|
표시자 이 파일 짝 파일 리프 전체
|
|
synchronized 0 5 3
|
|
volatile 0 0 0
|
|
java.util.concurrent 0 0 10
|
|
ReentrantLock 0 0 0
|
|
CopyOnWrite 0 0 0
|
|
not thread 0 0 1
|
|
single-threaded 0 0 0
|
|
같은 검색을 리프 전체에 걸어 파일별로 :
|
|
GrpcContextBinder.java 2 건
|
|
GrpcCancellationCoordinator.java 5 건
|
|
GrpcOutcomeReplay.java 2 건
|
|
GrpcRetryBudget.java 1 건
|
|
GrpcCredentialRotationManager.java 1 건
|
|
GrpcSerializedStreamWriter.java 9 건
|
|
GrpcStreamAdmission.java 3 건
|
|
GrpcStreamSequence.java 1 건
|
|
리프 전체 62 파일
|
|
|
|
# pending.add 가 실행되는 조건
|
|
GrpcCompletionReconciler.java:62 GrpcCompletionResolution fromLedger =
|
|
GrpcCompletionReconciler.java:63 statusQuery.resolve(method, callerFingerprint, idempotencyKey);
|
|
GrpcCompletionReconciler.java:64 if (fromLedger.status() == GrpcOperationStatus.COMMITTED
|
|
GrpcCompletionReconciler.java:65 || fromLedger.status() == GrpcOperationStatus.FAILED_TERMINAL) {
|
|
GrpcCompletionReconciler.java:66 return fromLedger;
|
|
GrpcCompletionReconciler.java:67 }
|
|
GrpcCompletionReconciler.java:68 if (fromLedger.status() == GrpcOperationStatus.NOT_FOUND && businessProbe != null) {
|
|
GrpcCompletionReconciler.java:69 Optional<String> observed = businessProbe.apply(method);
|
|
GrpcCompletionReconciler.java:70 if (observed.isPresent()) {
|
|
GrpcCompletionReconciler.java:71 return GrpcCompletionResolution.of(
|
|
GrpcCompletionReconciler.java:72 GrpcOperationStatus.COMMITTED,
|
|
GrpcCompletionReconciler.java:73 observed,
|
|
GrpcCompletionReconciler.java:74 "no ledger claim, but the business resource shows the effect");
|
|
GrpcCompletionReconciler.java:75 }
|
|
GrpcCompletionReconciler.java:76 return fromLedger;
|
|
GrpcCompletionReconciler.java:77 }
|
|
GrpcCompletionReconciler.java:78 if (fromLedger.requiresReconciliation()) {
|
|
GrpcCompletionReconciler.java:79 pending.add(new PendingCase(method, callerFingerprint, idempotencyKey, fromLedger.reason()));
|
|
GrpcCompletionReconciler.java:80 }
|
|
GrpcCompletionReconciler.java:81 return fromLedger;
|
|
GrpcCompletionReconciler.java:82 }
|
|
GrpcCompletionResolution.java:46 }
|
|
GrpcCompletionResolution.java:47
|
|
GrpcCompletionResolution.java:48 /** Whether this must be handed to a reconciliation job rather than answered now. */
|
|
GrpcCompletionResolution.java:49 public boolean requiresReconciliation() {
|
|
GrpcCompletionResolution.java:50 return status == GrpcOperationStatus.UNKNOWN || status == GrpcOperationStatus.IN_PROGRESS;
|
|
GrpcCompletionResolution.java:51 }
|
|
GrpcCompletionResolution.java:52 }
|
|
|
|
# 같은 리프에서 같은 모양의 필드를 가진 클래스
|
|
deadline/GrpcCancellationCoordinator.java:27: private final Map<String, GrpcCancellableOperation> operations = new LinkedHashMap<>();
|
|
validation/GrpcTransportValidator.java:104: private final List<Rule<T>> rules = new ArrayList<>();
|
|
validation/GrpcTransportValidator.java:105: private final Set<String> seen = new LinkedHashSet<>();
|
|
idempotency/GrpcCompletionReconciler.java:25: private final List<PendingCase> pending = new ArrayList<>();
|
|
streaming/GrpcSerializedStreamWriter.java:31: private final Deque<GrpcStreamEnvelope<T>> queue = new ArrayDeque<>();
|
|
그중 GrpcCancellationCoordinator 의 공개 메서드 :
|
|
GrpcCancellationCoordinator.java:32 public GrpcCancellationCoordinator(GrpcCancellationToken token) {
|
|
GrpcCancellationCoordinator.java:45 public synchronized void register(GrpcCancellableOperation operation) {
|
|
GrpcCancellationCoordinator.java:69 public synchronized List<String> cancel(GrpcCancellationReason cancellationReason, Instant at) {
|
|
GrpcCancellationCoordinator.java:90 public synchronized void markCommitBoundaryCrossed() {
|
|
GrpcCancellationCoordinator.java:100 public synchronized boolean businessEffectAborted() {
|
|
GrpcCancellationCoordinator.java:105 public boolean cancelled() {
|
|
GrpcCancellationCoordinator.java:110 public synchronized Optional<GrpcCancellationReason> reason() {
|
|
GrpcCancellationCoordinator.java:119 public void requireMayStartSideEffect(String operationName) {
|
|
|
|
# 이 타입을 참조하는 자리 — 경로 제한 없이, 절단 없이
|
|
docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md
|
|
docs/adr/ADR-GRPC-003-three-axis-execution-evidence.md
|
|
docs/runbooks/grpc-platform-operations.md
|
|
src/grpc/grpc-policy/src/main/java/dev/caskeleton/grpc/idempotency/GrpcCompletionReconciler.java
|
|
src/grpc/grpc-policy/src/test/java/dev/caskeleton/grpc/idempotency/GrpcCompletionReconcilerTest.java
|
|
main · GrpcCompletionReconciler.java:22 public final class GrpcCompletionReconciler {
|
|
main · GrpcCompletionReconciler.java:43 public GrpcCompletionReconciler(GrpcOperationStatusQuery statusQuery) {
|
|
test · GrpcCompletionReconcilerTest.java:11 class GrpcCompletionReconcilerTest {
|
|
test · GrpcCompletionReconcilerTest.java:96 GrpcCompletionReconciler reconciler = new GrpcCompletionReconciler(query);
|
|
test · GrpcCompletionReconcilerTest.java:108 GrpcCompletionReconciler reconciler = new GrpcCompletionReconciler(query);
|
|
test · GrpcCompletionReconcilerTest.java:121 GrpcCompletionReconciler reconciler = new GrpcCompletionReconciler(query);
|
|
test · GrpcCompletionReconcilerTest.java:138 GrpcCompletionReconciler reconciler = new GrpcCompletionReconciler(query);
|
|
자기 파일 밖 main 참조 : 0 개
|
|
[대조] GrpcCancellationCoordinator : 2 개 (0 이 아니어야 정상)
|
|
|
|
# 운영 런북이 자기를 어떻게 규정하는가
|
|
grpc-platform-operations.md:1 # Runbook: gRPC platform operations
|
|
grpc-platform-operations.md:2
|
|
grpc-platform-operations.md:3 Scope: the `:grpc:*` family. All of it is build-only today — every leaf's `runtime_memberships` is
|
|
grpc-platform-operations.md:4 empty — so nothing here fires in production yet. It is written now because the states it covers are
|
|
grpc-platform-operations.md:5 the ones an on-call cannot work out from first principles at three in the morning, and shipping the
|
|
grpc-platform-operations.md:6 behaviour before the runbook means the first person to meet one is doing that.
|
|
grpc-platform-operations.md:7
|
|
grpc-platform-operations.md:8 Configuration lives under `ca-skeleton.grpc.platform.*` and is bound by `GrpcPlatformProperties`.
|
|
grpc-platform-operations.md:9 The platform does not start unless `ca-skeleton.grpc.platform.enabled=true`.
|
|
그 런북이 요구하는 스위치가 코드에 있는가 : 0 개 파일
|
|
독립 문서의 같은 서술 :
|
|
grpc-support-matrix.md:66 Not released. Every `:grpc:*` leaf is `runtime_memberships: []` in the module registry, so the
|
|
grpc-support-matrix.md:67 platform is build-only: it compiles, its lanes run, and no deployed artifact carries it.
|
|
|
|
# 그 런북이 이 클래스에 기대하는 동작
|
|
grpc-platform-operations.md:34 4. `NOT_FOUND` means the operation never started and is safe to re-issue. `FAILED_TERMINAL` means the
|
|
grpc-platform-operations.md:35 same.
|
|
grpc-platform-operations.md:36 5. `UNKNOWN` means the ledger could not be consulted. Nothing may be concluded. The case is queued by
|
|
grpc-platform-operations.md:37 `GrpcCompletionReconciler` and retried later.
|
|
grpc-platform-operations.md:38 6. If the method is not keyed, there is no ledger row. Resolve it against the business resource, or
|
|
grpc-platform-operations.md:39 escalate to the service owner. This is the case the keyed profile exists to avoid.
|
|
grpc-platform-operations.md:40
|
|
grpc-platform-operations.md:41 **Escalate when** the reconciler's pending list grows across passes. That means the ledger is
|
|
grpc-platform-operations.md:42 unreachable rather than slow.
|