Files
llm-wiki/raw/branch-notes/feature-operational-error-observability-foundation.md
T

77 KiB


branch: feature-operational-error-observability-foundation

Layer: raw/branch-notes/ — 운영 실패 분류와 관측성의 첫 기준 branch. 완료 후 /ingestwiki/projects/에만 추출합니다. status_label: in-progress | review | merged | abandoned

부모 (필수)

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)

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 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-After surfacing (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_id MDC 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 참조.

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개 사각(F1F8) 식별 → 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_UNKNOWN retryable=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.category enum, 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.category enum과 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.category enum 10개 final. (D10)
  • 2026-05-22: MDC Key Standard = snake_case 강제. (D11)
  • 2026-05-22: error.details JSON shape = {field, rejectedValue, code, message}. (D12)
  • 2026-06-01: (D13) retryable 응답은 재시도 시점을 Retry-After 헤더로 surface. 503/TRANSIENT_DEPENDENCY 는 Retry-After MUST(RFC 9110 §10.2.3), 429/RATE_LIMIT 은 Retry-After(+ X-RateLimit-* 권고) — 429 세부는 rate-limit-idempotency owner. / 이유: envelope error.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 강제" 와 "envelope meta.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 flat traceId shape 는 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 toSHOULD 보다 약한 어조. 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_idmeta.requestIdX-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-onlyactually-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) 응답 노출 ApiErrorcategory 필드 + ErrorResponseFactorycode.category().name() 주입 shared-contract/response/ApiError.java, adapter-web/error/ErrorResponseFactory.java D10
G2 meta.{requestId,traceId,correlationId} 객체 ResponseMeta record + Envelope/BulkEnvelope 가 flat traceIdmeta 로 교체 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.java javadoc 이 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 (Spring FieldError.getField() 가 native dot path — 변환 비용 0). JSON pointer(RFC 6901)는 nested/array 위치 표현이 필요한 경우에만 허용. 한 응답 내 혼용 금지.
  • rejectedValue masking 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 (boundary BATCH_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-Afterdelta-seconds(정수) 를 default 로 emit (HTTP-date 아님 — skeleton 단순성). 값 = error-codes.yaml retry_after_seconds 컬럼. upstream 503 의 Retry-After passthrough 여부는 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: trueRetry-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-C5 MUST 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 기록 대상은 모든 5xxINTERNAL(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) 기록 + span status=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.codeappend-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 의 envelope meta.traceId == log MDC trace_id. 구현 위치·메커니즘 (actually-implemented): RequestLoggingFilter 가 micrometer-tracing 미공급 시 trace_idrequest_id 로 미러(RequestLoggingFilter.java L52-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=false diagnostic 플래그 분리는 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 span status=ERROR + exception 이벤트. 4xx 는 span ERROR 아님(unset/OK). client 응답엔 stack 미포함. (D16 Q10/Q11)
    • retryable=true + Retry-After 부재 — envelope error.retryable: true(503/429)인데 Retry-After 헤더 없으면 실패. 둘은 함께 존재해야 함. (D13)
    • 민감 필드 rejectedValue — validation field 가 password/token/secret 등(annotation 또는 denylist 매칭)이면 rejectedValue omit/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 는 본 branch VALIDATION category 의 consumer 이며 별도 details shape. validation 외 category 의 details 는 null. (boundary D5 공유)
  • 다른 계약 의존 (해당 계약이 바뀌면 본 branch 영향):

검증해야 할 주장

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.ymlspring.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_idtrace_id로 미러링하며 ResponseMetaFactory는 MDC 값을 읽는다. 별도 trace.sampled=false 표시는 distributed-tracing owner에 남아 있음 local profile 통합 테스트 — 같은 request 의 envelope traceId == log traceId 확인 fallback 메커니즘 actually-implemented; 통합 테스트와 sampled flag는 planned
error.detailsrejectedValue 가 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.yamlAUTH_KID_UNKNOWNcategory=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/BulkEnvelope flat traceIdResponseMeta meta
  • adapter-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 와 다른 코드 허용).

마주친 문제

  • (해소) @WebMvcTest nested @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에서 파생된 자료)

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

Sub-branches (세부 작업)

  • (없음 — project 직접 자식 branch, 하위 branch 없음)

근거 자료

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

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

강의 (이 작업을 위해 학습한 강의)

  • (없음 — official-doc 근거 기반)

job-posting tie-ins (이 작업에서 파생된 글감)

관련 일일 노트

  • (없음 — 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 doc wiki/projects/ca-tmpl/api-error-envelope-design.md (status draft, documented-only 태그)가 코드 실측(Phase C2 완료)보다 stale. 본 branch verified 추출 전 해당 canonical status 갱신 필요.

완료 후 정리

Ground-truth 대조 (2026-06-04, /ingest): ca-tmpl @0c996fc("운영 에러 관측성 foundation 계약 구현")의 코드를 실측한 결과 G1~G5/G7(envelope meta/category 1급, Category 10-enum, MDC snake_case, correlation_id 처리, HeaderSanitizer)가 모두 코드에 존재하고 HEAD db61075에서도 유지됨. Envelope/ApiError/ResponseMeta/Category/OperationalError/MdcKeys/HeaderSanitizer/ResponseMetaFactory/RequestLoggingFilter 실재 확인. ProblemDetail 거부는 ArchUnit CleanArchitectureTest(L355) + application.yml problemdetails.enabled:false + ProblemDetailDisabledConfigTest로 build-time 강제. G6(Retry-After 헤더 발행/span ERROR)는 RetryAfterAdvisor seam/stub만(owner branch 위임). tenant_id MDC 키는 아직 미정의(조건부). stale 잔재(com.example.blog/sample-ticket) 없음 — sample 모듈 sample-portfolio. → branch status: verified. governing project docs 2건 + concept docs 2건 동기화 완료.

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목:
    • locally-verified 항목:
    • prod-verified 항목:
  • 추출하지 않을 항목 (planned / documented-only / abandoned):