init: 클린 아키텍처 백엔드

This commit is contained in:
DongHyeonka
2026-07-24 14:29:36 +09:00
parent 9eed16d097
commit 821fe00c32
971 changed files with 74769 additions and 1 deletions
+120
View File
@@ -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
+918
View File
@@ -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
+220
View File
@@ -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
+294
View File
@@ -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
+540
View File
@@ -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
+203
View File
@@ -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
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — ADAPTER_DISABLED (런타임 어댑터 비활성화 호출)
category: INTERNAL
error_codes: [ADAPTER_DISABLED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: ADAPTER_DISABLED (`runbook://adapter/adapter-disabled`)
## Symptoms
- HTTP 500 with `error.code=ADAPTER_DISABLED`
- Code invoked an optional adapter (Kafka/Redis/Slack/Email) that is disabled in this deployment
## Diagnosis
- Check adapter name in log (`adapter_name` field)
- Review deployment config — which optional adapters are enabled?
## Action
- Enable the adapter in deployment configuration (env flag)
- Or update application logic to skip disabled-adapter paths
## Escalation
- Escalate to deployment team if adapter should be enabled but isn't
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_AUDIENCE_MISMATCH (대상 불일치)
category: AUTH
error_codes: [AUTH_AUDIENCE_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_AUDIENCE_MISMATCH (`runbook://auth/audience-mismatch`)
## Symptoms
- HTTP 401 with `error.code=AUTH_AUDIENCE_MISMATCH`
- Token `aud` claim does not include this service's expected audience
## Diagnosis
- Check token `aud` claim value
- Compare against configured `spring.security.oauth2.resourceserver.jwt.audiences`
## Action
- Verify client is requesting tokens scoped to the correct audience
- Update audience configuration if service identifier changed
## Escalation
- Escalate to auth-platform team if misconfiguration is system-wide
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_CLAIM_MAPPING_FAILED (클레임 매핑 실패)
category: AUTH
error_codes: [AUTH_CLAIM_MAPPING_FAILED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_CLAIM_MAPPING_FAILED (`runbook://auth/claim-mapping-failed`)
## Symptoms
- HTTP 401 with `error.code=AUTH_CLAIM_MAPPING_FAILED`
- Token validated but required claims (sub, roles, tenant) missing or unexpected type
## Diagnosis
- Inspect token payload claims via logs
- Check claim extractor configuration
## Action
- Verify IdP token template includes required claims
- Update claim mapping configuration if IdP schema changed
## Escalation
- Escalate to auth-platform team if IdP changed claim schema
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_ISSUER_MISMATCH (발급자 불일치)
category: AUTH
error_codes: [AUTH_ISSUER_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_ISSUER_MISMATCH (`runbook://auth/issuer-mismatch`)
## Symptoms
- HTTP 401 with `error.code=AUTH_ISSUER_MISMATCH`
- Token `iss` claim does not match configured expected issuer
## Diagnosis
- Compare token `iss` against `spring.security.oauth2.resourceserver.jwt.issuer-uri`
- Check if IdP environment changed
## Action
- Update issuer config if IdP migrated
- Reject tokens from unexpected issuers
## Escalation
- Escalate to platform-security if unexpected issuer detected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_JWKS_UNAVAILABLE (JWKS 엔드포인트 장애)
category: TRANSIENT_DEPENDENCY
error_codes: [AUTH_JWKS_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_JWKS_UNAVAILABLE (`runbook://auth/jwks-unavailable`)
## Symptoms
- HTTP 503 with `error.code=AUTH_JWKS_UNAVAILABLE`
- All authentication failing; JWKS refresh attempts failing
## Diagnosis
- Check IdP JWKS endpoint health: `curl -sf https://<idp-host>/.well-known/jwks.json`
- Check network connectivity from app pods to IdP
## Action
- Enable cached JWKS fallback if available
- Coordinate with IdP team for restoration
## Escalation
- P1 page: IdP team immediately if JWKS endpoint unreachable > 2 minutes
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_KID_UNKNOWN (키 ID 미인식)
category: AUTH
error_codes: [AUTH_KID_UNKNOWN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_KID_UNKNOWN (`runbook://auth/kid-unknown`)
## Symptoms
- HTTP 401 with `error.code=AUTH_KID_UNKNOWN`, `retryable=true`
- Token `kid` header not present in cached JWKS
## Diagnosis
- Check if IdP key rotation occurred recently
- Verify JWKS cache TTL and refresh timing
## Action
- Force JWKS cache refresh
- Confirm new key is published in IdP JWKS endpoint
## Escalation
- Escalate to IdP team if new kid not appearing in JWKS after 10 minutes
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — INTERNAL_AUTH_MISCONFIGURATION (공개 경로 설정 오류)
category: INTERNAL
error_codes: [INTERNAL_AUTH_MISCONFIGURATION]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: INTERNAL_AUTH_MISCONFIGURATION (`runbook://auth/public-path-misconfiguration`)
## Symptoms
- HTTP 500 with `error.code=INTERNAL_AUTH_MISCONFIGURATION`
- Security filter misconfiguration detected at runtime
## Diagnosis
- Check `verifyPublicPathSnapshot` output in CI
- Review recent changes to `SecurityConfig` or `application.yml` public path list
## Action
- Revert misconfigured public path change
- Run `./gradlew verifyPublicPathSnapshot` to compare snapshot
## Escalation
- P1 immediate: if auth bypass is possible due to misconfiguration
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_EXPIRED (토큰 만료)
category: AUTH
error_codes: [AUTH_TOKEN_EXPIRED]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_EXPIRED (`runbook://auth/token-expired`)
## Symptoms
- HTTP 401 with `error.code=AUTH_TOKEN_EXPIRED`
- Spike may indicate clock skew or long-lived token usage
## Diagnosis
- Check `exp` claim vs server clock
- Check NTP sync on token-issuing host
## Action
- Client must refresh tokens before expiry
- Verify clock skew tolerance is configured (default 60s)
## Escalation
- Escalate if spike is widespread or clock drift is confirmed
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_INVALID_SIGNATURE (서명 검증 실패)
category: AUTH
error_codes: [AUTH_TOKEN_INVALID_SIGNATURE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_INVALID_SIGNATURE (`runbook://auth/token-invalid-signature`)
## Symptoms
- HTTP 401 with `error.code=AUTH_TOKEN_INVALID_SIGNATURE`
- `log_level=ERROR` — may indicate forged tokens or wrong signing key
## Diagnosis
- Check if JWKS endpoint returned a new key set
- Check for token forgery attempts in logs
## Action
- Verify JWKS key IDs match token headers
- Alert security team if forgery suspected
## Escalation
- Immediate P1 escalation if forgery indicators present
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_MALFORMED (토큰 파싱 실패)
category: AUTH
error_codes: [AUTH_TOKEN_MALFORMED]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_MALFORMED (`runbook://auth/token-malformed`)
## Symptoms
- HTTP 401 responses with `error.code=AUTH_TOKEN_MALFORMED`
- Token present but fails JWT parse (not 3-part, non-base64, etc.)
## Diagnosis
- Inspect raw Authorization header value in logs
- Check if token generation tooling has a bug
## Action
- Identify source of malformed tokens
- Fix or update client token generation
## Escalation
- Escalate if spike suggests infrastructure issue
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTH_TOKEN_MISSING (인증 토큰 누락)
category: AUTH
error_codes: [AUTH_TOKEN_MISSING]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTH_TOKEN_MISSING (`runbook://auth/token-missing`)
## Symptoms
- HTTP 401 responses with `error.code=AUTH_TOKEN_MISSING`
- Client missing Authorization header or Bearer token
## Diagnosis
- Check request logs for missing Authorization header
- Verify client SDK configuration
## Action
- Confirm API clients are sending Authorization header
- Check gateway/proxy configuration for header stripping
## Escalation
- Escalate if widespread or affecting critical workflows
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,69 @@
---
title: Runbook — JWT key rotation 시 인증 실패 spike
category: AUTH
error_codes: [AUTH_TOKEN_EXPIRED, AUTH_KID_UNKNOWN, AUTH_JWKS_UNAVAILABLE, AUTH_TOKEN_INVALID_SIGNATURE]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: JWT key rotation 시 인증 실패 spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `auth_401_error_rate_high` 또는 `jwks_refresh_failure_spike`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `runbook_link`, `dependency_name`
- 임계: 401 error rate > 5% 5분 지속 OR JWKS refresh failure count > 10건/분
## 2. First Response (5분 이내)
### Step 1 — 확인
1. JWKS endpoint health check: `curl -sf https://<idp-host>/.well-known/jwks.json | jq '.keys | length'`
2. log query에서 `error.code` 분포 확인 — `AUTH_KID_UNKNOWN` 비중이 높으면 rotation 원인 강력 시사
3. IdP rotation schedule 확인 (직전 24h 내 rotation 이벤트가 있었는지)
### Step 2 — 임시 격리
- JWKS cache TTL을 짧게(예: 60s) 강제하여 새 kid 전파 가속
- 새 kid가 JWKS에 publish되어 있는지 확인. 누락이면 IdP에 republish 요청
## 3. Diagnosis
- log query (Loki/CloudWatch): `{service="auth"} | error.category="AUTH" | dependency_name="jwks-endpoint"`
- metric panel: `auth_jwks_cache_hit_ratio`, `auth_jwks_refresh_failure_total`, `auth_kid_unknown_total`
- trace: 실패한 request 1건에서 `traceId` 추출 → IdP outbound span 확인
- 가능한 원인:
- 새 kid가 JWKS에 publish되기 전 token 발급 → 24h overlap window 안에 있는지 확인
- JWKS endpoint 장애 (5xx, timeout) → IdP status page 확인
- 시계 skew로 인한 만료 오판 → NTP sync 상태 확인
## 4. Mitigation
- 단기: old kid를 임시 재허용 (rollback). overlap window를 48h로 일시 확장
- IdP에 새 JWKS publish 재시도 요청
- 장기: rotation 절차에 "publish → 24h 대기 → switch" 단계 강제. observability에 kid 분포 metric 추가
## 5. Escalation
- P2 → P1 격상 조건: 401 error rate > 20% 또는 다중 tenant에 동시 발생
- 다음 on-call로 page: 10분 내 회복 안 되면 IdP team 또는 platform-security team page
## 6. Recovery / Verification
- 회복 확인 metric: `auth_401_error_rate < 1%` 5분 지속, `AUTH_KID_UNKNOWN` 건수 0
- post-incident:
- rotation 절차 RCA 작성
- JWKS overlap window 정책 문서 업데이트
- kid 분포 dashboard 영구화
## 7. Related
- error-codes.yaml rows: `AUTH_TOKEN_EXPIRED`, `AUTH_KID_UNKNOWN`, `AUTH_JWKS_UNAVAILABLE`, `AUTH_TOKEN_INVALID_SIGNATURE`
- metrics.yaml: `auth_jwks_cache_hit_ratio`, `auth_jwks_refresh_failure_total`
- 관련 branch: [[feature-security-operational-baseline]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 IdP 종류·rotation 정책·JWKS endpoint URL·dashboard 링크로 보강 필요.
@@ -0,0 +1,72 @@
---
title: Runbook — cross-tenant 접근 시도 감지
category: AUTHZ
error_codes: [AUTHZ_INSUFFICIENT_PERMISSION, AUTHZ_TENANT_MISMATCH]
severity: P2
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: cross-tenant 접근 시도 감지
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `authz_cross_tenant_violation` 또는 `authz_403_spike`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `principal_id_pseudonymized`, `tenant_id`, `runbook_link`
- 임계:
- P2: 403 with `error.code=AUTHZ_TENANT_MISMATCH` > 10건/5분
- P1 격상: 동일 principal에서 3개 이상 tenant 시도 OR 5분 내 100건 초과
## 2. First Response (5분 이내)
### Step 1 — 확인
1. log query로 위반 principal 식별 (pseudonymized): `error.code=AUTHZ_TENANT_MISMATCH`
2. principal의 정상 tenant scope 확인 (IdP claim 또는 entitlement table)
3. `CROSS_TENANT_ADMIN` capability 보유 여부 확인 — 보유자라면 false positive 가능성
### Step 2 — 임시 격리
- 명백한 위반 패턴이면 principal session 강제 만료 (token revocation list 추가)
- security incident channel 통보 (`#sec-incident`)
- 위반 request의 source IP / user-agent 기록
## 3. Diagnosis
- log query: `{service="api"} | error.category="AUTHZ" | principal_id_pseudonymized="<hash>"`
- metric panel: `authz_denied_total{reason="tenant_mismatch"}`, `authz_principal_tenant_distribution`
- trace: 위반 request의 `traceId`로 호출 chain 확인. token claim의 `tenant_id`와 요청 path의 `tenant_id` 비교
- 가능한 원인:
- account takeover (계정 탈취) → 즉시 session revoke + 비밀번호 reset 요구
- client bug (잘못된 tenant id 전송) → product team에 통보
- 정상 admin operation 누락된 capability → entitlement 보정
## 4. Mitigation
- 단기: principal session revoke, source IP rate-limit 강화
- 위반이 client bug면 client patch release 협조
- 장기: tenant boundary 검증 layer를 controller가 아닌 repository 진입점에서 강제 ([[feature-repository-access-permission-contract]])
## 5. Escalation
- 다음 on-call로 page: 보안 incident channel 즉시 page. 5분 내 security on-call 응답 없으면 CISO escalation
- legal/compliance 통보 필요 여부 판단 (개인정보 noted시)
## 6. Recovery / Verification
- 회복 확인 metric: `AUTHZ_TENANT_MISMATCH` 건수 정상 baseline 복귀
- post-incident:
- account takeover면 forensics 수행 + audit log 보존
- cross-tenant 검증 unit test 추가
- 위반 패턴 detection rule 영구화
## 7. Related
- error-codes.yaml rows: `AUTHZ_INSUFFICIENT_PERMISSION`, `AUTHZ_TENANT_MISMATCH`
- metrics.yaml: `authz_denied_total`, `authz_principal_tenant_distribution`
- 관련 branch: [[feature-tenant-context-policy]], [[feature-repository-access-permission-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 tenant 모델·capability 정의·security team 연락 체계로 보강 필요.
@@ -0,0 +1,34 @@
---
title: Runbook — AUTHZ_INSUFFICIENT_PERMISSION (권한 부족)
category: AUTHZ
error_codes: [AUTHZ_INSUFFICIENT_PERMISSION]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTHZ_INSUFFICIENT_PERMISSION (`runbook://authz/insufficient-permission`)
## Symptoms
- HTTP 403 with `error.code=AUTHZ_INSUFFICIENT_PERMISSION`
- Valid token but missing required role or permission
## Diagnosis
- Check user's assigned roles in IdP
- Review endpoint's required permission annotation
## Action
- Grant correct role/permission to user
- Verify endpoint permission requirement is correct
## Escalation
- Escalate to access-management team if bulk users affected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — AUTHZ_TENANT_MISMATCH (테넌트 cross-access 시도)
category: AUTHZ
error_codes: [AUTHZ_TENANT_MISMATCH]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: AUTHZ_TENANT_MISMATCH (`runbook://authz/tenant-mismatch`)
## Symptoms
- HTTP 403 with `error.code=AUTHZ_TENANT_MISMATCH`
- `log_level=ERROR` — cross-tenant access attempt detected
## Diagnosis
- Extract `traceId`, check `X-Tenant-Id` vs token tenant claim
- Determine if this is misconfigured client or intentional attack
## Action
- Block repeat offenders at gateway level
- Alert security team for investigation
## Escalation
- P1 if confirmed malicious cross-tenant access attempt
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — CACHE_STAMPEDE_LOCK_TIMEOUT (캐시 스탬피드 락 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [CACHE_STAMPEDE_LOCK_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: CACHE_STAMPEDE_LOCK_TIMEOUT (`runbook://cache/stampede-lock-timeout`)
## Symptoms
- HTTP 503 with `error.code=CACHE_STAMPEDE_LOCK_TIMEOUT`
- Multiple concurrent cache misses on same key; lock contention
## Diagnosis
- Check cache hit ratio metrics
- Identify cache keys with high miss rates
## Action
- Verify stampede lock TTL is configured appropriately
- Pre-warm cache for high-traffic keys on startup
## Escalation
- Escalate if backend load spike accompanies stampede
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — CACHE_UNAVAILABLE (캐시 연결 불가)
category: TRANSIENT_DEPENDENCY
error_codes: [CACHE_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: CACHE_UNAVAILABLE (`runbook://cache/unavailable`)
## Symptoms
- HTTP 503 with `error.code=CACHE_UNAVAILABLE`
- Redis connection errors in logs
## Diagnosis
- Check Redis cluster health
- Verify network connectivity from app to Redis
## Action
- Check Redis sentinel/cluster status
- Enable cache degradation path if configured for optional caches
## Escalation
- P1 if required cache is down and no degradation path exists
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_DEADLOCK (데드락)
category: CONFLICT
error_codes: [DB_DEADLOCK]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_DEADLOCK (`runbook://db/deadlock`)
## Symptoms
- HTTP 409 with `error.code=DB_DEADLOCK`
- SQLState 40P01 in Postgres logs
## Diagnosis
- Check `pg_locks` and `pg_stat_activity` during deadlock
- Identify conflicting transaction lock order
## Action
- Client should retry (retryable=true)
- Fix lock ordering in code if recurring
## Escalation
- Escalate to DBA if deadlock rate is sustained > 1% of transactions
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_IDLE_IN_TX_TIMEOUT (트랜잭션 idle 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_IDLE_IN_TX_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_IDLE_IN_TX_TIMEOUT (`runbook://db/idle-in-tx-timeout`)
## Symptoms
- HTTP 503 with `error.code=DB_IDLE_IN_TX_TIMEOUT`
- SQLState 25P03; transaction held open too long without activity
## Diagnosis
- Check `idle_in_transaction_session_timeout` Postgres setting
- Look for application-level long-running transaction holders
## Action
- Reduce transaction scope in application code
- Verify `spring.jpa.properties.hibernate.connection.timeout` is bounded
## Escalation
- Escalate to DBA if connection pool exhaustion results
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_QUERY_CANCELED (쿼리 취소)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_QUERY_CANCELED]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_QUERY_CANCELED (`runbook://db/query-canceled`)
## Symptoms
- HTTP 503 with `error.code=DB_QUERY_CANCELED`
- SQLState 57014; query exceeds statement timeout
## Diagnosis
- Check `statement_timeout` in Postgres
- Identify slow queries in `pg_stat_statements`
## Action
- Optimize slow query or add index
- Adjust statement timeout if query is legitimately long
## Escalation
- Escalate to DBA for query optimization if recurring
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_SERIALIZATION_FAILURE (직렬화 실패)
category: CONFLICT
error_codes: [DB_SERIALIZATION_FAILURE]
severity: P3
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_SERIALIZATION_FAILURE (`runbook://db/serialization-failure`)
## Symptoms
- HTTP 409 with `error.code=DB_SERIALIZATION_FAILURE`
- SQLState 40001; high concurrent transaction contention
## Diagnosis
- Check DB transaction isolation level
- Identify hot rows / hot tables under high concurrency
## Action
- Client should retry with exponential backoff (retryable=true)
- Optimize transaction scope if spike is sustained
## Escalation
- Escalate to DBA if sustained serialization failure rate > 5%
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DB_UNAVAILABLE (데이터베이스 연결 불가)
category: TRANSIENT_DEPENDENCY
error_codes: [DB_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DB_UNAVAILABLE (`runbook://db/unavailable`)
## Symptoms
- HTTP 503 with `error.code=DB_UNAVAILABLE`
- SQLState 08* connection errors in logs
## Diagnosis
- Check DB server health and connection pool exhaustion
- Review network connectivity from app pods to DB
## Action
- Check DB primary health; failover to replica if available
- Drain connection pool and reconnect
## Escalation
- P1: immediate if DB primary is down
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_4XX_CLIENT (업스트림 클라이언트 오류)
category: PERMANENT_DEPENDENCY
error_codes: [DEPENDENCY_4XX_CLIENT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_4XX_CLIENT (`runbook://dependency/4xx-client`)
## Symptoms
- HTTP 502 with `error.code=DEPENDENCY_4XX_CLIENT`
- Upstream returned 401/403/400 — credential, scope, or request format issue
## Diagnosis
- Check upstream response body in logs for error detail
- Verify API credentials and scopes are valid
## Action
- Rotate credentials if expired
- Fix request format if API contract changed
## Escalation
- Escalate to upstream API owner if contract change is suspected
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_5XX_SERVER (업스트림 서버 오류)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_5XX_SERVER]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_5XX_SERVER (`runbook://dependency/5xx-server`)
## Symptoms
- HTTP 502 with `error.code=DEPENDENCY_5XX_SERVER`
- Upstream returned 5xx; transient server-side failure
## Diagnosis
- Check `dependency_name` tag for which upstream is failing
- Review upstream service status page
## Action
- Client should retry (retryable=true)
- Monitor upstream recovery
## Escalation
- P1 if critical upstream is in sustained 5xx state
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_CIRCUIT_OPEN (서킷 브레이커 개방)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_CIRCUIT_OPEN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_CIRCUIT_OPEN (`runbook://dependency/circuit-open`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_CIRCUIT_OPEN`
- Circuit breaker (Resilience4j) in OPEN state for a dependency
## Diagnosis
- Check Resilience4j circuit breaker metrics for the dependency
- Check upstream health; circuit opens after failure threshold breached
## Action
- Wait for circuit half-open probe (automatic after wait duration)
- Resolve upstream issue to allow circuit to close
## Escalation
- P1 if circuit remains open > 5 minutes on a critical dependency
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_CONNECT_FAILED (외부 의존성 연결 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_CONNECT_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_CONNECT_FAILED (`runbook://dependency/connect-failed`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_CONNECT_FAILED`
- TCP connection refused or network unreachable to upstream
## Diagnosis
- Check `dependency_name` tag for which upstream is unreachable
- Verify network path and firewall rules
## Action
- Check upstream service availability
- Verify service discovery / DNS resolution
## Escalation
- P1 if upstream is a critical service dependency
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_DNS_FAILED (DNS 조회 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_DNS_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_DNS_FAILED (`runbook://dependency/dns-failed`)
## Symptoms
- HTTP 503 with `error.code=DEPENDENCY_DNS_FAILED`
- DNS resolution failure for upstream hostname
## Diagnosis
- Test DNS resolution from app pod: `nslookup <upstream-host>`
- Check cluster DNS (CoreDNS) health
## Action
- Verify upstream hostname configuration
- Check CoreDNS / cluster DNS health
## Escalation
- P1 if cluster DNS is degraded
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — DEPENDENCY_TIMEOUT (외부 의존성 타임아웃)
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_TIMEOUT]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DEPENDENCY_TIMEOUT (`runbook://dependency/timeout`)
## Symptoms
- HTTP 504 with `error.code=DEPENDENCY_TIMEOUT`
- Upstream service did not respond within configured timeout (default: global 10s)
## Diagnosis
- Check `dependency_name` in log for which upstream is timing out
- Review upstream service latency metrics
## Action
- Check upstream service health
- Verify timeout settings match SLA expectations
## Escalation
- P1 if critical upstream is timing out at scale
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+81
View File
@@ -0,0 +1,81 @@
---
title: Runbook — 외부 의존성 unavailable
category: TRANSIENT_DEPENDENCY
error_codes: [DEPENDENCY_TIMEOUT, DEPENDENCY_CONNECT_FAILED, DEPENDENCY_DNS_FAILED, DEPENDENCY_CIRCUIT_OPEN, DEPENDENCY_5XX_SERVER, CACHE_UNAVAILABLE, DB_UNAVAILABLE]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: 외부 의존성 unavailable
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `dependency_error_rate_critical` 또는 `circuit_breaker_open`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `dependency_name`, `dependency_kind`(required|optional), `runbook_link`
- 임계:
- P1: required dependency의 error rate > 50% 1분 OR circuit_open state 활성
- P2: optional dependency degraded (fail-open으로 동작 중)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. `dependency_name` 별 status page 확인 (외부 SaaS면 vendor status, internal이면 해당 service dashboard)
2. log query로 실패 패턴 확인: timeout / connect / DNS / 5xx 중 어떤 모드인지
3. runtime-health Dependency Matrix에서 required vs optional 분류 확인
4. circuit breaker state 확인 (Resilience4j metric)
### Step 2 — 임시 격리
- required dep이면 readiness probe로 traffic 차단 (회복 대기) — cascade failure 방지
- optional dep이면 fail-open with degraded mode 확인. degraded banner를 client에 노출
- DNS failure면 resolver/coredns 상태 확인. cache 강제 flush 검토
## 3. Diagnosis
- log query: `{service="app"} | dependency_name="<name>" | stats count by error.code`
- metric panel:
- `resilience4j_circuitbreaker_state{name="<name>"}`
- `resilience4j_retry_calls_total{kind="failed_without_retry"}`
- `hikaricp_connections_active`, `hikaricp_connections_pending` (DB_UNAVAILABLE)
- `http_client_requests_seconds_count{outcome="SERVER_ERROR"}`
- trace: 실패 request의 outbound span에서 timeout/connect/DNS 분류, target endpoint 확인
- 가능한 원인:
- vendor outage → status page 확인, 회복 대기
- 네트워크 문제 (DNS, security group, NAT) → infra team 확인
- connection pool 고갈 (Hikari) → pool size/timeout 점검
- circuit breaker open 후 half-open 전환 실패 → 수동 reset 검토
- retry-storm으로 인한 self-DoS → retry budget 축소
## 4. Mitigation
- 단기: required면 회복 대기 + traffic 차단, optional이면 degraded mode로 유지
- pool 고갈이면 일시 pool size 상향 + leak detection 활성화
- circuit이 stuck이면 수동 reset (`actuator/circuitbreakerevents`)
- 장기: retry budget·timeout·circuit 임계 재조정, fallback path 보강, vendor SLA 재협상
## 5. Escalation
- 다음 on-call로 page: required dep 5분 내 회복 안 되면 외부 dep team 또는 vendor에 page
- 다중 dep 동시 장애면 incident commander 호출 (네트워크 전반 문제 의심)
## 6. Recovery / Verification
- 회복 확인 metric: dependency error rate < 1% 5분 지속, circuit_breaker_state = CLOSED, pool utilization 정상
- post-incident:
- vendor postmortem 요청 (외부 SaaS면)
- timeout/retry/circuit 설정 재검토
- degraded mode가 사용자 경험에 미친 영향 측정
- chaos test에 해당 시나리오 추가
## 7. Related
- error-codes.yaml rows: `DEPENDENCY_TIMEOUT`, `DEPENDENCY_CONNECT_FAILED`, `DEPENDENCY_DNS_FAILED`, `DEPENDENCY_CIRCUIT_OPEN`, `DEPENDENCY_5XX_SERVER`, `CACHE_UNAVAILABLE`, `DB_UNAVAILABLE`
- metrics.yaml: `resilience4j_circuitbreaker_state`, `hikaricp_connections_active`, `http_client_requests_seconds_count`
- 관련 branch: [[feature-outbound-http-client-baseline]], [[feature-persistence-failure-baseline]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 dependency 목록·required/optional 분류·vendor 연락 체계·circuit/timeout 임계로 보강 필요.
@@ -0,0 +1,34 @@
---
title: Runbook — DOWNLOAD_STREAMING_FAILURE (스트리밍 다운로드 실패)
category: TRANSIENT_DEPENDENCY
error_codes: [DOWNLOAD_STREAMING_FAILURE]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: DOWNLOAD_STREAMING_FAILURE (`runbook://file/download-streaming-failure`)
## Symptoms
- HTTP 503 with `error.code=DOWNLOAD_STREAMING_FAILURE`
- Streaming response truncated; backpressure or timeout (60s / 100MB limit)
## Diagnosis
- Check streaming response timeout configuration
- Review download size vs 100MB limit
## Action
- Verify storage backend is reachable
- Check for network congestion on download path
## Escalation
- Escalate to infra if storage backend is degraded
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+74
View File
@@ -0,0 +1,74 @@
---
title: Runbook — 5xx Internal error spike
category: INTERNAL
error_codes: [INTERNAL_ERROR, INTERNAL_AUTH_MISCONFIGURATION, JVM_OOM]
severity: P1
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: 5xx Internal error spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `http_5xx_error_rate_critical`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `request_id`, `traceId`, `runbook_link`
- 임계: 5xx error rate > 5% 5분 지속 OR > 10% 1분
## 2. First Response (5분 이내)
### Step 1 — 확인
1. 가장 최근 deploy 시각 확인 (CI/CD dashboard, artifact registry digest)
2. JVM metric 확인: heap usage, GC pause, CPU, thread count
3. log에서 실패 request 1건 추출 → `request_id`, `traceId` 확보
4. error.code 분포 확인: `INTERNAL_ERROR` vs `JVM_OOM` vs `INTERNAL_AUTH_MISCONFIGURATION`
### Step 2 — 임시 격리
- 직전 deploy가 의심되면 즉시 rollback (artifact registry에서 직전 image digest pin)
- OOM 패턴이면 affected pod evict → ASG/HPA로 replacement 유도
- LB에서 unhealthy pod 격리 (readiness probe failure 유도)
## 3. Diagnosis
- log query: `{service="app"} | http.status>=500 | stats count by error.code`
- metric panel: `jvm_memory_used_bytes{area="heap"}`, `jvm_gc_pause_seconds`, `process_cpu_seconds_total`, `http_server_requests_seconds_count{status=~"5.."}`
- trace: 실패 request의 `traceId`로 span chain 확인 → stack trace에서 root exception 추출
- heap dump 위치: `/var/tmp/heap/heapdump-<pid>.hprof` (JVM ergonomics: `-XX:MaxRAMPercentage=75 -XX:+HeapDumpOnOutOfMemoryError`)
- 가능한 원인:
- 직전 deploy의 회귀 버그 → rollback
- JVM OOM (메모리 leak 또는 부하 증가) → heap dump 분석
- 외부 의존성 설정 오류 (`INTERNAL_AUTH_MISCONFIGURATION`) → config secret 확인
- thread starvation (pool 고갈) → thread dump (`jstack <pid>`)
## 4. Mitigation
- 단기: 직전 deploy rollback, OOM pod replacement, traffic 일시 감소(scale-out 또는 rate-limit 강화)
- config 오류면 secret/configmap rollback
- 장기: heap dump 기반 leak 수정, capacity planning 재검토
## 5. Escalation
- 다음 on-call로 page: 10분 내 회복 안 되면 incident commander 호출, severity 1 incident 선언
- 데이터 손상 의심되면 DBA team page
## 6. Recovery / Verification
- 회복 확인 metric: 5xx rate < 0.5% 5분 지속, JVM heap usage < 70%, GC pause p99 < 500ms
- post-incident:
- rollback 원인 RCA 작성 (배포 게이트 강화 필요 여부)
- heap dump 분석 결과 공유
- JVM ergonomics(`-XX:MaxRAMPercentage`) 재검토
- rollback 자동화 절차 점검
## 7. Related
- error-codes.yaml rows: `INTERNAL_ERROR`, `INTERNAL_AUTH_MISCONFIGURATION`, `JVM_OOM`
- metrics.yaml: `jvm_memory_used_bytes`, `jvm_gc_pause_seconds`, `http_server_requests_seconds_count`
- 관련 branch: [[feature-operational-error-observability-foundation]], [[feature-container-runtime-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 deploy 파이프라인·heap dump 보관 경로·rollback 자동화 명령으로 보강 필요.
+67
View File
@@ -0,0 +1,67 @@
---
title: Runbook — background job dead letter
category: INTERNAL
error_codes: [JOB_DEAD_LETTER]
severity: P1
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: background job dead letter (`runbook://job/dead-letter`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `job_dead_letter`
- alert payload 필수 field: `error.code=JOB_DEAD_LETTER`, `job_name`, `correlation_id`, `runbook_link`
- 임계: `job.dlq.total` > 0 for 5m (p1) — retry 소진 후 DLQ 진입은 자동 회복이 없으므로 점검 대상
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_DEAD_LETTER` 라인 확인: `job_name`, 최종 실패 원인 예외, `correlation_id` 추출
2. `job.retry.total{outcome=EXHAUSTED}` 추이로 DLQ 유입 규모 파악
3. DLQ 적재 위치(향후 retry carrier 확정 시 DB 테이블/큐) 확인 — 현재 skeleton은 vocabulary 단계
### Step 2 — 임시 격리
- DLQ는 max attempts(3) 소진의 최종 상태 — 자동 재시도 없음, 수동 개입 필수
- 비즈니스 크리티컬 job이면 §4의 수동 처분(재처리 또는 폐기)을 우선 수행
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_DEAD_LETTER" | stats count by job_name`
- metric panel: `job.dlq.total{job_name}`, `job.retry.total{job_name, outcome}`
- 최종 실패 원인 분류:
- poison input(직렬화/계약 위반) → 입력 결함, 재처리해도 실패 — 수정 후 재처리 또는 폐기
- 외부 의존성 장기 outage 중 attempts 소진 → 의존성 회복 후 재처리로 해결 가능
- non-transient error(권한/도메인/스키마)인데 retry된 경우 → 분류기 보강 필요(WAF-REL05-C3: 즉시 DLQ가 정답)
## 4. Mitigation (수동 처분 — 둘 중 하나)
- **재처리 (기본)**: 원인 해소 후 해당 job을 다시 enqueue. 소비자는 멱등(idempotencyKey dedupe) 의무가 있으므로 중복 처리 안전
- **폐기 (영구)**: 작업이 더 이상 유효하지 않으면 DLQ에서 제거. ⚠ 비즈니스 오너 승인 후에만 수행하고 incident 기록에 남김
- 장기: poison input 재발 방지(입력 계약 테스트 보강), non-transient error는 retry 없이 즉시 DLQ로 분류
## 5. Escalation
- 처분 판단(재처리 vs 폐기)이 불가하면 해당 job의 비즈니스 오너에게 escalate
- DLQ 누적이 특정 `job_name`에 집중되면 해당 job 코드 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `job.dlq.total` 증가 멈춤, 재처리분의 소비자 dedupe 동작 확인
- post-incident: DLQ 원인 분류 기록, 같은 원인의 재발 방지 테스트 추가
## 7. Related
- error-codes.yaml rows: `JOB_DEAD_LETTER` (INTERNAL, 500, retryable=false)
- metrics.yaml: `job.dlq.total{job_name}`, `job.retry.total{job_name, outcome=DLQ}`
- 코드: `app-bootstrap` `async/BackgroundJobMetrics`(retry/DLQ vocabulary 기록 seam — D2/D4)
- 관련 runbook: [[job-executor-rejected]], [[job-timeout]], [[outbox-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D4 retry/DLQ vocabulary SSOT — outbox/outbound가 consume)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. retry carrier(Spring Retry / Resilience4j / 자체) 확정 후 DLQ 저장소·재처리 절차 보강 필요.
+71
View File
@@ -0,0 +1,71 @@
---
title: Runbook — async executor rejected
category: TRANSIENT_DEPENDENCY
error_codes: [JOB_EXECUTOR_REJECTED]
severity: P1
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: async executor rejected (`runbook://job/executor-rejected`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `executor_rejected`
- alert payload 필수 field: `error.code=JOB_EXECUTOR_REJECTED`, `executor_name`, `policy`, `runbook_link`
- 임계: `executor.rejected.total` > 0 for 1m (p1) — bounded pool이 saturation으로 task를 거부
- 보조 신호: `executor.saturation` gauge > queue capacity의 80% for 5m (p2)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_EXECUTOR_REJECTED` 라인 확인: `executor_name`, `policy=AbortPolicy`, `queue_size` 추출
2. `executor.saturation` 패널에서 큐 점유율 추이 확인 — 일시적 burst인지 지속 saturation인지 판별
3. 동시 유입 원인 파악: 신규 배포 / 트래픽 spike / 다운스트림 지연으로 worker가 장기 점유되는지
### Step 2 — 임시 격리
- AbortPolicy 거부는 호출부에 `RejectedExecutionException`으로 surface됨 — fire-and-forget `@Async` 호출이면 호출부의 async-exception 처리(log/metric)로 흡수됐는지 확인
- 지속 saturation이면 유입 측(트래픽/스케줄러 빈도)을 우선 감속
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_EXECUTOR_REJECTED" | stats count by executor_name`
- metric panel: `executor.saturation{executor_name}`, `executor.rejected.total{executor_name, policy}`
- 가능한 원인 우선순위:
- 다운스트림 의존성 지연 → worker가 반납되지 않아 큐 포화 (가장 흔함)
- 트래픽 spike → 정상 부하 한계 초과
- pool 과소 설정 (`APP_ASYNC_EXECUTOR_*`)
- non-idempotent 작업이 retry로 누적
## 4. Mitigation
- 단기: 유입 감속(상위 rate-limit / 스케줄러 interval 확대) 또는 다운스트림 의존성 회복
- pool 재조정(restart-only): `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
— ⚠ queue를 무한정 키우지 말 것(unbounded 금지, D7). 부하테스트로 수치 검증 후 변경
- CallerRunsPolicy로의 전환은 use-case 차원의 명시적 결정 필요(request thread latency 침식 — TPE-JDK21-C6)
## 5. Escalation
- 다운스트림 의존성 장애가 근본 원인이면 해당 의존성 오너에게 escalate
- pool 재조정으로도 saturation이 지속되면 용량 계획(capacity planning) 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `executor.rejected.total` 증가 멈춤, `executor.saturation` < 80% 정상화
- 거부된 작업의 재처리 경로(멱등 retry / 다음 스케줄 cycle) 정상 동작 확인
## 7. Related
- error-codes.yaml rows: `JOB_EXECUTOR_REJECTED` (TRANSIENT_DEPENDENCY, 503, retryable=true, retry_after 5s)
- metrics.yaml: `executor.rejected.total{executor_name, policy}`, `executor.saturation{executor_name}`
- 코드: `app-bootstrap` `async/AsyncExecutorConfig`(bounded executor), `async/LoggingAbortPolicy`(reject log+metric), `async/BackgroundJobMetrics`
- env: `APP_ASYNC_EXECUTOR_CORE_SIZE` / `APP_ASYNC_EXECUTOR_MAX_SIZE` / `APP_ASYNC_EXECUTOR_QUEUE_CAPACITY`
- 관련 runbook: [[job-timeout]], [[job-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D7 saturation policy)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 부하 프로파일·alert 채널·pool 수치 확정 시 보강 필요.
+69
View File
@@ -0,0 +1,69 @@
---
title: Runbook — background job timeout
category: TRANSIENT_DEPENDENCY
error_codes: [JOB_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-13
status: stub
---
# Runbook: background job timeout (`runbook://job/timeout`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `job_timeout`
- alert payload 필수 field: `error.code=JOB_TIMEOUT`, `job_name`, `correlation_id`, `runbook_link`
- 임계: `job.retry.total{outcome=RETRY}` 급증 또는 graceful-shutdown 중 in-flight job interrupt 발생
- 연관: shutdown phase에서 19s await 초과로 interrupt된 job (D8)
## 2. First Response (10분 이내)
### Step 1 — 확인
1. ERROR log에서 `JOB_TIMEOUT` 라인 확인: `job_name`, 마지막 단계, 소요 시간 추출
2. timeout이 정상 실행 중 발생인지, graceful-shutdown(배포/스케일다운) 중 interrupt인지 구분
3. 해당 job이 멱등(retry-on-next-cycle 안전)인지 확인 — 비멱등이면 §4에서 신중히 처리
### Step 2 — 임시 격리
- shutdown 중 interrupt면: 다음 기동 시 재시도 대상인지(멱등 전제) 확인, 중복 부작용 여부 점검
- 정상 실행 중 timeout이면: 해당 job의 외부 의존성(DB/HTTP) 지연 여부 확인
## 3. Diagnosis
- log query: `{service="app"} | error.code="JOB_TIMEOUT" | stats count by job_name`
- metric panel: `job.retry.total{job_name, outcome}`
- 가능한 원인 우선순위:
- 외부 의존성(DB lock / 느린 HTTP) 지연으로 job p99 상승
- job 작업량 증가로 단일 cycle이 19s 예산 초과 (D8 — interrupt 노출)
- interrupt 미반응 blocking call(JDBC 등) → awaitTermination 초과 (K8S-POD-LC-C2 SIGKILL 경로)
## 4. Mitigation
- 단기: 의존성 회복 / job 입력 배치 크기 축소
- job p99가 구조적으로 19s를 넘으면: 작업을 분할하거나, grace period 연장 검토(parent project 운영 계약 소유자 승인 필요 — OUT_OF_BRANCH_SCOPE)
- 비멱등 job이 재시도로 중복 부작용을 내면 멱등키/dedupe 도입 우선
## 5. Escalation
- 의존성 지연이 근본 원인이면 해당 의존성 오너에게 escalate
- shutdown 예산(20s) vs k8s `terminationGracePeriodSeconds`(30s) 정합 이슈면 플랫폼/런타임 오너에게 escalate
## 6. Recovery / Verification
- 회복 확인: `JOB_TIMEOUT` 신규 발생 멈춤, `job.retry.total{outcome=SUCCESS}` 정상 비율 회복
- 멱등 재시도분의 부작용 중복 없음 확인
## 7. Related
- error-codes.yaml rows: `JOB_TIMEOUT` (TRANSIENT_DEPENDENCY, 500, retryable=true, retry_after 10s)
- metrics.yaml: `job.retry.total{job_name, outcome}`
- 코드: `app-bootstrap` `async/AsyncExecutorConfig`(awaitTermination 19s — D8 graceful shutdown)
- env: `APP_SERVER_SHUTDOWN_TIMEOUT`(owner: feature-env-driven-runtime-configuration D2)
- 관련 runbook: [[job-executor-rejected]], [[job-dead-letter]]
- 관련 branch: [[feature-background-job-async-contract]] (D4 retry / D8 shutdown)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 retry carrier·job p99·shutdown 예산 확정 시 보강 필요.
+34
View File
@@ -0,0 +1,34 @@
---
title: Runbook — LOCK_ACQUISITION_TIMEOUT (분산 락 획득 타임아웃)
category: CONFLICT
error_codes: [LOCK_ACQUISITION_TIMEOUT]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: LOCK_ACQUISITION_TIMEOUT (`runbook://lock/acquisition-timeout`)
## Symptoms
- HTTP 409 with `error.code=LOCK_ACQUISITION_TIMEOUT`
- Distributed lock wait exceeded configured timeout; high contention on a resource
## Diagnosis
- Check `lock.acquisition` metric for lock name and duration
- Identify lock holders (check DB `integration_lock` table)
## Action
- Client should retry with backoff (retryable=true)
- Optimize critical section holding time if lock contention is systemic
## Escalation
- Escalate if lock holder appears stuck (potential deadlock in distributed lock)
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — ACTUATOR_FORBIDDEN (Actuator 접근 거부)
category: AUTHZ
error_codes: [ACTUATOR_FORBIDDEN]
severity: P2
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: ACTUATOR_FORBIDDEN (`runbook://management/actuator-forbidden`)
## Symptoms
- HTTP 403 with `error.code=ACTUATOR_FORBIDDEN`
- Attempt to access restricted actuator endpoint (env/configprops/heapdump/shutdown)
## Diagnosis
- Identify which actuator endpoint was accessed
- Check caller identity (internal tooling vs external)
## Action
- Verify management port is not exposed externally
- For heapdump/threaddump: follow break-glass runbook procedure
## Escalation
- P1 if forbidden actuator access appears to be external attack
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+35
View File
@@ -0,0 +1,35 @@
---
title: Runbook — MIGRATION_FAILED (DB 마이그레이션 실패)
category: INTERNAL
error_codes: [MIGRATION_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: MIGRATION_FAILED (`runbook://migration/failed`)
## Symptoms
- Container exits with code 70 (migration failure exit)
- Structured log with `error.code=MIGRATION_FAILED`, `startup.phase=migration`
- App refuses to start (fail-fast)
## Diagnosis
- Check Flyway migration log for which script failed and why
- Review latest migration script for SQL errors
## Action
- Fix migration script or roll back to previous migration version
- Run migration manually in repair mode if checksum mismatch
## Escalation
- P1 immediate: app cannot start until migration is resolved
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+73
View File
@@ -0,0 +1,73 @@
---
title: Runbook — outbox dead letter
category: INTERNAL
error_codes: [OUTBOX_DEAD_LETTER]
severity: P1
owner: oncall
last_updated: 2026-06-11
status: stub
---
# Runbook: outbox dead letter (`runbook://outbox/dead-letter`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `outbox_dead_letter`
- alert payload 필수 field: `error.code=OUTBOX_DEAD_LETTER`, `event_type`, `event_id`, `correlation_id`, `runbook_link`
- 임계: `outbox.publisher.published.total{outcome=DEAD}` > 0 (DEAD 전이는 자동 회복이 없으므로 단건도 점검 대상)
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `OUTBOX_DEAD_LETTER` 라인 확인: `event_id`, `event_type`, `correlation_id`, 마지막 실패 원인 예외 추출
2. DB에서 DEAD row 확인: `SELECT * FROM outbox_event WHERE status = 'DEAD' ORDER BY occurred_at;`
3. **차단 영향 파악 (중요)**: strict per-aggregate FIFO 정책상 DEAD row는 같은 `aggregate_id`의 후행 이벤트를 계속 차단함 —
`SELECT count(*) FROM outbox_event b WHERE b.status <> 'PUBLISHED' AND EXISTS (SELECT 1 FROM outbox_event d WHERE d.status='DEAD' AND d.aggregate_id=b.aggregate_id AND d.occurred_at < b.occurred_at);`
### Step 2 — 임시 격리
- DEAD는 max attempts(3) 소진의 최종 상태 — 자동 재시도 없음, 수동 개입 필수
- 차단된 aggregate가 비즈니스 크리티컬하면 아래 §4의 수동 처분(재발행 또는 skip)을 우선 수행
## 3. Diagnosis
- log query: `{service="app"} | error.code="OUTBOX_DEAD_LETTER" | stats count by event_type`
- 마지막 실패 원인 분류:
- poison event (payload 직렬화/계약 위반) → payload 자체 결함, 재발행해도 실패 — 수정 후 재발행 또는 skip
- broker 장기 outage 중 attempts 소진 → broker 회복 후 재발행으로 해결 가능
- 구성 오류 (Kafka disabled 상태에서 producer 활성) → 구성 수정 후 재발행
- 가능한 원인 우선순위: 구성 오류 > broker outage > poison payload
## 4. Mitigation (수동 처분 — 둘 중 하나)
- **재발행 (기본)**: 원인 해소 후 해당 row를 다시 claim 가능 상태로 되돌림 —
`UPDATE outbox_event SET status = 'PENDING', attempt_count = 0, next_attempt_at = now() WHERE event_id = '<id>' AND status = 'DEAD';`
(consumer는 at-least-once + idempotencyKey dedupe 의무가 있으므로 중복 발행은 안전)
- **skip (영구 폐기)**: 이벤트가 더 이상 유효하지 않으면 PUBLISHED로 마킹해 FIFO 차단을 해제 —
`UPDATE outbox_event SET status = 'PUBLISHED' WHERE event_id = '<id>' AND status = 'DEAD';`
⚠️ skip은 다운스트림에 영구 이벤트 갭을 만든다 — 비즈니스 오너 승인 후에만 수행하고 incident 기록에 남김
- 장기: poison event 재발 방지(payload 계약 테스트 보강), DEAD 빈발 event_type의 producer 검증 강화
## 5. Escalation
- 처분 판단(재발행 vs skip)이 불가하면 해당 이벤트의 비즈니스 오너에게 escalate
- DEAD 누적이 특정 event_type에 집중되면 producer 코드 오너에게 page
## 6. Recovery / Verification
- 회복 확인: `SELECT count(*) FROM outbox_event WHERE status='DEAD';` = 0, 차단됐던 aggregate의 후행 이벤트가 PUBLISHED로 전이
- `outbox.publisher.lag` 정상화(< 60s), 재발행분의 consumer dedupe 동작 확인
- post-incident: DEAD 원인 분류 기록, 같은 원인의 재발 방지 테스트 추가
## 7. Related
- error-codes.yaml rows: `OUTBOX_DEAD_LETTER` (INTERNAL, retryable=false)
- metrics.yaml: `outbox.publisher.published.total{outcome=DEAD}`, `outbox.pending.size{status=DEAD}`, `outbox.publisher.lag`
- 코드: `application-core` `PublishPendingOutboxEventsUseCase`(FAILED→DEAD 전이), `adapter-persistence` `outbox/OutboxEventJpaRepository`(FIFO 게이트 — DEAD가 후행 차단)
- 관련 runbook: [[outbox-publish-failed]]
- 관련 branch: [[feature-domain-event-outbox-contract]], [[feature-background-job-async-contract]] (max attempts/DLQ vocabulary SSOT)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 broker·DLQ 토픽·승인 체계 확정 시 보강 필요.
+77
View File
@@ -0,0 +1,77 @@
---
title: Runbook — outbox publish 일시 실패
category: TRANSIENT_DEPENDENCY
error_codes: [OUTBOX_PUBLISH_FAILED]
severity: P2
owner: oncall
last_updated: 2026-06-11
status: stub
---
# Runbook: outbox publish 일시 실패 (`runbook://outbox/publish-failed`)
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `outbox_publish_failed_rate` 또는 `outbox_publisher_lag`
- alert payload 필수 field: `error.code=OUTBOX_PUBLISH_FAILED`, `event_type`, `correlation_id`, `runbook_link`
- 임계 (metrics.yaml verbatim):
- P2: `outbox.publisher.published.total{outcome=FAILED}` rate > 1% for 10m
- P2: `outbox.publisher.lag` > 60s for 10m / P1: > 300s for 5m
- P2: `outbox.pending.size{status=PENDING}` growing for 10m
## 2. First Response (5분 이내)
### Step 1 — 확인
1. ERROR log에서 `OUTBOX_PUBLISH_FAILED` 라인 확인: `event_type`, `event_id`, `correlation_id`, `attempt_count` 추출
2. broker(기본 Kafka adapter) 상태 확인: `APP_MESSAGING_KAFKA_ENABLED` 값과 broker endpoint 가용성
- Kafka disabled(default) 상태에서 outbox 이벤트가 append 되고 있으면 publish 경로가 `AdapterDisabledException`으로 전부 실패하는 구성 오류 — 이 경우 producer use case 쪽 활성화/구성을 먼저 의심
3. `outbox.pending.size` status 분포 확인 (FAILED 누적 vs PENDING 누적)
### Step 2 — 임시 격리
- 일시 실패는 자동 backoff 재시도(30s × 2^(attempt-1) + jitter, max attempts 3)가 동작 — 즉시 수동 개입 불필요
- broker 장기 다운이면 DEAD 전이 누적 전에 broker 회복을 우선 (max attempts 소진 시 `runbook://outbox/dead-letter`로 이관)
- relay 자체를 멈춰야 하면 `ca-skeleton.outbox.relay-enabled=false`로 스케줄러 비활성 (이벤트는 outbox 테이블에 안전하게 보존됨 — 유실 없음)
## 3. Diagnosis
- log query: `{service="app"} | error.code="OUTBOX_PUBLISH_FAILED" | stats count by event_type`
- metric panel:
- `outbox.publisher.published.total{outcome}` — FAILED 비율
- `outbox.publisher.lag{event_type}` — 최고령 미발행 이벤트 age
- `outbox.pending.size{status}` — 상태별 분포
- DB 확인: `SELECT status, count(*) FROM outbox_event GROUP BY status;`
- 가능한 원인:
- broker outage/네트워크 → broker 측 회복 대기
- Kafka adapter 미구성(enabled인데 brokers 누락은 기동 시 차단됨) / disabled 상태에서 producer 활성화
- poison event (직렬화 불가/payload 계약 위반) → 재시도 무의미, attempts 소진 후 DEAD로 흘러감 (의도된 동작)
- 동일 aggregate head 실패로 후행 이벤트가 FIFO 게이트에 차단되어 lag 증가 (strict per-aggregate FIFO — 설계 의도)
## 4. Mitigation
- 단기: broker 회복 후 backoff 만료 시 자동 재발행 — `outcome=PUBLISHED` 회복 확인
- IN_FLIGHT orphan(claim 후 crash)은 in-flight-timeout(기본 PT5M) 경과 후 자동 재claim — at-least-once이므로 중복 발행 가능, consumer dedupe(idempotencyKey)가 흡수
- 장기: `ca-skeleton.outbox.poll-interval`/`batch-size` 조정, broker 가용성 SLA 점검, 빈발 event_type의 payload 계약 검토
## 5. Escalation
- P1 lag(>300s 5m) 지속 + broker 회복 불가면 broker/infra 팀에 page
- DEAD 전이가 발생하기 시작하면 `runbook://outbox/dead-letter` 절차로 이관
## 6. Recovery / Verification
- 회복 확인 metric: `outcome=FAILED` rate < 1% 10분 지속, `outbox.publisher.lag` < 60s, `outbox.pending.size{status=FAILED}` 감소 추세
- post-incident: 실패 구간의 DEAD row 유무 확인, consumer 측 중복 처리량 확인(dedupe 동작 검증), backoff/attempts 상수 재평가
## 7. Related
- error-codes.yaml rows: `OUTBOX_PUBLISH_FAILED` (TRANSIENT_DEPENDENCY, retryable=true, retry_after 30s)
- metrics.yaml: `outbox.publisher.published.total`, `outbox.publisher.lag`, `outbox.pending.size`
- 코드: `application-core` `PublishPendingOutboxEventsUseCase`(상태머신), `adapter-persistence` `outbox/OutboxEventJpaRepository`(SKIP LOCKED claim + FIFO 게이트), `adapter-outbound` `messaging/outbox/KafkaOutboxMessagePublishAdapter`(fail-closed)
- 관련 runbook: [[outbox-dead-letter]]
- 관련 branch: [[feature-domain-event-outbox-contract]], [[feature-background-job-async-contract]] (retry/DLQ vocabulary SSOT)
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 실제 broker 채택·alert 라우팅·대시보드 링크 확정 시 보강 필요.
+73
View File
@@ -0,0 +1,73 @@
---
title: Runbook — Rate limit 초과 spike
category: RATE_LIMIT
error_codes: [RATE_LIMIT_EXCEEDED, IDEMPOTENT_IN_FLIGHT]
severity: P3
owner: oncall
last_updated: 2026-05-22
status: stub
---
# Runbook: Rate limit 초과 spike
## 1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name: `rate_limit_429_high`
- alert payload 필수 field: `operation`, `error.code`, `error.category`, `rate_limit_key_type`(ip|principal|tenant), `runbook_link`
- 임계:
- P3: 429 rate > 1% 10분 지속 (일상적 abuse 차단 효과 정상)
- P2 격상: 정상 client(known principal/tenant)에서 spike 또는 spike와 함께 5xx 동반
## 2. First Response (5분 이내)
### Step 1 — 확인
1. rate-limit key 분포 확인: IP/principal/tenant 중 어디서 spike가 발생했는지
- log query: `error.code=RATE_LIMIT_EXCEEDED | stats count by rate_limit_key_type, rate_limit_key`
2. top-N offending key 추출 (상위 10건)
3. 정상 client 식별 — 알려진 partner/internal service면 P2 격상
### Step 2 — 임시 격리
- abuse traffic 패턴이면 WAF/gateway에서 IP block (geo, ASN 단위)
- IDEMPOTENT_IN_FLIGHT 다발이면 client의 retry-storm 의심 → client에 retry-after 협조 요청
## 3. Diagnosis
- log query: `{service="gateway"} | error.code="RATE_LIMIT_EXCEEDED" | stats count by rate_limit_key`
- metric panel: `gateway_rate_limit_dropped_total`, `gateway_rate_limit_bucket_utilization`
- trace: 429 응답의 `Retry-After` 헤더 값, `rate_limit_remaining` header 확인
- 가능한 원인:
- abuse / bot traffic → IP/ASN block
- 정상 client의 traffic 증가 (캠페인, 신규 feature) → limit 일시 상향
- retry-storm (client backoff 미적용) → client에 idempotency-key + exponential backoff 권고
- limit 설정 오류 (잘못된 정량 threshold) → config rollback
## 4. Mitigation
- 단기: abuse면 IP/ASN block, 정상 client면 해당 key의 limit 일시 상향(예: 2x, 1시간 TTL)
- IDEMPOTENT_IN_FLIGHT 다발: idempotency-key 정책 점검, client 협조 요청
- 장기: limit 정책을 tenant tier별 차등으로 재설계, abuse pattern detection 자동화
## 5. Escalation
- 다음 on-call로 page: 30분 내 정상 client 회복 안 되면 product team 통보
- 정상 client에 SLO 위반 가능성 있으면 CSM/계정담당 통보
## 6. Recovery / Verification
- 회복 확인 metric: 429 rate < 0.5% 10분 지속, 정상 client의 success rate 정상화
- post-incident:
- 일시 상향한 limit 원복 (TTL 만료 확인)
- abuse pattern을 detection rule에 영구 등록
- retry-storm이면 client SDK 가이드 보완
## 7. Related
- error-codes.yaml rows: `RATE_LIMIT_EXCEEDED`, `IDEMPOTENT_IN_FLIGHT`
- metrics.yaml: `gateway_rate_limit_dropped_total`, `gateway_rate_limit_bucket_utilization`
- 관련 branch: [[feature-rate-limit-idempotency-contract]]
---
> **Stub 상태 안내**: 이 runbook은 skeleton 단계의 stub. 도메인 도입 시 실제 gateway 제품(NGINX/Envoy/Kong 등)·tenant tier 정책·WAF 연동 절차로 보강 필요.
+36
View File
@@ -0,0 +1,36 @@
---
title: Runbook — JVM_OOM (JVM OutOfMemoryError)
category: INTERNAL
error_codes: [JVM_OOM]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: JVM_OOM (`runbook://runtime/jvm-oom`)
## Symptoms
- Container exits with code 137 (ExitOnOutOfMemoryError triggered)
- Structured log entry with `error.code=JVM_OOM` before exit
## Diagnosis
- Check heap dump if `-XX:HeapDumpOnOutOfMemoryError` is configured
- Review memory usage trends before crash
- Check for memory leaks: large cache growth, unbounded lists, session accumulation
## Action
- Restart container immediately (k8s will auto-restart with liveness probe)
- If recurring: increase heap `-Xmx` or fix memory leak
## Escalation
- P1: immediate if multiple pods crashing simultaneously
- Page SRE / infra team for heap analysis
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+35
View File
@@ -0,0 +1,35 @@
---
title: Runbook — PROFILE_MISMATCH (프로파일 불일치)
category: INTERNAL
error_codes: [PROFILE_MISMATCH]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: PROFILE_MISMATCH (`runbook://startup/profile-mismatch`)
## Symptoms
- Container exits with code 71 (profile mismatch exit)
- Structured log with `error.code=PROFILE_MISMATCH`, `startup.phase=profile-check`
- Production profile active with local-only settings enabled
## Diagnosis
- Check active Spring profiles (`spring.profiles.active`)
- Identify which local-only setting is incorrectly enabled in prod profile
## Action
- Remove local-only setting from production deployment config
- Ensure prod profile does not inherit local/dev profile settings
## Escalation
- P1: security risk if local settings expose debug endpoints in production
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — REQUIRED_ADAPTER_DISABLED (필수 어댑터 비활성화)
category: INTERNAL
error_codes: [REQUIRED_ADAPTER_DISABLED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: REQUIRED_ADAPTER_DISABLED (`runbook://startup/required-adapter-disabled`)
## Symptoms
- Container exits with code 72 (required adapter disabled exit)
- Structured log with `error.code=REQUIRED_ADAPTER_DISABLED`, `startup.phase=adapter-check`
## Diagnosis
- Identify which adapter is disabled but required
- Check adapter enable flags in environment config
## Action
- Enable required adapter in deployment configuration
- If adapter is intentionally disabled, update the required/optional designation
## Escalation
- P1: app cannot start; coordinate with deployment team
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
@@ -0,0 +1,34 @@
---
title: Runbook — STARTUP_VALIDATION_FAILED (환경 변수 검증 실패)
category: INTERNAL
error_codes: [STARTUP_VALIDATION_FAILED]
severity: P1
owner: oncall
last_updated: 2026-06-15
status: stub
---
# Runbook: STARTUP_VALIDATION_FAILED (`runbook://startup/validation-failed`)
## Symptoms
- Container exits with code 78 (env validation failure exit)
- Structured log with `error.code=STARTUP_VALIDATION_FAILED`, `startup.phase=env-validation`
## Diagnosis
- Check which required env variable is missing or malformed
- Review container environment and secrets injection
## Action
- Supply missing environment variables to deployment
- Verify secrets are correctly mounted / injected
## Escalation
- P1: app cannot start; coordinate with deployment/secrets team
---
> **Stub**: Phase D2 — author body after domain adoption. (feature-operational-runbook-contract D9)
+42
View File
@@ -0,0 +1,42 @@
---
title: Runbook — <TITLE>
category: <CATEGORY>
error_codes: [<ERROR_CODE_1>, <ERROR_CODE_2>]
severity: <P1|P2|P3>
owner: oncall
last_updated: <YYYY-MM-DD>
status: <stub|active>
---
# Runbook: <TITLE> (`runbook://<area>/<scenario>`)
## Symptoms
- What observable signals trigger this runbook?
- Alert name, metric thresholds, log patterns
## Diagnosis
- Step-by-step diagnostic commands and queries
- Log queries (Loki/CloudWatch)
- Metric panels to check
- Trace investigation approach
## Action
- Immediate mitigation steps
- Configuration changes
- Manual intervention procedures
## Escalation
- Conditions for severity upgrade (e.g., P2 → P1)
- Who to page and when
- Fallback procedures if on-call cannot resolve
---
> **Note**: This is the canonical runbook template.
> Copy this file, rename it to match the `runbook://area/scenario` pattern (→ `area-scenario.md`),
> fill in the frontmatter fields, replace section bodies with operational content,
> then set `status: active` and remove from `STUB_ALLOWLIST` in `RunbookCoverageContractTest`.
@@ -0,0 +1,127 @@
# Harness Policy Engine Implementation Plan
> **Spec:** `docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md`
**Goal:** Replace topology- and platform-specific duplicated harness rules with a registry,
strict evidence validators, generated platform variants, and risk-based review policies.
**Working policy:** human-only commits. Each task leaves changes in the working tree.
## Task 1 — Registry, resolver, and Gradle SSOT
**Files:**
- Add `.harness/project/modules.yaml`
- Add `.harness/lib/module_registry.py`
- Add `.harness/validators/validate_modules.py`
- Add `.harness/tests/test_module_registry.py`
- Modify `src/settings.gradle`
- Modify the dependency-verifier section of `src/build.gradle`
**Steps:**
- [ ] Write failing tests for 19-leaf loading, nested owner resolution, nearest `CLAUDE.md`,
unknown paths, and settings/registry parity.
- [ ] Add the registry and stdlib loader/resolver.
- [ ] Make Gradle settings and dependency verification consume registry data.
- [ ] Run Python tests and `./gradlew projects verifyCleanArchitectureDependencies`.
## Task 2 — Registry-driven import gate and mutation suite
**Files:**
- Modify `.claude/hooks/ca_import_gate.py`
- Modify `.claude/hooks/test_ca_import_gate.py`
- Add `.harness/tests/test_import_gate_mutations.py`
**Steps:**
- [ ] Add failing real-path tests for every registered production module.
- [ ] Replace flat-path regex/prefix rules with registry owner and role policy.
- [ ] Normalize Claude snake_case and Antigravity camelCase tool events.
- [ ] Fail closed on malformed in-scope events and marker failures.
- [ ] Run all import-gate tests.
## Task 3 — Verdict schema, evidence artifacts, and platform adapters
**Files:**
- Add `.harness/schemas/verdict.schema.json`
- Add `.harness/schemas/evidence.schema.json`
- Add `.harness/lib/verdict.py`
- Add `.harness/validators/validate_verdict.py`
- Add `.harness/validators/validate_evidence.py`
- Add `.harness/adapters/antigravity_hook.py`
- Add `.harness/tests/test_verdict.py`
- Modify `.claude/hooks/ca_verdict_gate.py`
- Modify `.claude/hooks/test_ca_verdict_gate.py`
- Add `.agents/plugins/ca-superpowers/hooks.json`
**Steps:**
- [ ] Write negative tests for missing required enums, negative counts, Gradle arithmetic,
behavior change without red, missing upstream artifacts, malformed input, and revision
mismatch.
- [ ] Implement strict validation and evidence recording with source/diff hashes.
- [ ] Adapt Claude fenced verdicts to the common model.
- [ ] Add Antigravity Stop/pre-tool adapter and plugin hook wiring.
- [ ] Run validator, adapter, and JSON syntax tests.
## Task 4 — Canonical agents and deterministic rendering
**Files:**
- Add `.harness/agents/*.md`
- Add `.harness/project/platforms.yaml`
- Add `.harness/generators/render_agents.py`
- Add `.harness/tests/test_platform_parity.py`
- Regenerate `.claude/agents/*`, `.codex/agents/*.toml`, `.agents/agents/*/agent.json`
- Update `.agents/plugins/ca-superpowers/README.md` and `plugin.json`
- Update `.codex/agents/README.md`
**Steps:**
- [ ] Seed canonical sources from the newest human-only Claude policy, then update module
discovery and runner validation to use the registry.
- [ ] Add generated metadata and stable output ordering.
- [ ] Render all variants and add a `--check` parity mode.
- [ ] Assert commit policy, source hashes, tool permissions, and body parity in tests.
## Task 5 — Risk/profile policies and guidance drift cleanup
**Files:**
- Add `.harness/manifest.yaml`
- Add `.harness/core/risk-policy.yaml`, `.harness/core/evidence-policy.yaml`
- Add current architecture/language/build/framework/capability profile files
- Add `.harness/validators/resolve_task.py` and tests
- Modify `AGENTS.md`, root `CLAUDE.md`, clean-architecture rule, workflow skill,
advisory-depth rule, reporting-standards rule, and plugin README
- Modify stale module `CLAUDE.md` files and add missing leaf-module guidance where useful
**Steps:**
- [ ] Add failing task-classification tests for high-risk one-file changes and low-risk
multi-file fixture/docs changes.
- [ ] Implement profile resolution.
- [ ] Replace `N!`, routine all-quote grep, file-count report split, and unconditional
counterargument policies with the design profiles.
- [ ] Replace flat module documentation and focused commands with registry-backed nested names.
- [ ] Run policy grep assertions and harness tests.
## Task 6 — Full review and verification
- [ ] Run harness unit/mutation/parity suite.
- [ ] Run `./gradlew projects` and `./gradlew verifyCleanArchitectureDependencies`.
- [ ] Run the focused ArchUnit suite.
- [ ] Run `./gradlew check`.
- [ ] Audit the working-tree diff in order: architecture → spec → quality.
- [ ] Fix findings and restart the review chain, up to three loops.
## Task 7 — LLM Wiki capture
- [ ] Read the LLM Wiki authority and branch-note template.
- [ ] Update/create the detached-HEAD branch note with implementation decisions, changed files,
verification evidence, failures, and open risks.
- [ ] Create/link derived error, interview, or blog-topic raw notes only when supported by the
completed work; otherwise record an explicit “none” judgment in the branch note.
@@ -0,0 +1,187 @@
# Harness Policy Engine Refactoring Design
- **Date:** 2026-07-20
- **Status:** Approved by user request
- **Scope:** repository-local development harness (`.harness`, `.agents`, `.claude`, `.codex`, root/module guidance, Gradle module registry integration)
- **Source:** user-provided “개발 하네스 분석·리뷰” plus repository evidence gathered on 2026-07-20
## 1. Problem Statement
The repository now has 19 nested Gradle leaf modules, but the write-time import gate,
agent prompts, runner allowlist, and root guidance still contain parts of the previous flat
module topology. Platform variants are copied manually, so commit policy and orchestration
already differ between Claude, Codex, and Antigravity. Verdict validation checks a text
summary but does not consistently require enum fields, non-negative counts, or arithmetic
balance.
The harness must move from duplicated platform prompts to a small policy engine with one
project manifest, deterministic renderers, strict validators, and platform adapters.
## 2. Goals
1. Make the actual nested Gradle topology a single machine-readable source of truth.
2. Resolve a touched file to its nearest owning leaf module without assuming `src/<module>`.
3. Generate write-time import policy and focused Gradle task validation from that registry.
4. Validate machine verdicts with required fields, non-negative integers, arithmetic rules,
upstream evidence, revision identity, and TDD red evidence for behavior changes.
5. Materialize validated evidence as JSON artifacts that platform hooks can share.
6. Render Claude, Codex, and Antigravity agent variants from one canonical source and fail
parity checks when generated files drift.
7. Use one human-only commit policy on every platform.
8. Replace file-count and exhaustive-report rules with risk and review profiles.
9. Add mutation and cross-platform static parity tests.
## 3. Non-Goals
- This change does not run authenticated end-to-end golden tasks inside all three external
products. It supplies the deterministic fixtures and validators those runs will consume.
- It does not add application features or alter production Java behavior.
- It does not require PyYAML, jsonschema, Pydantic, or another runtime dependency. Harness
data files use JSON syntax, which is valid YAML, and validators use Python stdlib only.
- It does not make natural-language agent self-reports authoritative. Hooks convert accepted
reports into evidence artifacts; validators remain authoritative.
## 4. Architecture
```text
.harness/project/modules.yaml ──┬── Gradle settings/includes
├── Gradle dependency verification
├── owning-module resolver
├── import gate
└── Gradle command validator
.harness/agents/*.md + platforms.yaml
└── render_agents.py
├── .claude/agents/*.md
├── .codex/agents/*.toml
└── .agents/agents/*/agent.json
Claude hook ───────────────┐
Antigravity hook adapter ──┼── verdict validator ── evidence JSON
Codex validation command ──┘
```
### 4.1 Project registry
`modules.yaml` contains, per leaf module:
- stable module id
- repository-relative source path
- Gradle path
- role
- Java package roots (informational and import-policy lookup only)
- allowed project dependencies
- focused test command
- profiles/capabilities
- owning `CLAUDE.md` when present
- an intentional mutation import used by gate tests
`src/settings.gradle` reads the registry to declare projects. The
`verifyCleanArchitectureDependencies` task reads the same registry instead of maintaining a
second dependency map.
### 4.2 Owning-module resolution
Owner selection uses the longest filesystem-boundary match among registered leaf source
paths. Package prefixes never decide ownership because `support` owns a broad
`dev.caskeleton.adapter.outbound` package and sample code mirrors production packages.
Instruction discovery walks upward from the touched file and returns the nearest
`CLAUDE.md`; if a leaf has none, root `CLAUDE.md` and `AGENTS.md` are the explicit fallback.
### 4.3 Import gate
The import gate first resolves the registered leaf module, then applies:
- dependency-derived sibling module isolation
- role-specific framework rules for domain, application, inbound, outbound, persistence,
identifier, shared-contract, bootstrap, and sample roles
- global unsafe-pattern checks
All registered production modules receive a mutation test using their real nested source
path. Sample-only exemptions are explicit registry data, not accidental regex misses.
### 4.4 Verdict and evidence
The canonical verdict schema requires `agent`, `verdict`, `task_id`, `revision`, and agent
specific evidence. Non-blocked verdicts require every declared field. Counts are non-negative.
Required equations include:
- spec totals balance
- Gradle `run = passed + failed + skipped`
- ready Gradle results include at least one command and no failed command
- behavior-changing implementation requires at least one observed red test
- quality-ready references validated architecture and spec artifacts
Claude's fenced `ca-verdict` remains a compatibility input, but its accepted form is converted
to the same JSON evidence model. Missing or malformed payloads for detected CA agents fail
closed. Evidence records include a source-message hash and current revision/diff identity.
### 4.5 Platform rendering and hook adapters
Canonical agent Markdown lives under `.harness/agents/`; platform metadata lives in
`.harness/project/platforms.yaml`. Generated files carry `generated_from`, `source_hash`,
`generator_version`, and `do_not_edit` metadata.
Antigravity gains a plugin `hooks.json` and a platform adapter using the documented camelCase
stdin/stdout contract. Claude keeps its native hook entry points but calls the common library.
Codex variants instruct the runner/reviewer to invoke the common validation command because
the repository has no equivalent local lifecycle-hook registration surface.
### 4.6 Risk and review profiles
Risk is determined by change surface, not file count:
- high: security, migration/schema, public contract, module dependency, architecture rule,
transaction/concurrency, CI/deployment
- medium: behavior, multiple modules, external integration
- low: docs/comments, test fixture, local refactor protected by characterization tests
Review profiles:
- `review-lite`: direct diff references; no saved report by default
- `review-standard`: verify blocking citations; one report only when risk or findings justify it
- `audit-deep`: verify all quotes and persist detailed findings
- `regulated`: immutable evidence and full traceability
Option analysis uses a dependency DAG and at most 35 materially distinct alternatives.
Counterarguments are required for judgment-dependent findings, not deterministic failures.
## 5. Commit Policy
All platforms use `human-only`. Implementers never stage or commit. Reviewers may inspect a
working-tree diff before commit or an explicit immutable range after the human commits.
## 6. Verification Strategy
1. Stdlib unit tests for registry loading and owner resolution.
2. Mutation tests for every registered production module path.
3. Strict verdict negative tests: missing fields, negatives, arithmetic imbalance, missing
upstream evidence, revision mismatch, and behavior change without red evidence.
4. Golden renderer tests and `--check` parity validation.
5. JSON validation of generated Antigravity hook and agent files.
6. Gradle `projects`, architecture dependency verification, focused ArchUnit test, and full
`check` after harness tests pass.
## 7. Migration and Compatibility
- Existing fenced verdicts remain parseable only when they satisfy the new required fields.
- Generated platform files are overwritten only by the renderer and documented as generated.
- Root and module guidance is updated to the registered nested topology.
- Actual external cross-platform golden executions remain a follow-up; static parity and seeded
mutation coverage become mandatory in this change.
## 8. Acceptance Criteria
- A seeded forbidden import under every nested production module is rejected.
- No legacy flat adapter path remains in gate tests or agent task allowlists.
- `settings.gradle`, dependency verification, import gate, and Gradle runner resolve the same
19 leaf modules from `modules.yaml`.
- Missing/negative/inconsistent ready verdicts fail validation.
- Claude and Antigravity adapters invoke the shared validator; accepted verdicts produce JSON
evidence.
- Rendering followed by `--check` reports no platform drift.
- Agent variants uniformly state human-only commit policy and risk-based orchestration.
- `N!` enumeration, all-quote routine grep, file-count report splitting, and unconditional
counterargument requirements are absent from active rules.
- Harness tests and Gradle checks pass, or every unrun/failing command is reported with risk.