77 KiB
title: branch / feature-operational-error-observability-foundation
source_type: branch-note
status: verified
last_reviewed: 2026-06-04
branch: feature-operational-error-observability-foundation
parent_branch:
related_projects: [ca-skeleton]
governing_docs: [wiki/projects/ca-tmpl/api-error-envelope-design, wiki/projects/ca-tmpl/observability-log-metric-trace-runbook]
tags: [branch, ca-skeleton, error-handling, observability]
created: 2026-05-21
target_merge:
status_label: in-progress
last_pass: 2026-06-04 (/branch-spec gate — note already mature(D1D21+Phase C2). governing_docs 추가 + ## Coverage 섹션 생성; ground-truth 재검증 무드리프트(Category/ResponseMeta/HeaderSanitizer/MdcKeys/RetryAfterAdvisor/ResponseMetaFactory 실재); 게이트: depth Ready(Blocking 0) + coverage Covered(Blocking 0); depth Should-fix 4건 해소(§엣지 D7 fallback 메커니즘 actually-implemented 기재 / §1 CONFLICT vs DATA_INTEGRITY 분기 경계 Q2 / §7 Q10 4xx=unset 근거 / §8 deprecated yaml Q13); governing doc 2건 최신화(api-error-envelope-design → verified, observability foundation-slice → actually-implemented). 이전: 2026-06-01 Phase C2 구현 완료 — G1G5/G7 actually-implemented+locally-verified(./gradlew check 통과), G6 seam/stub; 미커밋 working tree(사용자 단일 커밋 예정, 중간 SHA git reset 폐기); 파생노트 errors/interviews/blog-topics 캡처. 이전: reinforcement + template 정합 F1F8/D13D19; §0 Gap Map + D20 envelope 방향 + D21 Phase C2 분리)
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-001
kind: project-work-item
project: ca-skeleton-operational-contract
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-001
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
contract_packet_sha256: bd90ec8c5bf60567e7393c1c904ef74f8ecfedfe1ecac9a49a8dd5694b71ecc0
## Coverage 섹션 생성; ground-truth 재검증 무드리프트(Category/ResponseMeta/HeaderSanitizer/MdcKeys/RetryAfterAdvisor/ResponseMetaFactory 실재); 게이트: depth Ready(Blocking 0) + coverage Covered(Blocking 0); depth Should-fix 4건 해소(§엣지 D7 fallback 메커니즘 actually-implemented 기재 / §1 CONFLICT vs DATA_INTEGRITY 분기 경계 Q2 / §7 Q10 4xx=unset 근거 / §8 deprecated yaml Q13); governing doc 2건 최신화(api-error-envelope-design → verified, observability foundation-slice → actually-implemented). 이전: 2026-06-01 Phase C2 구현 완료 — G1branch: feature-operational-error-observability-foundation
Layer:
raw/branch-notes/— 운영 실패 분류와 관측성의 첫 기준 branch. 완료 후/ingest로wiki/projects/에만 추출합니다.status_label:in-progress|review|merged|abandoned
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 §3 Structured API Response · §6 Operational Error Category · §7 Retryable · §8 Structured Log / Distributed Tracing · §25 SSOT Owner Map(error envelope / category enum / ID meaning / MDC key) 영역의 결정/근거/금지 사항을 정제한다.
형제 branch (cross-cite)
- raw/branch-notes/feature-boundary-validation-mapping-contract — envelope custom 채택 공유(D5).
MAPPING_FAILED/BATCH_PARTIAL_FAILURE를 본 branch enum 의VALIDATIONcategory 로 등록하는 consumer. - raw/branch-notes/feature-resource-identifier-contract — MDC snake_case 정합(
user_id/resource_id), error code never-reuse(D17)와 ID never-reuse(D15) 대칭. - raw/branch-notes/feature-api-contract-baseline — HTTP header registry(
X-Request-Id/Retry-After/X-RateLimit-*/WWW-Authenticate) producer/cross-owner. - raw/branch-notes/feature-security-operational-baseline —
WWW-Authenticate발행(D18) + inbound 헤더 trust 의 보안 측면(D15) owner. - raw/branch-notes/feature-log-management-contract — 본 branch MDC snake_case 표준을 consume(log redaction / PII MDC key 분리).
- raw/branch-notes/feature-metrics-alerting-contract — metrics.yaml(error_code cardinality bound) owner. 본 branch enum 을 metric tag dimension 으로 consume.
- raw/branch-notes/feature-distributed-tracing-contract — W3C trace context + span 세부(D15/D16) owner. 본 branch 는 ID 의미 + error→span 기록 의도만 정의.
- raw/branch-notes/feature-rate-limit-idempotency-contract — 429/
Retry-After운영 세부(D13) owner.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: error·observability 6필드 contract와 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 |
|---|
목표
모든 adapter와 boundary가 같은 실패 언어를 사용하도록 운영 실패 분류 체계를 먼저 고정합니다. 이 branch가 없으면 DB, HTTP, Security, Kafka, Redis, Slack/Email 실패가 각자 다른 방식으로 응답/로그/재시도 정책을 갖게 됩니다.
- 이슈:
- PR:
범위
포함 범위
- structured API response envelope 기준.
- operational error category/code 기준.
- retryable/non-retryable 기준 + 재시도 시점의
Retry-Aftersurfacing (D13). - diagnostic context 기준 + inbound 헤더 sanitization (D14) + trace context trust boundary (D15).
- requestId/traceId/correlationId/MDC key 기준 + snake↔camel↔kebab 표현 매핑 (D19).
- operational error 의 trace span 기록 의도 (D16, server-side).
- error code lifecycle (stability / never-reuse) 기준 (D17).
- Spring 기본 예외 처리 테스트 기준.
제외 범위
의도적으로 제외. 형제 branch 의 owner 결정으로 위임.
- DB/JPA 세부 예외 분류.
- outbound HTTP client 구현.
- JWT 세부 인증 실패 분류 +
WWW-Authenticate헤더 발행 (security-operational-baseline owner — D18 은 cross-cite 만). - Kafka/Redis/Slack/Email adapter 구현.
- error-codes.yaml / mdc-keys.yaml / metrics.yaml 의 실제 row 편집 (registry-governance + ca-tmpl repo).
- 429/
Retry-After운영 세부 + W3C trace context 의 propagation/span 세부 (rate-limit-idempotency / distributed-tracing owner). - multi-tenancy 모델 (tenant context-policy branch — 본 branch 의
tenant_idMDC key 는 "활성 시" 조건부).
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/company-tech-blogs/stripe-error-format | endpoint dimension 명시 사례 (D3) |
| raw/company-tech-blogs/toss-payments-error-format | 한국 컨벤션 reference + UPPER_SNAKE_CASE 사례 (D3/D4) |
| raw/official-docs/problem-detail-rfc-7807 | IETF 표준이지만 실패 전용, success/error 비대칭 — 거부 근거 (D1) |
| raw/official-docs/spring-problem-detail | Spring 6 기본 지원이지만 ca-tmpl envelope과 충돌 (D1) |
| raw/official-docs/google-api-error-format | 가장 표현력 풍부, retryable detail 1급 + ErrorInfo.reason machine-readable id (D5/D10/D12/D17) |
| raw/official-docs/json-api-errors-spec | field error pointer / errors array (D12) |
| raw/official-docs/graphql-errors-spec | partial success 1급 |
| raw/company-tech-blogs/github-api-error-format | validation code 어휘 사례 (D9) |
| raw/official-docs/rfc9110-http-semantics | Retry-After semantics(§10.2.3) + 413 temporary(§15.5.14) — retryable surfacing (D13). 401+WWW-Authenticate(§11.6.1) cross-cite (D18) |
| raw/official-docs/tracing-w3c-trace-context-spec | traceparent 4-field 형식(검증 가능) + propagation/PII 의무 — inbound trace trust boundary (D15) |
| raw/official-docs/owasp-logging-cheat-sheet | inbound header MDC 값 sanitization — log injection / CRLF / log forgery (CWE-117) 방어 (D14) |
| raw/official-docs/otel-exceptions-semantic-conventions | span exception 이벤트(exception.type/message/stacktrace) + span status ERROR — 서버 측 telemetry 전용 (D16) |
| raw/official-docs/stripe-resource-id-convention | opaque string / error message 변경 = backward-compatible → error code 가 안정 계약 표면 (D17) |
외부 근거 / 대안 조사 (2026-05-22 — Topic 4)
본 branch의 custom envelope 결정 ({success, data, error.{code, category, message, retryable, details}, meta}, ProblemDetail forbidden)에 대한 외부 source 조사. 비교 분석은 (예정) wiki/concepts/api-error-envelope-design.md 참조.
- 채택 결정 (custom envelope, success/error 대칭, retryable 1급):
- (어떤 표준도 1:1 매칭 없음 — Stripe/Toss와 가장 유사하나 success flag는 ca-tmpl 고유)
- raw/company-tech-blogs/stripe-error-format — endpoint dimension 명시 사례
- raw/company-tech-blogs/toss-payments-error-format — 한국 컨벤션 reference
- 명시적으로 거부한 표준:
- raw/official-docs/problem-detail-rfc-7807 — IETF 표준이지만 실패 전용, success/error 비대칭
- raw/official-docs/spring-problem-detail — Spring 6 기본 지원이지만 ca-tmpl envelope과 충돌
- 검토한 대안:
- 대안 1: RFC 7807 ProblemDetail — 위 2개
- 대안 2: Google rpc.Status (gRPC) — raw/official-docs/google-api-error-format (가장 표현력 풍부, retryable detail 1급)
- 대안 3: JSON:API errors — raw/official-docs/json-api-errors-spec
- 대안 4: GraphQL errors — raw/official-docs/graphql-errors-spec (partial success 1급)
- 대안 5: GitHub custom envelope — raw/company-tech-blogs/github-api-error-format
- 비교 핵심: ca-tmpl의
successflag +retryable1급은 어떤 표준에도 없음. Google rpc.Status만 retryable을 detail로 가짐. ProblemDetail은 실패 전용 평면이라 ca-tmpl의 운영 요구와 구조적 충돌. → ca-tmpl이 ProblemDetail을 거부한 trade-off: 표준 lock-in 회피 + success/error 대칭 + 운영 메타 1급화.
TODO
TODO drained — 결정은 §구현 가이드 (error.category Enum / MDC Key Standard / ID 명명 매핑 / error.details / Retry-After / sanitization+trust / span 기록 / code lifecycle) 참조. 2026-06-01 reinforcement pass 로 D13~D19 추가.
진행 중 메모
- 이 branch는 다른 모든 branch의 선행 계약입니다.
- 2026-06-01 reinforcement pass: 다관점 브레인스토밍으로 8개 사각(F1
F8) 식별 → D13D19 추가 + template 구조 정합 + 공식문서 2건(OWASP Logging, OTel Exceptions) raw 캡처. 4개 선행 계약(skeleton-package-blueprint / architecture-enforcement / boundary-validation-mapping / resource-identifier) 의 결정과 충돌 없음 — F1 은 부모 §6 stale 재정합(안정화), F2~F8 은 additive 또는 sibling/owner cross-cite. 설계:docs/superpowers/specs/2026-06-01-operational-error-observability-foundation-reinforcement-design.md. - F1 (부모 §6/§8 정합): 부모 project-note §6 의 stale 13-category 목록을 본 branch 의 canonical 10-enum 으로 정합.
- 2026-06-01 코드 검증 정정: 이전 §0 Realization Map 이 "boundary 가 이미 구현" 이라 과장(overclaim)했으나, ca-tmpl 코드 실측 결과 boundary 는 단순 shape(flat traceId / category 없는 error / camelCase MDC)만 구현 — foundation 계약의 full target(meta 객체/category 1급/snake_case/correlation_id/sanitization/Retry-After/span ERROR)은 미구현(G1~G7). §0 을 "Realization Gap Map"(진입점 + GAP 표)으로 교정. 결정: 코드는 Phase C2 로 보류(D21), envelope 방향은 foundation meta+category(D20). 코드 미수정.
- 2026-06-01 registry 검증 (re-tag 권고 후속):
ca-tmpl/docs/registries/error-codes.yaml(49 codes) 를 read-only 검증한 결과 이미 10-enum 으로 완전 정리됨 — 옛 category(AUTHENTICATION/PERSISTENCE/CACHE 등) 0건, category 분포가 부모 §21 L811 과 정확히 일치. per-code 재태그는 no-op(이미 완료, "Phase A 4차 audit Conflict 13 해소"). 따라서 stale 했던 유일 artifact 는 부모 §6 본문이었고(이미 정합), yaml 은 원래부터 정확. 단 검증 중 이상치 1건(AUTH_KID_UNKNOWNretryable=false + retry_after_seconds=5) 발견 → 사용자 결정으로retryable: true적용(2026-06-01, JWKS 키 회전 가정, 가역 — 키 고정 시 false 복귀). Claims To Verify 에locally-verified로 등재.
결정 사항
- 2026-05-21:
ProblemDetail은 사용하지 않고 자체 envelope 응답을 사용. (D1) - 2026-05-21: domain/business-specific exception보다 operational failure classification을 우선. (D2)
- 2026-05-22: response envelope field는
success,data,error,meta를 기본값으로 둠. (D3) - 2026-05-22: error code는
UPPER_SNAKE_CASE, category는 coarse-grained operational category로 둠. (D4) - 2026-05-22: client-safe message와 internal diagnostic context는 같은 객체에 섞지 않음. (D5)
- 2026-05-22: 이 branch가 error envelope schema,
error.categoryenum,requestId/traceId/correlationId의미, MDC/log key 표준의 SSOT owner. (D6) - 2026-05-22: tracing disabled 상태에서도
meta.traceId는 누락하지 않음. 실제 trace가 없으면 generated opaque id를 사용하고trace.sampled=false를 diagnostic context/log에만 남김. (D7) - 2026-05-22:
requestId는 inbound HTTP request 단위 식별자,traceId는 distributed trace 상관관계 식별자,correlationId는 business-neutral workflow 식별자로 final 정의. (D8) - 2026-05-22: 본 branch는
error.categoryenum과 MDC Key 표준의 SSOT. **실 error code list (AUTH_TOKEN_EXPIRED, DB_UNIQUE_VIOLATION 등)는 별도ca-tmpl/docs/registries/error-codes.yaml에 통합 SSOT로 작성 (Phase B). 본 branch는 그 yaml의 schema/category 매핑만 정의. (D9) - 2026-05-22:
error.categoryenum 10개 final. (D10) - 2026-05-22: MDC Key Standard = snake_case 강제. (D11)
- 2026-05-22:
error.detailsJSON shape ={field, rejectedValue, code, message}. (D12) - 2026-06-01: (D13) retryable 응답은 재시도 시점을
Retry-After헤더로 surface. 503/TRANSIENT_DEPENDENCY 는Retry-AfterMUST(RFC 9110 §10.2.3), 429/RATE_LIMIT 은Retry-After(+X-RateLimit-*권고) — 429 세부는 rate-limit-idempotency owner. / 이유: envelopeerror.retryable: true만으로는 client 가 언제 재시도할지 모름. / 검토한 대안: (a) envelope 에retryAfterSeconds필드 추가 — HTTP 표준 헤더 중복, (b) 헤더만 — 채택. / 근거: raw/official-docs/rfc9110-http-semantics. - 2026-06-01: (D14) inbound header (
X-Request-Id,X-Correlation-Id) 값을 MDC/로그에 반영할 때 CR/LF/구분자 sanitization 의무 (log injection / log forgery 방어). / 이유: client 제공 값이 그대로 로그에 들어가면 CWE-117 log injection. / 검토한 대안: (a) 구조화 JSON 로깅만 신뢰 — 필드 smuggling/길이 폭주 잔존, (b) sanitization + 구조화 로깅 병행 — 채택. / 근거: raw/official-docs/owasp-logging-cheat-sheet. - 2026-06-01: (D15) client 제공
traceparent/X-Request-Id/X-Correlation-Id의 trust boundary — edge 에서 format 검증 + length cap, 무효 시 재생성(traceparent 는 새 trace 시작). / 이유: 외부 입력을 무검증으로 trace/MDC 에 채택하면 위조·과대 헤더 risk. / 검토한 대안: (a) 항상 재생성(client 값 무시) — cross-service correlation 손실, (b) 항상 신뢰 — 위조 risk, (c) 검증 후 수용/무효 시 재생성 — 채택. trust 의 보안 세부는 security-operational-baseline, propagation 세부는 distributed-tracing owner. / 근거: raw/official-docs/tracing-w3c-trace-context-spec. - 2026-06-01: (D16) operational
INTERNAL(5xx) 오류는 server 측 span 에exception이벤트(exception.type/message/stacktrace) 기록 + span status ERROR 설정. client HTTP 응답에는 stack trace 미포함(D5/판정 기준 Forbidden). / 이유: 관측성 = log + trace + metric. ID 전파만으로는 error 가 trace 에 안 남음. / 검토한 대안: (a) log 에만 stack — trace 상관 단절, (b) span event + status ERROR — 채택. span 세부는 distributed-tracing owner. / 근거: raw/official-docs/otel-exceptions-semantic-conventions. - 2026-06-01: (D17)
error.code는 안정적 공개 API 계약 — append-only, rename/재사용 금지(never-reuse), 폐기는 compatibility 절차(Deprecation/Sunset). / 이유: code 가 client 분기/알림/runbook 에 박힌 후 rename/재사용은 breaking + audit 혼선. resource-identifier D15(ID never-reuse)와 대칭. / 검토한 대안: (a) code 자유 변경 — client 깨짐, (b) append-only + deprecation 절차 — 채택. deprecation 절차 owner = api-compatibility. / 근거: raw/official-docs/stripe-resource-id-convention, raw/official-docs/google-api-error-format. - 2026-06-01: (D18)
AUTH(401) 응답은WWW-Authenticate헤더 동반(RFC 9110 §11.6.1 MUST). / 이유: bare 401 은 HTTP 표준 위반. / 검토한 대안: 없음(표준 의무). 헤더 발행 정책 owner = security-operational-baseline — 본 branch 는 envelope/category 측 cross-cite 만. / 근거: raw/branch-notes/feature-security-operational-baseline (§25 owner) + RFC 9110 §11.6.1. - 2026-06-01: (D19) 동일 식별자(request/trace/correlation/tenant)의 표현 계층별 명명 매핑 명시 — MDC =
snake_case(request_id), envelope meta =camelCase(meta.requestId), HTTP header =kebab-case(X-Request-Id) / W3C lowercase(traceparent). / 이유: branch-note 단독 독해 시 "MDC snake_case 강제" 와 "envelopemeta.requestId(camel)" 가 모순처럼 보임 — 의도적 매핑임을 명문화. / 검토한 대안: 단일 case 통일 — HTTP/W3C/JSON 관례와 충돌. envelope camelCase owner = schema-serialization. / 근거: raw/project-notes/ca-skeleton-operational-contract §21 + §25. - 2026-06-01: (D20) envelope shape 충돌 해소 방향 = foundation
meta.{requestId,traceId,correlationId}객체 +error.category채택 (G1/G2/G7). 현재 코드의 boundary flattraceIdshape 는 Phase C2 에서 마이그레이션. / 이유: 부모 §3 + §21(L818-833)이 meta.* envelope field 를 확정하고 §25 가 envelope schema 소유를 foundation 에 부여 — flat shape 는 미완 subset. boundary 결정 D5(ProblemDetail 거부)/D6(success-error 대칭)은 불변 (richer shape 는 additive, 결정 reversal 아님). / 검토한 대안: 계약을 flat shape 로 하향 수정 — 관측성 계약(meta.{}) 포기라 기각. / 근거: 부모 §3/§21/§25 + 2026-06-01 코드 검증(§0 GAP Map). - 2026-06-01: (D21) Phase C2 코드 구현(G1~G7 해소)은 본 reinforcement 패스 범위 밖 — 별도
writing-plans로 분리. / 이유: Envelope/ApiError shape 변경 + MDC camel→snake 는 boundary 의 realized 코드/테스트(VirtualThreadMdcE2ETest/GlobalExceptionHandlerTest/WorkLogController)를 깨므로 조율된 마이그레이션 plan + 검증 체크포인트 필요 — ad-hoc 금지. cross-owned 항목(Retry-After 헤더/WWW-Authenticate/span 조립)은 owner branch 가 구현, foundation 은 hook + cross-cite stub. / 검토한 대안: 지금 전면 구현 — blast radius 무계획 처리 위험으로 기각. / 근거: 사용자 결정 2026-06-01.
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다.
Decision ID는 이 branch-note 안에서 안정적으로 유지한다. Company-tech-blog evidence 는company-case-study로만 표기 — official best practice 아님.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | ProblemDetail 사용 거부, 자체 envelope 응답 사용 |
raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C1, raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C2, raw/official-docs/spring-problem-detail.md#SPRING-PD-C1, raw/official-docs/spring-problem-detail.md#SPRING-PD-C3 |
official-standard (RFC 7807 의 application/problem+json + type MUST 식별자 — ca-tmpl 의 success/error 대칭 + retryable 1급 필드와 구조적 충돌) + official-vendor-doc (Spring ProblemDetail 의 RFC 9457 representation) |
RFC 7807/9457 거부의 trade-off: 표준 lock-in 회피 vs 표준 호환성 손실. RFC 7807 extensions 메커니즘 (RFC7807-C3) 으로 retryable/category 표현 가능했음 |
| D2 | domain/business-specific exception 보다 operational failure classification 우선 | UNSUPPORTED_DECISION (DDD / clean architecture 일반 원칙 — 외부 official-standard / official-vendor-doc 직접 근거 없음) | N/A | 내부 정책으로만 표현. wiki 추출 시 wiki/concepts/clean-architecture-* 류 별도 근거 필요 |
| D3 | response envelope field = success, data, error, meta default |
UNSUPPORTED_DECISION (어떤 표준도 success flag 를 직접 정의하지 않음. raw/company-tech-blogs/stripe-error-format.md / raw/company-tech-blogs/toss-payments-error-format.md 는 company-case-study — 공식 best practice 아님) |
company-case-study (Stripe STRIPE-ERR-C5 4-type enum + Toss TOSS-ERR-C1 {code,message} 2-field — ca-tmpl 의 envelope 는 양쪽 모두와 다름) |
success flag 의 raw source 0건. ca-tmpl 자체 design — 면접/외부 공개 시 "내부 design choice" 로만 표현 |
| D4 | error code = UPPER_SNAKE_CASE, category = coarse-grained operational |
raw/company-tech-blogs/toss-payments-error-format.md#TOSS-ERR-C3, raw/company-tech-blogs/toss-payments-error-format.md#TOSS-ERR-C4, raw/company-tech-blogs/toss-payments-error-format.md#TOSS-ERR-C5 (UPPER_SNAKE_CASE 사례 — UNAUTHORIZED_KEY, INVALID_REQUEST, ALREADY_PROCESSED_PAYMENT 등) |
company-case-study (Toss 의 코드 형식 사례 — 공식 표준 아님) |
Toss case 는 vendor convention. GitHub GH-ERR-C4 는 lowercase (missing, invalid) — 업계 통일 컨벤션 없음. UPPER_SNAKE_CASE 결정의 spec 근거 부재 |
| D5 | client-safe message 와 internal diagnostic context 분리 (같은 객체에 섞지 않음) | raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C5 (detail member 는 client 가 정정하는 데 도움 — debugging 정보 제공이 아닌) + raw/official-docs/google-api-error-format.md#GOOG-ERR-C2 (message 는 developer-facing debug message) |
official-standard + official-vendor-doc |
RFC 7807 ought to 는 SHOULD 보다 약한 어조. Google GOOG-ERR-C2 는 developer-facing 정의 — end-user 메시지 분리 자체는 ca-tmpl 내부 정책 |
| D6 | 본 branch 가 error envelope schema, error.category enum, requestId/traceId/correlationId 의미, MDC/log key 표준의 SSOT owner |
UNSUPPORTED_DECISION (SSOT ownership 분할은 내부 운영 정책) | N/A | branch ownership 분할은 외부 표준 인용 대상 아님. 부모 §25 SSOT Owner Map 과 정합 |
| D7 | tracing disabled 상태에서도 meta.traceId 누락 금지 — generated opaque id 사용 + trace.sampled=false diagnostic |
UNSUPPORTED_DECISION (인용된 official-docs 에 "tracing disabled 시 opaque id 생성" 정책 직접 근거 없음) | N/A | OTel SDK noop tracer 동작 별도 verbatim 필요 |
| D8 | requestId/traceId/correlationId 의 정확한 의미 final 정의 (request 단위 / distributed trace 상관 / business-neutral workflow) | raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C2 (traceId 의 W3C 정의만 부분 지지) + UNSUPPORTED_DECISION (requestId / correlationId 의미는 ca-tmpl 내부 컨벤션 — 외부 표준 없음) |
official-standard (traceId only) |
requestId / correlationId 는 vendor / 컨벤션 별. ca-tmpl 내부 정의로만 표현 |
| D9 | 실 error code 카탈로그는 ca-tmpl/docs/registries/error-codes.yaml 통합 SSOT (Phase B) — 본 branch 는 schema/category 매핑만 |
raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C4 (GitHub 6개 validation error code 어휘 — 외부 카탈로그 사례) |
company-case-study |
GitHub 6-code 어휘는 vendor convention. ca-tmpl 의 yaml registry 패턴 자체는 외부 표준 인용 없음 |
| D10 | error.category enum 10개 (VALIDATION/AUTH/AUTHZ/NOT_FOUND/CONFLICT/RATE_LIMIT/TRANSIENT_DEPENDENCY/PERMANENT_DEPENDENCY/DATA_INTEGRITY/INTERNAL) |
raw/official-docs/google-api-error-format.md#GOOG-ERR-C1 (Google google.rpc.Code enum 사례 — NOT_FOUND=5 등 코드 매핑) |
official-vendor-doc (Google AIP-193) |
Google Code enum 은 16개 (OK, CANCELLED, INVALID_ARGUMENT 등) — ca-tmpl 10개와 1:1 아님. ca-tmpl enum 은 자체 design. 부모 §6 의 stale 13-목록은 본 enum 으로 정합 필요 (F1) — §21 L814 매핑 기준 |
| D11 | MDC Key Standard = snake_case 강제 (request_id, trace_id, span_id, correlation_id, tenant_id, user_principal) |
UNSUPPORTED_DECISION (인용된 official-docs 에 snake_case MDC key 강제 표준 없음 — Logback / SLF4J 는 컨벤션 미강제) | N/A | ECS 는 dot notation (trace.id), Micrometer 는 dot.case — ca-tmpl 의 snake_case 는 자체 design choice. envelope/header 표현은 D19 매핑 |
| D12 | error.details JSON shape = {field, rejectedValue, code, message} (validation field error) |
raw/official-docs/json-api-errors-spec.md#JSONAPI-ERR-C3 (source.pointer = JSON Pointer for field location), raw/official-docs/json-api-errors-spec.md#JSONAPI-ERR-C2, raw/official-docs/google-api-error-format.md#GOOG-ERR-C5 (BadRequest, PreconditionFailure typed payloads) |
official-standard (JSON:API) + official-vendor-doc (Google AIP-193) |
ca-tmpl 의 field 는 dot path 또는 JSON pointer 둘 다 허용 — JSON:API source.pointer 는 RFC 6901 JSON Pointer 만. spec 일치 아님 |
| D13 | retryable 응답은 Retry-After 헤더로 재시도 시점 surface (503/TRANSIENT_DEPENDENCY MUST, 429/RATE_LIMIT + X-RateLimit-* 권고) |
raw/official-docs/rfc9110-http-semantics.md#RFC9110-C21 (Retry-After = follow-up request 대기 시간, 503 시 unavailable 예상 시간) + raw/official-docs/rfc9110-http-semantics.md#RFC9110-C6 (413 temporary → Retry-After SHOULD) |
official-standard (503/3xx) |
429 의 Retry-After 는 RFC 6585 §4 (본 raw 미보관 — rate-limit-idempotency owner). X-RateLimit-* 는 비표준 관례 (headers.yaml 등록). UNSUPPORTED_IMPL_DECISION: retry_after_seconds 값 자체는 error-codes.yaml project-decision |
| D14 | inbound HTTP header (X-Request-Id, X-Correlation-Id) 값을 MDC 에 반영할 때 CR / LF / 구분자 문자를 strip 하는 sanitization 을 의무화 (log injection / log forgery 방어) |
raw/official-docs/owasp-logging-cheat-sheet.md#OWASP-LOG-C1 (외부 trust zone 데이터는 untrusted), raw/official-docs/owasp-logging-cheat-sheet.md#OWASP-LOG-C3 (CR/LF/delimiter sanitization 명시), raw/official-docs/owasp-logging-cheat-sheet.md#OWASP-LOG-C5 (CWE-117 명시 위협) |
official-reference (OWASP Cheat Sheet Series — 규범적 국제표준 아님, engineering guidance) |
UNSUPPORTED_IMPL_DECISION: sanitization 의 구체적 구현(regex, allowlist charset, 최대 길이)은 OWASP 가 직접 규정하지 않음 — 길이/charset 제한은 사용자 임의 trade-off |
| D15 | client 제공 traceparent/X-Request-Id/X-Correlation-Id 의 trust boundary — format 검증 + length cap, 무효 시 재생성 (traceparent 무효 시 새 trace) |
raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C2 (traceparent 4-field 형식 — 검증 가능) + raw/official-docs/tracing-w3c-trace-context-spec.md#W3C-TC-C5 (propagation MUST + tracestate PII 금지 MUST NOT) |
official-standard (format/propagation) + UNSUPPORTED_IMPL_DECISION (trust-vs-continue / length cap / 무효→재생성 detail) |
무효 traceparent 재시작 정책 + tracestate 32-member/길이 한계는 W3C 별도 섹션 미보관. trust 의 보안 측면 = security-operational-baseline owner, propagation/span 세부 = distributed-tracing owner |
| D16 | operational INTERNAL(5xx) 오류 발생 시 server 측 span 에 exception 이벤트(exception.type/message/stacktrace) 기록 + span status ERROR. client HTTP 응답에는 stack trace 미포함 |
raw/official-docs/otel-exceptions-semantic-conventions.md#OTEL-EXC-C1 (event name MUST be exception) + #OTEL-EXC-C2 (exception.type/message/stacktrace attribute) + #OTEL-EXC-C4 (오류 시 SHOULD set span status ERROR) + #OTEL-EXC-C6 (Application developer 가 status 자유 설정 가능) |
official-vendor-doc (OTel Semantic Conventions) |
(a) exception.stacktrace 를 client 응답에서 제외해야 한다는 OTel 직접 근거 없음 — ca-tmpl 자체 보안 정책 (D5, Forbidden); (b) HTTP 5xx↔span ERROR 매핑은 HTTP semconv 별도; (c) exceptions-spans 사양 deprecated → exceptions-in-logs 전환 시 재검토. span 세부 = distributed-tracing owner |
| D17 | error.code 는 안정적 공개 API 계약 — append-only, rename/재사용 금지(never-reuse), 폐기는 compatibility 절차 |
raw/official-docs/stripe-resource-id-convention.md#STRIPE-C2 (opaque string + error message 변경 = backward-compatible 분류 → code 가 안정 계약 표면) + raw/official-docs/google-api-error-format.md#GOOG-ERR-C4 (모든 error 에 ErrorInfo 필수 — machine-readable 식별자) |
company-case-study + official-vendor-doc + UNSUPPORTED_DECISION (never-reuse 자체는 ca-tmpl 운영 정책 — resource-identifier D15 ID never-reuse 대칭) |
deprecation 절차(Deprecation/Sunset 헤더 발행 시점) = api-compatibility owner. STRIPE-C2 는 message 변경이 호환임을 말할 뿐 code 불변을 직접 증명하지 않음 (대비 관계로 인용) |
| D18 | AUTH(401) 응답은 WWW-Authenticate 헤더 동반 (RFC 9110 §11.6.1 MUST) |
raw/branch-notes/feature-security-operational-baseline (WWW-Authenticate 발행 owner — 부모 §25 L1086) + RFC 9110 §11.6.1 (raw claim 미보관 — 필요 시 rfc9110-http-semantics.md 보강) |
cross-branch-SSOT |
본 branch 는 envelope/category 측 cross-cite 만 — 헤더 발행 정책은 security-operational-baseline owner. 401 enum row 가 bare 401 을 암시하지 않도록 §구현 가이드 §9 에 명시 |
| D19 | 동일 식별자의 표현 계층별 명명 매핑 — MDC=snake_case / envelope meta=camelCase / HTTP header=kebab-case(W3C lowercase) | raw/project-notes/ca-skeleton-operational-contract §21 (request_id↔meta.requestId↔X-Request-Id 매핑 등록) + §25 (envelope camelCase owner = schema-serialization) |
project-ssot |
UNSUPPORTED_IMPL_DECISION: 매핑 방향/케이스 선택 자체는 registry + schema-serialization 분담 결정 — 외부 표준 단일 근거 없음. resource-identifier §9 의 user.id/resource.id(dot) illustration 은 snake(user_id/resource_id)로 conform 필요 |
| D20 | envelope shape 충돌 해소 = foundation meta.{requestId,traceId,correlationId} 객체 + error.category 채택 (G1/G2/G7). 현재 코드 flat traceId 는 Phase C2 마이그레이션 |
raw/project-notes/ca-skeleton-operational-contract §3 (성공/실패 응답 meta.* 명세) + §21 L818-833 (Response Envelope 요약: meta.requestId/meta.traceId/meta.correlationId 필수) + §25 (envelope schema owner = foundation) |
project-ssot |
boundary realized 코드/테스트(VirtualThreadMdcE2ETest/GlobalExceptionHandlerTest/WorkLogController) 마이그레이션 비용 — Phase C2 plan(D21)에 포함. boundary 결정 D5/D6 은 불변(additive). 2026-06-01 코드 검증(§0 (B) GAP Map) 근거 |
| D21 | Phase C2에서 G1~G5/G7 구현과 로컬 검증을 완료했다. G6 및 cross-owned Retry-After 헤더/WWW-Authenticate/span 조립은 각 owner 구현 + foundation hook/cross-cite stub으로 유지한다 | UNSUPPORTED_DECISION (구현 phasing 은 ca-tmpl 운영 결정 — 외부 근거 대상 아님. 사용자 결정 2026-06-01) | N/A | G6와 cross-owned 항목은 owner 계약이 갱신될 때 통합 검증 필요 |
구현 가이드
결정 이 "무엇" 이라면 본 §는 "어디에 어떻게" 의 사전 명세. 각 sub-section 은
Decision ID+Supporting Claim ID를 Trace (R1). 근거 없는 detail 은UNSUPPORTED_IMPL_DECISION(R2). 본 branch 범위 밖은 sibling/owner SSOT 로 이관 (R3).
0. Realization Gap Map (현재 코드 진입점 vs 계약 target)
Trace: 본 branch 는 schema/enum/ID 의미 SSOT (design). 2026-06-01 코드 검증 정정: 이전 본 §0 은 "boundary 5·6차 패스가 이미 구현" 이라 적었으나, 실제 코드 확인 결과 boundary 가 구현한 것은 더 단순한 shape 이고 본 foundation 계약의 full target 은 미구현 (Phase C2).
Envelope.java/ApiError.java/OperationalError.java의 javadoc 도 스스로를 boundary D5/D6 소유 라 명시 — foundation 의 meta 객체/category 1급/snake_case 는 아직 없음. 본 §0 은 진입점 위치 + 계약 vs 코드 GAP 의 정직한 지도다 (CLAUDE.md §6:documented-only를actually-implemented로 표기 금지).
- 증거 등급: 진입점 클래스 =
actually-implemented(존재). 계약 target shape =planned(Phase C2 미구현).
(A) 진입점 클래스 (존재 — boundary 패스가 단순 shape 로 구현):
| 계약 요소 | 진입점 클래스 (ca-tmpl) | 현재 구현 상태 |
|---|---|---|
| envelope schema | shared-contract response/Envelope<T> + adapter-web envelope/EnvelopeBodyAdvice |
actually-implemented (단, 단순 shape — (B) 참조) |
| 예외 → dispatch | adapter-web error/GlobalExceptionHandler + error/ErrorResponseFactory |
actually-implemented |
| error code | shared-contract error/OperationalError enum + error/ApiErrorCode interface |
actually-implemented (code 목록 — category 개념 부재) |
| MDC 생성/set/clear | adapter-web RequestLoggingFilter |
actually-implemented (camelCase, X-Request-Id only) |
(B) 계약 target vs 현재 코드 GAP — Phase C2 해소 완료 (2026-06-01): G1~G5/G7 = actually-implemented locally-verified(./gradlew check 통과), G6 = seam/stub(owner 위임).
| GAP | 계약/레지스트리 요구 | 해소 상태 | 증거 (ca-tmpl) | 관련 결정 |
|---|---|---|---|---|
| G1 | error.category (10-enum) 응답 노출 |
✅ ApiError 에 category 필드 + ErrorResponseFactory 가 code.category().name() 주입 |
shared-contract/response/ApiError.java, adapter-web/error/ErrorResponseFactory.java |
D10 |
| G2 | meta.{requestId,traceId,correlationId} 객체 |
✅ ResponseMeta record + Envelope/BulkEnvelope 가 flat traceId→meta 로 교체 |
shared-contract/response/ResponseMeta.java, Envelope.java, BulkEnvelope.java |
D19 / 판정기준 Required fields |
| G3 | MDC snake_case (request_id/trace_id/correlation_id) |
✅ MdcKeys(snake) + RequestLoggingFilter 전환 + logback includeMdcKeyName snake |
adapter-web/observability/MdcKeys.java, RequestLoggingFilter.java, app-bootstrap/logback-spring.xml |
D11 / D19 / mdc-keys.yaml |
| G4 | correlation_id / X-Correlation-Id 처리 |
✅ 필터가 X-Correlation-Id 수신/생성 + MDC/응답헤더 반영 |
RequestLoggingFilter.java |
D8 / mdc-keys.yaml |
| G5 | D14 inbound 헤더 CR/LF sanitization | ✅ HeaderSanitizer(CR/LF·제어문자 strip + length cap), 필터가 inbound id 에 적용 |
adapter-web/observability/HeaderSanitizer.java, RequestLoggingFilter.java |
D14 |
| G6 | D13 Retry-After (503/429) + D16 5xx span ERROR | ⏸ seam/stub 만 (RetryAfterAdvisor) — 헤더 발행/span 조립은 owner branch(rate-limit/distributed-tracing), tracing 의존성 부재 |
adapter-web/observability/RetryAfterAdvisor.java |
D13 / D16 / D21 |
| G7 | error.category 10-enum 개념 |
✅ Category enum 10값 + ApiErrorCode.category() + OperationalError 매핑 |
shared-contract/error/Category.java, ApiErrorCode.java, OperationalError.java |
D10 |
Blast radius (실현됨): GAP 해소가 boundary 의 realized shape(flat
traceId/category 없는 error/camelCase MDC)를 바꾸면서VirtualThreadMdcE2ETest/GlobalExceptionHandlerTest/EnvelopeBodyAdviceTest/BulkEnvelopeTest/WorkLogController/PortfolioErrorCode가 깨졌고 전부 마이그레이션. boundary 결정 D5/D6 은 불변(richer shape 는 additive). 방향 = D20, phasing = D21(writing-plans로 plan 작성 후 ca-implementer + 리뷰체인으로 착수, 이후 사용자 요청으로 미커밋 직접 구현 전환). 트러블슈팅: raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01.
1. error.category Enum (final)
Trace: D10 +
GOOG-ERR-C1(google.rpc.Code enum 사례). HTTP status / retryable default 는 enum 의 운영 가정.
- F1 부모 §6 정합: 부모 project-note §6 의 stale 13-category 목록(
AUTHENTICATION/AUTHORIZATION/PERSISTENCE/DEPENDENCY/SECURITY/MESSAGE/CACHE/NOTIFICATION)은 본 10-enum 으로 정합 필요. 매핑 =AUTHENTICATION→AUTH,AUTHORIZATION→AUTHZ,PERSISTENCE→{DATA_INTEGRITY, TRANSIENT_DEPENDENCY}(부모 §21 L814 "Conflict 13 해소"). per-code category 는 error-codes.yaml authoritative.- retryable 출처 (Q1, 구현 명확화): 런타임
error.retryable값은 error-codes.yaml 의 per-code row 가 authoritative — 아래 표의 retryable 은 yaml 작성 default 일 뿐 런타임 분기가 아니다. 구현자는 category 로 retryable 을 계산하지 않고 code row 값을 읽는다. "CONFLICT 의 lock-only 는 true" 도 런타임 category 분기가 아니라 별도 code (예:OPTIMISTIC_LOCK_CONFLICT= category CONFLICT, retryable=true) 로 표현한다.- UNSUPPORTED_IMPL_DECISION: HTTP status / retryable default 매핑값(예: PERMANENT_DEPENDENCY=502, RATE_LIMIT retryable=true)은 ca-tmpl 운영 가정 — 일부는 incident 회고로 재검토 (Claims To Verify 참조).
- CONFLICT(409) vs DATA_INTEGRITY(409) 런타임 분기 (Q2, UNSUPPORTED_IMPL_DECISION + 경계): 두 category 모두 409 라 어떤 persistence 예외가 어느 쪽인가는 enum 만으로 안 갈린다. 본 branch 는 분기 기준이 아니라 enum 만 소유 — 실제 JPA 예외 → code 매핑(예:
OptimisticLockingFailureException/serialization/deadlock 계열 → CONFLICT, unique/FK/null/check 위반 → DATA_INTEGRITY)은 per-code 로 error-codes.yaml + raw/branch-notes/feature-persistence-failure-baseline (persistence adapter owner) 책임. 코드 ground truth:OperationalError.javajavadoc 이 optimistic-lock 계열을CONFLICT(=DB_SERIALIZATION_FAILURE/DB_DEADLOCK와 같은 family)로 명시. 구현자는 category 로 분기를 계산하지 않고 persistence adapter 가 던지는 code 의category()를 읽는다.
| value | 의미 | HTTP status default | retryable default |
|---|---|---|---|
| VALIDATION | client request 형식·shape 오류 | 400 | false |
| AUTH | 인증 실패 | 401 | false |
| AUTHZ | 권한 부족 | 403 | false |
| NOT_FOUND | 자원 없음 | 404 | false |
| CONFLICT | invariant/optimistic lock/constraint violation | 409 | false (lock-only는 true) |
| RATE_LIMIT | rate limit/quota 초과 | 429 | true (Retry-After 이후 — D13) |
| TRANSIENT_DEPENDENCY | 외부 의존성 일시 실패 | 503 | true (Retry-After — D13) |
| PERMANENT_DEPENDENCY | 외부 의존성 영구 실패 | 502 | false |
| DATA_INTEGRITY | DB 무결성 위반 | 409 | false |
| INTERNAL | 분류 불가 내부 오류 | 500 | false |
2. MDC Key Standard (final)
Trace: D11 (snake_case 강제). source / propagation channel 은 운영 wiring.
- UNSUPPORTED_IMPL_DECISION: snake_case 선택 자체 (ECS dot notation / Micrometer dot.case 대안 존재). 표현 계층별 매핑은 §3 (D19).
- OUT_OF_BRANCH_SCOPE (Q12):
user_principal의 "pseudonymized" 알고리즘 (HMAC-SHA256 +PSEUDONYMIZATION_SALT등)은 본 branch 범위 밖 — raw/branch-notes/feature-security-operational-baseline / raw/branch-notes/feature-log-management-contract owner. 본 branch 는 log only + pseudonymized 형태로만 기록 이라는 계약만 정의(평문 principal/raw id 금지).
snake_case 강제. MDC key 단위는 camelCase / dot.case 금지 (envelope/header 표현은 §3 매핑).
| MDC key | source | propagation channel |
|---|---|---|
| request_id | inbound filter (생성 또는 X-Request-Id 헤더 — D14 sanitization / D15 검증 후) | response header X-Request-Id |
| trace_id | Micrometer Tracing | W3C traceparent header |
| span_id | Micrometer Tracing | W3C traceparent |
| correlation_id | inbound header X-Correlation-Id 또는 생성 (D14/D15) | HTTP X-Correlation-Id, message header correlation_id |
| tenant_id | tenant context (활성 시 — tenant-context-policy 도착 시) | downstream HTTP X-Tenant-Id (with allowlist) |
| user_principal | security context (pseudonymized only) | log only, headers forbidden |
3. ID 명명 표현 계층 매핑 (snake ↔ camel ↔ kebab)
Trace: D19 + 부모 §21 (registry 매핑) + §25 (envelope camelCase owner = schema-serialization).
- UNSUPPORTED_IMPL_DECISION: 케이스 선택 자체는 registry + schema-serialization 분담. 본 표는 동일 식별자 의 계층별 표현이 의도적 매핑임을 명문화 (branch 단독 독해 시 모순 오인 방지).
| 식별자 | MDC key (log) | envelope meta (JSON) | HTTP header |
|---|---|---|---|
| request id | request_id (snake) |
meta.requestId (camel) |
X-Request-Id (kebab) |
| trace id | trace_id (snake) |
meta.traceId (camel) |
traceparent (W3C lowercase) |
| span id | span_id (snake) |
(envelope 미노출) | traceparent (W3C lowercase) |
| correlation id | correlation_id (snake) |
meta.correlationId (camel) |
X-Correlation-Id (kebab) |
| tenant id | tenant_id (snake) |
(활성 시) | X-Tenant-Id (kebab) |
- 계약: 같은 논리 식별자는 위 3-열이 1:1 매핑이어야 함. 표현 case 가 달라도 의미 는 동일 (테스트 계약 "response meta 의 ID 의미가 log MDC key 의미와 다르면 실패").
- downstream 구속: log-management-contract 가 user/resource id 의 MDC key 를 추가할 때 snake_case(
user_id/resource_id) 사용 — resource-identifier §9 의user.id/resource.id(dot) illustration 은 본 표준에 conform.
4. error.details JSON shape (validation field error)
Trace: D12 +
JSONAPI-ERR-C3(field pointer) +GOOG-ERR-C5(typed payload).
field출력 format (Q5, 구현 명확화): producer 는 한 응답에서 dot path 를 default 로 emit (SpringFieldError.getField()가 native dot path — 변환 비용 0). JSON pointer(RFC 6901)는 nested/array 위치 표현이 필요한 경우에만 허용. 한 응답 내 혼용 금지.rejectedValuemasking trigger (Q6, 구현 명확화): 민감 필드는 (a)@Sensitive/@Masked마커 annotation, 또는 (b) name denylist (password,token,secret,apiKey,ssn,card*) 매칭 시rejectedValue를 omit (또는****). default = omit. 정밀 DLP/PII 분류는 raw/branch-notes/feature-log-management-contract / raw/branch-notes/feature-security-operational-baseline owner.- UNSUPPORTED_IMPL_DECISION: dot-path default 선택 + denylist 어휘 자체는 ca-tmpl 운영 trade-off — 외부 표준 단일 근거 없음. validation 외 category 의 details 는
null(boundaryBATCH_PARTIAL_FAILURE만 별도 shape).
{
"field": "user.email",
"rejectedValue": "<omitted if sensitive>",
"code": "VALIDATION_EMAIL_FORMAT",
"message": "invalid email format"
}
5. Retryable → Retry-After / X-RateLimit-* surfacing (D13)
Trace: D13 +
RFC9110-C21(Retry-After 503/3xx) +RFC9110-C6(413 temporary).
- format + 값 출처 (Q7, 구현 명확화):
Retry-After는 delta-seconds(정수) 를 default 로 emit (HTTP-date 아님 — skeleton 단순성). 값 = error-codes.yamlretry_after_seconds컬럼. upstream 503 의Retry-Afterpassthrough 여부는 raw/branch-notes/feature-rate-limit-idempotency-contract / outbound adapter owner.- UNSUPPORTED_IMPL_DECISION:
retry_after_seconds값 + delta-seconds 선택 = error-codes.yaml / ca-tmpl project-decision.X-RateLimit-*는 비표준 관례 (headers.yaml). 429 세부 = rate-limit-idempotency owner.
| category | HTTP | retryable | retry hint header |
|---|---|---|---|
| TRANSIENT_DEPENDENCY | 503 | true | Retry-After (MUST, RFC9110-C21) |
| RATE_LIMIT | 429 | true | Retry-After (+ X-RateLimit-Limit/Remaining/Reset 권고 — rate-limit owner) |
| 그 외 retryable=false | — | false | (헤더 없음) |
- envelope
error.retryable: true와Retry-After헤더는 함께 존재해야 함 (envelope 만 true + 헤더 부재 = 실패).
6. inbound 헤더 sanitization (D14) + trace trust boundary (D15)
Trace: D14 +
OWASP-LOG-C1/C3/C5(untrusted / CRLF sanitize / CWE-117); D15 +W3C-TC-C2/C5(traceparent format / propagation).
- strip vs encode + "구분자" 범위 (Q8, 구현 명확화): 본 skeleton 은 구조화 JSON 로깅 전제 → 핵심 위협은 CR/LF/제어문자에 의한 줄 위조. default = strip (제거) of
\r\n+ ASCII 제어문자 (< 0x20). "구분자(delimiter) strip" 은 pattern-layout 로깅을 쓸 때만 해당 (그 경우 layout 구분자 추가 strip) — JSON 로깅에서는 불필요. reject(요청 거부)·encode 아님 (값은 보존하되 control char 만 제거).- traceparent 무효 처리 위치 (Q9, 위임): 무효
traceparent→ 새 trace 시작은 Micrometer Tracing 의 W3C propagator 동작에 위임 (대부분 자동) — 본 branch 는 수용/무효→재생성 계약만 명시. propagator 구성/검증 detail = raw/branch-notes/feature-distributed-tracing-contract owner.- UNSUPPORTED_IMPL_DECISION: ①sanitization regex/charset/최대 길이 값 (OWASP 미규정 — 사용자 trade-off). ②trust 의 보안 세부 = security-operational-baseline owner (cross-cite).
- inbound
X-Request-Id/X-Correlation-Id수신 → MDC/로그 반영 전 CR/LF/제어문자 strip + length cap (값 부재/무효 시 server 생성). 위치 =RequestLoggingFilter(§0). - inbound
traceparent수신 → W3C 4-field format(W3C-TC-C2) 검증. 무효 형식이면 새 trace 시작 (client 값 무시 — Micrometer propagator 위임). 유효하면 propagation 의무(W3C-TC-C5). tracestate에 PII 금지(W3C-TC-C5MUST NOT) — outbound 전파 시 동일.
7. operational error → trace span 기록 (D16, server-side)
Trace: D16 +
OTEL-EXC-C1/C2/C4/C6(exception event / attributes / span status ERROR / app-set status).
- 대상 범위 (Q10, 구현 명확화): span status ERROR + exception 기록 대상은 모든 5xx —
INTERNAL(500) +PERMANENT_DEPENDENCY(502) +TRANSIENT_DEPENDENCY(503). 4xx(client error)는 span status =unset(OK 아님 — server-side fault 가 아니므로 ERROR 도 아님; OTel 기본 unset 유지). D16 텍스트의 "INTERNAL" 은 대표 예시이며 5xx 전체에 적용.- UNSUPPORTED_IMPL_DECISION (4xx=unset 근거):
OTEL-EXC-C4는 "오류 시 SHOULD ERROR" 만 규정하고 HTTP 4xx↔span status 매핑은 직접 규정하지 않음(HTTP semconv 별도, 본 raw 미보관). "4xx=unset" 은 ca-tmpl 운영 trade-off — 실제 4xx/5xx↔span status wiring 은 raw/branch-notes/feature-distributed-tracing-contract owner 가 HTTP semconv 기준으로 확정.- 기록 위치 (Q11, 구현 명확화):
recordException+setStatus(ERROR)호출 위치 =GlobalExceptionHandler(§0) 또는 Micrometer Observation 의 error stop. 둘 중 택1 — skeleton default = Observation 자동(handler 가 Observation scope 안에서 던지면 자동 기록). 정확한 wiring = distributed-tracing owner.- UNSUPPORTED_IMPL_DECISION: client 응답 stack 제외는 OTel 직접 근거 없음 — ca-tmpl 보안 정책(D5/Forbidden). HTTP 5xx↔span ERROR 매핑은 HTTP semconv 별도. span detail 조립 = distributed-tracing owner.
- 모든 5xx 발생 시 server span 에
exception이벤트(exception.type/exception.message/exception.stacktrace) 기록 + spanstatus=ERROR. 4xx 는 대상 아님. - client HTTP 응답에는 stack trace 미포함 (판정 기준 Forbidden 과 정합) — exception 세부는 telemetry 전용.
8. error code lifecycle (never-reuse, D17)
Trace: D17 +
STRIPE-C2(format/message 변경 = backward-compat → code 가 안정 표면) +GOOG-ERR-C4(machine-readable 식별자).
- UNSUPPORTED_IMPL_DECISION: never-reuse 자체는 ca-tmpl 운영 정책 (resource-identifier D15 대칭). deprecation 절차(Deprecation/Sunset) = api-compatibility owner.
error.code는 append-only — rename / 의미 변경 / 재사용 금지. 폐기는 삭제가 아니라 deprecated 표시 + compatibility 절차.- error-codes.yaml row 변경 시
compatibility_impact컬럼(부모 §21 L812) 기반 registry-governance 검사. - deprecated code 의 yaml 표현 (Q13, UNSUPPORTED_IMPL_DECISION + 경계): 코드 ground truth —
error-codes.yaml은 현재compatibility_impact: none|additive|behavior-change|breaking컬럼만 있고 deprecated/sunset 전용 컬럼은 미정의. 폐기 표현 제안 스케치(미구현·미합의): row 에deprecated: true+sunset_date: YYYY-MM-DD+replacement_code:추가. 컬럼명·발행 시점·Deprecation/Sunset 헤더 연동의 실제 절차는 본 branch 범위 밖 — raw/branch-notes/feature-api-compatibility-deprecation-contract owner 가 확정. 본 branch 는 "code 는 폐기돼도 재사용 안 됨" 계약만 소유.
9. 401 → WWW-Authenticate (D18, cross-cite)
Trace: D18 — 부모 §25 L1086 (security-operational-baseline owner) + RFC 9110 §11.6.1 MUST.
AUTH(401) 응답은WWW-Authenticate헤더 동반 (bare 401 금지). 헤더 발행 정책 은 security-operational-baseline owner — 본 branch 는 enum 의 401 row 가 헤더 의무를 암시함을 명시만.
판정 기준
| 구분 | 기준 |
|---|---|
| Decision | 자체 structured envelope를 사용하고 ProblemDetail은 사용하지 않음 |
| Allowed | validation field error처럼 클라이언트가 수정 가능한 정보만 error.details에 포함 |
| Forbidden | exception class name, stack trace, SQL, token, internal endpoint, upstream raw body 노출 (client 응답). inbound 헤더 값의 미-sanitized 로그 반영 (D14) |
| Required fields | success, error.code, error.category, error.retryable, meta.requestId, meta.traceId, meta.correlationId |
| Required headers | retryable 응답(503/429)에 Retry-After (D13); 401 에 WWW-Authenticate (D18, security owner) |
| Failure condition | 5xx/validation/auth/access denied/no handler/type mismatch가 envelope와 log contract를 깨면 실패 |
SSOT Ownership
| contract | owner decision | consumers |
|---|---|---|
| error envelope schema | 이 branch에서만 field 추가/삭제/required 여부 변경 | business validation, schema serialization, API compatibility |
error.category enum |
이 branch registry가 final (부모 §6 은 본 enum 으로 정합 — F1) | persistence, outbound, security, cache, message branches |
| request/correlation/trace ID meaning + 표현 매핑 (D19) | 이 branch 정의가 final (envelope camelCase 표기 owner = schema-serialization) | distributed tracing, log management, metrics alerting |
| MDC/log key names | 이 branch registry와 contract-registry branch가 final | log management, operational runbook |
| error code lifecycle (never-reuse, D17) | 이 branch + error-codes.yaml registry | api-compatibility (deprecation 절차) |
consumer branch가 위 값을 바꾸려면 이 branch의 Decision과 registry를 먼저 변경합니다.
테스트 계약
- 모든 실패 응답은 envelope schema를 만족해야 함.
- 5xx 응답에 raw exception class/stack trace가 client 응답에 노출되면 실패.
- requestId/traceId/correlationId가 response meta와 log MDC에 존재해야 함.
- tracing disabled profile에서도
meta.traceId가 비어 있거나 누락되면 실패. - response meta의 ID 의미가 log MDC key 의미와 다르면 실패 (D19 매핑 위반).
- retryable 필드가 없는 operational error는 실패.
- retryable=true(503/429) 응답에
Retry-After헤더가 없으면 실패 (D13). - inbound 헤더(
X-Request-Id/X-Correlation-Id) 값이 CR/LF sanitization 없이 로그에 기록되면 실패 (D14). - 무효 형식
traceparent수신 시 새 trace 를 시작하지 않고 그대로 채택하면 실패 (D15). INTERNAL(5xx) 발생 시 server span status 가 ERROR 로 설정되지 않으면 실패 (D16, server-side telemetry).error.code의 rename/재사용이 compatibility 검사 없이 통과되면 실패 (D17, registry-governance).ProblemDetail타입이나 필드 구조에 의존하는 테스트가 있으면 실패.- MDC key 이름이 위 "MDC Key Standard" 표와 불일치하면 실패.
error.category값이 위 enum (VALIDATION/AUTH/AUTHZ/NOT_FOUND/CONFLICT/RATE_LIMIT/TRANSIENT_DEPENDENCY/PERMANENT_DEPENDENCY/DATA_INTEGRITY/INTERNAL) 외 값이면 실패.- validation error response의
error.details항목이 위 JSON shape(field/rejectedValue/code/message)를 따르지 않으면 실패.
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존을 미리 열거. (§테스트 계약·§구현 가이드 Q-notes·§형제 branch cross-cite·§SSOT Ownership 에 분산된 것을 R4 형식으로 통합 — 새 결정 없음.)
-
실패·엣지 경로 (각 경로의 기대 동작 — 위반 시 §테스트 계약 실패):
- tracing disabled (local/test profile) —
meta.traceId누락 금지. 기대: 같은 request 의 envelopemeta.traceId== log MDCtrace_id. 구현 위치·메커니즘 (actually-implemented):RequestLoggingFilter가 micrometer-tracing 미공급 시trace_id를request_id로 미러(RequestLoggingFilter.javaL52-54 주석 "trace_id mirrors request_id until micrometer-tracing supplies one (D7)");request_id자체는 inbound 헤더 부재/blank 시 서버UUID.randomUUID()생성(L75).ResponseMetaFactory는 MDC 값을 읽기만 하므로 fallback 책임은 필터에 있음. (D7) —trace.sampled=falsediagnostic 플래그 분리는planned(distributed-tracing owner). - 무효 형식
traceparent수신 — 그대로 채택 금지. W3C 4-field 검증 실패 시 새 trace 시작 (Micrometer propagator 위임). 과대 길이 헤더는 length cap. (D15 /W3C-TC-C2) - inbound 헤더 CR/LF 주입 (
X-Request-Id/X-Correlation-Id) — MDC/로그 반영 전\r/\n/제어문자(<0x20) strip. 기대: 주입 시도해도 로그 라인 1개 유지 + control char 부재. (D14 /OWASP-LOG-C3/C5/ CWE-117) - 5xx vs 4xx span 처리 — 모든 5xx(
INTERNAL/PERMANENT_DEPENDENCY/TRANSIENT_DEPENDENCY)는 server spanstatus=ERROR+exception이벤트. 4xx 는 span ERROR 아님(unset/OK). client 응답엔 stack 미포함. (D16 Q10/Q11) - retryable=true +
Retry-After부재 — envelopeerror.retryable: true(503/429)인데Retry-After헤더 없으면 실패. 둘은 함께 존재해야 함. (D13) - 민감 필드
rejectedValue— validation field 가password/token/secret등(annotation 또는 denylist 매칭)이면rejectedValueomit/mask. default = omit. (D12 Q6) - enum/lifecycle 위반 —
error.category가 10-enum 외 값이거나,error.code가 compatibility 검사 없이 rename/재사용되면 실패(append-only). (D10 / D17) - partial failure — boundary
BATCH_PARTIAL_FAILURE는 본 branchVALIDATIONcategory 의 consumer 이며 별도 details shape. validation 외 category 의 details 는null. (boundary D5 공유)
- tracing disabled (local/test profile) —
-
다른 계약 의존 (해당 계약이 바뀌면 본 branch 영향):
- raw/branch-notes/feature-security-operational-baseline 의
D18에 의존 —WWW-Authenticate헤더 발행 정책 owner. 본 branch 는 401 enum row 가 헤더 의무를 암시함만 명시(bare 401 금지). 발행 방식 변경 시 §9 cross-cite 갱신 필요. - raw/branch-notes/feature-rate-limit-idempotency-contract 의
D13(429 세부) 에 의존 — 429Retry-After(RFC 6585 §4, raw 미보관) +X-RateLimit-*운영 세부 owner. 본 branch 는 retryable surfacing 계약만. - raw/branch-notes/feature-distributed-tracing-contract 의
D15/D16에 의존 — W3C propagator 구성/검증 + span 조립 detail owner. 본 branch 는 ID 의미 + error→span 기록 의도 만 정의. - raw/branch-notes/feature-schema-serialization-contract — envelope
meta.*camelCase 표기 owner. D19 의 snake↔camel 매핑은 이 owner 의 직렬화 규칙에 의존. - raw/branch-notes/feature-log-management-contract — 본 branch MDC snake_case 표준을 consume(
user_id/resource_id추가 + redaction/PII MDC 분리).user_principalpseudonymization 알고리즘 도 이 owner(§2 Q12 OUT_OF_BRANCH_SCOPE). - raw/branch-notes/feature-resource-identifier-contract — MDC snake_case 정합(
user.id/resource.iddot illustration →user_id/resource_idconform) + error code never-reuse(D17)와 ID never-reuse 대칭. - raw/branch-notes/feature-api-compatibility-deprecation-contract —
error.codedeprecation/Sunset 절차 owner. lifecycle(D17) 폐기 흐름이 이 계약에 의존. - raw/branch-notes/feature-contract-registry-governance —
error-codes.yaml/mdc-keys.yaml/metrics.yamlrow 편집 + diff gate owner. 본 branch 는 schema/category 매핑만, 실 row 는 registry.
- raw/branch-notes/feature-security-operational-baseline 의
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
Spring Boot spring.mvc.problemdetails.enabled 가 default false (ca-tmpl 의 ProblemDetail 거부 정책과 충돌 없음) |
SPRING-PD-C4 Does not prove: property default 값 본 인용 범위 밖 |
Spring Boot reference docs 별도 fetch + application.yml 검증 |
planned |
ca-tmpl envelope 의 meta.traceId 가 모든 5xx/validation/auth 응답에서 누락 없이 채워짐 |
foundation owner branch — 모든 ControllerAdvice/Filter 에서 동작 보장 필요 | contract test: 각 카테고리별 fixture exception 발생 → response body 의 meta.traceId non-empty 확인 |
planned |
| ProblemDetail 타입/필드 의존 테스트 부재 (build-time 강제) | Spring Boot 가 일부 built-in exception 을 ProblemDetail 로 자동 변환 (SPRING-PD-C2/C3) — autoconfigure 누락 시 leak 가능 |
ArchUnit test: org.springframework.http.ProblemDetail import 금지 + application.yml 의 spring.mvc.problemdetails.enabled=false 확인 |
planned |
error.category enum 10개 의 retryable default (RATE_LIMIT/TRANSIENT_DEPENDENCY = true, 나머지 false) 가 실제 운영에서 정합 |
enum default 는 ca-tmpl 운영 가정 — 일부 (e.g., NOT_FOUND with eventual consistency) 는 retryable 일 수 있음 | 실제 incident 회고 + adapter 별 retryable override 메커니즘 검증 | needs-confirmation |
MDC key snake_case (request_id) 가 Spring MVC RequestContextHolder 와 Reactor Context 양쪽에서 일관 propagation |
foundation 결정 — 실제 reactive stack 에서 MDC 전파 확인 필요 | reactive integration test + @Async test |
planned |
| tracing disabled profile (e.g., local) 에서 request-id fallback traceId 의 uniqueness + log-envelope 정합 | 현재 fallback은 RequestLoggingFilter가 서버 생성 request_id를 trace_id로 미러링하며 ResponseMetaFactory는 MDC 값을 읽는다. 별도 trace.sampled=false 표시는 distributed-tracing owner에 남아 있음 |
local profile 통합 테스트 — 같은 request 의 envelope traceId == log traceId 확인 | fallback 메커니즘 actually-implemented; 통합 테스트와 sampled flag는 planned |
error.details 의 rejectedValue 가 PII/sensitive 값 일 때 자동 masking (e.g., password field) |
validation field 가 password 일 때 rejectedValue 그대로 노출 위험 | contract test: password field validation 실패 → rejectedValue 가 **** 또는 omitted |
planned |
error.category=DATA_INTEGRITY (default 409) 와 ca-tmpl 의 DB optimistic lock (CONFLICT, retryable=true) 분기 정합 |
DATA_INTEGRITY vs CONFLICT 모두 409 — runtime 분류 logic 명확성 필요 | persistence adapter exception → category 매핑 contract test | planned |
(D13) retryable=true(503/429) 응답에 Retry-After 헤더가 envelope error.retryable 와 함께 존재 |
RFC9110-C21 은 503/3xx 만 normative — 429 는 RFC 6585; 헤더 발행이 실제 wiring 됐는지 미검증 | contract test: 503/429 fixture → 응답 헤더 Retry-After non-empty + envelope retryable=true |
planned |
(D14) inbound X-Request-Id 에 \r\n 주입 시 로그가 1줄로 유지되고 CR/LF 가 strip |
OWASP 는 원칙만 — 실제 sanitizer 구현/적용 위치 미검증 | injection test: X-Request-Id: foo\r\nFAKE LOG → 로그 라인 1개 + control char 부재 grep |
planned |
(D15) 무효 형식 traceparent 수신 시 새 trace 시작 + 과대 헤더 length cap |
W3C 무효 처리/길이 한계 raw 미보관 — 구현 분기 미검증 | integration test: malformed traceparent → 신규 trace-id 생성; 초과 길이 헤더 거부/절단 |
planned |
(D16) 5xx 발생 시 server span status=ERROR + exception 이벤트 기록, client 응답엔 stack 부재 |
OTel 사양은 attribute 정의만 — 실제 SDK wiring + 응답 분리 미검증 | OTel in-memory test exporter: span status ERROR + exception event 존재; 동시에 response body 에 stacktrace 부재 grep | planned |
(D17) error.code rename/삭제/재사용 시 compatibility 검사 실패 |
never-reuse 는 정책 — registry-governance 자동 게이트 미검증 | error-codes.yaml diff gate (removed/renamed code 검출 시 build 실패) — registry-governance owner | needs-confirmation |
(D18) AUTH(401) 응답에 WWW-Authenticate 헤더 존재 |
헤더 발행 owner = security-operational-baseline — cross-branch 정합 미검증 | security-baseline contract test (401 → WWW-Authenticate non-empty) cross-link |
planned |
| (D19) 동일 식별자의 MDC snake ↔ envelope camel ↔ header kebab 매핑이 일관 | 표현 case 가 달라 매핑 drift 위험 | contract test: 한 request 의 meta.requestId == MDC request_id == X-Request-Id (값 동일) |
planned |
(D13/F1 검증) error-codes.yaml 의 AUTH_KID_UNKNOWN 이 category=AUTH, retryable=false 인데 retry_after_seconds: 5 보유 — D13("retryable 응답만 Retry-After surface")과 모순 |
2026-06-01 registry 검증에서 발견된 유일 이상치. source 주석(feature-security-operational-baseline L88 "JWKS 미캐시 → 401 + Retry-After 5s") + client_safe_message: "please retry" 가 retryable 의도를 시사 |
해소됨 (2026-06-01): 사용자 결정 = JWKS 키 회전 가정 → retryable: true 로 수정 (option b). retry_after_seconds=5/runbook_link 유지, §21 runbook 규칙 충족, yaml parse OK. 가역 — 키 고정 정책 전환 시 false 복귀(yaml inline 주석 명시) |
locally-verified (registry 정합 확인; contract-verification:auth-category 테스트는 CI/사용자 실행) |
Phase C2 구현 진행 현황 (완료 — 2026-06-01)
이력 정정: 초기에 subagent 루프가 Task 1/2/2b 를 개별 커밋(
ca7e12f/5945d07/31ea05c/fad374f)으로 진행했으나, 사용자 요청으로git reset --mixed하여 그 커밋들은 폐기(SHA 무효)하고 변경은 working tree 에 보존, 이후 나머지 Task 를 직접 편집으로 완료. 단일 커밋은 사용자가 직접 생성 예정 — 본 노트는 SHA 대신 파일·검증 기준으로 기록.
상태: G1·G2·G3·G4·G5·G7 = actually-implemented locally-verified. G6(Retry-After 헤더 발행 / span 기록) = seam·stub 만(owner branch 위임 — D21). 전체 검증: ./gradlew check (전 모듈 test + verifyCleanArchitectureDependencies + ArchUnit CleanArchitectureTest) → BUILD SUCCESSFUL.
신규 파일
shared-contract:error/Category.java(10-enum, G7/D10),response/ResponseMeta.java(requestId/traceId/correlationId, G2)adapter-web/observability/:MdcKeys.java(snake_case 상수),HeaderSanitizer.java(CR/LF·제어문자 strip — D14/CWE-117),ResponseMetaFactory.java(snake MDC→camel meta 투영 — D19),RetryAfterAdvisor.java(D13/D16 cross-owned seam — D21)- 테스트:
CategoryTest/ApiErrorTest/EnvelopeTest(shared-contract),HeaderSanitizerTest/ResponseMetaFactoryTest/RetryAfterAdvisorTest/RequestLoggingFilterTest/EnvelopeMetaIntegrationTest(adapter-web),PortfolioErrorCodeTest(sample-portfolio)
수정 파일
shared-contract:ApiErrorCode.category()추가,OperationalError코드별 category 매핑(retryable per-code 유지),ApiError에 category 필드,Envelope/BulkEnvelopeflattraceId→ResponseMeta metaadapter-web:ErrorResponseFactory/EnvelopeBodyAdvice/HealthcheckController(meta+category 반영),RequestLoggingFilter(snake_case MDC + X-Correlation-Id + sanitization)app-bootstrap:logback-spring.xml(snake_case includeMdcKeyName),application.yml/application-test.yml(spring.mvc.problemdetails.enabled=false)sample-portfolio:PortfolioErrorCode(category()),WorkLogController(ResponseMetaFactory),BulkEnvelopeTest/VirtualThreadMdc*Test(ResponseMeta·snake_case 정합)
category 할당표 (OperationalError)
VALIDATION_FAILED/BAD_PARAMETER/MAPPING_FAILED/BATCH_PARTIAL_FAILURE/METHOD_NOT_ALLOWED/UNSUPPORTED_MEDIA_TYPE → VALIDATION, INTERNAL_ERROR → INTERNAL, UNAUTHENTICATED/INVALID_TOKEN → AUTH, FORBIDDEN → AUTHZ, ROUTE_NOT_FOUND → NOT_FOUND. (405/415→VALIDATION 은 UNSUPPORTED_IMPL_DECISION — 10-enum 에 transport 카테고리 없음.) retryable 은 per-code 유지(§1 Q1 — INTERNAL_ERROR 처럼 category default 와 다른 코드 허용).
마주친 문제
- (해소)
@WebMvcTestnested@SpringBootConfiguration컨텍스트 오염: 신규 envelope-meta 계약 테스트를app-bootstrap의@WebMvcTest(nested@SpringBootConfiguration포함)로 작성했더니, (a) production profile placeholder 로BindException, (b) 같은 패키지OperationalContractRuntimeTest의 config 자동 탐지를 오염시켜EnvelopeBodyAdvice미등록 회귀. git stash / 파일 mv 비파괴 격리로 원인 확정 → 세 컴포넌트가 모두 adapter-web 소속이므로 adapter-web standalone MockMvc(EnvelopeMetaIntegrationTest)로 재설계해 해소. 상세: raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01 - (해소) 인터페이스 변경의 숨은 consumer 컴파일 break:
ApiErrorCode.category()추가 →PortfolioErrorCode(구현체),ApiError/Envelope/BulkEnvelope시그니처 변경 →BulkEnvelopeTest(allOk(List,String)/traceId()) 컴파일 실패. 컴파일러가 전부 노출 → 한 패스 마이그레이션. (계획 누락 consumer 였음 — 추상 메서드 추가의 blast radius 가 안전장치로 작동.)
묶음 (이 branch에서 파생된 자료)
- raw/company-tech-blogs/github-api-error-format
- raw/company-tech-blogs/stripe-error-format
- raw/company-tech-blogs/toss-payments-error-format
- raw/official-docs/google-api-error-format
- raw/official-docs/graphql-errors-spec
- raw/official-docs/json-api-errors-spec
- raw/official-docs/otel-exceptions-semantic-conventions
- raw/official-docs/owasp-logging-cheat-sheet
- raw/official-docs/persistence-spring-dataaccessexception-hierarchy
- raw/official-docs/problem-detail-rfc-7807
- raw/official-docs/spring-problem-detail
본 feature branch 는 leaf — 자식 branch 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
Sub-branches (세부 작업)
- (없음 — project 직접 자식 branch, 하위 branch 없음)
근거 자료
- raw/official-docs/rfc9110-http-semantics — D13:
Retry-Aftersemantics (RFC9110-C21/C6). D18: 401+WWW-Authenticate §11.6.1 cross-cite - raw/official-docs/tracing-w3c-trace-context-spec — D15:
traceparent4-field 형식 + propagation/PII 의무 (W3C-TC-C2/C5) - raw/official-docs/owasp-logging-cheat-sheet — D14: inbound header MDC 값 sanitization / log injection(CWE-117) 방어 (OWASP-LOG-C1/C3/C5)
- raw/official-docs/otel-exceptions-semantic-conventions — D16: span exception 이벤트 + span status ERROR 공식 사양 (OTEL-EXC-C1/C2/C4/C6)
- raw/official-docs/stripe-resource-id-convention — D17: opaque string/error message 변경 = backward-compatible → code 안정성 (STRIPE-C2)
- raw/official-docs/google-api-error-format — D10/D12/D17: google.rpc.Code enum + ErrorInfo (GOOG-ERR-C1/C4/C5)
오류 기록 (본 feature 작업 중 발생)
- raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01 — Phase C2 envelope-meta 계약 테스트 추가 중
@WebMvcTestnested@SpringBootConfiguration이 같은 패키지OperationalContractRuntimeTest컨텍스트를 오염시킨 회귀. 격리 진단 → standalone MockMvc 재설계로 해소.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/operational-error-envelope-and-observability-foundation — 운영 envelope 에 category/meta 를 additive 로 확장, snake↔camel↔kebab 식별자 매핑, inbound 헤더 log injection 방어, category vs per-code retryable 분리.
강의 (이 작업을 위해 학습한 강의)
- (없음 — official-doc 근거 기반)
job-posting tie-ins (이 작업에서 파생된 글감)
- raw/blog-topics/operational-error-envelope-meta-category-migration-2026-06-01 — 운영 에러 분류 enum SSOT 고정 + envelope category/meta additive 마이그레이션 + 식별자 계층 매핑 + log injection 방어 + @WebMvcTest 오염 트러블슈팅(5개 글감 후보).
관련 일일 노트
- (없음 — scaffolding + reinforcement + Phase C2 구현 단계, 별도 일일 노트 미작성)
관심사 커버리지 (coverage-auditor 자동 생성)
/coverage가 생성하는 생성물 — 손유지 금지. 기준:rules/coverage-gate.md. governing_docs:api-error-envelope-design+observability-log-metric-trace-runbook. 마지막 감사: 2026-06-04 → Covered (Blocking 0 / Should-fix 0 / Advisory 0). Should-fix(UNLINKED_DELEGATION)는 본 표 추가로 해소.
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| custom envelope 채택 + ProblemDetail 거부 (success/error 대칭) | covered-here | — | — | D1 (official-standard RFC7807 + official-vendor-doc Spring PD); Envelope.java actually-implemented |
error.category 10-enum 1급 필드 |
covered-here | — | — | D10 (official-vendor-doc Google AIP-193); Category.java 10값 actually-implemented |
envelope fields success/data/error/meta + code UPPER_SNAKE_CASE + retryable 1급 |
covered-here | — | — | D3 (UNSUPPORTED_DECISION — success flag 외부 표준 없음), D4 (company-case-study); ApiError.java actually-implemented |
| client-safe message vs internal diagnostic 분리 | covered-here | — | — | D5 (official-standard RFC7807-C5 + official-vendor-doc GOOG-ERR-C2); §판정기준 Forbidden |
error.details {field, rejectedValue, code, message} |
covered-here | — | — | D12 (official-standard JSON:API + official-vendor-doc Google AIP-193) |
meta.{requestId, traceId, correlationId} envelope 1급 노출 |
covered-here | — | — | D19·D20 (project-ssot §3/§21/§25); ResponseMeta.java actually-implemented |
| exception leak 금지 (stack/SQL/token/internal path 응답 미포함) | covered-here | — | — | D5 + §판정기준 Forbidden |
| envelope 대안 5종 비교·거부 근거 | covered-here | — | — | D1 + §외부 근거/대안 조사 |
error code catalog → error-codes.yaml SSOT (본 branch 는 schema/category 매핑만) |
covered-here | — | — | D9 (company-case-study GH-ERR-C4); error-codes.yaml ground-truth 확인 |
error.category enum + envelope schema SSOT ownership |
covered-here | — | — | D6 (UNSUPPORTED_DECISION — 내부 운영 정책); §SSOT Ownership |
| MDC key snake_case 표준 | covered-here | — | — | D11 (UNSUPPORTED_DECISION — 공식 표준 없음, ECS/Micrometer 대안); MdcKeys.java actually-implemented |
| ID 표현 계층 매핑 (MDC snake ↔ envelope camel ↔ HTTP kebab) | covered-here | — | — | D19 (project-ssot); ResponseMetaFactory.java actually-implemented |
| requestId/traceId/correlationId 의미 final 정의 | covered-here | — | — | D8 (official-standard W3C-TC-C2 for traceId; requestId/correlationId = UNSUPPORTED_DECISION) |
| traceId never-missing (tracing disabled 시 generated opaque id) | covered-here | — | — | D7 (UNSUPPORTED_DECISION — noop tracer 직접 근거 없음); RequestLoggingFilter.java actually-implemented |
| inbound 헤더 CR/LF sanitization (CWE-117) | covered-here | — | — | D14 (official-reference OWASP-LOG-C1/C3/C5); HeaderSanitizer.java actually-implemented |
| traceparent trust boundary (format 검증 + 무효 시 재생성) | covered-here | — | — | D15 (official-standard W3C-TC-C2/C5); propagation 세부 위임 → raw/branch-notes/feature-distributed-tracing-contract |
| 5xx span status ERROR + exception 이벤트 기록 의도 | covered-here | — | — | D16 (official-vendor-doc OTEL-EXC-C1/C2/C4/C6); span 조립 세부 위임 → raw/branch-notes/feature-distributed-tracing-contract |
Retry-After 헤더 surface (503 MUST / 429 권고) |
covered-here | — | — | D13 (official-standard RFC9110-C21/C6); 429 세부 위임 → raw/branch-notes/feature-rate-limit-idempotency-contract |
WWW-Authenticate on 401 (RFC 9110 §11.6.1 MUST) |
delegated | raw/branch-notes/feature-security-operational-baseline D18 | — | 발행 정책 owner; 본 branch §9 cross-cite (envelope/category 측만) |
error.code lifecycle (append-only, never-reuse, deprecation 절차) |
covered-here | — | — | D17 (company-case-study STRIPE-C2 + official-vendor-doc GOOG-ERR-C4; never-reuse = UNSUPPORTED_DECISION); 폐기 절차 위임 → raw/branch-notes/feature-api-compatibility-deprecation-contract |
| Phase C2 phasing 결정 (G1~G7 구현 범위 분리) | covered-here | — | — | D21 (UNSUPPORTED_DECISION — 사용자 결정 2026-06-01) |
| structured JSON Logback + masking/redaction + log sampling | delegated | raw/branch-notes/feature-log-management-contract | — | 본 branch MDC snake_case 표준 consume; PII MDC 분리 owner |
| Micrometer dot.case metric + Prometheus + alert severity + error_code cardinality bound | delegated | raw/branch-notes/feature-metrics-alerting-contract | — | 본 branch enum 을 metric tag dimension 으로 consume |
| W3C traceparent 전파 + sampling + OTel bridge wiring | delegated | raw/branch-notes/feature-distributed-tracing-contract | — | 본 branch 는 ID 의미 + error→span 기록 의도 만 정의 |
| envelope camelCase 직렬화 규칙 | delegated | raw/branch-notes/feature-schema-serialization-contract | — | D19 에 "envelope camelCase owner = schema-serialization" 명기 |
| error-codes/mdc-keys/metrics.yaml row 편집 + diff gate | delegated | raw/branch-notes/feature-contract-registry-governance | — | 본 branch 는 schema/category 매핑만, 실 row 는 registry |
STALE_OWNER 참고 (coverage 범위 밖,
/ingest선행 조건): governing docwiki/projects/ca-tmpl/api-error-envelope-design.md(statusdraft,documented-only태그)가 코드 실측(Phase C2 완료)보다 stale. 본 branch verified 추출 전 해당 canonical status 갱신 필요.
완료 후 정리
Ground-truth 대조 (2026-06-04, /ingest): ca-tmpl @0c996fc("운영 에러 관측성 foundation 계약 구현")의 코드를 실측한 결과 G1~G5/G7(envelope
meta/category1급,Category10-enum, MDC snake_case,correlation_id처리,HeaderSanitizer)가 모두 코드에 존재하고 HEADdb61075에서도 유지됨.Envelope/ApiError/ResponseMeta/Category/OperationalError/MdcKeys/HeaderSanitizer/ResponseMetaFactory/RequestLoggingFilter실재 확인. ProblemDetail 거부는 ArchUnitCleanArchitectureTest(L355) +application.ymlproblemdetails.enabled:false+ProblemDetailDisabledConfigTest로 build-time 강제. G6(Retry-After 헤더 발행/span ERROR)는RetryAfterAdvisorseam/stub만(owner branch 위임).tenant_idMDC 키는 아직 미정의(조건부). stale 잔재(com.example.blog/sample-ticket) 없음 — sample 모듈sample-portfolio. → branchstatus: verified. governing project docs 2건 + concept docs 2건 동기화 완료.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):