docs(dcy-031): OPS-005 이벤트 발행 신뢰성 Discovery + REQ-091~094

운영팀 OPS-005(이벤트 발행 실패) 코드검증 → dcy-031 confirm-discovery.
스코프 B(재시도/강제재연결)+A(idle keepalive)+D(DLQ 가시성/emit 경보).
REQ-091~094 draft-req → confirm-req 완료(4건 Approved). M2 트랙B.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-19 21:21:19 +09:00
co-authored by Claude Opus 4.8
parent 9e35467336
commit bc79f60274
7 changed files with 603 additions and 1 deletions
@@ -0,0 +1,105 @@
# req-091: 이벤트 발행 재시도 + 강제 재연결 (idle 후 첫 발행 무유실)
- Status: Approved
- Type: Exception
- Priority: High
- Owner: project maintainers
## Intent
- 발행 전용 RabbitMQ 연결이 idle heartbeat 사망(half-open) 상태에서 `is_open=True`를 신뢰한 채 `basic_publish`가 실패하면 이벤트가 재시도·DLQ 없이 조용히 유실된다. 이 결함을 제거해, idle 후 첫 발행 시도에서 연결이 죽어 있어도 강제 재연결 후 1회 재시도로 이벤트가 유실되지 않도록 한다. tsl(M2) 라이브 선행 조건인 슬롯 전이 이벤트 신뢰성을 보장한다.
## Requirement
- `publish_event`(`adapters/messaging/rabbitmq.py`)는 아래 재시도 정책을 구현한다.
- 발행(`basic_publish` + publisher confirms 대기) 실패 시 — 예외 종류와 무관하게 — 강제 재연결(`_ensure_connected`가 아니라 명시적 `close` 후 재연결)을 수행한 뒤 동일 메시지를 1회 재시도한다.
- 재시도까지 실패한 경우에만 `TransportUnavailableError`를 호출자에게 올린다.
- `is_open` 플래그 기반 조기 반환(`_ensure_connected` 단독 판단)에 의존하지 않는다 — `is_open=True`라도 발행이 실패하면 강제 재연결 경로를 탄다.
- 재시도 1회에는 명시적 타임아웃 상한을 적용해 execution-loop를 과도하게 블록하지 않는다(NFR-2 흡수).
- 이 변경은 `adapters/messaging/rabbitmq.py`의 `publish_event` 경로에 국소화하며, consume/RPC 경로(`call_rpc`, consumer 스레드), `declare_topology`, 슬롯 도메인/broker 집행 로직을 건드리지 않는다(NFR-3 흡수).
- M1 운영 배포본(`9e35467`)의 wire 계약 — `trex.events` exchange 토폴로지, routing key 형식, 페이로드 스키마, consumer RPC 큐(intent/slots/control) — 은 불변이다(NFR-1 흡수).
## Acceptance Criteria
1. Given 발행 전용 연결이 idle 60초 이상 경과하여 서버 측 종료(half-open) 상태이고, When `publish_event`가 호출되면, Then 강제 재연결 후 동일 이벤트를 1회 재시도해 발행에 성공하며 `TransportUnavailableError`가 발생하지 않는다(S-1 연결).
2. Given 발행 전용 연결이 half-open이고 재시도 1회도 실패하는 조건에서, When `publish_event`가 호출되면, Then `TransportUnavailableError`가 호출자에 전파되고, 이벤트 유실은 인지된 실패로 처리된다(재시도 없이 삼키던 기존 동작 제거).
3. Given 발행 재시도 타임아웃 상한이 설정되어 있고, When 재연결 또는 재시도 중 타임아웃이 초과하면, Then 해당 publish 시도는 실패로 처리되며 execution-loop 블록이 명시적 상한을 초과하지 않는다(NFR-2 — 타임아웃 값은 design 단계에서 확정).
4. Given 정상 발행(연결 살아 있음) 조건에서, When `publish_event`가 호출되면, Then 기존과 동일하게 publisher confirms + `trex.events` exchange에 발행되며, 재시도 경로가 추가 레이턴시를 유발하지 않는다(회귀 무).
5. Given `call_rpc` / intent consumer / slots-rpc consumer / control-rpc consumer 경로에서, When 연결 상태를 확인하면, Then 이 REQ의 변경이 해당 경로의 동작에 영향을 주지 않는다(consume/RPC 경로 무회귀, NFR-1).
6. Given RabbitMQ 브로커가 재시작된 후 발행 전용 연결이 끊긴 상태에서, When 슬롯 전이 이벤트가 발행 시도되면, Then 강제 재연결 후 재시도로 이벤트가 성공적으로 전달된다(S-2 연결).
## Impacted Area
- Docs: docs/runtime-flows.md(`슬롯 이벤트 발행/구독` 흐름 — 재시도/재연결 경로 추가 기재, REQ-094에서 갱신)
- Modules: `trex_core/adapters/messaging/rabbitmq.py`(`publish_event` L168-197, `_ensure_connected` L94-103). 슬롯 도메인/broker 집행/consume 경로 불변.
- Tests: `tests/adapters/messaging/` — half-open 연결 simulate 후 publish 성공 확인(AC-1), 재시도 소진 후 실패 확인(AC-2), 타임아웃 상한 확인(AC-3), 정상 발행 회귀(AC-4), consume/RPC 경로 회귀(AC-5), broker 재시작 시나리오(AC-6). `tests/application/test_messaging_event_publisher.py` 기존 회귀 유지.
## Notes
- 출처: dcy-031 FR-1(B), NFR-1, NFR-2, NFR-3, R-1, S-1, S-2.
- NFR-2(타임아웃 상한 값)와 강제 재연결 구체 구현 방식(close-and-reconnect vs. selective channel reopen)은 draft-batch-design에서 결정한다. 본 REQ는 "1회 재시도 + 타임아웃 상한" 정책만 확정하며 수치 기준은 AC-3 Notes에서 "design 단계 결정"으로 추적한다.
- OQ-1(keepalive 스레드 위치) 결정은 REQ-092에 귀속하며 본 REQ와 독립이다.
- 재시도 at-least-once 의미(중복 발행 가능성)는 OQ-2로 추적 중 — 소비자(orb/tsl) 멱등성은 delivery 검증(DATA) 단계에서 확인한다.
- R-1(M1 라이브 회귀): 본 batch는 opus qc-reviewer 강제(CONFIRM ①, dcy-031 §10).
## Change Log
### CHG-20260619-01
- Date: 2026-06-19
- Author: trex-core-dev
#### Change Summary
- dcy-031 FR-1(B) 로부터 최초 생성(Proposed). NFR-1·NFR-2·NFR-3 흡수. S-1·S-2 연결.
#### Intent
- 이벤트 발행 재시도 + 강제 재연결 정책을 backlog에 정규화한다.
#### Acceptance Criteria Delta
- Added:
- AC-1: idle 후 첫 발행 재연결+재시도 성공
- AC-2: 재시도 소진 후 TransportUnavailableError 전파
- AC-3: 타임아웃 상한으로 execution-loop 과블록 방지
- AC-4: 정상 발행 경로 회귀 무
- AC-5: consume/RPC 경로 회귀 무
- AC-6: broker 재시작 후 발행 복구
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md (재시도/재연결 경로)
- Modules:
- trex_core/adapters/messaging/rabbitmq.py
- Tests:
- tests/adapters/messaging/ (half-open/재시도/타임아웃/회귀)
#### Delivery Trace Delta
- Batches:
- none (draft-req 단계, batch 미편성)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 suggest-batch-reqs + draft-batch 편성
#### Validation Plan
- stub 또는 mock 기반 half-open 시뮬레이션 후 publish 재시도 성공/실패 시나리오 검증. 기존 messaging 회귀 스위트 전체 통과 확인.
### CHG-20260619-02
- Date: 2026-06-19
- Author: 팀장 (confirm-req)
#### Change Summary
- Proposed → **Approved**. 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 충족, AC 구체·측정 가능, source dcy-031(confirmed, Open OQ 0) 정합, blocker 없음.
#### Intent
- delivery input 승인. NFR-2 타임아웃 수치는 비핵심 운영 메모로 draft-batch-design 확정(승인 비차단).
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs + draft-batch (REQ-091~094 단일 batch 편성 예정)
@@ -0,0 +1,102 @@
# req-092: 발행 전용 연결 idle keepalive (서버 측 heartbeat 사망 예방)
- Status: Approved
- Type: Exception
- Priority: High
- Owner: project maintainers
## Intent
- 발행 전용 RabbitMQ 연결은 consumer 루프(`process_data_events` 0.2s 틱)와 달리 publish 사이에 I/O가 전혀 없어 `heartbeat=30` 설정에도 불구하고 ~60초 idle 후 서버가 연결을 종료한다. keepalive로 idle 구간에도 heartbeat I/O가 나가도록 해 서버 측 연결 종료를 사전 예방하고, REQ-091 재시도 경로를 보완하는 2차 방어선을 구성한다.
## Requirement
- 발행 전용 연결에 idle keepalive를 도입해, heartbeat 주기(`heartbeat=30`) 내에 `process_data_events`가 최소 1회 이상 호출되도록 보장한다.
- keepalive는 pika `BlockingConnection`의 스레드 안전성 제약을 위반하지 않는다. 즉, 발행 전용 연결을 소유한 스레드에서만 keepalive I/O를 실행한다.
- keepalive의 구체 구현 방식(별도 keepalive 스레드 vs. execution-loop 틱에 삽입)은 draft-batch-design에서 OQ-1 기준으로 확정한다. 본 REQ는 "발행 연결 소유 스레드 내 I/O 보장" 정책만 확정하며, 구현 위치는 설계 단계 결정으로 추적한다.
- 이 변경은 `adapters/messaging/rabbitmq.py` 및 필요 최소 범위의 `runtime/daemon.py`에 국소화하며, consume/RPC 경로·슬롯 도메인·broker 집행 로직을 건드리지 않는다(NFR-3 흡수).
- M1 운영 배포본(`9e35467`)의 wire 계약은 불변이다(NFR-1 흡수).
## Acceptance Criteria
1. Given idle 구간이 `heartbeat × 2`(60초) 이상 지속될 때, When keepalive가 활성화되어 있으면, Then 발행 전용 연결이 서버에 의해 종료되지 않고 살아 있는 상태를 유지한다(S-1 1차 방어선).
2. Given keepalive 구현이 별도 스레드를 사용하거나 루프 틱에 삽입되는 경우, When 해당 I/O가 실행될 때, Then 발행 전용 연결을 소유한 스레드에서만 `process_data_events`가 호출되며, 다른 스레드가 동일 연결에 직접 I/O를 호출하지 않는다(pika 스레드 안전성 불변식).
3. Given keepalive가 동작 중이고 동시에 발행이 발생할 때, When 두 작업이 겹치면, Then 연결 상태 손상 또는 예외 없이 정상 완료된다(pika 스레드 안전성 불변식 보강).
4. Given keepalive 설정이 적용된 상태에서, When execution-loop의 매도 트리거 처리 타이밍을 확인하면, Then keepalive 틱이 execution-loop를 과도하게 블록하지 않는다(NFR-2 흡수 — 허용 블록 시간 상한은 design 단계 확정).
5. Given consumer 스레드(intent/slots/control RPC)를 확인할 때, When keepalive 변경이 적용된 코드베이스를 검사하면, Then consumer 경로의 동작·큐 바인딩·토폴로지에 변화가 없다(consume/RPC 무회귀, NFR-1).
6. Given keepalive 구현 방식이 설계 단계에서 확정된 후, When batch design 문서를 확인하면, Then OQ-1(keepalive 실행 위치·스레드 소유 모델)이 명시적으로 결정되어 있다(OQ-1 종료 조건 추적).
## Impacted Area
- Docs: docs/runtime-flows.md(`데몬 lifecycle` 흐름 — keepalive 틱 추가 기재, REQ-094에서 갱신). docs/project-structure.md(keepalive 컴포넌트 추가 시 반영, REQ-094에서 갱신).
- Modules: `trex_core/adapters/messaging/rabbitmq.py`(keepalive 메커니즘 추가). `trex_core/runtime/daemon.py`(keepalive 연동, 최소 변경). 슬롯 도메인/broker 집행/consume 경로 불변.
- Tests: `tests/adapters/messaging/` — idle 60초 후 연결 생존 확인(AC-1), 스레드 소유 검증(AC-2), 동시 발행+keepalive 충돌 없음(AC-3), execution-loop 블록 상한(AC-4), consume 경로 회귀(AC-5).
## Notes
- 출처: dcy-031 FR-2(A), NFR-1, NFR-2, NFR-3, R-2(keepalive pika 스레드 충돌), S-1.
- OQ-1(keepalive 구현 위치 — 별도 스레드 vs. execution-loop 삽입): 설계 단계 결정. AC-6에서 추적. 종료 조건: batch design 문서에 명시되면 Answered.
- R-2(pika 스레드 비안전성): 이 위험은 AC-2/AC-3으로 검증하며, 설계 단계(OQ-1)에서 구현 방식을 확정해 완화한다.
- keepalive는 REQ-091 재시도와 독립 계층이다. keepalive는 1차 예방(연결 사망 방지), REQ-091은 2차 복구(사망 후 재연결+재시도). 두 REQ는 함께 배치될 예정이지만 독립 구현·독립 검증 가능하다.
## Change Log
### CHG-20260619-01
- Date: 2026-06-19
- Author: trex-core-dev
#### Change Summary
- dcy-031 FR-2(A) 로부터 최초 생성(Proposed). NFR-1·NFR-2·NFR-3 흡수. R-2(pika 스레드) OQ-1 인계. S-1 연결.
#### Intent
- 발행 전용 연결 idle keepalive 정책을 backlog에 정규화하고, OQ-1 설계 결정 추적을 AC-6으로 고정한다.
#### Acceptance Criteria Delta
- Added:
- AC-1: idle 60초 후 연결 생존
- AC-2: 발행 소유 스레드에서만 I/O (스레드 안전성)
- AC-3: 동시 발행+keepalive 충돌 없음
- AC-4: execution-loop 블록 상한
- AC-5: consume/RPC 경로 무회귀
- AC-6: OQ-1 종료 조건 추적
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md, docs/project-structure.md
- Modules:
- trex_core/adapters/messaging/rabbitmq.py, trex_core/runtime/daemon.py
- Tests:
- tests/adapters/messaging/ (idle/스레드/동시성/블록/회귀)
#### Delivery Trace Delta
- Batches:
- none (draft-req 단계, batch 미편성)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 REQ-091과 함께 단일 batch 편성 예정
#### Validation Plan
- time-freeze 또는 sleep mock으로 idle 구간을 시뮬레이션한 후 연결 생존 + 스레드 안전성 검증. 기존 messaging 회귀 스위트 전체 통과 확인.
### CHG-20260619-02
- Date: 2026-06-19
- Author: 팀장 (confirm-req)
#### Change Summary
- Proposed → **Approved**. 필수 필드 충족, AC 구체, source dcy-031(confirmed, Open OQ 0) 정합. OQ-1(keepalive 실행 위치)은 AC-6에서 design 인계 추적 — 승인 비차단.
#### Intent
- delivery input 승인. keepalive 구현 위치(별도 스레드 vs execution-loop 틱)는 draft-batch-design에서 확정.
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: REQ-091과 함께 단일 batch 편성 예정
@@ -0,0 +1,104 @@
# req-093: DLQ 가시성/경보 + emit 실패 경보 (trex.events.dlq 관측성)
- Status: Approved
- Type: Exception
- Priority: High
- Owner: project maintainers
## Intent
- `trex.events.dlq`(unroutable + 발행 실패 양쪽)는 현재 durable 큐로 보존되지만 소비/알림 주체가 없어 운영에서 조용히 누적된다. 또한 `_make_event_emitter`의 발행 실패는 warning 로그로 삼켜져 운영자가 인지하지 못한다. DLQ 유입/깊이를 관측 가능하게 만들고, 최종 실패 시 식별 가능한 경보 레벨 로그를 남겨 운영 피드백 루프를 닫는다.
## Requirement
- `trex.events.dlq` 유입(unroutable + 발행 실패 양쪽)과 깊이(queue depth)를 운영에서 관측·경보 가능하게 한다. 구현 방식(메트릭 노출 / 로그 기반 / 소비자 데몬)은 draft-batch-design에서 OQ-3 기준으로 결정한다.
- unroutable 이벤트: `mandatory=True` → `UnroutableError` → `_publish_dlq`로 `trex.events.dlq`에 보존하는 기존 경로는 유지하되, DLQ 유입이 관측 가능해야 한다.
- 발행 실패 이벤트: REQ-091의 재시도까지 실패한 경우 `TransportUnavailableError`가 올라오면, 해당 실패를 DLQ(또는 별도 실패 경보 채널)에 추적 가능한 형태로 기록하거나 메트릭을 카운트한다. 구체 경로는 OQ-3 결정에 따른다.
- `_make_event_emitter`(`runtime/daemon.py`)의 발행 실패 처리를 보강한다: REQ-091 재시도 후에도 실패 시 단순 `WARNING` 삼킴이 아니라 `ERROR` 또는 그에 준하는 경보 레벨 로그를 남긴다. 로그에는 routing key, 슬롯 ID(또는 correlation 정보), 실패 사유가 포함되어 식별 가능해야 한다.
- `_publish_dlq`의 best-effort 실패(DLQ 자체 발행 실패)도 `ERROR` 수준 로그를 남긴다.
- 이 변경은 `adapters/messaging/rabbitmq.py`(`_publish_dlq`)와 `runtime/daemon.py`(`_make_event_emitter`) 및 관측성 메커니즘 추가 범위에 국소화한다. wire 계약·consume/RPC 경로·슬롯 도메인 불변(NFR-1·NFR-3 흡수).
## Acceptance Criteria
1. Given unroutable 이벤트가 `trex.events.dlq`에 유입될 때, When 관측 지표(로그/메트릭/소비자)를 확인하면, Then DLQ 유입이 운영자가 인지 가능한 형태로 노출된다(S-3 연결).
2. Given `trex.events.dlq` 큐의 깊이(depth)를 확인할 때, When 유입 이벤트가 누적되면, Then 운영자가 접근 가능한 지표(로그/메트릭/RabbitMQ 관리 API 등)로 깊이를 파악할 수 있다(S-3 연결).
3. Given REQ-091 재시도 후에도 발행이 실패한 경우, When `_make_event_emitter`에서 `TransportUnavailableError`를 처리할 때, Then `WARNING` 수준이 아닌 `ERROR`(또는 이상) 수준 로그가 남고, routing key와 슬롯 식별 정보가 포함된다(FR-4 연결).
4. Given `_publish_dlq`의 DLQ 재발행 자체가 실패한 경우, When 예외가 발생하면, Then `ERROR` 수준 로그가 남고 메시지 유실이 silent하게 처리되지 않는다.
5. Given DLQ 가시성 구현 방식이 설계 단계에서 확정된 후, When batch design 문서를 확인하면, Then OQ-3(DLQ 가시성 구현 방식 — 메트릭/로그/소비자 데몬 중 택1)이 명시적으로 결정되어 있다(OQ-3 종료 조건 추적).
6. Given DLQ 유입/경보 메커니즘이 추가된 상태에서, When consumer 경로(intent/slots/control RPC)를 확인하면, Then 해당 경로의 동작·큐 바인딩·토폴로지에 변화가 없다(consume/RPC 무회귀, NFR-1).
## Impacted Area
- Docs: docs/operations.md(DLQ 가시성/경보 운영 절차 신규 기재, REQ-094에서 함께 갱신 검토). docs/runtime-flows.md(`슬롯 이벤트 발행/구독` DLQ 경로 설명 보강, REQ-094에서 갱신).
- Modules: `trex_core/adapters/messaging/rabbitmq.py`(`_publish_dlq` L199-216 — DLQ best-effort 실패 ERROR 로그 + 가시성 훅). `trex_core/runtime/daemon.py`(`_make_event_emitter` L238-248 — 발행 실패 ERROR 경보). 관측성 구현(OQ-3 결정 후 추가). wire 계약·consume/RPC 경로·슬롯 도메인 불변.
- Tests: `tests/adapters/messaging/` — DLQ 유입 관측 지표 확인(AC-1), DLQ 깊이 노출(AC-2), emit 실패 ERROR 로그(AC-3), DLQ 재발행 실패 ERROR(AC-4), consume 경로 회귀(AC-6). `tests/application/test_messaging_event_publisher.py` 기존 회귀 유지.
## Notes
- 출처: dcy-031 FR-3(D), FR-4, A-3(dcy-029 관측성 자산 재사용 여부 검토), S-3.
- OQ-3(DLQ 가시성 구현 방식 — 메트릭/로그/소비자 중 택1): 설계 단계 결정. AC-5에서 추적. 종료 조건: batch design에 명시되면 Answered. dcy-029(REQ-084~090) 관측성 자산(구조화 로깅 인프라) 재사용 가능성을 설계 단계에서 검토한다.
- FR-4(emit 실패 경보)는 독립 REQ로 승격하지 않고 본 REQ에 흡수한다 — FR-3(DLQ 가시성)과 같은 코드 경로(`_make_event_emitter`, `_publish_dlq`)를 공유하며 독립 구현·검증 시 오버헤드 대비 가치가 낮다.
- `_publish_dlq` best-effort 정책(실패 시 silent log → ERROR로 강화)은 기존 wire 계약 변경 없이 로그 레벨 조정만으로 구현 가능해 설계 결정 없이도 확정 가능하다.
- 본 변경은 DLQ 소비자 데몬(별도 상시 프로세스)을 강제하지 않는다 — 관측 방식은 OQ-3에서 결정.
## Change Log
### CHG-20260619-01
- Date: 2026-06-19
- Author: trex-core-dev
#### Change Summary
- dcy-031 FR-3(D) + FR-4로부터 최초 생성(Proposed). NFR-1·NFR-3 흡수. FR-4를 본 REQ에 흡수. OQ-3 설계 인계. A-3(dcy-029 자산 재사용) 인계. S-3 연결.
#### Intent
- DLQ 가시성/경보 + emit 실패 경보 정책을 backlog에 정규화하고, OQ-3 설계 결정 추적을 AC-5로 고정한다.
#### Acceptance Criteria Delta
- Added:
- AC-1: DLQ 유입 관측 가능
- AC-2: DLQ 깊이 파악 가능
- AC-3: emit 실패 ERROR 수준 경보
- AC-4: DLQ 재발행 실패 ERROR
- AC-5: OQ-3 종료 조건 추적
- AC-6: consume/RPC 경로 무회귀
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs:
- docs/operations.md, docs/runtime-flows.md
- Modules:
- trex_core/adapters/messaging/rabbitmq.py, trex_core/runtime/daemon.py
- Tests:
- tests/adapters/messaging/ (DLQ/emit 경보/회귀)
#### Delivery Trace Delta
- Batches:
- none (draft-req 단계, batch 미편성)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 REQ-091·092와 함께 단일 batch 편성 예정
#### Validation Plan
- 로그 캡처 기반으로 DLQ 유입 로그/ERROR 레벨 확인. mock `_publish_dlq` 실패 시나리오로 AC-4 검증. OQ-3 결정 후 구현 방식에 맞는 통합 검증 추가.
### CHG-20260619-02
- Date: 2026-06-19
- Author: 팀장 (confirm-req)
#### Change Summary
- Proposed → **Approved**. 필수 필드 충족, AC 구체, source dcy-031(confirmed, Open OQ 0) 정합. FR-4 흡수 승인(동일 emit 경로). OQ-3(DLQ 가시성 구현 방식)은 AC-5에서 design 인계 추적 — 승인 비차단.
#### Intent
- delivery input 승인. DLQ 가시성 구현 방식(메트릭/로그/소비자)은 draft-batch-design에서 dcy-029 관측성 자산 재사용성과 함께 확정.
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: REQ-091·092와 함께 단일 batch 편성 예정