Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/108-governance-config-reachability.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

169 lines
17 KiB
Plaintext

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- * <p>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- * <p>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: * <p>{@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