Sub-scope 01 (notification) governance + 루트 + platform/autoconfigure — manifest and probes revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5 generatedAt=2026-08-30T06:37:36+00:00 === OWNED FILES === 660f77ed47c90d8da6c66022c2b5d66e5a706942 CLAUDE.md 47 4fbe71c26464d9e2a3dfa420876338c9f5cdf89f README.md 78 517a950162f15d2bf8c4f55bb0323859ab4db99e build.gradle 54 b30c50f91495749ea97560903b4662d887a5e543 gradle.lockfile 167 23a2136015f44e177bdab5a477f7f498ec5d8ebb src/main/java/dev/caskeleton/adapter/outbound/notification/NotificationConfig.java 40 416c2cb2d052d3a834e03dfdd8fb996224aa9c37 src/main/java/dev/caskeleton/adapter/outbound/notification/NotificationRoutesSettings.java 28 928df24c90847cd9db99a489e3b7149656ec207a src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/AssembledProvider.java 63 c114dfcd96177d602589eff07dc731a88eec3fa2 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformAutoConfiguration.java 118 182c790f192069fc98caeb269e460b659ff5aca1 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformMode.java 24 fb23f3b75a076d06de387da7ee9f2c16f977d219 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformSettings.java 190 eba7ccdc3d555b0b7a2ad72c05797ca638d2fd95 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java 239 4ce10c47f9c74c5e426d92093b010b69ced464ed src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java 98 8870a02465769a3a8c4987c8e0e935ed0e614984 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSmtpProviderConfig.java 211 c6393dabcd7bd61abadedc6b2c9387d19554ed36 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSmtpSettings.java 60 aac01be3780329c32f76e3b857f7e8de3c3b5501 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/ProviderRuntimeAssembler.java 35 c75b240b6b23bff058b58df5ddf13fa4fed95d17 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/ProviderType.java 87 d73384a60e4797e54127f12e5a018d58bf90350d src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/SmtpProviderRuntimeAssembler.java 164 6f630e9813d2ed3db38bc429931d1b0d74cb3bf9 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformSettingsTest.java 166 f9fac342d9de203c1777315cc05b1cd55890a6c0 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssemblyTest.java 267 5f8df14bba9f88525e41d483ce8444ccaf8df4fc src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationReleaseGateTest.java 63 92052e37ceccc1af9f5f72b3f9d69e94cbff3928 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirementsTest.java 173 1831898eba674f57a4be0f318ba667421fbcc4a8 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/SmtpProviderRuntimeAssemblerTest.java 133 === 8.1 활성화 게이트 전수 === $ grep -rn 'ConditionalOn' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/*.java adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/*.java | sed 's|.*/notification/||' platform/autoconfigure/NotificationPlatformAutoConfiguration.java:19:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; platform/autoconfigure/NotificationPlatformAutoConfiguration.java:20:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; platform/autoconfigure/NotificationPlatformAutoConfiguration.java:34:@ConditionalOnProperty( platform/autoconfigure/NotificationPlatformAutoConfiguration.java:42: @ConditionalOnMissingBean platform/autoconfigure/NotificationPlatformAutoConfiguration.java:49: @ConditionalOnMissingBean platform/autoconfigure/NotificationPlatformAutoConfiguration.java:56: @ConditionalOnMissingBean platform/autoconfigure/NotificationPlatformAutoConfiguration.java:70: @ConditionalOnMissingBean platform/autoconfigure/NotificationPlatformAutoConfiguration.java:88: *

{@code @ConditionalOnBean(SecretMaterialProvider.class)} used to guard this, to express platform/autoconfigure/NotificationPlatformAutoConfiguration.java:102: @ConditionalOnMissingBean platform/autoconfigure/NotificationPlatformAutoConfiguration.java:114: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:17:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; platform/autoconfigure/NotificationSmtpProviderConfig.java:18:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; platform/autoconfigure/NotificationSmtpProviderConfig.java:58:@ConditionalOnProperty(name = "spring.mail.host") platform/autoconfigure/NotificationSmtpProviderConfig.java:64: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:78: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:88: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:102: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:113: @ConditionalOnMissingBean platform/autoconfigure/NotificationSmtpProviderConfig.java:128: @ConditionalOnMissingBean(name = "notificationSmtpExecutor") platform/autoconfigure/NotificationSmtpProviderConfig.java:162: @ConditionalOnMissingBean(name = "smtpProviderRuntimeAssembler") exit=0 $ git ls-files 'adapter/outbound/notification/src/main/resources' 2>/dev/null | head; echo 'main resources: none tracked' main resources: none tracked exit=0 $ git grep -rn 'NotificationPlatformAutoConfiguration\|NotificationConfig' -- 'app-bootstrap/src/main' | head app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:3:import dev.caskeleton.adapter.outbound.notification.NotificationConfig; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:6:import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformAutoConfiguration; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:27: *

{@link NotificationPlatformAutoConfiguration} is named here for a blunter reason: nothing app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:47: NotificationPlatformAutoConfiguration.class, app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:51: NotificationConfig.class, app-bootstrap/src/main/resources/application.yml:959: # Add a provider = new files only (NotificationConfig); select it here. exit=0 === 8.2 INGEST_ONLY 가 실제로 readiness 를 내리는가 === $ git grep -rn 'INGEST_ONLY' -- 'adapter/outbound/notification/src/main' 'app-bootstrap/src/main' | head -20 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformMode.java:23: INGEST_ONLY adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:35: *

  • zero providers without {@link NotificationPlatformMode#INGEST_ONLY}, because a platform adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:141: routes.isEmpty() ? NotificationPlatformMode.INGEST_ONLY : NotificationPlatformMode.SERVING); adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:183: if (routes.isEmpty() && mode != NotificationPlatformMode.INGEST_ONLY) { adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:187: + " declare ca-skeleton.notification.platform.mode=INGEST_ONLY so readiness reports" adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java:83: * The purposes every mode needs, including {@code INGEST_ONLY}. app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:128: * Everything that starts a thread, and therefore everything {@code INGEST_ONLY} must not have. app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:132: * method — so a deployment in {@code INGEST_ONLY}, the mode whose whole purpose is to accept and app-bootstrap/src/main/resources/application.yml:487: # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY exit=0 === 8.3 실행 probe: dependencyPolicy 와 API 표면 === $ ./gradlew :adapter:outbound:notification:verifyDependencyPolicy → BUILD SUCCESSFUL (EXIT=0) $ ./gradlew verifyNotificationApiSurface → OK — 586 public types, unchanged (EXIT=0) $ wc -l ../docs/notification/api-surface-snapshot.txt 589 ../docs/notification/api-surface-snapshot.txt exit=0 === 8.4 비밀 요구가 능력에 따라 좁아지는가 === $ grep -n 'ALWAYS\|required.add' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java 36: Set required = EnumSet.copyOf(ALWAYS); 42: required.add(SecretPurpose.CALLBACK_SIGNING); 43: required.add(SecretPurpose.CALLBACK_FINGERPRINT_HMAC); 54: required.add(SecretPurpose.PROVIDER_CREDENTIAL); 57: required.add(SecretPurpose.VAPID_SIGNING); 63: required.add(SecretPurpose.CALLBACK_SIGNING); 92: private static final Set ALWAYS = exit=0 $ git grep -h ' void [a-z]' -- 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure' | sed 's/^ *//' void productionWebPushWithoutVapidKeyFailsStartup() { void apnsWithoutTopicFailsStartup() { void aCallbackCapableProviderRequiresASigningSecret() { void aDisabledProfileIsNotValidated() { void ambiguousFallbackCannotBeEnabled() { void anUnboundedClaimBatchIsRefused() { void aLeaseShorterThanThePollIntervalIsRefused() { void defaultsAreUsableAndConservative() { void aConfiguredProfileBecomesARuntimeAndARoute() { void anUnknownProviderTypeFailsTheBoot() { void aFamilyWithNoAssemblerIsRefused() { void twoProfilesOnOneChannelNeedAPrimary() { void aMarkedPrimaryResolvesTheAmbiguity() { void twoPrimariesAreRefused() { void aDisabledProfileAssemblesNothing() { void anEnabledPlatformWithNoProviderRefusesToStart() { void ingestOnlyIsADeclaration() { void twoAssemblersForOneFamilyAreRefused() { void requiredDocumentationAndAdrsExist() { void supportMatrixDoesNotClaimGuaranteedDelivery() throws IOException { void theEvidenceDocumentStatesTheForbiddenPromotions() throws IOException { void theAcceptPathKeysAreAlwaysRequired() { void anSmtpOnlyPlatformIsNotAskedForAProviderCredential() { void aDisabledProfileDoesNotDemandItsKeys() { void anEnabledWebPushProfileDemandsItsKeys() { void callbacksOffDoNotDemandTheCallbackKeys() { void callbacksOnDemandBothCallbackKeys() { void aProfileNamingASigningRefDemandsTheSigningPurpose() { void theAssemblerServesSmtpOnEmail() { void aConfiguredProfileAssemblesIntoARuntime() { void smtpClaimsNothingItCannotDo() { void thePerProfileBoundsComeFromTheProfile() { exit=0