Files
llm-wiki/raw/branch-notes/feature-keycloak-four-pattern-tradeoff-matrix.md
T

232 lines
20 KiB
Markdown

---
title: branch / feature-keycloak-four-pattern-tradeoff-matrix
source_type: branch-note
status: raw
id: BR-KEYCLOAK-PATTERNS-OVERVIEW-019
kind: project-work-item
project: keycloak-patterns-overview
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-019
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1, DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1]
refines: []
overrides: []
depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-003, WI-KEYCLOAK-PATTERNS-OVERVIEW-008, WI-KEYCLOAK-PATTERNS-OVERVIEW-010, WI-KEYCLOAK-PATTERNS-OVERVIEW-012]
imports: []
delegates: []
accepts_delegations: []
contract_packet: 1
contract_packet_sha256: d06cbb2df49a1cb669394e4f97e96c796f4c2fdfed0d7fd63021b63af73e8234
branch: feature-keycloak-four-pattern-tradeoff-matrix
parent_branch:
related_projects: [keycloak-patterns-overview]
tags: [branch]
created: 2026-07-23
target_merge:
status_label: in-progress
---
# branch: feature-keycloak-four-pattern-tradeoff-matrix
<!-- section-id: branch-parent -->
## 부모 (필수)
- [[raw/project-notes/keycloak-patterns-overview]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: 4패턴 비교표의 모든 cell이 구현 WI evidence를 가리킨다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | done-bar는 E2E success와 signature security failure 재현·해결 evidence다 | `WI-KEYCLOAK-PATTERNS-OVERVIEW-019` 완료 조건에 적용 | `[[raw/project-notes/keycloak-patterns-overview]]` |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | `WI-KEYCLOAK-PATTERNS-OVERVIEW-019` 완료 조건에 적용 | `[[raw/project-notes/keycloak-patterns-overview]]` |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- GENERATED: artifact-imports:start -->
### 가져온 artifact 계약
| Artifact Ref | Owner | Producer | Schema Ref |
|---|---|---|---|
<!-- GENERATED: artifact-imports:end -->
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
<!-- GENERATED: project-contract-imports:end -->
<!-- GENERATED: received-delegations:start -->
### 수신한 위임
| Delegation Ref | From | Concern | Status |
|---|---|---|---|
<!-- GENERATED: received-delegations:end -->
<!-- GENERATED: flow:start -->
### 가져온 흐름 단계
| Stage Ref | Order | Owner | Input | Action | Output |
|---|---:|---|---|---|---|
<!-- GENERATED: flow:end -->
<!-- section-id: branch-goal -->
## 목표
- `WI-KEYCLOAK-PATTERNS-OVERVIEW-019`의 완료 조건을 구현한다: 4패턴 비교표의 모든 cell이 구현 WI evidence를 가리킨다
- 산출물: AP1~AP4 인증 통합 아키텍처의 **트레이드오프 매트릭스** — {토큰 위치 · 인증 강제/검증 주체 · XSS/CSRF surface · 선택 기준 · keycloak 설정} 을 한 표로, 각 cell 은 source claim 또는 구현 WI evidence 를 가리킨다 (§구현 가이드 1 이 표의 owner).
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- Work Item 완료 조건
- 매트릭스 스켈레톤(행·열·이론 근거 cell) 작성 — 행 정의는 project `AUTH-TAXONOMY` 결정 소비, cell 근거는 raw source claim 직접 인용 (D1·D2·D4)
- Evidence cell 채움 규칙 정의 — 의존 WI 완료 시 어떤 증거를 어떤 등급으로 링크하는지 (D3)
### 제외 범위
> 의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.
- project decision registry 변경
- 각 패턴의 실 구현·함정 재현 — 의존 WI(003·008·010·012 및 그 자식들) 소유 (`OUT_OF_BRANCH_SCOPE`)
- Google IdP brokering 을 별도 행으로 다루는 것 — cross-cutting 변형은 project §2.2 소유, 본 표에는 비고 1줄만 (D1)
- 패턴별 세부 설정값(SameSite 값, NetworkPolicy 명세 등) — 해당 구현 WI branch 소유 (`OUT_OF_BRANCH_SCOPE`)
## 근거 (필수, 최소 1개+)
> 이 branch의 구현·설계 결정의 **근거가 되는 외부 자료**. 공식 문서·대기업 기술 블로그·강의 등 raw 자료를 인용.
| Source | 정당화하는 결정 |
|---|---|
| `[[raw/official-docs/oauth2-browser-based-apps-ietf-draft]]` | D1 행 구성(BFF/TMB/Browser-client 3패턴 정의 C1~C3) · D4 선택 기준 서열(C4 보안 내림차순, C5 TMB 경량 절충) |
| `[[raw/official-docs/oauth2-proxy-overview-config-official]]` | D1 의 AP4 행(IETF 3종 밖 별도 패턴 — 검증된 C4 헤더 전달·C5 OIDC issuer 로 지지; reverse-proxy 일반동작 C1 은 source 재확인 실패로 `needs-confirmation` → 앵커 제외) · 매트릭스 AP4 행 cell(C2 옵션 활성 시 access 헤더 전달, C4 헤더 전달, C5 OIDC issuer 설정) |
| `[[raw/official-docs/owasp-html5-storage-xss-spa]]` | D4 · 매트릭스 XSS surface 열의 AP1 행(C1 localStorage 세션 금지, C2 XSS 1건 전체 탈취) |
| `[[raw/official-docs/spring-security-resource-server-jwt]]` | 매트릭스 AP1 행의 검증 주체·keycloak 설정 cell(C1 issuer-uri 검증, C6 audiences 검증) |
| `[[raw/company-tech-blogs/curity-bff-pattern-spa]]` | D4 의 AP3 선택 조건(C1 토큰을 브라우저 밖에, C6 refresh 탈취 위험) — **vendor 사례, 공식 기준 승격 금지**(IETF C4 와 결합해서만 사용) |
## TODO
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
- [x] 매트릭스 스켈레톤 + source claim 근거 연결 (§구현 가이드 1) — 등급: `documented-only`
- [ ] Evidence cell 에 적힌 모든 WI(anchor 003·008·010·012 + 자식 004·005·006·009·011·013·014) 완료 시 구현 증거 링크·등급으로 교체 (§구현 가이드 2 절차, D3 선택 조건) — 등급: `planned`
- [ ] 4패턴 비교표의 모든 cell이 구현 WI evidence를 가리킨다 (완료 조건) — 등급: `planned`
## 진행 중 메모
- 2026-07-23 `NO_GROUND_TRUTH`: 구현 repo `/home/donghyeon/workspace/keycloak-pattern/`(단수) 는 README only — 구현 코드 없음(2026-07-25 재확인), 의존 anchor WI 4개(003·008·010·012) 전부 `planned`. 따라서 본 세션 산출은 **이론 골격 + 근거 연결까지** — Evidence cell 은 전부 `planned(WI-NNN)` placeholder.
## 결정 사항
> 추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. 상세는 아래 `결정-근거 매핑` D-row 가 소유.
- 2026-07-23: D1 행 = AP1~AP4 4행(Google 은 비고) / 이유: 인증 아키텍처 축이 canonical / 대안: 구 6패턴(배포×federation) 축 / 근거: `[[raw/official-docs/oauth2-browser-based-apps-ietf-draft]]`
- 2026-07-23: D2 열 = project §5 의 5열 + Evidence 열 / 이유: 완료 조건이 cell→WI evidence 연결 요구 / 근거: 상속 `ACCEPTANCE-001@1`
- 2026-07-23: D3 Evidence cell 규칙 = `planned(WI-NNN)` → 구현 후 `[[wi-slug]] · 등급` / 이유: 미검증 셀의 등급 과장 차단 / 근거: 상속 `ACCEPTANCE-001@1` + CLAUDE.md §6
- 2026-07-23: D4 선택 기준 열 = IETF 보안 내림차순 + 조건 분기 / 이유: 벤더 중립 서열 존재 / 대안: 벤더 블로그 권고 서열 / 근거: `[[raw/official-docs/oauth2-browser-based-apps-ietf-draft]]`
<!-- section-id: decision-evidence -->
## Decision Evidence Map / 결정-근거 매핑
> `선택 조건` 열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면 `N/A`.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 매트릭스 행은 AP1~AP4 4행으로 구성하고, Google IdP brokering 은 행이 아니라 비고 1줄로 둔다. 행 정의 자체는 project `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` 소비(재정의 아님). | 분류 축이 "누가 토큰을 쥐고 누가 인증을 강제하나"인 동안 이 결정 / 배포 토폴로지·federation 축으로 비교하려면 구 6패턴 표(project 가 2026-07-14 §2.3 으로 강등)로 회귀 | `raw/official-docs/oauth2-browser-based-apps-ietf-draft.md#OAUTH-BBA-C1`, `#OAUTH-BBA-C2`, `#OAUTH-BBA-C3`, `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C4`, `#OAUTH2PROXY-C5` | `official-standard` (IETF 3패턴) + `official-vendor-doc` (AP4 — 검증된 C4·C5; C1 은 소스 재확인 실패로 `needs-confirmation`, 앵커 제외) | AP4 는 IETF 3종 밖 — oauth2-proxy 가 draft 의 BFF 정의와 1:1 이라는 증거 없음(source 의 "Does not prove" 명시). AP4 행 각주로 경계 유지, 검증은 `검증해야 할 주장` #1 |
| D2 | 매트릭스 열은 project §5 가 정한 {토큰 위치 · 검증 주체 · XSS/CSRF surface · 선택 기준 · keycloak 설정}([[raw/project-notes/keycloak-patterns-overview]] 소유)에 **Evidence 열 1개를 추가**한 6열로 한다. | 완료 조건이 "모든 cell 이 구현 WI evidence 를 가리킨다"인 동안 Evidence 열 필수 / 이론 비교표만 필요하면 5열로 충분하나 그 경우 본 WI 완료 조건 미충족 | 상속 `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1`, `raw/official-docs/oauth2-browser-based-apps-ietf-draft.md#OAUTH-BBA-C4` (선택 기준 열의 서열 축) | `project-decision` + `official-standard` | 열 이름·순서는 project §5 문구 개정 시 함께 갱신 필요(pointer 이므로 비차단 전파 대상) |
| D3 | Evidence cell 은 `planned(WI-NNN)` placeholder 로 시작하고, 해당 WI 의 E2E + signature 함정 재현·해결 증거 확보 후에만 `[[raw/branch-notes/<wi-slug>]] · locally-verified` 형태로 교체한다. `documented-only` 링크로는 완료 조건 미충족. | Evidence cell 에 적힌 WI 가 하나라도 `planned`/미검증인 동안 이 규칙 / **Evidence cell 에 적힌 모든 WI**(anchor 4개 + 각 행의 자식 WI: 004·005·006·009·011·013·014)가 `locally-verified` 도달 시 전 cell 교체 후 본 branch 완료 선언 | 상속 `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` (done-bar = E2E + 함정 재현·해결 evidence) | `project-decision` | `NO_GROUND_TRUTH`(2026-07-23): 구현 repo 자체가 미존재 — 등급 판정 기준이 실측 대상 없이 문서 규칙으로만 존재 |
| D4 | '선택 기준' 열은 IETF 보안 내림차순(AP3 BFF > AP2 TMB > AP1 Browser-client; AP4 는 서열 밖 — 백엔드 인증코드 0줄 + 네트워크 격리 의존)을 기본 서열로 하고, 상황 분기(토큰 브라우저 노출 금지→AP3 / 프록시 전량 경유 부담→AP2 / polyglot 다수 백엔드 균일 인증→AP4[heuristic] / 학습·최단 셋업→AP1+PKCE)를 병기한다. | 벤더 중립 서열이 필요한 동안 IETF 서열 채택 / 특정 벤더 스택 고정 상황이면 해당 벤더 권고(예: curity)를 1차 근거로 쓸 수 있으나 본 프로젝트는 표준 우선 | `raw/official-docs/oauth2-browser-based-apps-ietf-draft.md#OAUTH-BBA-C4`, `#OAUTH-BBA-C5`, `raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C1`, `#CURITY-BFF-C6`, `raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C1`, `#OWASP-HTML5-C2` | `official-standard` 주도 + `company-case-study` 보조 | IETF 서열은 정성적("decreasing order of security") — 정량 근거 아님. curity C1("유일한 방법")은 vendor 과장 가능성 → IETF C4 와 결합해서만 인용 (`검증해야 할 주장` #4). polyglot→AP4 분기는 외부 claim 미보유(project §3-1-1·§기술 결정 AP4 행 documented-only 앵커 소비) — `UNSUPPORTED_IMPL_DECISION`(§구현 가이드 1 라벨 ③) |
<!-- section-id: implementation -->
## 구현 가이드
> **3-rule**: R1 Reference 필수 · R2 UNSUPPORTED_IMPL_DECISION 명시 · R3 OUT_OF_BRANCH_SCOPE 정제 (CLAUDE.md §15.5)
### 1. 4패턴 트레이드오프 매트릭스 (본 branch 의 deliverable 스켈레톤)
> **Trace**: 행 구성=D1(OAUTH-BBA-C1~C3, OAUTH2PROXY-C4·C5) · 열 구성=D2(ACCEPTANCE-001@1) · '선택 기준' cell 내용=D4(OAUTH-BBA-C4·C5, CURITY-BFF-C1·C6, OWASP-HTML5-C1·C2) · Evidence cell=D3
>
> - **UNSUPPORTED_IMPL_DECISION**: ① Evidence placeholder 표기를 `planned(WI-NNN)` 텍스트로 통일 — 근거 raw 는 표기 형식을 권고하지 않음. trade-off: 후속 스크립트/사람이 `planned(` prefix 로 미완 cell 을 grep 가능. ② AP4 토큰 위치 cell 의 "세션은 프록시, 백엔드·브라우저 토큰 0" — 세션 저장 위치를 진술하는 official claim 미보유, project §2.1 AP4 행(project-decision) 소비 + 기본 구성 가정. ③ D4 의 polyglot 다수 백엔드→AP4 분기 — 외부 source claim 미보유이나 project §3-1-1 P1A 트레이드오프 + §기술 결정 AP4 행("polyglot 균일 적용", project-decision·documented-only) 앵커 소비. 순수 heuristic 아님 — 단 정량 근거는 없음.
| AP | 토큰 위치 | 인증 강제·검증 주체 | XSS/CSRF surface | 선택 기준 | keycloak 설정 | Evidence |
|---|---|---|---|---|---|---|
| **AP1** SPA-direct + RS | 브라우저(JS) — `OAUTH-BBA-C3` | SPA(public client+PKCE)가 토큰 획득, RS 가 JWT `iss`/`aud` 검증 — `SSRS-JWT-C1`·`C6` | XSS 1건 = 저장 토큰 전체 탈취; localStorage 세션 보관 금지 — `OWASP-HTML5-C1`·`C2` | 학습·최단 셋업, 프론트가 OIDC 완전 제어; 보안 서열 최하 — `OAUTH-BBA-C4` | public client + PKCE, RS `issuer-uri`/`audiences``SSRS-JWT-C1`·`C6` | `planned(WI-003·004·005·006)` |
| **AP2** Token-Mediating Backend | access→브라우저, refresh→백엔드만 — `OAUTH-BBA-C2` (refresh 보관 위치 문언은 draft §6.2 서술, 실측은 검증 #2·WI-009) | 백엔드(confidential client)가 토큰 획득, 브라우저가 RS 직접 호출 — `OAUTH-BBA-C2` | refresh 는 보호되나 access 는 브라우저 노출 — `OAUTH-BBA-C5`, `CURITY-BFF-C6` | BFF 전량 프록시 부담 없는 경량 절충(BFF 보다 덜 안전, browser-client 보다 안전) — `OAUTH-BBA-C5` | confidential client(+secret), 브라우저에 access 만 전달 — `OAUTH-BBA-C2` | `planned(WI-008·009)` |
| **AP3** BFF | 백엔드 세션(브라우저 토큰 0개) — `OAUTH-BBA-C1`, `CURITY-BFF-C3` | 백엔드(confidential client)가 인증+전량 프록시 — `OAUTH-BBA-C1` | 토큰 XSS 면역 — `CURITY-BFF-C4`(httpOnly cookie); cookie 자동첨부 → CSRF surface(방어는 WI-011 소유) — 논리 도출(project §3-2 AP3 시퀀스 CSRF alt-block; C4 는 Does-not-prove 로 CSRF/SameSite 미지지) | 토큰 브라우저 노출 금지 요건일 때; 보안 서열 최상 — `OAUTH-BBA-C4`, `CURITY-BFF-C1` | confidential client + httpOnly session cookie — `CURITY-BFF-C4` | `planned(WI-010·011)` |
| **AP4** Edge forward-auth | 프록시 세션(백엔드·브라우저 토큰 0 — `--pass-access-token` 미사용 기본 구성 시, `OAUTH2PROXY-C2` 옵션 활성 시 access 가 upstream 헤더로 전달됨) — project §2.1 AP4 행 소비, `UNSUPPORTED_IMPL_DECISION` ② | 별도 reverse proxy 가 OIDC 수행, 백엔드는 전달 헤더 신뢰(인증코드 0줄) — `OAUTH2PROXY-C4`·`C5` | `X-Forwarded-User` 위조 — 헤더 신뢰 모델에서 논리 도출(위협 재현·차단은 WI-014 완료 조건 소유, project §8.0) | polyglot 다수 백엔드에 균일 인증[heuristic — UNSUPPORTED ③]; IETF 3종 밖 실무 패턴 — D1 Open Risk 참조 | proxy `--oidc-issuer-url` (discovery), `--pass-user-headers``OAUTH2PROXY-C4`·`C5` | `planned(WI-012·013·014)` |
- 비고(행 아님): **Google IdP brokering** 은 어느 AP 에도 realm 설정만으로 얹히는 cross-cutting 변형 — 정의·함정은 [[raw/project-notes/keycloak-patterns-overview]] §2.2 소유.
- R3 정제: 각 cell 의 세부 방어 구현(SameSite 값, NetworkPolicy 명세, audience validator 코드)은 Evidence 에 적힌 구현 WI branch 소유 — 본 표는 pointer 만 유지.
### 2. Evidence cell 채움 절차 (구현 WI 완료 시)
> **Trace**: D3 (ACCEPTANCE-001@1 도출). 절차만 정의 — 실행은 각 WI 완료 시점.
>
> - **UNSUPPORTED_IMPL_DECISION**: cell 교체 단위를 "anchor WI 묶음"이 아니라 **개별 WI**로 함 — 근거 raw 없음. trade-off: 부분 진행을 표에 즉시 반영(전량 대기 시 표가 오래 stale). 다중-WI cell 의 혼합 상태 표기 예: `[[raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce]] · locally-verified + planned(WI-004·005·006)`.
1. 해당 WI branch 의 `## 완료 후 정리` 에서 E2E 증거(200 OK 로그/스크린샷) + signature 함정 before/after 확인.
2. 증거 등급 판정 — `locally-verified` 이상만 인정 (`documented-only` 는 완료 조건 미충족, D3).
3. cell 의 `planned(WI-NNN)``[[raw/branch-notes/<wi-slug>]] · locally-verified` 로 교체.
4. 전 cell 교체 완료 시 본 branch TODO 최종 항목 체크 → `/ingest``wiki/projects/` 추출 후보.
<!-- section-id: edge-failure-dependency -->
## 엣지·실패·의존
- **실패·엣지 경로**:
- 의존 WI 부분 완료 — cell 단위 독립 교체(§구현 가이드 2), 미완 cell 은 `planned(...)` 유지. 표 전체를 블록하지 않음.
- project taxonomy 개정(`AUTH-TAXONOMY-001` @2 발행) — packet 이 @1 고정이므로 preflight 가 `STALE_INHERITANCE_REVISION` 으로 차단 → 행 구성 재검토 후 packet 재생성.
- AP4↔IETF-BFF 매핑 드리프트 발견(검증 #1 실패) — 매트릭스 AP4 행 각주 갱신 + D1 Open Risk 재판정. 표 삭제 아님.
- **다른 계약 의존**: `WI-KEYCLOAK-PATTERNS-OVERVIEW-003`(AP1 anchor), `WI-KEYCLOAK-PATTERNS-OVERVIEW-008`(AP2), `WI-KEYCLOAK-PATTERNS-OVERVIEW-010`(AP3), `WI-KEYCLOAK-PATTERNS-OVERVIEW-012`(AP4) — frontmatter `depends_on` 은 이 anchor 4개(project registry row 소유). Evidence cell 은 anchor 의 자식 WI(004·005·006·009·011·013·014)도 인용하므로 **완료 선언은 cell 에 적힌 모든 WI 기준**(D3 선택 조건). anchor 완료 조건이 바뀌면 §구현 가이드 2 의 판정 기준도 재검토.
<!-- section-id: claims-to-verify -->
## 검증해야 할 주장 / Claims To Verify
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| AP4(oauth2-proxy)가 IETF BFF 요건(draft §6.1.3)과 어디서 갈라지는지 | source 가 "1:1 매핑 미증명"을 명시(OAUTH-BBA usage boundary) | WI-012 구현 후 draft §6.1.3 MUST 항목 체크리스트 대조 | `needs-confirmation` |
| AP2 에서 refresh token 이 브라우저 network 응답에 나타나지 않는다 | IETF 정의일 뿐 우리 구현의 실측 아님 | WI-009 완료 조건(network 탭/response body 검사)으로 검증 | `planned` |
| 매트릭스 각 행의 이론 서술이 single-EC2 실구현에서 재현된다 | `NO_GROUND_TRUTH` — 구현 repo 미존재, anchor WI 전부 planned | 의존 WI 4개의 E2E + 함정 재현 evidence 로 cell 단위 검증 | `planned` |
| "토큰을 브라우저 밖에 두는 것이 유일한 보호 방법"(curity C1)의 일반화 | vendor 블로그 표현 — IETF 는 3패턴 모두 trade-off 로 허용(C4) | IETF draft §6.3.2 방어 요건과 대조해 한정 서술(AP3 선택 조건)로만 유지 | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
`/coverage` 실행 전.
## 마주친 문제
아직 없음.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: branches:start -->
<!-- GENERATED: branches:end -->
## 관련 일일 노트
해당 없음.
## 완료 후 정리
- PR 링크:
- 리뷰 메모: