Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/204-inbound-grpc-probes.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

186 lines
10 KiB
Plaintext

adapter-inbound-grpc — bounded scope manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T08:18:53+00:00
=== OWNED FILES ===
df8de3ec8bfc53ef761bbeb9f4727ce148bd4f9b CLAUDE.md 81
0ae9758763c0413fa0e5dc67d8fa4d349e8469c2 README.md 94
a43fffb399ffd727426fe254687c2e4ab3d4c6c0 build.gradle 48
25d3aff7d9c48c628d0bb2ee28f61f5fab367789 gradle.lockfile 176
e808c723ef7d8d6f963d53b06960e93364c1f0ae src/main/java/dev/caskeleton/adapter/inbound/grpc/ApiErrorException.java 38
d167e2f11d20ca2b5a3e8c4b5949179f036988f8 src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcAuthenticationInterceptor.java 42
b6d4b8bcbbd77aed5ad9354cdf77e5e97e0c2f4c src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcAuthenticationPolicy.java 17
559d1f500e9e4dbae49d9b94a560e50c7a3aad82 src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptor.java 131
ae2081a132978493283b7f4add97381b92f28c8d src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerConfig.java 57
0a306682112e2d267c2e242b4e875b97176410b3 src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerProperties.java 114
918516bbbbe562f725b7bf48873c13853bab0c7e src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerRunner.java 146
07efccf35fce03b90ca73a77eacc8d396a0dc660 src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcStatusMapper.java 57
e69571c2d3d605b9aec6269050df3b979612d41c src/test/java/dev/caskeleton/adapter/inbound/grpc/ApiErrorExceptionTest.java 25
a38d902a09cae66b28df3841993b54d843a97463 src/test/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptorTest.java 132
343da8d0ea529ab4d8d70bab97173d32400ae31f src/test/java/dev/caskeleton/adapter/inbound/grpc/GrpcP1BoundaryWireTest.java 355
7a65db1d5d2d57a0a916ae8879d0805905f962b5 src/test/java/dev/caskeleton/adapter/inbound/grpc/GrpcSafeActivationTest.java 111
4c97503068ad9e3a1345c287f9d661a1bfc0ed23 src/test/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerRunnerBootTest.java 104
c32c0f1e591a4b0694014fcfa9f70cdb281e77a5 src/test/java/dev/caskeleton/adapter/inbound/grpc/GrpcStatusMapperTest.java 55
file_count=18
=== 8.1 도달성: feature 서비스와 정책 구현이 존재하는가 ===
$ grep -rn 'implements BindableService\|BindableService\|GrpcAuthenticationPolicy' --include=*.java . | grep -v 'adapter/inbound/grpc/' | wc -l
0
exit=0
$ grep -rn 'ca-skeleton.grpc' --include=*.yml --include=*.yaml . | wc -l
0
exit=0
$ grep -rn 'inbound.grpc' --include=*.java app-bootstrap/src/main sample-portfolio/src/main
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java:71: "dev.caskeleton.adapter.inbound.grpc",
exit=0
=== 8.2 조건 형제 비교: cause chain 순회 관용구 두 가지 (저장소 전체) ===
$ grep -rn 'getCause() == current' --include=*.java adapter | sed 's|adapter/||'
inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptor.java:124: if (current.getCause() == current) {
inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/advanced/mvc/MvcDisconnectDetector.java:61: if (current.getCause() == current) {
inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/advanced/webflux/WebFluxDisconnectDetector.java:66: if (current.getCause() == current) {
outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/TransactionRetryClassifier.java:19: if (current.getCause() == current) {
exit=0
$ grep -rn 'depth < \|depth <= \|MAXIMUM_DEPTH' --include=*.java adapter | grep -i 'cause\|depth <' | grep -v test | sed 's|adapter/||' | head
inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/JwtDecoderConfig.java:63: for (int depth = 0; current != null && depth < 32; depth++) {
inbound/graphql/src/main/java/dev/caskeleton/adapter/inbound/graphql/cost/GraphQlDocumentShape.java:28: if (depth < 0
inbound/graphql/src/main/java/dev/caskeleton/adapter/inbound/graphql/observation/GraphQlRequestObservationConvention.java:169: if (depth <= 3) {
inbound/graphql/src/main/java/dev/caskeleton/adapter/inbound/graphql/observation/GraphQlRequestObservationConvention.java:172: if (depth <= 6) {
inbound/graphql/src/main/java/dev/caskeleton/adapter/inbound/graphql/observation/GraphQlRequestObservationConvention.java:175: if (depth <= 12) {
outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/imperative/bulk/SpringDataBulkFailureExtractor.java:30: for (int depth = 0; current != null && depth < MAXIMUM_DEPTH; depth++) {
outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/MongoFailureExtractor.java:41: for (int depth = 0; current != null && depth < MAXIMUM_DEPTH; depth++) {
outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/NotificationSchedulerWorker.java:124: for (int depth = 0; current != null && depth < 8; depth++) {
outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/JdkNotificationHttpGateway.java:98: for (int depth = 0; cause != null && depth < 10; depth++, cause = cause.getCause()) {
exit=0
$ sed -n '93,99p' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/http/JdkNotificationHttpGateway.java
private static boolean bodyWasLikelyCommitted(IOException failure) {
// Depth-bounded rather than cycle-detecting: a cause chain can be circular (two exceptions
// each initCause'd to the other), and an unbounded walk over one hangs the dispatch thread.
// Ten is far deeper than any real transport wrapping.
Throwable cause = failure;
for (int depth = 0; cause != null && depth < 10; depth++, cause = cause.getCause()) {
if (cause instanceof java.net.ConnectException
exit=0
$ sed -n '118,130p' adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptor.java
private static ApiErrorCode errorCodeOf(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
if (current instanceof ApiErrorCarrier carrier) {
return carrier.errorCode();
}
if (current.getCause() == current) {
break;
}
current = current.getCause();
}
return null;
}
exit=0
=== 8.3 에러 계약: 모든 non-OK close 가 재작성되는가 ===
$ sed -n '90,116p' adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptor.java
private <REQT, RESPT> ServerCall<REQT, RESPT> sanitizingCall(
ServerCall<REQT, RESPT> delegate, AtomicBoolean closed) {
return new SimpleForwardingServerCall<>(delegate) {
@Override
public void close(Status status, Metadata trailers) {
if (!closed.compareAndSet(false, true)) {
return;
}
if (status.isOk()) {
super.close(status, trailers);
return;
}
ApiErrorCode code = errorCodeOf(status.getCause());
if (code == null) {
code = OperationalError.INTERNAL_ERROR;
}
super.close(
statusMapper.toStatus(code.category()).withDescription(code.code()),
statusMapper.trailersFor(code));
}
};
}
private static void closeWithError(ServerCall<?, ?> call, RuntimeException exception) {
call.close(Status.fromThrowable(exception).withCause(exception), new Metadata());
}
exit=0
$ grep -n 'closeWithError(sanitizingCall' adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcExceptionHandlingInterceptor.java
48: closeWithError(sanitizingCall, e);
84: closeWithError(sanitizingCall, e);
exit=0
=== 8.4 설정 게이트와 검증 ===
$ grep -n 'ConditionalOnProperty' -A 5 adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerConfig.java | head -8
6:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
7-import org.springframework.boot.context.properties.EnableConfigurationProperties;
8-import org.springframework.context.annotation.Bean;
9-import org.springframework.context.annotation.Configuration;
10-
11-/**
--
18:@ConditionalOnProperty(
exit=0
$ grep -n 'AssertTrue' -A 4 adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerProperties.java
3:import jakarta.validation.constraints.AssertTrue;
4-import jakarta.validation.constraints.Max;
5-import jakarta.validation.constraints.Min;
6-import jakarta.validation.constraints.NotBlank;
7-import java.net.InetAddress;
--
90: @AssertTrue(
91- message = "insecure gRPC requires allow-insecure-local=true and a loopback bind address")
92- public boolean isInsecureLocalConfigurationValid() {
93- return !enabled || (allowInsecureLocal && isLoopbackBindAddress());
94- }
exit=0
$ grep -n 'ignoreUnknownFields\|@Validated' adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerProperties.java
17:@ConfigurationProperties(prefix = "ca-skeleton.grpc", ignoreUnknownFields = false)
18:@Validated
exit=0
$ sed -n '69,73p' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
"dev.caskeleton.bootstrap.settings",
"dev.caskeleton.bootstrap.tracing",
"dev.caskeleton.adapter.inbound.grpc",
"dev.caskeleton.adapter.inbound.web",
"dev.caskeleton.adapter.inbound.websocket",
exit=0
$ sed -n '73,77p' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
"dev.caskeleton.adapter.inbound.websocket",
"dev.caskeleton.adapter.outbound.cache.redis",
"dev.caskeleton.adapter.outbound.fileserver",
"dev.caskeleton.adapter.outbound.objectstorage",
"dev.caskeleton.application",
exit=0
=== 8.4b health 상태를 언제 SERVING 으로 두는가 ===
$ grep -n 'setStatus\|builder.build().start()' adapter/inbound/grpc/src/main/java/dev/caskeleton/adapter/inbound/grpc/GrpcServerRunner.java
86: healthStatusManager.setStatus(
94: server = builder.build().start();
exit=0
=== 실행 검증 ===
$ tail -8 /tmp/grpc_test.log
> Task :adapter:inbound:grpc:grpcTransportQualificationTestEvidence
grpcTransportQualificationTest: 15 tests, 0 skipped
BUILD SUCCESSFUL in 12s
17 actionable tasks: 6 executed, 11 up-to-date
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.0.0/userguide/configuration_cache_enabling.html
GRADLE_EXIT=0
exit=0