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>
205 lines
14 KiB
Plaintext
205 lines
14 KiB
Plaintext
Sub-scope 01 (inbound-web) governance + config/core/contract/moduleboundary/autoconfigure
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T07:10:55+00:00
|
|
|
|
=== OWNED FILES ===
|
|
90e57818b114cc3277e0c58e0d4eccb976afb2cd CLAUDE.md 201
|
|
f4f0854d4adc874917c733c5aff42c679325481b README.md 463
|
|
ee75f0c51d4b8625503fc0ff7f49ad8c1fbb607d build.gradle 258
|
|
6e6bc29750eff1949506a5dc1df8c32743380f30 gradle.lockfile 277
|
|
0024fa7a3cdf02c9783b6740a8c0ff1b734dbe8c src/main/java/dev/caskeleton/config/JacksonNullableConfig.java 105
|
|
41a7edb09e2a0040bfe1d5cf6fd2e38592a7c232 src/main/java/dev/caskeleton/config/OpenApiContractConfig.java 37
|
|
84920ababeb83cdfbe254745c45ea264afcfb24a src/main/java/dev/caskeleton/config/PresentationWebConfig.java 25
|
|
87d4837e94c1c7dc3e9d79a7b18f4486e9710fb5 src/main/java/dev/caskeleton/contract/WebEnumValue.java 32
|
|
ff70b7a9ac01833458721918d4f2cb76ea59d2cf src/main/java/dev/caskeleton/contract/WebWireType.java 44
|
|
3458946bc981ed7e4a0c17aa702fc9e9da4d255e src/main/java/dev/caskeleton/contract/WebWireTypeManifest.java 81
|
|
79e01fc9aa6cc24542917620fb94ad15872512e2 src/main/java/dev/caskeleton/contract/WireTypeRule.java 24
|
|
63ef452cc59c081d2450087163612271d2e264b7 src/main/java/dev/caskeleton/controller/HealthcheckController.java 18
|
|
742d75642c00db97eeb12d27641e7d60f072282c src/main/java/dev/caskeleton/core/ActorContext.java 52
|
|
43f77605463a7671a04e99ac8af02d2dfeebea04 src/main/java/dev/caskeleton/core/ApiMajorVersion.java 32
|
|
81061fc734055971a2632e6136c90b8ae9b8ce2f src/main/java/dev/caskeleton/core/ExternalRequestContext.java 65
|
|
742ea591147ab56be4ccd71bbd485f9899b481eb src/main/java/dev/caskeleton/core/TenantContext.java 50
|
|
b19770b60e88badbde6b933527c48a05095eb55f src/main/java/dev/caskeleton/core/WebOperationName.java 36
|
|
e80feb5edc21b177414826d4b784fdc01b97f33d src/main/java/dev/caskeleton/core/WebRequestContext.java 78
|
|
632c41067197433eed616cd43894e570a3396de8 src/main/java/dev/caskeleton/core/WebRequestId.java 32
|
|
be49ad426c13326382d041765c837fed0aea09c5 src/main/java/dev/caskeleton/core/WebRouteId.java 31
|
|
1ba81b37055de78dbc46200b860854c97b007bcf src/main/java/dev/caskeleton/core/WebTraceId.java 31
|
|
70b8727cc42f5d5b27c8e2025df7995f81783abc src/main/java/dev/caskeleton/moduleboundary/WebModuleBoundary.java 108
|
|
6cfcb639fc0174b53540bd7129e90061464a040d src/main/java/dev/caskeleton/moduleboundary/WebModulePurity.java 21
|
|
40e3687b86a848bab42ed5aec3b9f645e80243ce src/main/java/dev/caskeleton/moduleboundary/WebStableModule.java 539
|
|
391d8bf972c51976c059528da4aff25a599934bf src/main/java/dev/caskeleton/mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java 168
|
|
e7a62fb15a3de1fd58573476ec56846e2a5c74a2 src/main/java/dev/caskeleton/mvc/autoconfigure/WebMvcPlatformSettings.java 37
|
|
87cb1ad431e8536ec28beaaf6de891deff47b311 src/main/java/dev/caskeleton/settings/CorsSettings.java 54
|
|
b4becc39e34a72771f42e9a0ad4598eeb97e83fd src/main/java/dev/caskeleton/settings/PresentationSettings.java 27
|
|
6e3a63624607b3c42a0ad94b92e73fdb8d87fded src/main/java/dev/caskeleton/settings/SecuritySettings.java 110
|
|
fca7bd62588b76d0a7bb909ef28eb750795fa5fb src/main/java/dev/caskeleton/webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java 131
|
|
5a2fc1b08a347bc3e2e06363382319c2a2e49b39 src/main/java/dev/caskeleton/webflux/autoconfigure/WebFluxPlatformSettings.java 42
|
|
447164ed3148b30fd23b563db6fc17bd0ed0d29c src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 2
|
|
f5d40ef0725211ae1812234493bce907d5bc44ce src/main/resources/META-INF/web/problem-catalog.yaml 99
|
|
4b59e1d21ff12d2306cace7587a9de8ff50252cc src/main/resources/META-INF/web/wire-type-manifest.yaml 39
|
|
a889295a34227c510c1a8aba307e8c50ca159e0f src/test/java/dev/caskeleton/config/JacksonNullableConfigTest.java 40
|
|
8b137891791fe96927ad78e64b0aad7bded08bdc src/test/java/dev/caskeleton/contract/.gitkeep 1
|
|
649cc3fe219ff2fa7e57f96edaa0626879a0f5d4 src/test/java/dev/caskeleton/contract/WebWireTypeManifestTest.java 91
|
|
06441218c2f0de61cf9851cdf595ec6c7ef2880c src/test/java/dev/caskeleton/core/WebIdentifierTest.java 78
|
|
beacf79d70a763159e9b9773eef323d82b532cb8 src/test/java/dev/caskeleton/core/WebRequestContextTest.java 112
|
|
31680e537e6be10081bf3624caa9611db5d47757 src/test/java/dev/caskeleton/fixtures/badcontroller/FixtureTransactional.java 20
|
|
e25217a977f625fdb0d271b119bd83ef00b2c0d6 src/test/java/dev/caskeleton/fixtures/badcontroller/RepositoryHoldingController.java 30
|
|
715702f50bacea7318099fa63786bff4dce24762 src/test/java/dev/caskeleton/moduleboundary/WebBuildModel.java 263
|
|
e373861528986b059c4ba8b70236097a76294fad src/test/java/dev/caskeleton/moduleboundary/WebModuleBoundaryTest.java 131
|
|
e6ff0865e0c0a1c93483c784f068cf137e19bb30 src/test/java/dev/caskeleton/moduleboundary/WebSourceGraph.java 30
|
|
8b137891791fe96927ad78e64b0aad7bded08bdc src/test/java/dev/caskeleton/mvc/.gitkeep 1
|
|
07b801351e0eb27d1326aeba0ee89f5c30d357a2 src/test/java/dev/caskeleton/mvc/autoconfigure/WebMvcPlatformAutoConfigurationTest.java 112
|
|
82564f4441e1d4870e743ba3591732242b49ebef src/test/java/dev/caskeleton/settings/CorsSettingsTest.java 142
|
|
2d01c28417abd053f8f1d3c1a7edd24341cecb0b src/test/java/dev/caskeleton/settings/PresentationSettingsTest.java 57
|
|
0b10a1b13d97b9f36f60d3dd924c1ae79a89ec37 src/test/java/dev/caskeleton/settings/SecuritySettingsTest.java 95
|
|
10e95f134229b47bc82d0c39ed8be0c803456a7e src/test/java/dev/caskeleton/webflux/autoconfigure/WebFluxPlatformAutoConfigurationTest.java 112
|
|
e5fcde1a925be6e7a76bae96ad2179914e0e1b73 src/test/resources/application-web-contract.yaml 19
|
|
file_count=51
|
|
|
|
=== 8.1 도달성: 5개 커스텀 레인이 실제로 실행되는가 ===
|
|
$ grep -rn 'webCrossStackParityTest|webFluxContractTest|webJettyCompatTest|webNginxProxyTest|webAdvancedTest' --include=*.gradle --include=*.groovy . | grep -v 'adapter/inbound/web/build.gradle' | wc -l
|
|
0
|
|
exit=0
|
|
|
|
$ (cd /shared/codebase/clean-architecture-backend-template && grep -rn 'web.*Test' .github/workflows/web-pr.yml .github/workflows/web-nightly.yml .github/workflows/web-release.yml .github/workflows/web-advanced-nightly.yml .github/workflows/web-advanced-release.yml | grep ':adapter:inbound:web:')
|
|
.github/workflows/web-pr.yml:77: :adapter:inbound:web:webCrossStackParityTest
|
|
.github/workflows/web-pr.yml:112: :adapter:inbound:web:webNginxProxyTest
|
|
.github/workflows/web-nightly.yml:45: :adapter:inbound:web:webJettyCompatTest
|
|
.github/workflows/web-nightly.yml:46: :adapter:inbound:web:webFluxContractTest
|
|
.github/workflows/web-release.yml:45: :adapter:inbound:web:webCrossStackParityTest
|
|
.github/workflows/web-release.yml:46: :adapter:inbound:web:webNginxProxyTest
|
|
.github/workflows/web-advanced-nightly.yml:46: :adapter:inbound:web:webAdvancedTest
|
|
.github/workflows/web-advanced-release.yml:45: :adapter:inbound:web:webJettyCompatTest
|
|
.github/workflows/web-advanced-release.yml:46: :adapter:inbound:web:webFluxContractTest
|
|
.github/workflows/web-advanced-release.yml:53: :adapter:inbound:web:webAdvancedTest
|
|
exit=0
|
|
|
|
=== 8.2 조건 형제 비교: 두 자동설정의 게이트 ===
|
|
$ grep -n 'ConditionalOn\|matchIfMissing\|@Bean' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java | head -20
|
|
23:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
24:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
25:import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
|
38: * <p>{@link ConditionalOnWebApplication} with {@code SERVLET} is what keeps this starter and the
|
|
42: * <p>Every bean is {@link ConditionalOnMissingBean}. An adopter that registers its own operation
|
|
47:@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
|
48:@ConditionalOnProperty(
|
|
52: matchIfMissing = true)
|
|
57: @Bean
|
|
58: @ConditionalOnMissingBean(WebOperationCatalog.class)
|
|
64: @Bean
|
|
65: @ConditionalOnMissingBean
|
|
73: @Bean
|
|
74: @ConditionalOnMissingBean
|
|
87: @Bean
|
|
88: @ConditionalOnMissingBean
|
|
94: @Bean
|
|
95: @ConditionalOnMissingBean
|
|
101: @Bean
|
|
102: @ConditionalOnMissingBean
|
|
exit=0
|
|
|
|
$ grep -n 'ConditionalOn\|matchIfMissing\|@Bean' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java | head -20
|
|
21:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
22:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
23:import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
|
31: * <p>{@link ConditionalOnWebApplication} with {@code REACTIVE} is the other half of the mutual
|
|
39: * forbids; duplicating six {@code @Bean} methods is the cheaper of the two costs.
|
|
42:@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
|
43:@ConditionalOnProperty(
|
|
47: matchIfMissing = true)
|
|
52: @Bean
|
|
53: @ConditionalOnMissingBean
|
|
63: @Bean
|
|
64: @ConditionalOnMissingBean
|
|
65: @ConditionalOnProperty(
|
|
69: matchIfMissing = true)
|
|
75: @Bean
|
|
76: @ConditionalOnMissingBean(WebOperationCatalog.class)
|
|
82: @Bean
|
|
83: @ConditionalOnMissingBean
|
|
91: @Bean
|
|
92: @ConditionalOnMissingBean
|
|
exit=0
|
|
|
|
$ cat adapter/inbound/web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
|
|
dev.caskeleton.adapter.inbound.web.mvc.autoconfigure.WebMvcPlatformAutoConfiguration
|
|
dev.caskeleton.adapter.inbound.web.webflux.autoconfigure.WebFluxPlatformAutoConfiguration
|
|
exit=0
|
|
|
|
=== 8.3 배선: app-bootstrap 이 이 leaf 에서 가져오는 타입 ===
|
|
$ grep -rh '^import dev.caskeleton.adapter.inbound.web' app-bootstrap/src/main --include=*.java | sort -u
|
|
import dev.caskeleton.adapter.inbound.web.auth.RestrictedPathRule;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.admin.FileserverAdminController;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.config.FileserverWebProperties;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.config.TransferExecutorProperties;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.draft12.Draft12Properties;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.http.MvcConditionalRequestFactory;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.http.MvcDownloadResponseWriter;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.http.ZeroCopyEligibility;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.mapper.MultipartUploadRequestMapper;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.mapper.RawUploadRequestMapper;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.nginx.DefaultNginxInternalUriMapper;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.nginx.NginxDelegationProperties;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.nginx.NginxDownloadStrategy;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.nginx.NginxInternalUriMapper;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.problem.FileserverProblemFactory;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.security.FileserverRequestContextFactory;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusChecksumVerifier;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusProperties;
|
|
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusRequestParser;
|
|
exit=0
|
|
|
|
$ for a in Component RestController RestControllerAdvice Configuration AutoConfiguration; do echo "@$a files = $(grep -rl "@$a" adapter/inbound/web/src/main --include=*.java | wc -l)"; done
|
|
@Component files = 7
|
|
@RestController files = 18
|
|
@RestControllerAdvice files = 6
|
|
@Configuration files = 23
|
|
@AutoConfiguration files = 2
|
|
exit=0
|
|
|
|
=== 8.4 모듈 경계 강제 ===
|
|
$ grep -c 'WebStableModule\.' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/moduleboundary/WebStableModule.java
|
|
0
|
|
exit=1
|
|
|
|
$ grep -c '^ [A-Z_]*(' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/moduleboundary/WebStableModule.java
|
|
62
|
|
exit=0
|
|
|
|
$ grep -n 'void [a-z]' adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/moduleboundary/WebModuleBoundaryTest.java
|
|
34: static void scanProductionTree() {
|
|
40: void theScanActuallyReadsTheProductionTree() {
|
|
49: void everyProductionPackageHasADeclaredModuleIdentity() {
|
|
57: void everyDeclaredModuleExistsInTheProductionTree() {
|
|
65: void everyCrossModuleImportIsADeclaredEdge() {
|
|
71: void coreModulesStayFrameworkFree() {
|
|
79: void anUndeclaredCrossModuleImportIsRejected(@TempDir Path tree) {
|
|
88: void aFrameworkImportInACoreModuleIsRejected(@TempDir Path tree) {
|
|
97: void aPackageWithNoDeclaredIdentityIsRejected(@TempDir Path tree) {
|
|
106: void aScanThatFindsNothingFailsInsteadOfPassing(@TempDir Path tree) {
|
|
113: private static void writeType(
|
|
exit=0
|
|
|
|
$ grep -n 'FRAMEWORK_IMPORT' -A 8 adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/moduleboundary/WebBuildModel.java | head -14
|
|
44: public static final Pattern FRAMEWORK_IMPORT =
|
|
45- Pattern.compile(
|
|
46- // `tools.jackson` is Jackson 3 and `com.fasterxml` is Jackson 2. Both are listed because
|
|
47- // this repository runs on Spring 7, whose message converters take Jackson 3 — so a CORE
|
|
48- // module could have imported a mapper without this detector noticing, which is a hole in
|
|
49- // exactly the check that is supposed to have none.
|
|
50- "^(org\\.springframework|jakarta|reactor|io\\.micrometer|com\\.fasterxml"
|
|
51- + "|tools\\.jackson|org\\.slf4j|io\\.swagger)\\.");
|
|
52-
|
|
--
|
|
114: } else if (FRAMEWORK_IMPORT.matcher(imported).find()) {
|
|
115- frameworkImports.get(moduleId).add(imported);
|
|
116- }
|
|
117- }
|
|
exit=0
|
|
|
|
=== 8.4b CORS 소비처 ===
|
|
$ grep -n 'setAllowedOrigin' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java
|
|
151: cfg.setAllowedOrigins(corsSettings.allowedOrigins());
|
|
exit=0
|
|
|