fix: preserve sell event routing owner

This commit is contained in:
Hermes Agent
2026-09-20 20:34:00 +09:00
parent 3f6a967aa1
commit c66acf6e47
20 changed files with 766 additions and 32 deletions
+43
View File
@@ -0,0 +1,43 @@
---
id: "req-0011"
status: approved
discovery: "dcy-0011"
criteria:
create:
- "ac-0038"
- "ac-0039"
update: []
retire: []
satisfy: []
---
# SELL 체결 이벤트 owner 보존과 unroutable 차단
## Objective
SELL 주문 admission에서 확정된 전략 owner를 체결 event routing까지 보존하여 해당 전략 queue로만 전달한다. owner를 결정할 수 없는 SELL 체결은 정상 전략 event로 위장해 `_` routing key로 발행하지 않고 명시적 전달 실패로 관측하되, 이미 성공한 broker 체결, slot 전이, ledger outbox 처리는 되돌리지 않는다.
## Scope
- `HOLDING → PENDING_SELL` admission에서 non-empty `SellIntent.sell_strategy_id`, slot에 보존된 non-empty `sell_strategy_id`, non-empty `strategy_id` 순서로 SELL event owner를 확정하고 기존 durable `sell_strategy_id`에 저장한다. Whitespace-only 값은 owner로 인정하지 않는다.
- 확정된 owner는 기존 durable slot field를 통해 dispatch 전후와 daemon 재시작 후에도 보존하고, full fill 및 partial fill 후 external cancel의 `slot_filled_sell` routing hint로 사용한다.
- zero-fill cancel 또는 reject는 fill event를 만들지 않고 기존 rollback 계약을 유지하며 확정 owner를 `HOLDING`에 보존한다. 이후 명시적 새 owner가 없는 SELL admission은 이 owner를 다시 사용하고, 명시적 새 owner가 있으면 그것으로 교체한다.
- owner 미확정 SELL 체결은 `trex.events` 정상 전략 topic 또는 DLQ에 synthetic owner message를 publish하지 않는다. stable reason code `sell_event_owner_missing`, tick-level counter, ERROR log, best-effort alarm으로 구분해 관측한다.
- 정상 owner가 있는 event의 publish 실패도 금융 처리를 rollback하지 않는다. 기존 payload의 `slot_id`, `event_type`, `sequence_at`을 변경하지 않으며, 재시작·반복 polling에서 같은 terminal execution의 event 재발행을 새로 보장하지 않는다.
- `docs/interface-contract.md`와 `docs/runtime-flows.md`의 RabbitMQ event routing 및 slot event 발행 구간에 owner 선택, pre-publish skip, 전달 실패 의미를 구현과 함께 동기화한다.
## Constraints
- `sell_strategy_id`와 `strategy_id`의 의미를 합치거나 legacy mapping을 추가하지 않는다.
- 새 persistence column이나 strategy-event outbox를 추가하지 않는다. restart 보장은 terminal 처리 전에 durable `PENDING_SELL` row를 다시 읽는 범위이며, terminal commit 뒤 실패한 event의 재생 저장소를 이번 Line에서 도입하지 않는다.
- publisher confirm과 1회 reconnect/retry, RabbitMQ mandatory publish 및 기존 transport DLQ 계약은 유지한다.
- alarm payload와 log는 secret, credential, connection string 또는 raw exception text를 포함하지 않는다.
- production broker 주문, production lifecycle, production daemon 재기동은 검증에서 실행하지 않는다.
## Non-Goals
- 이미 발생한 과거 DLQ message의 복구 또는 재발행
- 전략 consumer 구현 변경 및 전역 exactly-once 보장
- RabbitMQ topology, queue TTL/max-length, exchange 이름 변경
- BUY event owner 계약 변경
- durable strategy-event outbox 또는 운영 replay command 추가