init: 클린 아키텍처 백엔드
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# Registry: Repository Access Capabilities
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/capabilities.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# Owner branch: feature-repository-access-permission-contract
|
||||
# Last updated: 2026-06-05
|
||||
#
|
||||
# Notes
|
||||
# - capability는 사용자 권한이 아니라 application use case가 infrastructure capability를
|
||||
# 사용할 수 있는지에 대한 계약 (feature-repository-access-permission-contract).
|
||||
# - enforcement default = ArchUnit annotation-based rule. compile-time annotation processor는
|
||||
# alternative. runtime AOP는 forbidden.
|
||||
# - capability 제거는 항상 breaking change. 추가는 additive (registry row 동반 시).
|
||||
# - annotation 표기 (as-built, F1/F2 reconciled 2026-06-05): 코드 SSOT는 단일
|
||||
# `@UseCaseCapability` (TYPE target, typed attribute). 노트 D2/D11의 flat
|
||||
# `@UseCaseRepositoryAccess(Capability[])` 모델은 superseded. 7 capability ↔ as-built 매핑:
|
||||
# READ_REPOSITORY/WRITE_REPOSITORY → repositoryAccess, TRANSACTION_REQUIRED → transactionMode,
|
||||
# EXTERNAL_OUTBOUND_ALLOWED → externalOutboundAllowed, SENSITIVE_READ → sensitiveRead,
|
||||
# BULK_WRITE → bulkWrite, CROSS_TENANT_ADMIN → crossTenantAdmin.
|
||||
# 각 row의 annotation: 필드는 아래에서 as-built 표기로 정합됨.
|
||||
|
||||
capabilities:
|
||||
|
||||
# source: feature-repository-access-permission-contract — 판정 기준 "Required capability: READ_REPOSITORY"
|
||||
# source: feature-application-port-usecase-contract — "read-only query use case는 readOnly 와 READ_REPOSITORY capability만 선언 가능"
|
||||
- name: READ_REPOSITORY
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(repositoryAccess = READ_REPOSITORY)"
|
||||
semantics: "use case가 read-only repository operation을 호출하는 것을 허용. query use case의 기본 capability. write/sensitive/bulk 작업은 별도 capability 선언이 없으면 forbidden."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:read-repository-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — 판정 기준 "Required capability: WRITE_REPOSITORY"
|
||||
# source: feature-application-port-usecase-contract — "write use case는 transactionMode, idempotency, repositoryAccess를 명시해야 함"
|
||||
- name: WRITE_REPOSITORY
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(repositoryAccess = WRITE_REPOSITORY)"
|
||||
semantics: "use case가 mutating repository operation(insert/update/delete)을 호출하는 것을 허용. 단일/소량 write 기준이며 batch size > 100은 BULK_WRITE 별도 선언 필요. read-only use case에서 이 capability 없이 write repository 접근하면 fail."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:write-repository-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — decisions 2026-05-22
|
||||
# "SENSITIVE_READ marker = registry-managed metadata table (entity FQN + field name 단위)"
|
||||
- name: SENSITIVE_READ
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(sensitiveRead = true)"
|
||||
semantics: "PII/credential 등 sensitive field를 읽는 use case가 선언해야 하는 capability. marker는 registry-managed metadata table(entity FQN + field name 단위)에서 lookup. domain annotation 또는 JPA entity annotation 형태는 forbidden(domain에 framework 의존 회피). pseudonymized data read는 documented 시에만 예외 허용."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:sensitive-read-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — decisions 2026-05-22
|
||||
# "BULK_WRITE threshold = N > 100 또는 batch size > 100. 미만은 일반 WRITE_REPOSITORY로 충분"
|
||||
- name: BULK_WRITE
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(bulkWrite = true)"
|
||||
semantics: "단일 transaction 내 N > 100 또는 batch size > 100 mutating operation을 수행하는 use case가 선언해야 하는 capability. 이 미만이면 일반 WRITE_REPOSITORY로 충분. lock 점유 시간, pool 영향, retry 비용이 큰 작업을 명시화."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: WRITE_REPOSITORY
|
||||
threshold: 100
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:bulk-write-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — decisions 2026-05-22
|
||||
# "TRANSACTION_REQUIRED는 application-port branch의 TransactionPort contract와 연결되어야 하며 Spring @Transactional 직접 import로 충족하지 않음"
|
||||
# source: feature-application-port-usecase-contract — TransactionPort Contract
|
||||
- name: TRANSACTION_REQUIRED
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(transactionMode = WRITE | READ_ONLY | REQUIRES_NEW)"
|
||||
semantics: "use case가 TransactionPort(또는 TransactionalUseCaseRunner)를 통해 transactional boundary를 갖는 것을 강제. Spring @Transactional의 application package 직접 import는 forbidden. infrastructure가 Spring transaction implementation을 제공하고 application은 port만 호출."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:transaction-required-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — decisions 2026-05-22
|
||||
# "EXTERNAL_OUTBOUND_ALLOWED 분류 = outbox row INSERT는 in-process(불요), polling publisher의 broker publish는 outbound(필요)"
|
||||
# source: feature-application-port-usecase-contract — "outbound adapter 호출 use case에 EXTERNAL_OUTBOUND_ALLOWED가 없으면 실패"
|
||||
- name: EXTERNAL_OUTBOUND_ALLOWED
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(externalOutboundAllowed = true)"
|
||||
semantics: "use case가 외부 HTTP/message broker로 outbound 호출을 발생시키는 것을 허용. outbox claim 분류: outbox row INSERT는 in-process이므로 본 capability 불요. polling publisher의 broker publish는 outbound이므로 필요. domain event without transport detail은 outbound 호출이 아니므로 별도 분류."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:external-outbound-capability
|
||||
|
||||
# source: feature-repository-access-permission-contract — decisions 2026-05-22
|
||||
# "CROSS_TENANT_ADMIN capability를 capability vocabulary에 추가 (tenant branch feature-tenant-context-policy와 cross-link)"
|
||||
- name: CROSS_TENANT_ADMIN
|
||||
scope: use_case_method
|
||||
enforcement: archunit
|
||||
annotation: "@UseCaseCapability(crossTenantAdmin = true)"
|
||||
semantics: "tenant 경계를 넘어 데이터에 접근/변경하는 admin use case가 선언해야 하는 capability. tenant-context-policy의 cross-tenant 정책과 cross-link되어야 하며, 단일 tenant 범위 use case에서 이 capability를 선언하면 review에서 reject. SENSITIVE_READ가 동반될 가능성이 높지만 자동 결합은 아님."
|
||||
owner_branch: feature-repository-access-permission-contract
|
||||
bound_to_capability: null
|
||||
threshold: null
|
||||
compatibility_impact: breaking
|
||||
required_test: architecture-enforcement:cross-tenant-admin-capability
|
||||
|
||||
# Row count verification
|
||||
# - feature-repository-access-permission-contract 판정 기준 "Required capability" 표에 명시된 7개:
|
||||
# READ_REPOSITORY, WRITE_REPOSITORY, SENSITIVE_READ, BULK_WRITE, TRANSACTION_REQUIRED,
|
||||
# EXTERNAL_OUTBOUND_ALLOWED, CROSS_TENANT_ADMIN.
|
||||
# - source에 명시되지 않은 capability는 본 registry에 추가하지 않음 (추측 금지).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -0,0 +1,220 @@
|
||||
# Registry: HTTP Headers
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/headers.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# Last updated: 2026-05-22
|
||||
#
|
||||
# Conventions:
|
||||
# - HTTP header name: kebab-case (X-Request-Id, X-Tenant-Id)
|
||||
# - W3C standard headers: lowercase (traceparent, tracestate)
|
||||
# - mdc_key: snake_case (foundation SSOT)
|
||||
# - envelope_meta_field: camelCase (envelope SSOT)
|
||||
|
||||
headers:
|
||||
# source: feature-operational-error-observability-foundation.md L97
|
||||
# "request_id | inbound filter (생성 또는 X-Request-Id 헤더) | response header X-Request-Id"
|
||||
- name: X-Request-Id
|
||||
direction: both
|
||||
type: ulid
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: request_id
|
||||
envelope_meta_field: requestId
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:envelope-headers
|
||||
|
||||
# source: feature-api-contract-baseline.md L67
|
||||
# "X-Api-Version은 실험/compatibility 보조 header이며 path version과 충돌하면 path가 우선"
|
||||
- name: X-Api-Version
|
||||
direction: inbound
|
||||
type: string
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-api-contract-baseline
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:api-versioning
|
||||
|
||||
# source: feature-api-contract-baseline.md L77 / feature-rate-limit-idempotency-contract.md L66-67
|
||||
# "idempotency header 이름은 Idempotency-Key" / "기본 scope는 (authenticatedPrincipal, idempotencyKey, useCaseName)"
|
||||
- name: Idempotency-Key
|
||||
direction: inbound
|
||||
type: string
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:idempotency-replay
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract.md L85 / foundation L85
|
||||
# "RATE_LIMIT | ... | 429 | true (Retry-After 이후)" / "retry-after 기준 없이 429를 반환하면 실패"
|
||||
- name: Retry-After
|
||||
direction: outbound
|
||||
type: duration-seconds
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:rate-limit-headers
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract.md scope L26 (rate limit response/log 기준)
|
||||
# rate-limit 응답 표면 (limit/remaining/reset 3종은 표준 rate-limit signaling)
|
||||
- name: X-RateLimit-Limit
|
||||
direction: outbound
|
||||
type: numeric
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:rate-limit-headers
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract.md scope L26 (rate limit response/log 기준)
|
||||
- name: X-RateLimit-Remaining
|
||||
direction: outbound
|
||||
type: numeric
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:rate-limit-headers
|
||||
|
||||
# source: feature-rate-limit-idempotency-contract.md scope L26 (rate limit response/log 기준)
|
||||
- name: X-RateLimit-Reset
|
||||
direction: outbound
|
||||
type: rfc3339-date
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-rate-limit-idempotency-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:rate-limit-headers
|
||||
|
||||
# source: feature-api-compatibility-deprecation-contract.md L87
|
||||
# "deprecation marker | OpenAPI deprecated: true + branch note | response header optional"
|
||||
- name: Deprecation
|
||||
direction: outbound
|
||||
type: rfc3339-date
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-api-compatibility-deprecation-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:deprecation-marker
|
||||
|
||||
# source: feature-api-compatibility-deprecation-contract.md L87
|
||||
# "deprecation marker | OpenAPI deprecated: true + branch note | response header optional" (RFC 8594 Sunset)
|
||||
- name: Sunset
|
||||
direction: outbound
|
||||
type: rfc3339-date
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-api-compatibility-deprecation-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:deprecation-marker
|
||||
|
||||
# source: feature-distributed-tracing-contract.md L64, L85
|
||||
# "propagation header는 W3C traceparent default" / "HTTP | traceparent, tracestate (W3C)"
|
||||
- name: traceparent
|
||||
direction: both
|
||||
type: string
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: trace_id
|
||||
envelope_meta_field: traceId
|
||||
owner_branch: feature-distributed-tracing-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:trace-propagation
|
||||
|
||||
# source: feature-distributed-tracing-contract.md L66, L85
|
||||
# "propagation format = W3C traceparent + tracestate only. B3 propagation은 forbidden"
|
||||
- name: tracestate
|
||||
direction: both
|
||||
type: comma-separated
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-distributed-tracing-contract
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:trace-propagation
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L100
|
||||
# "correlation_id | inbound header X-Correlation-Id 또는 생성 | HTTP X-Correlation-Id, message header correlation_id"
|
||||
- name: X-Correlation-Id
|
||||
direction: both
|
||||
type: ulid
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: correlation_id
|
||||
envelope_meta_field: correlationId
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:envelope-headers
|
||||
|
||||
# source: feature-tenant-context-policy.md L69, L101 (foundation)
|
||||
# "tenant resolution 우선순위 = ... (2) 명시적 X-Tenant-Id 헤더 (admin/internal API only)" /
|
||||
# "tenant_id | tenant context (활성 시) | downstream HTTP X-Tenant-Id (with allowlist)"
|
||||
- name: X-Tenant-Id
|
||||
direction: both
|
||||
type: ulid
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: tenant_id
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-tenant-context-policy
|
||||
case_style: kebab
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:tenant-header-policy
|
||||
|
||||
# source: feature-security-operational-baseline.md L66
|
||||
# "JWT Resource Server를 baseline security model로 둠" (Bearer token via Authorization header)
|
||||
- name: Authorization
|
||||
direction: inbound
|
||||
type: bearer-token
|
||||
required: false
|
||||
generated_if_missing: false
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:jwt-resource-server
|
||||
|
||||
# source: feature-security-operational-baseline.md L83-90 (AuthN/AuthZ Decision Matrix)
|
||||
# 401 응답 시 WWW-Authenticate (Bearer realm/error) — Spring Security JWT Resource Server 표준 challenge header
|
||||
- name: WWW-Authenticate
|
||||
direction: outbound
|
||||
type: string
|
||||
required: false
|
||||
generated_if_missing: true
|
||||
mdc_key: null
|
||||
envelope_meta_field: null
|
||||
owner_branch: feature-security-operational-baseline
|
||||
case_style: kebab
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:jwt-resource-server
|
||||
@@ -0,0 +1,294 @@
|
||||
# Registry: MDC / Log Keys
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/mdc-keys.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# MDC SSOT: feature-operational-error-observability-foundation
|
||||
# Last updated: 2026-05-22
|
||||
#
|
||||
# Conventions:
|
||||
# - MDC key naming: snake_case (foundation L93 "snake_case 강제. camelCase / dot.case 금지.")
|
||||
# - cardinality_safe_for_metric=true 인 key만 metric tag로 사용 가능
|
||||
# - foundation L93-102 표 "MDC Key Standard (final)" 6개가 core SSOT
|
||||
|
||||
mdc_keys:
|
||||
# source: feature-operational-error-observability-foundation.md L97
|
||||
# "request_id | inbound filter (생성 또는 X-Request-Id 헤더) | response header X-Request-Id"
|
||||
- key: request_id
|
||||
type: ulid
|
||||
source: inbound_filter
|
||||
required_in: [request, dependency, security, application]
|
||||
http_header_mapping: X-Request-Id
|
||||
envelope_field: meta.requestId
|
||||
propagation: [http, async, message]
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-mdc-keys
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L98
|
||||
# "trace_id | Micrometer Tracing | W3C traceparent header"
|
||||
- key: trace_id
|
||||
type: string
|
||||
source: observation_context
|
||||
required_in: [request, dependency, application]
|
||||
http_header_mapping: traceparent
|
||||
envelope_field: meta.traceId
|
||||
propagation: [http, async, message]
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-mdc-keys
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L99
|
||||
# "span_id | Micrometer Tracing | W3C traceparent"
|
||||
# NOTE: background-job-async-contract L71 "span_id는 Micrometer Observation context에서 자동 전파(MDC explicit copy 불필요)"
|
||||
- key: span_id
|
||||
type: string
|
||||
source: observation_context
|
||||
required_in: [request, dependency]
|
||||
http_header_mapping: traceparent
|
||||
envelope_field: null
|
||||
propagation: [http, async]
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-mdc-keys
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L100
|
||||
# "correlation_id | inbound header X-Correlation-Id 또는 생성 | HTTP X-Correlation-Id, message header correlation_id"
|
||||
- key: correlation_id
|
||||
type: ulid
|
||||
source: inbound_filter
|
||||
required_in: [request, dependency, application]
|
||||
http_header_mapping: X-Correlation-Id
|
||||
envelope_field: meta.correlationId
|
||||
propagation: [http, async, message]
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-mdc-keys
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L101 + feature-tenant-context-policy.md L70
|
||||
# "tenant_id | tenant context (활성 시) | downstream HTTP X-Tenant-Id (with allowlist)" /
|
||||
# "tenant ID format = opaque ULID (26 chars Crockford base32)"
|
||||
# NOTE: tenant L73 "tenant_id ULID 원본은 metric tag에 직접 사용 금지"
|
||||
- key: tenant_id
|
||||
type: ulid
|
||||
source: security_context
|
||||
required_in: [request, dependency, security, audit]
|
||||
http_header_mapping: X-Tenant-Id
|
||||
envelope_field: null
|
||||
propagation: [http, async, message]
|
||||
owner_branch: feature-tenant-context-policy
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:tenant-leakage
|
||||
|
||||
# source: feature-operational-error-observability-foundation.md L102
|
||||
# "user_principal | security context (pseudonymized only) | log only, headers forbidden"
|
||||
- key: user_principal
|
||||
type: string
|
||||
source: security_context
|
||||
required_in: [security, audit]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-operational-error-observability-foundation
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-mdc-keys
|
||||
|
||||
# ── log type extensions (log-management-contract L101-109 "Log Type별 필수 필드") ──
|
||||
|
||||
# source: feature-log-management-contract.md L105 "request | request_id, trace_id, method, uri_template, status, duration_ms"
|
||||
# NOTE: application-port-usecase-contract / business 측 operation 식별자 (uri_template과 별도 application-set)
|
||||
- key: operation
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [application, dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L105 "request | request_id, trace_id, method, uri_template, status, duration_ms"
|
||||
- key: method
|
||||
type: string
|
||||
source: inbound_filter
|
||||
required_in: [request]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L105 "request | request_id, trace_id, method, uri_template, status, duration_ms"
|
||||
# NOTE: metrics L86 "status_code | 7 (1xx-5xx + ok/other)" — bounded
|
||||
- key: status
|
||||
type: numeric
|
||||
source: inbound_filter
|
||||
required_in: [request]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L105-106 "request | ... duration_ms" / "dependency | ... duration_ms"
|
||||
- key: duration_ms
|
||||
type: numeric
|
||||
source: application_set
|
||||
required_in: [request, dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L106 "dependency | dependency_name, dependency_type, duration_ms, outcome, error_code"
|
||||
# NOTE: metrics L88 "dependency_name | 50" — bounded
|
||||
- key: dependency_name
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L106 "dependency | dependency_name, dependency_type, ..."
|
||||
- key: dependency_type
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L106 + metrics L91 "outcome (resilience4j) | 5 (SUCCESS/FAILURE/CIRCUIT_OPEN/TIMEOUT/REJECTED)"
|
||||
- key: outcome
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L106 "dependency | ... error_code (실패 시)"
|
||||
# NOTE: metrics L89 "error_code | 100 — error registry row 상한과 정합" — bounded
|
||||
- key: error_code
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [dependency]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L107
|
||||
# "security | event_type, user_principal (pseudonymized), source_ip (anonymized — last octet zeroed)"
|
||||
- key: event_type
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [security, audit]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L107 "security | ... source_ip (anonymized — last octet zeroed)"
|
||||
# NOTE: metrics L93 "high-cardinality 금지 tag: ... ip_address"
|
||||
- key: source_ip_anon
|
||||
type: string
|
||||
source: inbound_filter
|
||||
required_in: [security]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L108 "audit | actor, action, target, before_hash, after_hash, occurred_at"
|
||||
- key: actor
|
||||
type: string
|
||||
source: security_context
|
||||
required_in: [audit]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L108 "audit | actor, action, target, ..."
|
||||
- key: action
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [audit]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: true
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
|
||||
# source: feature-log-management-contract.md L108 "audit | actor, action, target, ..."
|
||||
- key: target
|
||||
type: string
|
||||
source: application_set
|
||||
required_in: [audit]
|
||||
http_header_mapping: null
|
||||
envelope_field: null
|
||||
propagation: [none]
|
||||
owner_branch: feature-log-management-contract
|
||||
cardinality_safe_for_metric: false
|
||||
case_style: snake
|
||||
compatibility_impact: none
|
||||
required_test: contract-verification:log-fields
|
||||
@@ -0,0 +1,540 @@
|
||||
# Registry: Metrics
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/metrics.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# Owner branch: feature-metrics-alerting-contract
|
||||
# Last updated: 2026-05-22
|
||||
#
|
||||
# Notes
|
||||
# - Naming: Micrometer dot.case + unit suffix (.seconds | .bytes | .total).
|
||||
# - Tag cardinality bounds are SSOT of feature-metrics-alerting-contract "Cardinality Bounds" table.
|
||||
# - High-cardinality tags forbidden globally: user_id, request_id, raw_url, raw_query,
|
||||
# raw_header_value, ip_address. These MUST NOT appear in any row.
|
||||
# - tenant_id label is bounded mapping table id OR cohort bucket only (ULID raw forbidden).
|
||||
# - error_code tag cardinality_limit follows error-codes.yaml row count (max 100).
|
||||
|
||||
metrics:
|
||||
|
||||
# === HTTP server (inbound) ===
|
||||
# source: feature-metrics-alerting-contract — Metric/Alert Defaults
|
||||
# "HTTP metric | http.server.requests with method/status/uri-template | raw URL or user id tag"
|
||||
- name: http.server.requests
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: method
|
||||
cardinality_limit: 8
|
||||
allowed_values: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, OTHER]
|
||||
- name: status
|
||||
cardinality_limit: 7
|
||||
allowed_values: [1xx, 2xx, 3xx, 4xx, 5xx, ok, other]
|
||||
- name: uri_template
|
||||
cardinality_limit: 200
|
||||
validation: must_be_template_not_raw_uri
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p1: "error_rate > 5% for 5m OR > 10% for 1m"
|
||||
p2: "error_rate > 1% for 10m"
|
||||
p3: "error_rate > 0.1% for 1h"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [method, status, uri_template]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — P1/P2/P3 정량 기준 (HTTP latency p99)
|
||||
# "P1: p99 > 5s 5분 / P2: p99 > 1s 10분 / P3: p99 > 500ms 30분"
|
||||
- name: http.server.requests.latency
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: method
|
||||
cardinality_limit: 8
|
||||
allowed_values: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, OTHER]
|
||||
- name: uri_template
|
||||
cardinality_limit: 200
|
||||
validation: must_be_template_not_raw_uri
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p1: "p99 > 5s for 5m"
|
||||
p2: "p99 > 1s for 10m"
|
||||
p3: "p99 > 500ms for 30m"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [method, uri_template, duration_ms]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === HTTP client (outbound dependency) ===
|
||||
# source: feature-metrics-alerting-contract — Metric/Alert Defaults
|
||||
# "dependency metric | dependency.client.requests with dependency.name/type/outcome | endpoint with secret tag"
|
||||
# source: feature-outbound-http-client-baseline — "circuit breaker metric은 dependency.name, dependency.type, outcome까지만 tag로 허용"
|
||||
- name: dependency.client.requests
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: dependency_name
|
||||
cardinality_limit: 50
|
||||
- name: dependency_type
|
||||
cardinality_limit: 10
|
||||
allowed_values: [http, grpc, db, cache, queue, broker, other]
|
||||
- name: outcome
|
||||
cardinality_limit: 5
|
||||
allowed_values: [SUCCESS, FAILURE, CIRCUIT_OPEN, TIMEOUT, REJECTED]
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p1: "required dep unavailable for 2m"
|
||||
p2: "optional dep degraded for 5m"
|
||||
p3: "spike alert (10x baseline)"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [dependency_name, dependency_type, outcome, duration_ms]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-outbound-http-client-baseline — decisions
|
||||
# "retry/circuit breaker 기본 라이브러리는 Resilience4j"
|
||||
# source: feature-metrics-alerting-contract — "retry/CB minimum: resilience4j.retry.calls{outcome}"
|
||||
- name: resilience4j.retry.calls
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: name
|
||||
cardinality_limit: 50
|
||||
- name: outcome
|
||||
cardinality_limit: 5
|
||||
allowed_values: [SUCCESS, FAILURE, CIRCUIT_OPEN, TIMEOUT, REJECTED]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "retry exhaustion rate > 1% for 10m"
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
log_field_mapping: [dependency_name, outcome, retry_attempt]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — "resilience4j.circuitbreaker.state"
|
||||
- name: resilience4j.circuitbreaker.state
|
||||
type: gauge
|
||||
unit: total
|
||||
tags:
|
||||
- name: name
|
||||
cardinality_limit: 50
|
||||
- name: state
|
||||
cardinality_limit: 6
|
||||
allowed_values: [CLOSED, OPEN, HALF_OPEN, DISABLED, FORCED_OPEN, METRICS_ONLY]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p1: "state == OPEN for required dependency for 2m"
|
||||
p2: "state == OPEN for optional dependency for 5m"
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
log_field_mapping: [dependency_name]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — "resilience4j.circuitbreaker.calls{outcome}"
|
||||
- name: resilience4j.circuitbreaker.calls
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: name
|
||||
cardinality_limit: 50
|
||||
- name: outcome
|
||||
cardinality_limit: 5
|
||||
allowed_values: [SUCCESS, FAILURE, CIRCUIT_OPEN, TIMEOUT, REJECTED]
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p2: "CIRCUIT_OPEN rate > 1% for 10m"
|
||||
owner_branch: feature-outbound-http-client-baseline
|
||||
log_field_mapping: [dependency_name, outcome, duration_ms]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === DB connection pool ===
|
||||
# source: feature-persistence-failure-baseline — Hikari Alert Threshold
|
||||
# "pool wait p99 > 100ms 5분 지속 → P2 / pool exhaustion (active = max) > 1분 → P1"
|
||||
# source: feature-metrics-alerting-contract — "hikaricp.connections.acquire{outcome='timeout'} p99 > 100ms"
|
||||
- name: hikaricp.connections.acquire
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: pool
|
||||
cardinality_limit: 5
|
||||
- name: outcome
|
||||
cardinality_limit: 3
|
||||
allowed_values: [SUCCESS, TIMEOUT, FAILURE]
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p1: "pool exhaustion (active == max) for 1m"
|
||||
p2: "acquire p99 > 100ms for 5m"
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
log_field_mapping: [pool, outcome]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-persistence-failure-baseline — In scope "Hikari metric 노출 기준" + Hikari Alert Threshold
|
||||
- name: hikaricp.connections.usage
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: pool
|
||||
cardinality_limit: 5
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p2: "usage p99 elevated > 10m"
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
log_field_mapping: [pool]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-persistence-failure-baseline — In scope "Hikari metric 노출 기준"
|
||||
- name: hikaricp.connections.active
|
||||
type: gauge
|
||||
unit: total
|
||||
tags:
|
||||
- name: pool
|
||||
cardinality_limit: 5
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p1: "active == max for 1m"
|
||||
owner_branch: feature-persistence-failure-baseline
|
||||
log_field_mapping: [pool]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — Histogram Buckets/Percentile "DB query: same"
|
||||
- name: db.query.duration
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: operation
|
||||
cardinality_limit: 20
|
||||
allowed_values: [select, insert, update, delete, batch, ddl, other]
|
||||
- name: outcome
|
||||
cardinality_limit: 3
|
||||
allowed_values: [SUCCESS, FAILURE, TIMEOUT]
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p2: "p99 > 1s for 10m"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [operation, outcome, duration_ms]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Background job / async executor ===
|
||||
# source: feature-background-job-async-contract — Decisionized Work Items "saturation policy"
|
||||
# "AbortPolicy default (core=10, max=50, queue=200)"
|
||||
- name: executor.saturation
|
||||
type: gauge
|
||||
unit: total
|
||||
tags:
|
||||
- name: executor_name
|
||||
cardinality_limit: 10
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "queue size > 80% capacity for 5m"
|
||||
p1: "rejection rate > 0 for 1m"
|
||||
owner_branch: feature-background-job-async-contract
|
||||
log_field_mapping: [executor_name]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-background-job-async-contract — Decisionized Work Items "saturation | bounded executor + rejection log"
|
||||
- name: executor.rejected.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: executor_name
|
||||
cardinality_limit: 10
|
||||
- name: policy
|
||||
cardinality_limit: 3
|
||||
allowed_values: [AbortPolicy, CallerRunsPolicy, DiscardPolicy]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p1: "rejection_count > 0 for 1m"
|
||||
owner_branch: feature-background-job-async-contract
|
||||
log_field_mapping: [executor_name, policy]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-background-job-async-contract — Decisionized Work Items "retry/DLQ | exp backoff jitter, max 3, DLQ exhausted"
|
||||
- name: job.retry.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: job_name
|
||||
cardinality_limit: 50
|
||||
- name: outcome
|
||||
cardinality_limit: 4
|
||||
allowed_values: [SUCCESS, RETRY, EXHAUSTED, DLQ]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "EXHAUSTED rate > 1% for 10m"
|
||||
owner_branch: feature-background-job-async-contract
|
||||
log_field_mapping: [job_name, outcome, retry_attempt]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-background-job-async-contract — "DLQ after exhausted attempts"
|
||||
- name: job.dlq.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: job_name
|
||||
cardinality_limit: 50
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p1: "DLQ rate sustained > 0 for 5m"
|
||||
owner_branch: feature-background-job-async-contract
|
||||
log_field_mapping: [job_name]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Distributed lock ===
|
||||
# source: feature-distributed-lock-contract D7 — "metric lock.acquisition (tag: outcome =
|
||||
# acquired/timeout/error) — 신규 제안" / D5 — try-lock + 유한 waitTime + lease(TTL). 분산
|
||||
# 상호배제(distributedLockProvider) 획득 시도 결과를 센다. key 는 tag 로 넣지 않는다
|
||||
# (무한 cardinality — 위 전역 금지 규칙). timeout outcome 은 LOCK_ACQUISITION_TIMEOUT 발생과 1:1.
|
||||
- name: lock.acquisition
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: outcome
|
||||
cardinality_limit: 3
|
||||
allowed_values: [acquired, timeout, error]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "timeout rate > 5% for 10m"
|
||||
owner_branch: feature-distributed-lock-contract
|
||||
log_field_mapping: [outcome]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-distributed-lock-contract §Edge / D5 (SI-LOCK-C5) — "lease 만료 후 unlock →
|
||||
# ConcurrentModificationException — 삼킴 금지, 로그+metric 후 정상 흐름 복귀". Counts releases that
|
||||
# found the lease already expired (the JdbcLock row was reclaimed by another instance before
|
||||
# the holder called close()). A sustained nonzero rate means lease TTL is shorter than real
|
||||
# critical-section duration — raise APP/lease TTL or shorten the protected work. Not an
|
||||
# acquisition outcome, hence a separate counter from lock.acquisition.
|
||||
- name: lock.lease.expired
|
||||
type: counter
|
||||
unit: total
|
||||
tags: []
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "lease-expired rate sustained > 0 for 10m"
|
||||
owner_branch: feature-distributed-lock-contract
|
||||
log_field_mapping: []
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Outbox publisher ===
|
||||
# source: feature-domain-event-outbox-contract — Outbox Defaults
|
||||
# "DB outbox table with eventId, aggregateId, eventType, payload, occurredAt, status, attemptCount, nextAttemptAt"
|
||||
- name: outbox.publisher.published.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: event_type
|
||||
cardinality_limit: 50
|
||||
- name: outcome
|
||||
cardinality_limit: 4
|
||||
allowed_values: [PUBLISHED, FAILED, DEAD, IN_FLIGHT]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "FAILED rate > 1% for 10m"
|
||||
owner_branch: feature-domain-event-outbox-contract
|
||||
log_field_mapping: [event_type, outcome, event_id]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-domain-event-outbox-contract — row status enum PENDING/IN_FLIGHT/PUBLISHED/FAILED/DEAD
|
||||
- name: outbox.publisher.lag
|
||||
type: gauge
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: event_type
|
||||
cardinality_limit: 50
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "lag > 60s for 10m"
|
||||
p1: "lag > 300s for 5m"
|
||||
owner_branch: feature-domain-event-outbox-contract
|
||||
log_field_mapping: [event_type]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-domain-event-outbox-contract — row status enum + Outbox Defaults attemptCount
|
||||
- name: outbox.pending.size
|
||||
type: gauge
|
||||
unit: total
|
||||
tags:
|
||||
- name: status
|
||||
cardinality_limit: 5
|
||||
allowed_values: [PENDING, IN_FLIGHT, PUBLISHED, FAILED, DEAD]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "PENDING size growing for 10m"
|
||||
owner_branch: feature-domain-event-outbox-contract
|
||||
log_field_mapping: [status]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Cache ===
|
||||
# source: feature-cache-consistency-contract — Decisionized Work Items "cache pattern | cache-aside default"
|
||||
- name: cache.gets.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: cache_name
|
||||
cardinality_limit: 50
|
||||
- name: result
|
||||
cardinality_limit: 3
|
||||
allowed_values: [hit, miss, error]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p3: "hit_ratio < baseline 0.5x for 1h"
|
||||
owner_branch: feature-cache-consistency-contract
|
||||
log_field_mapping: [cache_name, result]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-cache-consistency-contract — "invalidation = after-commit only", "invalidation 실패가 조용히 무시되면 실패"
|
||||
- name: cache.invalidations.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: cache_name
|
||||
cardinality_limit: 50
|
||||
- name: outcome
|
||||
cardinality_limit: 3
|
||||
allowed_values: [SUCCESS, FAILURE, SKIPPED]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "FAILURE rate > 0 for 5m"
|
||||
owner_branch: feature-cache-consistency-contract
|
||||
log_field_mapping: [cache_name, outcome]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Log appender ===
|
||||
# source: feature-log-management-contract — Sampling Policy (final)
|
||||
# "async appender overflow default: drop oldest INFO/DEBUG with counter metric (log.appender.dropped.total)"
|
||||
- name: log.appender.dropped.total
|
||||
type: counter
|
||||
unit: total
|
||||
tags:
|
||||
- name: appender
|
||||
cardinality_limit: 5
|
||||
- name: level
|
||||
cardinality_limit: 2
|
||||
allowed_values: [INFO, DEBUG]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "dropped > 0 sustained for 10m"
|
||||
owner_branch: feature-log-management-contract
|
||||
log_field_mapping: [appender, level]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === Distributed tracing ===
|
||||
# source: feature-distributed-tracing-contract — decisions
|
||||
# "trace sampling rate default = prod 1%, staging 10%, dev/local 100%"
|
||||
- name: tracing.sampling.rate
|
||||
type: gauge
|
||||
unit: total
|
||||
tags:
|
||||
- name: profile
|
||||
cardinality_limit: 4
|
||||
allowed_values: [prod, staging, dev, local]
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p3: "effective rate deviates from configured for 1h"
|
||||
owner_branch: feature-distributed-tracing-contract
|
||||
log_field_mapping: [profile]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# === JVM baseline ===
|
||||
# source: feature-metrics-alerting-contract — In scope "JVM/process metric"
|
||||
- name: jvm.memory.used
|
||||
type: gauge
|
||||
unit: bytes
|
||||
tags:
|
||||
- name: area
|
||||
cardinality_limit: 2
|
||||
allowed_values: [heap, nonheap]
|
||||
- name: id
|
||||
cardinality_limit: 10
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p2: "heap used / max > 0.85 for 10m"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [area, id]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — In scope "JVM/process metric"
|
||||
- name: jvm.gc.pause
|
||||
type: timer
|
||||
unit: seconds
|
||||
tags:
|
||||
- name: action
|
||||
cardinality_limit: 10
|
||||
- name: cause
|
||||
cardinality_limit: 10
|
||||
percentiles: [0.5, 0.9, 0.95, 0.99]
|
||||
histogram_buckets: slo_driven
|
||||
alert_severity_thresholds:
|
||||
p2: "p99 pause > 500ms for 10m"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: [action, cause]
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — In scope "JVM/process metric"
|
||||
- name: jvm.threads.live
|
||||
type: gauge
|
||||
unit: total
|
||||
tags: []
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p3: "thread count > 2x baseline for 30m"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: []
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
|
||||
# source: feature-metrics-alerting-contract — In scope "JVM/process metric" (process uptime)
|
||||
- name: process.uptime
|
||||
type: gauge
|
||||
unit: seconds
|
||||
tags: []
|
||||
percentiles: null
|
||||
histogram_buckets: null
|
||||
alert_severity_thresholds:
|
||||
p1: "uptime reset unexpectedly < 60s (crash loop signal)"
|
||||
owner_branch: feature-metrics-alerting-contract
|
||||
log_field_mapping: []
|
||||
compatibility_impact: additive
|
||||
required_test: contract-verification:metrics-cardinality
|
||||
@@ -0,0 +1,203 @@
|
||||
# Registry: Secrets Classification
|
||||
# SSOT: wiki/projects/ca-tmpl/registries/secrets-classification.yaml
|
||||
# Schema owner: feature-contract-registry-governance
|
||||
# Owner branch: feature-secrets-config-source-contract
|
||||
# Last updated: 2026-05-22
|
||||
#
|
||||
# Conventions:
|
||||
# - 3-tier classification (feature-secrets-config-source-contract 2026-05-22):
|
||||
# public-config | sensitive-config | secret
|
||||
# - `secret` rows: prod_default 항상 null. dev fake 식별자는 `__LOCAL_DEV_` prefix
|
||||
# (feature-secrets-config-source-contract 2026-05-22: "dev/local sentinel value prefix = __LOCAL_DEV_").
|
||||
# - prod profile에서 `__LOCAL_DEV_` prefix 발견 시 startup fail
|
||||
# (feature-secrets-config-source-contract 2026-05-22).
|
||||
# - Masking 기본 = `full_except_last_4` (feature-secrets-config-source-contract 2026-05-22:
|
||||
# "full mask except last 4 chars for non-secret tokens"). 진짜 secret(password/private key)은 `full`.
|
||||
# - Naming suffix는 보조 신호 (feature-secrets-config-source-contract: "_TOKEN, _KEY, _PASSWORD").
|
||||
# - public-config 항목은 env-keys.yaml에서 직접 정의되며 본 파일에는 reference row만 둠.
|
||||
|
||||
secrets:
|
||||
|
||||
# === Tier 3: secret (true secret — password/private-key/HMAC-salt) ===
|
||||
|
||||
- name: APP_DATASOURCE_PASSWORD
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# "DB credential은 dual-bind 60s" + "__LOCAL_DEV_FAKE_DB_PASSWORD" 예시
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: dual-bind-60s
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:db-password-no-leak-in-actuator
|
||||
|
||||
- name: APP_SECURITY_JWT_SIGNING_KEY
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# "JWT signing key는 24h overlap window 유지 (security branch와 cross-link)"
|
||||
# + feature-security-operational-baseline "rotation overlap window = 새 kid 도입 → 24h 동안 old kid 병행"
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: overlap-24h
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:jwt-signing-key-rotation-overlap
|
||||
|
||||
- name: APP_SECURITY_OAUTH_CLIENT_SECRET
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# "secret classification은 ... naming pattern은 보조(suffix _TOKEN, _KEY, _PASSWORD)"
|
||||
# + feature-security-operational-baseline "JWT Resource Server를 baseline security model" (OAuth 자격 증명 분류)
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: restart-only
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:oauth-client-secret-no-leak
|
||||
|
||||
- name: APP_EXTERNAL_API_KEY
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# "external API key는 application restart 시 reload"
|
||||
# (per-dependency suffix는 adapter 등록 시 추가; 본 row는 baseline 분류 정의)
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: restart-only
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full_except_last_4
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:external-api-key-no-leak
|
||||
|
||||
- name: APP_CACHE_REDIS_PASSWORD
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# 3-tier classification "secret" + feature-integration-adapter-templates "Redis | disabled optional module"
|
||||
# (Redis enabled + auth 사용 시 secret으로 분류)
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: restart-only
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:redis-password-no-leak
|
||||
|
||||
- name: APP_PRIVACY_PSEUDONYMIZATION_SALT
|
||||
# source: feature-data-retention-privacy-contract 2026-05-22
|
||||
# "pseudonymization key = HMAC-SHA-256 with rotating salt. salt rotation interval = 90일.
|
||||
# rotation 시 old salt 90일 retain (lookup 가능)."
|
||||
# + feature-tenant-context-policy "tenant identifier는 raw PII가 아니어야 하며 ... pseudonymized id"
|
||||
classification: secret
|
||||
source: secret-manager
|
||||
rotation_policy: salt-rotation-90d
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-data-retention-privacy-contract
|
||||
masking_rule: full
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:pseudonymization-salt-rotation
|
||||
|
||||
# === Tier 2: sensitive-config (token-bearing URL or id with exposure restriction) ===
|
||||
|
||||
- name: APP_NOTIFICATION_SLACK_WEBHOOK_URL
|
||||
# source: feature-integration-adapter-templates 2026-05-22
|
||||
# "Slack | disabled optional module | notification failure policy"
|
||||
# Slack webhook URL은 token을 path에 포함하므로 sensitive-config (URL 형태이지만 secret과 동급 취급)
|
||||
classification: sensitive-config
|
||||
source: secret-manager
|
||||
rotation_policy: manual
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full_except_last_4
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:slack-webhook-no-leak
|
||||
|
||||
- name: APP_SECURITY_GOOGLE_OAUTH_CLIENT_ID
|
||||
# source: feature-secrets-config-source-contract 2026-05-22
|
||||
# "sensitive-config" tier (id이지만 노출 제한)
|
||||
# + feature-integration-adapter-templates "Google Email | disabled optional module"
|
||||
classification: sensitive-config
|
||||
source: mounted-env
|
||||
rotation_policy: manual
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full_except_last_4
|
||||
compatibility_impact: behavior-change
|
||||
required_test: secrets-contract:google-oauth-client-id-masked
|
||||
|
||||
- name: APP_DATASOURCE_USERNAME
|
||||
# source: feature-secrets-config-source-contract 2026-05-22 — "sensitive-config" tier
|
||||
# (DB user는 password와 함께 노출되면 위험하므로 sensitive-config)
|
||||
classification: sensitive-config
|
||||
source: mounted-env
|
||||
rotation_policy: dual-bind-60s
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full_except_last_4
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:datasource-username-masked-in-actuator
|
||||
|
||||
- name: APP_DATASOURCE_URL
|
||||
# source: feature-secrets-config-source-contract 2026-05-22 — JDBC URL은 host/db 포함하므로 sensitive-config
|
||||
# (env-keys.yaml에서는 public-config 처리; 본 파일에서는 노출 통제 관점에서 sensitive로 재분류 — masking 기준 명시 목적)
|
||||
classification: sensitive-config
|
||||
source: mounted-env
|
||||
rotation_policy: restart-only
|
||||
prod_default: null
|
||||
dev_sentinel_prefix: __LOCAL_DEV_
|
||||
owner_branch: feature-secrets-config-source-contract
|
||||
masking_rule: full_except_last_4
|
||||
compatibility_impact: breaking
|
||||
required_test: secrets-contract:datasource-url-masked-in-actuator
|
||||
|
||||
# === Tier 1: public-config (reference only — full row in env-keys.yaml) ===
|
||||
|
||||
- name: APP_PROFILE
|
||||
# source: feature-env-driven-runtime-configuration — public-config tier reference
|
||||
classification: public-config
|
||||
source: application-yml
|
||||
owner_branch: feature-env-driven-runtime-configuration
|
||||
masking_rule: none
|
||||
reference: env-keys.yaml#APP_PROFILE
|
||||
|
||||
- name: APP_NAME
|
||||
# source: feature-env-driven-runtime-configuration — public-config tier reference
|
||||
classification: public-config
|
||||
source: application-yml
|
||||
owner_branch: feature-env-driven-runtime-configuration
|
||||
masking_rule: none
|
||||
reference: env-keys.yaml#APP_NAME
|
||||
|
||||
- name: SERVER_PORT
|
||||
# source: feature-env-driven-runtime-configuration — Spring native, public-config tier reference
|
||||
classification: public-config
|
||||
source: application-yml
|
||||
owner_branch: feature-env-driven-runtime-configuration
|
||||
masking_rule: none
|
||||
reference: env-keys.yaml#SERVER_PORT
|
||||
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
# source: feature-env-driven-runtime-configuration — Spring native, public-config tier reference
|
||||
classification: public-config
|
||||
source: application-yml
|
||||
owner_branch: feature-env-driven-runtime-configuration
|
||||
masking_rule: none
|
||||
reference: env-keys.yaml#SPRING_PROFILES_ACTIVE
|
||||
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
# source: feature-distributed-tracing-contract — public-config tier reference
|
||||
classification: public-config
|
||||
source: application-yml
|
||||
owner_branch: feature-distributed-tracing-contract
|
||||
masking_rule: none
|
||||
reference: env-keys.yaml#OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
Reference in New Issue
Block a user