378 lines
39 KiB
Markdown
378 lines
39 KiB
Markdown
---
|
|
title: branch / feature-operational-runbook-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-operational-runbook-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton]
|
|
governing_docs: [wiki/projects/ca-tmpl/observability-log-metric-trace-runbook]
|
|
tags: [branch, ca-skeleton, runbook, incident, operations]
|
|
created: 2026-05-22
|
|
target_merge:
|
|
status_label: in-progress
|
|
last_pass: 2026-06-15 (ca-quality-reviewer fixes applied to `RunbookCoverageContractTest.java` in operational-runbook worktree. 이전: D1 구현 완료 — `RunbookCoverageContractTest.java` + 34 새 stub runbook + template.md. 2026-06-14 /branch-spec — depth **Ready**(Blocking 0 / Should-fix 3) + coverage **Covered**(missing 0). 추가: §구현 가이드(runbook resolver + `error_codes:` reverse-index coverage gate + link-check smoke) · §엣지·실패·의존 · §Audit & Findings · frontmatter parent_branch/governing_docs · §Coverage. ground-truth(grep): `Category.java` 10-value enum, runbook `error_codes:` frontmatter 10/10(=coverage SSOT, forward `runbook://` 34 orphan 은 방향 불일치), 10/10 `status: stub`, error-codes.yaml L28 `retryable=true⇒runbook 필수`(D10 누락=COVERAGE_DRIFT). 미해소(사용자 영역): D11 runbook granularity(forward scheme vs error_codes SSOT) / outbox branch 의 dangling `D15` 참조 / D5~D8 alerting = OUT_OF_BRANCH_SCOPE → 위임 권고: [[raw/branch-notes/feature-metrics-alerting-contract]])
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-031
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-031
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-BOOTSTRAP-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
contract_packet_sha256: b942acb6eac3400b82ecca38728ac3f708fb6ab615d362d06f5927cb941075cb
|
|
---
|
|
|
|
# branch: feature-operational-runbook-contract
|
|
|
|
> Layer: `raw/branch-notes/` — 장애 알림 이후 운영자가 확인하고 판단할 기준을 runbook 계약으로 정의합니다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
|
|
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다. governing 문서는 [[wiki/projects/ca-tmpl/observability-log-metric-trace-runbook]] (§Runbook 슬라이스).
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: 각 failure category에 trigger·diagnosis·recovery drill이 연결된다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-BOOTSTRAP-001@1` | 신규 환경의 default 진입 명령은 ./gradlew bootstrap이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
alert는 시작점일 뿐입니다. 운영자가 어떤 로그 필드, metric, trace, dependency 상태를 먼저 봐야 하는지 없으면 장애 대응 품질이 사람마다 달라집니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- alert별 first check 기준.
|
|
- DB unavailable, dependency timeout, auth failure spike, 5xx spike, queue lag, cache unavailable runbook 기준.
|
|
- degrade/fail-fast 판단 기준.
|
|
- dashboard/log query/runbook link 필드 기준.
|
|
|
|
### 제외 범위
|
|
|
|
- 실제 on-call 조직 운영.
|
|
- provider dashboard 생성.
|
|
- SLA/SLO 법적 약정.
|
|
- **alert severity(P1/P2/P3) 정의 · threshold · dedup/flapping/maintenance-window mute** — [[raw/branch-notes/feature-metrics-alerting-contract]] 가 owner (governing doc §Metric 위임). 본 branch 는 runbook 계약(scheme/link-check/coverage)만. 노트 내 잔존 D5~D8 은 `## Audit & Findings` 의 `OUT_OF_BRANCH_SCOPE` 참조(사용자 결정 영역이라 본문 보존).
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/official-docs/runbook-pagerduty-incident-response-doc.md]] | PagerDuty 권장 runbook 5단 구조(Purpose / Severity / First check / Mitigation / Escalation |
|
|
| [[raw/company-tech-blogs/runbook-atlassian-gitops-runbook-as-code.md]] | git-hosted markdown runbook이 Confluence wiki 대비 drift 적고 PR review 가능 |
|
|
| [[raw/company-tech-blogs/runbook-woowahan-incident-techblog.md]] | 국내 사례 |
|
|
| [[raw/official-docs/google-sre-workbook-on-call-monitoring.md]] | D2 (runbook = 운영 계약): `SRE-WB-OC-C4/C5/C6` — playbook 구성 요소 + alert↔playbook 1:1 coupling 권고. D10 (Error Registry ↔ Runbook CI gate): `SRE-WB-OC-C5/C6` — alert↔playbook coupling 까지만 보증, error-registry 확장은 **UNSUPPORTED_EXTENSION**. Strength = `official-reference` (community consensus), NOT `official-vendor-doc` |
|
|
| [[raw/official-docs/lychee-link-checker.md]] | D4 (link-check smoke validation): `LYCHEE-C1/C2/C3` — Rust async stream-based link checker + Markdown/HTML 1차 지원 + plain text fallback. Strength = `official-vendor-doc` (project README self-description), 도구 capability 근거로만 사용 (best-practice 주장 금지) |
|
|
| [[raw/official-docs/prometheus-alertmanager-silences.md]] | D6 (maintenance window P2/P3 mute, P1 유지): `ALERTMANAGER-SIL-C1/C2/C3` — silence = 시간 제한 mute + matcher AND 매칭 메커니즘. Strength = `official-vendor-doc`. severity 기반 P2/P3 vs P1 매핑 정책은 ca-tmpl 자체 결정 (Alertmanager 가 보증하지 않음) |
|
|
|
|
## 외부 근거 / 대안 조사 (2026-05-22 — Group G-A: Operational runbook)
|
|
|
|
### 채택 결정 + 뒷받침
|
|
|
|
- 결정: **`runbook://{area}/{scenario}` scheme + repo-relative `docs/runbooks/*.md` 허용 + link-check smoke + Error Registry ↔ Runbook coverage CI gate**.
|
|
- 뒷받침 source:
|
|
- [[raw/official-docs/runbook-pagerduty-incident-response-doc.md]] — PagerDuty 권장 runbook 5단 구조(Purpose / Severity / First check / Mitigation / Escalation)와 ca-tmpl Runbook Link Contract required metadata가 1:1 매핑.
|
|
- [[raw/company-tech-blogs/runbook-atlassian-gitops-runbook-as-code.md]] — git-hosted markdown runbook이 Confluence wiki 대비 drift 적고 PR review 가능. ca-tmpl의 repo path 허용 결정 정합.
|
|
- [[raw/company-tech-blogs/runbook-woowahan-incident-techblog.md]] — 국내 사례. 장애 유형별 runbook 분리 + alert 생성 시 runbook 동시 작성 원칙이 ca-tmpl coverage CI gate와 정합.
|
|
|
|
### 검토 대안 + source
|
|
|
|
- 대안 1 — **Confluence/wiki SaaS runbook**: [[raw/company-tech-blogs/runbook-atlassian-gitops-runbook-as-code.md]]에서 drift / login 차단 / version 없음 단점 명시. ca-tmpl forbidden.
|
|
- 대안 2 — **PagerDuty Runbook Automation / auto-remediation**: [[raw/official-docs/runbook-pagerduty-incident-response-doc.md]]. mitigation 자동 실행 가능하나 vendor lock-in + mutation risk. ca-tmpl out-of-scope (Phase D2 이후 여지).
|
|
|
|
### 비교 핵심 1줄
|
|
|
|
`runbook://` scheme + git markdown은 **service repo PR cycle + link-check + CI coverage gate**로 drift 방지가 강점, Confluence wiki는 검색 UX 강점이나 drift, auto-remediation은 자동화 효율 vs mutation risk trade-off.
|
|
|
|
## TODO
|
|
|
|
> TODO drained — 결정은 error.category enum 표 / MDC Key Standard 표 / error.details JSON shape 참조
|
|
|
|
## 진행 중 메모
|
|
|
|
- 2026-06-14 (/branch-spec): ca-tmpl ground truth 재검증 후 §구현 가이드·§엣지·실패·의존·§Audit & Findings 추가, frontmatter `parent_branch`/`governing_docs` 보강, 섹션을 템플릿 순서로 재배치. 기존 D1~D10·표·외부 근거 본문은 verbatim 보존. 핵심 신규 근거 = runbook resolver(`runbook://{area}/{scenario}`→`docs/runbooks/{area}-{scenario}.md`) 6건 정합 / 34 orphan / 4 unref (grep 2026-06-14) + Category enum 10-value(`Category.java`) consume 확인 + error-codes.yaml L24-28 의 `retryable=true⇒runbook 필수` 절(노트 누락 = COVERAGE_DRIFT).
|
|
|
|
## 결정 사항
|
|
|
|
- 2026-05-22: alert에는 operation, dependency, error.category, error.code, retryable, runbook link가 연결되어야 함.
|
|
- 2026-05-22: runbook은 implementation detail이 아니라 운영 계약의 일부로 관리.
|
|
- 2026-05-22: runbook link 형식은 `runbook://{area}/{scenario}` 또는 repository relative markdown path만 허용. placeholder/empty link는 canonical promotion 실패.
|
|
- 2026-05-22: runbook link 검증은 link-check smoke로 수행하며 자동화가 없으면 수동 evidence table이 필수.
|
|
- 2026-05-22: alert deduplication window = 5분 (동일 alert key 재발 시 silent). flapping suppression = 15분 내 3회 toggle 시 mute 30분.
|
|
- 2026-05-22: maintenance window 등록 시 P2/P3 알림은 mute, P1은 유지.
|
|
- 2026-05-22: P3 정의 = business hours 대응, on-call page 안 함, dashboard만 갱신.
|
|
- 2026-05-22: P1 발화 임계(2분) + alert dedup window(5분) = 같은 incident의 2nd alert이 5분 내 silent. 5분 후 재발 시 P1 재발화. 의도된 noise 억제 (operator burnout 방지).
|
|
- 2026-05-22: 본 branch는 runbook link 형식과 검증 SSOT. 실 runbook 본문은 `ca-tmpl/docs/runbooks/*.md` 또는 ca-tmpl repo `docs/runbooks/` 에 작성 (Phase D2). 본 branch는 스키마/coverage 정책만.
|
|
|
|
## Work Item Contract
|
|
|
|
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
|
|
|
|
| field | required | rule |
|
|
| --- | --- | --- |
|
|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
|
|
| Allowed | yes | 허용되는 예외와 조건 |
|
|
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
|
|
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
|
|
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
|
|
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
|
|
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. PagerDuty / Atlassian / 우아한형제들 인용은 모두 `company-case-study` — 공식 best practice 로 단정 금지. 본 branch 의 다수 결정은 raw 인용 부재로 `UNSUPPORTED_DECISION`.
|
|
|
|
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|
|
|
| D1 | alert 에 operation / dependency / error.category / error.code / retryable / runbook link 6 field 필수 | `raw/official-docs/runbook-pagerduty-incident-response-doc.md#PD-RB-C2` (alert body 의무 항목), `raw/official-docs/runbook-pagerduty-incident-response-doc.md#PD-RB-C3` (runbook link 의무) | `company-case-study` | PagerDuty 권장이며 공식 표준 아님. 6 field 의 정확한 enumeration 은 ca-tmpl 자체 결정 — verbatim 인용 부재 |
|
|
| D2 | runbook 은 implementation detail 이 아닌 운영 계약의 일부로 관리 | `raw/official-docs/google-sre-workbook-on-call-monitoring.md#SRE-WB-OC-C4` (playbook = severity/impact/debugging/mitigation 을 포함하는 alert 대응 표준 자산), `#SRE-WB-OC-C5` (alert 생성 시 대응 playbook entry 함께 생성, stress/MTTR/human-error 감소), `#SRE-WB-OC-C6` (각 alert 는 대응 playbook entry 를 가져야 하며 새 alert 는 new code 처럼 review) | `official-reference` | SRE Workbook 은 `official-reference` (community consensus) 이지 `official-vendor-doc` 이 아님 — playbook 의 markdown 파일 schema 까지는 보증하지 않음 (Usage Boundary). Atlassian raw `ATL-RB-C5` 는 여전히 `needs-confirmation` 상태로 보조 근거 보강 권고 |
|
|
| D3 | runbook link 형식 = `runbook://{area}/{scenario}` 또는 repository relative markdown path 만 허용, placeholder/empty 는 promotion fail | `raw/official-docs/runbook-pagerduty-incident-response-doc.md#PD-RB-C4` (예시 runbook 링크 = URL 기반) | `company-case-study` | PagerDuty 예시는 https URL 만 표시 — custom scheme `runbook://` 은 ca-tmpl 자체 추론, vendor 인용 부재. 해석 규칙은 §구현 가이드 §1 (ground-truth 6건 정합/34 orphan) |
|
|
| D4 | runbook link 검증 = link-check smoke + 자동화 없으면 수동 evidence table 필수 | `raw/official-docs/lychee-link-checker.md#LYCHEE-C1` (fast / async / stream-based Rust link checker), `#LYCHEE-C2` (Markdown / HTML / 기타 포맷에서 broken hyperlink + mail address 검출), `#LYCHEE-C3` (HTML/Markdown 1차 지원 + 그 외 plain text fallback) | `official-vendor-doc` | lychee README 는 self-description — "공식 best practice 도구" 가 아닌 capability 근거로만 사용. wikilink (이중 대괄호(double-bracket)) native 지원 여부는 본 인용 밖 (별도 PoC 필요). 수동 evidence table 의 형식은 lychee 가 보증 안 함. **`planned`** — ca-tmpl 에 link-check 스크립트 부재 (grep 2026-06-14) |
|
|
| D5 | alert deduplication window = 5분 (동일 key 재발 silent), flapping suppression = 15분 내 3회 toggle 시 mute 30분 | UNSUPPORTED_DECISION — Prometheus Alertmanager 또는 PagerDuty deduplication 공식 doc 인용 부재 (정량값). **OUT_OF_BRANCH_SCOPE** — alerting-routing 영역(§Audit & Findings) | `unsupported` | Alertmanager / PagerDuty 공식 doc 인용 권고. 단 이 결정은 runbook 계약이 아닌 alert-routing 계약 → owner 후보 = `feature-metrics-alerting-contract` 또는 신규 alert-routing branch (Audit 참조). 본 branch 에서 자동조사 보류 |
|
|
| D6 | maintenance window 시 P2/P3 mute, P1 유지 | `raw/official-docs/prometheus-alertmanager-silences.md#ALERTMANAGER-SIL-C1` (silence = 주어진 시간 동안 알람 mute = 시간 제한 suppression), `#ALERTMANAGER-SIL-C2` (silence 는 routing tree 와 동일하게 matcher 기반 설정), `#ALERTMANAGER-SIL-C3` (incoming alert 가 모든 (all) equality/regex matcher 만족 시 silence 적용) | `official-vendor-doc` | Alertmanager 는 silence 메커니즘 자체 (시간 제한 mute + matcher AND) 만 보증 — "P2/P3 mute / P1 유지" 라는 severity 기반 정책 매핑은 ca-tmpl 자체 결정 (Alertmanager 가 severity 라는 label 을 표준으로 정의하지 않음). silence 의 start/end grammar / 무한 silence 가능 여부는 본 인용 밖 (UI / API spec 별도). **OUT_OF_BRANCH_SCOPE** — severity 매핑은 `feature-metrics-alerting-contract` owner (Audit 참조) |
|
|
| D7 | P3 정의 = business hours 대응, on-call page 안 함, dashboard 만 갱신 | `raw/official-docs/runbook-pagerduty-incident-response-doc.md#PD-RB-C5` (PagerDuty High/Medium/Low/Notification 4단계) | `company-case-study` | PagerDuty 4단계와 ca-tmpl P1/P2/P3 의 1:1 매핑은 ca-tmpl 자체 결정 (raw Usage Boundary 명시: "1:1 매핑 보장 안 됨"). **RESTATED_FOREIGN_DECISION** — P1/P2/P3 severity 정의의 owner 는 `feature-metrics-alerting-contract` (§P1/P2/P3 정량 기준). reference-only 위임 권고 (Audit 참조) |
|
|
| D8 | P1 발화 임계(2분) + alert dedup window(5분) = 2nd alert 이 5분 내 silent, 5분 후 재발화 (operator burnout 방지) | UNSUPPORTED_DECISION — 정량값 (2분/5분) 외부 reference 부재 | `unsupported` | PagerDuty 또는 Google SRE 의 fatigue prevention doc 인용 권고. **OUT_OF_BRANCH_SCOPE** — P1 발화 임계(2분)는 `feature-metrics-alerting-contract` 의 `required dep unavailable >2분` 와 중복(RESTATED_FOREIGN_DECISION); dedup window 부분은 alert-routing. 본 branch 에서 자동조사 보류 (Audit 참조) |
|
|
| D9 | 본 branch = runbook link 형식 + 검증 SSOT (실 runbook 본문은 Phase D2 `ca-tmpl/docs/runbooks/*.md` 작성) | UNSUPPORTED_DECISION — 내부 스코프 결정 | `internal-only` | scope drift 위험 — D2/Phase D2 timing 추적 필요. **update 2026-06-14**: `docs/runbooks/` 에 10개 파일 실재(grep) — 본문 일부는 이미 작성됨. 단 per-code scheme link 40 vs 파일 10 (34 orphan) 으로 coverage 미완 (Audit `RUNBOOK_LINK_RESOLUTION_DRIFT`) |
|
|
| D10 | Error Registry ↔ Runbook coverage CI gate — `retryable=false` + category ∈ {TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY, AUTH, AUTHZ, RATE_LIMIT, INTERNAL} 인 row 는 runbook link 필수, 누락 시 release-block | `raw/official-docs/google-sre-workbook-on-call-monitoring.md#SRE-WB-OC-C5` (alert 생성 시 대응 playbook entry 함께 생성이 SRE 일반 관행), `#SRE-WB-OC-C6` (각 alert 는 대응 playbook entry 를 가져야 하며 review 대상) — **UNSUPPORTED_EXTENSION**: 본 raw Usage Boundary 명시 — "error code ↔ runbook 1:1 mapping 이 alert ↔ playbook 1:1 mapping 과 동치라는 점은 SRE Workbook 이 직접 보증하지 않음. error registry 개념 자체가 SRE Workbook 에 등장하지 않음" | `official-reference` (alert↔runbook 까지만) + `unsupported-extension` (error-registry↔runbook 까지의 확장) | SRE Workbook 은 alert↔playbook coupling 만 보증, 이를 **error-registry↔runbook coupling 으로 확장 적용** 하는 것은 ca-tmpl 자체 결정. CI gate 의 release-block 정책 (자동화 도구 / fail criteria) 외부 reference 부재. **COVERAGE_DRIFT 2026-06-14**: error-codes.yaml L24-28 은 추가로 "`retryable=true` 인 모든 row ⇒ runbook 필수" 절을 포함하나 본 row 는 이를 누락 — 정합 권고(Audit 참조). 우아한형제들 raw URL 교체 후 verbatim 재인용 권고 |
|
|
|
|
## 구현 가이드
|
|
|
|
> *결정 (Decisions)* 이 "*무엇* 을 할 것인가" 라면, 본 §는 "*어디에 어떻게* 구현될 것인가" 의 사전 명세. 본 branch in-scope = **runbook 계약** (D1 payload runbook field · D2 · D3 scheme · D4 link-check · D9 본문 위치 · D10 coverage gate). alerting-platform 결정(D5~D8)은 §Audit `OUT_OF_BRANCH_SCOPE` 로 분리 — 본 §에 구현 detail 을 남기지 않음(R3).
|
|
>
|
|
> 계약 값 SSOT: `ca-tmpl/docs/registries/error-codes.yaml` (`owner_branch` 다수) + `runbook_link` 컬럼. Category enum SSOT = `feature-operational-error-observability-foundation` 의 `src/shared-contract/.../error/Category.java` (10-value, 본 branch 는 **consume only**).
|
|
|
|
### 1. runbook_link 해석 규칙 (resolver)
|
|
|
|
> **Trace**: D3 / `PD-RB-C4`. Ground-truth grep 2026-06-14 (`ca-tmpl/docs/runbooks/` + `error-codes.yaml`).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: 첫 `/` 만 `-` 로 치환(area 1-segment·scenario 1-segment 가정)은 ca-tmpl 자체 결정 — PagerDuty 인용은 https URL 만 보증. trade-off: scenario 에 `/` 포함 시 모호 → scenario 는 단일 kebab segment 강제.
|
|
|
|
| 입력 | 변환 | 결과 | 상태 |
|
|
|---|---|---|---|
|
|
| `runbook://{area}/{scenario}` | area·scenario 사이 `/` → `-` (area/scenario 각 단일 segment) | `docs/runbooks/{area}-{scenario}.md` (repo-relative) | `actually-implemented` (6건 resolve 정합: `runbook://job/executor-rejected`→`docs/runbooks/job-executor-rejected.md` 등) |
|
|
| repository relative markdown path | 그대로 | `docs/runbooks/*.md` | `actually-implemented` (파일 10건 실재) |
|
|
| `runbook://area/scenario` (placeholder) / empty | — | promotion fail | `planned` (게이트 미구현) |
|
|
|
|
### 2. runbook coverage gate (error-registry ↔ runbook)
|
|
|
|
> **Trace**: D10 / `SRE-WB-OC-C5/C6` (alert↔playbook 까지만; error-registry 확장은 UNSUPPORTED_EXTENSION). 정책 값 SSOT = `error-codes.yaml` L24-28.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: error-registry↔runbook 1:1 강제 + release-block 자동화 도구 선택은 외부 reference 부재. trade-off: alert↔playbook(보증됨)을 error-code 단위로 확장 — 운영상 합리적이나 SRE 문헌이 직접 보증하지 않음.
|
|
|
|
각 `error-codes.yaml` row 판정 (category enum = `Category.java` 10-value consume):
|
|
|
|
| 조건 | runbook_link | 근거 |
|
|
|---|---|---|
|
|
| `retryable=false` + category ∈ {AUTH, AUTHZ, RATE_LIMIT, INTERNAL, TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY} | **필수** | D10 / error-codes.yaml L25-26 |
|
|
| **`retryable=true` 인 모든 row** | **필수** | error-codes.yaml L28 — **노트 D10 이 누락한 절**(Audit `COVERAGE_DRIFT`) |
|
|
| `retryable=false` + category ∈ {VALIDATION, NOT_FOUND, CONFLICT, DATA_INTEGRITY} (client-error) | `null` 허용 | error-codes.yaml L27 |
|
|
| 위 필수 조건인데 `runbook_link` 누락/placeholder | **release-block** | D10 |
|
|
|
|
> **Ground-truth coverage 방향 (grep 2026-06-14)**: 실제 coverage 의 authoritative 방향은 *runbook→codes* — `docs/runbooks/*.md` 10개 **전부** frontmatter 에 `error_codes: [...]` 선언(예: `dependency-unavailable.md` → 7 codes `[DEPENDENCY_TIMEOUT, DEPENDENCY_CONNECT_FAILED, DEPENDENCY_DNS_FAILED, DEPENDENCY_CIRCUIT_OPEN, DEPENDENCY_5XX_SERVER, CACHE_UNAVAILABLE, DB_UNAVAILABLE]`). 따라서 coverage gate 는 "각 필수 error code 가 *정확히 한* runbook 의 `error_codes:` 리스트에 등장" 으로 구현하는 것이 정합 — error-codes.yaml 의 per-code `runbook://` link 를 forward resolve(§1)하는 방식이 아님. 이 reverse-index 가 consolidated(many-codes→one-runbook)를 자연히 허용해 §엣지의 granularity 문제를 설계상 해소.
|
|
|
|
### 3. link-check / placeholder smoke
|
|
|
|
> **Trace**: D4 / `LYCHEE-C1/C2/C3`. **전체 `planned`** — ca-tmpl 에 link-check 스크립트/테스트 부재(grep 2026-06-14).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: lychee 가 custom `runbook://` scheme + 이중대괄호 wikilink 를 native 지원하는지는 인용 밖 → resolver(§1)가 scheme 을 file path 로 먼저 치환한 뒤 lychee 에 *file 모드*로 넘기는 2단계 필요. trade-off: 치환 단계 버그 가능 → resolver 단위 테스트로 고정.
|
|
|
|
판정 항목 (resolve 된 target 파일에 대해): (a) 파일 존재, (b) 본문에 `(?i)(TODO|TBD|PLACEHOLDER|FIXME)` 미포함, (c) `docs/runbooks/template.md` 로 시작하지 않음(template 자체는 link target 아님). 1건이라도 위반 시 fail (§테스트 계약과 동치).
|
|
|
|
> **Stub gap (grep 2026-06-14)**: 현재 `docs/runbooks/*.md` 10개 **전부** `status: stub` frontmatter — placeholder regex 에 안 걸려 *stub 이 smoke 통과*. → smoke fail 집합에 `status: stub`(또는 본문 'Stub')을 포함해야 미완 runbook 을 block. `docs/runbooks/template.md` 는 실재하지 않음 → 규칙 (c)는 현재 no-op(무해, 파일 생성 시 활성).
|
|
|
|
### 4. 현재 coverage 상태 (ground-truth 2026-06-15 D1 구현 후)
|
|
|
|
> **Trace**: D4·D10 의 검증 대상 실측. 이 표가 §Claims To Verify + §Audit `RUNBOOK_LINK_RESOLUTION_DRIFT` 의 근거.
|
|
|
|
| 지표 | 값 (2026-06-15) |
|
|
|---|---|
|
|
| `error-codes.yaml` 의 distinct `runbook://` link | 39 (RATE_LIMIT_EXCEEDED 포함) |
|
|
| `docs/runbooks/*.md` 실파일 | 45 (기존 10 + 신규 34 + template.md) |
|
|
| `{area}-{scenario}.md` 규칙으로 resolve OK | 39 / 39 (**orphan 0**) |
|
|
| mandatory code 가 어떤 runbook `error_codes:` 에도 없음 | **0** (coverage 100%) |
|
|
| `status: stub` 본문 (미완) | **44 / 44** (전부 stub — Phase D2 예정) |
|
|
| **authoritative coverage 방향** | runbook `error_codes:` frontmatter (양방향 SSOT 정합) |
|
|
| **STUB_ALLOWLIST** 등재 | 44개 (기존 10 + 신규 34) |
|
|
|
|
**이전 값 (2026-06-14)**: `error-codes.yaml` 40 link / 실파일 10 / orphan 34 / coverage 미달 다수
|
|
|
|
→ **주의**: 위 "34 orphan" 은 *forward* (per-code scheme→file) 가정의 수치. 실제 coverage SSOT 는 runbook `error_codes:` frontmatter(§2 ground-truth note) — 이 방향으로 보면 consolidated 파일이 codes 를 묶어 선언하므로 granularity 는 *설계상* 해소. 남는 작업: (a) error-codes.yaml 의 모든 필수 code 가 어떤 runbook `error_codes:` 에도 없으면 = *진짜 missing*, (b) 10개 stub 본문 작성(Phase D2), (c) D4/D10 게이트 코드.
|
|
|
|
## Runbook Link Contract
|
|
|
|
| field | default |
|
|
| --- | --- |
|
|
| link format | `runbook://area/scenario` or `docs/runbooks/*.md` |
|
|
| required metadata | severity, first metric, first log query, dependency owner, rollback/degrade decision |
|
|
| forbidden | empty link, `TBD`, inaccessible URL |
|
|
| verification | link-check smoke or manual evidence table |
|
|
|
|
## Error Registry ↔ Runbook Coverage
|
|
|
|
- CI gate: error registry의 모든 `retryable=false` + `category ∈ {TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY, AUTH, AUTHZ, RATE_LIMIT, INTERNAL}` row는 runbook link 필수.
|
|
- 누락 시 release-block. 자동 비교는 verification suite가 수행.
|
|
|
|
## 엣지·실패·의존
|
|
|
|
> R4(깊이 게이트) 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거.
|
|
|
|
- **실패·엣지 경로**:
|
|
- **Orphan runbook link (34건)** — `error-codes.yaml` 의 per-code scheme link 40건 중 34건이 대응 파일 없음(§구현 가이드 §4, grep 2026-06-14). link-check(D4) 도입 시 전부 fail. 기대 동작: D10 게이트가 release-block.
|
|
- **Granularity (consolidated runbook)** — 실파일 4건(`auth-token-rotation-failure` 등)은 incident-class 단위 *consolidated* 라 forward per-code scheme link 와 1:1 안 맞음. 단 ground-truth 상 coverage SSOT 는 runbook `error_codes:` frontmatter(many-codes→one-runbook, §구현 가이드 §2) → 이 방향이면 정상. 남은 결정: forward `runbook://` scheme 을 *유지*(유지 시 alias/redirect 필요) vs `error_codes:` frontmatter 단일 SSOT 로 *수렴* — D11 후보(사용자 영역, Audit `RUNBOOK_LINK_RESOLUTION_DRIFT`).
|
|
- **Custom scheme 비렌더링** — `runbook://` 는 PagerDuty/Slack 등 외부 채널에서 클릭 불가 가능(§Claims To Verify). 기대 동작: alert renderer 가 scheme→repo/https URL 치환 후 발송(`planned`).
|
|
- **Placeholder body** — link target 파일이 존재해도 본문에 TODO/TBD/PLACEHOLDER/FIXME 있으면 fail(§테스트 계약 §placeholder).
|
|
- **다른 계약 의존**:
|
|
- [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `Category.java` (10-value enum) — 본 branch coverage gate(§구현 가이드 §2)가 category 집합을 **consume**. enum 변경 시 게이트 카테고리 집합 재검토.
|
|
- [[raw/branch-notes/feature-metrics-alerting-contract]] 의 alert payload(D10) + P1/P2/P3 severity(§P1/P2/P3 정량 기준) — 본 branch 의 runbook_link field 는 그 alert payload 계약 위에 얹힘. severity/dedup/threshold 의 owner 는 그 branch (본 노트 D5~D8 의 Audit `OUT_OF_BRANCH_SCOPE` 참조).
|
|
- [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] 의 Required vs Optional Dependency Matrix — §테스트 계약의 degrade decision 판정이 그 matrix row 를 consume.
|
|
- `ca-tmpl/docs/registries/error-codes.yaml` (`owner_branch` 다수) — coverage gate 의 입력. registry schema 변경 시 게이트 parser 영향.
|
|
|
|
## Audit & Findings
|
|
|
|
> ca-tmpl ground truth 대조에서 발견한 drift/scope. 사용자 작성 결정 영역은 자동 rewrite 보류 — *정합 권고만*(CLAUDE.md §11, §15.5 R3, consistency-contract Single-Owner).
|
|
|
|
- **COVERAGE_DRIFT** (정합 권고) — `error-codes.yaml` L24-28 의 runbook policy 는 **두 절**: (1) `retryable=false`+category∈{6개}⇒runbook 필수, (2) **`retryable=true` 인 모든 row⇒runbook 필수**. 본 노트 §Error Registry ↔ Runbook Coverage + D10 은 (1)만 기술, (2)를 누락. → §구현 가이드 §2 에는 (2)를 반영했으나, 사용자 결정 테이블(§Error Registry, D10)은 보존. 권고: D10 + §Error Registry 에 `retryable=true` 절 추가.
|
|
- **RUNBOOK_LINK_RESOLUTION_DRIFT** (grep 2026-06-14) — 두 coverage 방향이 공존: (forward) error-codes.yaml 의 per-code `runbook://` link 40개 → `{area}-{scenario}.md` 규칙으로 6건만 resolve / 34 미존재; (reverse, **실제 SSOT**) runbook `error_codes:` frontmatter 10/10 선언 → consolidated 허용. 즉 forward 의 "34 orphan" 은 실제 coverage 미달이 아니라 *두 방향의 granularity 불일치*. 권고: **D11 후보** — forward scheme 을 (a) `error_codes:` 단일 SSOT 로 수렴(scheme 은 라벨, link-check 는 reverse-index 검사) vs (b) per-code 1:1 파일 분리(40 파일). 실제 구현은 이미 (a) consolidated+frontmatter 채택 → 노트 §1 forward resolver 가정과 정합 필요. 결정은 사용자 영역.
|
|
- **OUT_OF_BRANCH_SCOPE — alerting-platform 결정 (D5/D6/D7/D8)** — governing doc(`observability-log-metric-trace-runbook` §Metric)은 alert severity P1/P2/P3 + threshold 를 [[raw/branch-notes/feature-metrics-alerting-contract]] 에 위임. 그 branch 가 P1/P2/P3 severity(§P1/P2/P3 정량 기준) + alert payload(D10) 의 owner.
|
|
- D7(P3 정의)·D8(P1 2분 임계) = 그 owner 와 중복 → **RESTATED_FOREIGN_DECISION**. 권고: reference-only 포인터([[feature-metrics-alerting-contract]] §P1/P2/P3)로 위임.
|
|
- D5(dedup 5분/flapping)·D6(maintenance mute) = alert-routing(Alertmanager silence/inhibition) 영역으로 두 branch 어디에도 owner 없음. 권고: alerting branch 또는 신규 `feature-alert-routing-contract` 로 이관.
|
|
- 사용자 작성 결정이라 본문(결정 사항·D5~D8 row) 보존 — 이관은 사용자 결정. 본 branch 자동조사에서 D5/D8 fill-here 연구는 **보류**(out-of-scope 결정을 entrench 하지 않음, R3).
|
|
- 본 branch in-scope runbook 결정 = D1·D2·D3·D4·D9·D10.
|
|
- **D1 구현 완료 (2026-06-15 locally-verified)** — `RunbookCoverageContractTest.java` 가 `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/` 에 신규 생성됨. JUnit 4-test gate(COVERAGE / LINK_FORMAT / LINK_RESOLUTION / PLACEHOLDER_STUB_SMOKE). `./gradlew :app-bootstrap:test --tests '*RunbookCoverageContractTest'` PASS. `./gradlew :app-bootstrap:test` 전체 PASS. 34개 신규 stub runbook 생성(`docs/runbooks/*.md`), `template.md` 신규 생성. `STUB_ALLOWLIST` 44개(기존 10 + 신규 34). 커버리지: 40 mandatory code 전부 runbook `error_codes:` frontmatter 에 등록, 39개 `runbook://` link 전부 파일 resolve.
|
|
- **ca-quality-reviewer fixes applied (2026-06-15 locally-verified)** — 3개 수정. (1) Fix 1 (BLOCKING): Test D `if (!Files.isDirectory(runbooksDir)) return;` → `Assumptions.assumeTrue(...)` 로 교체 — bare return 이 PASS 로 보고되던 것을 SKIPPED 로 수정, 클래스 Javadoc "never silently passed" 계약 이행. (2) Fix 2 (ADVISORY): 미사용 import 2개 제거 — `import static org.assertj.core.api.Assertions.fail;`, `import java.util.LinkedHashMap;`. grep 으로 실 사용 없음 확인. (3) Fix 3 (MINOR): `extractFrontmatter` 에 `end <= 4` guard 추가 — 빈 frontmatter(`---\n---`) 시 `StringIndexOutOfBoundsException` 잠재 버그 선제 차단. `./gradlew :app-bootstrap:test --tests '*RunbookCoverageContractTest'` PASS (4 tests run, BUILD SUCCESSFUL).
|
|
- **NO automation yet** — D4(link-check)·D10(coverage gate)는 이전에 `planned`. ca-tmpl 에 runbook-coverage 테스트가 **2026-06-15 실 구현됨**(`actually-implemented`). 단 lychee 등 외부 link-checker 통합은 여전히 `planned`.
|
|
|
|
## 테스트 계약
|
|
|
|
- alert payload 메타 누락: 모든 P1/P2 alert payload에 다음 5 field가 모두 있어야 함: `operation`, `dependency_name` (해당 시), `error.code` (해당 시), `error.category`, `runbook_link`. 측정 방법: Prometheus rule yaml 또는 동등 alert definition 파일을 parse하여 5 field 존재 verify. 1 field라도 누락 시 fail.
|
|
- degrade decision 미정: `feature-runtime-health-lifecycle-contract`의 Required vs Optional Dependency Matrix에 해당 dependency row가 존재해야 함. 측정 방법: alert가 발생한 dependency_name이 dependency matrix의 row name과 매칭. 미매칭 또는 `required` column 값이 명시 안 됨이면 fail.
|
|
- runbook orphan alert: 모든 alert definition의 `runbook_link` field가 `runbook://{area}/{scenario}` 또는 `docs/runbooks/*.md` 형식이어야 하고 실제 파일 존재. 측정 방법: alert yaml의 runbook_link → 실제 markdown 파일 path resolve + file exists. 미존재 시 fail.
|
|
- placeholder runbook link: runbook link target 파일 안에 `TODO`, `TBD`, `PLACEHOLDER` 같은 string이 본문에 있으면 fail. 측정 방법: link target 파일을 read → regex `(?i)(TODO|TBD|PLACEHOLDER|FIXME)` match 시 fail. 또한 link target이 `docs/runbooks/template.md`로 시작하면 fail (template 자체는 link target 아님).
|
|
|
|
## 검증해야 할 주장
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| `runbook://{area}/{scenario}` scheme 이 on-call tool (PagerDuty/Opsgenie) 에서 정상 렌더링 | PagerDuty raw Usage Boundary 명시: "보통 https/file URL 만 클릭 가능" | PagerDuty 또는 Opsgenie 에서 custom scheme link payload 테스트 | `needs-confirmation` |
|
|
| Error Registry 와 Runbook coverage 가 CI 에서 자동 비교됨 (수동 누락 없음) | 자동화 도구의 외부 reference 부재 (D4/D10) | CI script 구현 + error registry yaml ↔ runbook file 매칭 테스트 | `planned` |
|
|
| `runbook://{area}/{scenario}` → `docs/runbooks/{area}-{scenario}.md` resolve 규칙으로 모든 scheme link 가 실제 파일에 도달 | grep 2026-06-14: 40 link 중 6 resolve / **34 orphan** / 파일 4 unref(consolidated) — resolve 규칙과 실제 파일 granularity 불일치 | resolver 구현 + per-code↔consolidated alias 표 결정 후 40 link 전수 resolve 테스트 | `needs-confirmation` |
|
|
| Link-check smoke 가 runbook target 파일 존재 + placeholder string (TODO/TBD/PLACEHOLDER/FIXME) 미포함 검증 | link-check 도구 선정 필요 | markdown-link-check / lychee 도입 + grep 기반 placeholder 검사 추가 | `planned` |
|
|
| Alert deduplication window 5분, flapping suppression 15분 내 3회 toggle → 30분 mute 정량값이 operator burnout 방지에 효과적 | 정량값 외부 reference 부재 (D5/D8). **OUT_OF_BRANCH_SCOPE** — alert-routing owner 에서 검증 | Alertmanager silencing 정책 적용 + on-call 회고로 burnout 지표 측정 (alerting branch) | `planned` |
|
|
| ca-tmpl P1/P2/P3 와 PagerDuty High/Medium/Low 매핑이 일관됨 | `PD-RB-C5` Usage Boundary 명시: "1:1 매핑 보장 안 됨". severity owner = `feature-metrics-alerting-contract` | severity 매핑 표 작성 + on-call SLA 정합 검토 (alerting branch) | `planned` |
|
|
| Alert payload 5 field (operation, dependency_name, error.code, error.category, runbook_link) 가 P1/P2 모두 채워짐 | raw 인용 (PagerDuty) 은 alert body description 까지만 보장, 5 field enumeration 은 ca-tmpl 자체 결정 | Prometheus rule yaml parse + 5 field 존재 verify (테스트 계약) | `planned` |
|
|
| Maintenance window 시 P2/P3 mute, P1 유지 정책이 incident 누락 없이 동작 | maintenance window 정책의 외부 reference 부재 (D6). **OUT_OF_BRANCH_SCOPE** | maintenance window simulation 테스트 + 누락 alert log 분석 (alerting branch) | `planned` |
|
|
| 우아한형제들 사례 (장애 유형별 runbook 분리, postmortem→runbook update) 가 본 branch CI gate 와 정합 | raw URL 교체 보류 — `WW-RB-C5` 가 `needs-confirmation` | raw URL `4886` 교체 또는 별도 raw 분리 후 verbatim 재인용 + 비교 재작성 | `needs-confirmation` |
|
|
| Atlassian "Runbooks as Code / version-controlled / peer-reviewed" 권고가 ca-tmpl git-hosted markdown 정책 정합 | `ATL-RB-C5` 가 `needs-confirmation` (원본 URL 404) | archive.org 스냅샷 또는 별도 Atlassian 페이지 (handbook chapter) 재확보 | `needs-confirmation` |
|
|
|
|
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
|
|
|
> `/coverage` 가 채우는 **생성물** — 손으로 유지하지 않는다. governing 문서(frontmatter `governing_docs`: `wiki/projects/ca-tmpl/observability-log-metric-trace-runbook` §Runbook)가 요구하는 관심사를 이 브랜치가 빠짐없이 덮는지의 결과. 기준: `rules/coverage-gate.md`.
|
|
> 상태: `covered-here`(이 브랜치 결정) / `delegated`(다른 owner 브랜치) / `missing`(아무도 안 맡음 → Blocking).
|
|
|
|
> 2026-06-14 coverage-auditor 판정: **Covered** (missing 0 / Blocking 0 / Should-fix 2 / Advisory 1). governing = `observability-log-metric-trace-runbook` §Runbook.
|
|
|
|
| 관심사 | 상태 | owner | 심각도 | 근거 |
|
|
|--------|------|-------|--------|------|
|
|
| `runbook://` scheme + repo-path 매핑 | covered-here | — | OK | D3 + §구현 가이드 §1 |
|
|
| alert payload 에 runbook_link field | covered-here | — | OK | D1 + §테스트 계약 |
|
|
| link-check / drift 검증 | covered-here | — | Should-fix (`planned`) | D4 + §구현 가이드 §3 — 도구 미도입 + stub gap |
|
|
| error-registry ↔ runbook coverage gate | covered-here | — | Should-fix (`planned`) | D10 + §구현 가이드 §2 — `retryable=true` 절 D10 누락(COVERAGE_DRIFT) |
|
|
| runbook 본문 구조 (7-section 표준) | covered-here (deferred) | — (Phase D2) | Should-fix | D9 — 본문은 Phase D2; outbox branch 가 `D15` 로 참조하나 **D15 미존재**(dangling, Audit) |
|
|
| alert severity P1/P2/P3 정의 + threshold | delegated | [[raw/branch-notes/feature-metrics-alerting-contract]] | OK | §P1/P2/P3 정량 기준 (governing §Metric 위임) |
|
|
| alert dedup / flapping / maintenance-window mute | delegated (owner 미지정) | [[raw/branch-notes/feature-metrics-alerting-contract]] 또는 신규 alert-routing | Advisory | D5/D6/D8 OUT_OF_BRANCH_SCOPE (Audit) |
|
|
| custom scheme 외부 채널(PagerDuty/Slack) 렌더링 | covered-here | — | Advisory (`needs-confirmation`) | §Claims To Verify — PagerDuty/Opsgenie PoC |
|
|
|
|
## 완료 후 wiki 추출 대상
|
|
|
|
- `wiki/projects/ca-skeleton-operational-contract.md`의 operational runbook canonical section.
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음(문서 단계).
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/company-tech-blogs/metric-toss-payments-alert-severity-techblog]]
|
|
- [[raw/company-tech-blogs/runbook-atlassian-gitops-runbook-as-code]]
|
|
- [[raw/company-tech-blogs/runbook-woowahan-incident-techblog]]
|
|
- [[raw/official-docs/google-sre-workbook-on-call-monitoring]]
|
|
- [[raw/official-docs/lychee-link-checker]]
|
|
- [[raw/official-docs/prometheus-alertmanager-silences]]
|
|
- [[raw/official-docs/runbook-pagerduty-incident-response-doc]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: blog-topics:start -->
|
|
- [[raw/blog-topics/runbook-coverage-junit-contract-test-2026-07-02]]
|
|
<!-- GENERATED: blog-topics:end -->
|
|
|
|
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
|
|
|
|
### 오류 기록 (본 feature 작업 중 발생)
|
|
|
|
- (없음 — D1 구현 중 오류 없음. `./gradlew :app-bootstrap:test` PASS 확인.)
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
- (없음 — 별도 추출할 면접 질문 없음. CI gate 설계 패턴은 blog-topics 후보로 충분.)
|
|
|
|
### 블로그·채용공고 연계 글감
|
|
|
|
- "JUnit 테스트로 운영 runbook coverage gate 구현하기 — Gradle task 대신 테스트를 선택한 이유" (D1 구현 결정 근거: 병렬 feature 간 root build.gradle 충돌 회피)
|
|
|
|
## 관련 일일 노트
|
|
|
|
> 이 브랜치를 작업한 날짜들. 양방향 nav 유지.
|
|
|
|
- (없음 — 캡처 시 추가)
|
|
|
|
## 완료 후 정리
|
|
|
|
> 머지/종료 시점에 채움.
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목:
|
|
- `locally-verified` 항목:
|
|
- `prod-verified` 항목:
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|