# 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_READ` **455** / `STRUCTURAL_ONLY` **0** / `EXCLUDED` **0** / `UNCLASSIFIED` **0**
- 최초 분석 revision `a24ece9c` → 재검증 revision `21234e38` · 이 리프의 변경 파일 **0**
- 재검증 증거: `EVD-333`(소스 드리프트 0), `EVD-334`(lane 재실행) — `EVD-334`의 jq/exit-78 finding과 compose 계약 독립 검증이 이 리프의 것이다
> 재검증이 확인한 것은 대상이 움직이지 않았다는 사실이지, 아래 서술이 옳다는 보증이 아니다.
> 이번 사이클에서 코드에 대고 다시 확인한 항목은 이 문서의 검증 절과 위 증거가 가리키는 범위다.
---
> **분석 대상** `src/app-bootstrap` · revision `a24ece9cf797f7ea647e33bf846b115208ed1ba5`
> **분모** 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이다:**
```java
/**
* The five adapters this skeleton ships behind an explicit switch, and the names that address them.
*
*
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다** — 어떤 런타임에도 올라가지 않는다. 그리고 그 사실이 기계로 강제된다:
```java
// conditionalTransportTest/.../ConditionalTransportCompositionContractTest
/** Transports that must not reach any runtime: no membership, and no composition-root edge. */
private static final Map 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 in `conditionalTransportQualification` rather than in `test`** — and `conditionalTransportQualification` is 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을 읽고 두 가지가 바뀌었다:
1. **`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).
2. **`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로 정직하게 실패한다.
기록하는 것은 가드의 비대칭이다:
```java
@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. 완료 게이트
- [x] denominator 455 / 455 FULL_READ
- [x] 8개 sub-scope 각각 §8.1~§8.4 수행 — 조립 표면 전수(`.imports` 6 · `spring.factories` 4종 · 관리 컨텍스트 1) · `MasterSwitch` 다섯과 런타임 멤버십 대조 · main 참조 0 파일의 등록 경로 전수 분류 · 시작 검증기 12종 배선 확인
- [x] evidence `227`–`235` 생성
- [x] 실행 검증: tests=1016 failures=1(환경 원인) errors=0 skipped=4
- [x] **분석 중 판정 철회 1건**(§4.1 — 활성화 모델의 다섯 어댑터 범위는 결함이 아님) · **타 모듈 판정 하향 1건**(모듈 17 §4.1 P1 → P2)
- [x] 소스 미변경
- [x] **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
```