init: 클린 아키텍처 백엔드
This commit is contained in:
@@ -0,0 +1,918 @@
|
||||
# Registry: Error Codes
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/error-codes.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# Category enum owner: feature-operational-error-observability-foundation
|
||||
# Last updated: 2026-05-22
|
||||
# Note: 이 파일은 Phase B 산출물. Phase C2(ca-tmpl 실 코드)에서 generated Java constants의 source.
|
||||
#
|
||||
# Schema (per row):
|
||||
# code: UPPER_SNAKE_CASE
|
||||
# category: VALIDATION | AUTH | AUTHZ | NOT_FOUND | CONFLICT |
|
||||
# RATE_LIMIT | TRANSIENT_DEPENDENCY | PERMANENT_DEPENDENCY |
|
||||
# DATA_INTEGRITY | INTERNAL
|
||||
# http_status: int (async-only failures use 500 placeholder)
|
||||
# retryable: bool
|
||||
# retry_after_seconds: int | null (RATE_LIMIT/TRANSIENT 권고 backoff)
|
||||
# owner_branch: source branch (raw/branch-notes/feature-*.md)
|
||||
# owner_layer: presentation | application | domain | infrastructure | crosscut
|
||||
# client_safe_message: no token / no principal raw / no internal path / no stack trace
|
||||
# log_level: ERROR | WARN | INFO
|
||||
# runbook_link: runbook://area/scenario OR null (client-error만 null 허용)
|
||||
# compatibility_impact: none | additive | behavior-change | breaking
|
||||
# required_test: owning contract test identifier
|
||||
#
|
||||
# Runbook policy (operational-runbook-contract L80):
|
||||
# retryable=false + category ∈ {AUTH, AUTHZ, RATE_LIMIT, INTERNAL,
|
||||
# TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY} ⇒ runbook_link 필수.
|
||||
# VALIDATION/NOT_FOUND/CONFLICT/DATA_INTEGRITY는 client-error로 runbook 면제 가능.
|
||||
# retryable=true 인 모든 row는 runbook_link 필수.
|
||||
|
||||
errors:
|
||||
|
||||
# ============================================================
|
||||
# AUTH (feature-security-operational-baseline / Decision Matrix)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-security-operational-baseline L82 — "token 누락 | 401 | AUTH_TOKEN_MISSING | AUTH"
|
||||
- code: AUTH_TOKEN_MISSING
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication required"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://auth/token-missing"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L83 — "token malformed (parse fail) | 401 | AUTH_TOKEN_MALFORMED | AUTH"
|
||||
- code: AUTH_TOKEN_MALFORMED
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://auth/token-malformed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L84 — "token expired (clock skew tolerance 60s 초과) | 401 | AUTH_TOKEN_EXPIRED | AUTH"
|
||||
- code: AUTH_TOKEN_EXPIRED
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication expired"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://auth/token-expired"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L85 — "invalid signature | 401 | AUTH_TOKEN_INVALID_SIGNATURE | AUTH"
|
||||
- code: AUTH_TOKEN_INVALID_SIGNATURE
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/token-invalid-signature"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L86 — "issuer mismatch | 401 | AUTH_ISSUER_MISMATCH | AUTH"
|
||||
- code: AUTH_ISSUER_MISMATCH
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/issuer-mismatch"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L87 — "audience mismatch | 401 | AUTH_AUDIENCE_MISMATCH | AUTH"
|
||||
- code: AUTH_AUDIENCE_MISMATCH
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/audience-mismatch"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L88 — "unknown kid (JWKS 미캐시) | 401 + Retry-After 5s | AUTH_KID_UNKNOWN | AUTH"
|
||||
- code: AUTH_KID_UNKNOWN
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: true # 2026-06-01: false→true. JWKS 키 회전 중 unknown kid 는 ~5s 후 JWKS refresh 로 해소 가능(transient). retry_after_seconds=5 + client_safe_message "please retry" 와 정합. 키 고정 정책으로 전환 시 false 복귀.
|
||||
retry_after_seconds: 5
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed, please retry"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://auth/kid-unknown"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L89 — "JWKS endpoint outage ... | AUTH_JWKS_UNAVAILABLE | TRANSIENT_DEPENDENCY"
|
||||
- code: AUTH_JWKS_UNAVAILABLE
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 30
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Authentication service temporarily unavailable"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/jwks-unavailable"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-security-operational-baseline L90 — "claim mapping failure ... | 401 | AUTH_CLAIM_MAPPING_FAILED | AUTH"
|
||||
- code: AUTH_CLAIM_MAPPING_FAILED
|
||||
category: AUTH
|
||||
http_status: 401
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Authentication failed"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/claim-mapping-failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# ============================================================
|
||||
# AUTHZ (feature-security-operational-baseline)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-security-operational-baseline L91 — "valid token + 권한 부족 | 403 | AUTHZ_INSUFFICIENT_PERMISSION | AUTHZ"
|
||||
- code: AUTHZ_INSUFFICIENT_PERMISSION
|
||||
category: AUTHZ
|
||||
http_status: 403
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Permission denied"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://authz/insufficient-permission"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:authz-category
|
||||
|
||||
# source: feature-security-operational-baseline L92 — "valid token + tenant cross-access | 403 | AUTHZ_TENANT_MISMATCH | AUTHZ"
|
||||
- code: AUTHZ_TENANT_MISMATCH
|
||||
category: AUTHZ
|
||||
http_status: 403
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Permission denied"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://authz/tenant-mismatch"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:authz-category
|
||||
|
||||
# ============================================================
|
||||
# INTERNAL (feature-security-operational-baseline + container-runtime)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-security-operational-baseline L93 — "public path misconfiguration ... | 500 + P1 alert | INTERNAL_AUTH_MISCONFIGURATION | INTERNAL"
|
||||
- code: INTERNAL_AUTH_MISCONFIGURATION
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
owner_layer: crosscut
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://auth/public-path-misconfiguration"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:auth-category
|
||||
|
||||
# source: feature-container-runtime-contract L113 — "JVM OutOfMemoryError → ExitOnOutOfMemoryError로 137 exit, log에 error.code=JVM_OOM 명시"
|
||||
- code: JVM_OOM
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-container-runtime-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://runtime/jvm-oom"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:container-runtime-oom
|
||||
|
||||
# ============================================================
|
||||
# DB / Persistence (feature-persistence-failure-baseline / SQLState Matrix)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-persistence-failure-baseline L85 — "08* | all | TRANSIENT_DEPENDENCY | DB_UNAVAILABLE | true"
|
||||
- code: DB_UNAVAILABLE
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 5
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Service temporarily unavailable"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://db/unavailable"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L86 — "40001 | Postgres/MySQL | CONFLICT | DB_SERIALIZATION_FAILURE | true"
|
||||
- code: DB_SERIALIZATION_FAILURE
|
||||
category: CONFLICT
|
||||
http_status: 409
|
||||
retryable: true
|
||||
retry_after_seconds: 1
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request conflicted with another transaction, please retry"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://db/serialization-failure"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L87 — "40P01 | Postgres | CONFLICT | DB_DEADLOCK | true (backoff)"
|
||||
- code: DB_DEADLOCK
|
||||
category: CONFLICT
|
||||
http_status: 409
|
||||
retryable: true
|
||||
retry_after_seconds: 1
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request conflicted, please retry"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://db/deadlock"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L88 — "23502 | Postgres | DATA_INTEGRITY | DB_NULL_VIOLATION | false"
|
||||
- code: DB_NULL_VIOLATION
|
||||
category: DATA_INTEGRITY
|
||||
http_status: 409
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request violates a required field constraint"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L89 — "23503 | Postgres | DATA_INTEGRITY | DB_FK_VIOLATION | false"
|
||||
- code: DB_FK_VIOLATION
|
||||
category: DATA_INTEGRITY
|
||||
http_status: 409
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request references missing resource"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L90 — "23505 | Postgres | CONFLICT | DB_UNIQUE_VIOLATION | false (business mapping)"
|
||||
- code: DB_UNIQUE_VIOLATION
|
||||
category: CONFLICT
|
||||
http_status: 409
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Resource already exists"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L91 — "23514 | Postgres | DATA_INTEGRITY | DB_CHECK_VIOLATION | false"
|
||||
- code: DB_CHECK_VIOLATION
|
||||
category: DATA_INTEGRITY
|
||||
http_status: 409
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request violates a value constraint"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L92 — "25P03 | Postgres | TRANSIENT_DEPENDENCY | DB_IDLE_IN_TX_TIMEOUT | true"
|
||||
- code: DB_IDLE_IN_TX_TIMEOUT
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 2
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Service temporarily unavailable"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://db/idle-in-tx-timeout"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# source: feature-persistence-failure-baseline L93 — "57014 | Postgres | TRANSIENT_DEPENDENCY | DB_QUERY_CANCELED | false"
|
||||
- code: DB_QUERY_CANCELED
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Request was canceled, please retry later"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://db/query-canceled"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:persistence-mapping
|
||||
|
||||
# ============================================================
|
||||
# Rate limit / Idempotency (feature-rate-limit-idempotency-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract — rate limit response/log 기준 / Retry-After header 기준 (scope L29, L33)
|
||||
- code: RATE_LIMIT_EXCEEDED
|
||||
category: RATE_LIMIT
|
||||
http_status: 429
|
||||
retryable: true
|
||||
retry_after_seconds: 1
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Too many requests, please retry after the indicated interval"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://rate-limit/exceeded"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:rate-limit
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract L71 — "200ms 초과 시 409 IDEMPOTENT_IN_FLIGHT (retryable=false, client는 polling)"
|
||||
- code: IDEMPOTENT_IN_FLIGHT
|
||||
category: CONFLICT
|
||||
http_status: 409
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
owner_layer: application
|
||||
client_safe_message: "A previous identical request is still being processed, please poll for result"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:idempotency
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract L72 — "fingerprint mismatch (same key + different body) = 422 IDEMPOTENT_REQUEST_MISMATCH"
|
||||
- code: IDEMPOTENT_REQUEST_MISMATCH
|
||||
category: VALIDATION
|
||||
http_status: 422
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
owner_layer: application
|
||||
client_safe_message: "Idempotency key reused with different request body"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:idempotency
|
||||
|
||||
# ============================================================
|
||||
# File / Resource (feature-file-resource-handling-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-file-resource-handling-contract L69 — "spring.servlet.multipart.max-file-size 10MB ... Spring 단의 enforcement가 실패 시 envelope 응답 보장" / 테스트 계약 "oversized upload가 generic 500으로 처리되면 실패"
|
||||
- code: UPLOAD_SIZE_EXCEEDED
|
||||
category: VALIDATION
|
||||
http_status: 413
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-file-resource-handling-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Uploaded file exceeds maximum size"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:file-upload
|
||||
|
||||
# source: feature-file-resource-handling-contract L72 — "allowed content-type allowlist starting set ..."
|
||||
- code: UPLOAD_CONTENT_TYPE_REJECTED
|
||||
category: VALIDATION
|
||||
http_status: 415
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-file-resource-handling-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Uploaded content type is not allowed"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:file-upload
|
||||
|
||||
# source: feature-file-resource-handling-contract — Decisionized Work Items "path traversal | normalized storage key only ... | traversal test"
|
||||
- code: PATH_TRAVERSAL_DETECTED
|
||||
category: VALIDATION
|
||||
http_status: 400
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-file-resource-handling-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Invalid file path"
|
||||
log_level: ERROR
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:file-upload
|
||||
|
||||
# source: feature-file-resource-handling-contract L73 — "streaming download backpressure = response timeout 60s, max stream 100MB. 초과 시 truncate + ERROR log"
|
||||
- code: DOWNLOAD_STREAMING_FAILURE
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 5
|
||||
owner_branch: feature-file-resource-handling-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Download failed, please retry"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://file/download-streaming-failure"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:file-download
|
||||
|
||||
# ============================================================
|
||||
# API contract transport-standard codes (feature-api-contract-baseline)
|
||||
# ============================================================
|
||||
# NOTE: feature-api-contract-baseline owns the transport-shape failure
|
||||
# classification (D8 413/414, D9 406/415, D12 405, D15 412). These rows mirror
|
||||
# dev.caskeleton.shared.error.OperationalError; the D11 status-mapping
|
||||
# consistency test (owner: this branch, producer) fails the build when a code's
|
||||
# registry http_status and the enum httpStatus() drift apart.
|
||||
|
||||
# source: feature-api-contract-baseline.md D12 — "405 Method Not Allowed + Allow header 의무"
|
||||
- code: METHOD_NOT_ALLOWED
|
||||
category: VALIDATION
|
||||
http_status: 405
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "HTTP method not allowed for this resource"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# source: feature-api-contract-baseline.md D9 — "406 Not Acceptable = 응답 표현 협상 실패"
|
||||
- code: NOT_ACCEPTABLE
|
||||
category: VALIDATION
|
||||
http_status: 406
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "No acceptable representation for the requested Accept header"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# source: feature-api-contract-baseline.md D15 — "If-Match mismatch 시 412 Precondition Failed"
|
||||
- code: PRECONDITION_FAILED
|
||||
category: CONFLICT
|
||||
http_status: 412
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Resource was modified by another request; refetch and retry"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# source: feature-api-contract-baseline.md D8 — "request size limit 실패 분류 (413)"
|
||||
- code: PAYLOAD_TOO_LARGE
|
||||
category: VALIDATION
|
||||
http_status: 413
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Request payload is too large"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# source: feature-api-contract-baseline.md D8 형제 — "URI 길이 실패 분류 (414)"
|
||||
# NOTE: enforcement is Tomcat/gateway-owned (rejected before Spring dispatch);
|
||||
# this row + code exist for status-mapping consistency. End-to-end 414 contract
|
||||
# test is `planned` (gateway/Tomcat maxHttpHeaderSize 8KB boundary).
|
||||
- code: URI_TOO_LONG
|
||||
category: VALIDATION
|
||||
http_status: 414
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Request URI is too long"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# source: feature-api-contract-baseline.md D9 — "415 Unsupported Media Type = 요청 본문 format 미지원"
|
||||
- code: UNSUPPORTED_MEDIA_TYPE
|
||||
category: VALIDATION
|
||||
http_status: 415
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Request Content-Type is not supported"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:api-contract-status-mapping
|
||||
|
||||
# ============================================================
|
||||
# Tenant (feature-tenant-context-policy)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-tenant-context-policy L71 — "tenant 미지원 모드에서 X-Tenant-Id 헤더 수신 시 400 TENANT_NOT_SUPPORTED (filter 단계)"
|
||||
- code: TENANT_NOT_SUPPORTED
|
||||
category: VALIDATION
|
||||
http_status: 400
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-tenant-context-policy
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Tenant context is not supported by this deployment"
|
||||
log_level: WARN
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:tenant-policy
|
||||
|
||||
# ============================================================
|
||||
# Validation / Business rule (feature-business-rule-validation-contract)
|
||||
# ============================================================
|
||||
# NOTE: business-rule-validation branch는 mapping 규칙 SSOT (syntax→VALIDATION,
|
||||
# policy→AUTHZ/CONFLICT, invariant→CONFLICT/VALIDATION, persistence→PERSISTENCE/CONFLICT)
|
||||
# 이며 구체 code는 example로 VALIDATION_EMAIL_FORMAT만 등장
|
||||
# (feature-operational-error-observability-foundation L110). 실제 도메인별 code는
|
||||
# Phase D(도메인 feature 적용) 시 본 registry에 추가.
|
||||
|
||||
# source: feature-operational-error-observability-foundation L110 — "code: VALIDATION_EMAIL_FORMAT, // registry-registered code" (validation field error JSON shape example)
|
||||
- code: VALIDATION_EMAIL_FORMAT
|
||||
category: VALIDATION
|
||||
http_status: 400
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Invalid email format"
|
||||
log_level: INFO
|
||||
runbook_link: null
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:validation-envelope
|
||||
|
||||
# ============================================================
|
||||
# Cache (feature-cache-consistency-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-cache-consistency-contract — Decisionized Work Items "Redis unavailable | degrade only if declared | fail-fast for required cache | generic INTERNAL | unavailable mapping" / 테스트 "Redis unavailable이 degrade 가능 여부 없이 INTERNAL로 처리되면 실패"
|
||||
- code: CACHE_UNAVAILABLE
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 2
|
||||
owner_branch: feature-cache-consistency-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Service temporarily unavailable"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://cache/unavailable"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:cache-consistency
|
||||
|
||||
# source: feature-cache-consistency-contract L70 — "stampede 방지 default = single-instance Caffeine local lock, multi-instance HPA 시 Redisson RLock distributed mutex" / 테스트 "동일 key에 대해 동시 cache miss 시 backend 호출이 1회로 제한되는지 verify (stampede). 미충족 시 실패"
|
||||
- code: CACHE_STAMPEDE_LOCK_TIMEOUT
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 1
|
||||
owner_branch: feature-cache-consistency-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Service temporarily unavailable"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://cache/stampede-lock-timeout"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:cache-consistency
|
||||
|
||||
# ============================================================
|
||||
# Outbound HTTP (feature-outbound-http-client-baseline)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-outbound-http-client-baseline L70 — "outbound HTTP timeout default = connect 2s / read 5s / global call 10s" + scope "timeout/connect/DNS failure 분류" / 테스트 "upstream timeout은 retryable dependency failure로 분류되어야 함"
|
||||
- code: DEPENDENCY_TIMEOUT
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 504
|
||||
retryable: true
|
||||
retry_after_seconds: 2
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service did not respond in time, please retry"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://dependency/timeout"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# source: feature-outbound-http-client-baseline — scope "timeout/connect/DNS failure 분류" + L70 connect=2s timeout
|
||||
- code: DEPENDENCY_CONNECT_FAILED
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 2
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service unreachable, please retry"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://dependency/connect-failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# source: feature-outbound-http-client-baseline — scope "timeout/connect/DNS failure 분류"
|
||||
- code: DEPENDENCY_DNS_FAILED
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 5
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service unreachable, please retry"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://dependency/dns-failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# source: feature-outbound-http-client-baseline — scope "upstream 4xx/5xx 분류" / 테스트 "401/403은 credential/scope/config 문제로 분류되어야 함"
|
||||
- code: DEPENDENCY_4XX_CLIENT
|
||||
category: PERMANENT_DEPENDENCY
|
||||
http_status: 502
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service rejected the request"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://dependency/4xx-client"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# source: feature-outbound-http-client-baseline — scope "upstream 4xx/5xx 분류"
|
||||
- code: DEPENDENCY_5XX_SERVER
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 502
|
||||
retryable: true
|
||||
retry_after_seconds: 2
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service error, please retry"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://dependency/5xx-server"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# source: feature-outbound-http-client-baseline L69 — "circuit breaker metric은 dependency.name, dependency.type, outcome까지만 tag로 허용" + Decisionized "circuit breaker | Resilience4j optional env"
|
||||
- code: DEPENDENCY_CIRCUIT_OPEN
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 10
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Upstream service temporarily unavailable, please retry later"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://dependency/circuit-open"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbound-http
|
||||
|
||||
# ============================================================
|
||||
# Outbox (feature-domain-event-outbox-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-domain-event-outbox-contract L67 — "outbox row status enum = PENDING / IN_FLIGHT / PUBLISHED / FAILED / DEAD" + scope "publish 실패 분류" / 판정 "publish 실패가 retry/DLQ/log/runbook 기준 없이 삼켜지면 실패"
|
||||
- code: OUTBOX_PUBLISH_FAILED
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 500
|
||||
retryable: true
|
||||
retry_after_seconds: 30
|
||||
owner_branch: feature-domain-event-outbox-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://outbox/publish-failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbox-publish
|
||||
|
||||
# source: feature-domain-event-outbox-contract L67 — outbox status enum "DEAD" / Outbox Defaults "DLQ | background-job branch owner"
|
||||
- code: OUTBOX_DEAD_LETTER
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-domain-event-outbox-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://outbox/dead-letter"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:outbox-dlq
|
||||
|
||||
# ============================================================
|
||||
# Background job / Async (feature-background-job-async-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-background-job-async-contract — Decisionized "saturation | bounded executor + rejection log" / L72 "saturation policy default = AbortPolicy" / 테스트 "executor rejection이 structured log 없이 발생하면 실패"
|
||||
- code: JOB_EXECUTOR_REJECTED
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 503
|
||||
retryable: true
|
||||
retry_after_seconds: 5
|
||||
owner_branch: feature-background-job-async-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Service temporarily unavailable"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://job/executor-rejected"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:async-saturation
|
||||
|
||||
# source: feature-background-job-async-contract L69 — "기본 backoff는 exponential backoff with jitter, max attempts 3, DLQ after exhausted attempts" + scope "shutdown 중 job 처리 기준" / L73 graceful shutdown ≤19s
|
||||
- code: JOB_TIMEOUT
|
||||
category: TRANSIENT_DEPENDENCY
|
||||
http_status: 500
|
||||
retryable: true
|
||||
retry_after_seconds: 10
|
||||
owner_branch: feature-background-job-async-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://job/timeout"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:async-timeout
|
||||
|
||||
# source: feature-background-job-async-contract L69 — "DLQ after exhausted attempts" + Decisionized "retry/DLQ | exp backoff jitter, max 3, DLQ exhausted | ... | infinite retry"
|
||||
- code: JOB_DEAD_LETTER
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-background-job-async-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://job/dead-letter"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:async-dlq
|
||||
|
||||
# ============================================================
|
||||
# Distributed Lock (feature-distributed-lock-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-distributed-lock-contract D7 — "lock 획득 실패/timeout 의 error code =
|
||||
# LOCK_ACQUISITION_TIMEOUT (category CONFLICT, retryable true, client_safe true) + metric
|
||||
# lock.acquisition" / D5 — "try-lock + 유한 waitTime + lease(TTL) 필수, 무한 blocking 금지".
|
||||
# category CONFLICT 는 기존 enum 재사용; retryable=true — 락 보유자가 임계 구역을 빠져나오면
|
||||
# 동일 요청 재시도로 해소된다(transient contention). DB_DEADLOCK / DB_SERIALIZATION_FAILURE 와
|
||||
# 같은 retryable CONFLICT 계열(409). 본 코드는 distributedLockProvider 획득 timeout 전용이며
|
||||
# cache stampede lock 의 CACHE_STAMPEDE_LOCK_TIMEOUT(cache-consistency, TRANSIENT_DEPENDENCY 503)
|
||||
# 과 의미가 구분된다 — 후자는 캐시 백엔드 의존성 timeout, 전자는 분산 상호배제 contention.
|
||||
- code: LOCK_ACQUISITION_TIMEOUT
|
||||
category: CONFLICT
|
||||
http_status: 409
|
||||
retryable: true
|
||||
retry_after_seconds: 1
|
||||
owner_branch: feature-distributed-lock-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Resource is busy, please retry"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://lock/acquisition-timeout"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:lock-acquisition-timeout
|
||||
|
||||
# ============================================================
|
||||
# Migration / Startup (feature-migration-startup-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-migration-startup-contract L71 — "startup exit code 표준 = ... migration 실패=70 ..." + Decisionized "startup failure log | structured log with startup.phase, error.code, error.category"
|
||||
- code: MIGRATION_FAILED
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-migration-startup-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://migration/failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:migration-startup
|
||||
|
||||
# source: feature-migration-startup-contract L71 — "startup exit code 표준 = env 누락/malformed=78 ..." / 테스트 "required env 누락 시 startup이 성공하면 실패"
|
||||
- code: STARTUP_VALIDATION_FAILED
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-migration-startup-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://startup/validation-failed"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:migration-startup
|
||||
|
||||
# source: feature-migration-startup-contract L71 — "startup exit code 표준 = ... required adapter disabled=72" / 테스트 "disabled required adapter로 app이 뜨면 실패"
|
||||
- code: REQUIRED_ADAPTER_DISABLED
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-migration-startup-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://startup/required-adapter-disabled"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:migration-startup
|
||||
|
||||
# source: feature-integration-adapter-templates §구현 가이드 §4 (Layer 3) + §Audit A2.
|
||||
# Runtime-lifecycle fail-fast for an invoke against a DISABLED optional adapter
|
||||
# (Kafka/Redis/Slack/Google Email). Deliberately distinct from the startup-lifecycle
|
||||
# REQUIRED_ADAPTER_DISABLED above (exit 72): a runtime invoke ≠ a startup validation,
|
||||
# so reusing the startup code would conflate two lifecycles (A2 resolution — new
|
||||
# runtime code owned by this branch). retryable=false: the adapter stays disabled
|
||||
# until redeploy, so retrying the same call never clears it.
|
||||
- code: ADAPTER_DISABLED
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-integration-adapter-templates
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://adapter/adapter-disabled"
|
||||
compatibility_impact: none
|
||||
required_test: adapter-contract:adapter-disabled-runtime-call
|
||||
|
||||
# source: feature-migration-startup-contract L71 — "startup exit code 표준 = ... profile mismatch=71" / 테스트 "prod profile에서 local-only 설정이 켜지면 실패"
|
||||
- code: PROFILE_MISMATCH
|
||||
category: INTERNAL
|
||||
http_status: 500
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-migration-startup-contract
|
||||
owner_layer: infrastructure
|
||||
client_safe_message: "Internal server error"
|
||||
log_level: ERROR
|
||||
runbook_link: "runbook://startup/profile-mismatch"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:migration-startup
|
||||
|
||||
# ============================================================
|
||||
# Management / Actuator (feature-management-actuator-security-contract)
|
||||
# ============================================================
|
||||
|
||||
# source: feature-management-actuator-security-contract — Exposure Policy "env/configprops | forbidden" "heapdump/threaddump | forbidden unless break-glass runbook" "shutdown | forbidden" / 테스트 "prod에서 env/configprops endpoint가 노출되면 실패"
|
||||
- code: ACTUATOR_FORBIDDEN
|
||||
category: AUTHZ
|
||||
http_status: 403
|
||||
retryable: false
|
||||
retry_after_seconds: null
|
||||
owner_branch: feature-management-actuator-security-contract
|
||||
owner_layer: presentation
|
||||
client_safe_message: "Permission denied"
|
||||
log_level: WARN
|
||||
runbook_link: "runbook://management/actuator-forbidden"
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:management-actuator
|
||||
Reference in New Issue
Block a user