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

