48 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-metrics-alerting-contract | branch-note | raw | feature-metrics-alerting-contract |
|
|
|
2026-05-22 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-019 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-019 |
|
1 | 3b83f7a53dc82997a9f9d3ff12f2106e16782bce65d5f95f8516a46532b0b2ee |
branch: feature-metrics-alerting-contract
Layer:
raw/branch-notes/— metrics와 alerting 기준을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다. governing 문서는 wiki/projects/ca-tmpl/observability-log-metric-trace-runbook (§Metric).
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: metric key·cardinality·alert contract test가 통과한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1 |
foundation이 envelope schema와 error.category enum의 단일 owner다 | Work Item 완료 조건에 적용 | 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 |
|---|
목표
로그만으로 운영 감시는 부족합니다. skeleton은 HTTP, dependency, DB pool, JVM, retry/circuit breaker의 기본 metric과 P1/P2/P3 alert severity를 가져야 합니다.
- 이슈:
- PR:
범위
포함 범위
- HTTP latency/error rate metric.
- dependency latency/error rate metric.
- DB pool metric.
- JVM/process metric.
- retry/circuit breaker metric.
- alert severity
P1/P2/P3기준. - metric naming/tag 기준.
제외 범위
- Grafana dashboard 구현.
- Prometheus/CloudWatch 특정 vendor 설정.
- SLO/SLA 정식 수립.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/metric-micrometer-naming-convention-official.md | Micrometer dot |
| raw/company-tech-blogs/metric-toss-payments-alert-severity-techblog.md | 국내 fintech의 P1/P2/P3 운영 사례 |
| raw/official-docs/metric-google-sre-slo-burn-rate.md | threshold alert가 baseline이고 burn-rate는 후속 도입이라는 ca-tmpl 결정이 SRE Workbook 전형적 진화 경로와 정합함을 확인 |
| raw/official-docs/metric-otel-metrics-data-model-spec.md | naming 일부 다름(`http |
| raw/official-docs/resilience4j-micrometer-module | Resilience4j Micrometer 모듈 — resilience4j.circuitbreaker.calls/state/resilience4j.retry.calls/bulkhead.queue.depth/ratelimiter.available.permissions metric 명 + kind/name tag 의 1차 근거 (D4 retry/CB metric default consume 직접 증명) |
| raw/official-docs/metric-micrometer-high-cardinality-tags-detector | D8 — unbounded tag(userID/requestID/traceID 등)가 millions of time series + excessive memory consumption 야기함을 Micrometer 공식 문서가 명시. high-cardinality 금지 tag 목록의 직접 근거 (MM-HCARD-C1, MM-HCARD-C2) |
| raw/official-docs/metric-prometheus-label-cardinality-best-practices | D8 — Prometheus 공식 — "every unique combination of key-value label pairs represents a new time series" + user IDs / email / unbounded set label 금지 직접 경고 (PROM-CARD-C1, PROM-CARD-C2) |
| raw/official-docs/metric-micrometer-histogram-percentile-concepts | D9 — latency timer 의 percentile/histogram 게시 전략 (publishPercentiles vs publishPercentileHistogram vs serviceLevelObjectives). client-side percentiles 가 dimension 간 집계 불가하다는 공식 caveat (MM-HIST-C2, MM-HIST-C4). |
| raw/official-docs/metric-google-sre-workbook-on-call | D10 — alert(page)가 monitoring console(dashboard) 링크를 포함해야 하고, 각 alert 에 playbook/runbook entry 가 있어야 한다는 Google SRE 공식 근거 (SRE-ONCALL-C1, SRE-ONCALL-C2, SRE-ONCALL-C4) |
| raw/official-docs/metric-google-ewaschuk-philosophy-on-alerting | D10 — "각 alert/alert family 마다 playbook(runbook) entry" 원칙 + "page 는 actionable" + 4원칙(urgent/important/actionable/real) 의 직접 근거 (SRE-PHIL-C1, SRE-PHIL-C2, SRE-PHIL-C3) |
| raw/official-docs/metric-prometheus-histograms-vs-summaries-practices | D9 — Summary quantile 을 인스턴스 간 avg() 로 집계하면 통계적으로 무의미하다는 Prometheus 공식 경고 (PROM-HIST-C1, PROM-HIST-C2). classic histogram 올바른 집계 구문 (PROM-HIST-C3). |
외부 근거 / 대안 조사 (2026-05-22 — Group G-A: Metrics alerting)
채택 결정 + 뒷받침
- 결정: Micrometer dot.case naming + Prometheus exposition + P1/P2/P3 정량 threshold + cardinality bounds.
- 뒷받침 source:
- raw/official-docs/metric-micrometer-naming-convention-official.md — Micrometer dot.case + unit suffix convention이 Spring Boot 3 default와 100% 일치함을 spec으로 확인.
- raw/company-tech-blogs/metric-toss-payments-alert-severity-techblog.md — 국내 fintech의 P1/P2/P3 운영 사례. 영문 dot-case naming 강제 + alert payload에 dashboard/log/runbook 링크 필수 정책이 ca-tmpl과 정합.
- raw/official-docs/metric-google-sre-slo-burn-rate.md — threshold alert가 baseline이고 burn-rate는 후속 도입이라는 ca-tmpl 결정이 SRE Workbook 전형적 진화 경로와 정합함을 확인.
검토 대안 + source
- 대안 1 — OpenTelemetry metrics 직접 채택: raw/official-docs/metric-otel-metrics-data-model-spec.md. naming 일부 다름(
http.server.request.durationvshttp.server.requests), Micrometer OTLP bridge 사용 시 swap 가능. - 대안 2 — SLO burn-rate alerting: raw/official-docs/metric-google-sre-slo-burn-rate.md. SLO 정식 수립 후 도입 권장, 현재는 잠정 SLO p99=1s 기반 threshold.
비교 핵심 1줄
Micrometer + Prometheus는 Spring Boot 3 default + JVM 생태계 표준으로 도입 비용 최저, OTel metrics는 cross-language 통일, SLO burn-rate는 SLO 수립 후 단계.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Metric / Alert Defaults" / "Cardinality Bounds" / "Histogram Buckets / Percentile" / "P1/P2/P3 정량 기준" / "Retry / CircuitBreaker / DB Pool Minimum Metric Set" 참조. HTTP/dependency/DB pool/JVM/retry-CB/alert severity/naming-tag 모두 표 또는 결정 라인으로 반영됨. 잔존 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 승급 위치 |
진행 중 메모
- log field와 metric tag 이름은 가능한 한 일치시킵니다. (registry 각 행의
log_field_mapping이 SSOT — log/metric 상관용, raw/branch-notes/feature-log-management-contract 와 정합)
결정 사항 (decisions)
- 2026-05-22: metrics/alerting을 log contract와 별도 branch로 분리.
- 2026-05-22: metric naming은 Micrometer naming default, log/trace key는 foundation registry를 소비.
- 2026-05-22: alert threshold는 임의 수치가 아니라 SLO/error budget 또는 documented operational default에 연결.
- 2026-05-22: retry/circuit breaker metric은 outbound branch의 Resilience4j default를 소비.
- 2026-05-22: metric naming convention = Micrometer dot.case default. unit suffix는 Micrometer convention(
.seconds/.bytes/.total) 강제. - 2026-06-14: (branch-spec 자동조사) D8 cardinality 금지 정책을 Prometheus/Micrometer 공식 문서로 격상 —
UNSUPPORTED_DECISION해소. 근거 raw/official-docs/metric-prometheus-label-cardinality-best-practices, raw/official-docs/metric-micrometer-high-cardinality-tags-detector. (수치 상한 ≤200/≤50 등은 여전히 운영 가정.) - 2026-06-14: (branch-spec 자동조사) D9 histogram 전략 정합 — Prometheus 환경에서 client-side
publishPercentiles는 non-aggregable.publishPercentileHistogram+serviceLevelObjectives(→histogram_quantile()집계)를 cross-instance source of truth 로 둔다. 근거 raw/official-docs/metric-micrometer-histogram-percentile-concepts, raw/official-docs/metric-prometheus-histograms-vs-summaries-practices. registrymetrics.yaml가 두 방식을 동시 선언함을 surface. - 2026-06-14: (branch-spec 자동조사) D10 alert payload — runbook + dashboard(monitoring console) 링크는 Google SRE 공식 지지로 격상(raw/official-docs/metric-google-sre-workbook-on-call, raw/official-docs/metric-google-ewaschuk-philosophy-on-alerting). log query 링크는
operational-default로 격하 표기(SRE 문헌 직접 명문 없음).
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다.
Decision ID는 이 branch-note 안에서 안정적으로 유지한다.Supporting Claims는raw/<category>/<slug>.md#<CLAIM-ID>형식으로 연결한다.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | metrics/alerting 을 log contract 와 별도 branch 로 분리 | N/A (조직 운영 정책) | UNSUPPORTED_DECISION (조직 / branch 분할은 내부 운영 정책 — 외부 raw 근거 없음) | N/A | branch 분할 자체는 외부 표준 인용 대상 아님. 운영 편의 |
| D2 | metric naming = Micrometer dot.case default + unit suffix (.seconds/.bytes/.total) 강제 |
JVM/Micrometer 스택일 때 이 결정. cross-language 통일 필요 시 D6 대안(OTel) | raw/official-docs/metric-micrometer-naming-convention-official.md#MM-NAME-C1, raw/official-docs/metric-micrometer-naming-convention-official.md#MM-NAME-C2, raw/official-docs/metric-micrometer-naming-convention-official.md#MM-NAME-C3 |
official-vendor-doc (Micrometer reference — lowercase dot 컨벤션 + 시스템 별 자동 변환 + http.server.requests 예시) |
MM-NAME-C4 (suffix .count/.total 자동 부착) 및 MM-NAME-C5 (base unit handling) 는 본 페이지 발췌에 명시 없음 → needs-confirmation (concepts/timers 별도 fetch 필요). unit suffix 강제 정책의 표준 출처 미확보 |
| D3 | alert threshold = SLO/error budget 또는 documented operational default 에 연결 (임의 수치 금지) | SLO 수립 후엔 burn-rate(D5)로 전환, 미수립 단계엔 documented default | raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C1, raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C6 |
official-vendor-doc (Google SRE Workbook — SLO 를 actionable alert 으로 + error budget 정의) |
SRE-BURN-C1 Usage Boundary: SLO 미수립 서비스에 적용 가능하다는 뜻 아님. ca-tmpl 의 "잠정 SLO p99=1s" 는 정식 SLO 가 아님 |
| D4 | retry/CB metric = Resilience4j default consume | retry/CB 라이브러리가 Resilience4j 일 때 (owner: outbound branch) | raw/official-docs/resilience4j-micrometer-module.md#R4J-MICROMETER-C1 (Micrometer 모듈이 InfluxDB/Prometheus 등 monitoring system 지원), #R4J-MICROMETER-C2 (resilience4j.circuitbreaker.calls + kind (successful/failed/ignored) + name tag), #R4J-MICROMETER-C3 (resilience4j.circuitbreaker.state Gauge + 5개 state: closed/open/half_open/forced_open/disabled), #R4J-MICROMETER-C4 (TaggedRetryMetrics.ofRetryRegistry(...).bindTo(meterRegistry) 패턴) |
official-vendor-doc (Resilience4j 공식 docs verbatim — 2026-05-27 확인) |
Spring Boot starter (resilience4j-spring-boot3) 자동 bind 동작은 cited raw 범위 밖 — R4J-MICROMETER-C2 Usage Boundary 명시 ("Spring Boot starter 가 자동으로 bind 한다는 뜻은 본 인용에서는 명시 안 됨"). state Gauge value 가 boolean 인지 enum index 인지 미명시 — 실측 필요. histogram/percentile default 노출 여부도 cited raw 범위 밖. enum drift: registry resilience4j.circuitbreaker.state 는 6 state(CLOSED/OPEN/HALF_OPEN/DISABLED/FORCED_OPEN/METRICS_ONLY)를 선언 — R4J-MICROMETER-C3 인용(5 state)보다 METRICS_ONLY 1개 많음. owner feature-outbound-http-client-baseline 와 enum 정합을 코딩 전 확인 |
| D5 | burn-rate 기반 alert 는 추후 도입 (현재 단순 threshold) | SLO 정식 수립 후 이 결정 폐기 → burn-rate 채택 | raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C2, raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C3, raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C4, raw/official-docs/metric-google-sre-slo-burn-rate.md#SRE-BURN-C5 |
official-vendor-doc (paging 시작값 2%/1h + 5%/6h, multi-window 1/12 ratio, burn-rate powerful) |
SRE-BURN-C5 Usage Boundary: threshold alert 가 항상 inferior 라는 결론 아님 — SLO 미수립 단계에서는 threshold 가 가능한 fallback. ca-tmpl 의 현재 단계와 정합 |
| D6 | OpenTelemetry metrics 직접 채택 거부 (Micrometer + Prometheus 유지) | cross-language 신호 통일이 필수가 되면 재검토 (Micrometer OTLP bridge swap) | raw/official-docs/metric-otel-metrics-data-model-spec.md#OTEL-MET-C1, raw/official-docs/metric-otel-metrics-data-model-spec.md#OTEL-MET-C2 |
official-standard (OTel data model 의 Prometheus Remote Write 변환 보장 — swap 가능성) |
OTEL-MET-C7 (HTTP semantic conventions 의 required attributes) 는 본 페이지에 명시 없음 → needs-confirmation. ca-tmpl 의 Micrometer naming (uri_template) 과 OTel semconv (http.route) 정합 별도 검증 |
| D7 | P1/P2/P3 정량 기준 (잠정 SLO 기반): P1=>5%/5분, P2=>1%/10분, P3=>0.1%/1시간 | 정식 SLO 합의 시 burn-rate(D5) 기반으로 재산정 | UNSUPPORTED_DECISION (raw/company-tech-blogs/metric-toss-payments-alert-severity-techblog.md#TOSS-ALERT-C3 는 unverified — 출처 검증 실패. 본 raw 의 TOSS-ALERT-C3/C4/C5/C6/C7 모두 needs-confirmation. company-tech-blog 는 official best practice 가 아님) |
company-case-study (TOSS-ALERT-C1 verified only — 로깅 inputs) |
정량 threshold 값은 ca-tmpl 잠정 SLO 의 운영 가정. 외부 공식 표준 없음. toss 사례를 official best practice 로 표현 금지 |
| D8 | cardinality bounds: user_id/request_id/raw_url 등 high-cardinality tag 금지 + bounded whitelist (status_code≤7 / uri_template≤200 / dependency_name≤50) | tag 값이 unbounded(사용자 입력 유래) 면 금지·정규화. trace_id 등 개별 식별자가 필요하면 exemplar/trace 로(D 의존: tracing) | raw/official-docs/metric-prometheus-label-cardinality-best-practices.md#PROM-CARD-C1, raw/official-docs/metric-prometheus-label-cardinality-best-practices.md#PROM-CARD-C2, raw/official-docs/metric-micrometer-high-cardinality-tags-detector.md#MM-HCARD-C1, raw/official-docs/metric-micrometer-high-cardinality-tags-detector.md#MM-HCARD-C2 |
official-vendor-doc (Prometheus 공식 — high-cardinality label 이 time series 폭증 야기 직접 경고; user IDs / email / unbounded set 금지 명시 + Micrometer 공식 userID/requestID/traceID 명시) |
PROM-CARD-C2 는 user_id/email/unbounded set 을 예시로 열거 — request_id/raw_url/ip_address 금지는 이 원칙에서 추론한 적용. UNSUPPORTED_IMPL_DECISION: 정량 상한값(≤200/≤50 등)은 공식 spec 없는 운영 가정 |
| D9 | latency timer = SLO-driven 분포 게시. registry(metrics.yaml)는 timer 행마다 percentiles: [0.5,0.9,0.95,0.99](client-side) + histogram_buckets: slo_driven(aggregable) 둘 다 선언 |
Prometheus + 다중 인스턴스 → 집계는 histogram 버킷. 단일 인스턴스 즉시 가시성만 필요 → client-side percentile 로 충분 | raw/official-docs/metric-micrometer-histogram-percentile-concepts.md#MM-HIST-C2 (Prometheus 대상 시 histogram 게시 공식 권장 — 차원 간 집계 가능), #MM-HIST-C4 (client-side percentile 은 redundant + non-aggregable across dimensions), raw/official-docs/metric-prometheus-histograms-vs-summaries-practices.md#PROM-HIST-C1 (quantile 평균은 통계적으로 무의미), #PROM-HIST-C3 (histogram_quantile() 가 올바른 집계 구문) |
official-vendor-doc (Micrometer) + official-standard (Prometheus) |
설계 위험: client-side publishPercentiles 값은 인스턴스 간 avg()/sum() 불가(PROM-HIST-C2 // BAD!). 다중 인스턴스 cross-instance p99 은 slo_driven 버킷 + histogram_quantile() 가 source of truth. publishPercentileHistogram 기본 ~73 버킷/dim → cardinality 부담(min/maxExpectedValue 튜닝). UNSUPPORTED_IMPL_DECISION: SLO 경계값(100ms/500ms/1s)은 잠정 SLO 역산 — 공식 근거 없음 |
| D10 | alert payload = runbook + dashboard(monitoring console) 링크 [official-supported] + log query 링크 [operational default] | runbook/dashboard 가 존재하면 링크 강제. 미작성 단계엔 placeholder 허용 | runbook+dashboard: raw/official-docs/metric-google-sre-workbook-on-call.md#SRE-ONCALL-C1 ("Ensure pages link to relevant monitoring consoles"), #SRE-ONCALL-C4 ("Each alert should have a corresponding playbook entry"), raw/official-docs/metric-google-ewaschuk-philosophy-on-alerting.md#SRE-PHIL-C1 (alert/family 마다 playbook entry), #SRE-PHIL-C2 ("Every page should be actionable") · log query: UNSUPPORTED_IMPL_DECISION (SRE 문헌이 log query URL 까지는 직접 명문화 안 함 — ca-tmpl 운영 default) |
official-vendor-doc (Google SRE — runbook+dashboard) / operational-default (log query) |
Ewaschuk 문서는 playbook entry 의 필요성을 말함 — alert annotation 에 URL embed 를 직접 명문화하진 않음(SRE-ONCALL-C1 의 "pages link to consoles" 가 dashboard 링크를 직접 지지). log query URL 은 vendor-specific(CloudWatch/Kibana/Loki) → 환경 이전 시 깨질 수 있음. toss(TOSS-ALERT-C5) 는 여전히 unverified — official 표현 금지 |
구현 가이드
결정 (Decisions) 이 "무엇" 이라면, 본 §는 "어디에 어떻게" 의 사전 명세. 상세 표(아래 §Metric/Alert Defaults · §Cardinality Bounds · §Histogram Buckets/Percentile · §P1/P2/P3 · §Retry/CB/DB Pool)가 값의 SSOT 이며, 본 §는 그 표를 코드·registry anchor 에 연결한다. 계약 값의 최종 SSOT =
ca-tmpl/docs/registries/metrics.yaml(owner_branch).
1. Metric registry SSOT 와 owner 분할
Trace: D2(naming)·D8(cardinality)·D9(histogram) + In-scope 모든 metric. Supporting anchor:
ca-tmpl/docs/registries/metrics.yaml의owner_branch:행 (계약 값의 SSOT — invent 금지).
본 branch 가 소유(owner_branch: feature-metrics-alerting-contract)하는 registry 행 — 정의·tag·alert threshold 가 본 branch 결정:
| metric | type | tags (cardinality 상한) | alert |
|---|---|---|---|
http.server.requests |
timer (seconds) | method≤8 / status≤7 / uri_template≤200 | P1 err>5%/5m·>10%/1m, P2 >1%/10m, P3 >0.1%/1h |
http.server.requests.latency |
timer | method≤8 / uri_template≤200 | P1 p99>5s/5m, P2 p99>1s/10m, P3 p99>500ms/30m |
dependency.client.requests |
timer | dependency_name≤50 / dependency_type≤10 / outcome≤5 | P1 required dep down 2m, P2 optional degraded 5m, P3 spike 10x |
db.query.duration |
timer | operation≤20 / outcome≤3 | P2 p99>1s/10m |
jvm.memory.used |
gauge (bytes) | area≤2 / id≤10 | P2 heap/max>0.85/10m |
jvm.gc.pause |
timer | action≤10 / cause≤10 | P2 p99>500ms/10m |
jvm.threads.live |
gauge (total) | — | P3 >2x baseline/30m |
process.uptime |
gauge (seconds) | — | P1 uptime reset <60s (crash loop) |
다른 branch 가 소유하는 행을 consume(본 branch 는 alert severity/cardinality 계약만 정합, 정의는 owner):
| consumed metric(s) | owner branch | 본 branch reference |
|---|---|---|
resilience4j.retry.calls / circuitbreaker.state / circuitbreaker.calls |
raw/branch-notes/feature-outbound-http-client-baseline | D4, §Retry/CB/DB Pool |
hikaricp.connections.acquire / usage / active |
feature-persistence-failure-baseline |
§Retry/CB/DB Pool |
executor.* / job.* |
feature-background-job-async-contract |
(alert severity 정합만) |
lock.* |
feature-distributed-lock-contract |
(cardinality 정합만) |
outbox.* |
feature-domain-event-outbox-contract |
(cardinality 정합만) |
cache.* |
feature-cache-consistency-contract |
(cardinality 정합만) |
log.appender.dropped.total |
feature-log-management-contract |
§진행 중 메모 (log↔metric 정합) |
tracing.sampling.rate |
feature-distributed-tracing-contract |
§엣지 (exemplar 위임) |
2. 강제 메커니즘 (enforcement) — 현재 등급
Trace: D8(cardinality)·D2(naming) + §테스트 계약. Supporting anchor:
src/grep (2026-06-14).
- registry 모든 행은
required_test: contract-verification:metrics-cardinality선언 → 계약 위반 시 실패해야 하는 테스트. - 실측(2026-06-14
src/grep):shared-contract/src/main/java/dev/caskeleton/shared/metrics/패키지는 비어 있음. cardinality/naming 강제 클래스 +contract-verification:metrics-cardinality테스트 =planned(미구현). 본 branch 소유 HTTP/dependency/JVM timer 계측 코드(MeterRegistryCustomizer/Timer.builderconfig)도 미작성 =documented-only. - 현재 등급 요약: registry/계약 =
documented-only; 코드 계측 + 강제 테스트 =planned. (sibling 의BackgroundJobMetrics/OutboxMetrics/MeteredDistributedLockPort/OutboundHttpResilienceConfig는actually-implemented— 각자 owner 범위, 본 branch 자기 보고로 FACT 화 금지.) - 실측(2026-06-15 구현 Task 1):
shared-contract모듈에 4개 pure contract type 추가 —actually-implemented+locally-verified:AlertSeverity(enum, D7) — P1/P2/P3,key(),fromKey(String)case-insensitive. 11 tests PASS.MetricNaming(final class, D2/D3) —ALLOWED_UNITS,isValidName(),isAllowedUnit(),toPrometheusName(). 27 tests PASS.ForbiddenMetricTags(final class, D8) —FORBIDDEN,isForbidden(),firstForbidden(). 16 tests PASS.CardinalityBounds(final class, §Cardinality Bounds) — named int constants +limitFor(). 16 tests PASS.- 모두 Java stdlib only (import 검증 완료).
./gradlew :shared-contract:testBUILD SUCCESSFUL.
- 실측(2026-06-15 구현 Task 2):
app-bootstrap모듈에 runtime enforcement + contract test 추가 —actually-implemented+locally-verified:MetricsCardinalityMeterFilter(implementsMeterFilter, D8 runtime deny-list) —accept()returns DENY for any forbidden tag key inForbiddenMetricTags.FORBIDDEN. 11 tests PASS.MetricsDistributionMeterFilter(implementsMeterFilter, D9 SLO-driven histogram) —configure()appliespercentilesHistogram(true)+percentiles(0.5,0.9,0.95,0.99)+ SLO boundaries (100ms/500ms/1s/5s) + min/maxExpected for 5 owned timers (http.server.requests,http.server.requests.latency,dependency.client.requests,db.query.duration,jvm.gc.pause); passes through unchanged for non-owned meters. 23 tests PASS. (Fix 2026-06-15:dependency.client.requestswas initially missing fromSLO_DRIVEN_TIMERSdespite being an ownedslo_driventimer permetrics.yaml:84,89— spec reviewer Req #10 PARTIAL finding. Added in surgical correction with TDD red→green proof.)MetricsContractConfig(@Configuration) —ObjectProvider<MeterRegistry>+@PostConstruct installFilters(); public staticinstall(MeterRegistry)for testability; no-op when registry absent. 5 tests PASS.MetricsAlertingContractTest— 15 contract tests (global D2/D8/D9/cardinality/alert-key checks + row-specific #1/#2/#3/#4 + MeterFilter behaviour + new registry↔filter coverage drift guard); all 15 PASS locally (metrics.yaml present).Assumptions.assumeTrue(metricsRoot != null, ...)guard in place — skips (not fails) when docs/registries/metrics.yaml absent on CI. Unusedimport java.util.Collection;removed../gradlew :app-bootstrap:testBUILD SUCCESSFUL (full suite)../gradlew verifyCleanArchitectureDependenciesBUILD SUCCESSFUL.- 설치 방식:
MeterFilter.@Bean방식 아님 —registry.config().meterFilter(...)직접 (OutboundHttpResilienceConfig I8 패턴 미러). No new Gradle dependencies added.
- UNSUPPORTED_IMPL_DECISION: 강제 메커니즘 형태 —
MeterFilterdeny-list(MM-HCARD-C4) vs registry-vs-actuator diff 스모크 vs runtimeHighCardinalityTagsDetector(MM-HCARD-C5는 Observation API 만 권고) — 는 근거 raw 가 원칙만 권고하고 메커니즘은 비권고 → 구현자 trade-off. 권고:MeterFilterdeny-list + registry↔/actuator/prometheusdiff 스모크 병행.
3. unit suffix 변환
Trace: D2 +
MM-NAME-C1~C3.
- registry naming = Micrometer dot.case. Prometheus exposition 시
.→_, unit suffix(seconds/bytes/total) 자동 변환. - UNSUPPORTED_IMPL_DECISION: Spring Boot 3 가
http.server.requests에 자동 부착하는 정확한 Prometheus suffix(_seconds_bucket/_count/_sum)는 cited raw 미명시 → §Claims To Verify 의 actuator 확인 항목으로 위임.
Metric / Alert Defaults
| item | default | forbidden |
|---|---|---|
| HTTP metric | http.server.requests with method/status/uri-template |
raw URL or user id tag |
| dependency metric | dependency.client.requests with dependency.name/type/outcome |
endpoint with secret tag |
| retry metric | Resilience4j retry/circuit metric | retry without metric |
| alert severity | P1, P2, P3 |
severity missing |
| threshold source | SLO/default table | unexplained magic number |
Cardinality Bounds
| tag | 상한 (per metric) |
|---|---|
| status_code | 7 (1xx-5xx + ok/other) |
| uri_template | 200 |
| dependency_name | 50 |
| error_code | 100 — error registry(ca-tmpl/docs/registries/error-codes.yaml)의 row 상한과 정합. registry 상한 변경 시 본 표 동시 업데이트. |
| tenant_id | 1000 (활성 시) — ULID 원본을 직접 사용하지 않음. metric label로는 (a) bounded mapping table id (tenant 등록 시 ascending integer 부여) 또는 (b) tenant cohort bucket(예: hash mod 100) 사용. 1001번째 tenant 등장 시 cardinality 정책: 새 tenant는 bucket으로 자동 fold. |
| outcome (resilience4j) | 5 (SUCCESS/FAILURE/CIRCUIT_OPEN/TIMEOUT/REJECTED) |
high-cardinality 금지 tag: user_id, request_id, raw_url, raw_query, raw_header_value, ip_address. (근거: PROM-CARD-C1/C2 user IDs·email·unbounded set 금지 + MM-HCARD-C1/C2 userID/requestID/traceID → millions of time series.)
Histogram Buckets / Percentile
Trace: D9. registry SSOT =
ca-tmpl/docs/registries/metrics.yaml(timer 행의percentiles+histogram_buckets: slo_driven).
- HTTP latency / DB query / dependency call (registry timer 행 공통):
- aggregable 소스 (권장 source of truth):
publishPercentileHistogram()+serviceLevelObjectives(...)→ registryhistogram_buckets: slo_driven. Prometheushistogram_quantile(0.95, sum by (le)(rate(..._bucket[5m])))로 인스턴스 간 집계 (MM-HIST-C2,PROM-HIST-C3). - client-side 편의값:
publishPercentiles(0.5, 0.9, 0.95, 0.99)→ registrypercentiles: [...]. 단일 인스턴스 즉시 가시성용. 인스턴스 간 집계 금지 (MM-HIST-C4,PROM-HIST-C1/C2// BAD!).
- aggregable 소스 (권장 source of truth):
- bucket = SLO-driven. 명시적 SLO 미수립 시 잠정 SLO p99 = 1s 사용. (SLO 경계값은
UNSUPPORTED_IMPL_DECISION— 잠정 SLO 역산.) publishPercentileHistogram기본 ~73 버킷/dim →minimumExpectedValue/maximumExpectedValue로 범위 제한해 cardinality 관리.
P1/P2/P3 정량 기준 (잠정 SLO 기반)
| severity | error rate | latency p99 | dependency lag | scope |
|---|---|---|---|---|
| P1 | >5% 5분 지속 또는 >10% 1분 | p99 > 5s 5분 | required dep unavailable >2분 | release-blocking incident |
| P2 | >1% 10분 지속 | p99 > 1s 10분 | optional dep degraded > 5분 | on-call 즉시 대응 |
| P3 | >0.1% 1시간 지속 | p99 > 500ms 30분 | spike alert (10x baseline) | business hours 대응 |
burn-rate 기반 alert는 추후 도입(현재는 단순 threshold). 위 수치는 D7 = UNSUPPORTED_DECISION (잠정 SLO 운영 가정 — 외부 공식 표준 없음).
Retry / CircuitBreaker / DB Pool Minimum Metric Set
- retry/CB minimum:
resilience4j.retry.calls{outcome},resilience4j.circuitbreaker.state,resilience4j.circuitbreaker.calls{outcome}. (owner: raw/branch-notes/feature-outbound-http-client-baseline, D4 consume.) - DB pool exhaustion 감지 metric:
hikaricp.connections.acquire{outcome="timeout"}p99 > 100ms. (owner:feature-persistence-failure-baseline.)
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존.
- 실패·엣지 경로:
- high-cardinality leak:
uri_template미정규화 시 404/raw path 가 series 폭증 (MM-HCARD-C2,PROM-CARD-C1). 기대 동작:MeterFilterdeny + uri 정규화 → bounded(≤200). 미정규화 metric 은metrics-cardinality테스트 실패. - cross-instance percentile 오집계: client-side
publishPercentiles를 인스턴스 간avg()(PROM-HIST-C2// BAD!) → 통계적 무의미값. 기대 동작: 집계는slo_driven히스토그램 버킷 +histogram_quantile()만. - registry drift:
metrics.yaml행 ↔ 실제 노출 metric(tag 추가/이름 변경) 불일치. 기대 동작: registry↔/actuator/prometheusdiff 스모크 실패. - threshold 누락/임의수치: SLO/default table 근거 없는 magic number → §테스트 계약 위반.
- error_code tag 상한 초과:
error-codes.yamlrow > 100 이면 cardinality cap 초과 → §Cardinality Bounds 표 + registry 동시 업데이트 필요. - tenant_id 1001번째: bucket 자동 fold(§Cardinality Bounds). ULID 원본 직접 label 금지.
- high-cardinality leak:
- 다른 계약 의존:
- raw/branch-notes/feature-outbound-http-client-baseline D4 —
resilience4j.*metric(명/outcome enum) 정의 소유. 그 계약이 바뀌면 본 branch 의 alert severity 행 영향. - raw/branch-notes/feature-persistence-failure-baseline —
hikaricp.*metric + pool exhaustion threshold 소유. 본 branch 는 DB pool alert 기준만 consume. - raw/branch-notes/feature-log-management-contract — log field ↔ metric tag 이름 일치 정책. registry 각 행
log_field_mapping이 상관 SSOT.log.appender.dropped.totalowner. - raw/branch-notes/feature-contract-registry-governance —
metrics.yaml스키마 owner. 행 스키마(필수 키) 변경 시 본 branch 행 갱신. - raw/branch-notes/feature-distributed-tracing-contract — high-cardinality(trace_id) 는 metric label 대신 exemplar/trace 로 위임(
MM-HCARD-C5). exemplar 도입은 tracing 인프라 의존 → 추후. ca-tmpl/docs/registries/error-codes.yaml—error_codetag cardinality cap(100) 의 SSOT.
- raw/branch-notes/feature-outbound-http-client-baseline D4 —
테스트 계약
- HTTP request metric에 method/status/uri template tag가 없으면 실패.
- dependency metric에 dependency.name/type이 없으면 실패.
- DB pool exhaustion을 감지할 metric 기준이 없으면 실패.
- alert severity가 없는 dependency outage 기준은 실패.
- high-cardinality tag가 metric에 들어가면 실패.
- alert threshold의 근거가 SLO/default table에 없으면 실패.
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
Spring Boot 3 default meter (http.server.requests) 가 Micrometer 자동 변환으로 Prometheus 에서 http_server_requests_seconds_* 로 노출 |
MM-NAME-C3 의 timer 예시는 http_server_requests_duration_seconds 표기 — Spring Boot 3 + Micrometer 버전에 따라 suffix 차이 존재 |
actuator /actuator/prometheus 응답에서 실제 metric name 확인 |
planned |
ca-tmpl 의 uri_template tag (Micrometer naming) 과 OTel semconv http.route 의 정합성 |
OTEL-MET-C7 미명시 (본 페이지 범위 밖) — semconv 별도 페이지 확인 필요 |
OTel Java instrumentation + Spring MVC 통합 시 http.route attribute value 확인 |
needs-confirmation |
| P1 threshold "(>5% 5분 또는 >10% 1분)" 가 SLO 99.9% 기준 burn rate 으로 환산 시 약 50x 정당성 | SRE-BURN-C2 의 2%/1h + 5%/6h reasonable 시작값만 직접 지지 — 50x 환산은 별도 계산 |
SLO 99.9% 가정 + 실제 traffic 으로 burn rate 산출 + multi-window 표 비교 | planned |
HikariCP DB pool exhaustion 감지 metric (hikaricp.connections.acquire{outcome="timeout"} p99 > 100ms) 의 정확한 metric name |
HikariCP / Spring Boot 3 default meter 명세 본 branch 인용 자료에 없음 | actuator /actuator/prometheus 에서 HikariCP metric name 확인 |
planned |
Resilience4j default metric 이름 (resilience4j.retry.calls{outcome}, resilience4j.circuitbreaker.state) 의 verbatim |
본 branch 인용 자료에 Resilience4j docs 없음 | Resilience4j Micrometer integration docs 별도 raw 등록 + actuator 확인 | needs-confirmation |
client-side publishPercentiles + publishPercentileHistogram 동시 선언 시 Micrometer 가 둘 다 노출하는지 (혼합 모드 동작) |
MM-HIST-C4 는 "redundant" 라고만 명시 — 실제 노출 여부 미확인 |
actuator /actuator/prometheus 에서 _bucket + quantile gauge 동시 존재 확인 |
planned |
| toss 의 P1/P2/P3 정의 (결제 차단/일부 가맹점/내부 지표) 가 실제 toss 공식 정책 | TOSS-ALERT-C3 는 needs-confirmation — verbatim 미확인 |
toss 공식 SLASH 발표/페이지 재발굴 또는 ca-tmpl 정책으로만 표현 | needs-confirmation |
| metric naming 영문 dot-case 강제 가 toss 의 명시적 contract | TOSS-ALERT-C6 는 needs-confirmation — 출처 검증 실패 |
Micrometer 표준으로만 정당화하고 toss 인용은 제거 또는 격하 표현 | needs-confirmation |
관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
/coverage가 채우는 생성물 — 손으로 유지하지 않는다. 기준:rules/coverage-gate.md. governing_docs:wiki/projects/ca-tmpl/observability-log-metric-trace-runbook(§Metric documented-only). 상태:covered-here(이 브랜치 결정) /delegated(다른 owner 브랜치) /missing(아무도 안 맡음 → Blocking).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| Micrometer dot.case naming + Prometheus exporter | covered-here | — | — | D2 (governing §Metric:61) |
| Alert severity P1/P2/P3 분리 | covered-here | — | — | D7 + §P1/P2/P3 표 (governing §Metric:62) |
| Cardinality bound (userId/requestId unbounded label 금지) | covered-here | — | — | D8 + §Cardinality Bounds (governing §Metric:63) |
| SLO burn-rate vs traffic-based threshold 대안 결정 | covered-here | — | — | D3 + D5 (governing §Metric:64) |
| HTTP latency/error rate metric | covered-here | — | — | D2·D7 + §구현 가이드 §1 (http.server.requests) |
| dependency latency/error rate metric | covered-here | — | — | D2·D7 + §구현 가이드 §1 (dependency.client.requests) |
| JVM/process metric | covered-here | — | — | §구현 가이드 §1 (jvm.*, process.uptime) |
| metric naming/tag 기준 | covered-here | — | — | D2 + D8 + §Cardinality Bounds |
| DB pool metric | delegated | raw/branch-notes/feature-persistence-failure-baseline | OK | §구현 가이드 §1 consumed 표 (hikaricp.*) |
| retry/circuit breaker metric | delegated | raw/branch-notes/feature-outbound-http-client-baseline | OK | D4 + §구현 가이드 §1 consumed 표 (resilience4j.*) |
| exemplar/trace_id → metric label 대신 tracing 위임 | delegated | raw/branch-notes/feature-distributed-tracing-contract | Should-fix | D8 위임 링크 — 단 수신 브랜치 In-scope 에 exemplar 미명시(UNLINKED_DELEGATION 경계, 후속 /branch-spec feature-distributed-tracing-contract) |
coverage-auditor 판정 (2026-06-14): Covered — Blocking 0 / Should-fix 1 (exemplar 위임 수신 브랜치 In-scope 보강) / Advisory 0.
마주친 문제
- 아직 없음.
묶음
- raw/company-tech-blogs/metric-toss-payments-alert-severity-techblog
- raw/official-docs/metric-google-ewaschuk-philosophy-on-alerting
- raw/official-docs/metric-google-sre-slo-burn-rate
- raw/official-docs/metric-google-sre-workbook-on-call
- raw/official-docs/metric-micrometer-high-cardinality-tags-detector
- raw/official-docs/metric-micrometer-histogram-percentile-concepts
- raw/official-docs/metric-micrometer-naming-convention-official
- raw/official-docs/metric-otel-metrics-data-model-spec
- raw/official-docs/metric-prometheus-histograms-vs-summaries-practices
- raw/official-docs/metric-prometheus-label-cardinality-best-practices
- raw/official-docs/outbound-resilience4j-vs-spring-retry
- raw/official-docs/persistence-hikaricp-pool-sizing-wiki
- raw/official-docs/resilience4j-micrometer-module
본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (본 feature 작업 중 발생)
-
(없음 — 2026-06-15 Task 1: shared-contract pure contract type 구현 완료. 컴파일/테스트 오류 없음.)
-
(없음 — 2026-06-15 Task 2: app-bootstrap runtime enforcement + contract test 구현 완료. 컴파일/테스트 오류 없음.)
-
[2026-06-15 Spec-reviewer fix]
MetricsDistributionMeterFilter.SLO_DRIVEN_TIMERS에서dependency.client.requests누락 (Req #10 PARTIAL).metrics.yaml기준 이 행은owner_branch: feature-metrics-alerting-contract+type: timer+histogram_buckets: slo_driven— 나머지 4개 owned timer 와 동일 D9 그룹. 원인: Task 2 초기 구현 시 spec §Histogram Buckets/Percentile "dependency call" 항목을SLO_DRIVEN_TIMERSSet 에 추가하지 않음. 수정:SLO_DRIVEN_TIMERS5개로 확장 + 단위 테스트@ValueSource5개로 확장 +MetricsAlertingContractTest에 registry↔filter drift guard 테스트(every_owned_slo_driven_timer_is_configured_by_distribution_filter) 추가 + 미사용import java.util.Collection;제거 + plan doc Task 2 item 2 수정. TDD red(2개 테스트 실패) → green(전체 suite PASS) 증명 완료. -
[2026-06-15 Code-quality polish pass] 코드 품질 리뷰어 지적 4건 수정 (
actually-implemented+locally-verified):- Important 1 (
MetricsContractConfigTestD9 test):install_applies_slo_distribution_to_http_server_requests— 기존 단언(timer().isNotNull())은MetricsDistributionMeterFilter미설치 시에도 통과.timer.takeSnapshot().histogramCounts().isNotEmpty()로 강화.percentilesHistogram(true)+serviceLevelObjectives(...)조합이 실제로 SLO 버킷을 만들어야만 통과.@DisplayName도 단언 내용에 맞게 수정. - Important 2 (
MetricsContractConfigTestno-op test):config_is_noop_without_meter_registry— 기존 단언(config.isNotNull())은@PostConstruct경로를 전혀 호출하지 않음.installFilters()가시성을public→package-private 으로 낮추고, 테스트와 같은 패키지에서assertThatCode(config::installFilters).doesNotThrowAnyException()로 교체. NPE 회귀 시 실패함을 보장.DistributedTracingContractTest.tracing_sampling_rate_gauge_is_noop_without_meter_registry선례 일치. - Minor 1 (
MetricsAlertingContractTest):Collectors.toList()2곳을Stream.toList()(Java 21 immutable)로 교체. 미사용import java.util.stream.Collectors;제거. - Minor 2 (
MetricsCardinalityMeterFilter,MetricsDistributionMeterFilter): stateless infrastructure leaf class 에final추가.MetricsContractConfig(@Configuration, CGLIB proxy) 는 손대지 않음. - Minor 4 (
AlertSeverity, shared-contract — controller 직접 수정): inlinejava.util.Locale.ROOTFQN 2곳을import java.util.Locale;+Locale.ROOT로 정리 (파일 내 import 스타일 일관성)../gradlew :shared-contract:test --rerun-taskscompileJava+test 재실행 BUILD SUCCESSFUL 로 확인. - Minor 3 (의도적 미변경):
MetricsContractConfig는final로 만들지 않음 —@Configurationfull-mode CGLIB proxy 가 필요하므로final시 context load 실패. 리뷰어도 동일 지적. ./gradlew :app-bootstrap:testBUILD SUCCESSFUL (23 tasks).
- Important 1 (
-
controller 최종 검증 (2026-06-15): 리뷰 체인(ca-architect-sentinel
ready/ ca-spec-reviewerready(Req #10 fix 후) / ca-quality-reviewer 지적 4건 수정) 완료 후 컨트롤러가 전체 검증 실행 —./gradlew :shared-contract:test :app-bootstrap:test verifyCleanArchitectureDependencies= 594 tests / 594 pass / 0 fail / 0 skip, arch dependency check +CleanArchitectureTest(48) PASS.locally-verified등급은 컨트롤러 검증 근거를 가짐 (자기 보고 아님). 커밋은 사용자가 직접 수행 — 작업 트리에만 변경 잔류.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- Micrometer dot.case naming 과 Prometheus underscore naming 의 차이, 그리고 변환 시 exporter 가 자동 부착하는 suffix(
_seconds_bucket등)를 개발자가 직접 처리해야 하는지 여부. - metric label cardinality 폭발이 발생하는 원인과
user_id/request_id가 metric label 로 금지되는 이유 (tracing exemplar 와 차이). OptionalIntvsOptional<Integer>선택 기준 (Java 원시 타입 boxing 비용 vs API 일관성).- Micrometer
@Bean MeterFiltervsregistry.config().meterFilter()직접 설치 차이 — Spring Boot ActuatorMeterRegistryCustomizer없는 환경에서@Bean MeterFilter가 왜 무효인가. publishPercentileHistogram(aggregable cross-instance) vspublishPercentiles(client-side non-aggregable) 차이 — Prometheus 다중 인스턴스 p99 집계 시 어떤 방식이 올바른가.DistributionStatisticConfig.build().merge(config)에서.merge()순서가 왜 중요한가 (caller config 우선 vs filter 우선).
부가 tooling 변경 (2026-06-15): 리팩토링 어드바이저 구조
본 절은 metrics/alerting 계약 자체가 아니라, 이 브랜치 작업 중 추가한 하네스 tooling(리팩토링 비평 에이전트 + 표준 SSOT)을 기록한다. metrics 코드는 이 구조의 첫 드라이런 대상이었다. 외부 표준 근거가 raw 에 미등록이므로 아래 설계 결정은
needs-confirmation로 표기한다 (Decision Evidence Map 의 D1~D10 과 별개 — 본 절은 도구 결정).
변경 파일 (전부 markdown — Java/Gradle 동작 무변경)
- 신규
.agents/plugins/ca-superpowers/rules/refactoring-standards.md— 리팩토링 판단 SSOT (D1 JavaDoc 계약표면한정 / D2 네이밍 / D3 구조 / D4 계약타입 형태). 등급:actually-implemented - 신규
.claude/agents/ca-refactor-advisor.md— 기존 커밋 코드 선제 스윕 →docs/superpowers/plans/에 행위보존 plan 작성. read-only onsrc/**, verdict 미게이트. 등급:actually-implemented - 수정
.claude/skills/ca-superpowers-workflow/SKILL.md— Subagent Lanes + Dispatch Tree 에 리팩토링 스윕 분기. 등급:actually-implemented - 수정
.claude/agents/ca-quality-reviewer.md— mandatory reads + G1 표에 표준 문서 연결(SSOT 공유). 등급:actually-implemented - 산출물
docs/superpowers/plans/2026-06-15-metrics-refactor-plan.md— 드라이런이 생성한 metrics 리팩토링 plan (P1=1/P2=1/P3=1, 전부 D1). 등급: plan 은actually-implemented, 리팩토링 실행 자체는planned
도구 설계 결정 (사용자 대화형 선택 — 별도 Decision-ID 체계)
- RD1: 표준 문서 먼저 명문화 후 에이전트가 참조 (vs 에이전트 내부 판단 / 기존 reviewer 확장). 이유: "naming 미명문화"가 근본 원인 → 객관 기준 SSOT 필요. 근거: 사용자 선택 + Google Java Style Guide(객관 표준) —
needs-confirmation(raw 미등록) - RD2: JavaDoc 정책 = 계약 표면에만 (vs 공개 API 전부 / 전면 최소화). 이유: 스켈레톤에서 문서 가치가 가장 높은 곳은 템플릿 사용자가 의존하는 계약 표면. 근거: 사용자 선택 —
needs-confirmation - RD3: 출력 = 실행 가능 plan 파일 → ca-implementer 위임 (vs findings 리포트 / 자동 plan화). 이유: 기존 plan→implementer 머신 재사용. 근거: 사용자 선택 + 기존 리뷰 체인 패턴
- RD4: verdict 게이트 미편입 (독립 어드바이저). 근거:
ca_verdict_gate.py:166-167이 미등록 agent_type 을emit_allow()로 통과 (코드 확인) — 훅 무수정 - RD5: 어드바이저가 plan 파일 직접 Write (
docs/superpowers/plans/한정,src/**금지). 근거: 사용자 선택 (왕복 최소)
검증
- 구조 검증: 4파일 grep 통과 (D1~D4 4헤더 / agent Write 포함·verdict 0 / SKILL 2곳 / reviewer 2곳).
- 통합 드라이런: ca-refactor-advisor 계약을 metrics 스코프에 실행 → 유효 plan 생성,
src/**무수정 확인, verdict 블록 없음, 모든 file:linesed/grep검증. 라이브subagent_type디스패치는 세션 리로드 후 가능 — 정의가 세션 시작 시점 레지스트리에 없어 fallback(general-purpose 에 정의 파일 준수)으로 계약 검증. - Java/Gradle: 동작 무변경이라 테스트 미실행 (해당 없음).
Gotchas (재사용 가능한 도구 마찰)
- 새
.claude/agents/*.md는 세션 시작 시 로드된 레지스트리에만 등록 → 생성 직후 같은 세션에서subagent_type으로 디스패치 불가. 리로드 필요. - ca-tmpl 세션의
wiki_claim_gatePreToolUse 훅이echo문자열 안의>=/>를 shell 리다이렉트로 오인해 무해한grepBash 를 차단 →>문자를 피해 재실행으로 우회.
Cluster (이 부가 작업 한정)
- Errors: 위 Gotchas 2건 (별도
raw/errors/노트는 선택 — 필요 시 canonical 추출). - Interview prep: "새 subagent 를 세션 중 추가했을 때 즉시 디스패치되지 않는 이유(레지스트리 로드 타이밍)" / "리팩토링 비평을 객관 표준 SSOT 로 분리하는 설계 이점".
- Blog topics: "Clean Architecture 스켈레톤에서 리팩토링 어드바이저 + implementer 위임 구조 설계" — branch note 외 별도 글감 가능, 현재 미작성.
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- (없음 — Phase E 설계 단계. C2 구현 진입 시 daily note 연결)
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):