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>
53 KiB
app-bootstrap — 코드베이스 분석
SSOT identity — 2026-08-31 재검증
- registered leaf id:
app-bootstrap - canonical state
analysisFile:analysis/18-app-bootstrap.md(이 문서) — 이 leaf의 단일 SSOT - source path:
src/app-bootstrap· Gradle:app-bootstrap - registry
allowed_dependencies:["domain-core", "application-core", "adapter-outbound-persistence-jpa", "adapter-outbound-support", "adapter-outbound-messaging", "adapter-outbound-cache-redis", "adapter-outbound-notification", "adapter-outbound-fileserver", "adapter-outbound-httpclient", "adapter-outbound-identifier", "adapter-inbound-web", "shared-contract", "adapter-outbound-persistence-mongo", "adapter-inbound-graphql", "messaging-spring-boot-starter"] - registry
runtime_memberships:["app-bootstrap"] - coverage ledger:
FULL_READ455 /STRUCTURAL_ONLY0 /EXCLUDED0 /UNCLASSIFIED0 - 최초 분석 revision
a24ece9c→ 재검증 revision21234e38· 이 리프의 변경 파일 0 - 재검증 증거:
EVD-333(소스 드리프트 0),EVD-334(lane 재실행) —EVD-334의 jq/exit-78 finding과 compose 계약 독립 검증이 이 리프의 것이다
재검증이 확인한 것은 대상이 움직이지 않았다는 사실이지, 아래 서술이 옳다는 보증이 아니다. 이번 사이클에서 코드에 대고 다시 확인한 항목은 이 문서의 검증 절과 위 증거가 가리키는 범위다.
분석 대상
src/app-bootstrap· revisiona24ece9cf797f7ea647e33bf846b115208ed1ba5분모 455 tracked files (main 157 · test 288 · functionalTest 4 · sampleOffTest 1 · conditionalTransportTest 1 · governance 4) LOC main Java 12,380 · test Java 32,568 (main의 2.6배) 근거evidence/raw/227-app-bootstrap-module-inventory.txt, 이하228–235
0. 이 모듈의 위치
컴포지션 루트다. 지금까지 분석한 17개 모듈에서 반복해서 물었던 질문 — "이 능력을 누가 조립하는가" — 의 답이 여기 있거나 없다.
조립 표면 전체가 네 개 리소스 파일에 있다:
META-INF/spring/...AutoConfiguration.imports (6줄)
bootstrap.autoconfigure.fileserver.FileserverPlatformAutoConfiguration
bootstrap.autoconfigure.httpclient.HttpClientPlatformAutoConfiguration
bootstrap.autoconfigure.persistencejpa.PersistenceJpaRootAutoConfiguration
bootstrap.autoconfigure.messaging.DisabledMessagingSentinelAutoConfiguration
bootstrap.notification.NotificationRootAutoConfiguration
bootstrap.activation.AdapterActivationAutoConfiguration
META-INF/spring.factories
EnvironmentPostProcessor = MasterSwitchEnvironmentPostProcessor,
RuntimeEnvironmentProfileValidator,
CapabilityDependencyEnvironmentValidator,
TracingSamplingEnvironmentPostProcessor,
RedisReadinessGroupPostProcessor,
DatabaseReadinessGroupPostProcessor
SpringBootExceptionReporter = StartupFailureExceptionReporter
AutoConfigurationImportFilter = JpaOffAutoConfigurationImportFilter
ApplicationListener = ResolvedProfileLoggingContextListener
META-INF/spring/...ManagementContextConfiguration.imports (1줄)
bootstrap.autoconfigure.fileserver.FileserverAdminManagementContextConfiguration
CaSkeletonApplication.java — @ComponentScan + @ConfigurationPropertiesScan
여섯 개 자동설정 진입점에 web·websocket·grpc가 없다. 그 셋은 컴포넌트 스캔에 의존하며, 모듈 14·17에서 확인했듯 각각 다른 결과를 낳았다.
1. 커버리지 원장
| # | sub-scope | main | test | 기타 | 합 | 상태 |
|---|---|---|---|---|---|---|
| 1 | governance + resources + CaSkeletonApplication + activation + settings |
21 | 37 | 4 | 62 | COMPLETE |
| 2 | autoconfigure/* (httpclient 16 · fileserver 15 · jpa 10 · persistencejpa 3 · messaging 1) |
45 | 20 | – | 65 | COMPLETE |
| 3 | runtime + runtime/startup + logging + metrics + tracing |
49 | 36 | – | 85 | COMPLETE |
| 4 | notification + outbox + idempotency + messaging + async + concurrency + lock |
35 | 24 | – | 59 | COMPLETE |
| 5 | security + management/security + redis + mongo + authz |
7 | 5 | – | 12 | COMPLETE |
| 6 | test: 아키텍처 규칙 + 위반/허용 픽스처 | 0 | 90 | – | 90 | COMPLETE |
| 7 | test: contract 레인 + integration | 0 | 54 | – | 54 | COMPLETE |
| 8 | test: onboarding 픽스처 + 잔여 + 대체 소스셋 | 0 | 22 | 6 | 28 | COMPLETE |
| TOTAL | 157 | 288 | 10 | 455 | 8 / 8 |
FULL_READ 455 · STRUCTURAL_ONLY 0 · EXCLUDED 0 · UNCLASSIFIED 0. 분할은 227-...의 패키지 트리에서 기계 계산(중복 0 · 미할당 0).
Sub-scope 01 — governance + CaSkeletonApplication + activation + settings (62 files)
근거
evidence/raw/228-app-bootstrap-activation-probes.txt(file_count=62)
2. 무엇을 하는 코드인가
활성화 모델의 SSOT는 shared-contract의 MasterSwitch enum이다:
/**
* The five adapters this skeleton ships behind an explicit switch, and the names that address them.
*
* <p>One place knows the names. Spread across conditions as string literals, a rename becomes a
* silent activation change: the condition stops matching, the adapter stops assembling, and
* nothing reports it.
*/
public enum MasterSwitch {
PERSISTENCE_JPA ("ca-skeleton.persistence-jpa.enabled", "APP_PERSISTENCE_JPA_ENABLED"),
PERSISTENCE_MONGO ("ca-skeleton.persistence-mongo.enabled", "APP_PERSISTENCE_MONGO_ENABLED"),
MESSAGING ("app.messaging.enabled", "APP_MESSAGING_ENABLED"),
NOTIFICATION_PLATFORM("ca-skeleton.notification.platform.enabled", "APP_NOTIFICATION_PLATFORM_ENABLED"),
GRAPHQL ("backend.graphql.enabled", "APP_GRAPHQL_ENABLED");
그 enum이 네 개 장치를 구동한다:
| 장치 | 하는 일 |
|---|---|
MasterSwitchEnvironmentPostProcessor (71) |
값이 정확히 true/false가 아니면 부팅 거부 |
CapabilityDependencyValidator (156) |
능력 간 의존(예: outbox는 JPA+messaging 필요) 검증 |
AdapterActivationEndpoint (74) → AdapterActivationReport (34) |
해석된 상태를 액추에이터로 발행 |
MasterSwitchRegistryContractTest |
enum ↔ docs/registries/env-keys.yaml ↔ application.yml 삼자 일치 |
MasterSwitchEnvironmentPostProcessor의 거부 메시지가 이 모듈의 성격을 보여준다:
"%s must be exactly true or false, but was "%s". A value this close to a boolean is a deployment that believes it set the switch; it is rejected rather than read as off."
그리고 원시 값을 읽는 이유도 적혀 있다 — "An adapter that is off has to be able to start next to an environment full of its own malformed configuration — a validator that bound the namespace to check the switch would fail exactly the deployments the switch exists to protect."
AdapterActivationReport는 "설정된 것"이 아니라 "해석된 것"을 낸다:
"A smoke lane that asserts on the environment it passed in is asserting on its own input. This is the application's answer: the profile it settled on, the state each master switch parsed to, and which capabilities are asking for a relational connection."
MasterSwitchRegistryContractTest가 삼자 일치를 enum에서 파생시킨다 — "Deriving every case from MasterSwitch means a sixth adapter cannot be added without this test demanding its row."
3. Negative-space probes — sub-scope 01
3.1 (8.4) 카운트 드리프트 — "다섯 어댑터"와 실제 스위치를 가진 어댑터
MasterSwitch가 다섯을 담는다. 저장소가 실제로 출하하는, 기본 꺼짐이고 운영자가 켜는 인바운드 어댑터 스위치를 전수하면:
| 스위치 | 기본 | MasterSwitch |
env-keys 레지스트리 | 확인 출처 |
|---|---|---|---|---|
ca-skeleton.persistence-jpa.enabled |
off | 예 | 예 | — |
ca-skeleton.persistence-mongo.enabled |
off | 예 | 예 | — |
app.messaging.enabled |
off | 예 | 예 | — |
ca-skeleton.notification.platform.enabled |
off | 예 | 예 | — |
backend.graphql.enabled |
off | 예 | 예 | — |
ca-skeleton.grpc.enabled |
off | 아니오 | 아니오 | 모듈 15 §2 |
ca-skeleton.websocket.enabled |
off | 아니오 | 아니오 | 모듈 17 §14 |
backend.websocket.enabled |
off | 아니오 | 아니오 | 모듈 17 §0 |
backend.web.mvc.enabled · backend.web.webflux.enabled |
on(matchIfMissing=true) |
아니오 | 아니오 | 모듈 14 §3.2 |
backend.web.budgets.enabled · app.web-platform.durable-operations.enabled |
off | 아니오 | 아니오 | 모듈 14 §15.4 · §19.2 |
app.websocket-platform.advanced.* (4종) |
off | 아니오 | 아니오 | 모듈 17 §0 |
기계 확인:
$ grep -rn 'ca-skeleton.grpc|ca-skeleton.websocket|backend.web.mvc|backend.websocket' \
--include=*.java app-bootstrap/src/main app-bootstrap/src/test -> 0
$ grep -in 'grpc|websocket' docs/registries/env-keys.yaml -> 0 (341개 키 등록)
$ grep -in 'grpc|websocket' docs/registries/capabilities.yaml -> 0
$ grep -n 'grpc|websocket' src/config/architecture/modules.json -> 등록된 leaf로는 존재
즉 모듈 레지스트리는 두 leaf를 알고, 활성화 모델과 운영자용 env 레지스트리는 모르는 상태다. §4.1.
3.2 (8.1) 도달성 — 여섯 자동설정 진입점이 덮는 범위
| 진입점 | 대상 leaf | 모듈 분석에서의 결과 |
|---|---|---|
FileserverPlatformAutoConfiguration + FileserverStartupConfiguration |
outbound fileserver + inbound web의 fileserver/** |
모듈 14 §38 — 이 leaf에서 유일하게 완전 조립된 하위 트리 |
HttpClientPlatformAutoConfiguration |
outbound httpclient | 모듈 11 |
PersistenceJpaRootAutoConfiguration + JpaOffAutoConfigurationImportFilter |
persistence-jpa | 모듈 5 |
DisabledMessagingSentinelAutoConfiguration |
messaging | 모듈 12 |
NotificationRootAutoConfiguration |
outbound notification | 모듈 13 |
AdapterActivationAutoConfiguration |
자기 자신(액추에이터) | — |
inbound web · websocket · grpc에 대응하는 진입점이 없다. 그 셋은 컴포넌트 스캔으로만 조립되고, 스캔 경계(AUTO_CONFIGURED_PACKAGES 정규식)가 web의 다섯 패키지를 제외하면서 그것을 넘겨받을 자동설정을 만들지 않은 것이 모듈 14 §8.1이다.
3.3 (8.2) 조건 형제 비교 — 두 종류의 "꺼짐"
| 방식 | 예 | 꺼졌을 때의 상태 |
|---|---|---|
| 마스터 스위치 + 게이트된 루트 자동설정 | jpa · mongo · messaging · notification · graphql | 빈 0개 + 설정 바인딩도 안 됨(@EnableConfigurationProperties가 게이트 안쪽) |
컴포넌트 스캔 + @ConditionalOnProperty |
grpc · websocket · web | 빈 0개 + 설정은 바인딩됨(@ConfigurationPropertiesScan이 세 패키지를 포함) |
CaSkeletonApplication의 javadoc이 첫 번째 방식의 이유를 적는다:
"The asymmetry that existed before — beans gated, settings not — is why a notification settings object bound itself in a deployment whose notification master was off. A capability whose beans are gated but whose settings still bind is gated only where somebody remembered to gate it."
그리고 두 번째 방식이 정확히 그 비대칭이다 — dev.caskeleton.adapter.inbound.grpc · .web · .websocket 세 패키지가 @ConfigurationPropertiesScan 목록에 있다.
3.4 (8.3) 중복 메커니즘 — 세 개의 환경 검증기
MasterSwitchEnvironmentPostProcessor(스위치 값 문법) · RuntimeEnvironmentProfileValidator(93, 프로파일) · CapabilityDependencyEnvironmentValidator(62 → CapabilityDependencyValidator 156, 능력 간 의존). 셋 다 EnvironmentPostProcessor이고 관심사가 다르다. 중복 아님.
4. Sub-scope 01 findings
4.1 — 다섯 어댑터 범위는 런타임 멤버십 레지스트리와 일치한다 (결함 아님)
§3.1의 표를 처음에는 "출하되는 스위치가 다섯보다 많다"는 결함으로 기록했다. 그 판정은 틀렸다. src/config/architecture/modules.json의 runtime_memberships가 결정적이다:
adapter-inbound-web runtime_memberships = ["app-bootstrap", "sample-portfolio"]
adapter-inbound-graphql runtime_memberships = ["app-bootstrap"]
adapter-inbound-grpc runtime_memberships = []
adapter-inbound-websocket runtime_memberships = []
gRPC와 WebSocket은 build-only leaf다 — 어떤 런타임에도 올라가지 않는다. 그리고 그 사실이 기계로 강제된다:
// conditionalTransportTest/.../ConditionalTransportCompositionContractTest
/** Transports that must not reach any runtime: no membership, and no composition-root edge. */
private static final Map<String, String> BUILD_ONLY_TRANSPORTS =
Map.of("adapter-inbound-grpc", "dev.caskeleton.adapter.inbound.grpc.GrpcServerConfig",
"adapter-inbound-websocket", "dev.caskeleton.adapter.inbound.websocket.stomp.WebSocketConfig");
그 테스트의 javadoc이 세 전송의 등급을 나눈다:
"gRPC and WebSocket are build-only — no membership, and nothing may put them on a runtime. GraphQL is shipped and switch-gated, which is a stronger claim and carries a stronger obligation: the switch has to be the thing that decides."
따라서 두 어댑터가 MasterSwitch·env-keys.yaml·AdapterActivationReport에 없는 것은 누락이 아니라 일관성이다. 런타임에 오르지 않는 어댑터에는 운영자용 활성화 스위치가 필요하지 않다.
남는 것은 backend.web.* 하나다. adapter-inbound-web은 두 런타임에 올라가고(["app-bootstrap","sample-portfolio"]), 그 스위치들 — backend.web.mvc.enabled · backend.web.webflux.enabled(둘 다 matchIfMissing=true, 기본 켜짐) · backend.web.budgets.enabled · app.web-platform.durable-operations.enabled — 은 MasterSwitch에도 env-keys.yaml 341개 키에도 없다. §4.1b.
4.1b P3 — 출하되는 web 어댑터의 스위치가 활성화 모델 밖에 있다
§4.1. adapter-inbound-web은 두 런타임 멤버이므로 build-only 예외에 해당하지 않는다. 그런데 그 네 개 스위치가 조건 안의 문자열 리터럴로만 존재해 MasterSwitch의 javadoc이 경계하는 상태다 — "Spread across conditions as string literals, a rename becomes a silent activation change."
다만 web은 다른 넷과 성질이 다르다. MVC/WebFlux 스위치는 matchIfMissing = true로 기본 켜짐이므로 "옵션 어댑터"가 아니고, MasterSwitch가 규정하는 "explicit switch" 모델(전부 기본 꺼짐, env-keys.yaml에 행이 있고 삼자 일치 테스트가 강제)에 그대로 넣을 수 없다. 그리고 모듈 14 §8.1이 확인했듯 web의 조립 자체가 미해결이다 — 스캔에서 다섯 패키지를 빼고 넘겨받는 자동설정을 만들지 않은 상태다.
기록하는 것은 순서다: web의 스위치를 활성화 모델에 넣는 것은 모듈 14 §8.1(어떤 자동설정이 무엇을 소유하는가)을 먼저 정한 뒤에 할 수 있는 일이다. 지금은 "무엇을 스위치로 부를지"가 결정되지 않았다.
4.1c P3/기록 — 조건부 전송 게이트가 빨간 채로 방치된 이력이 기록돼 있다
ConditionalTransportCompositionContractTest의 javadoc이 자기 이력을 적는다:
"This test asserted that all three transports had
runtime_memberships: []and that their classes load. Both halves aged badly. ... The test went red the moment that landed and nobody saw it, because this suite runs inconditionalTransportQualificationrather than intest— andconditionalTransportQualificationis one of the two commands CI runs. A gate that is red in a lane nobody runs locally is a gate that reports whatever the last person to run it saw."
그리고 두 번째 절반에 대한 판정도 남긴다 — "A type resolving proves the jar is on a classpath; it says nothing about whether a composition assembles the transport... class existence is not composition evidence."
이 문장이 모듈 14 §48.1·모듈 17 §4.1과 같은 원칙을 다른 각도에서 말한다. 기록으로 남긴다.
4.2 P3/기록 — 세 인바운드 leaf의 설정이 마스터 스위치 밖에서 바인딩된다
§3.3. CaSkeletonApplication의 javadoc이 "beans gated, settings not"을 과거의 사고로 기록하면서, 그 형태를 grpc·web·websocket 세 패키지에 대해 유지한다. 모듈 15 §4.2에서 확인했듯 grpc에서는 기본값이 전부 유효해 무해하고, 모듈 17 §4.1에서는 backend.websocket 설정이 바인딩만 되고 소비되지 않는 상태를 만든다.
Sub-scope 02 — autoconfigure/* (65 files, main 45 + test 20)
근거
evidence/raw/229-app-bootstrap-autoconfigure-probes.txt(file_count=65)
5. 무엇을 하는 코드인가
다섯 개 능력의 조립 루트. httpclient(16) · fileserver(15) · jpa(10) · persistencejpa(3) · messaging(1).
fileserver(15)가 이 저장소에서 가장 완전한 조립 사례다. 모듈 14 §38에서 확인했듯 FileserverPlatformAutoConfiguration이 URI 매퍼·다운로드 전략·요청 컨텍스트 팩토리를 만들고, FileserverStartupConfiguration:87이 attestMapping()을 시작 시 호출하며, FileserverAdminManagementContextConfiguration이 관리 평면을 별도 관리 컨텍스트에 등록한다 — CaSkeletonApplication이 그 이유를 적는다: "those routes belong to the management context, and a component scan that also found them would publish the management plane on the public connector — the exposure the separate context exists to remove."
**persistencejpa(3)**는 JpaOffAutoConfigurationImportFilter를 포함한다 — graphql의 GraphQlOffAutoConfigurationImportFilter(모듈 16 §3.2)와 같은 형태로, 프레임워크 자동설정을 후보 집합에서 제거해 "빈은 없는데 프레임워크가 서비스하는" 상태를 막는다. DataSourceRequirement가 MasterSwitch를 직접 읽어 어떤 능력이 관계형 연결을 요구하는지 계산한다.
**messaging(1)**은 DisabledMessagingSentinelAutoConfiguration 하나다 — 꺼진 메시징을 사용하려 할 때 조용한 no-op 대신 실패를 내는 센티널.
6. Negative-space probes
6.1 (8.1) 도달성
45개 main 파일 중 다른 main 파일이 참조하지 않는 것은 전부 @Configuration/@AutoConfiguration 루트이며, 이는 .imports가 진입점으로 등록하므로 정상이다. 고아 파일 없음.
6.2 (8.2) 조건 형제 비교 — 두 off 필터
JpaOffAutoConfigurationImportFilter(app-bootstrap)와 GraphQlOffAutoConfigurationImportFilter(graphql leaf). 같은 문제를 같은 방식으로 푼다. 차이는 위치다 — JPA 것은 컴포지션 루트에, GraphQL 것은 leaf에 있다. 둘 다 spring.factories에 등록돼 있고 동작한다.
web·websocket에는 대응물이 없다. 그 둘은 프레임워크가 발행하는 엔드포인트를 갖지 않으므로(자체 @Configuration이 등록) 필요하지 않다.
6.3 (8.4) 카운트 — .imports 여섯 줄과 다섯 능력
.imports의 여섯 항목 중 다섯이 능력 루트이고 하나(AdapterActivationAutoConfiguration)가 자기 액추에이터다. MasterSwitch의 다섯과 일치한다 — 단 PERSISTENCE_MONGO는 .imports에 루트가 없고 컴포넌트 스캔 제외 정규식(adapter\.outbound\.mongo\..*)으로만 관리된다. §7.1.
7. Findings
7.1 P3/기록 — PERSISTENCE_MONGO만 자동설정 루트가 없다
다섯 마스터 스위치 중 넷은 .imports에 게이트된 루트를 갖는다(jpa · messaging 센티널 · notification · graphql은 자기 leaf에). Mongo는 AUTO_CONFIGURED_PACKAGES 정규식의 adapter\.outbound\.mongo\..* 제외만 있고 그것을 넘겨받는 루트가 .imports에 없다.
모듈 14 §8.1이 정확히 그 형태의 결함이었다(제외했는데 넘겨받지 않음). Mongo에서 같은 상태인지는 이 sub-scope의 파일만으로는 판정되지 않는다 — outbound mongo leaf(모듈 6)가 자기 .imports를 갖는지 확인이 필요하고, 그 모듈은 이미 COMPLETE로 분석됐으나 이 관점에서 재확인하지 않았다. 기록으로 남긴다.
Sub-scope 03 — runtime + runtime/startup + logging + metrics + tracing (85 files, main 49 + test 36)
근거
evidence/raw/230-app-bootstrap-runtime-probes.txt(file_count=85)
8. 무엇을 하는 코드인가 — 이 저장소에서 시작 검증이 실제로 도는 곳
열두 개 검증기가 전부 배선돼 있다:
StartupSafetyValidator (103) main_refs=3 RuntimeSafetyConfig
SecretSourceValidator (182) main_refs=3 SecretSourceConfig
RequiredEnvironmentValidator (49) main_refs=1
HikariPoolConstraintValidator main_refs=2
JpaSchemaSafetyValidator main_refs=1
OpenInViewSafetyValidator main_refs=1
PersistenceVendorProdSafetyValidator main_refs=2
PostgreSqlTransportSecurityValidator main_refs=2
RedisActivationValidator main_refs=2
RuntimeNumericBoundsValidator main_refs=1
FlywayProdSafetyValidator main_refs=1
MigrationStartupRunner (53) main_refs=1 MigrationStartupConfig
RuntimeSafetyConfig(@Configuration, @Bean 7개)가 "Wires the runtime-safety startup fail-fast validators into the running application"을 한다.
StartupSafetyValidator가 SmartInitializingSingleton인 이유가 적혀 있다 — "the check runs once after every singleton is instantiated but before the context finishes refreshing; a violation throws so the context refuses to start."
시작 실패의 어휘가 구조화돼 있다. StartupFailures(75)가 단일 발생원이고 "Every factory method emits the structured failure log (with the startup.phase / error.code / error.category fields) before returning the exception to throw." StartupErrorCode · StartupPhase · StartupFailureLogState가 그 어휘이고, StartupFailureExceptionReporter가 SpringBootExceptionReporter로 등록돼 컨텍스트가 없는 시점의 실패도 같은 형식으로 낸다. 종료 코드까지 규정한다(STARTUP_VALIDATION_FAILED = exit 78, MigrationFailedException = exit 70).
logging(8)은 logback을 통해 배선된다. MetricsAsyncAppender · SamplingTurboFilter · SecretMaskingMessageConverter · SecretMaskingJsonGeneratorDecorator · StartupFailureSpringBootLogFilter 다섯이 Java 참조가 0인데, logback-spring.xml이 클래스 이름으로 등록한다(:56 · :59 · :62 · :91 · :124 · :137 · :148). ResolvedProfileLoggingContextListener는 spring.factories의 ApplicationListener이고, 그 이유가 주석에 있다 — "An ApplicationListener, not a bean: this event fires before there is a context to hold one."
9. Negative-space probes
9.1 (8.1) 도달성 — main 참조 0인 파일의 전수 분류
49개 main 파일 중 다른 main 파일이 참조하지 않는 것은 열둘이고, 전부 설명된다:
| 분류 | 파일 | 등록 경로 |
|---|---|---|
@Configuration 루트 |
RuntimeSafetyConfig · SecretSourceConfig · SecretSourceSettings · MigrationStartupConfig · TracingConfig · PseudonymizationConfig |
컴포넌트 스캔 |
| logback 컴포넌트 | MetricsAsyncAppender · SamplingTurboFilter · SecretMaskingMessageConverter · SecretMaskingJsonGeneratorDecorator · StartupFailureSpringBootLogFilter |
logback-spring.xml |
spring.factories |
ResolvedProfileLoggingContextListener · TracingSamplingEnvironmentPostProcessor |
spring.factories |
고아 없음. 지금까지 분석한 17개 모듈 중 이 성질을 가진 것은 grpc(모듈 15)와 여기뿐이다.
9.2 (8.2) 조건 형제 비교 — 시작 검증기의 운명
| 모듈 | 검증기 | 배선 |
|---|---|---|
| app-bootstrap | 12종 | 전부 |
| outbound fileserver ← app-bootstrap | NginxInternalUriMapper.attestMapping() |
예 (모듈 14 §39.1) |
| inbound web | WebPlatformStartupValidator (62) |
아니오 (모듈 14 §44.2) |
| inbound websocket | WebSocketPlatformStartupValidator (125) · WebSocketStackExclusivity (78) |
아니오 (모듈 17 §4.1) |
| inbound graphql | GraphQlPlatformStartupValidator |
예 (leaf 자기 자동설정) |
검증기가 도는지 여부는 그 능력에 컴포지션 루트의 자동설정이 있는지와 정확히 일치한다.
9.3 (8.3)·(8.4) 중복·드리프트 — 없음
StartupFailures가 단일 발생원이고 종료 코드·단계·카테고리가 한 어휘에 모여 있다. 검증기 12종이 서로 다른 불변식을 보며 겹치지 않는다.
10. Findings — 없음
Sub-scope 04 — notification + outbox + idempotency + messaging + async + concurrency + lock (59 files, main 35 + test 24)
근거
evidence/raw/231-app-bootstrap-capability-probes.txt(file_count=59)
11. 무엇을 하는 코드인가
능력별 조립. notification(10) + notification/observation(4)이 모듈 13에서 확인한 배선의 출처다 — NotificationRootAutoConfiguration(.imports 등록, @ConditionalOnProperty) · NotificationPlatformWorkerConfig(모듈 13 §12.1이 확인한 배경 작업자 3종 등록) · NotificationPlatformProviderConfig · NotificationPlatformDispatchConfig · NotificationPlatformObservabilityConfig.
outbox(6) · idempotency(5) · messaging(1, KafkaSenderConfig 113) · async(5) · concurrency(2) · lock(2)이 나머지다.
12. Negative-space probes
12.1 (8.1) 도달성
35개 main 파일 중 main 참조 0인 것은 전부 @Configuration 루트다(IdempotencyConfig · PostgreSqlIdempotencyProviderConfig · AsyncExecutorConfig · DomainContextConfig · DistributedLockConfig · KafkaSenderConfig · NotificationRootAutoConfiguration). 고아 없음.
12.2 (8.2) 조건 형제 비교 — 모듈 13의 미배선 항목이 여기 있는가
모듈 13에서 P2로 기록한 다섯 중 컴포지션 루트가 고칠 수 있는 것을 확인했다:
| 모듈 13 발견 | app-bootstrap에 배선 지점이 있는가 |
|---|---|
§17.1 AccessContext 감사 미기록 |
아니오 — leaf 내부 문제(복호화 경로가 감사 포트를 부르지 않음) |
| §25.1 Web Push SSRF 가드 미적용 | 아니오 — leaf 내부(WebPushSubscriptionValue의 private 사본) |
| §29.1 FCM ambiguous 미번역 | 아니오 — leaf 내부(FcmBatchCoordinator) |
| §12.1 배경 작업자 3종 | 예 — 배선됨(NotificationPlatformWorkerConfig:57·90·119) |
즉 컴포지션 루트가 할 수 있는 부분은 이미 돼 있고, 남은 것은 leaf 내부의 회로다.
13. Findings — 없음
Sub-scope 05 — security + management/security + redis + mongo + authz (12 files, main 7 + test 5)
근거
evidence/raw/232-app-bootstrap-security-probes.txt(file_count=12)
14. 무엇을 하는 코드인가
security/AuthenticationModeCompositionConfig(48)가 JWT/Redis-세션 두 인증 모드의 조립을 결정하고, management/security(1)가 관리 평면 보안을, redis/RedisCapabilityConfig(376 — 이 모듈에서 가장 큰 단일 파일)가 Redis 능력 전체를, mongo/MongoPlatformHealthConfig(37)가 Mongo 헬스를, authz(1)가 권한 배선을 담당한다.
15. Negative-space probes
15.1 (8.1)·(8.2) 도달성과 게이트
7개 main 파일 전부 @Configuration이고 @ConditionalOnProperty/@ConditionalOnBean으로 게이트된다. MongoPlatformHealthConfig가 @ConditionalOnBean + @ConditionalOnMissingBean + @ConditionalOnProperty 셋을 함께 쓰는데, 자동설정 안에서의 @ConditionalOnBean은 Boot가 평가 순서를 통제하므로 모듈 14 §7.1이 경고한 컴포넌트 스캔 상의 위험이 없다.
16. Findings — 없음
Sub-scope 06 — test: 아키텍처 규칙 + 위반/허용 픽스처 (90 files)
근거
evidence/raw/233-app-bootstrap-architecture-probes.txt(file_count=90)
17. 무엇을 하는 코드인가
아키텍처 규칙 14종이 여기서 프로덕션 트리에 적용된다:
ArchitectureViolationFixtureTest CleanArchitectureTest ContractSuiteIsolationArchTest
DisabledAdapterArchitectureTest DomainFeatureOnboardingContractTest FileserverUseCaseContractDeviationTest
GraphQlInboundOwnershipBoundaryTest JpaProductionArchitectureTest MongoRawAccessBoundaryTest
NamingConventionTest NotificationArchitectureTest ProductionClassImportOption
TestTaxonomyArchitectureTest WebProductionArchitectureTest
WebProductionArchitectureTest가 모듈 14 §46에서 확인한 WebArchitectureRules.all() 7규칙을 프로덕션 트리에 적용하는 지점이고, NotificationArchitectureTest·JpaProductionArchitectureTest·MongoRawAccessBoundaryTest·GraphQlInboundOwnershipBoundaryTest가 각 leaf의 규칙 팩에 대해 같은 일을 한다.
위반/허용 픽스처가 76개다. architecture/violations/**(application 14 · domain 5 · streaming 4 · boundary 3 · adapter 5 · slice 2 · fixtureleak 4 · contractisolation 4 · shared 1 · serialization 1 …)와 architecture/allowed/**(streaming 2 · slice 2 · contractisolation 4 · application 2). 규칙마다 거부되어야 할 합성 트리와 통과해야 할 합성 트리를 함께 두는 형태다.
모듈 14 §3.4에서 확인한 WebModuleBoundaryTest의 부정 픽스처 넷과 같은 원칙이 저장소 규모로 적용돼 있다 — "A boundary test that has never been shown to fail is indistinguishable from one that scans the wrong directory."
18. Negative-space probes
18.1 (8.1)·(8.4) 규칙과 픽스처의 대응
14개 규칙 클래스에 76개 픽스처가 붙는다. ArchitectureViolationFixtureTest가 위반 픽스처들이 실제로 거부되는지를 확인하는 메타 테스트이고, TestTaxonomyArchitectureTest가 테스트 자체의 분류 규약을 강제한다.
ProductionClassImportOption이 스캔 대상을 프로덕션 클래스로 한정하는 공유 옵션 — 픽스처가 규칙에 잡히지 않도록 하는 장치다. architecture/violations/fixtureleak(4)는 그 장치가 새는 경우를 픽스처로 고정한다.
18.2 (8.3) 중복 메커니즘 — 규칙 팩의 위치
leaf가 규칙 팩을 소유하고(WebArchitectureRules in web testkit) 컴포지션 루트가 적용하는 형태와, 컴포지션 루트가 규칙을 직접 쓰는 형태(CleanArchitectureTest)가 공존한다. 전자는 leaf가 자기 규칙을 소유하고 후자는 저장소 전역 규칙이므로 역할이 다르다. 중복 아님.
19. Findings — 없음
Sub-scope 07 — test: contract 레인 + integration (54 files)
근거
evidence/raw/234-app-bootstrap-contract-probes.txt(file_count=54)
20. 무엇을 하는 코드인가
30개 계약 테스트가 저장소 전역 불변식을 확인한다. 성격별로:
| 성격 | 예 |
|---|---|
| 레지스트리 일치 | ErrorCodeRegistryMappingTest · SecretsClassificationRegistryTest · ContractRegistrySchemaGovernanceTest · RepositoryAccessCapabilityRegistryTest · RegistryGovernanceCatalog |
| 관측·로그 계약 | StructuredLogFieldContractTest · MetricsAlertingContractTest · DistributedTracingContractTest · SqlLoggingForbiddenContractTest · PiiTokenBodyForbiddenContractTest |
| 보안 표면 | ActuatorSecurityHttpTest · ManagementActuatorSecurityContractTest · ProfileSeparationContractTest |
| 활성화·조건부 | OptionalAdapterConditionalExecutionContractTest · ConditionalTransportQualificationContractTest · RedisOptionalityContractTest · EnvProfileMatrixContractTest |
| 실패 분류 | PersistenceFailureMappingContractTest · LockFailureClassificationContractTest · LockAcquisitionTimeoutClassificationContractTest · BackgroundJobErrorCodeContractTest |
| 운영 | RunbookCoverageContractTest · ContainerRuntimeOomContractTest · SecretReloadContractTest · DeveloperExperienceContractTest |
| 자기 검사 | ContractSuiteCompletenessTest |
ContractSuiteCompletenessTest가 존재한다는 사실이 이 sub-scope의 성격을 말한다 — 계약 스위트 자체의 완전성을 검사하는 계약이다.
21. Negative-space probes
21.1 (8.2) 조건 형제 비교 — 세 전송의 조건부 실행 증거
ConditionalTransportQualificationContractTest(test 레인)와 ConditionalTransportCompositionContractTest(별도 conditionalTransportTest 소스셋)와 ConditionalTransportEvidenceFunctionalTest(functionalTest 소스셋) 셋이 같은 축을 다른 레인에서 본다.
두 번째 것의 javadoc이 §4.1c에서 인용한 자기 이력을 담는다 — 레인 분리가 만든 사각지대를 스스로 기록한 사례다.
21.2 (8.1) 도달성 — 레지스트리 계약이 실제 레지스트리 파일을 읽는가
MasterSwitchRegistryContractTest가 docs/registries/env-keys.yaml과 src/app-bootstrap/src/main/resources/application.yml을 실제로 읽어 대조한다(§2). ErrorCodeRegistryMappingTest·SecretsClassificationRegistryTest도 docs/registries/ 아래 파일을 읽는다. 파일 기반 SSOT가 테스트로 고정돼 있다.
22. Findings — 없음
Sub-scope 08 — test: onboarding 픽스처 + 잔여 + 대체 소스셋 (28 files)
근거
evidence/raw/235-app-bootstrap-testrest-probes.txt(file_count=28)
23. 무엇을 하는 코드인가
dev.caskeleton.onboarding.**(14 파일)이 저장소 밖 패키지의 합성 feature다 — onboarding/domain/feature · application/usecase · application/query · application/port · adapter/inbound/web/dto · adapter/outbound. DomainFeatureOnboardingContractTest(§17)가 이것을 대상으로 "새 feature를 추가하는 절차"가 규칙을 만족하는지 확인한다.
대체 소스셋 셋:
conditionalTransportTest/ ConditionalTransportCompositionContractTest (§4.1)
functionalTest/ BuildVerificationPurityContractTest
ConditionalTransportEvidenceFunctionalTest
RuntimeMembershipFunctionalTest
StrictQualificationTestConventionFunctionalTest
sampleOffTest/ SampleOffClasspathContractTest
RuntimeMembershipFunctionalTest가 modules.json의 runtime_memberships를 실제 부트 JAR에 대해 확인하는 것으로 보이며, 그것이 §4.1의 build-only 판정을 뒷받침하는 두 번째 증거다.
SampleOffClasspathContractTest(sampleOffTest 소스셋)는 샘플 모듈을 제외한 클래스패스에서의 계약을 확인한다 — 스켈레톤이 샘플 없이 성립하는지의 증거다.
24. Findings — 없음
25. 모듈 종합 — app-bootstrap
25.1 커버리지 원장 정산
8개 sub-scope, 455 / 455 FULL_READ · STRUCTURAL_ONLY 0 · EXCLUDED 0 · UNCLASSIFIED 0.
25.2 발견 종합 — P1 0건 · P2 0건 · P3 3건 · 기록 2건
| 심각도 | § | 발견 |
|---|---|---|
| P3 | 4.1b | 출하되는 adapter-inbound-web의 스위치 넷이 활성화 모델(MasterSwitch·env-keys.yaml·액추에이터·삼자 일치 테스트) 밖에 있다. 다만 둘은 기본 켜짐이라 "explicit switch" 모델에 그대로 넣을 수 없고, 모듈 14 §8.1(조립 소유권)을 먼저 정해야 한다 |
| P3 | 26.1 | ComposeMergeCharacterizationTest가 docker compose 부재는 skip하고 jq 부재는 실패로 처리한다 — 같은 테스트의 도구 가드가 불완전하다 |
| P3/기록 | 7.1 | 다섯 마스터 스위치 중 PERSISTENCE_MONGO만 .imports에 게이트된 루트가 없고 스캔 제외 정규식만 있다 |
| 기록 | 4.1c | 조건부 전송 게이트가 별도 레인에서만 돌아 빨간 채로 방치된 이력이 그 테스트 javadoc에 기록돼 있다 |
| 기록 | 4.2 | 세 인바운드 leaf(grpc·web·websocket)의 설정이 @ConfigurationPropertiesScan으로 마스터 스위치 밖에서 바인딩된다 |
25.3 이 모듈의 성격 — 조립이 실제로 일어나는 곳
main 157 파일에 고아가 없다. main 참조가 0인 파일은 전부 설명된다 — @Configuration/@AutoConfiguration 루트(.imports 또는 컴포넌트 스캔), logback 컴포넌트(logback-spring.xml이 클래스 이름으로 등록), spring.factories 항목(EnvironmentPostProcessor 6 · SpringBootExceptionReporter · AutoConfigurationImportFilter · ApplicationListener).
지금까지 분석한 18개 모듈 중 이 성질을 가진 것은 grpc(모듈 15)와 여기뿐이다.
시작 검증기 12종이 전부 배선돼 있다(§8). 그리고 그 사실이 다른 모듈의 결과를 설명한다:
| 검증기 | 배선 | 대응 자동설정 |
|---|---|---|
app-bootstrap runtime/* 12종 |
예 | RuntimeSafetyConfig 등 |
fileserver attestMapping() |
예 | FileserverStartupConfiguration (app-bootstrap) |
graphql GraphQlPlatformStartupValidator |
예 | leaf 자기 자동설정 |
web WebPlatformStartupValidator |
아니오 | 없음 |
websocket WebSocketPlatformStartupValidator · WebSocketStackExclusivity |
아니오 | 없음 |
검증기가 도는지 여부가 그 능력에 자동설정 루트가 있는지와 정확히 일치한다. 그것이 이 저장소의 조립 결함을 설명하는 단일 규칙이다.
25.4 이 모듈이 나머지 분석을 교정했다
app-bootstrap을 읽고 두 가지가 바뀌었다:
modules.json의runtime_memberships가 build-only 등급을 규정한다 — grpc·websocket은[], graphql은["app-bootstrap"], web은["app-bootstrap","sample-portfolio"]. 그리고ConditionalTransportCompositionContractTest가 "nothing may put them on a runtime"을 기계로 강제한다. 이것으로 모듈 17 §4.1을 P1 → P2로 하향했다(모듈 17 §26.6).MasterSwitch다섯의 범위가 자의적이지 않다 — 런타임에 오르는 어댑터 중 옵션인 것들이다. 처음에 "출하 스위치가 다섯보다 많다"로 기록한 것을 §4.1에서 철회했다.
두 교정 모두 컴포지션 루트를 읽지 않고는 도달할 수 없었다. 조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다는 것이 이 모듈이 남긴 방법론적 결론이다.
26. 실행 검증
$ ./gradlew :app-bootstrap:test
BUILD FAILED in 1m 47s
GRADLE_EXIT=1
test-results 집계: classes=169 tests=1016 failures=1 errors=0 skipped=4
FAILED: ComposeMergeCharacterizationTest.every lane renders exactly the services its contract names
26.1 P3 — 실패는 환경 원인이며, 그 테스트의 도구 가드가 불완전하다
org.opentest4j.AssertionFailedError: [verify-compose-profile-contracts.sh said:
jq is required
]
expected: 0 but was: 78
at ComposeMergeCharacterizationTest.everyLaneMatchesItsContract(ComposeMergeCharacterizationTest.java:62)
$ which jq -> NO_JQ
저장소 결함이 아니다. 분석 컨테이너에 jq가 없고, 위임된 스크립트가 그것을 요구하며 exit 78로 정직하게 실패한다.
기록하는 것은 가드의 비대칭이다:
@Test
void everyLaneMatchesItsContract() {
Assumptions.assumeTrue(dockerComposeIsAvailable(), "docker compose is not on this machine");
ProcessResult result = run(List.of("./scripts/verify-compose-profile-contracts.sh"));
assertThat(result.exitCode()).isZero();
}
docker compose 부재는 skip으로 처리하고, 같은 스크립트가 요구하는 jq 부재는 실패로 나타난다. 도구가 없는 기계에서 이 테스트는 계약 위반처럼 읽히는 실패를 낸다 — 메시지가 "jq is required"라 원인은 드러나지만, 이미 skip을 선택한 테스트가 두 번째 도구에 대해서만 다르게 행동한다.
이 저장소는 반대 방향의 원칙도 갖고 있다 — webNginxProxyTest가 "A lane that quietly passes when the container runtime is missing is a lane that has been certifying nothing since whenever Docker last broke"로 skip을 거부한다. 두 원칙 중 어느 쪽을 택하든 한 테스트 안에서 도구별로 갈리지는 않는 편이 낫다.
나머지 1,015개 테스트는 통과한다(failures=1, errors=0, skipped=4).
26.2 재검증 — 그 레인 계약이 실제로 성립하는지 독립 경로로 확인했다 (2026-08-31)
§26.1이 남긴 실질적 공백은 "그래서 15개 레인이 계약과 맞는가"가 확인되지 않은 채였다는 점이다. 실패 원인이 환경이라는 판정은 옳지만, 그 판정은 계약의 성립 여부를 말해 주지 않는다.
jq는 설치하지 않았다 — 사용자 기계의 시스템 변경이다. 대신 verify-compose-profile-contracts.sh의
6개 검사를 파이썬으로 이식해 같은 docker compose config 렌더링 위에서 돌렸다. 아무것도 기동하지
않는 정적 검사다(EVD-334).
compose version 2.40.3+ds1-0ubuntu1~24.04.1 >= 2.24.4
off-local: app @ local off-dev: app @ dev
off-prod: app @ prod local-jpa: app,db @ local
local-mongo: app,mongo,mongo-rs-init @ local
local-messaging: app,kafka @ local local-messaging-outbox: app,db,kafka @ local
local-notification-ingest / -serving / -handoff … @ local
local-graphql: app,auth-smoke,graphql-smoke,keycloak @ local
shared-infra-local / shared-infra-dev / prod-smoke …
all-adapters: app,auth-smoke,db,db-migrate-capabilities,db-promote-capabilities,
graphql-smoke,kafka,keycloak,mailpit,mongo,mongo-rs-init,notification-smoke @ local
PORT RESULT: all 15 lanes match src/config/runtime/compose-profile-contracts.json (exit 0)
이식본이 검사한 것은 원본과 같은 여섯이다 — ①compose 버전 하한 ②레인별 정확한 서비스 집합
③렌더된 SPRING_PROFILES_ACTIVE ④병합 모델의 마운트 대상 중복 ⑤서비스-역할 분할과 --wait 대상
정합 ⑥Keycloak realm의 밑줄 키.
이것이 무엇을 말하고 무엇을 말하지 않는가. 계약 위반의 증거가 없다는 것이지, 원본 스크립트가
통과한다는 것이 아니다. 그 구별은 테스트 자신의 주석이 이미 경계한 것이다 — "a test that re-derived
the same checks in Java would be a second opinion that can agree with the contract while the script
disagrees." jq가 있는 기계에서 원본 스크립트로 확인하는 것이 여전히 정본이며, 여기서 얻은 것은
그 확인이 이루어질 때까지의 잠정 근거다.
exit 78은 sysexits.h의 EX_CONFIG — "설정이 없어 확인할 수 없음"이고, 스크립트 끝의 exit 1이
"계약이 틀렸음"이다. §26.1이 기록한 가드 비대칭의 비용이 정확히 이것이다: 두 코드가 하나의 실패로
뭉개지면서, 아무것도 검사되지 않은 상태가 계약 위반으로 보고됐다.
27. 완료 게이트
- denominator 455 / 455 FULL_READ
- 8개 sub-scope 각각 §8.1~§8.4 수행 — 조립 표면 전수(
.imports6 ·spring.factories4종 · 관리 컨텍스트 1) ·MasterSwitch다섯과 런타임 멤버십 대조 · main 참조 0 파일의 등록 경로 전수 분류 · 시작 검증기 12종 배선 확인 - evidence
227–235생성 - 실행 검증: tests=1016 failures=1(환경 원인) errors=0 skipped=4
- 분석 중 판정 철회 1건(§4.1 — 활성화 모델의 다섯 어댑터 범위는 결함이 아님) · 타 모듈 판정 하향 1건(모듈 17 §4.1 P1 → P2)
- 소스 미변경
- 2026-08-31 재검증 — 리프 소스 변경 0(
EVD-333), lane 재실행 tests=1016 failures=1(동일 환경 원인), 그리고 §26.1이 남긴 공백을 독립 경로로 메움 — 15개 레인 전부 계약 일치(§26.2,EVD-334)
Source anchors
이 문서가 backtick으로 인용한 타입·경로를 저장소 트리에 대고 해석한 결과다. 해석된 것만 싣는다 — 총 93개 (main 49 · test 38 · 기타 6).
src/app-bootstrap/build.gradle
src/config/architecture/modules.json (app-bootstrap 항목)
main:
src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java
src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationReport.java
src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java
src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyValidator.java
src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java
src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java
src/main/java/dev/caskeleton/bootstrap/async/AsyncExecutorConfig.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminManagementContextConfiguration.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/messaging/DisabledMessagingSentinelAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java
src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/concurrency/DomainContextConfig.java
src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java
src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java
src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java
src/main/java/dev/caskeleton/bootstrap/logging/MetricsAsyncAppender.java
src/main/java/dev/caskeleton/bootstrap/logging/PseudonymizationConfig.java
src/main/java/dev/caskeleton/bootstrap/logging/ResolvedProfileLoggingContextListener.java
src/main/java/dev/caskeleton/bootstrap/logging/SamplingTurboFilter.java
src/main/java/dev/caskeleton/bootstrap/logging/SecretMaskingJsonGeneratorDecorator.java
src/main/java/dev/caskeleton/bootstrap/logging/SecretMaskingMessageConverter.java
src/main/java/dev/caskeleton/bootstrap/logging/StartupFailureSpringBootLogFilter.java
src/main/java/dev/caskeleton/bootstrap/messaging/KafkaSenderConfig.java
src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java
src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java
src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java
src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java
src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java
src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java
src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeSafetyConfig.java
src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java
src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceSettings.java
src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/MigrationFailedException.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/MigrationStartupConfig.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupErrorCode.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailureExceptionReporter.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailureLogState.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java
src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupPhase.java
src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java
src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java
src/main/resources/application.yml
test:
src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/GraphQlInboundOwnershipBoundaryTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/JpaProductionArchitectureTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/MongoRawAccessBoundaryTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/NotificationArchitectureTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/ProductionClassImportOption.java
src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java
src/test/java/dev/caskeleton/bootstrap/architecture/WebProductionArchitectureTest.java
src/test/java/dev/caskeleton/bootstrap/compose/ComposeMergeCharacterizationTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ActuatorSecurityHttpTest.java
src/test/java/dev/caskeleton/bootstrap/contract/BackgroundJobErrorCodeContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ConditionalTransportQualificationContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ContainerRuntimeOomContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ContractRegistrySchemaGovernanceTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ContractSuiteCompletenessTest.java
src/test/java/dev/caskeleton/bootstrap/contract/DeveloperExperienceContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/EnvProfileMatrixContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ErrorCodeRegistryMappingTest.java
src/test/java/dev/caskeleton/bootstrap/contract/LockAcquisitionTimeoutClassificationContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/LockFailureClassificationContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ManagementActuatorSecurityContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/MetricsAlertingContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/OptionalAdapterConditionalExecutionContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/PersistenceFailureMappingContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/PiiTokenBodyForbiddenContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/RedisOptionalityContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/RegistryGovernanceCatalog.java
src/test/java/dev/caskeleton/bootstrap/contract/RepositoryAccessCapabilityRegistryTest.java
src/test/java/dev/caskeleton/bootstrap/contract/RunbookCoverageContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/SecretReloadContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/SecretsClassificationRegistryTest.java
src/test/java/dev/caskeleton/bootstrap/contract/SqlLoggingForbiddenContractTest.java
src/test/java/dev/caskeleton/bootstrap/contract/StructuredLogFieldContractTest.java
기타:
docs/registries/env-keys.yaml
src/conditionalTransportTest/java/dev/caskeleton/bootstrap/transport/ConditionalTransportCompositionContractTest.java
src/config/architecture/modules.json
src/functionalTest/java/dev/caskeleton/bootstrap/contract/ConditionalTransportEvidenceFunctionalTest.java
src/functionalTest/java/dev/caskeleton/bootstrap/contract/RuntimeMembershipFunctionalTest.java
src/sampleOffTest/java/dev/caskeleton/bootstrap/contract/SampleOffClasspathContractTest.java
해석되지 않은 인용 (11종) — 외부 타입·문서상 약칭 등:
env-keys.yaml
modules.json
evidence/raw/227-app-bootstrap-module-inventory.txt
evidence/raw/228-app-bootstrap-activation-probes.txt
evidence/raw/229-app-bootstrap-autoconfigure-probes.txt
evidence/raw/230-app-bootstrap-runtime-probes.txt
evidence/raw/231-app-bootstrap-capability-probes.txt
evidence/raw/232-app-bootstrap-security-probes.txt
evidence/raw/233-app-bootstrap-architecture-probes.txt
evidence/raw/234-app-bootstrap-contract-probes.txt
evidence/raw/235-app-bootstrap-testrest-probes.txt