fix(intents): isolate alarm context lookup and sanitize alarm failure logs

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-26 05:02:19 +00:00
committed by GitHub
co-authored by genichin
parent 5dd3d57f86
commit 1ca9e60c98
9 changed files with 249 additions and 18 deletions
+30
View File
@@ -0,0 +1,30 @@
---
id: "dcy-0008"
status: confirmed
---
# 최초 `reconcile_warned` intent 거부 알림
## Problem
`IntentService`는 재조정 경고(`reconcile_warned`)가 남아 있는 slot의 전략 buy/sell intent를 거부하지만, 현재 경고 알림을 발행하지 않아 운영자가 최초 거부를 즉시 인지하기 어렵다. 기존 intent dedup 기준(`IntentStore.try_insert/get/finalize`)과 차단 정책은 유지되어야 하며, 같은 `intent_id` 재시도에서는 추가 알림이 없어야 한다.
## Evidence
- `trex_core/application/intents.py`는 `reconcile_warned` 거부 결과를 `IntentStore.finalize()`로 저장하지만 `trex.alerts` 알림 발행이 없다.
- daemon은 공용 `AlarmEmitter`를 이미 생성해 다른 서비스에 주입하지만 `IntentService`에는 연결하지 않는다.
- issue #74와 승인된 REQ-0008/AC-0025는 finalize 이후 최초 거부 1회 알림, allowlist payload, optional context 조회 실패/발행 실패의 비차단 경계를 요구한다.
## Scope
- 대상은 외부 전략 buy/sell intent의 `rejected/reconcile_warned` 결과다.
- `IntentStore.finalize()` 성공 뒤에만 `severity=warn`, `code=strategy_intent_rejected` 알림을 best-effort로 발행한다.
- 필수 fields(`instance_key`, `intent_id`, `intent_type`, `reason`, `broker_submitted=false`)와 확인된 optional fields(`slot_id`, `portfolio_id`, `symbol`, `strategy_id`)만 포함한다.
- 같은 `intent_id`는 기존 durable dedup 결과를 재사용해 추가 발행하지 않고, 다른 `intent_id`는 독립 발행한다.
- 발행기 부재/실패/optional context 조회 실패는 저장 결과와 RPC 응답을 변경하지 않는다.
## Out of Scope
- AC-0023/AC-0024의 warning 판정·clear authority 변경
- broker 주문/취소, slot/warning/ledger/portfolio-hub write side effect 추가
- 신규 durable outbox, publish retry persistence, restart 후 보장 재발행
+3
View File
@@ -0,0 +1,3 @@
---
id: "line-0008"
---
+57
View File
@@ -0,0 +1,57 @@
---
id: "req-0008"
status: approved
discovery: "dcy-0008"
criteria:
create:
- "ac-0025"
update: []
retire: []
satisfy: []
---
# 최초 `reconcile_warned` intent 거부 알림 발행
## Objective
`trex-core`는 재조정 경고가 남아 있는 slot에 대한 외부 전략 buy/sell intent가 `rejected/reconcile_warned`로 확정될 때, 기존 idempotency와 차단 정책을 유지하면서 새 `intent_id`의 최초 거부에 한해 `trex.alerts` 경고를 한 건 발행해야 한다.
## Scope
- 경고 발행 조건
- `IntentStore.try_insert()`가 성공한 최초 처리여야 한다.
- 외부 전략 buy 또는 sell intent여야 한다.
- 최종 결과가 `status="rejected"`, `reason="reconcile_warned"`여야 한다.
- `IntentStore.finalize()`가 성공한 뒤여야 한다.
- 경고 계약
- `severity=warn`
- `code=strategy_intent_rejected`
- dedup 경계
- 같은 `intent_id` 재요청은 저장된 기존 결과를 반환하고 경고를 재발행하지 않는다.
- `duplicate_in_flight`와 최초 처리 중 중복 요청은 경고 대상이 아니다.
- 같은 slot이라도 `intent_id`가 다르면 독립 발행한다.
- process-local set, 별도 notification table, `reconcile_notification_state`를 추가하지 않는다.
- payload allowlist
- 필수: `instance_key`, `intent_id`, `intent_type`, `reason="reconcile_warned"`, `broker_submitted=false`
- optional(확인 가능 시에만): `slot_id`, `portfolio_id`, `symbol`, `strategy_id`
- buy `strategy_id`와 sell `sell_strategy_id`는 공통 `strategy_id`로 표현한다.
- `quantity`, `entry_price`, `limit_price`, `venue`, raw request/strategy `extra`는 포함하지 않는다.
- failure isolation
- emitter 부재/실패, optional context 조회 실패는 저장 결과와 RPC 응답을 바꾸지 않는다.
- 이미 확정된 거부를 rollback하거나 admitted로 바꾸지 않는다.
- production wiring
- `IntentService`는 optional `AlarmEmitter`를 받는다.
- daemon은 기존 공용 `AlarmEmitter`를 `IntentService`에 전달한다.
- emitter 없이 생성하는 기존 테스트/사용 경로는 계속 동작한다.
## Constraints
- AC-0023·AC-0024의 기존 warning 판정, clear authority, intent dedup semantics를 변경하지 않는다.
- 경고 발행을 위해 broker/slot/warning/ledger/portfolio-hub side effect를 추가하지 않는다.
- 경고/로그에 account number/hash, credential/token/password/secret, DSN/connection string, raw payload, exception 본문을 노출하지 않는다.
## Non-Goals
- 다른 거부 사유를 위한 범용 경고 정책 추가
- safety-sell/kill-switch/watchdog 내부 경로 신규 알림
- notifier 렌더링/채널, RabbitMQ topology, production 배포/릴리즈 절차 변경