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 ServerCall sanitizingCall( ServerCall 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