Files
llm-wiki/raw/branch-notes/feature-env-driven-runtime-configuration.md
T

53 KiB

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, contract_packet_sha256
title source_type status id kind project work_item inherits refines overrides depends_on contract_packet branch parent_branch related_projects governing_docs tags created target_merge status_label contract_packet_sha256
branch / feature-env-driven-runtime-configuration branch-note raw BR-CA-SKELETON-OPERATIONAL-CONTRACT-004 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-004
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1
1 feature-env-driven-runtime-configuration
ca-skeleton
wiki/projects/ca-tmpl/config-and-adapter-templates.md
branch
ca-skeleton
env
configuration
runtime
2026-05-21 in-progress e68380e05a6baa55af05d9d692b7986fc91202315b02276cecfd7bb83c0098ca

branch: feature-env-driven-runtime-configuration

Layer: raw/branch-notes/ — 서버별 운영 전환을 env로 가능하게 하는 설정 계약을 정의합니다.

[!important] 주도권 이전 고지 (2026-07-28) 아래 관심사의 owner 가 신규 branch 로 이동했다. 근거·절차: docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md §5.

이전 ID 대상 D-row 이전한 관심사 신규 owner
H8 D8 의 집행 메커니즘 부분 multi-instance 활성화 판정 — bean 이름 presence 검사(SmartInitializingSingleton + getBeanProvider) → typed capability descriptor raw/branch-notes/feature-capability-provider-selection-contract

이전 범위는 판정 메커니즘뿐이다. APP_MULTI_INSTANCE_ENABLED flag 자체, env-keys.yaml row, APP_ prefix 통일(D2), env drift 검증(D7)은 본 branch 가 계속 소유한다.

본문은 아직 제거하지 않았다. 신규 branch 는 스캐폴딩 상태이므로 D8 은 재판정 전까지 잠정 근거로 유효하며, 그 시점에 docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md §5.2 6단계에 따라 포인터로 치환한다. D8 의 집행 메커니즘은 이미 UNSUPPORTED_IMPL_DECISION 라벨이 붙어 있어 재판정 대상임이 노트 자체에 기록되어 있다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: env configuration 6필드 contract와 invalid-config test가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1 framework는 Spring Boot 3.5.14다 Spring Boot env binding과 startup validation에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

묶음

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

근거 자료

오류 기록 (본 feature 작업 중 발생)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • raw/interviews/startup-fail-fast-config-validation-2026-06-06SmartInitializingSingleton vs EnvironmentPostProcessor vs ApplicationReadyEvent, 계층형 @Validated+JSR-303 / compact-constructor throw, prod 가드의 case-sensitive profile 매칭 트레이드오프, name 기반 bean presence 검사.

Blog topics (이 작업에서 나온 글감)

  • raw/blog-topics/env-example-drift-gate-gradle-2026-06-06 — env drift gate 설계 여정(글감). ⚠ 이 노트는 1차 설계(surface=정답, registry 미강제)를 담고 있으나 2026-06-08 B 결정으로 registry=SSOT(check C)로 전환 — surface→registry SSOT 전환 자체가 더 좋은 글감(블로그 갱신 시 반영).

목표

local/dev/staging/prod 서버별 동작이 코드 수정 없이 env로 전환되어야 합니다. error exposure, logging, tracing, adapter enablement, timeout/retry/security/datasource 설정을 env contract로 고정합니다.

  • 이슈:
  • PR:

범위

포함 범위

  • env key naming 기준.
  • server profile matrix.
  • error detail exposure toggle.
  • logging/tracing toggle.
  • datasource/pool env.
  • outbound timeout/retry/circuit breaker env.
  • optional adapter enablement env.
  • security/CORS env.
  • invalid env fail-fast 기준.

제외 범위

  • secret manager 연동.
  • Kubernetes/Helm chart 작성.
  • 실제 production deployment 구성.

TODO

TODO drained 2026-05-22 — env prefix/naming, local/dev/staging/prod matrix, error exposure, logging/tracing, datasource/pool, outbound timeout/retry/circuit breaker, adapter enablement, invalid env fail-fast 모두 "결정 사항" / "판정 기준" / "Feature Flag / Reload Defaults" / "테스트 계약"에 반영됨. 잔존 TODO 없음.

Work Item Contract

각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.

field required rule
Decision yes 구현자가 선택해야 하는 기본값
Allowed yes 허용되는 예외와 조건
Forbidden yes 절대 금지되는 구현/문서 상태
Required registry update conditional error/env/header/log/metric/capability 변경 시 필수
Required contract test yes 계약 위반 시 실패해야 하는 테스트
Failure condition yes review/build에서 실패로 판정할 상태
Canonical extraction target yes wiki/projects 승급 위치

진행 중 메모

  • env는 secret만이 아니라 운영 모드 전환 장치입니다.

결정 사항 (decisions)

  • 2026-05-21: 운영 계약 전체를 env로 제어하는 방향.
  • 2026-05-22: application-owned env는 APP_ prefix를 사용.
  • 2026-06-05 (확정): env naming SSOT = env-keys.yaml registry 의 APP_*. APP_ 전면 통일(datasource/server 등 Spring-native 매핑 키도 예외 없이 APP_). 현행 코드의 DB_*/LOG_*/CORS_*/OIDC_*/SERVER_*APP_* rename 은 후속 코드 마이그레이션(§Audit ENV_PREFIX_DRIFT).
  • 2026-05-22: local/dev/staging/prod matrix를 문서와 테스트 양쪽에 둠.
  • 2026-05-22: prod profile에서 body logging과 internal error detail exposure는 기본 금지.
  • 2026-05-22: feature flag 기본값은 env-startup flag. runtime/canary flag는 optional이며 registry row, owner, rollout/rollback rule 없이는 허용하지 않음.
  • 2026-05-22: reload policy 기본값은 no runtime reload. secret/config reload가 필요하면 secrets branch와 startup validation test를 연결.
  • 2026-05-22: 모든 env 바인딩은 @ConfigurationProperties + @Validated 강제. validation 미적용 bean 등록 시 fail.
  • 2026-06-05 (확정): validation = 계층형. 단순 제약(필수·범위·정규식)은 @Validated+JSR-303 선언 기본, JSR-303 로 표현 불가한 조건부/교차필드만 compact constructor 에서 처리하되 invalid 면 throw(fail-fast). lenient default 금지(현행 CorsSettings 음수 maxAge default 는 throw 로 수정 후속).
  • 2026-06-06 (확정): env 조합 기반 fail-fast 집행 컴포넌트 = SmartInitializingSingleton validator bean(context refresh 완료 전 1회 검사 → invalid 시 throw) + contract test 이중. EnvironmentPostProcessor(bean presence 검사 불가)·ApplicationReadyEvent(늦음) 대비 선택. D8 multi-instance 5종 강제 + prod-unsafe toggle 모두 이 컴포넌트가 집행.
  • 2026-05-22: Spring Duration unit 표기 = 30s 1택. ISO-8601 PT30S 형식은 forbidden (가독성/일관성). @DurationUnit을 통한 정수만 받는 형식은 허용 (예: int 30 + @DurationUnit(SECONDS)). byte는 DataSize (10MB).
  • 2026-05-22: boolean 표기 = true/false only (1/0/on/off forbidden).
  • 2026-05-22: APP_PROFILE 우선순위 = SPRING_PROFILES_ACTIVE > APP_PROFILE (Spring native 표준 우선). 두 값 불일치 시 startup fail.
  • 2026-06-06 (확정, 위 항목 대체): APP_PROFILE 도입 포기. profile = SPRING_PROFILES_ACTIVE 단독(런타임 환경 선택자는 Spring native 영역). 우선순위/mismatch-fail 로직 미구현. SPRING_PROFILES_ACTIVE unset → startup fail 유지.
  • 2026-05-22: .env.example drift 검증 도구 = custom Gradle task verifyEnvExample (registry의 env-registry 표 vs .env.example 비교). ci-quality-gates의 .env.example drift gate가 이를 실행.
  • 2026-06-08 (확정, 위 항목 대체 — B): .env.example 두지 않음(src/.env git-tracked 단일 소스). drift 도구 = verifyEnvKeys (registry ↔ application.yml ↔ .env 3-way). registry(env-keys.yaml) = enforced SSOT: check C 가 live 모든 APP_ 키의 registry 행 존재를 build-time 강제. registry 를 as-built 55키와 전면 정렬(39행 추가 + APP_LOG_LEVEL 5분할 + APP_SHUTDOWN_TIMEOUTAPP_SERVER_SHUTDOWN_TIMEOUT).
  • 2026-05-22: multi-instance claim parsing 메커니즘 = env property APP_MULTI_INSTANCE_ENABLED boolean (default false). true로 설정 시 다음이 모두 강제: (a) ShedLock/distributed lock bean 등록, (b) Redisson RLock based cache stampede protection, (c) outbox publisher leader election (SKIP LOCKED), (d) distributed rate limiter (Redis counter), (e) migration runner platform job. flag true인데 위 5종 contract test 1개라도 없으면 startup fail-fast. feature-runtime-health-lifecycle-contract, feature-background-job-async-contract, feature-cache-consistency-contract, feature-domain-event-outbox-contract, feature-rate-limit-idempotency-contract, feature-migration-startup-contract가 모두 본 flag를 consume. APP_MULTI_INSTANCE_ENABLED row를 ca-tmpl/docs/registries/env-keys.yaml에 추가 (Phase D1 후속, 또는 별도 PR).

근거 (필수, 최소 1개+)

본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.

Source 정당화하는 결정
raw/official-docs/config-12-factor-app-config 12-factor §III
raw/official-docs/config-spring-cloud-config-server-official 중앙 git-backed + @RefreshScope; 인프라 SPOF + bootstrap 의존
raw/official-docs/config-spring-cloud-kubernetes-configmap-reload 3-level reload: refresh/restart_context/shutdown; partial-state 디버깅 + k8s lock-in
raw/official-docs/config-aws-appconfig-feature-flag-deployment managed validator + CloudWatch auto-rollback; AWS lock-in + per-call billing
raw/company-tech-blogs/config-launchdarkly-feature-flag-best-practice SaaS A/B/canary + user-targeting; 외부 의존 + flag debt + cost
raw/official-docs/config-spring-boot-externalized-configuration D4 Duration/DataSize binding 포맷, D6 SPRING_PROFILES_ACTIVE relaxed binding, D10 @ConfigurationProperties + @Validated

외부 근거 / 대안 조사 (2026-05-22 — Group G-I: Env-driven Runtime Configuration)

본 branch의 APP_ prefix + Duration 30s 1택 + boolean true/false only + no-runtime-reload + .env.example drift verify + APP_MULTI_INSTANCE_ENABLED claim parsing 결정에 대한 외부 source.

판정 기준

구분 기준
Decision 코드 수정 없이 env만으로 서버별 동작을 전환
Allowed Spring 런타임이 직접 읽는 native env(SPRING_PROFILES_ACTIVE 등)만 원래 이름 유지. application-owned env 는 예외 없이 APP_* (D2, 2026-06-05 확정 — datasource/server 등 Spring property 로 매핑되는 키도 operator-facing 이름은 APP_*)
Forbidden profile별로 같은 의미의 env key 이름을 다르게 정의
Required config APP_NAME, error exposure, log, trace, datasource, outbound timeout/retry, adapter enablement, security/CORS. profile 은 Spring-native SPRING_PROFILES_ACTIVE 필수(unset 시 startup fail) — D6 확정으로 APP_PROFILE 미사용
Failure condition required env 누락, invalid enum/range, prod unsafe toggle이 startup에서 감지되지 않으면 실패

Feature Flag / Reload Defaults

item default allowed forbidden
feature flag startup env flag runtime flag with registry owner hidden code toggle
canary out of core platform rollout with runbook undocumented partial rollout
config reload no runtime reload secret manager reload with validation silent changed behavior
flag registry env registry row required external flag system mapping unregistered flag

테스트 계약

  • required env 누락 시 startup fail-fast.
  • prod profile에서 body logging enabled면 실패.
  • prod profile에서 internal error detail exposure enabled면 실패.
  • disabled adapter가 bean/use case path에서 사용되면 실패.
  • .env/application.yml/registry 3-way 불일치(필수 env 누락, orphan, 미등록 APP_ 키) 시 verifyEnvKeys build 실패 (registry SSOT, check C).
  • feature flag registry owner 강제: 모든 runtime/canary flag(@FeatureFlag annotation 또는 APP_FEATURE_* env)는 env-keys.yaml에 row가 존재하고 owner_branch field가 비어 있지 않아야 함. 측정 방법: bean에서 @Value("${app.feature.*}") 또는 @FeatureFlag 사용 시 해당 key가 yaml에 row로 존재 verify. 미존재 또는 owner 누락 시 fail.

결정-근거 매핑

본 branch 의 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. Decision ID 는 안정적으로 유지한다. company-tech-blog 출처는 company-case-study 로 표기하며 공식 best practice 로 일반화하지 않는다.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 운영 계약 전체를 env 로 제어 (코드 수정 없이 서버별 동작 전환) N/A — 운영 계약 전체를 env 로 제어하는 1택. 대안(코드 하드코딩 / profile 별 분기 코드)은 12-factor §III 가 거부 raw/official-docs/config-12-factor-app-config.md#TWELVE-FACTOR-CONFIG-C1, raw/official-docs/config-12-factor-app-config.md#TWELVE-FACTOR-CONFIG-C2 official-reference (12-factor manifesto, not formal standard) 12-factor 본문은 prefix grouping 을 권장하지 않음 — APP_ 그룹화 정당성은 별도
D2 APP_ prefix 전면 통일 (application-owned env). SSOT = env-keys.yaml registry (2026-06-05 사용자 결정) N/A — APP_ 전면 통일 1택. prefix 없거나 다른 prefix 면 외부 의존 env(SPRING_*/JAVA_OPTS)와 시각 구분 불가. datasource/server 등 Spring-native 매핑 키도 일관성 위해 APP_ 통일(Spring 표준명 예외 두지 않음) team-decision (2026-06-05) — prefix 규약은 어떤 official source 도 명시 안 함(12-factor TWELVE-FACTOR-CONFIG-C5 는 "granular orthogonal controls" 만 언급). 일관성·시각 구분 위한 팀 결정 team-decision (no external source) 현행 코드(application.yml)는 DB_*/LOG_*/CORS_*/OIDC_*/SERVER_* 사용 → APP_* 로 rename 하는 코드 마이그레이션이 후속 작업(§Audit ENV_PREFIX_DRIFT RESOLVED). registry 가 ground-truth, 코드가 따라옴
D3 no runtime reload (Spring Cloud Config Server / k8s ConfigMap auto-reload / AppConfig 거부) 기본 no-reload. runtime reload 는 secret manager reload + startup validation test 가 연결될 때만 허용(secrets branch). 그 외 config 변경은 재배포로만 raw/official-docs/config-spring-cloud-config-server-official.md#SCC-SERVER-C1, raw/official-docs/config-spring-cloud-kubernetes-configmap-reload.md#SCK-RELOAD-C1, raw/official-docs/config-aws-appconfig-feature-flag-deployment.md#AWS-APPCONFIG-C1 (대안 capability 만 인용 — 본 결정은 대안의 trade-off 거부) official-vendor-doc (대안 capability 근거) 대안의 capability 인용은 "거부 이유" 의 사실 기반일 뿐 "no runtime reload 가 best practice" 의 증거는 아님
D4 Duration unit = 30s 1택, ISO-8601 PT30S forbidden N/A — 가독성 1택. Spring Binder 가 30s/PT30S/30 모두 허용하므로 기술 분기가 아닌 팀 규약 raw/official-docs/config-spring-boot-externalized-configuration.md#SPRING-EXTCONFIG-C1 (Spring Boot 가 30s / PT30S / 30 세 형식 모두 허용함을 확인), raw/official-docs/config-spring-boot-externalized-configuration.md#SPRING-EXTCONFIG-C3 (DataSize 10MB suffix 허용 확인) — 형식 선택 자체는 팀 가독성 규약 (UNSUPPORTED_IMPL_DECISION): Spring 공식 근거는 "두 형식이 동등하다"는 기계적 가능성만 지지하며 30s 가 더 권장된다는 증거는 없음 official-vendor-doc (포맷 허용 범위) Spring Boot 가 양쪽 모두 허용하므로 30s 1택 규약 자체는 팀 결정 — 기계적으로는 PT30S 도 동작함
D5 boolean = true/false only (1/0, on/off forbidden) N/A — 일관성 1택. Spring Binder 가 1/0·on/off 도 허용하나 contract 수준 1택 UNSUPPORTED_DECISION — 일관성 운영 결정. 외부 official 근거 없음 none branch 자체 정합성 규칙
D6 profile = SPRING_PROFILES_ACTIVE 단독 (2026-06-06 확정: APP_PROFILE 도입 포기) N/A — profile 은 application-owned config 값이 아니라 런타임 환경 선택자(Spring native 영역)이므로 SPRING_PROFILES_ACTIVE 단독. APP_PROFILE 별도 도입은 정보 이중화 + mismatch fail 비용만 추가 → 포기. SPRING_PROFILES_ACTIVE unset 시 startup fail(default profile 미부여)로 환경 명시 강제 raw/official-docs/config-spring-boot-externalized-configuration.md#SPRING-EXTCONFIG-C4 (relaxed binding: spring.profiles.activeSPRING_PROFILES_ACTIVE) + team-decision (단독 채택) official-vendor-doc (relaxed binding 메커니즘) + team-decision profile selector 는 D2 APP_ 통일의 예외(Spring 런타임이 직접 읽는 native env). 향후 product 요구로 앱이 profile 을 자체 노출/검증해야 하면 그때 APP_PROFILE 재검토
D7 env drift = custom Gradle task verifyEnvKeys (registry ↔ application.yml ↔ .env 3-way lock-step). B 확정(2026-06-08): registry = SSOT (check C), .env.example 미사용 N/A — drift 검증 도구 1택. 대안(수동 리뷰/외부 lint)은 CI 자동 강제 불가 UNSUPPORTED_DECISION — 도구 선택 운영 결정 none 외부 official 근거 없음. registry 미등록 키는 build fail(check C)
D8 APP_MULTI_INSTANCE_ENABLED flag = multi-instance contract 5종 강제 + fail-fast. 집행 = SmartInitializingSingleton validator bean + contract test 이중 (2026-06-06) false(default)면 single-instance 허용. true 면 5종 contract(lock/stampede/leader/rate-limit/migration) bean presence 를 SmartInitializingSingletongetBeanProvider 로 검사 → 1개라도 없으면 throw(startup 중단) UNSUPPORTED_DECISION — flag 자체는 branch 정합성(외부 근거 없음). 집행 메커니즘은 team-decision + UNSUPPORTED_IMPL_DECISION (아래 trade-off) none (flag) / team-decision (집행) trade-off: EnvironmentPostProcessor 는 bean 정의 이전이라 presence 검사 불가 → 부적합. SmartInitializingSingleton(refresh 완료 전, 모든 singleton 초기화 직후)이 ApplicationReadyEvent(트래픽 직전)보다 이르게 fail. contract test 는 CI 회귀 방지 이중
D9 feature flag 기본값 = env-startup flag, runtime/canary flag = registry row + owner 필수 기본 env-startup flag. runtime/canary flag 가 필요할 때만 registry row + owner_branch + rollout/rollback rule 필수(없으면 불허) raw/official-docs/config-aws-appconfig-feature-flag-deployment.md#AWS-APPCONFIG-C2 (operational flag use case), raw/official-docs/config-aws-appconfig-feature-flag-deployment.md#AWS-APPCONFIG-C5 (auto-rollback 보완 기능 비교 baseline), raw/company-tech-blogs/config-launchdarkly-feature-flag-best-practice.md#LD-FF-C1 ~ LD-FF-C5 official-vendor-doc (AppConfig 비교 baseline) + company-case-study (LaunchDarkly — 일반화 금지) LaunchDarkly 는 SaaS 사례. AppConfig capability 인용은 "ca-tmpl 이 비싼 대안을 도입하지 않는 이유" 의 비교 근거일 뿐
D10 계층형 validation (2026-06-05 사용자 결정): ① 단순 제약(필수·범위·정규식) = @Validated + JSR-303 선언 기본, ② JSR-303 로 표현 불가한 조건부/교차필드만 compact constructor 에서 처리 — 단 invalid 면 throw(fail-fast), lenient default 금지 제약 종류로 분기: 단순 제약이면 @Validated+JSR-303(선언적, startup 자동 fail). 조건부/cross-field(예: enabled=true 일 때만 origins 필수)면 constructor 에서 throw. 정상 default(예: enabled=false 시 빈 origins)는 invalid 아님 → default 허용 raw/official-docs/config-spring-boot-externalized-configuration.md#SPRING-EXTCONFIG-C5 (Spring Boot 가 @Validated 를 인식해 JSR-303 jakarta.validation 제약을 자동 실행함을 공식 확인) + team-decision (계층 분리 + no-lenient 규약) official-vendor-doc (@Validated 메커니즘) + team-decision (계층 분리 규약) 현행 CorsSettings@Validated 없이 constructor + 음수 maxAge lenient default → 본 결정에 맞게 (a) 단순 제약은 @Validated 로, (b) 음수 maxAge 는 throw 로 코드 수정 후속(§Audit VALIDATION_POLICY_DRIFT/INVALID_RANGE_LENIENT RESOLVED)

구현 가이드

naming SSOT 확정(2026-06-05): env 변수 naming = env-keys.yaml registry 의 APP_* 전면 통일(D2). 본 §의 anchor 인 ca-tmpl 실제 코드(application.yml)는 현재 DB_*/LOG_*/CORS_*/OIDC_*/SERVER_* 를 쓰므로 APP_* 로 rename 하는 코드 마이그레이션이 본 branch 구현의 일부다. 아래 표의 "현행 env" 컬럼은 마이그레이션 대상(before), 목표는 APP_*(after).

1. env → property → Settings 3층 바인딩 구조 (actually-implemented)

Trace: D1(env 전체 제어)·D2(prefix)·D10(@ConfigurationProperties) / SPRING-EXTCONFIG-C5. anchor = src/app-bootstrap/src/main/resources/application.yml L2 주석 "Mirrors src/.env … input validation lives in the *Settings records".

  • UNSUPPORTED_IMPL_DECISION: *Settings record 명명 + <module>/settings/ 패키지 위치 — 어떤 external source 도 규정 안 함. trade-off: 기존 ca-tmpl 컨벤션 답습(이미 5개 클래스가 따름) → 일관성 우선.
Layer 위치 역할 상태
A. operator env src/.env (git-tracked 단일 소스, placeholder 소비) 운영자가 세팅하는 실제 env 변수 actually-implemented (.env.example 미사용 — RESOLVED)
B. ${ENV} 브리지 application.yml env → Spring property 매핑. Spring-native(spring.*/server.*/logging.*) 또는 custom ca-skeleton.* 로 분기 actually-implemented
C. *Settings record <module>/settings/<Domain>Settings.java, @ConfigurationProperties(prefix="ca-skeleton.<group>") 타입 바인딩 + allowed-value 검증의 집(home) actually-implemented (5종, 아래)

현존 *Settings (코드 grep 확인): bootstrap/settings/BootstrapSettings(@Validated), bootstrap/settings/LoggingSettings, adapter-web/settings/PresentationSettings, adapter-web/settings/SecuritySettings, adapter-web/settings/CorsSettings. Spring property prefix 는 app.* 가 아니라 ca-skeleton.* 다.

2. fail-fast 메커니즘 (혼합 — 통일 안 됨)

Trace: D10 / SPRING-EXTCONFIG-C5 + §테스트 계약. anchor = BootstrapSettings.java, CorsSettings.java.

  • 집행 컴포넌트 확정(2026-06-06, D8): env 조합 기반 fail-fast(prod-unsafe toggle, multi-instance 5종)는 SmartInitializingSingleton validator bean 이 context refresh 완료 전 1회 검사 → invalid 면 throw. (EnvironmentPostProcessor 는 bean presence 검사 불가라 부적합, ApplicationReadyEvent 는 늦음). contract test 로 회귀 방지 이중.
검증 스타일 메커니즘 예시 상태
필수-무default 필드 @Validated + @NotBlank/@NotNull → 누락/blank 시 BindValidationException startup fail BootstrapSettings.appName actually-implemented
단순 제약(필수·범위·정규식) @Validated + JSR-303(@NotBlank/@Min/@Positive 등) → startup 자동 fail-fast 신규 작성 기준(D10 ①). BootstrapSettings 가 선례 planned(CorsSettings.maxAge 등에 적용 후속)
조건부/교차필드 compact constructor 에서 검사 후 invalid 면 throw(fail-fast, lenient 금지) CorsSettings(enabled=true+empty origins). 단 음수 maxAge 는 현행 lenient default → throw 로 수정 후속 actually-implemented(스타일) / lenient 부분은 planned 수정
prod-unsafe / multi-instance fail env 조합(APP_LOG_BODY*+prod, 또는 APP_MULTI_INSTANCE_ENABLED=true+5종 bean) 위반 시 SmartInitializingSingleton validator 가 throw ProdProfileSafetyTest + multi-instance contract test (미존재) planned (집행 컴포넌트는 확정, 코드 미작성)

정책 확정(2026-06-05, D10): 단순 제약 = @Validated+JSR-303, 조건부/교차필드 = constructor + throw(lenient 금지). 따라서 신규 *Settings 작성 기준이 명확하다. 현행 CorsSettings 는 (a) 단순 제약을 @Validated 로 끌어올리고 (b) 음수 maxAge lenient default 를 throw 로 바꾸는 코드 수정이 후속(VALIDATION_POLICY_DRIFT/INVALID_RANGE_LENIENT RESOLVED — §Audit).

3. profile 해석 (actually-implemented, 단 단일화)

Trace: D6 / SPRING-EXTCONFIG-C4. anchor = application.yml L16-18 spring.profiles.active: ${SPRING_PROFILES_ACTIVE}.

현행 코드는 SPRING_PROFILES_ACTIVE 단독 사용 — D6 확정(2026-06-06)과 정합. APP_PROFILE 은 도입하지 않으므로 우선순위/mismatch-fail 로직은 구현 대상 아님. application.yml L18 spring.profiles.active: ${SPRING_PROFILES_ACTIVE} 가 SSOT이며, unset 시 placeholder 미해소로 startup fail(default profile 미부여) — actually-implemented.

4. env drift 검증 — verifyEnvKeys 3-way lock-step (actually-implemented)

Trace: D7. anchor = src/build.gradle verifyEnvKeys task + docs/registries/env-keys.yaml.

  • UNSUPPORTED_IMPL_DECISION: gate 형태(custom Gradle task)는 도구 선택 운영 결정(D7 자체 UNSUPPORTED). trade-off: registry ↔ application.yml ↔ .env 3-way 를 CI 에서 자동 강제.

B 확정(2026-06-08): registry = enforced SSOT. .env.example 은 두지 않음(src/.env 가 git-tracked 단일 소스 → redacted 사본 중복). verifyEnvKeys 게이트 3-check: (A) application.yml 의 required placeholder(inline default 없는 ${VAR}) ⊆ .env, (B) .env orphan 0, (C) .env 의 모든 APP_ 키 ∈ env-keys.yaml(registry SSOT 강제). SPRING_* native 는 미추적. checkdependsOn. 게이트 통과: verifyEnvKeys: OK — 68 env keys, 64 required placeholders covered, 55 APP_ keys registered.

5. 코드 마이그레이션 체크리스트 (본 branch 결정의 ca-tmpl 코드 반영)

본 branch 의 확정 결정이 만드는 실제 코드 작업. 모두 ground-truth 대조로 도출됨(§Audit).

# 작업 근거 결정 파일
M1 env 변수 DB_*/LOG_*/CORS_*/OIDC_*/SERVER_*APP_* rename (registry env-keys.yaml 이름에 정렬). SPRING_PROFILES_ACTIVE 등 Spring native 는 유지 D2 application.yml, src/.env
M2 application.yml L147 ca-skeleton.cmd.app-nameca-skeleton.bootstrap.app-name (latent bug fix) Advisory application.yml
M3 CorsSettings: 단순 제약을 @Validated+JSR-303 로, 음수 maxAge lenient default → throw D10 CorsSettings.java
M4 SmartInitializingSingleton validator bean 작성: prod-unsafe + APP_MULTI_INSTANCE_ENABLED 5종 bean presence 검사 → throw D8 app-bootstrap (신규)
M5 verifyEnvKeys Gradle task: registry ↔ application.yml ↔ .env 3-way lock-step (check C = registry SSOT 강제). .env.example 미사용 D7 build.gradle, env-keys.yaml

OUT_OF_BRANCH_SCOPE: adapter on/off 3-layer(@ConditionalOnProperty + ArchUnit static + AdapterDisabledException)는 governing doc §29 G-I 영역이지만 owner 는 raw/branch-notes/feature-integration-adapter-templates — 본 §에 명세 남기지 않음(§Coverage 위임 행 참조).

구현 완료 기록 (2026-06-06 1차 + 2026-06-08 B) — M1~M5 actually-implemented

ca-tmpl src/ 실 코드에 M1~M5 전부 반영. ./gradlew check (전 모듈 test + ArchUnit CleanArchitectureTest + verifyCleanArchitectureDependencies + verifyEnvKeys) BUILD SUCCESSFUL. 리뷰 체인 ca-architect-sentinel / ca-spec-reviewer / ca-quality-reviewer 모두 PASS. 2026-06-08 B 후속: registry = enforced SSOT 로 전환 — env-keys.yaml as-built 55키 전면 정렬(39행 추가 + 2 이름충돌 해소) + verifyEnvKeys check C 추가. 독립 검증: verifyEnvKeys BUILD SUCCESSFUL(55 APP_ keys registered), :app-bootstrap:test·:adapter-web:test BUILD SUCCESSFUL, live APP_ 키 missing 0.

# 작업 상태 핵심 구현 사실
M1 env DB_*/LOG_*/CORS_*/OIDC_*/SERVER_*APP_* actually-implemented src/.env + application.yml placeholder 전면 rename. scope = audit ENV_PREFIX_DRIFT 의 5 prefix 정확히 (PRESENTATION_API_BASE_PATH·SECURITY_PUBLIC_PATHS 는 목록 외라 유지). SERVER_*APP_SERVER_*(D2 전면통일). 매핑: DB_→APP_DATASOURCE_, LOG_→APP_LOG_, CORS_→APP_SECURITY_CORS_(ORIGINS/ALLOW_CREDENTIALS/MAX_AGE 는 registry 명), OIDC_→APP_SECURITY_JWT_. 정직성 위해 SecuritySettings/LoggingSettings 로그 문자열 + 매칭 test 단언도 갱신. SPRING_*·SPRING_PROFILES_ACTIVE native 유지.
M2 ca-skeleton.cmd.app-nameca-skeleton.bootstrap.app-name actually-implemented application.yml L147 + application-test.yml 둘 다 수정. latent bug(클래스는 ca-skeleton.bootstrap 바인딩)는 full-context 기동에서만 발현했던 것 — @WebMvcTest slice 라 기존 test 는 통과했었음.
M3 CorsSettings 계층형 validation actually-implemented / locally-verified @Validated + @PositiveOrZero(maxAge<0 → BindValidationException startup fail). cross-field(enabled=true+empty origins)는 compact constructor throw(D10 prose 예시, 기존 warn+fail-closed 대체). logger 제거. CorsSettingsTest 4 메서드 재작성(ValidationAutoConfiguration 주입).
M4 SmartInitializingSingleton startup 가드 + 플래그 도입 actually-implemented / locally-verified 신규 StartupSafetyValidator(bootstrap.runtime) + RuntimeSafetyConfig(@Bean wiring) + RuntimeSafetySettings(@ConfigurationProperties("ca-skeleton.runtime")). prod profile + (APP_ERROR_DETAIL_EXPOSURE_ENABLED|APP_LOG_BODY_CAPTURE_ENABLED)=true → throw. APP_MULTI_INSTANCE_ENABLED=true + 5종 coordination bean(name 기반 presence) 누락 → throw. 세 플래그를 .env/application.yml/application-test.yml 에 신규 wiring. StartupSafetyValidatorTest 8 메서드. profile 매칭은 의도적 case-insensitive(prod 오타 가드).
M5 env drift Gradle task verifyEnvKeys actually-implemented / locally-verified B 확정(2026-06-08): registry = enforced SSOT. .env.example 미사용(src/.env 가 git-tracked 단일 소스). 게이트 3-check: (A) application.yml required placeholder ⊆ .env, (B) .env orphan 0, (C) .env 의 모든 APP_ 키 ∈ env-keys.yaml (registry 미등록 키는 build fail; SPRING_* 미추적). checkdependsOn. verifyEnvKeys: OK — 68 env keys, 64 required placeholders covered, 55 APP_ keys registered. (초기 2026-06-06 설계는 surface-only A/B 였으나 2026-06-08 B 결정으로 check C + registry 전면 정렬 추가 — 아래 결정 노트.) raw/blog-topics/env-example-drift-gate-gradle-2026-06-06

registry(docs/registries/env-keys.yaml) 정렬 — 2026-06-06 1차 + 2026-06-08 B 완성:

  • 1차(2026-06-06): APP_PROFILE row 제거(D6 폐기), SERVER_PORTAPP_SERVER_PORT(D2), APP_MULTI_INSTANCE_ENABLED 추가(D8), 헤더 convention/Last-updated 갱신.
  • B(2026-06-08): registry 를 as-built 55 APP_ 키와 전면 정렬. 누락 39행 추가(datasource extras 7 → env-driven, server 12 → env-driven, log granular 17 → log-management, CORS 3 → security). 이름 충돌 해소: APP_LOG_LEVEL 단일 → APP_LOG_LEVEL_{ROOT,APP,SPRING,WEB,SQL} 5분할(code 이름 채택), APP_SHUTDOWN_TIMEOUT(container-runtime) → APP_SERVER_SHUTDOWN_TIMEOUT(env-driven, termination-grace 정렬은 container-runtime cross-ref 주석 보존). 독립 검증: live APP_ 55키 전부 registry 존재(missing 0).

결정 노트(2026-06-08, B = registry SSOT): 초기 2026-06-06 구현은 "drift 정답 소스 = application.yml surface, registry 1:1 강제 불가"로 갔으나(check A/B only), 사용자가 B(registry = enforced SSOT) 선택. 따라서 ① registry 를 as-built 와 전면 정렬, ② verifyEnvKeys 에 check C(모든 live APP_ 키 ∈ registry) 추가, ③ build.gradle 주석을 "registry SSOT lock-step"으로 갱신. cross-branch 이름/owner 2건은 사용자 결정(이름=code 채택, APP_SERVER_* owner=env-driven). M1 의 SERVER_* rename 은 D2 전면통일 우선(registry 2026-05-22 주석/governing §9 의 "SERVER_* native"는 stale).

엣지·실패·의존

R4 캡처용. 정상 경로 외 실패/엣지 + 다른 계약 의존.

  • 실패·엣지 경로:
    • APP_NAME 누락/blank → BindValidationException, context refuses to start (actually-implemented, BootstrapSettings).
    • CORS_ENABLED=true + CORS_ALLOWED_ORIGINS empty → log.warn + 모든 브라우저 호출 reject(fail-open 아님, fail-closed). actually-implemented(CorsSettings).
    • invalid range(음수 APP_SECURITY_CORS_MAX_AGE) → D10 확정에 따라 throw(fail-fast). 현행 코드의 lenient default(3600)+warn 는 throw 로 수정 후속.
    • prod profile + body logging / internal error detail exposure ON → fail 기대이나 enforcing test 부재(planned).
    • SPRING_PROFILES_ACTIVE unset → ${SPRING_PROFILES_ACTIVE} placeholder 미해소 → startup fail(default profile 없음). 엣지: 의도적 default 미부여인지 확인 필요.
  • 다른 계약 의존 (env 값 semantics 위임 — 본 branch 는 env→Settings 바인딩·검증 계약을 소유, 값 정책은 owner branch):

검증해야 할 주장

Claim Why uncertain How to verify Status
ca-tmpl APP_ prefix 가 12-factor "granular orthogonal controls" 와 양립 12-factor 는 grouping 을 권장하지 않음 — prefix grouping 이 orthogonality 를 약화시키는지 불확실 env-keys.yaml registry 에 각 key 의 orthogonality 명시 + ArchUnit/registry-scan 으로 cross-coupling 탐지 needs-confirmation
.env.example drift verifier 가 registry 와 100% 일치 보장verifyEnvKeys 가 registry↔application.yml↔.env 100% 일치 강제 (해소) verifyEnvKeys check C 가 live APP_ 키 ⊆ registry 강제 + 독립 검증 missing 0 actually-implemented (B, 2026-06-08)
APP_MULTI_INSTANCE_ENABLED=true 시 5종 contract test 가 모두 fail-fast 동작 5종 contract test 가 아직 작성되지 않음 feature-runtime-health-lifecycle / feature-cache-consistency 등 5 branch 의 contract test 작성 후 통합 검증 planned
SPRING_PROFILES_ACTIVEAPP_PROFILE 불일치 시 startup fail wont-fix (2026-06-06: APP_PROFILE 도입 포기, D6)
prod profile 에서 body logging / internal error detail exposure enabled 시 startup fail 구현 미확인 ProdProfileSafetyTest contract test 구현 — SPRING_PROFILES_ACTIVE=prod + APP_LOG_BODY_CAPTURE_ENABLED=true 조합에서 SmartInitializingSingleton validator 가 startup fail 시키는지 verify planned
feature flag registry owner 강제 env-keys.yaml registry schema 미확정 env-keys.yaml schema 에 owner_branch field 추가 + @FeatureFlag annotation processor 가 yaml 와 cross-check planned
AppConfig / LaunchDarkly 채택 trigger (50+ flag 또는 product team 운영) branch 가 의도적으로 위임한 영역 flag 수가 50 초과하거나 A/B canary 요구가 발생할 때 별도 검토 trigger needs-confirmation

관심사 커버리지

기준: governing_docs = wiki/projects/ca-tmpl/config-and-adapter-templates.md (canonical §9 Env config + §29 G-I Adapter). 상태: covered-here / delegated / missing. 기준 SSOT: rules/coverage-gate.md.

관심사 상태 owner 심각도 근거
env prefix / naming 계약 covered-here OK D2 — APP_* 통일 확정(2026-06-05). 코드 rename 후속 작업
Duration 30s 포맷 covered-here OK D4 / SPRING-EXTCONFIG-C1,C3
boolean true/false only covered-here OK D5
no-runtime-reload covered-here OK D3
env drift 검증 covered-here OK D7 — verifyEnvKeys 3-way(registry SSOT, check C) actually-implemented (B, 2026-06-08)
@ConfigurationProperties + @Validated covered-here OK D10 — 계층형 validation 확정(2026-06-05). CorsSettings 코드 수정 후속
profile 해석/matrix covered-here OK D6 — SPRING_PROFILES_ACTIVE 단독 확정(2026-06-06)
error detail exposure toggle covered-here OK §테스트 계약 (registry APP_ERROR_DETAIL_EXPOSURE_ENABLED; 코드 SERVER_ERROR_INCLUDE_*)
body logging toggle covered-here OK §테스트 계약 (registry APP_LOG_BODY_CAPTURE_ENABLED)
datasource / pool env covered-here OK registry APP_DATASOURCE_* / 코드 DB_* (§1 표)
required env fail-fast covered-here OK D10 / BootstrapSettings
adapter on/off — Layer1 @ConditionalOnProperty delegated raw/branch-notes/feature-integration-adapter-templates OK governing §29 G-I; §구현 가이드 OUT_OF_SCOPE 주석
adapter on/off — Layer2 ArchUnit static delegated raw/branch-notes/feature-integration-adapter-templates OK governing §29 G-I
adapter on/off — Layer3 AdapterDisabledException delegated raw/branch-notes/feature-integration-adapter-templates OK governing §29 G-I
outbound timeout/retry/CB env 값 delegated raw/branch-notes/feature-outbound-http-client-baseline OK registry owner_branch
tracing enable/sample-rate env 값 delegated raw/branch-notes/feature-distributed-tracing-contract OK registry owner_branch
log level/sampling/file env 값 delegated raw/branch-notes/feature-log-management-contract OK registry owner_branch
security/CORS/JWT env 값 delegated raw/branch-notes/feature-security-operational-baseline OK registry owner_branch
secret-classified env (DB_PASSWORD, JWT key) delegated raw/branch-notes/feature-secrets-config-source-contract OK registry owner_branch
cache redis env 값 delegated raw/branch-notes/feature-cache-consistency-contract OK registry owner_branch

missing: 0 — governing doc 의 모든 관심사가 owner 보유. env naming(D2)·validation(D10)·profile(D6)·D8 집행·prefix bug·env drift(D7) 전부 RESOLVED + actually-implemented. 잔여 🟡 0건. Blocking 아님.

Audit & Findings (2026-06-05 — /branch-spec ca-tmpl ground-truth 대조)

ca-tmpl src/ + docs/registries/ 를 읽기 전용으로 대조해 발견한 drift. 사용자 작성 결정 영역이므로 자동 rewrite 하지 않고 정합 권고만 남긴다(CLAUDE.md §11, branch-spec §2). 해소는 /branch-spec 재실행 또는 사용자 결정.

라벨 내용 증거 권고 (사용자 결정)
ENV_PREFIX_DRIFT RESOLVED (2026-06-05) env 변수 naming 이 3-way 불일치였음: 노트 D2 / env-keys.yaml(APP_*) / 코드 application.yml(DB_*·LOG_*·CORS_*·OIDC_*·SERVER_*) registry 에 DB_URL 등 0건, application.yml 에 APP_DATASOURCE 등 0건 (grep) 결정: APP_* 전면 통일, SSOT = registry(D2). 코드(application.yml+src/.env)를 APP_* 로 rename 하는 것이 본 branch 구현 작업의 일부
REGISTRY_CODE_DRIFT RESOLVED (B, 2026-06-08) env-keys.yaml 이 as-built env 이름/surface 와 매칭 안 됐음(48행 vs 55키, granular 키 다수 누락) 위와 동일 grep registry 를 as-built 55키와 전면 정렬(39행 추가 + 2 이름충돌 해소) + verifyEnvKeys check C 가 registry↔.env 를 CI 강제. 독립 검증 missing 0
REGISTRY_GITIGNORED ACCEPTED (사용자 결정 2026-06-09) ca-tmpl .gitignore/docs 전체를 ignore(CLAUDE.md/.claude/.codex 등 AI 툴링과 함께한 의도적 repo 정책) → SSOT registry(env-keys.yaml)가 version-control 안 됨. drift 가드(check C / RegistryTest)는 파일 부재 시 assumeTrueSKIP(통과 아님). .gitignore:2:/docs, git ls-files 미추적 사용자 결정(2026-06-09): 현 정책 유지 — registry 는 local dev artifact, docs/ 전체 gitignore 유지. 한계 수용: fresh clone/CI(docs 부재)에서 registry drift 가드는 강제되지 않고 SKIP. 따라서 "registry=enforced SSOT"는 registry-present(로컬) 환경에서만 성립함을 명시. (재고 시: docs/registries 만 un-gitignore, 또는 wiki SSOT→mirror CI 동기화.)
VALIDATION_POLICY_DRIFT RESOLVED (2026-06-05) D10 "모든 바인딩 @Validated 강제" vs CorsSettings@Validated 없이 constructor 검증 CorsSettings.java(no @Validated), BootstrapSettings.java(@Validated) 결정: 계층형 — 단순 제약 @Validated+JSR-303, 조건부/교차필드만 constructor + throw(D10). CorsSettings 코드 조정 후속
PROFILE_DUALITY_DRIFT RESOLVED (2026-06-06) D6 의 APP_PROFILE env 가 코드에 부재(SPRING_PROFILES_ACTIVE 단독)였음 application.yml L18 결정: APP_PROFILE 도입 포기, SPRING_PROFILES_ACTIVE 단독(D6). mismatch-fail 로직 미구현, Claims 행 wont-fix
ENV_FILE_NAME_DRIFT RESOLVED (2026-06-06) D7 .env.example vs 실제 src/.env application.yml L2 주석 결정: .env.example 두지 않고 src/.env(tracked) 단일 소스로 통일(사용자 2026-06-06). drift 게이트는 verifyEnvKeys(.env↔application.yml).
INVALID_RANGE_LENIENT RESOLVED (2026-06-05) §판정 기준 "invalid range → fail" vs CorsSettings 음수 maxAge → default+warn(lenient) CorsSettings compact ctor 결정: invalid range → throw(fail-fast)(D10). CorsSettings 음수 maxAge default 를 throw 로 수정 후속
SETTINGS_PREFIX_INTERNAL_DRIFT 진단 완료 (2026-06-06) — latent bug application.yml L147 ca-skeleton.cmd.app-name 이 stale. 클래스+테스트는 ca-skeleton.bootstrap.app-name 로 일관(다른 4개 *Settingsca-skeleton.<group> 컨벤션). 실제 기동 시 BootstrapSettings.appName 미바인딩 → @NotBlank startup fail 날 버그 BootstrapSettings.java+BootstrapSettingsTest.java(both ca-skeleton.bootstrap) vs application.yml L147 (ca-skeleton.cmd) 클래스가 SSOT. ca-tmpl application.yml L147 cmd:bootstrap: 수정(코드 후속 bugfix). 신규 *Settingsca-skeleton.<group> 컨벤션
LENIENT_DEFAULT_EXCEPTIONS ACCEPTED (사용자 결정 2026-06-09) D10 "lenient default 금지"는 CorsSettings 에 적용(throw 로 수정, RESOLVED)했으나, LoggingSettings(bootstrap.settingsSecuritySettings(adapter-web.settings)는 여전히 warn-and-default. 감사가 D10 위배로 잡음. 그러나 둘 다 careless 가 아니라 문서화된 근거 있는 예외: (1) LoggingSettings — logback 이 <springProperty>이미 자기 default 로 바인딩한 뒤라 record 는 operator 경고 surface 일 뿐(여기서 throw 해도 logback 은 이미 진행). (2) SecuritySettings L28 — "audience 없음 → audience 검증 skip" 은 선택적 보안 기능 토글이지 typo 마스킹 fallback 이 아님. LoggingSettings.java(File/Async/Json compact ctor log.warn+default), SecuritySettings.java:28 사용자 결정(2026-06-09): lenient 유지 — D10 은 "의미 있는 invalid 를 silent default 로 가리지 말 것"이 취지이며, 위 둘은 owning-library(logback)/optional-feature 라 예외가 정당. D10 을 보편 강제가 아니라 예외 명시 규약으로 정합. (audience 를 prod 필수로 하려면 별도 prod-profile fail-fast 결정 — 본 branch 범위 밖.)
REGISTRY_VALIDATION_UNENFORCED RESOLVED (2026-06-09) registry env-keys.yaml 가 high-risk numeric 키에 validation: positive_int/non_negative_int 컬럼을 선언하나 코드가 강제 안 함(Spring-native 로 흘러가 Hikari/Tomcat 가 늦게·cryptic 하게 reject). 감사 "fictional validation columns". RuntimeNumericBoundsValidator.java(신규), RuntimeSafetyConfig(@Bean) 신규 RuntimeNumericBoundsValidator(SmartInitializingSingleton, 고위험 numeric만) 가 resolved Spring property 를 읽어 범위 위반 시 fail-fast — APP_* 키 이름 명시 메시지. pool max/min-idle, tomcat max/min-spare/max-conn/accept-count 6키. RuntimeNumericBoundsValidatorTest 4 메서드(:app-bootstrap:test 144/144 green). 이로써 positive_int/non_negative_int 컬럼이 실제 강제. log. 등 logback-owned·Duration 키는 owning-lib 위임(범위 밖).*

마주친 문제

  • 아직 없음.

관련 일일 노트

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: M1 env APP_* 전면통일(.env/application.yml/Settings 로그문자열/test), M2 ca-skeleton.bootstrap.app-name bug fix, M3 CorsSettings 계층형 validation, M4 StartupSafetyValidator(prod-unsafe + multi-instance presence) + 3 플래그 wiring, M5 verifyEnvKeys 3-way gate(registry SSOT, check C), registry env-keys.yaml as-built APP_ 키 전면 정렬(B, 2026-06-08: 39행 추가 + 2 이름충돌 해소), M6 RuntimeNumericBoundsValidator(2026-06-09 — 고위험 numeric pool/tomcat 6키 fail-fast, registry positive_int/non_negative_int 컬럼 실제 강제, RuntimeNumericBoundsValidatorTest 4) + RuntimeSafetyConfig @Bean wiring.
    • 2026-06-09 갱신: live APP_ 키 수 = 57(검증: grep '^APP_' src/.env | sort -u | wc -l). 본문의 historical "55"(2026-06-08 게이트 출력)는 그 시점 값 — 현재 57. lenient 정책은 LENIENT_DEFAULT_EXCEPTIONS(§Audit) 로 정합(LoggingSettings/SecuritySettings 의도적 예외).
    • locally-verified 항목: ./gradlew check BUILD SUCCESSFUL(전 모듈 test + ArchUnit + verifyCleanArchitectureDependencies + verifyEnvKeys), verifyEnvKeys drift 주입→FAIL / clean→OK + check C 단독 발화 확인, :app-bootstrap:test·:adapter-web:test BUILD SUCCESSFUL(독립 재검증), live APP_ 55키 registry missing 0, 리뷰 체인(sentinel/spec/quality) 전부 PASS.
    • prod-verified 항목: 없음(skeleton, prod 배포 이력 없음).
  • 추출하지 않을 항목 (planned / documented-only / abandoned): adapter on/off 3-layer(owner: integration-adapter-templates), outbound/tracing/cache/security/secret 값 semantics(각 owner branch), multi-instance 5종 contract bean 실제 구현(각 owner branch, 본 branch 는 presence 계약만 소유), APP_PROFILE(D6 abandoned).