init: llm-wiki-haness 하네스 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:21:35 +09:00
parent 42bf3db4fd
commit 6c53ded9cb
2436 changed files with 194486 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
# Reserved for the transactional vault migration.
+142
View File
@@ -0,0 +1,142 @@
---
title: 2026-05-27 일일 노트
source_type: daily-note
status: raw
tags: [daily, ca-tmpl, ca-skeleton, clean-architecture]
date: 2026-05-27
branches: [
feature-skeleton-package-blueprint-contract
]
---
# 2026-05-27
> Layer: `raw/daily-notes/` — 그날의 혼합 일일 기록. 그 자체는 wiki로 옮기지 않으며, `/ingest`가 promotable 항목만 추출.
## 활성 브랜치
ca-tmpl Phase C2 실 코드 진입을 위한 첫 착수 브랜치. 오늘은 전체 roadmap 구현이 아니라, 첫 브랜치 범위를 확정하고 시작 조건을 정리한다.
- `feature-skeleton-package-blueprint-contract` (planned) — [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
## 오늘의 계획
- [ ] [ca-tmpl] Phase C2 전체 구현 순서를 dependency-first roadmap으로 고정한다.
- [ ] [feature-skeleton-package-blueprint-contract] 오늘 실제 착수 범위를 package/module skeleton으로 제한한다.
- [ ] [feature-skeleton-package-blueprint-contract] 완료 조건을 `actually-implemented``locally-verified` 증거로 갱신할 수 있게 정의한다.
## 구현 순서 메모 / Phase C2 roadmap
> 아래 목록은 오늘 하루 작업량이 아니라 Phase C2 전체 roadmap이다. 오늘은 1단계의 첫 브랜치 착수까지만 현실적인 범위로 둔다.
### 0. 전제
- ca-tmpl은 현재 Phase A-E 문서/설계 완료, Phase C2 실 코드 미진입 상태다.
- `wiki/projects/ca-tmpl.md` 기준으로 모든 16개 의사결정 문서는 `documented-only`다.
- 따라서 개발 브랜치는 "기능 추가"가 아니라 `documented-only` 결정을 실제 코드와 테스트 증거로 승급시키는 작업이다.
### 1. Foundation / package skeleton
1. `feature-skeleton-package-blueprint-contract`
2. `feature-architecture-enforcement-rules`
3. `feature-application-port-usecase-contract`
이 단계에서 module/package layout, use case/port 기본 타입, ArchUnit boundary rule을 먼저 만든다. 이후 작업은 이 구조를 기준으로 파일 위치와 의존 방향을 맞춘다.
### 2. API boundary + error envelope
1. `feature-boundary-validation-mapping-contract`
2. `feature-api-contract-baseline`
3. `feature-operational-error-observability-foundation`
Controller DTO validation, request/response mapper, structured success/error envelope, exception ownership을 먼저 고정한다. 이후 persistence/outbound/runtime 오류도 같은 envelope와 category로 흘려보낼 수 있어야 한다.
### 3. Observability baseline
1. `feature-log-management-contract`
2. `feature-distributed-tracing-contract`
3. `feature-metrics-alerting-contract`
4. `feature-operational-runbook-contract`
로그/MDC/trace/metric key는 후속 adapter와 background job에서 공통으로 소비한다. runbook은 stub로 먼저 두고, 실제 장애 재현이 생기면 갱신한다.
### 4. Config + optional adapter switch
1. `feature-env-driven-runtime-configuration`
2. `feature-secrets-config-source-contract`
3. `feature-integration-adapter-templates`
4. `feature-outbound-http-client-baseline`
환경 변수와 secret 분류, adapter on/off 조건, outbound timeout/retry 기본값을 묶는다. 이 단계가 끝나야 DB/cache/message adapter를 같은 방식으로 붙일 수 있다.
### 5. Data consistency + sample domain fixture
1. `feature-persistence-failure-baseline`
2. `feature-transaction-concurrency-contract`
3. `feature-cache-consistency-contract`
4. `feature-domain-event-outbox-contract`
5. `feature-sample-domain-contract-fixture`
sample-ticket fixture를 사용해 persistence failure, transaction boundary, cache degradation, outbox publish 흐름을 검증한다. 이때 sample은 비즈니스 기능이 아니라 contract 검증 fixture로만 둔다.
### 6. Security + tenant + idempotency
1. `feature-security-operational-baseline`
2. `feature-management-actuator-security-contract`
3. `feature-tenant-context-policy`
4. `feature-repository-access-permission-contract`
5. `feature-rate-limit-idempotency-contract`
인증/인가/actuator 분리, tenant context propagation, repository access capability, idempotency key 저장소를 묶어 검증한다.
### 7. Runtime + lifecycle + migration
1. `feature-runtime-health-lifecycle-contract`
2. `feature-migration-startup-contract`
3. `feature-container-runtime-contract`
4. `feature-background-job-async-contract`
health endpoint, readiness/startup, migration ordering, container shutdown, async context propagation을 검증한다. 로컬 docker-compose에서 재현 가능한 확인 절차를 남긴다.
### 8. Governance + CI quality gate
1. `feature-contract-registry-governance`
2. `feature-contract-verification-test-suite`
3. `feature-ci-quality-gates-contract`
4. `feature-build-release-supply-chain-contract`
5. `feature-developer-experience-contract`
6. `feature-implementation-readiness-scorecard`
registry yaml 기반 generated constants, contract test suite, CI gate, supply chain metadata, README/onboarding, readiness scorecard를 마지막에 묶는다. 앞 단계의 산출물이 있어야 gate가 실제로 검증할 대상이 생긴다.
## 한 일
- [ca-tmpl] branch-note 기반 Phase C2 구현 순서 초안을 daily-note에 기록했다.
- [ca-tmpl] 오늘 하루 범위와 Phase C2 전체 roadmap을 분리했다.
## 배운 점
> wiki/concepts/로 promotable 후보
- ca-tmpl의 다음 단계는 새 설계가 아니라 `documented-only` 결정을 코드와 로컬 검증 증거로 승급시키는 단계다.
- 구현 순서는 domain feature가 아니라 contract dependency 순서로 잡아야 한다.
## 트러블슈팅
- 없음. 오늘 기록은 개발 진입 순서 정리이며 코드 실행은 아직 하지 않음.
## 면접·포트폴리오로 옮길 만한 것
> 후보 표기만. daily-note에서 `wiki/interview/` 또는 `wiki/portfolio/`를 직접 만들지 않는다.
- "documented-only 설계를 actually-implemented로 승급시키는 절차" → `wiki/projects/ca-tmpl.md` 갱신 후 파생 가능.
- "Clean Architecture skeleton에서 구현 순서를 contract dependency 기준으로 잡은 이유" → Phase C2 로컬 검증 후 portfolio 후보.
## 내일로 넘긴 것
- [ca-tmpl] `/home/donghyeon/workspace/ca-tmpl/`에서 `feature-skeleton-package-blueprint-contract` 구현 시작.
- [ca-tmpl] 첫 구현 브랜치 완료 후 `wiki/projects/ca-tmpl/clean-architecture-package-layout.md`의 evidence section 갱신.
## 잡담 / 회의 / 기타
- 사용자가 "시간이 너무 지나서 개발 단계로 들어가야 한다"고 판단. 오늘 기록은 그 전환점을 남기는 목적이다.
@@ -0,0 +1,65 @@
---
title: 2026-05-28 일일 노트
source_type: daily-note
status: raw
tags: [daily, ca-tmpl, ca-skeleton, clean-architecture, archunit]
date: 2026-05-28
branches: [
feature-architecture-enforcement-rules
]
---
# 2026-05-28
> Layer: `raw/daily-notes/` — 그날의 **혼합 일일 기록**. 그 자체는 wiki로 옮기지 않으며, `/ingest`가 promotable 항목만 추출해 다른 wiki 영역으로 보낸다. 원본은 raw에 영구 보관.
## 활성 브랜치
`feature-skeleton-package-blueprint-contract` 다음으로 이어서 개발할 브랜치는 `feature-architecture-enforcement-rules`다. 오늘은 Phase C2 전체 roadmap을 처리하지 않고, 방금 구현된 multi-module skeleton의 경계가 깨지지 않도록 architecture enforcement를 실제 코드와 테스트로 붙이는 데 집중한다.
- `feature-architecture-enforcement-rules` (local-verified, not merged) — [[raw/branch-notes/feature-architecture-enforcement-rules]]
## 오늘의 계획
브랜치별 항목은 `[branch-name]` 프리픽스. 오늘 개발 범위는 아래 3개로 제한한다.
- [x] [feature-architecture-enforcement-rules] ca-tmpl repo의 현재 module dependency graph를 확인하고, 허용/금지 dependency matrix를 `domain-core`, `application-core`, `adapter-*`, `app-bootstrap`, `shared-contract`, `sample-ticket` 기준으로 확정한다.
- [x] [feature-architecture-enforcement-rules] Gradle dependency guardrail을 구현해서 `domain-core -> Spring/adapter`, `application-core -> adapter-*`, production module -> `sample-ticket` 의존을 차단한다.
- [x] [feature-architecture-enforcement-rules] ArchUnit test를 추가해서 forbidden import/annotation 규칙을 검증하고, 최소한 architecture test와 관련 Gradle verification task를 로컬에서 실행한다.
## 한 일
- ca-tmpl `src/build.gradle``verifyCleanArchitectureDependencies`를 보강해 declared module coverage와 forbidden project dependency 메시지를 강화했다.
- ca-tmpl `CleanArchitectureTest`에 application `@Transactional` 금지, controller direct domain response 금지, mapper boundary, shared-contract package allowlist 규칙을 추가했다.
- 임시 위반 코드로 신규 ArchUnit 규칙 실패를 확인한 뒤 제거했다.
- 임시 `app-bootstrap -> sample-ticket` 의존으로 Gradle dependency verifier 실패를 확인한 뒤 제거했다.
- `cd src && ./gradlew :app-bootstrap:test verifyCleanArchitectureDependencies`, `cd src && ./gradlew test`를 실행해 로컬 검증을 마쳤다.
## 배운 점
> wiki/concepts/로 promotable 후보
- multi-module Clean Architecture에서는 package 위치보다 module dependency direction이 1차 경계다.
- `sample-ticket`은 template fixture/reference module로 유지하되, production module이 sample에 의존하지 못하도록 enforcement rule이 필요하다.
## 트러블슈팅
> raw/errors/ 또는 wiki/projects/ 또는 관련 branch-note의 "마주친 문제" 섹션으로 promotable 후보
- Gradle wrapper가 `~/.gradle` lock 파일을 쓰려 하면서 sandbox 기본 실행에서는 `Read-only file system` 오류가 났다. 검증 명령은 승인된 escalated 실행으로 재수행했다. 상세: [[raw/errors/gradle-wrapper-readonly-cache-2026-05-28]]
- repo 내부 `docs/superpowers/plans/2026-05-28-architecture-enforcement-rules.md``.gitignore``/docs` 규칙 때문에 git 변경 목록에 잡히지 않는다. 최종 wiki 기록은 [[raw/branch-notes/feature-architecture-enforcement-rules]]에 반영했다.
## 면접·포트폴리오로 옮길 만한 것
> 후보 표기만. daily-note에서 `wiki/interview/` 또는 `wiki/portfolio/`를 직접 만들지 않는다.
- Clean Architecture skeleton에서 module boundary를 문서가 아니라 Gradle/ArchUnit rule로 강제한 이유. 글감 raw note: [[raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28]]. 예상 질문 raw note: [[raw/interviews/clean-architecture-boundary-enforcement]]. 실제 로컬 검증 후 `wiki/projects/ca-tmpl/clean-architecture-package-layout` 또는 별도 architecture enforcement canonical 문서로 승급 후보.
## 내일로 넘긴 것
- [feature-application-port-usecase-contract] architecture enforcement가 통과한 뒤 `application/port/in`, `application/port/out`, transaction runner, repository port 위치를 정리한다.
- [feature-sample-removal-adoption-contract] architecture enforcement에 production module -> `sample-ticket` 금지 rule이 반영된 뒤 sample-off runtime isolation 구현 범위를 구체화한다.
## 잡담 / 회의 / 기타
- 오늘 daily-note는 `feature-skeleton-package-blueprint-contract` 다음 개발 착수 범위를 제한하기 위한 계획이다.
@@ -0,0 +1,55 @@
---
title: 2026-06-14 일일 노트
source_type: daily-note
status: raw
tags: [daily, ca-tmpl, ca-skeleton, clean-architecture, logging, observability]
date: 2026-06-14
branches: [
feature-log-management-contract
]
---
# 2026-06-14
> Layer: `raw/daily-notes/` — 그날의 혼합 일일 기록. 그 자체는 wiki로 옮기지 않으며, `/ingest`가 promotable 항목만 추출.
## 활성 브랜치
- `feature-log-management-contract` (implemented) — [[raw/branch-notes/feature-log-management-contract]]
## 한 일
feature-log-management-contract Phase C2 전면 구현 — 문서화돼 있던 DRIFT-1~6 + sampling 전부 코드로 해소.
- 사용자 결정 2건 확정: **Q1**=dependency 실패 레벨 어댑터종류 분리(optional fail-open WARN / core HTTP ERROR), **Q2**=full HMAC pseudonymization.
- DRIFT-2 Layer 1 masking: `LogMaskingPatterns`(정규식 SSOT) → `SecretMaskingJsonGeneratorDecorator`(JSON) + `SecretMaskingMessageConverter`(`%maskedMsg`).
- DRIFT-1/D10: `logback-spring.xml` `<springProfile>` 분기(local/dev pattern vs prod JSON).
- DRIFT-3: `RequestLoggingFilter` uri_template.
- DRIFT-4: `OutboundDependencyLogger` snake_case + dependency_type + WARN + 5 callers.
- DRIFT-5: `MetricsAsyncAppender``log.appender.dropped.total`.
- DRIFT-6: `UserPrincipalPseudonymizer`(port) + `HmacUserPrincipalPseudonymizer`(adapter-identifier) + `PseudonymizationConfig`/`PrivacySettings`(app-bootstrap) + filter 배선.
- sampling: `SamplingTurboFilter`.
- 오케스트레이션: Task 1~4 는 `ca-implementer` 디스패치, Task 5(logback XML/custom appender/turbofilter/masking)는 API 검증(MaskingJsonGeneratorDecorator, AsyncAppenderBase, springProfile) 후 메인 에이전트 직접 구현. 리뷰 체인 3단계 ALL PASS.
## 배운 점
- `LogstashEncoder`(JSON)는 PatternLayout 을 우회 → `%replace` 마스킹 무력. JSON 은 `MaskingJsonGeneratorDecorator` 필요. → [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]]
- `AsyncAppender` 드롭 결정론 테스트: `discardingThreshold > queueSize` 트릭. → [[raw/interviews/deterministic-logback-asyncappender-drop-metric-test-2026-06-14]]
## 트러블슈팅
- `@Component` 필터에 생성자 의존성 추가 → `@WebMvcTest` 슬라이스(`OperationalContractRuntimeTest`) 컨텍스트 로드 실패. `@Import(PseudonymizationConfig.class)` 로 해소. → [[raw/errors/webmvctest-component-filter-constructor-dep-breaks-slice-2026-06-14]]
- `ca-implementer` Task 4 디스패치가 중간 truncate(5 callers 중 2개만 수정) → 메인 에이전트가 잔여 3 callers + 4 test 단언을 직접 마무리.
- local profile 실행 시 logback status 경고 3종 발견. (1)`<conversionRule converterClass=...>` deprecated → `class` 로 교체(내 변경, 수정 완료 + `SecretMaskingMessageConverterTest``class` 등록 + 마스킹 검증). (2)`<if>`-in-`<root>` + (3)`<if condition=...>` 속성 deprecated **해소(2026-06-14, 사용자 재요청)**: Janino `<if condition='property(K).equals(V)'>` 6곳 → logback 1.5.20+ 내장 `<condition class="ch.qos.logback.core.boolex.PropertyEqualityCondition"><key>K</key><value>V</value>` (조회 경로 `OptionHelper.propertyLookup(local,context)` 가 Janino 와 동일 → `<springProperty scope=context>` 값 그대로 읽어 동작 무변경). `<root>``<if>` 3개는 logback 권고대로 `<root>` 를 top-level `<condition>+<if>` 로 감싸 un-nest(ASYNC×FILE 2×2, 한 번에 하나만 활성). **janino 의존성(build.gradle) 제거** — dead dep(소스 `condition=` 0건) + Janino 동적 코드 컴파일 보안취약(2027 제거예정) 해소. 검증: logback-core 1.5.34(`dependencyInsight`) + `bootRun`(local) `|-WARN/|-ERROR` 0건 + 정상 기동(Tomcat:8080) + local PatternLayout 분기 정상. **owner 정정**: 토글 구조는 base-template 커밋 f9ad280("ca 구조 변경") 유래, 파일 owner 는 [[raw/branch-notes/feature-log-management-contract]] (D4 logging 바인딩, 최신 touch d10a751) — 직전 `migration-startup-contract D8` 귀속은 conflation(D8 은 build.gradle 인접 `logstash-logback-encoder` 줄을 govern). 기록: owner § Audit & Findings DRIFT-7. local=human-readable PatternLayout 분기(D10)는 의도대로 동작.
## 면접·포트폴리오로 옮길 만한 것
- [[raw/interviews/deterministic-logback-asyncappender-drop-metric-test-2026-06-14]]
- [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]]
## 내일로 넘긴 것
- D9 전용 audit appender(생산자 부재 + retention은 data-retention 소유 → 보류).
- ~~pre-existing ArchUnit 실패(`OutboundHttpSettings` B7 false-positive, commit d702572)~~ **해결(사용자 요청)**: `CleanArchitectureTest` B7 규칙 `outbound_adapter_method_returns_only_domain_or_primitives``@ConfigurationProperties` 제외 추가(기존 `@Configuration` 제외와 동일 패턴). 세팅 홀더는 외부 응답 매핑 surface 가 아니라 config 바인딩 타입 → ACL 누출 대상 아님. 규칙의 실제 보호(외부 응답 타입 누출 차단)는 그대로 — 순수 filter narrowing. `:app-bootstrap:test` 0 실패.
- ~~logback `<if>`-in-`<root>` / `condition` 속성 deprecation 정리(사용자 재요청)~~ **해결(2026-06-14)**: 트러블슈팅 §(2)/(3) — Janino→내장 `PropertyEqualityCondition` + `<root>` un-nest + janino dep 제거, `bootRun` 검증 완료.
- 사용자 커밋 대기.
@@ -0,0 +1,55 @@
---
title: 2026-06-30 일일 노트
source_type: daily-note
status: raw
tags: [daily]
date: 2026-06-30
branches: [develop]
---
# 2026-06-30
> Layer: `raw/daily-notes/` — 그날의 **혼합 일일 기록**. 그 자체는 wiki로 옮기지 않으며, `/ingest`가 promotable 항목만 추출해 다른 wiki 영역으로 보냅니다. 원본은 raw에 영구 보관.
## 활성 브랜치
오늘 작업한 브랜치 목록과 진행 상태. 브랜치 노트로 양방향 링크.
- `develop` (review) — [[raw/branch-notes/feature-developer-experience-contract]]
## 오늘의 계획
- [x] [develop] CleanArchitectureTest.java의 자원 누수 경고 해결
- [x] [develop] README.md에서 누락되었던 feature-developer-experience-contract 식별자 복구로 테스트 통과 확인
- [x] [develop] Spring Boot 3.5.x EOL 경고 무시를 위한 VS Code settings.json 설정 반영
- [x] [develop] Spring Boot 4.x & Testcontainers 2.0 마이그레이션 호환성 평가 및 명세서 작성
## 한 일
- [develop] CleanArchitectureTest.java에서 `callTransactionPortMethodRequiredByCapability` 메소드에 `@SuppressWarnings("resource")`를 추가하여 ECJ의 자원 누수 오탐지 경고 해결.
- [develop] CleanArchitectureTest.java의 internal FQCN 임포트 스타일 정리 및 spotless 적용.
- [develop] README.md에 `feature-developer-experience-contract` 문자열을 추가하여 DeveloperExperienceContractTest의 계약 실패 검증 통과.
- [develop] Spring Boot Tools의 EOL 경고 무시를 위해 `.vscode/settings.json``spring-boot.ls.problem.version-validation.SUPPORTED_OSS_VERSION` 등의 설정을 추가.
- [develop] Spring Boot 4.x 및 Testcontainers 2.0로 업그레이드 시 발생하는 빌드 의존성 좌표 변경, 패키지 리로케이션 및 autoconfiguration 호환성을 평가하고 마이그레이션 가이드 문서(docs/superpowers/specs/2026-06-30-testcontainers-2-0-migration-spec.md)를 설계 및 작성.
## 배운 점
- Eclipse Compiler for Java (ECJ)는 anonymous inner class의 리턴 형태나 인스턴스화 위치에 따라 리소스 누수를 잘못 오탐지할 수 있으며, 이 경우 `@SuppressWarnings("resource")`를 적합하게 활용하여 코드를 깨끗하게 유지할 수 있다.
- VS Code Spring Boot Tools 확장 프로그램의 Spring Boot EOL 지원 경고는 `.vscode/settings.json`을 사용하여 워크스페이스 레벨에서 개별 무시(IGNORE)가 가능하다.
- Testcontainers 2.0.0 버전에서 모듈명 접두사 표준화(testcontainers-*) 및 패키지 리로케이션(org.testcontainers.<module> 형태로 이동) 등의 중대한 변경사항이 존재하며, 이로 인해 Spring Boot 3.5.x와 혼용 시 @ServiceConnection 바인딩 관련 ClassNotFoundException 위험이 있음을 확인.
## 트러블슈팅
- 없음.
## 면접·포트폴리오로 옮길 만한 것
- 없음.
## 내일로 넘긴 것
- 없음.
## 잡담 / 회의 / 기타
- 로컬 빌드 및 전체 테스트를 무사히 통과시키고 마크다운 및 리소스 경고 문제를 매끄럽게 처리함.
+265
View File
@@ -0,0 +1,265 @@
---
title: daily-tasks / Hub
source_type: meta
status: stable
tags: [meta, daily-task, hub]
last_reviewed: 2026-05-28
---
# daily-tasks / Hub
> Layer: `raw/daily-tasks/` — **매일 아침 학습용 실습 과제** 의 카테고리 진입점. 사수가 신입에게 주는 형식의 자율 학습 과제를 두 트랙으로 분리 누적.
## 0. 한 줄 요약
| 항목 | 값 |
|---|---|
| 사용 cadence | 매일 아침 |
| 트랙 | `develop/` + `infra/` 두 가지 동시 진행 (각 ~2시간) |
| 1과제 분량 | `duration_estimate: 120` 분 default (Pomodoro 4-5개) — *완료 신호* 까지의 자기 추정치 |
| Template | `templates/daily-task-develop-template.md` / `templates/daily-task-infra-template.md` |
| 산출물 | branch (`daily-task/<track>/<slug>`), commit/PR, manifest, dashboard/alert, 회고 |
| Promotion 경로 | `verified` 항목만 `/ingest``wiki/concepts/` 또는 `wiki/projects/` (CLAUDE.md §15) |
## 1. 폴더 구조
```text
raw/daily-tasks/
├── README.md ← 이 파일 (hub)
├── develop/
│ └── YYYY-MM-DD-<implementation-slug>.md ← 매일 1개
└── infra/
└── YYYY-MM-DD-<implementation-slug>.md ← 매일 1개
```
## 2. 명명 규칙
- 파일명: `YYYY-MM-DD-<implementation-slug>.md`
- `YYYY-MM-DD` = `target_date` (수행 예정일). 미래 과제를 미리 작성해도 무방.
- `<implementation-slug>` = **무엇을 배우고 구현하는지** 를 4~7 단어 영문 kebab-case 로. 슬러그만 보고도 과제 내용 파악 가능해야 함.
- 좋은 예:
- `develop/2026-05-29-archunit-controller-domain-return-rule.md`
- `develop/2026-05-30-jackson-fail-on-unknown-properties-policy.md`
- `infra/2026-05-29-actuator-readiness-probe-db-disconnect.md`
- `infra/2026-05-30-prometheus-pod-restart-alert-rule.md`
- 나쁜 예 (금지):
-`develop/task-1.md` (의미 zero)
-`infra/day-3-monitoring.md` (numbered hierarchy + 의미 부족)
-`develop/오늘과제.md` (한글 파일명)
- 자세한 규칙: [[rules/naming-conventions]] (§2.2.1 daily-task 명명)
## 3. 두 트랙의 차이
| 항목 | develop | infra |
|---|---|---|
| 주 산출물 | 코드 commit / PR / 테스트 / ArchUnit rule | manifest / config / probe / alert rule / dashboard |
| 검증 채널 | unit test, contract test, build pipeline | kubectl + promql + log query + smoke test (≥2 채널 교차) |
| §5 흐름 | 코드 작성 → 테스트 작성 → 빌드 → PR | manifest 작성 → apply → 관측 → 롤백 drill |
| 시간 분포 | CPU bound (Pomodoro 직접) | apply / 수렴 *대기* 시간 포함 |
| 회복력 anchor (§11) | 없음 | **있음** — fail-fast vs degrade, 롤백 트리거 |
| Template | [[templates/daily-task-develop-template]] | [[templates/daily-task-infra-template]] |
## 4. 트랙별 6-month 커리큘럼
> 매일 1과제 × 2트랙을 6개월 (약 130 영업일) 진행했을 때 도달 목표를 *시니어 초반급 문제해결력* 으로 설정. 단순 지식 누적이 아닌 *trade-off articulation / system thinking / failure-mode awareness / root-cause tracing* 4역량의 동시 향상.
>
> **목표 정의 근거**: `[[raw/company-tech-blogs/senior-engineer-competency-mubin-shaikh]]#SR-MUBIN-C1` (system thinking — latency/throughput/failure-mode 까지), `#SR-MUBIN-C3` (trade-off 명시 — "best practice" 인용은 senior 미달), `#SR-MUBIN-C4` (증상 아닌 근본 원인 + 재발 방지까지), `#SR-MUBIN-C5` (커리어 초반=무엇을 만드는가, 후반=어떤 결정을 주도하는가).
>
> **격상 위험 주의** (raw 의 ELEV-1, ELEV-2): 본 anchor 는 *personal-blog 단독* 근거. 커리큘럼 본문에서 인용할 때는 "Mubin Shaikh 관점에서" 또는 "참고 기준으로" 한정. *공식 industry standard* 처럼 표현 금지.
### 4.0 4역량 anchor — *시니어 초반급* 의 조작적 정의
| 역량 | 의미 | 측정 신호 (도달 시) | 인용 |
|---|---|---|---|
| **System thinking** | 코드 한 함수가 아닌 시스템 전체 (request 진입~응답 반환 + 의존성 + 실패 전파) 로 사고 | 새 feature 를 *requirements → deployment → 운영* 까지 혼자 설계 가능 | `#SR-MUBIN-C1` |
| **Trade-off articulation** | 모든 결정에 "왜 이걸 골랐고 왜 다른 걸 안 골랐는가" 를 *최소 2-3개* 댈 수 있음. "best practice 이니까" 거부 | 자기 PR 의 design choice 를 1분 안에 3개 trade-off 와 함께 설명 | `#SR-MUBIN-C3` |
| **Failure-mode awareness** | 정상 path 가 아니라 *어떻게 깨지는가* 부터 설계. 새 기능 도입 시 새 실패 모드를 함께 명시 | 새 코드 / manifest 의 §11 운영 회복력 anchor 가 빈칸이 아님 | `#SR-MUBIN-C1`, `#SR-MUBIN-C4` |
| **Root-cause tracing** | production issue 를 증상 (retry 실패) 이 아닌 근본 원인 (idempotency 누락) 까지 추적. 재발 방지 (alert / contract test) 까지 책임 | issue 1건당 fix + alert + contract test 의 3-pack 결과 | `#SR-MUBIN-C4` |
매 phase 끝에 위 4역량을 0~5 self-rate. 6개월 끝에서 모두 ≥ 3 이 목표 (`참고 기준`, Mubin Shaikh 관점).
### 4.1 develop 트랙 — 6 phase (각 4주)
| Phase | 핵심 anchor | 시니어 사고 강제 (trade-off) | 산출물 |
|---|---|---|---|
| **D-P1** Boundary Contract Enforcement | ArchUnit, Spring MVC exception, Bean Validation 4-layer, mapper boundary | 정적 분석 vs runtime 검증 trade-off / false-positive vs leak coverage | 5-8 ArchUnit rule, mapping exception classifier, contract test 묶음 |
| **D-P2** Mapper & Serialization Safety | record + canonical constructor, MapStruct optional, Jackson polymorphic 보안 (CVE-2019-14379 패턴), PATCH semantics (RFC 7396 미채택) | 수기 mapper vs generated trade-off / `enableDefaultTyping` 보안 vs 편의 / null=deletion vs absent 의미 | mapper 패턴 카탈로그 + polymorphic deserialization 보안 test + PATCH endpoint 3-상태 contract |
| **D-P3** Data & Transaction Contract | JPA, `TransactionPort` 추상화, optimistic / pessimistic lock, idempotency key, repository capability | tx 경계 위치 (controller/service/UC) trade-off / lock 종류 선택 / idempotency table vs request-key cache | TransactionPort 구현 + idempotency 처리 + capability 테스트 |
| **D-P4** Domain Event & Async Boundary | outbox pattern, transactional event publish, async executor, virtual thread (Loom) 호환성 | 동기 vs 비동기 trade-off / outbox 폴링 주기 vs latency / virtual thread + ThreadLocal MDC | outbox publisher + async boundary test + virtual thread compatibility test |
| **D-P5** API Surface & Schema Evolution | OpenAPI spec-first, contract test, API versioning, breaking change 분류 | spec-first vs code-first trade-off / version 전략 (header/path) / unknown field 허용 시점 | OpenAPI v1 + spec drift detection + deprecation policy |
| **D-P6** Performance & Concurrency | JMH micro-bench, async profiler, jstack 분석, concurrency primitives (ReentrantLock vs synchronized vs StampedLock) | latency vs throughput trade-off / bench reliability (warmup, GC noise) / lock 선택 | JMH report + bottleneck analysis + lock comparison |
#### D-P1 상세 — *시작 phase, 모든 후속 phase 의 baseline*
- **진입 조건**: ca-tmpl 빌드 통과, ArchUnit 의존성 추가 가능
- **학습 anchor**:
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]] D1~D14
- [[raw/official-docs/spring-mvc-rest-exception-handling]]
- [[raw/official-docs/validation-jakarta-bean-validation-3.0-spec]]
- [[raw/official-docs/schema-jackson-unknown-field-handling]]
- **변수 / 상황 anchor** (매 과제 §8 회고에 답할 것):
- rule 이 잡지 *못하는* 우회 패턴 (reflection / generic Object 반환 / dynamic proxy) — 어디까지 ArchUnit 으로 가는 게 합리적인가?
- false positive 1건 vs leak 1건의 비대칭 비용
- generated code (MapStruct, Lombok) exemption 의 위치
- **시니어 초반급 도달 신호** (이 phase 끝났을 때):
- controller / service / DTO 의 boundary leak 시나리오 *5개* 를 trade-off 와 함께 설명 가능
- 새 rule 추가 시 *false positive 측정* 부터 시작하는 절차가 몸에 익음
- **예상 과제 흐름** (영업일 기준):
- W1: controller return type rule + JSON leak integration test (오늘 작성된 첫 과제로 시작)
- W2: request DTO → application 직접 전달 금지 rule + Bean Validation group sequence
- W3: Mapping exception classifier + ResponseEntityExceptionHandler 확장
- W4: Jackson deserialization 정책 강제 + integration cross-check
#### D-P2 상세
- **진입 조건**: D-P1 의 boundary contract 가 코드로 강제됨
- **학습 anchor**:
- [[raw/official-docs/schema-jackson-polymorphic-deserialization]] (CVE-2019-14379 포함)
- [[raw/official-docs/patch-json-merge-rfc7396]]
- **변수 / 상황 anchor**:
- MapStruct generated code 가 build 마다 stale 가능 → CI 검증
- sealed `Command` interface 의 Jackson 2.15+ 자동 인식 vs 명시 `@JsonTypeInfo` trade-off
- PATCH `null` 의 의미 (3-상태) 를 OpenAPI 에 어떻게 노출하는가
- **시니어 초반급 도달 신호**:
- polymorphic deserialization gadget chain 의 *공격 시나리오* 를 1개 그릴 수 있음
- PATCH 의 silent overwrite 버그 패턴을 코드 리뷰에서 즉시 잡아냄
#### D-P3 ~ D-P6 (요약, 상세는 phase 진입 시 README 갱신)
각 phase 진입 시 *그 phase 의 첫 주차에* 본 README 의 해당 sub-section 을 D-P1/D-P2 와 동일 깊이로 채운다 — *phase 진입은 README 갱신부터*. 이게 진행 추적 anchor.
### 4.2 infra 트랙 — 6 phase (각 4주)
| Phase | 핵심 anchor | 시니어 사고 강제 (trade-off) | 산출물 |
|---|---|---|---|
| **I-P1** Health & Lifecycle | actuator probe (readiness/liveness 분리), graceful shutdown, startup validation, JVM/container 자원 한계 | probe period vs detection latency / liveness 에 DB 포함의 *치명적 함정* / fail-fast vs degrade | probe contract + chaos drill + startup validation matrix |
| **I-P2** Observability Fundamentals | structured JSON log, MDC propagation, OpenTelemetry trace context (virtual thread 호환), baseline metric (RED + USE), SLO 정의 | observability cost vs coverage / sampling rate / cardinality 폭발 위험 | dashboard 묶음 + alert rule + SLO 문서 |
| **I-P3** Resilience Pattern | circuit breaker (Resilience4j), retry, rate limit, backpressure, bulkhead | retry vs idempotency / breaker threshold / queue size 의 latency 영향 | resilience 통합 + chaos test (지연/단절/burst) |
| **I-P4** Cluster Operation | k8s manifest, helm chart, rollout/rollback drill, secret 관리 (sealed secret / external secret operator) | gitops vs imperative / blue-green vs canary / secret rotation 자동화 trade-off | helm chart + rollback runbook + secret rotation drill |
| **I-P5** Capacity & Cost | HPA (CPU/memory/custom metric), resource limits, profile-driven sizing, cost reporting | over-provision (cost) vs under-provision (SLO 위험) / HPA 스파이크 vs 비용 / right-sizing 의 측정 노이즈 | sizing report + HPA policy + cost dashboard |
| **I-P6** Security & Supply Chain | RBAC, network policy, image scan (Trivy), SBOM 생성, secret rotation, supply chain attestation | security vs DX trade-off / scan blocking vs warning / sbom 검증 강도 | SBOM pipeline + image scan gate + rotation drill |
#### I-P1 상세 — *시작 phase, 모든 infra 작업의 baseline*
- **진입 조건**: 로컬 cluster (kind/k3d/minikube) + Prometheus/Grafana 가 동작
- **학습 anchor**:
- [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle), §18 (Metrics/Alerting)
- [[raw/official-docs/runtime-health-spring-actuator-groups]]
- [[raw/official-docs/actuator-endpoint-exposure-spring-official]]
- [[raw/official-docs/actuator-management-port-spring-official]]
- **변수 / 상황 anchor** (매 과제 §8 회고에 답할 것):
- probe 가 *측정하려는 것* (트래픽 받을 준비) 과 *실제로 측정되는 것* (HTTP 200) 사이의 갭
- 측정값 간 시간차 (actuator vs kubectl vs prometheus) — scrape interval 영향
- liveness/readiness 혼동 시 발생하는 cascade failure (재기동 폭주)
- probe 자체의 timeout (actuator hang) — DB 가 죽었는데 readinessProbe 도 timeout
- **시니어 초반급 도달 신호**:
- readiness/liveness 의 운영적 차이를 *1분* 안에 설명 + 잘못 설정한 시스템의 cascade failure 시나리오 *2개* 묘사 가능
- 새 운영 변경 도입 시 *측정값 baseline → 변경 → 측정값 after → 차이 분석* 흐름이 자동
- **예상 과제 흐름**:
- W1: actuator readiness probe 분리 + DB 단절 시 측정 (오늘 작성된 첫 과제)
- W2: graceful shutdown + in-flight 요청 처리 (terminationGracePeriodSeconds 와 actuator 의 관계)
- W3: startup validation + 의도적 잘못된 env 로 fail-fast 시간 측정
- W4: JVM/container 자원 한계 시뮬레이션 + OOM 시 cleanup
#### I-P2 ~ I-P6 (요약)
D-P3~D-P6 와 동일 — phase 진입 시 본 README 의 해당 sub-section 을 채우는 것이 phase 시작.
### 4.3 변수 / 상황 anchor — 공통 메타 패턴
매 phase, 매 과제 §8 회고에 답해야 하는 메타 질문 (시니어 사고 강제):
1. **베이스라인 측정 없이 시작했는가?***없으면 변경 후의 "좋아졌다" 가 측정 불가*. 매 과제 §5 Step 1 은 항상 baseline.
2. **예상 결과 vs 실측의 차이는 몇 %인가?** — 일치하면 학습 0, 차이 클수록 학습 ↑. 차이가 0% 면 과제 너무 쉬움 (`difficulty` 조정 신호).
3. **이 결정의 *우회 가능 경로* 는 무엇인가?** — 정적 분석은 reflection 우회, alert 는 silent failure 우회, contract test 는 misconfig 우회. 우회 1개를 매번 명시.
4. **이 결정이 *추가하는* 실패 모드는 무엇인가?** — 새 rule 은 false positive, 새 probe 는 toggle 폭주, 새 alert 는 fatigue. 추가 실패 1개를 매번 명시.
5. ***되돌릴* 명령은 무엇인가?** — 롤백 명령을 *작성하기 전에* 코드/manifest 작성 금지. 매 infra 과제는 snapshot first.
이 5개 질문이 4역량 anchor (§4.0) 의 일상 운영판.
### 4.4 cross-track integration
매 phase 끝에 *두 트랙이 같은 도메인을 다르게 보는* cross-check 1개:
| 시점 | develop ↔ infra cross-check |
|---|---|
| P1 끝 | D-P1 의 ArchUnit rule 이 I-P1 의 probe-on-startup 검증과 일관: rule 위반 build 가 *startup validation* 단계에서도 잡히는가? |
| P2 끝 | D-P2 의 mapper masking 정책 ↔ I-P6 의 image scan 의 PII pattern. 둘이 동일 PII set 을 cover? |
| P3 끝 | D-P3 의 idempotency key ↔ I-P3 의 retry policy. retry 가 idempotency 없이 발동 시 contract test 가 잡는가? |
| P4 끝 | D-P4 의 outbox + virtual thread ↔ I-P2 의 trace propagation. virtual thread 경계에서 trace 가 끊기는가? |
| P5 끝 | D-P5 의 OpenAPI spec drift ↔ I-P4 의 helm rollout. spec drift 가 rollout 차단으로 이어지는가? |
| P6 끝 | D-P6 의 bottleneck profiling ↔ I-P5 의 HPA policy. 측정된 bottleneck 이 HPA metric 으로 연결되는가? |
### 4.5 진행 추적 / 자가평가
- 매 phase 끝 (4주차 금요일 권장): §4.0 4역량 표를 0-5 self-rate
- phase 가 4주를 넘으면 *진척이 안 나는 신호* → 학습 anchor 분할 (예: D-P2 를 mapper + Jackson 보안 2개로 쪼개기)
- 6개월 끝: 6회 self-rate 누적 → 역량별 성장 곡선 그리기
### 4.6 커리큘럼이 *틀어졌을 때*
- production / 회사 일정으로 1주 이상 멈추면: 멈춘 시점의 phase 마지막 과제 §8 회고를 다시 읽고 *그 phase 의 학습 anchor* 만 5분 재정리. *연속성* 회복 후 재개.
- 한 phase 가 *너무 쉬워서* 2주 만에 끝나면: 다음 phase 진입 ** 에 cross-track integration 과제 1개 (§4.4) 를 끼워 깊이 보강.
- 한 phase 가 *너무 어려워서* 6주 넘어가면: 학습 anchor 를 *반으로* 자르고 새 phase 추가. 6 phase → 7 phase 로 확장 허용.
## 5. 하루 흐름 권장
```text
07:00 - 09:00 develop 과제 1개 (~2h)
09:00 - 09:15 회고 (§8) + commit/PR
09:15 - 11:15 infra 과제 1개 (~2h)
11:15 - 11:30 회고 (§8) + apply 결과 정리
```
총 4시간 (이동시간 / 휴식 미포함). 각 트랙 회고 5분은 *반드시* — 회고 없는 과제 = 학습 손실 (`raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode#DP-RGC-C4`).
## 5. 과제 시작 / 종료 절차
### 시작 시
1. 어제의 §7 "다음 과제 thread" 를 본다 → 오늘 과제 후보 선정
2. 해당 template 복사 → `raw/daily-tasks/<track>/YYYY-MM-DD-<slug>.md`
3. frontmatter 채움 (`target_date`, `difficulty`, `duration_estimate`, `parent_project`, `prerequisites`)
4. §1~§4 채움 (목표 / 스토리라인 / 환경 / 사전 지식) — *과제 시작 전* 완료
5. `status_label: in-progress` 로 변경
### 종료 시
1. §5 단계 모두 체크
2. §6 자동 검증 명령 모두 통과
3. §7 결과물 + §8 회고 채움
4. §10 Closure — `status_label: done`, 소요 시간 실측, promotable 후보
5. (infra) §11 운영 회복력 anchor 채움
6. commit / PR 푸시
## 6. Promotion / Ingest
- `done` + `actually-implemented` 또는 `locally-verified` 등급 항목만 `/ingest` 대상
- 절대 `wiki/interview/``wiki/portfolio/`**직접** 이동 금지 (CLAUDE.md §15) — 반드시 `wiki/concepts/` 또는 `wiki/projects/` canonical 경유
- `documented-only` / `planned` 항목은 raw 영구 보관, wiki 추출 대상 아님
## 7. Sources / 근거 자료
본 hub 와 두 template 의 구조 근거:
| Source | 정당화 |
|---|---|
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | 9-section anchor (Learning Objectives / Storyline / Environment / Exercises / Assessments / Outcomes / Sources / Closure / Reflection) 의 vendor-normative 근거. **공식 best practice 격상 금지** — company-case-study 강도. |
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 (slightly higher than current), §6 objective 평가, §8 reflection 의 deliberate-practice 원리. **personal-blog 강도** — Ericsson 연구 2차 인용이므로 "Ericsson 연구 기반" 표현 금지, "경험 기반 권고" 로만 인용. |
| [[raw/company-tech-blogs/senior-engineer-competency-mubin-shaikh]] | 커리큘럼 "시니어 초반급 문제해결력" 목표의 외부 anchor — mid→senior 갭(trade-off articulation, system thinking, failure-mode awareness). **personal-blog 강도** — 공식 best practice 격상 금지. |
## 8. 누적 인덱스 (수동 또는 Dataview)
> 현재는 비어 있음. 과제가 쌓이면 트랙별로 최신 N개를 본 섹션에 손으로 적거나 Obsidian Dataview 쿼리로 자동화.
### develop (최신 순)
| 날짜 | 슬러그 | Phase | difficulty | status | 검증 결과 |
|---|---|---|---|---|---|
| 2026-05-29 | [[raw/daily-tasks/develop/2026-05-29-archunit-controller-domain-return-rule\|archunit-controller-domain-return-rule]] | D-P1 W1 | intermediate | not-started | — |
### infra (최신 순)
| 날짜 | 슬러그 | Phase | difficulty | status | 측정값 / 검증 |
|---|---|---|---|---|---|
| 2026-05-29 | [[raw/daily-tasks/infra/2026-05-29-actuator-readiness-probe-db-disconnect-detection\|actuator-readiness-probe-db-disconnect-detection]] | I-P1 W1 | intermediate | not-started | — |
@@ -0,0 +1,236 @@
---
title: daily-task / develop / archunit-controller-domain-return-rule
source_type: daily-task
track: develop
status: raw
status_label: not-started
difficulty: intermediate
duration_estimate: 120
prerequisites:
- "[[raw/branch-notes/feature-boundary-validation-mapping-contract]]"
- "[[raw/project-notes/ca-skeleton-operational-contract]]"
parent_project: ca-skeleton-operational-contract
parent_branch: feature-boundary-validation-mapping-contract
target_date: 2026-05-29
created: 2026-05-28
tags: [daily-task, validation, mapper, testing]
---
# daily-task / develop / archunit-controller-domain-return-rule
> Layer: `raw/daily-tasks/develop/` — **개발 트랙 일일 실습 과제**.
> `status_label`: `not-started` → 시작 시 `in-progress` → 종료 시 `done`
> `difficulty`: `intermediate` (ArchUnit 기본 사용 경험 가정, predicate 합성은 새로움)
> `duration_estimate`: 120 (Pomodoro 4-5개)
>
> **이 과제의 위치**: develop 트랙 1일차. [[raw/branch-notes/feature-boundary-validation-mapping-contract]] 의 첫 Claims To Verify ("controller 가 domain object 를 직접 반환하지 않는지") 를 *코드에서 강제* 하는 ArchUnit rule 을 작성한다.
## Parent / 부모 (필수)
- **Parent project**: [[raw/project-notes/ca-skeleton-operational-contract]] (§4 Boundary Validation & Mapper Contract)
- **연관 branch**: [[raw/branch-notes/feature-boundary-validation-mapping-contract]] — D1 (모든 경계에 validation/mapping 책임), D8 (domain object → response DTO 직접 노출 금지)
## 1. 학습 목표 / Learning Objectives
- [ ] **L1**: ArchUnit 의 `ArchRuleDefinition.classes().that()...should()` 체인으로 controller class 의 method return type 제약 rule 을 작성할 수 있다
- [ ] **L2**: 의도적 위반 코드 추가 시 build 가 *정확히* 위반된 rule 이름 + violating method signature 메시지로 깨짐을 확인할 수 있다
- [ ] **L3**: rule 이 `@Controller`, `@RestController` 양쪽 모두 cover 하고, `ResponseEntity<T>` wrapper 의 generic 인자도 검사하는지 직접 검증할 수 있다
- [ ] **L4 (optional, 시간 남으면)**: integration test 로 actual JSON response payload 에 domain entity field (e.g., `version`, `createdBy`) 가 leak 되지 않음을 검증할 수 있다
## 2. 스토리라인 / WHY (Storyline)
어제 보강한 `feature-boundary-validation-mapping-contract` 의 D8 결정 — *domain object 를 response DTO 로 직접 노출 금지* — 은 *문서상 합의* 일 뿐, 실제 코드는 Jackson 의 implicit reflective serialization 으로 controller method 가 `return entity` 라고 적어도 build 가 통과한다.
다음 신입이 이 결정을 모르고 `return ticket` 으로 적어도 컴파일러는 침묵하고, JSON response 에는 `passwordHash``version` 이 그대로 흘러간다. PR 리뷰어가 매번 *손으로* 잡아내야 하는 것은 contract 가 아니라 사회적 합의일 뿐. **사회적 합의는 컴파일러를 이기지 못한다.**
오늘은 *그 단 한 가지* rule — controller method return type 은 DTO record 또는 `ResponseEntity<DTO record>` 만 허용 — 을 작성하고, 의도적으로 위반된 코드를 추가해 build 가 깨지는 것을 *눈으로* 확인한다. 이 단 한 줄의 rule 이 다음 1년의 boundary leak 50건을 막을 것이다.
## 3. 환경 / Environment
**개발 도구**:
- Java: 21 (LTS)
- Build: Gradle 8.x
- IDE 권장: IntelliJ IDEA 2025.x
- 라이브러리: `com.tngtech.archunit:archunit-junit5:1.3.0`, Spring Boot 3.3.x, JUnit 5.10+
**사전 셋업**:
```bash
cd ~/workspace/ca-tmpl
git checkout main && git pull
git checkout -b daily-task/develop/archunit-controller-domain-return-rule
# 현재 ArchUnit 의존성 확인
./gradlew :adapter-web:dependencies | grep archunit
# 기존 ArchUnit test 위치 확인
find . -name 'CleanArchitectureTest.java' -path '*/test/*'
# 빌드 정상 확인
./gradlew :adapter-web:test --tests '*CleanArchitectureTest'
```
**예상 변경 파일**:
- `adapter-web/src/test/java/<base>/architecture/ControllerReturnTypeRuleTest.java` (신규)
- 또는 기존 `CleanArchitectureTest.java` 에 메서드 추가
## 4. 사전 지식 / Prerequisites
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]] — D1, D8, Claims To Verify 첫 항목 정독
- [[raw/project-notes/ca-skeleton-operational-contract]] §4 — Boundary Validation & Mapper Contract
- ArchUnit 핵심 API (모르면 5분만 보고 시작):
- `JavaClasses` 로딩 (`new ClassFileImporter().importPackages(...)`)
- `ArchRuleDefinition.methods()` chain
- `DescribedPredicate` 합성 (`and`, `or`, `not`)
## 5. 단계별 과제 / Exercises
### Step 1: 베이스라인 — 현재 위반 grep (~20min)
- **What**: 현재 ca-tmpl 의 controller code 에 이미 `return entity` 또는 `return domainObject` 패턴이 있는지 확인. 사전 측정.
- **How (hint)**: `grep -r "return.*Entity\b" adapter-web/src/main/java` / IDE에서 `@RestController` annotated class 들의 method return type 한 줄로 정렬해서 listing
- **Done when**:
- 현재 위반 카운트 N개 명시 (0이어도 무방 — 기준선만 확보)
- §7 결과물 섹션에 "baseline violation: N" 기록
### Step 2: ArchUnit rule 작성 (~30min)
- **What**: `ControllerReturnTypeRuleTest.java` 에 단일 `@ArchTest` rule 작성. controller class 의 모든 public method 의 return type 이 *허용 set* (DTO record / `ResponseEntity<DTO>` / `void`) 안에 있는지 검사.
- **How (hint)**:
- `classes().that().areAnnotatedWith(RestController.class)` 로 controller selection
- `.should()` 뒤에 custom `ArchCondition<JavaClass>` 작성 — class 내부 method 순회
- 허용 set 정의: 해당 패키지 (e.g., `<base>.web.dto.*`) 아래 record 인지, 또는 `ResponseEntity` 의 raw type 인지
- `ResponseEntity<T>` 의 generic 인자 추출은 `JavaParameterizedType` 사용
- **함정** (의도적 노출):
- `ResponseEntity<DomainEntity>` 처럼 wrapper 안에 domain 이 숨는 경우 — generic 인자도 검사해야 함
- record 가 *DTO 패키지가 아닌 domain 패키지에 있는* 경우 — 패키지 위치도 검사
- **Done when**:
- `./gradlew :adapter-web:test --tests '*ControllerReturnType*'` 통과
- rule 코드 30줄 이내 (복잡하면 분리)
### Step 3: 의도적 위반 → build 깨짐 확인 (~25min)
- **What**: 임의의 controller method return type 을 domain entity 로 *임시* 변경 → build 실행 → 에러 메시지 *정확히 읽고* 확인 → rule 이름이 메시지에 포함되는지 검증 → 위반 복구
- **How (hint)**:
- 가장 단순한 GET controller method 선택
- return type 만 변경 (구현은 그대로 두고 `(DomainType) (Object) responseDto` cast 같은 hack 사용)
- build 실패 시 stack trace 가 아니라 **violation 메시지** 의 첫 줄을 읽을 것
- **Done when**:
- 실패 메시지에 rule description (예: `controllers should return only DTO record or ResponseEntity<DTO record>`) 포함
- 실패 메시지에 정확한 violating method signature 포함
- 변경 복구 후 build 다시 통과
- **공통 실수**:
- rule 자체에 typo 가 있어 *항상* 실패 — 의도된 위반인지 unintended 위반인지 구분 필요
### Step 4: `ResponseEntity<DomainEntity>` 위반 잡기 (심화) (~25min)
- **What**: Step 3 의 위반을 `ResponseEntity<DomainEntity>` 형태로 변경. 현재 rule 이 이 패턴도 잡는가? 못 잡으면 rule 보강.
- **How (hint)**:
- ArchUnit 의 `JavaMethod.getReturnType()` 은 raw type만 반환 — generic 인자는 `getRawReturnType()``getReturnType()``JavaParameterizedType` cast 필요
- 또는 더 간단한 우회: `ResponseEntity` 인 경우에만 별도 검사 분기
- **트레이드오프 의식** (시니어 사고):
- rule 을 정교하게 만들수록 false positive 줄지만 rule 복잡도 ↑
- 대안: ArchUnit 대신 lightweight `@JsonView` 정책 + DTO 패키지 격리 → 다른 trade-off
- *이 결정은 본 과제 범위 밖이지만 §8 회고에 기록할 것*
- **Done when**:
- `ResponseEntity<DomainEntity>` 패턴이 build 실패로 검출됨
- rule 코드가 여전히 50줄 이내
### Step 5 (선택): integration test 로 JSON leak 검증 (~20min)
- **What**: 정상 endpoint 호출 → response JSON 을 deserialize → domain entity 의 internal field (e.g., `passwordHash`, `version`, `auditingFields.createdBy`) 가 *없음* 을 assert
- **How (hint)**:
- `@SpringBootTest(webEnvironment = RANDOM_PORT)` + `TestRestTemplate`
- JSON path assertion 또는 `Map<String, Object>` deserialize 후 keyset 검사
- 금지 field set 을 명시적으로 정의 (whitelist 아닌 blacklist — 추가 field 는 허용)
- **Done when**:
- test 통과 + 의도적으로 controller 가 entity 반환하도록 변경 시 test 실패
- 변경 복구
## 6. 검증 / Assessment
**자동 검증**:
```bash
# 1) 빌드 + 단위 테스트
./gradlew clean :adapter-web:test
# 합격 기준: exit 0
# 2) 본 과제의 ArchUnit rule
./gradlew :adapter-web:test --tests '*ControllerReturnType*'
# 합격 기준: PASS 로그 + rule 1개 이상 executed
# 3) 의도적 위반 시 빌드 깨기 (수동)
# - controller method return type 임시 변경
# - ./gradlew :adapter-web:test → FAILED
# - 메시지 확인 → 복구
# 4) (Step 5) integration test
./gradlew :adapter-web:test --tests '*JsonLeakIntegrationTest'
# 합격 기준: exit 0
```
**수동 self-check**:
- [ ] rule description 이 한 줄로 명확 (남이 봐도 무엇을 검사하는지 알 수 있음)
- [ ] 의도적 위반 메시지가 rule description + violating method signature 둘 다 포함
- [ ] rule 이 controller 패키지 *외부* class 는 검사하지 않음 (false positive 없음)
- [ ] commit 메시지가 "왜" 를 답함 (예: "Enforce controller→DTO return type to prevent domain leak in JSON response")
- [ ] **시니어 사고 체크** — 본 rule 의 trade-off 1-2개 (예: false positive 가능 시나리오, rule 우회 방법 — generic Object 반환 등) 를 §8 회고에 기록
## 7. 결과물 / Outcomes
- **commit / PR**:
- 브랜치: `daily-task/develop/archunit-controller-domain-return-rule`
- commits: <해시 + 1줄 메시지>
- PR URL (있다면):
- **신규/변경 파일**:
- `adapter-web/src/test/java/<base>/architecture/ControllerReturnTypeRuleTest.java` — controller return type rule
- (Step 5 했다면) `adapter-web/src/test/java/<base>/architecture/JsonLeakIntegrationTest.java`
- **베이스라인 측정값** (Step 1):
- Pre-rule violation count: <N>
- 위반 패턴: <패턴 목록>
- **학습한 개념** (wiki/concepts 로 ingest 후보):
- ArchUnit predicate 합성 (`and`/`or`/`not`)
- `JavaParameterizedType` 으로 generic 인자 검사
- `ResponseEntity<T>` 와 ArchUnit 의 generic erasure 다루기
- **다음 과제 thread**:
- request DTO 가 application service signature 에 직접 나타나는지 검사 (`feature-boundary-validation-mapping-contract` Claims To Verify 2번째 항목)
- MapStruct generated code 의 architecture exemption 검증
- `@JsonView` 또는 DTO 패키지 격리 대안의 trade-off 비교
## 8. 회고 / Reflection (~5min)
- **막혔던 곳** (몇 분 / 어디서):
- **예상과 다른 점**:
- 예: ArchUnit 의 generic type 처리 방식이 예상과 달랐다 / `ResponseEntity` 의 raw type 만 가능한 줄 알았는데 generic 도 가능했다 / 의도적 위반 메시지가 stack trace 안에 묻혀 있었다
- **다음 반복에서 개선할 점**:
- 베이스라인 측정 자동화? IDE 단축키? grep alias?
- rule 작성 전 *제일 단순한 1개 메서드* 부터 잡고 정교화하는 순서?
- **부수 효과로 발견한 것**:
- 예: 현재 코드베이스의 다른 패턴 위반 발견
- **이 과제의 난이도가 적정했는가**: `너무 쉬움` / `적정` / `너무 어려움`
- **시니어 사고 체크 항목** (필수):
- 본 rule 의 trade-off 1-2개를 명시했는가?
- 우회 가능 시나리오를 예측했는가?
- 본 rule 이 잡지 *못하는* 경계 leak 패턴은? (예: `Object` 반환, raw `Map`, exception body)
## 9. 출처 / Sources
| Source | 정당화 영역 |
|---|---|
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | template 9-section 구조 |
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 + §8 reflection |
| [[raw/branch-notes/feature-boundary-validation-mapping-contract]] | D1, D8, Claims To Verify 1번째 항목 (본 과제가 검증하는 결정) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §4 Boundary Validation & Mapper Contract |
## 10. 완료 후 정리 / Closure
- **최종 status_label**: `done` | `abandoned`
- **소요 시간 실측**: <분> (vs duration_estimate 120) — 차이는 §8 회고에
- **promotable 후보**:
- `actually-implemented``feature-boundary-validation-mapping-contract` Claims To Verify 1번째 항목 status 를 `planned``actually-implemented` 로 갱신
- `locally-verified` → build pass + 의도적 위반 build fail 양쪽 확인
- **추출하지 않을 항목** (단순 학습):
@@ -0,0 +1,360 @@
---
title: daily-task / infra / actuator-readiness-probe-db-disconnect-detection
source_type: daily-task
track: infra
status: raw
status_label: not-started
difficulty: intermediate
duration_estimate: 120
prerequisites:
- "[[raw/project-notes/ca-skeleton-operational-contract]]"
- "[[raw/official-docs/runtime-health-spring-actuator-groups]]"
parent_project: ca-skeleton-operational-contract
parent_branch:
target_date: 2026-05-29
created: 2026-05-28
tags: [daily-task, infra, observability, runtime]
---
# daily-task / infra / actuator-readiness-probe-db-disconnect-detection
> Layer: `raw/daily-tasks/infra/` — **인프라/운영 트랙 일일 실습 과제**.
> `status_label`: `not-started` → `in-progress` → `done`
> `difficulty`: `intermediate` (Spring Boot actuator 기본 사용 + k8s probe 개념 가정)
> `duration_estimate`: 120 (Apply / 측정 대기 시간 포함)
>
> **이 과제의 위치**: infra 트랙 1일차. [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle) — actuator health/readiness/liveness 기준 — 의 *측정 가능한 1차 검증*. develop 첫 과제 (`archunit-controller-domain-return-rule`) 와 같은 날 진행해 코드 contract + 운영 contract 가 한 사이클에 검증되는 경험을 만든다.
## Parent / 부모 (필수)
- **Parent project**: [[raw/project-notes/ca-skeleton-operational-contract]] (§15 Runtime/Lifecycle, §18 Metrics/Alerting)
- **연관 branch**: (없음 — operational contract 직접 검증)
## 1. 학습 목표 / Learning Objectives
- [ ] **L1**: Spring Boot `health/readiness``health/liveness` 의 의미 차이 — *내 서비스가 트래픽 받을 준비됐는가* (readiness) vs *프로세스를 죽여야 하는가* (liveness) — 를 1분 안에 누군가에게 설명할 수 있다
- [ ] **L2**: `application.yaml` 에 actuator health group 을 명시 설정하고 `/actuator/health/readiness` 에 DB indicator 가 포함됨을 검증할 수 있다
- [ ] **L3**: DB 단절 시 readiness 가 `OUT_OF_SERVICE` 로 전환되고 이 변화가 *몇 초 만에* (kubectl + prometheus 양 채널) 표면화되는지 *측정값으로* 제시할 수 있다
- [ ] **L4 (필수, advanced)**: liveness 는 *동일 상황에서 전환되지 않음* (pod kill ≠ DB 단절) 을 확인하고, 왜 그래야 하는지 trade-off 로 설명할 수 있다 — 이 한 줄이 mid 와 senior 의 차이
## 2. 스토리라인 / WHY (Storyline)
[[raw/project-notes/ca-skeleton-operational-contract]] §15 는 "actuator health/readiness/liveness 기준" 을 요구하지만, 많은 프로젝트가 default `/actuator/health` 만 보는 readinessProbe 로 만족한다. 이 default 의 의미는 **"프로세스가 살아있다"** 이지 **"트래픽 받을 준비됐다"** 가 아니다.
DB가 죽어도 Spring Boot 프로세스는 잘 살아있으니 `/actuator/health` 는 200을 반환하고, k8s readinessProbe 는 *ready* 라고 판정하고, 트래픽이 흘러오고, 5xx 가 양산된다. 알림이 울리고 사람이 새벽에 깨고, root cause 는 "왜 우리는 DB 단절을 readiness 에 반영하지 않았는가" 가 된다.
오늘은 *그 한 가지* — readiness 를 명시적으로 분리하고 DB indicator 를 포함 — 를 설정하고, 의도적으로 DB 를 *끊었을 때* 몇 초 후 not-ready 가 어디서 어떻게 표면화되는지 *측정값으로* 답할 수 있게 만든다.
심화 (L4): liveness 는 같은 상황에서 *전환되지 않아야* 한다. 왜냐하면 DB 단절은 *프로세스를 죽일 이유* 가 아니라 *트래픽을 잠시 차단할 이유* 이기 때문. 이걸 헷갈리면 pod 이 재기동 폭주에 들어가서 DB 가 살아나도 cluster 가 회복 불능. 이 trade-off 가 시니어 초반급 사고의 핵심.
## 3. 환경 / Environment
**작업 호스트**: 로컬 Linux/macOS/WSL2 (사용자 환경에 맞게)
**대상 환경**:
- Cluster: 로컬 `kind` 또는 `k3d` (cluster 없으면 시작 절차에 포함)
- Namespace: `ca-tmpl-dev`
- Kubeconfig context: `kind-ca-tmpl-dev` (예시)
**도구 버전**:
- `kubectl`: 1.30+
- `kind`: 0.23+ (또는 `k3d` 5.6+, 또는 minikube)
- `docker`: 24.x
- Spring Boot: 3.3.x (ca-tmpl 기존)
- 관측: Prometheus 2.50+ + Grafana 11.x (kube-prometheus-stack helm chart 권장)
**사전 셋업**:
```bash
# 1) 작업 디렉토리 + 브랜치
cd ~/workspace/ca-tmpl-infra # (또는 ca-tmpl 의 deploy/ 디렉토리)
git checkout -b daily-task/infra/actuator-readiness-probe-db-disconnect-detection
# 2) cluster 확인
kubectl config current-context
kubectl get ns ca-tmpl-dev || kubectl create ns ca-tmpl-dev
# 3) 현재 상태 스냅샷 (롤백 reference)
kubectl get all -n ca-tmpl-dev -o yaml > /tmp/snapshot-pre-readiness-probe.yaml
# 4) Prometheus / Grafana 준비 (없으면 설치)
helm list -n monitoring | grep prometheus || echo "kube-prometheus-stack 설치 필요"
# 5) 현재 ca-tmpl 의 application.yaml 확인
grep -A 10 'management:' ca-tmpl/src/main/resources/application.yaml || echo "actuator 설정 없음"
```
**변경 예정 리소스**:
- `ca-tmpl/src/main/resources/application.yaml``management.endpoint.health.probes.enabled=true`, group readiness/liveness 명시
- `deploy/k8s/ca-tmpl-deployment.yaml` — readinessProbe path 분리, livenessProbe 의 thresholds 명시
- (선택) `deploy/k8s/alerts/db-disconnect.yaml` — PrometheusRule 신규
## 4. 사전 지식 / Prerequisites
- [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle) + §18 (Metrics/Alerting) 정독
- [[raw/official-docs/runtime-health-spring-actuator-groups]] — actuator health group 공식 spec
- (있으면) Kubernetes liveness vs readiness 공식 정의 — `kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/`
- Spring Boot `DataSourceHealthIndicator` 의 default 동작 (connection validation query)
## 5. 단계별 과제 / Exercises
### Step 1: 베이스라인 측정 (~20min)
- **What**: 현재 상태를 *수치* 로 기록. 변경 후 비교 가능해야 함.
- **How (hint)**:
- 현재 `/actuator/health` 응답 body (DB indicator 가 *있는지* 없는지)
- `kubectl describe pod <ca-tmpl-pod>` → readinessProbe / livenessProbe 설정 (path, initialDelay, period, threshold)
- `kubectl get pod -w` 로 ready 상태 watch
- DB container 가 살아있는 동안의 readiness 응답 시간 (curl -w 로 측정)
- **Done when**: §7 결과물 섹션에 baseline 표 3행 이상 (`/actuator/health` 응답 type / readinessProbe path / readiness latency)
### Step 2: actuator group 설정 + manifest 변경 (~30min)
- **What**: `application.yaml` 에 health group 명시, k8s manifest 의 probe path 분리.
- **How (hint)**:
```yaml
# application.yaml
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: readinessState,db,diskSpace
liveness:
include: livenessState
show-details: never # PII / secret leak 방지 (CLAUDE.md §11)
```
```yaml
# k8s deployment.yaml (발췌)
spec:
containers:
- name: ca-tmpl
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3 # = 15초 후 NotReady
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 6 # = 60초 후 kill (보수적)
```
- **함정 / 의도적 노출**:
- readiness 에 `db`*너무 빨리* 포함시키면 부팅 시점에 DB 가 천천히 ready 되는 동안 pod 도 NotReady → 부팅 지연
- liveness 에 `db` 를 포함시키면 *DB 죽었다고 pod kill***이게 가장 큰 함정. 의도적으로 절대 안 한다.**
- **Done when**:
- `kubectl apply --dry-run=server -f <manifest>` 통과
- probe path / period / threshold 가 baseline 과 어떻게 다른지 diff 검토 완료
### Step 3: Apply + 정상 readiness 확인 (~25min)
- **What**: 실제 apply, rollout 대기, 정상 상태 측정.
- **How (hint)**:
```bash
kubectl apply -f deploy/k8s/ca-tmpl-deployment.yaml
kubectl rollout status deployment/ca-tmpl -n ca-tmpl-dev --timeout=120s
# 1) HTTP 응답 직접 확인
kubectl port-forward svc/ca-tmpl 8080:8080 -n ca-tmpl-dev &
curl -sS http://localhost:8080/actuator/health/readiness | jq .
curl -sS http://localhost:8080/actuator/health/liveness | jq .
# 2) k8s pod 상태
kubectl get pod -n ca-tmpl-dev -l app=ca-tmpl
# 3) prometheus query (kube-state-metrics)
# promql: kube_pod_container_status_ready{namespace="ca-tmpl-dev",container="ca-tmpl"}
```
- **Done when**:
- readiness 응답 = `{"status":"UP"}` (show-details=never 로 detail 미노출 — §11 정합)
- kubectl `READY 1/1`
- prometheus 의 `kube_pod_container_status_ready` = 1
### Step 4: 의도적 DB 단절 → not-ready 전환 시간 측정 (~25min, **본 과제의 핵심**)
- **What**: DB 를 *끊고* 몇 초 후 readiness 가 false 로 전환되는지 4-5 채널 교차 측정. liveness 는 전환되지 *않음* 을 확인.
- **How (hint)**:
```bash
# 1) 측정 시작 시각 기록
TS_START=$(date +%s)
echo "DB cut at $TS_START"
# 2) DB 단절 (postgres container stop 또는 service block)
kubectl delete pod -n ca-tmpl-dev -l app=postgres
# (또는) docker stop ca-tmpl-postgres
# 3) 즉시 watch 시작 — 별 터미널에서:
watch -n 1 "kubectl get pod -n ca-tmpl-dev -l app=ca-tmpl -o wide && curl -sS http://localhost:8080/actuator/health/readiness; echo; curl -sS http://localhost:8080/actuator/health/liveness"
# 4) NotReady 표면화 시각 측정
# - readiness 응답이 503 또는 OUT_OF_SERVICE 로 바뀌는 순간
# - kubectl 의 READY 가 0/1 로 바뀌는 순간
# - prometheus 의 metric 이 0 으로 바뀌는 순간
# 세 값의 차이 자체가 학습 포인트
# 5) liveness 가 *전환되지 않는지* 확인 (UP 유지)
```
- **측정해야 할 값들**:
- T_actuator: actuator readiness 가 OUT_OF_SERVICE 로 전환된 시각 (DB 단절 후 N초)
- T_kubectl: `kubectl get pod` 의 READY 가 0/1 로 표시되는 시각
- T_prometheus: prometheus metric 이 0 으로 바뀌는 시각 (kube-state-metrics scrape interval 의 영향)
- liveness 응답 상태: *반드시* UP 유지
- **함정 / 트레이드오프 의식** (시니어 사고):
- `failureThreshold=3`, `periodSeconds=5` 이면 *최대* 15초 후 표면화 — 더 빨리 잡으려면 period↓ 인데 false positive ↑
- HikariCP 의 `connection-timeout` 과 actuator probe timeout 의 상호작용 — actuator가 DB indicator 평가 시 30초 hang 하면 readinessProbe 자체도 timeout
- **prometheus scrape interval (예: 30초) 이 alert 표면화의 lower bound** — 5초마다 NotReady 가 토글되면 prometheus 는 못 봄. 이걸 모르면 "왜 alert 가 안 울리지" 미스터리 발생.
- **Done when**:
- 세 측정값 (T_actuator, T_kubectl, T_prometheus) 표로 기록
- liveness 가 *전환되지 않음* 명시적으로 확인
- **§8 회고에 "왜 세 값이 다른가" 한 문장 답변**
### Step 5 (선택, advanced): DB 복원 → readiness 자동 복귀 측정 (~20min)
- **What**: DB 다시 살리고 readiness 가 자동으로 UP 으로 돌아오는 시간 측정 + 그 사이 traffic 처리 동작 확인.
- **How (hint)**:
- DB pod 재시작
- HikariCP 의 connection pool 이 자동 복구되는지 (`hikari.minimum-idle` 영향)
- 복귀 시간 = HikariCP retry interval + actuator probe period
- **트레이드오프** (시니어 사고):
- 자동 복구가 *너무 빠르면* DB 가 flaky 할 때 readiness 가 토글 — load balancer 도 토글
- 의도적 hysteresis 권장 (예: 30초 연속 UP 일 때만 ready)
- **Done when**: 복귀 시간 측정값 + 그 사이 in-flight 요청의 운명 (drop / 502 / queue) 기록
## 6. 검증 / Assessment
**자동 검증** (4-5 채널 중 ≥2개 교차):
```bash
# 1) Probe / health (정상 상태)
curl -fsS http://localhost:8080/actuator/health/readiness | jq -e '.status == "UP"'
curl -fsS http://localhost:8080/actuator/health/liveness | jq -e '.status == "UP"'
# 합격 기준: 두 명령 모두 exit 0
# 2) k8s 리소스 상태 (rollout 후)
kubectl rollout status deployment/ca-tmpl -n ca-tmpl-dev --timeout=60s
# 합격 기준: successfully rolled out
# 3) PromQL — readiness 가 metric 으로 노출
# 권장 query: kube_pod_container_status_ready{namespace="ca-tmpl-dev",container="ca-tmpl"}
# 합격 기준: 정상 시 = 1
# 4) DB 단절 시뮬레이션 시 readiness 전환
# (Step 4 의 측정 결과를 contract test 로 만들 수 있다면 가산점)
# 5) Smoke test — 정상 endpoint 가 200 응답
curl -fsS http://localhost:8080/api/v1/<sample-endpoint>
# 합격 기준: exit 0 (정상 상태에서)
```
**수동 self-check**:
- [ ] 위 4-5채널 중 ≥2 가 *교차* 확인됨 (단일 채널 의존 금지)
- [ ] DB 단절 시 readiness 전환 시간이 measurable (Step 4 측정값 표 존재)
- [ ] liveness 가 DB 단절 상황에서 *UP 유지* — 측정으로 확인
- [ ] 롤백 명령 (`kubectl apply -f /tmp/snapshot-pre-readiness-probe.yaml`) 이 *완전히* 베이스라인으로 복귀 가능
- [ ] L1~L4 학습 목표 모두 *수행 가능* — 특히 L4 (liveness/readiness trade-off) 를 *한 줄로* 설명 가능
- [ ] manifest commit 메시지가 "왜" 를 답함
## 7. 결과물 / Outcomes
- **commit / PR**:
- 브랜치: `daily-task/infra/actuator-readiness-probe-db-disconnect-detection`
- commits: <해시 + 1줄>
- **변경된 manifest / 설정**:
- `ca-tmpl/src/main/resources/application.yaml` — actuator health group 명시
- `deploy/k8s/ca-tmpl-deployment.yaml` — probe path 분리, threshold 명시
- **측정값 표** (Step 1 baseline vs Step 4 적용 후):
| 측정 항목 | Baseline | DB 단절 후 |
|---|---|---|
| `/actuator/health/readiness` 응답 | UP / 200 | OUT_OF_SERVICE / 503 (T초 후) |
| `kubectl get pod` READY | 1/1 | 0/1 (T초 후) |
| prometheus `kube_pod_container_status_ready` | 1 | 0 (T초 후) |
| liveness 응답 | UP | **UP 유지** (의도) |
- **Dashboard / Alert**:
- Grafana panel: `ca-tmpl readiness` (kube_pod_container_status_ready over time)
- Alert rule (작성 시): readiness=0 이 60초 지속 시 P2 alert
- **Runbook stub**:
- 알람 발생 시 1차 확인: `kubectl describe pod -l app=ca-tmpl` + `curl /actuator/health/readiness`
- 즉시 fail-fast vs degrade: DB 단절 = readiness 차단 (fail-fast), pod kill 아님 (degrade with traffic block)
- **학습한 개념** (wiki/concepts 후보):
- readiness vs liveness 의 운영적 차이
- HikariCP connection timeout 과 probe timeout 의 상호작용
- prometheus scrape interval 이 alert detection 의 lower bound
- **다음 과제 thread**:
- HikariCP `connection-timeout` 의 적정값 측정
- readinessProbe failure 후 traffic drain (Kubernetes service endpoint 갱신 시간)
- chaos test 자동화 (chaos-mesh)
- circuit breaker (Resilience4j) 와 readiness 의 관계
## 8. 회고 / Reflection (~5min)
- **막혔던 곳** (몇 분 / 어디서):
- **예상과 다른 점** (특히 측정값 vs 예측):
- 예: `failureThreshold=3` 인데 readiness 가 *15초보다 늦게* 표면화 — 왜? (probe timeout? actuator hang?)
- prometheus metric 이 *훨씬 늦게* 변함 — scrape interval 영향
- **다음 반복에서 개선할 점**:
- **부수 효과로 발견한 것**:
- **이 과제의 난이도가 적정했는가**: `너무 쉬움` / `적정` / `너무 어려움`
- **시니어 사고 체크** (필수 1줄 답변):
- "왜 liveness 에 DB 를 포함하면 안 되는가?" — <답>
- "T_actuator, T_kubectl, T_prometheus 세 값이 다른 이유는 무엇인가?" — <답>
- "readiness 토글 (UP→OUT_OF_SERVICE→UP) 이 잦으면 어떤 운영 문제를 일으키는가?" — <답>
## 9. 출처 / Sources
| Source | 정당화 영역 |
|---|---|
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | template 9-section 구조 |
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 + §8 reflection |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §15 Runtime/Lifecycle (probe 기준) + §18 Metrics/Alerting |
| [[raw/official-docs/runtime-health-spring-actuator-groups]] | actuator health group 공식 spec — readiness/liveness 분리 근거 |
## 10. 완료 후 정리 / Closure
- **최종 status_label**: `done` | `abandoned`
- **소요 시간 실측**: <분> (vs 120) — 차이는 §8 회고에
- **promotable 후보**:
- `actually-implemented` → ca-skeleton-operational-contract §15 의 actuator probe 분리 결정의 *실 구현* 증거
- `locally-verified` → DB 단절 → readiness 전환 측정값 4채널 교차 확인
- `prod-verified` → (해당 없음 — 로컬 cluster)
- **추출하지 않을 항목**:
- chaos-mesh 자동화 / circuit breaker 통합 — 별도 daily-task 로 분할
## 11. 운영 회복력 / Operational Resilience (infra 전용 anchor)
- **본 변경이 도입하는 새 실패 모드**:
- DB indicator 가 *시간이 오래 걸리는 query* 면 readinessProbe 자체가 timeout → false NotReady
- probe period 가 *너무 짧으면* DB 가 잠시 hiccup 할 때 ready 토글 → load balancer 토글 → 502 spike
- **새 실패 모드의 fail-fast vs degrade 분류**:
- DB 단절 = fail-fast (트래픽 차단)
- DB 응답 지연 = degrade (slow 응답이지만 트래픽 유지) — readiness 에 포함시킬지 결정 필요
- **모니터링 누락 위험**:
- prometheus scrape interval 보다 *짧은* not-ready 윈도우는 못 봄 (false success)
- alert quiet hours 가 없으면 readiness toggle 시 alert 폭주
- **롤백 트리거 조건**:
- readiness false 가 5분 지속 + DB 자체는 정상 → 본 변경 자체의 false positive 가능성 → 즉시 롤백
- `kubectl apply -f /tmp/snapshot-pre-readiness-probe.yaml`
- **연관 alert / runbook**:
- [[raw/project-notes/ca-skeleton-operational-contract]] §28 Operational Runbook 의 "DB unavailable" 시나리오와 정합
- 본 과제의 PrometheusRule 이 §28 의 1차 alert 항목으로 등록되어야 함
@@ -0,0 +1,63 @@
---
title: 2026-06-06 투자 일일 조사
source_type: invest-daily
status: raw
confidence: unknown
tags: [invest-daily, personal-invest, macro]
date: 2026-06-06
last_reviewed: 2026-06-06
---
# 2026-06-06 투자 일일 조사
> Layer: `raw/invest-daily/` — 그날의 거시 자금흐름 조사. **수치마다 출처 링크 + 조사 시점 필수**(실시간 아님). `/invest-ingest`가 검증 가능한 항목만 `wiki/invest-concepts/`로 추출. 원본은 raw에 영구 보관.
> ⚠️ 아래 수치는 **웹 검색 기반(조사 2026-06-06)이며 실시간 호가가 아님**. 매매 전 증권사/거래소에서 현재값 재확인 필수.
## Parent
- [[wiki/invest/invest-hub]]
## 고정 체크리스트 (매일 동일)
| 자산군 | 핵심 지표 | 값 / 방향 | 출처 | 조사시점 |
|---|---|---|---|---|
| 금리 | 미 10Y | **4.46%** ↓ (전일 ~4bp 하락) | [TradingEconomics](https://tradingeconomics.com/united-states/government-bond-yield), [CNBC](https://www.cnbc.com/quotes/US10Y) | 6/5 종가 기준, 조사 6/6 |
| 금리 | 한 기준금리 | **2.50%** → (5/28 동결, 8연속) | [한국은행](https://www.bok.or.kr/portal/singl/baseRate/list.do?dataSeCd=01&menuNo=200643) | 조사 6/6 |
| 환율 | USD/KRW | **~1,5531,560** ↑ (원화 약세, 6/5 +1.77%) | [Investing](https://www.investing.com/currencies/usd-krw-historical-data), [TradingEconomics](https://tradingeconomics.com/south-korea/currency) | 6/56/6, 조사 6/6 |
| 원자재 | WTI | **~$90.5** ↓ (전일 -3.1%) | [TradingEconomics](https://tradingeconomics.com/commodity/crude-oil), [OilPrice](https://oilprice.com/) | 6/6, 조사 6/6 |
| 원자재 | 금 | **<$4,370/oz** ↓ (2026 최저, 주간 ~-4%) | [TradingEconomics](https://tradingeconomics.com/commodity/gold) | 6/6, 조사 6/6 |
| 주요지수 | S&P500 | **7,383.74** ↓ (-2.64%) | [CNBC](https://www.cnbc.com/2026/06/04/stock-market-today-live-updates.html) | 6/6 종가, 조사 6/6 |
| 주요지수 | 나스닥 종합 | **25,709.43** ↓ (-4.18%, 2025/4 이후 최대 낙폭) | [CNBC](https://www.cnbc.com/2026/06/04/stock-market-today-live-updates.html) | 6/6 종가, 조사 6/6 |
| 주요지수 | KOSPI | **8,160.59** ↓ (-5.54%; 6/4 사상최고 8,801서 급락) | [CNBC](https://www.cnbc.com/2026/05/15/asia-markets-live-updates-today-trump-xi-nikkei-225-kospi-hang-seng-index.html), [Investing](https://www.investing.com/indices/kospi-historical-data) | 6/6 종가, 조사 6/6 |
| 코인 | BTC | **~$62,000** ↓ (6월 ~-11%) | [Yahoo Finance](https://finance.yahoo.com/personal-finance/investing/article/bitcoin-and-ethereum-prices-today-friday-june-5-2026-prices-continue-their-descent---5-reasons-why-113631165.html), [Fortune](https://fortune.com/article/price-of-bitcoin-06-05-2026/) | 6/5, 조사 6/6 |
| 코인 | ETH | **~$1,769** ↓ (-2.4%) | [Yahoo Finance](https://finance.yahoo.com/personal-finance/investing/article/bitcoin-and-ethereum-prices-today-friday-june-5-2026-prices-continue-their-descent---5-reasons-why-113631165.html) | 6/5, 조사 6/6 |
## 오늘의 이슈 (가변)
> 그날 가장 큰 움직임·뉴스. 각 항목 출처 링크 필수.
- **전 자산 risk-off 급락** — 주식·코인·금·원유 동반 하락. 특히 반도체/기술주 투매로 나스닥 -4.18%(2025/4 이후 최악), KOSPI -5.54%로 사상최고서 이틀 만에 급락 ([CNBC 6/6](https://www.cnbc.com/2026/06/04/stock-market-today-live-updates.html), 조사 6/6).
- **원화 급약세** — USD/KRW 1,550원대, 최근 한 달 -7.91%·1년 -14.69%. 지정학 긴장 + 한국 증시 약세가 원화 압박 ([TradingEconomics](https://tradingeconomics.com/south-korea/currency), 조사 6/6).
- **강한 미 고용지표 → 금리 우려** — 예상보다 강한 미 고용보고서가 인플레/금리 우려를 키워 금이 2026년 최저로 ([TradingEconomics 금](https://tradingeconomics.com/commodity/gold), 조사 6/6).
- **중동 지정학** — 이스라엘-레바논 휴전 기대 + 미-이란 협상 관망으로 유가 하락, 안전자산 채권은 일부 수혜(미 10Y 하락) ([CNBC US10Y](https://www.cnbc.com/quotes/US10Y), 조사 6/6).
## 관찰·가설 (미검증)
> 내 해석. **검증 전이므로 사실 아님.** canonical로 옮기기 전 `/invest-research`로 확인 대상.
- 가설 1: "원화가 1,550원대까지 약세면, 환노출 미국 ETF(환헤지 X)는 환차익이 일부 완충 역할을 할 수 있다" — 환율 방향 전망은 매우 불확실. **검증 필요.**
- 가설 2: "광범위 지수가 하루 -2~-5% 빠진 날은 코어 ETF 적립 매수에 유리할 수 있다" — '저점 매수' 타이밍 판단은 위험. 전략 ②(코어=무손절·장기보유)와 ③(패닉 반응 금지)에 비춰 **충동 매매 경계.**
## Promotable 후보
> `/invest-ingest`로 `wiki/invest-concepts/` 또는 `invest-strategy/`에 올릴 만한 것만 표기.
- (후보) "환헤지 vs 환노출 ETF의 차이" → `wiki/invest-concepts/` 개념 문서 후보. 가설 1 검증 시 `/invest-research`로 먼저 조사.
## 출처 / Sources (deep-research 조사 기록)
> 이 노트는 템플릿에 본 섹션이 추가되기 전(2026-06-08 이전) 작성됨 — 전체 출처는 위 고정 체크리스트·이슈의 행별 인라인 링크에 보존되어 있음 (TradingEconomics / CNBC / 한국은행 / Investing / OilPrice / Yahoo Finance / Fortune, 조사 6/6). 2026-06-10 구조 마이그레이션으로 섹션만 추가.
## Related
- 어제 노트: (없음 — 첫 일일 노트)
+103
View File
@@ -0,0 +1,103 @@
---
title: 2026-06-08 투자 일일 조사
source_type: invest-daily
status: raw
confidence: medium
tags: [invest-daily, personal-invest, macro]
date: 2026-06-08
last_reviewed: 2026-06-08
---
# 2026-06-08 투자 일일 조사
> Layer: `raw/invest-daily/` — 그날의 거시 자금흐름 조사. **수치마다 출처 링크 + 조사 시점 필수**(실시간 아님). 원본은 raw에 영구 보관.
> 🔬 deep-research Workflow(3표 검증)로 조사. 확정 못 한 값은 **미확인**으로 표기(추측 금지).
## Parent
- [[wiki/invest/invest-hub]]
## 고정 체크리스트 (매일 동일)
> 수치 + 방향 + 출처 + 조사시점(2026-06-08). 미확인은 비우지 않고 명시.
| 자산군 | 핵심 지표 | 값 / 방향 | 출처 | 조사시점 |
|---|---|---|---|---|
| 금리 | 미 10Y | **4.459%** (6/1, 이란 지정학發 ↑; 6/8 당일값 미확인) | [CNBC](https://www.cnbc.com/2026/06/01/treasury-yields-us-iran-war-oil.html) | 6/1 (medium) |
| 금리 | 한 기준금리 | **2.50%** (5/28 8회 연속 동결, 5-2 표결) | [BOK](https://www.bok.or.kr/eng/bbs/E0000634/view.do?nttId=10098190&menuNo=400423) | 5/28 |
| 환율 | USD/KRW | **1,535.0** (전일 1,539.1 대비 -4.1, 원화 강세) | [Herald](https://biz.heraldcorp.com/article/10766382) | 6/8 종가 |
| 원자재 | WTI/Brent | Brent **~$93** (6월초, 이란 사태로 높음; 사태 전 $72) | [Kitco](https://www.kitco.com/news/article/2026-06-01/iran-war-volatility-has-boosted-commodities-across-complex-and-gold-oil-and) | 6/1 |
| 원자재 | 금 | **$4,370 하회** (2026 최저, 주간 ~-4%, 달러·금리 역풍) | [Kitco](https://www.kitco.com/news/article/2026-06-01/iran-war-volatility-has-boosted-commodities-across-complex-and-gold-oil-and) | 6월초 |
| 주요지수 | KOSPI | **7,484.41** (-676.18p, **-8.29%**, Level-1 서킷브레이커) | [Korea Herald](https://www.koreaherald.com/article/10765666) | 6/8 종가 |
| 주요지수 | S&P500/나스닥 | **미확인** (6/8 종가 교차검증 실패; 6/5 미국발 반도체 급락) | — | — |
| 코인 | BTC/ETH | **미확인** (단일 출처만 — 6/8 가격 교차검증 실패) | — | — |
## 오늘의 이슈 (가변)
1. **KOSPI -8.29% 폭락 + 서킷브레이커** (지수 사상 9번째, 포인트 기준 사상 2번째 하락). YTD +75% 급등 뒤의 급락. ([bloomingbit](https://en.bloomingbit.io/feed/news/113766), 6/8)
2. **직접 트리거 = 반도체/AI 매도**: Broadcom AI 칩 매출 가이던스 미스(~$16B vs 컨센서스 ~$17.2B) → 6/5 **SOX -10.3%**(2020.3 이후 최악, 시총 $1T+ 증발), **Nvidia -6.2%·Micron -13%**. ([thedeepdive](https://www.thedeepdive.ca/south-korea-halts-kospi-trading-after-8-crash-as-semiconductor-stocks-collapse/), 6/5~6/8)
3. **이란-미국 지정학**(협상 중단·호르무즈 봉쇄 위협) → 6/1 미 10Y 금리·유가 상승. ([CNBC](https://www.cnbc.com/2026/06/01/treasury-yields-us-iran-war-oil.html), 6/1)
## 관찰·가설 (미검증)
> **검증 전이므로 사실 아님.**
- 한국 증시는 반도체(삼성·하이닉스) 비중이 커서 **글로벌 반도체 매도에 증폭되어 반응**한 듯(가설). 개별종목 정확 하락폭·외국인 순매도 규모는 미확인.
- **위험회피 국면인데 원화는 오히려 강세**(1,535, -4.1원) — "위험회피→신흥국통화 약세" 직관과 반대. 왜인지 추가 조사 필요(가설).
- 금융주가 금리 상승에 올랐는지(로테이션 예상)는 이번 조사에서 **증거 없음** — 반도체 급락만 확인.
## Promotable 후보
> 첫 관측이라 아직 없음. **반복 확인된 패턴만** `/invest-research`로 검증 후 `/invest-ingest`.
- (없음 — 1회 관측으로 단정 금지)
## 분야 관찰 / Field Observations
> 오늘 움직인 카드 vs 그 카드 예측. 카드 허브: [[wiki/invest-concepts/field-map]]. **첫 채점.**
| 오늘 움직인 카드 | 방향 | 그 카드 예측 연결이 맞았나?(확인/반증) | 새 가설/메모 |
|---|---|---|---|
| [[wiki/invest-concepts/field-semiconductors]] | ↓↓ (SOX -10.3%, 엔비디아 -6.2%·마이크론 -13%, 6/5) | 카드 "반도체↑→지수↑"의 **역방향 확인 ✓** — 반도체↓ → KOSPI -8.29%↓ (반도체가 시장 끌어내림) | 글로벌 대장(엔비디아·SOX) → 한국 반도체 추종 급락(가설). 개별폭 미확인 |
| [[wiki/invest-concepts/field-gold]] | ↓ (2026 최저 $4,370 하회) | 카드 "달러↑·금리↑→금↓" **확인 ✓** (기회비용) | 로테이션 ②③축 성립 정황 |
| [[wiki/invest-concepts/field-us-rates]] | ↑ (6/1 4.459%, 이란發) | 카드 "금리↑→성장주↓" **부분 확인 ✓** (반도체=성장주 급락) / "금융↑" 부분은 **미확인** | 금융 반대움직임 증거 못 찾음 |
| [[wiki/invest-concepts/field-dollar]] | 강세 맥락(고금리·위험회피) | 카드 "달러↑→신흥국통화↓"가 **이날 KRW엔 반증 ✗** (위험회피인데 원화 1,535 강세) | ❗왜 원화 강세? = 가장 큰 학습거리 |
| [[wiki/invest-concepts/field-rotation]] | ②금리·③달러 축 | ③ 달러↑→금↓ **확인 ✓** / ② 금리↑→성장주↓ **확인 ✓** (금융↑은 미확인) | 로테이션 지도 첫 채점 — 2/3 성립 |
> **첫 관측 요약**: 카드 예측 중 *반도체→시장*, *금리/달러→금*, *금리→성장주*는 **확인**됐고, *달러→원화 약세*는 **반증**(원화 오히려 강세)됐다. 반증이 더 중요한 학습거리 — "왜 위험회피인데 원화가 강세였나"가 다음 `/invest-research` 후보.
## 출처 / Sources (deep-research 조사 기록)
> 이 노트는 deep-research Workflow가 **6각도 fan-out → 26개 사이트 fetch → 81 claim 추출 → 25 검증(14 confirmed / 11 killed)** 한 결과. 아래는 조사한 전(全) 출처. `[primary]`=공식·1차, `[secondary]`=언론, `[blog]/[unreliable]`=약함(특히 claims:0 = 교차검증 실패로 **미채택**).
**금리·채권**
- `[primary]` 한국은행(BOK) 5/28 통화정책 — https://www.bok.or.kr/eng/bbs/E0000634/view.do?nttId=10098190&menuNo=400423
- `[secondary]` TradingEconomics 미 10Y — https://tradingeconomics.com/united-states/government-bond-yield · KED Global — https://www.kedglobal.com/central-bank/newsView/ked202605280001
- `[unreliable]` 美 재무부 일별금리(fetch 실패·미채택) — https://home.treasury.gov/resource-center/data-chart-center/interest-rates/TextView
**환율**
- `[primary]` Fed H.10 — https://www.federalreserve.gov/releases/h10/hist/dat00_ko.htm
- `[secondary]` TradingEconomics KRW — https://tradingeconomics.com/south-korea/currency · investing.com — https://kr.investing.com/currencies/usd-krw · EBC(원화 약세 요인) — https://www.ebc.com/forex/why-is-the-south-korean-currency-so-weak-key-factors-explained
- `[unreliable]` 나무위키 원화 고환율(미채택) — https://namu.wiki/
**원자재(유가·금)**
- `[primary]` Saxo(지정학·금) — https://www.home.saxo/content/articles/commodities/gold-rises-with-oil-as-geopolitical-risk-overwhelms-rate-headwinds-30042026
- `[secondary]` Kitco — https://www.kitco.com/news/article/2026-06-01/iran-war-volatility-has-boosted-commodities-across-complex-and-gold-oil-and · CNBC(금리·유가) — https://www.cnbc.com/2026/06/01/treasury-yields-us-iran-war-oil.html
- `[blog]` fxdailyreport WTI(약함) — https://fxdailyreport.com/wti-crude-oil-price-analysis-for-june-8-2026/
**주가지수 (KOSPI 폭락·반도체)**
- `[secondary]` bloomingbit/Bloomberg — https://en.bloomingbit.io/feed/news/113766 · Korea Herald — https://www.koreaherald.com/article/10765666 · TheDeepDive(서킷브레이커·반도체) — https://www.thedeepdive.ca/south-korea-halts-kospi-trading-after-8-crash-as-semiconductor-stocks-collapse/ · Yahoo Finance(미장·고용·칩) — https://finance.yahoo.com/markets/live/... · 247wallst — https://247wallst.com/investing/2026/06/08/the-korean-stock-market-just-crashed-sunday-night-will-the-nasdaq-follow-tomorrow/
- `[unreliable]` CNBC live · TheStreet(claims:0 미채택)
**암호화폐 (BTC/ETH — 본문 미확인 처리)**
- `[secondary]` Yahoo(6/8·6/5) · cryptopond — https://cryptopond.com/bitcoin-breaks-below-60k-as-crypto-selloff-hits-new-2026-low/ · CoinDesk — https://www.coindesk.com/markets/2026/06/04/bitcoin-selloff-continues-...
- ⚠️ BTC/ETH 6/8 값은 **단일 출처**라 본문에서 미확인 처리(채택 안 함).
**뉴스·거시 로테이션 (claims:0 미채택)**
- `[unreliable]` bbntimes · CNN(6/5 매도) · CNBC oil(6/8) — 교차검증 실패로 미채택, 맥락 참고만.
> ⚠️ **미래 시점(2026) 수치는 환각 위험이 가장 큼.** `[primary]`라도 본인이 링크 열어 교차검증 권장. claims:0/`[unreliable]` 출처는 *조사는 했으나 채택 안 한* 기록(투명성용).
## Related
- 어제 노트: [[raw/invest-daily/2026-06-06]]
+50
View File
@@ -0,0 +1,50 @@
---
title: 매매 원장 / Trade Ledger
source_type: invest-ledger
status: raw
confidence: unknown
tags: [invest-ledger, personal-invest, finance]
created: 2026-06-05
last_reviewed: 2026-06-05
---
# 매매 원장 / Trade Ledger
> Layer: `raw/invest-ledger/` — 실제 매수/매도의 **사실 기록**. 단일 원장 파일에 모든 거래를 누적(설계 §8-3). `/invest-decide`가 행을 추가하며 `wiki/invest-strategy/`의 규칙 위반을 체크. wiki로 옮기지 않음.
## Parent
- [[wiki/invest/invest-hub]]
## 현재 포지션 / Open Positions
| 종목/티커 | 분류(코어/베팅) | 보유수량 | 평균단가 | 현재 비중% | 메모 |
|---|---|---|---|---|---|
## 거래 내역 / Trade Log
> 시간 역순(최신 위). 모든 행은 근거 문서 링크 필수.
> ⚠️ **실손익 = (단가×수량) − 수수료 ± 환차손익 − 세금.** 해외상장 ETF는 **양도세(연 250만 공제 후 22%, 손익통산)** + **체결 환율** 이 손익에 실질적 영향 → 아래 컬럼 기록. 국내상장은 세제 다름.
| 날짜 | 매수/매도 | 종목 | 수량 | 단가 | 수수료 | 체결환율 | 금액(원) | 계좌 | 근거(링크) | 규칙체크 |
|---|---|---|---|---|---|---|---|---|---|---|
## 규칙 위반 이력 / Rule-check Findings
> `/invest-decide`가 빨간 플래그를 낸 건 기록. 무시하고 진행했다면 그 사유도.
| 날짜 | 위반 규칙 | 내용 | 사용자 처리 |
|---|---|---|---|
## 손익 요약 / P&L Summary
> `/invest-review` 실행 시 갱신.
- 총 투입원금:
- 누적 수수료:
- 환차손익(해외):
- 평가금액:
- 실현손익(세전):
- 예상 양도세(해외 ETF, 250만 공제 후 22%):
- 실현손익(세후 추정):
- 목표 대비: