Sub-scope 01 negative-space probes (public surface / conditional sibling / duplicate mechanism) revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5 executedAt=2026-08-29T14:36:26+00:00 cwd=/shared/codebase/clean-architecture-backend-template/src observation boundary: tracked Java/YAML/Gradle sources at this revision; reflection, generated registration and consumers outside this repository are not covered. === 8.1 public surface reachability: config package types outside the owning leaf === $ git grep -n -w PersistenceJpaConfig -- . ":!adapter/outbound/persistence-jpa/**" sample-portfolio/README.md:404: adapter-persistence `PersistenceJpaConfig` 등)를 자동으로 발견·배선합니다. `app-bootstrap` 에만 있어 sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/persistence/SamplePostgreSqlPersistenceConfig.java:3:import dev.caskeleton.adapter.outbound.persistence.config.PersistenceJpaConfig; sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/persistence/SamplePostgreSqlPersistenceConfig.java:25:@Import(PersistenceJpaConfig.class) exit=0 $ git grep -n -w JpaAdapterComponentsConfig -- . ":!adapter/outbound/persistence-jpa/**" app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:3:import dev.caskeleton.adapter.outbound.persistence.config.JpaAdapterComponentsConfig; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:62: JpaAdapterComponentsConfig.class, exit=0 $ git grep -n -w PersistenceVendorSettings -- . ":!adapter/outbound/persistence-jpa/**" app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:4:import dev.caskeleton.adapter.outbound.persistence.config.PersistenceVendorSettings; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:86: * PersistenceVendorSettings}, because that type is not a registered bean here: the composition app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:112: environment.getProperty(PersistenceVendorSettings.VENDOR_PROPERTY, "postgresql").trim(); app-bootstrap/src/main/resources/application.yml:463: # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup. exit=0 === 8.2 conditional sibling comparison: leaf @ConfigurationProperties types and their enablement === $ git grep -n "@ConfigurationProperties(" -- adapter/outbound/persistence-jpa/src/main adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/config/PersistenceVendorSettings.java:18:@ConfigurationProperties(prefix = PersistenceVendorSettings.PREFIX) adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/lock/LockSettings.java:14:@ConfigurationProperties(prefix = "ca-skeleton.lock") adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/JpaTransactionSettings.java:10:@ConfigurationProperties(prefix = "ca-skeleton.jpa.transaction") exit=0 $ git grep -n "@EnableConfigurationProperties" -- adapter/outbound/persistence-jpa/src adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/lock/DistributedLockPersistenceConfig.java:24:@EnableConfigurationProperties(LockSettings.class) adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/JpaTransactionConfig.java:23:@EnableConfigurationProperties(JpaTransactionSettings.class) adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/config/PersistenceVendorSelectionTest.java:92: @EnableConfigurationProperties(PersistenceVendorSettings.class) exit=0 $ git grep -n -A22 "@ConfigurationPropertiesScan" -- app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java:21: * auto-configured packages with a regex filter; {@code @ConfigurationPropertiesScan} has no {@code app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-22- * excludeFilters} attribute, so it names the packages it does scan instead. The asymmetry that app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-23- * existed before — beans gated, settings not — is why a notification settings object bound itself app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-24- * in a deployment whose notification master was off. A capability whose beans are gated but whose app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-25- * settings still bind is gated only where somebody remembered to gate it. app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-26- * app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-27- *
The five optional adapters are absent from the list below on purpose. Each one\'s settings are app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-28- * registered by its capability root through {@code @EnableConfigurationProperties}, which is what app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-29- * ties binding to the master switch. Adding a package back here would restore the binding and app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-30- * silently undo the gate. app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-31- * app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-32- *
The third exclusion is what makes an optional capability optional. Configuration classes under app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-33- * {@code bootstrap.autoconfigure} are reachable only through their own auto-configuration entry, app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-34- * which carries the capability's master switch; leaving them inside the broad scan would let the app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-35- * component scanner discover each child configuration on its own and assemble the capability app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-36- * whatever that switch said. Registering them as auto-configuration instead makes "off" a app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-37- * structural fact rather than a condition every future bean has to remember to repeat. app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-38- */ app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-39-@SpringBootConfiguration app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-40-@EnableAutoConfiguration app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-41-@ComponentScan( app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-42- basePackages = { app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-43- "dev.caskeleton.bootstrap", -- app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java:58:@ConfigurationPropertiesScan( app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-59- basePackages = { app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-60- "dev.caskeleton.bootstrap.async", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-61- "dev.caskeleton.bootstrap.autoconfigure.fileserver", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-62- "dev.caskeleton.bootstrap.autoconfigure.httpclient", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-63- "dev.caskeleton.bootstrap.concurrency", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-64- "dev.caskeleton.bootstrap.idempotency", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-65- "dev.caskeleton.bootstrap.outbox", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-66- "dev.caskeleton.bootstrap.redis", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-67- "dev.caskeleton.bootstrap.runtime", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-68- "dev.caskeleton.bootstrap.security", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-69- "dev.caskeleton.bootstrap.settings", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-70- "dev.caskeleton.bootstrap.tracing", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-71- "dev.caskeleton.adapter.inbound.grpc", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-72- "dev.caskeleton.adapter.inbound.web", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-73- "dev.caskeleton.adapter.inbound.websocket", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-74- "dev.caskeleton.adapter.outbound.cache.redis", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-75- "dev.caskeleton.adapter.outbound.fileserver", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-76- "dev.caskeleton.adapter.outbound.objectstorage", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-77- "dev.caskeleton.application", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-78- "dev.caskeleton.domain", app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-79- "dev.caskeleton.shared" app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java-80- }) exit=0 === 8.2b vendor selector guards outside the leaf === $ git grep -n "ca-skeleton.persistence.vendor" -- app-bootstrap sample-portfolio app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java:32: * packages instead. They are mutually exclusive on {@code ca-skeleton.persistence.vendor}, so app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java:11: *
{@code application-prod.yml} already pins {@code ca-skeleton.persistence.vendor=postgresql}, app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java:34: static final String VENDOR_KEY = "ca-skeleton.persistence.vendor"; app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/LocalJpaVendorParityTest.java:42: "ca-skeleton.persistence.vendor", app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/LocalJpaVendorParityTest.java:71: assertThat(local.getProperty("ca-skeleton.persistence.vendor")).isEqualTo("postgresql"); exit=0 === 8.3 duplicate mechanism sweep: entity/repository/component scan declarations === $ git grep -n "@EntityScan\|@EnableJpaRepositories\|@ComponentScan(" -- "*.java" adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/config/JpaAdapterComponentsConfig.java:59:@ComponentScan( adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/config/PersistenceJpaConfig.java:38:@EntityScan( adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/config/PersistenceJpaConfig.java:61:@EnableJpaRepositories( adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/FileserverJpaPersistenceConfig.java:33: * had nothing to decide about. {@code @EnableJpaRepositories} does not cover them — it registers adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/FileserverJpaPersistenceConfig.java:38:@EntityScan(basePackages = "dev.caskeleton.adapter.outbound.persistence.fileserver") adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/FileserverJpaPersistenceConfig.java:39:@EnableJpaRepositories(basePackages = "dev.caskeleton.adapter.outbound.persistence.fileserver") adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/FileserverJpaPersistenceConfig.java:40:@ComponentScan(basePackages = "dev.caskeleton.adapter.outbound.persistence.fileserver") adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/NotificationJpaPersistenceConfig.java:26:@EntityScan(basePackages = "dev.caskeleton.adapter.outbound.persistence.notification") adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/NotificationJpaPersistenceConfig.java:27:@EnableJpaRepositories(basePackages = "dev.caskeleton.adapter.outbound.persistence.notification") adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/readiness/FileserverJpaTestContext.java:68: @EnableJpaRepositories( adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/readiness/FileserverJpaTestContext.java:70: @ComponentScan( app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java:41:@ComponentScan( app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/ShippedCompositionHarness.java:20: * {@code @EnableJpaRepositories} and the context dies with a {@code app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java:96: @ComponentScan("dev.caskeleton.adapter.outbound.persistence.postgresql.idempotency") app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java:472: @ComponentScan( app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java:477: // No @EntityScan or @EnableJpaRepositories here any more. FileserverJpaPersistenceConfig owns app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java:229: @EnableJpaRepositories(basePackageClasses = OutboxEventJpaRepository.class) sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/SamplePortfolioApplication.java:27:@ComponentScan( sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/outbound/persistence/config/JpaConfig.java:14:@EntityScan(basePackages = "dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity") sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/outbound/persistence/config/JpaConfig.java:15:@EnableJpaRepositories( exit=0 === 8.2c CaSkeletonApplication @ConfigurationPropertiesScan basePackages (exact annotation block) === $ sed -n 58,80p app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java @ConfigurationPropertiesScan( basePackages = { "dev.caskeleton.bootstrap.async", "dev.caskeleton.bootstrap.autoconfigure.fileserver", "dev.caskeleton.bootstrap.autoconfigure.httpclient", "dev.caskeleton.bootstrap.concurrency", "dev.caskeleton.bootstrap.idempotency", "dev.caskeleton.bootstrap.outbox", "dev.caskeleton.bootstrap.redis", "dev.caskeleton.bootstrap.runtime", "dev.caskeleton.bootstrap.security", "dev.caskeleton.bootstrap.settings", "dev.caskeleton.bootstrap.tracing", "dev.caskeleton.adapter.inbound.grpc", "dev.caskeleton.adapter.inbound.web", "dev.caskeleton.adapter.inbound.websocket", "dev.caskeleton.adapter.outbound.cache.redis", "dev.caskeleton.adapter.outbound.fileserver", "dev.caskeleton.adapter.outbound.objectstorage", "dev.caskeleton.application", "dev.caskeleton.domain", "dev.caskeleton.shared" }) exit=0 === 8.2d does any scanned base package cover dev.caskeleton.adapter.outbound.persistence? === $ sed -n 58,80p ... | grep -c outbound.persistence 0 === 8.2e application.yml vendor comment === $ sed -n 458,468p app-bootstrap/src/main/resources/application.yml # domain (out of scope for this branch); code defaults in LockSettings mirror these. lock: wait-time: 3s lease-ttl: 30s # Which RDBMS vendor composition the persistence adapter registers: postgresql | h2. Bound to # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup. # # A property rather than a profile check, because the vendor belongs to the datastore and not to # the environment that happens to use it. The profiles then state their choice: # application-local.yml selects h2, application-dev.yml and application-prod.yml select # postgresql, and prod additionally refuses h2 through PersistenceVendorProdSafetyValidator. exit=0