chore(governance): remove legacy StagePilot artifacts

This commit is contained in:
Hermes Agent
2026-08-08 21:36:27 +09:00
parent 536c534a50
commit 2e5f59e09c
601 changed files with 0 additions and 55555 deletions
@@ -1,245 +0,0 @@
# req-011: BrokerPort 추상화 + paper/stub async 더블
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 브로커를 코어에서 추상화하는 `BrokerPort` 를 정의하고(불변식 B 의 단일 경계), 결정적 검증용 paper/stub 어댑터를 제공한다. 이후 KIS 어댑터(REQ-012)와 집행 루프(REQ-013)가 이 포트 위에서 동작한다.
## Requirement
- `BrokerPort`(application 포트)에 `submit_order(side, symbol, qty, ...) -> broker_order_id`, `cancel_order(broker_order_id)`, `poll_fill(broker_order_id) -> FillStatus(accepted/partially_filled/filled/rejected/cancelled, avg_price, filled_qty)` 를 정의한다. paper/stub 어댑터는 동기 단축 없이 submit→poll 비동기 계약을 따르되 체결 시점·부분체결·거부를 프로그래밍 가능한 결정적 더블로 제공한다. 어댑터는 adapters 레이어에 두고 도메인은 의존하지 않는다.
- paper 어댑터의 order seq 발번은 optional seq store(주입 가능) 위에서 동작한다. seq store 가 주입되면 order seq 는 데몬 재시작 간에도 단조 증가해야 하며(Redis 영속, REQ-016 `RedisTokenCache` 패턴 재사용·best-effort), 미주입 시 기존 in-memory 카운터로 폴백한다. 이는 재시작 후 결정적 `execution_id` 재생성으로 인한 원장 `correlation_id` 충돌(매도 원장 누락)을 방지하기 위한 paper 한정 결정성 보강이다(issue #4).
## Acceptance Criteria
1. Given `BrokerPort` 정의, When paper 어댑터로 `submit_order`, Then `accepted` + broker_order_id 를 반환하고 즉시 `filled` 로 단축하지 않는다(다음 `poll_fill` 에서 체결).
2. Given paper 더블의 fill 시나리오 설정(즉시/N틱 후/부분/거부), When `poll_fill` 반복, Then 설정대로 결정적으로 상태가 진행된다.
3. Given 코드, When import 스윕, Then `trex_core.domain.slot` 은 broker/네트워크 의존이 0이고 BrokerPort 구현은 adapters 레이어에만 있다. (NFR-1/NFR-2)
4. Given paper 어댑터에 seq store 가 주입된 상태, When 데몬 재시작(seq store 영속)을 모사해 두 번째 인스턴스에서 `submit_order`, Then order seq 는 첫 인스턴스 이후로 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입 시 기존 in-memory `PAPER-000001` 발번으로 폴백하며 AC-1~3 비동기 계약은 불변이다. (issue #4)
## Impacted Area
- Docs: docs/interface-contract.md(브로커 경계) — REQ-015 에서 정합
- Modules: trex_core/application(BrokerPort), trex_core/adapters/broker(paper/stub, `seq_store.py` 신규), trex_core/runtime(config `PaperBrokerConfig.redis_url`, daemon `_build_broker` wiring)
- Tests: paper 더블 fill 시나리오 테스트, paper seq store 재시작 단조성 + correlation_id 비충돌 + in-memory 폴백 테스트
## Notes
- 불변식 B 의 단일 경계. KIS 어댑터는 REQ-012, 집행 루프는 REQ-013.
- 출처: dcy-003 FR-1(포트+paper), NFR-1/NFR-2 흡수.
- (issue #4/CHG-20260612-01) paper seq Redis 영속화는 paper 한정 테스트 하네스 결정성 보강이며 프로덕션(KIS `odno` 고유) 버그가 아니다. 원장 `correlation_id` 에 side 를 추가하는 대안 대신, 발번 단조성 보강(영향 최소·계약 불변)을 선택했다. Redis 인프라는 REQ-016 token_cache 패턴을 재사용한다.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- dcy-003 FR-1(포트+paper) 로부터 최초 생성(Proposed).
#### Intent
- BrokerPort + paper async 더블 요구사항을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- submit→poll 비동기 계약, 결정적 fill 시나리오, 도메인 순수성 3개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md
- Modules:
- application(BrokerPort), adapters/broker(paper)
- Tests:
- paper 더블 시나리오 테스트
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- paper 더블 비동기 체결 시나리오 단위테스트로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register 상태 Approved)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none (batch 편성 전)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성
#### Validation Plan
- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate).
#### Intent
- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- BAT-004 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-release 로 release 편성(BAT-001~004)
#### Validation Plan
- BAT-004 verification evidence 재참조로 충분.
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- (change-req, issue #4) paper 브로커 order seq 를 **재시작 간 단조 증가**하도록 영속화한다. 현재 `_seq` in-memory 카운터는 데몬 재시작 시 0 으로 리셋되어 `PAPER-000001` 부터 재발번 → 결정적 `execution_id`(`paper:{order_id}:{filled_qty}`) 재생성 → 원장 멱등 키 `correlation_id = {asset_id}:{order_id}:{execution_id}`(side 미포함)가 직전 run 의 매수와 충돌 → `ON CONFLICT DO NOTHING` dedup 으로 **정상 매도 원장 행이 조용히 누락**. paper 어댑터에 optional seq store(Redis-backed, REQ-016 `RedisTokenCache` 패턴 재사용)를 주입해 해소하고, Redis 미구성 시 기존 in-memory 동작으로 폴백. 신규 AC-4 가 추가 구현·검증을 필요로 하므로 **Implemented → Approved 되돌림**.
#### Intent
- paper 모드 테스트 하네스의 재시작 후 멱등 키 충돌(매도 원장 누락)을 제거한다. 실 KIS 는 broker order_id(`odno`)가 주문마다 고유해 충돌이 없으므로 **프로덕션 버그가 아닌 paper 한정 결정성 문제**다. 기존 BrokerPort/paper 비동기 계약(AC-1~3)은 불변, seq 발번 영속화만 추가.
#### Acceptance Criteria Delta
- Added:
- AC-4: paper 브로커에 seq store 가 주입되면 order seq 는 재시작 후에도 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 의 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입(Redis 미구성) 시 기존 in-memory `_seq` 동작으로 폴백하며 비동기 fill 계약(AC-1~3)은 불변이다.
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented → Approved)
- Modules:
- `trex_core/adapters/broker/paper.py`(optional seq store 주입 + `submit_order` seq 발번 경로)
- `trex_core/adapters/broker/seq_store.py`(신규: `SeqStore` 포트 + `RedisSeqStore`(INCR 기반, best-effort) + `InMemorySeqStore` 폴백, token_cache 패턴 정합)
- `trex_core/runtime/config.py`(`PaperBrokerConfig.redis_url` 추가)
- `trex_core/runtime/daemon.py`(`_build_broker` paper 경로 seq store wiring)
- Tests:
- 재시작 시뮬레이션(seq store 공유) 후 매도 correlation_id 비충돌 + 폴백 동작 단위 테스트
#### Delivery Trace Delta
- Batches:
- BAT-039 (예정, issue #4)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: yes (BAT-039 에서 신규 AC-4 + AC-1~3 회귀)
- Existing Implementation Invalidated: no (기존 비동기 계약/도메인 순수성 불변, 발번 경로에 optional 영속화만 추가). 단 신규 AC-4 미구현으로 Implemented 유지 불가 → Approved.
- Status Recommendation: Implemented → **Approved** (구현·검증 후 confirm-req-implemented 로 복귀)
- Follow-up Action: confirm-req → draft-batch(BAT-039) → 구현·검증 → confirm-req-implemented → issue #4 close
#### Validation Plan
- BAT-039: (a) 같은 seq store 를 공유한 두 PaperBroker 인스턴스(재시작 모사)에서 두 번째 인스턴스의 첫 order_id 가 첫 인스턴스 이후로 단조 증가, (b) 매수 후 재시작 매도 시 correlation_id 비충돌(원장 dedup 미발생), (c) seq store 미주입 시 기존 `PAPER-000001` 부터 in-memory 동작 폴백, (d) AC-1~3 기존 paper 테스트 회귀 없음. `pytest` 전체 + `ruff check` clean.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved → **Implemented** (confirm-req-implemented, 근거 batch BAT-039 release-candidate).
#### Intent
- BAT-039 verification.md 의 acceptance mapping + evidence(434 passed / seq store 재시작 단조·correlation_id 비충돌·in-memory 폴백 / fake redis INCR 단조)로 AC-1~4 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음 (상태 전이만)
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (상태 전이만)
#### Delivery Trace Delta
- Batches:
- BAT-039 (release-candidate)
- Releases:
- none (release 편성은 별도 진행)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: draft-release 로 release 편성(BAT-039 포함) + issue #4 close
#### Validation Plan
- BAT-039 verification evidence 재참조로 충분.
- AC-4: `test_shared_seq_store_is_monotonic_across_restart`, `test_post_restart_sell_does_not_collide_with_prior_buy`, `test_in_memory_fallback_starts_at_one`, `test_redis_seq_store_incr_is_monotonic_and_shared`.
- AC-1~3: 전체 회귀 434 passed(기존 paper 더블 시나리오 + 도메인 순수성 회귀 없음).
@@ -1,191 +0,0 @@
# req-012: KIS Open API 브로커 어댑터
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- `BrokerPort`(REQ-011)를 구현하는 실제 KIS(한국투자증권) Open API 어댑터를 추가해, 코어가 실제 주문 송신/취소/체결 조회를 수행할 수 있게 한다. 슬라이스에서는 KIS 모의투자 서버로 검증한다.
## Requirement
- KIS Open API 로 `submit_order`/`cancel_order`/`poll_fill`(체결 조회)을 구현하고, 접속 토큰 발급·갱신, 레이트리밋, 모의투자/실계좌 base URL 전환을 어댑터 내부에서 처리한다. 자격증명은 env/.env(REQ-010 패턴)로 주입한다. 실계좌 라이브 전환은 config 게이팅(기본 모의투자)으로 둔다.
## Acceptance Criteria
1. Given KIS 모의투자 자격증명, When 어댑터 기동, Then 토큰을 발급/갱신하고 인증된 호출이 가능하다.
2. Given 매수/매도 주문, When `submit_order`, Then KIS 모의투자에 주문이 접수되고 broker_order_id(주문번호)를 반환한다.
3. Given 접수된 주문, When `poll_fill`, Then KIS 체결 조회로 체결/부분체결/거부 상태를 반환한다.
4. Given config, When 실계좌/모의투자 전환, Then 기본은 모의투자이며 실계좌는 명시적 게이팅 없이는 활성화되지 않는다.
5. Given 자격증명 미설정/토큰 실패, When 호출, Then 명확한 오류로 실패하고 코어 슬롯 상태를 오염시키지 않는다.
## Impacted Area
- Docs: docs/interface-contract.md(브로커 경계/KIS), docs/runtime-flows.md — REQ-015
- Modules: trex_core/adapters/broker(kis), config(KIS 자격증명/base URL/게이팅), pyproject(HTTP 의존)
- Tests: KIS 모의투자 통합테스트(자격증명 가용 시), 토큰/오류 처리 단위테스트
## Notes
- 실거래 리스크(dcy-003 R-5): 슬라이스는 모의투자만, 실계좌 라이브는 Out of Scope(게이팅).
- KIS 모의투자 자격증명이 검증 환경에 없으면 통합테스트는 skip, 단위(토큰/오류)만 수행 — verification 에서 분리 판단.
- 출처: dcy-003 FR-1(KIS), OQ-1.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- dcy-003 FR-1(KIS 어댑터) 로부터 최초 생성(Proposed).
#### Intent
- 실제 KIS Open API 어댑터 요구사항을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- 토큰, 주문 접수, 체결 조회, 모의투자/실계좌 게이팅, 오류 처리 5개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md, docs/runtime-flows.md
- Modules:
- adapters/broker(kis), config, pyproject
- Tests:
- KIS 모의투자 통합 + 토큰/오류 단위
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- KIS 모의투자 통합테스트(가용 시) + 토큰/오류 단위테스트로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register 상태 Approved)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none (batch 편성 전)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성
#### Validation Plan
- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate).
#### Intent
- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- BAT-004 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-release 로 release 편성(BAT-001~004)
#### Validation Plan
- BAT-004 verification evidence 재참조로 충분.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-013/issue #9) KIS `poll_fill` 이 `inquire-daily-ccld` 응답의 `cncl_yn="Y"` 를 파싱해 외부 취소를 감지한다. 체결 0 취소 → `FillStatus(state="cancelled")`, 부분 체결 후 취소 → `state="partially_filled" + cancelled=True`(execution_id/fee 운반). 미지/부재 값은 보수적 비취소. BAT-017 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 계좌 직접 취소가 `accepted` 로 오판되어 슬롯이 pending_sell 에 영구 갇히던 갭(issue #9) 제거 — KIS 어댑터가 취소를 종료 상태로 정확히 매핑.
#### Acceptance Criteria Delta
- Added:
- poll_fill 이 cncl_yn 을 파싱: 체결 0 취소 → cancelled, 부분 체결 후 취소 → partially_filled+cancelled.
#### Impacted Area Delta
- Modules: `trex_core/adapters/broker/kis.py`(poll_fill), `trex_core/application/broker_port.py`(FillStatus.cancelled/is_partial_cancel)
- Tests: tests/adapters/broker/test_kis_cancel.py
#### Delivery Trace Delta
- Batches: BAT-017
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-017 에서 완료)
- Existing Implementation Invalidated: yes (취소를 accepted 로 오판하던 poll_fill)
- Status Recommendation: Implemented (change-req → BAT-017 재검증)
- Follow-up Action: confirm-req-implemented(BAT-017).
#### Validation Plan
- BAT-017: 취소 fixture(full/partial/non-cancel/full-fill) → FillStatus 매핑 단위.
@@ -1,279 +0,0 @@
# req-013: 주문 집행 루프 (claim·송신·체결·롤백)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 코어 데몬 내 백그라운드 execution 루프가 pending 슬롯을 집행해 브로커에 주문을 송신하고 체결을 슬롯 상태로 반영한다(불변식 A 단일 writer 유지). intent API 는 pending 전이까지만, 집행은 루프가 비동기 수행.
## Requirement
- 데몬 프로세스 내 별도 execution 루프가 주기적으로: ① `pending_buy`/`pending_sell` 슬롯을 `FOR UPDATE SKIP LOCKED`(broker_order_id IS NULL)로 claim → `BrokerPort.submit_order` → `broker_order_id` 기록(옵션 A), ② broker_order_id 있는 pending 슬롯을 `poll_fill` 로 비동기 관측해 매수 체결 시 `pending_buy→holding`(avg_price/qty), 매도 체결 시 `pending_sell→closed`(recovery 재진입 +1), ③ 거부/취소 시 `pending_buy→idle` 롤백을 수행한다. 모든 슬롯 쓰기는 SlotService(advisory lock) 단일 경로.
## Acceptance Criteria
1. Given pending_buy 슬롯(paper), When 집행 루프 1회, Then 매수 주문 송신 + broker_order_id 기록, broker_order_id 채워진 슬롯은 재디스패치되지 않는다(멱등). (NFR-2 단일 writer)
2. Given 송신된 매수 주문이 체결(poll), When 루프, Then `pending_buy→holding`(avg_price/qty 반영).
3. Given 매수 주문 거부(poll), When 루프, Then `pending_buy→idle` 롤백.
4. Given pending_sell 슬롯, When 집행+체결, Then 매도 주문 송신 → `pending_sell→closed` + 재진입 +1.
5. Given 슬롯 쓰기 경로, When 점검, Then 집행 루프도 SlotService(advisory lock)로만 슬롯을 전이한다(브로커 직접 슬롯 쓰기 없음).
## Impacted Area
- Docs: docs/runtime-flows.md(집행/체결 흐름) — REQ-015
- Modules: trex_core/application 또는 runtime(execution 루프), SlotService 재사용
- Tests: paper 더블로 매수/매도 lifecycle, 멱등 재디스패치, 거부 롤백, 체결→전이
## Notes
- OQ-3(옵션 A 실행큐)·OQ-4(별도 루프)·OQ-2(비동기 poll) 결정 반영. 폴링 주기/배치 크기는 config.
- reconciliation·크래시 복구는 REQ-014. KIS 어댑터는 REQ-012, paper 는 REQ-011.
- 출처: dcy-003 FR-2, FR-3, FR-4. NFR-2 흡수.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- dcy-003 FR-2·FR-3·FR-4 로부터 최초 생성(Proposed). NFR-2 흡수.
#### Intent
- 주문 집행 루프(매수/매도 claim·송신·체결·롤백) 요구사항을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- 매수 claim/멱등, 매수 체결→holding, 거부 롤백, 매도 체결→closed, 단일 writer 5개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md
- Modules:
- application/runtime(execution 루프)
- Tests:
- paper lifecycle/멱등/롤백/체결 테스트
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- paper 더블 매수/매도 lifecycle + 멱등 + 롤백 테스트로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register 상태 Approved)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none (batch 편성 전)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성
#### Validation Plan
- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate).
#### Intent
- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- BAT-004 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-release 로 release 편성(BAT-001~004)
#### Validation Plan
- BAT-004 verification evidence 재참조로 충분.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-012/issue #10) `_dispatch` 의 매도 가격 버그 정정: side 분기로 `entry_price`(매수가)를 지정가로 재사용하던 로직을 제거하고, side 무관 슬롯 `limit_price` **단일 필드**만 읽어 주문유형/가격을 결정한다 — 값 있으면 `order_type="00"`(그 지정가), 없으면 `order_type="01"`(시장가). BAT-016 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 매도가 매수 지정가에 걸린 지정가로 나가 미체결되는 정합성 버그(issue #10) 제거. dispatch 의 가격 결정을 `slot.limit_price` 로 일원화.
#### Acceptance Criteria Delta
- Updated:
- dispatch 가격/주문유형은 `slot.limit_price` 한 필드로 결정(side 분기 없음, 매수가 재사용 금지).
#### Impacted Area Delta
- Modules: `trex_core/application/execution.py`(`_dispatch`)
- Tests: tests/application/test_sell_pricing.py(지정가 매수 슬롯의 시장가/지정가 매도, 회귀)
#### Delivery Trace Delta
- Batches: BAT-016
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-016 에서 완료)
- Existing Implementation Invalidated: yes (entry_price 재사용 dispatch 경로)
- Status Recommendation: Implemented (change-req → BAT-016 재검증)
- Follow-up Action: confirm-req-implemented(BAT-016).
#### Validation Plan
- BAT-016: 지정가 매수 슬롯 + 가격 미전달 매도 → 시장가(entry_price 미사용 증명), + 가격 전달 매도 → 그 지정가, 시장가 매수 슬롯 회귀.
### CHG-20260608-02
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-013/issue #9) 집행 루프에 외부 취소 처리 추가: 전량 취소(`is_terminal_reject`)는 기존 롤백 경로 재사용, 부분 체결 후 취소(`is_partial_cancel`)는 신규 `_on_partial_cancel` — 체결분 원장 1회 기록 + 슬롯 qty 차감(매도 잔여 held / 매수 체결분) + holding 복귀(OQ-1 A안). BAT-017 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 부분 체결 후 취소에서 과매도·이중기록 없이 잔여 포지션만 재매도 가능하게(NFR-3). 전량 취소는 자동 holding 복귀.
#### Acceptance Criteria Delta
- Added:
- `_poll` 이 is_partial_cancel 분기로 부분 체결 회계 수행(원장 1회 + qty 차감 + holding 복귀). 전량 취소는 롤백.
#### Impacted Area Delta
- Modules: `trex_core/application/execution.py`(`_poll`, `_on_partial_cancel`, ExecutionResult.stale_pending)
- Tests: tests/application/test_execution_cancel.py
#### Delivery Trace Delta
- Batches: BAT-017
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-017 에서 완료)
- Existing Implementation Invalidated: no (신규 분기 추가, 기존 filled/reject 경로 불변)
- Status Recommendation: Implemented (change-req → BAT-017 재검증)
- Follow-up Action: confirm-req-implemented(BAT-017).
#### Validation Plan
- BAT-017: 전량 취소 → holding 복귀, 부분 취소 → 원장 1건 + qty 차감 + cost basis 유지 단위.
### CHG-20260610-03
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-022 실행 정책) 집행 루프에 두 가지 확장: ① **slippage dispatch**: `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 `entry_price*(1±pct)` 실효 지정가로 주문을 제출(매수 올림/매도 내림, KRX 호가단위). ② **timeout 취소**: `_poll` 이 dispatched-pending 슬롯의 `timeout_sec` 초과 시 `broker.cancel_order` 를 송신(best-effort, 취소-체결 race는 기존 is_terminal_reject/is_partial_cancel 경로로 수렴). Implemented → Approved 되돌림(신규 집행 로직 미구현, BAT-032 에서 재구현).
#### Intent
- 실행 정책(slippage + timeout)을 집행 루프에 배선해 미체결 고착 예방 + 과도한 불리 체결 방지.
#### Acceptance Criteria Delta
- Added:
- dispatch 시 슬롯 `slippage_pct` 가 있으면 `limit_price*(1+pct)` (buy) / `limit_price*(1-pct)` (sell) 실효 지정가로 제출한다(`entry_price` 없으면 무시).
- dispatched-pending 슬롯이 `timeout_sec` 를 초과하면 `broker.cancel_order` 를 송신하고, 다음 poll 에서 is_terminal_reject / is_partial_cancel 경로로 롤백/부분 보존이 수렴한다.
- timeout/slippage 미지정 슬롯은 기존 dispatch·poll 경로와 동일(회귀 없음).
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Modules: `trex_core/application/execution.py`(`_dispatch` slippage, `_poll` timeout cancel), ExecutionResult(+timeout_cancelled)
- Tests: tests/application/test_execution.py(slippage dispatch), test_execution_cancel.py(timeout cancel)
#### Delivery Trace Delta
- Batches: BAT-032
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-032 에서 완료 예정)
- Existing Implementation Invalidated: no (신규 분기 추가, 기존 dispatch/poll 경로 보존)
- Status Recommendation: revert-to-approved
- Follow-up Action: BAT-032 구현 후 confirm-req-implemented.
#### Validation Plan
- BAT-032: slippage 실효 지정가 단위 + timeout 취소 → 롤백/부분 보존 단위 + 회귀.
### CHG-20260611-IMPL
- Date: 2026-06-11
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음.
@@ -1,153 +0,0 @@
# req-016: KIS 인증 토큰 Redis 캐시
- Status: Implemented
- Type: Integration
- Priority: Medium
- Owner: project maintainers
## Intent
- KIS 접속 토큰을 Redis 에 캐시해 데몬 재기동·다중 프로세스에서도 토큰을 공유/생존시켜, KIS 토큰 발급 rate-limit(약 1회/분, EGW00133)을 피한다. trex-orbit 의 Redis 토큰 캐시 패턴을 trex-core 로 이식한다.
## Requirement
- KISBroker 에 선택적 토큰 캐시(`TokenCache`)를 주입한다. `_ensure_token` 은 ① 인메모리(L1) → ② Redis 캐시(L2, 키 `trex:broker:kis:{account_ref}:token`, 토큰+만료+app_key suffix JSON, `ex=ttl`) → ③ 신규 발급 순으로 해석한다. Redis 미설정/미설치/오류 시 기존 인메모리 동작으로 graceful fallback 한다. config 는 `TREX_KIS_REDIS_URL` 로 캐시를 활성화한다.
## Acceptance Criteria
1. Given Redis 캐시에 유효 토큰이 있음, When `_ensure_token`, Then HTTP 발급 없이 캐시 토큰을 사용한다.
2. Given 두 KISBroker 가 같은 캐시를 공유, When 하나가 발급·저장 후 다른 하나가 호출, Then 재발급 없이 캐시 토큰을 재사용한다(재기동/다중 프로세스 생존).
3. Given Redis 미설정/미설치/오류, When `_ensure_token`, Then 기존 인메모리 발급 동작으로 동작한다(회귀 없음).
4. Given 캐시된 토큰의 app_key suffix 가 현재와 불일치, When load, Then 캐시를 무시하고 재발급한다(자격증명 교체 안전).
## Impacted Area
- Docs: docs/interface-contract.md(KIS 어댑터 노트) — 필요 시
- Modules: trex_core/adapters/broker/token_cache.py(신규), kis.py(token_cache 주입), runtime/config.py(TREX_KIS_REDIS_URL), runtime/daemon.py(캐시 wiring), pyproject(redis 의존)
- Tests: tests/adapters/broker/test_token_cache.py (캐시 히트/공유/폴백/suffix)
## Notes
- minor-change: 기존 BrokerPort 뒤 토큰 캐시 계층 추가(외부 인터페이스/슬롯 흐름 변경 없음). batch-lite, design 불요.
- dcy-003 가 단일 인스턴스를 가정해 슬라이스에서 분산 캐시를 미포팅했던 갭의 후속 보완. 실 Redis 없을 때도 폴백으로 안전.
- 맥락: REQ-012(KIS 어댑터) 운영 견고성 개선.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- 최초 생성(Proposed) — KIS 토큰 Redis 캐시(재기동/다중프로세스 생존) Integration 요구사항.
#### Intent
- orbit Redis 토큰 캐시 패턴 이식으로 토큰 rate-limit·재발급 위험 완화.
#### Acceptance Criteria Delta
- Added:
- 캐시 히트, 공유 재사용, graceful fallback, app_key suffix 안전 4개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md(필요 시)
- Modules:
- adapters/broker/token_cache.py, kis.py, runtime/config.py, runtime/daemon.py, pyproject
- Tests:
- tests/adapters/broker/test_token_cache.py
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: batch-lite 편성
#### Validation Plan
- 캐시 히트/공유/폴백/suffix 단위테스트(인메모리 캐시 더블)로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (minor-change 저위험 단일 REQ, 외부 인터페이스/런타임 흐름 변경 없음).
#### Intent
- 승인 게이트 충족. batch-lite 로 즉시 delivery.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Approved)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none (batch-lite 편성 예정)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-batch 로 batch-lite 생성
#### Validation Plan
- batch-lite verification 에서 AC 검증.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-005 release-candidate).
#### Intent
- BAT-005 verification 의 AC↔evidence(캐시 히트/공유/폴백/suffix, 96 pass)로 모든 AC 충족.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented)
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- BAT-005 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-release 로 release 편성
#### Validation Plan
- BAT-005 verification evidence 재참조로 충분.
@@ -1,269 +0,0 @@
# req-017: LedgerPort 추상화 + portfolio-hub 어댑터 + stub 원장
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 체결을 외부 회계/포지션 원장(portfolio-hub)에 기록할 수 있도록, 도메인/네트워크 의존을 어댑터로 격리한 `LedgerPort` 계약과 portfolio-hub 어댑터, 그리고 결정론 검증용 stub/in-memory 원장을 제공한다.
## Requirement
- `LedgerPort.record_trade(asset_id, order_id, execution_id, side, qty, price, fee, tax, executed_at)` Protocol 을 application 레이어에 정의한다.
- `PortfolioHubLedger` 어댑터는 라이브 portfolio-hub 계약(`LedgerTradeRequest`)을 따라 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 로 거래를 기록한다. 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용하며, `execution_id` 로 멱등하고, portfolio-hub 가 반환하는 409 충돌을 `LedgerConflictError` 로 표면화한다. (정정: CHG-20260606-01 — 구 orbit 가정 `POST /v1/assets/{asset_id}/...`, body `qty`/`fee`/`tax` 폐기)
- 결정론 테스트용 stub/in-memory 원장 구현을 제공해 네트워크 없이 체결→기록 lifecycle 을 검증할 수 있게 한다.
- 어댑터 자격증명/URL 은 .env(REQ-010 패턴, `TREX_PORTFOLIO_HUB_*`)로 주입한다.
## Acceptance Criteria
1. Given in-memory stub 원장, When `record_trade(...)` 를 1회 호출, Then 원장에 거래 1건이 적재되고 인자(asset_id/side/qty/price/fee/tax/executed_at)가 보존된다.
2. Given portfolio-hub 어댑터, When `record_trade(...)`, Then `POST /v1/portfolios/{portfolio_id}/ledger/trades` 가 `execution_id` 를 포함해 호출되고 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용한다(라이브 `LedgerTradeRequest` 계약, 성공 응답 201). (정정: CHG-20260606-01)
3. Given portfolio-hub 가 409(중복/충돌)를 반환, When 어댑터가 응답을 처리, Then `LedgerConflictError` 로 표면화된다(예외를 삼키지 않는다).
4. Given 정적 import 스윕, When `trex_core/domain` 을 점검, Then portfolio-hub/네트워크 의존이 0 이다(LedgerPort 는 application 계약, 어댑터는 adapters 레이어 격리). (NFR-1)
## Impacted Area
- Docs: docs/interface-contract.md(LedgerPort), docs/project-structure.md(ledger 어댑터) — REQ-020 에서 정합
- Modules: 신규 `trex_core/application` LedgerPort 계약, 신규 `trex_core/adapters/ledger`(portfolio-hub 어댑터 + stub), `trex_core/runtime/config.py`(portfolio-hub URL/토큰)
- Tests: stub 원장 record_trade 단위테스트, 어댑터 요청 계약/409 처리 테스트, 도메인 import 스윕
## Notes
- 출처: dcy-004 FR-1, NFR-1.
- A-1 가정(폐기, CHG-20260606-01): orbit 동일 계약(`POST /v1/assets/{asset_id}/ledger/trades`, body `qty`/`fee`/`tax`) 가정은 라이브 portfolio-hub 실측과 불일치(404)로 확인돼 폐기. 실 계약은 `POST /v1/portfolios/{portfolio_id}/ledger/trades` + `LedgerTradeRequest`(`quantity`/`fee_amount`/`tax_amount`), 성공 201. execution_id 멱등은 유지 가정(중복 응답 코드는 후속 확인).
- 도메인 순수성(NFR-1)을 별도 REQ 로 승격하지 않고 본 REQ 의 AC-4(import 스윕)로 흡수한다.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- dcy-004 FR-1 로부터 최초 생성(Proposed). NFR-1(도메인 순수성) 흡수.
#### Intent
- 체결을 외부 원장에 기록하기 위한 LedgerPort 계약 + portfolio-hub 어댑터 + 결정론 stub 을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- stub record_trade 적재, 어댑터 POST 계약, 409→LedgerConflictError, 도메인 import 스윕 4개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md, docs/project-structure.md
- Modules:
- LedgerPort 계약, portfolio-hub 어댑터 + stub, config
- Tests:
- stub/어댑터/도메인 import 스윕 테스트
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- stub 원장 단위테스트 + 어댑터 요청 계약/409 테스트 + 도메인 import 스윕으로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과.
#### Intent
- LedgerPort 계약 + portfolio-hub 어댑터 + stub 원장을 delivery input 으로 승인.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- 없음
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: batch 편성
#### Validation Plan
- 기존 Acceptance Criteria 로 batch delivery 에서 검증.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반.
#### Intent
- LedgerPort/portfolio-hub adapter/in-memory stub 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (변경은 상태 전이만)
#### Delivery Trace Delta
- Batches:
- BAT-006 (release-candidate)
- Releases:
- none (배포 미생성)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: release 단계는 별도 진행
#### Validation Plan
- AC-1: tests/adapters/ledger/test_in_memory_ledger.py::test_record_trade_preserves_fields_and_is_idempotent_on_execution_id.
- AC-2: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_post_contract_includes_execution_id_and_path_uses_asset_id.
- AC-3: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_409_surfaces_as_ledger_conflict_error + tests/application/test_execution_ledger.py::test_conflict_blocks_without_auto_retry.
- AC-4: tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports.
### CHG-20260606-01
- Date: 2026-06-06
- Author: Justin Ko
#### Change Summary
- 실 portfolio-hub API 와의 ledger write 계약 불일치를 정정: 엔드포인트 `POST /v1/assets/{asset_id}/ledger/trades`(부재, 404) → **`POST /v1/portfolios/{portfolio_id}/ledger/trades`**, 요청 body 키 `qty`/`fee`/`tax` → **`quantity`/`fee_amount`/`tax_amount`**. `asset_id` 값은 슬롯 `portfolio_id`(REQ-018: asset_id == portfolio_id)라 `{portfolio_id}` path 세그먼트에 직접 매핑된다. 어댑터/테스트는 정정 구현·재검증 완료.
#### Intent
- 가정(A-1, orbit `post_ledger_trade` 동일 계약)으로 작성됐던 ledger write 계약을 라이브 portfolio-hub 실측(openapi `LedgerTradeRequest`)에 맞춰 정정하고, 정정 구현의 재검증 evidence(신규 contract 테스트 + 실 e2e 201)를 연결한다.
#### Acceptance Criteria Delta
- Added: none
- Updated:
- AC-2: portfolio-hub 어댑터 `record_trade(...)` 는 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 를 호출하고, 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용한다(`execution_id` 포함, `execution_id` 멱등). 성공 응답은 201.
- Removed: none
#### Impacted Area Delta
- Docs:
- 본 REQ Requirement/AC-2/Notes 정정
- BAT-006 design.md/verification.md 계약 정정 반영(같은 변경 묶음)
- Modules:
- trex_core/adapters/ledger/portfolio_hub.py (경로 + body 키 정정)
- Tests:
- tests/adapters/ledger/test_portfolio_hub_ledger.py (실 계약으로 갱신: path /v1/portfolios, body 키)
#### Delivery Trace Delta
- Batches:
- BAT-006 (release-candidate) — ledger write 계약 정정 반영, verification 재확인 대상
- Releases:
- REL-001 (confirmed) — BAT-006 포함. ledger write 경로가 live portfolio-hub 대상으로 동작하지 않던 release blocker 를 해소. release 문서는 직접 덮어쓰지 않고 본 항목으로 영향·후속 action 기록(rollout/verification 시 ledger POST 201 확인 항목 유효).
#### Revalidation Impact
- Reverification Needed: yes — 완료. 정정 어댑터에 대해 신규 contract 테스트 통과 + 실 portfolio-hub e2e(매수 체결 → `POST /v1/portfolios/{portfolio_id}/ledger/trades` 201 Created, outbox COMPLETED) 확인. 직전 404 로 쌓였던 outbox 항목이 정정 후 재기동 시 무유실 드레인되어 durability(REQ-019) 도 함께 실증.
- Existing Implementation Invalidated: no — 정정 후 현재 코드가 실 계약과 일치. 무효화된 것은 구 가정(A-1)에 기반한 엔드포인트/필드 기술뿐.
- Status Recommendation: Implemented 유지.
- Follow-up Action: 멱등/중복 응답 시그널(현 어댑터는 2xx body `duplicate` 플래그 + 409 처리 유지) 은 실 API 가 중복 execution_id 에 어떤 코드를 반환하는지 확인되면 후속 정합.
#### Validation Plan
- AC-2: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_post_contract_includes_execution_id_and_path_uses_portfolio_id (path `/v1/portfolios/PF-77/ledger/trades` + body `quantity`/`fee_amount`/`tax_amount` 단언, `qty`/`fee`/`tax` 부재 단언).
- AC-1/AC-3/AC-4: 기존 테스트 회귀(16 passed) 유지.
- 실 e2e: 매수 intent → 체결 → ledger POST 201 + outbox completed.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-015/issue #13) portfolio-hub 어댑터 오류 분류 분리: **404/400/422 → `LedgerPermanentError`**(신규, 재시도로 해소 불가), 409→conflict(불변), 408/429/5xx/network→retryable(불변). LedgerPort 에 `LedgerPermanentError` 타입 추가 + 어댑터 계약 헤더 갱신. BAT-019 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 미등록 포트폴리오 404 가 retryable 로 오분류되어 영구 재시도되던 근본 원인 제거 — 영속(클라이언트) 오류를 명시 타입으로 분리해 outbox 가 터미널 처리하게 한다.
#### Acceptance Criteria Delta
- Added:
- 어댑터가 404/400/422 를 `LedgerPermanentError` 로 매핑. LedgerPort 에 permanent 오류 타입.
#### Impacted Area Delta
- Modules: `trex_core/application/ledger_port.py`(LedgerPermanentError), `trex_core/adapters/ledger/portfolio_hub.py`(분류), `trex_core/adapters/ledger/in_memory.py`(permanent_executions 주입)
- Tests: tests/adapters/ledger/test_portfolio_hub_ledger.py(404/400/422 permanent, 429 retryable)
#### Delivery Trace Delta
- Batches: BAT-019
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-019 에서 완료)
- Existing Implementation Invalidated: yes (404 등을 retryable 로 분류하던 경로)
- Status Recommendation: Implemented (change-req → BAT-019 재검증)
- Follow-up Action: confirm-req-implemented(BAT-019).
#### Validation Plan
- BAT-019: 404/400/422→LedgerPermanentError, 429/5xx→retryable, 409→conflict 단위.
### CHG-20260608-02
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-018) `record_trade` 반환을 `LedgerWriteResult` → **`LedgerWriteOutcome{result, cash_balance_after?, asset_quantity_after?}`** 로 확장. portfolio-hub 응답 root flat `cash_balance_after`/`asset_quantity_after`(option D, portfolio-hub#8)를 `new`(201)·`duplicate`(200)에서 파싱(누락 시 None). 409/422 는 기존대로 예외. BAT-023 재구현·재검증 완료 → Implemented 유지.
#### Intent
- ledger write 응답의 권위 cash/quantity 를 운반해 SnapshotStore write-back(REQ-019/030)을 가능하게 한다(매수/매도 직후 현금 stale 제거).
#### Acceptance Criteria Delta
- Added:
- `record_trade` → `LedgerWriteOutcome` 반환(result + `*_after`). 어댑터가 응답 root 의 `cash_balance_after`/`asset_quantity_after` 파싱(new+duplicate, 누락 시 None).
#### Impacted Area Delta
- Modules: `trex_core/application/ledger_port.py`(LedgerWriteOutcome), `trex_core/adapters/ledger/portfolio_hub.py`(파싱), `trex_core/adapters/ledger/in_memory.py`(outcome 반환 + cash/qty 주입)
- Tests: tests/adapters/ledger/test_portfolio_hub_ledger.py(*_after new/duplicate/누락)
#### Delivery Trace Delta
- Batches: BAT-023
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-023 에서 완료)
- Existing Implementation Invalidated: yes (record_trade 반환 타입)
- Status Recommendation: Implemented (change-req → BAT-023 재검증)
- Follow-up Action: confirm-req-implemented(BAT-023).
#### Validation Plan
- BAT-023: 응답 *_after 파싱(new/duplicate/누락) 단위.
@@ -1,372 +0,0 @@
# req-018: 체결→원장 기록 (집행 루프 fill hook)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 코어 집행 루프가 체결(매수/매도) 시 해당 거래를 portfolio-hub 원장에 기록하도록, fill 전이 경로에 ledger 기록 hook 을 더하고 체결 데이터(execution_id/fee)를 브로커 계약으로 운반한다.
## Requirement
- 집행 루프가 매수 체결(`pending_buy→holding`)·매도 체결(`pending_sell→closed`) 시 LedgerPort 로 거래를 기록한다. ledger path 의 `asset_id` 는 슬롯 `portfolio_id`(= asset_id)를 직접 사용하고 side/qty/price/execution_id/fee/tax/executed_at 을 함께 보낸다.
- `FillStatus`(broker_port) 가 `execution_id`, `fee`, `tax` 를 운반하도록 계약을 확장한다. `execution_id` 는 결정적으로 합성한다(KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`, paper: `paper:{broker_order_id}:{filled_qty}`). `fee` 는 `calculate_fee(side, amount)`(매수·매도 공통 수수료율), `tax` 는 `calculate_tax(side, amount)`(매도 정책 증권거래세율, 매수 0)로 계산한다. 수수료율·세율은 단일 정책 지점에서 env(`TREX_KIS_FEE_BPS`=1.5, `TREX_KRX_TAX_SELL_BPS`=20.0)로 관리하고 paper·KIS 어댑터가 동일 정책을 사용한다(`tax=0.0` 하드코딩 금지). KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고 없을 때만 정책 폴백한다. (dcy-026)
- 원장 기록 주체는 코어 집행 경로 단일로 한정한다(전략/도메인은 원장에 접근하지 않는다).
## Acceptance Criteria
1. Given idle 슬롯에 매수 intent → 체결, When 집행 루프 fill 전이, Then 슬롯이 holding 으로 전이되고 원장에 buy 거래 1건이 기록된다(asset_id = 슬롯 portfolio_id).
2. Given holding 슬롯에 매도 intent → 체결, When 집행 루프 fill 전이, Then 슬롯이 closed 로 전이되고 원장에 sell 거래 1건이 기록된다.
3. Given 체결된 주문, When fill 을 읽음, Then `FillStatus` 가 결정적 `execution_id`, 계산된 `fee`, 계산된 `tax`(매도 정책 세율, 매수 0)를 운반한다.
4. Given 슬롯에 `portfolio_id` 가 없는 체결, When fill 전이, Then 원장 기록을 건너뛰거나(또는 정의된 정책으로 처리) 슬롯 전이 자체는 정상 수행된다.
5. Given 전략/도메인 코드, When 점검, Then 원장 기록은 코어 집행 경로에서만 발생한다(단일 기록 주체). (NFR-3)
6. Given 매도 체결(amount=price×qty), When fill 을 읽음, Then `tax` = `amount × TREX_KRX_TAX_SELL_BPS / 10_000`(기본 0.2%)로 계산되고, 매수 체결의 `tax` 는 0 이다.
7. Given 수수료/세율 정책, When paper·KIS 어댑터로 동일 `(side, amount)` 입력, Then 동일 정책 결과를 산출한다(단일 정책 지점 `calculate_fee`/`calculate_tax`, env 설정). `tax=0.0` 하드코딩이 코드에 남지 않는다.
8. Given KIS 체결 응답에 수수료/세금 필드 존재, When fill 파싱, Then 그 값을 정본으로 사용하고, 필드 부재 시에만 정책 추정값으로 폴백한다.
9. Given issue #24 4건 체결(BUY 291,500 / SELL 301,000 / BUY 295,000 / SELL 298,500, qty=1), When fee/tax 계산, Then `fee+tax` 합계가 1,376.9원(fee 177.9 + tax 1,199)에 수렴한다.
## Impacted Area
- Docs: docs/runtime-flows.md(체결→원장 기록 + fee/tax 산출 흐름), docs/interface-contract.md(FillStatus 확장) — REQ-020 에서 정합
- Modules: `trex_core/application/execution.py`(fill 전이에 ledger 기록 단계), `trex_core/application/broker_port.py`(FillStatus.execution_id/fee/tax + `calculate_fee`/`calculate_tax` 단일 정책 지점 + env 세율 상수), `trex_core/adapters/broker/{paper,kis}.py`(execution_id 합성 + fee/tax 계산, KIS 응답 정본 우선)
- Tests: stub 원장으로 매수/매도 체결 시 buy/sell 1건 + 슬롯 전이, FillStatus execution_id/fee/tax 운반, portfolio_id 부재 처리, 매도 세율 + issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책 + KIS 응답 우선
## Notes
- 출처: dcy-004 FR-2, OQ-1(asset_id == portfolio_id, orbit 근거), OQ-3(execution_id/fee/tax 출처), NFR-3.
- OQ-1: 슬롯은 이미 buy intent 가 `portfolio_id` 를 공급해 캐시하므로 추가 매핑/조회 없이 path param 으로 사용한다.
- `portfolio_id` 부재 시 기록 skip vs 거부 정책은 구현 시 확정(AC-4 는 둘 다 허용하되 슬롯 전이 일관성을 요구).
- 멱등/무유실/재시도(correlation_id, outbox)는 REQ-019 에서 다룬다. 본 REQ 는 기록 호출 경로와 데이터 운반에 한정한다.
- (dcy-026/CHG-20260612-01) 매도 증권거래세 정책 확정: 매도세 0.2%(`TREX_KRX_TAX_SELL_BPS`=20.0), 수수료 0.015%(`TREX_KIS_FEE_BPS`=1.5, 매수·매도 공통). 변수명에 `KIS`/`KRX` 도메인 라벨이 있으나 단일 정책 지점으로 paper·KIS 가 동일 공유. dcy-004 OQ-3 "정밀 세금 모델" 후속 해소.
- 과거 잘못 저장된 ledger 행 소급 보정은 Out of Scope(dcy-026 OQ-3). 배포 후 신규 체결부터 정확값 저장.
- REQ-011(BrokerPort 추상화)는 본 변경 대상 아님: fee/tax 는 REQ-018 의 `FillStatus` 확장 + `calculate_fee`/`calculate_tax` 정책 영역이며, REQ-011 의 포트 계약(submit/poll/cancel async 더블 + 도메인 순수성)은 영향받지 않음.
## Change Log
### CHG-20260604-01
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- dcy-004 FR-2 로부터 최초 생성(Proposed). OQ-1/OQ-3 결정 반영, NFR-3(단일 기록 주체) 흡수.
#### Intent
- 집행 루프 fill 전이에서 체결을 원장에 기록하는 경로와 FillStatus 확장(execution_id/fee)을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- 매수/매도 체결 기록+전이, FillStatus execution_id/fee, portfolio_id 부재, 단일 기록 주체 5개 기준
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md, docs/interface-contract.md
- Modules:
- execution.py(ledger hook), broker_port.py(FillStatus), paper/kis 어댑터(execution_id/fee)
- Tests:
- 체결→기록 통합테스트, FillStatus 운반, portfolio_id 부재
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- stub 원장으로 매수/매도 체결 lifecycle 통합테스트 + FillStatus 확장 테스트로 검증.
### CHG-20260604-02
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과.
#### Intent
- 집행 루프 fill 전이의 ledger 기록 hook 과 FillStatus 확장을 delivery input 으로 승인.
#### Acceptance Criteria Delta
- Added:
- 없음
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- 없음
- Modules:
- 없음
- Tests:
- 없음
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: batch 편성
#### Validation Plan
- 기존 Acceptance Criteria 로 batch delivery 에서 검증.
### CHG-20260604-03
- Date: 2026-06-04
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반.
#### Intent
- 집행 루프 fill→ledger hook + FillStatus.execution_id/fee 확장 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (변경은 상태 전이만)
#### Delivery Trace Delta
- Batches:
- BAT-006 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: release 단계는 별도 진행
#### Validation Plan
- AC-1: tests/application/test_execution_ledger.py::test_buy_fill_enqueues_one_ledger_item_and_writer_records_it.
- AC-2: tests/application/test_execution_ledger.py::test_sell_fill_enqueues_sell_ledger_item.
- AC-3: 위 buy 테스트가 FillStatus.execution_id/fee/tax 운반 직접 검증.
- AC-4: tests/application/test_execution_ledger.py::test_portfolio_id_absent_skips_ledger_but_transitions_slot.
- AC-5: tests/domain/test_domain_import_sweep.py + grep 결과(trex_core/application/execution.py 단일 enqueue 지점).
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-011/issue #11) `_record_ledger` 의 ledger skip 을 무성에서 **가시화**로 전환: portfolio_id/execution_id 누락 skip 시 WARN 로그 + `ExecutionResult.ledger_skipped` 카운터 노출(슬롯 전이는 그대로). BAT-015 재구현·재검증 완료 → Implemented 유지.
#### Intent
- portfolio_id 없는 체결의 ledger silent skip(회계 누락)을 운영자가 탐지 가능하게(issue #11 제안 ②). OQ-4 결정으로 outbox 마커 행은 도입하지 않고 로그+메트릭만; 영구 탐지는 REQ-055 점검 쿼리에 위임.
#### Acceptance Criteria Delta
- Updated:
- AC-4: skip 시 무성 반환 → WARN 로그 + 카운터 가시화(두 skip 경로 모두).
#### Impacted Area Delta
- Modules: `trex_core/application/execution.py`(`_record_ledger` 로그 + `_log` 추가)
- Docs: `docs/runtime-flows.md`
- Tests: tests/application/test_execution_ledger.py::test_ledger_skip_emits_warning
#### Delivery Trace Delta
- Batches: BAT-015
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-015 에서 완료)
- Existing Implementation Invalidated: no (관측만 추가, 전이 동작 불변)
- Status Recommendation: Implemented (change-req → BAT-015 재검증)
- Follow-up Action: confirm-req-implemented(BAT-015).
#### Validation Plan
- BAT-015: ledger skip WARN 로그 단위(caplog) + 회귀.
### CHG-20260608-02
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-013/issue #9) 부분 체결 후 외부 취소 시 체결분(`ccld_qty`)을 원장에 **1회** 기록한다. `_on_partial_cancel` 이 `_record_ledger`(기존)를 재사용해 partial fill 의 execution_id/fee/qty 를 정확히 운반. BAT-017 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 전량 체결만 다루던 원장 기록을 부분 체결+취소에도 확장해 체결분 누락/이중기록을 방지(정확 회계, OQ-1 A안).
#### Acceptance Criteria Delta
- Updated:
- AC-1/2 의 fill→원장 기록을 부분 체결 후 취소(체결분 1회)에도 적용.
#### Impacted Area Delta
- Modules: `trex_core/application/execution.py`(`_on_partial_cancel` → `_record_ledger` 재사용)
- Tests: tests/application/test_execution_cancel.py::test_partial_fill_then_cancel_records_and_keeps_remainder
#### Delivery Trace Delta
- Batches: BAT-017
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-017 에서 완료)
- Existing Implementation Invalidated: no (기록 경로 재사용, 호출 지점만 확장)
- Status Recommendation: Implemented (change-req → BAT-017 재검증)
- Follow-up Action: confirm-req-implemented(BAT-017).
#### Validation Plan
- BAT-017: 부분 취소 시 sell 원장 1건(qty=체결분, price=체결가) 단위.
### CHG-20260608-03
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-015/issue #13) ledger 터미널 실패 가시화 확장: `delivery_failed`(permanent/dead-letter)를 `WriterStats.delivery_failed` 카운터 + WARN 로그 + 구분 status 로 노출(dcy-011 `ledger_skipped` 가시화 패턴 정합). dead-letter 는 운영자 재처리 가능 상태이며 `find_ledger_gap_slots`(REQ-055)·skip 가시화와 연계. BAT-019 재구현·재검증 완료 → Implemented 유지.
#### Intent
- ledger 가 끝내 기록되지 않는 실패(404 영속·dead-letter)가 무성 pending 으로 남지 않고 운영자에게 노출되도록(NFR-2 가시성).
#### Acceptance Criteria Delta
- Updated:
- 원장 미기록(터미널 delivery_failed) 시 WARN 로그 + 카운터 + 구분 status 가시화.
#### Impacted Area Delta
- Modules: `trex_core/application/ledger_outbox.py`(WARN + WriterStats.delivery_failed)
- Tests: tests/application/test_ledger_outbox_backoff.py::test_permanent_failure_emits_warning
#### Delivery Trace Delta
- Batches: BAT-019
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-019 에서 완료)
- Existing Implementation Invalidated: no (관측만 추가)
- Status Recommendation: Implemented (change-req → BAT-019 재검증)
- Follow-up Action: confirm-req-implemented(BAT-019).
#### Validation Plan
- BAT-019: delivery_failed WARN 로그(caplog) + 카운터 단위.
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-026/issue #24) 매도 증권거래세 누락 정정. `FillStatus.tax` 가 매도 체결에 대해 정책 세율(증권거래세)로 계산되도록 계약을 정정한다. 기존 `tax 는 기본 0 으로 한다` 규정과 `tax=0.0` 하드코딩(paper/kis 어댑터)을 제거하고, 수수료율·세율을 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 env 설정으로 관리한다. KIS 어댑터는 브로커 응답 fee/tax 가 있으면 정본 우선, 없으면 정책 폴백. 기존 `tax=0` 전제 구현/검증 evidence 무효화 → **Implemented → Approved 되돌림**.
#### Intent
- 한국 주식 매도 증권거래세(약 0.2%)가 전 체결에서 `tax=0.0` 으로 저장되어 실현 손익·일일 수익률이 과대 계산되던 운영 버그(issue #24, 2026-06-11 4건 체결 증거)를 닫는다. `broker_port.py` 가 dcy-004 OQ-3 로 미뤄둔 "정밀 세금 모델"을 정식 정책으로 확정한다.
#### Acceptance Criteria Delta
- Updated:
- AC-3: `FillStatus` 가 결정적 `execution_id` 와 계산된 `fee`(tax=0) 운반 → `fee` 와 **매도 정책 세율로 계산된 `tax`**(매수 tax=0) 운반.
- Added:
- AC-6: 매도 체결의 `tax` 는 `amount × TREX_KRX_TAX_SELL_BPS / 10_000`(기본 20.0bps = 0.2%)로 계산되고, 매수 체결의 `tax` 는 0 이다.
- AC-7: 수수료율·세율은 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 env(`TREX_KIS_FEE_BPS`=1.5, `TREX_KRX_TAX_SELL_BPS`=20.0)로 관리되고 paper·KIS 어댑터가 동일 정책을 사용한다. `tax=0.0` 하드코딩은 제거된다.
- AC-8: KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고, 없을 때만 정책 추정값으로 폴백한다.
- AC-9: issue #24 4건 체결(BUY 291,500 / SELL 301,000 / BUY 295,000 / SELL 298,500, qty=1) 재현 시 `fee+tax` 합계가 §4 공식 기대값 **1,376.9원**(fee 177.9 + tax 1,199)에 수렴한다(회귀 테스트).
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md(체결→ledger fee/tax 산출·저장 흐름 갱신)
- Modules:
- `trex_core/application/broker_port.py`(`calculate_tax` 신규 + 세율 상수, env 로딩)
- `trex_core/adapters/broker/paper.py`(`tax=0.0` → `calculate_tax(...)`)
- `trex_core/adapters/broker/kis.py`(`tax=0.0` → 브로커 응답 파싱 + 정책 폴백)
- Tests:
- 매수/매도 fee·tax 단위 테스트 + issue #24 4건 회귀(1,376.9원) + KIS 응답 우선 경로 + paper·KIS 동일 정책 일치
#### Delivery Trace Delta
- Batches:
- BAT-038 (예정, dcy-026 fee/tax)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: yes (BAT-038 에서 매도 tax 계산·회귀 재검증)
- Existing Implementation Invalidated: yes (`tax=0` 전제 — AC-3 의 tax=0 evidence 및 paper/kis `tax=0.0` 하드코딩 무효)
- Status Recommendation: Implemented → **Approved** (구현/검증 후 confirm-req-implemented 로 복귀)
- Follow-up Action: confirm-req → draft-batch(BAT-038) → 구현·검증 → confirm-req-implemented
#### Validation Plan
- BAT-038: 매수/매도 fee·tax 단위 테스트(매도 0.2% 세율, 매수 tax=0), issue #24 4건 회귀(`fee+tax`=1,376.9원), KIS 응답 fee/tax 우선·폴백 경로, paper·KIS 동일 입력 동일 출력. `pytest` 전체 회귀 + `ruff check` clean.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved → **Implemented** (confirm-req-implemented, 근거 batch BAT-038 release-candidate).
#### Intent
- BAT-038 verification.md 의 acceptance mapping + evidence(429 passed / fee·tax 정책 단위 / issue #24 1,376.9원 회귀 / `tax=0.0` 하드코딩 제거 grep / runtime-flows 동기화)로 AC-1~9 충족 → 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음 (상태 전이만)
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (상태 전이만)
#### Delivery Trace Delta
- Batches:
- BAT-038 (release-candidate)
- Releases:
- none (release 편성은 별도 진행)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: draft-release 로 release 편성(BAT-038 포함)
#### Validation Plan
- BAT-038 verification evidence 재참조로 충분.
- AC-3/6: `test_paper_sell_fill_carries_policy_tax`, `test_calculate_tax_sell_only`, `test_paper_buy_fill_has_zero_tax`.
- AC-7: `test_calculate_fee_same_rate_buy_and_sell`, `test_fee_and_tax_zero_for_nonpositive_amount`, `tax=0.0` grep 0건.
- AC-8: design Key Decision 3(KIS daily-ccld fee/tax 필드 부재 → 정책 폴백) + 코드 리뷰.
- AC-9: `test_issue_24_four_fill_regression` (1,376.9원).
- AC-1/2/4/5: BAT-006 evidence 유효 + 전체 회귀 429 passed.
@@ -1,307 +0,0 @@
# req-021: messaging 토폴로지 + 메시지 계약 매핑
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- trex-core ↔ 전략 모듈 통신을 RabbitMQ(AMQP) 메시지 기반으로 정의해, 스냅샷·intent admission 은 request-reply 로, 체결/슬롯 상태는 이벤트(pub/sub)로 전달한다. 기존 logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id 필터, intent 스키마)은 transport-무관하게 보존하고 메시지 payload/속성/routing key 로 의미 손실 없이 매핑한다.
## Requirement
- 통신 토폴로지를 아래 3 패턴으로 확정한다(dcy-006 OQ-2).
1. 스냅샷 RPC(request-reply): 요청 queue `trex.snapshot.req`(direct/default exchange), 응답은 RabbitMQ direct reply-to(`amq.rabbitmq.reply-to`) + `correlation_id`. 요청 payload 에 `strategy_id` 와 `view` 모드(dcy-005 OQ-11/FR-8).
2. intent admission RPC(request-reply): 요청 queue `trex.intent.req`, 응답 = admission 결과(`status`, `slot_id`, `reason`). `correlation_id` = intent 멱등키(dcy-003 패턴) 로 RPC 상관과 dedup 일원화. 체결/종료(fill/close) 결과는 응답이 아니라 이벤트(③)로 전달해 admission 동기 + fill 비동기 2단계 시맨틱을 보존한다.
3. 이벤트 pub/sub: trex-core 가 체결·슬롯 상태 변화를 topic exchange `trex.events` 에 발행한다. routing key 는 `{event_type}.{strategy_id}.{symbol}` 형식이며, 전략 queue 는 `*.{strategy_id}.#` 패턴으로 바인딩해 자기 자산 이벤트만 수신한다(broker 단 필터).
- 매핑 원칙을 다음과 같이 고정한다: 요청 메시지(①·②) 의 `view` 모드와 `strategy_id` 는 message **payload** 에, 이벤트(③) 의 `strategy_id`·`symbol` 은 **routing key** 에 싣는다.
- 위 토폴로지/매핑은 transport-무관 logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id, intent 스키마)을 변경하지 않는다.
## Acceptance Criteria
1. Given trex-core daemon, When 기동, Then 스냅샷 RPC 요청 큐(`trex.snapshot.req`), intent RPC 요청 큐(`trex.intent.req`), 이벤트 토픽 익스체인지(`trex.events`)가 선언되어 있다.
2. Given stub/실 어댑터로 발행한 스냅샷 RPC 요청, When trex-core 가 응답, Then 응답 메시지가 HTTP 응답과 동일한 logical 필드(필드/마스킹/view 모드 적용)를 운반하고 `correlation_id` 가 요청과 일치한다.
3. Given intent admission RPC 요청 메시지, When trex-core 가 처리, Then 슬롯 전이 후 admission 응답(`status, slot_id?, reason?`)이 reply-to 큐로 반환되고 체결/종료는 응답이 아니라 후속 이벤트로 발행된다.
4. Given 동일 종목·이벤트가 전략 A 와 전략 B 의 queue 에 각각 `*.A.#`/`*.B.#` 패턴으로 바인딩된 상태, When trex-core 가 strategy_id=A 의 이벤트를 발행, Then A 의 queue 만 수신하고 B 의 queue 는 수신하지 않는다.
5. Given 요청 메시지, When 페이로드를 검사, Then `view`/`strategy_id` 가 payload 에 있고 routing key 에 들어가 있지 않다(요청 매핑 원칙).
## Impacted Area
- Docs: docs/interface-contract.md(메시지 계약), docs/runtime-flows.md(RPC/이벤트 흐름) — REQ-026 에서 정합.
- Modules: 신규 `trex_core/adapters/messaging/` 토폴로지 선언(exchange/queue/binding) 및 routing key 생성 로직.
- Tests: stub 어댑터로 RPC 요청-응답 매핑, 이벤트 routing key 바인딩 필터링, 요청/이벤트 매핑 원칙 검사.
## Notes
- 출처: dcy-006 FR-1, FR-2, OQ-2.
- 토폴로지 선언과 어댑터 구현 분리: 본 REQ 는 "무엇을/어떻게 매핑하나" 계약을 다루고, RabbitMQ 어댑터 + 런타임 통합 자체는 REQ-022 에서 다룬다.
- logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id, intent 스키마)은 dcy-005/002/003 소관이며 본 REQ 가 변경하지 않는다.
- dcy-005 의 스냅샷 read 계약이 아직 REQ 로 정규화되지 않았으면 본 REQ 의 AC-2 검증은 stub 응답 셰이프 수준에서 시작하고, 정식 계약 확정 후 evidence 를 보강한다.
## Change Log
### CHG-20260605-01
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- dcy-006 FR-1/FR-2 로부터 최초 생성(Proposed). 통신 토폴로지(스냅샷 RPC + intent RPC + 이벤트 topic) 와 요청/이벤트 매핑 원칙(view·strategy_id payload, event routing key)을 backlog 로 정규화.
#### Intent
- transport 추상화의 메시지 계약과 토폴로지 결정을 별도 REQ 로 분리해 어댑터 구현(REQ-022) 및 신뢰성(REQ-023)과 독립 진척.
#### Acceptance Criteria Delta
- Added:
- 토폴로지 선언 4종, RPC 응답 매핑 1종, 이벤트 routing key 필터링 1종, 매핑 원칙 검사 1종 (총 5개 기준)
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md, docs/runtime-flows.md
- Modules:
- adapters/messaging 토폴로지 선언
- Tests:
- RPC 요청-응답, 이벤트 routing key 필터링, 매핑 원칙 검사
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- stub 어댑터 통합테스트로 RPC 매핑·이벤트 routing key 필터링·매핑 원칙을 검증.
### CHG-20260605-02
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과.
#### Intent
- messaging 토폴로지 + 메시지 계약 매핑을 delivery input 으로 승인.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: REQ-022/023/025 와 함께 batch 편성(통신 단절 R-5 회피)
#### Validation Plan
- 기존 Acceptance Criteria 로 batch delivery 에서 검증.
### CHG-20260605-03
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반.
#### Intent
- messaging 토폴로지 + 메시지 계약 매핑 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음
#### Delivery Trace Delta
- Batches:
- BAT-007 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: release 단계는 별도 진행
#### Validation Plan
- AC-1: tests/adapters/messaging/test_rabbitmq_transport.py::test_declare_topology_creates_core_entities.
- AC-2: tests/application/test_messaging_snapshot_consumer.py + tests/adapters/messaging/test_rabbitmq_transport.py::test_rpc_roundtrip.
- AC-3: tests/application/test_messaging_intent_consumer.py + test_messaging_event_publisher.py 조합.
- AC-4: tests/application/test_messaging_event_publisher.py::test_event_for_strategy_a_does_not_reach_strategy_b_queue + tests/adapters/messaging/test_rabbitmq_transport.py::test_event_publish_and_per_strategy_consume.
- AC-5: tests/adapters/messaging/test_topology.py.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-011) buy intent **메시지 계약**에서 `symbol` 필드 제거, `portfolio_id` 필수화. buy 핸들러는 payload 에서 symbol 을 읽지 않고 portfolio_id 만 매핑(서버 resolve 는 REQ-007). BAT-015 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 요청 매핑 원칙(payload 식별자)을 정식 식별자(portfolio_id) 기준으로 정정(issue #6). buy 핸들러 payload 파싱 변경.
#### Acceptance Criteria Delta
- Updated:
- buy payload 계약: `symbol` 제거, `portfolio_id` 필수.
#### Impacted Area Delta
- Modules: `trex_core/application/messaging_handlers.py`(buy 핸들러)
- Docs: `docs/interface-contract.md`
- Tests: tests/application/test_messaging_intent_consumer.py + tests/adapters/messaging/test_rabbitmq_intent_e2e.py
#### Delivery Trace Delta
- Batches: BAT-015
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-015 에서 완료)
- Existing Implementation Invalidated: yes (symbol 기반 buy payload)
- Status Recommendation: Implemented (change-req → BAT-015 재검증)
- Follow-up Action: confirm-req-implemented(BAT-015).
#### Validation Plan
- BAT-015: buy payload portfolio_id 기반 핸들러 단위 + RabbitMQ e2e.
### CHG-20260608-02
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-016/REQ-034 연계) snapshot 요청 payload 에 워치독 억제 플래그 추가: `touch_watchdog`(기본 true) / `actor="operator"`. `make_snapshot_rpc_handler` 가 플래그 시 holding 슬롯 `last_seen_at` 갱신을 건너뛴다. 기본값=현행(전략 폴링=워치독 갱신) 유지(하위호환). BAT-020 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 운영자 CLI 조회가 전략 생존 신호로 오인되어 safety-sell 을 무력화하는 것 방지(R-4). 메시지 계약에 억제 플래그를 정식화.
#### Acceptance Criteria Delta
- Added:
- snapshot 요청 payload `touch_watchdog`/`actor` 플래그(기본 미지정=touch). 운영자 조회는 억제.
#### Impacted Area Delta
- Modules: `trex_core/application/messaging_handlers.py`(make_snapshot_rpc_handler 억제 분기)
- Docs: `docs/interface-contract.md`
- Tests: tests/application/test_messaging_snapshot_consumer.py(touch vs suppress)
#### Delivery Trace Delta
- Batches: BAT-020
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-020 에서 완료)
- Existing Implementation Invalidated: no (기본값 현행 유지, 플래그 추가)
- Status Recommendation: Implemented (change-req → BAT-020 재검증)
- Follow-up Action: confirm-req-implemented(BAT-020).
#### Validation Plan
- BAT-020: 기본 요청 touch / operator 요청 suppress 단위(caplog/recorder).
### CHG-20260608-03
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-017/REQ-034 연계) snapshot RPC 응답에 `watchdog` 필드(holding 슬롯 `{slot_id, portfolio_id, symbol, expired, last_seen}` 배열) 추가(best-effort, 실패 시 생략). 메시지 계약 확장. BAT-021 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 워치독 소유 상태를 운영자 CLI 가 관측할 수 있도록 스냅샷 응답 계약에 read-only 필드 정식화.
#### Acceptance Criteria Delta
- Added:
- snapshot 응답 optional `watchdog[]` 필드(read-only).
#### Impacted Area Delta
- Modules: `trex_core/application/messaging_handlers.py`(_attach_watchdog)
- Docs: `docs/interface-contract.md`
- Tests: tests/application/test_messaging_snapshot_consumer.py
#### Delivery Trace Delta
- Batches: BAT-021
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-021 에서 완료)
- Existing Implementation Invalidated: no (additive 필드)
- Status Recommendation: Implemented (change-req → BAT-021 재검증)
- Follow-up Action: confirm-req-implemented(BAT-021).
#### Validation Plan
- BAT-021: snapshot 응답 watchdog 필드 shape 단위.
### CHG-20260611-01
- Date: 2026-06-11
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-025) 토폴로지의 스냅샷 RPC 큐를 **`trex.snapshot.req` → `trex.slots.req`** 로 교체한다(즉시 교체, 과도기 없음 — dcy-025 OQ-1). 요청 payload 매핑 원칙도 갱신: `view`/`strategy_id` 대신 **`filter`/`spy`**(REQ-073). 응답은 slots[]+health 만(REQ-073/074). intent RPC·이벤트 pub/sub 토폴로지는 불변. 전략 모듈(trex-orb-v1) 소비 측 lockstep cutover 필요.
#### Intent
- 자산 중심 모델의 read 표면 재설계(dcy-025)에 따른 토폴로지·매핑 계약 정합.
#### Acceptance Criteria Delta
- Updated:
- AC-1: 기동 시 선언되는 read 큐가 `trex.snapshot.req` 가 아니라 `trex.slots.req`(per-instance)다.
- AC-2: read RPC 응답의 logical 필드 기준이 snapshot envelope(assets/view) 가 아니라 REQ-073/074 의 slots 계약이다.
- AC-5: 요청 payload 매핑 원칙의 필드가 `view`/`strategy_id` → `filter`/`spy` 로 바뀐다(payload 에 싣는 원칙은 유지).
- Added/Removed: none
#### Impacted Area Delta
- Modules: trex_core/adapters/messaging/topology.py(SNAPSHOT_REQ_QUEUE→SLOTS_REQ_QUEUE), messaging_handlers.py
- Docs: docs/interface-contract.md(REQ-075)
- Tests: 토폴로지 선언/큐명 단위
#### Delivery Trace Delta
- Batches: 신규 batch(REQ-073/074/075 와 동일 배치, dcy-025)
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (신규 batch 에서 갱신 AC 검증)
- Existing Implementation Invalidated: yes (snapshot 큐·view 매핑의 기존 evidence 가 표면 교체로 무효화)
- Status Recommendation: Implemented → Approved (batch 재구현·재검증 후 복귀)
- Follow-up Action: draft-batch → 구현/검증 → confirm-req-implemented
#### Validation Plan
- 신규 BAT: 토폴로지 선언(slots.req 존재·snapshot.req 부재) 단위 + filter/spy payload 매핑 단위.
@@ -1,193 +0,0 @@
# req-022: messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 메시징 접근을 application 경계의 transport 포트로 추상화하고, `pika`(동기) RabbitMQ 어댑터와 결정론 stub 을 adapters 레이어에 격리해 도메인 순수성을 유지하면서 메시지 기반 통신을 가능하게 한다. uvicorn 제거 후 데몬을 sync+thread 런타임으로 재구성한다.
## Requirement
- application 레이어에 `MessagingTransport` (또는 동등한) Protocol 을 정의해 publish/subscribe/RPC consume 진입점을 추상화한다.
- 신규 `trex_core/adapters/messaging/` 패키지에 두 구현체를 둔다.
- `RabbitMQTransport`: `pika`(동기) BlockingConnection 기반. consumer/publisher 스레드별 connection/channel 또는 `add_callback_threadsafe` 규약을 따른다(pika 는 thread-safe 가 아님).
- `InMemoryTransport` (stub): 결정론 테스트용 in-process publish/subscribe/RPC.
- 데몬을 sync+thread 런타임으로 재구성한다(dcy-006 OQ-5): consumer 스레드(intent RPC, prefetch=1 단일 FIFO; snapshot RPC) + 기존 execution/ledger 루프 스레드 + 이벤트 publisher(execution 루프가 자기 channel 로 발행). graceful shutdown 은 기존 `stop` event 패턴을 확장해 connection close 까지 포함한다.
- pika 자동 재연결이 없는 특성을 보정하기 위해 connection drop 시 백오프 기반 재연결 루프를 직접 구현하고 REQ-024 degrade 정책과 연동한다.
- `trex_core/domain` 은 transport/AMQP 의존 0 을 유지한다(NFR-1, import 스윕).
## Acceptance Criteria
1. Given in-process `InMemoryTransport` stub, When 테스트가 publish/subscribe/RPC 진입점을 호출, Then 외부 broker 없이 메시지 송수신이 동작한다.
2. Given `RabbitMQTransport`, When `TREX_RABBITMQ_URL` 로 broker 에 접속, Then connection/channel 이 열리고 declared 큐/익스체인지로 메시지 송수신이 가능하다(integration 수준, 인프라 미가용 시 skip 게이트 허용).
3. Given 데몬 기동, When lifecycle 을 시작, Then intent RPC consumer 스레드, snapshot RPC consumer 스레드, execution/ledger 스레드가 동시에 동작하고, shutdown 시 `stop` event 후 모든 스레드와 connection 이 정상 종료된다(`uvicorn.run` 의존 0).
4. Given broker connection 이 끊긴 상태, When 어댑터가 감지, Then 백오프 재연결을 직접 수행하고, REQ-024 degrade 정책(버퍼링 금지·보수적 중단)에 따라 신규 처리를 중단한다.
5. Given 정적 import 스윕, When `trex_core/domain` 을 점검, Then `pika`/AMQP/`trex_core.adapters.messaging` 의존이 0 이다(NFR-1).
## Impacted Area
- Docs: docs/project-structure.md(adapters/messaging 위치 + 런타임 스레드 구성), docs/runtime-flows.md(consumer 스레드/이벤트 publisher 흐름) — REQ-026 에서 정합.
- Modules:
- 신규 `trex_core/application/messaging.py`(`MessagingTransport` Protocol)
- 신규 `trex_core/adapters/messaging/` (`rabbitmq.py`, `in_memory.py`)
- 변경 `trex_core/runtime/daemon.py`(uvicorn 제거, consumer 스레드 추가, graceful shutdown 확장)
- 변경 `trex_core/runtime/config.py` (`TREX_RABBITMQ_URL` 로딩; REQ-025 와 함께 `TREX_API_HOST`/`TREX_API_PORT` 폐기)
- 영향 `trex_core/application/intents.py`, dcy-005 스냅샷 application 서비스 — transport-무관 포트로 호출되도록 재배선
- Tests:
- in-process stub 통합테스트(publish/subscribe/RPC)
- RabbitMQ integration 테스트(`TREX_RABBITMQ_URL` 게이트, 미가용 시 skip)
- 데몬 lifecycle 테스트(스레드 기동/종료)
- `tests/domain/test_domain_import_sweep.py` 가 `pika`·`adapters.messaging` 추가 금지 prefix 로 가드
## Notes
- 출처: dcy-006 FR-3, NFR-1, OQ-5, OQ-6.
- NFR-1(도메인 순수성) 은 REQ-017 AC-4 와 동일한 패턴으로 본 REQ AC-5 에 흡수한다(별도 REQ 미승격).
- HTTP 어댑터 제거와 `TREX_API_HOST/PORT` 폐기는 REQ-025 본문에서 책임진다(본 REQ 는 신규 transport 도입 범위).
- 실 RabbitMQ broker 인스턴스/자격증명 provision 은 delivery/운영 작업으로 위임됨(DATA, 비차단).
## Change Log
### CHG-20260605-01
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- dcy-006 FR-3 로부터 최초 생성(Proposed). NFR-1(도메인 순수성) 흡수.
#### Intent
- 메시징 transport 포트 + RabbitMQ 어댑터 + stub + sync+thread 데몬 재구성 backlog 정규화.
#### Acceptance Criteria Delta
- Added:
- stub publish/sub/RPC, RabbitMQ integration, 데몬 lifecycle, 재연결+degrade 연동, 도메인 import 스윕 (총 5개 기준)
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/project-structure.md, docs/runtime-flows.md
- Modules:
- application/messaging.py, adapters/messaging/{rabbitmq,in_memory}, runtime/daemon.py, runtime/config.py
- Tests:
- in-process stub, RabbitMQ integration(게이트), 데몬 lifecycle, import 스윕
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성
#### Validation Plan
- stub 기반 통합테스트 + RabbitMQ integration 테스트(미가용 시 skip) + import 스윕으로 검증.
### CHG-20260605-02
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과.
#### Intent
- messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon 재구성을 delivery input 으로 승인.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: REQ-021/023/025 와 함께 batch 편성
#### Validation Plan
- 기존 Acceptance Criteria 로 batch delivery 에서 검증.
### CHG-20260605-03
- Date: 2026-06-05
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반.
#### Intent
- MessagingTransport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon 재구성을 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음
#### Delivery Trace Delta
- Batches:
- BAT-007 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: release 단계는 별도 진행
#### Validation Plan
- AC-1: tests/adapters/messaging/test_in_memory_transport.py 5종.
- AC-2: tests/adapters/messaging/test_rabbitmq_transport.py 5종 (실 RabbitMQ 통합).
- AC-3: tests/runtime/test_daemon_no_http_surface.py::test_daemon_module_has_no_fastapi_or_uvicorn_import + Daemon 4 스레드 코드.
- AC-4: RabbitMQTransport 재연결 백오프 코드 + tests/adapters/messaging/test_in_memory_transport.py::test_unavailable_transport_raises_on_publish_and_rpc.
- AC-5: tests/domain/test_domain_import_sweep.py.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-016) 운영자 CLI 가 transport 의 request-reply RPC 클라이언트(`call_rpc`)를 재사용해 snapshot/intent 큐로 요청한다(전략 모듈과 동일 클라이언트, NFR-3). CLI 는 config 기반으로 transport 를 빌드하고 `call_rpc` 후 close 한다. BAT-020 재구현·재검증 완료 → Implemented 유지.
#### Intent
- CLI 와 전략 모듈이 동일 transport RPC 계약을 공유해 표면 분기 없는 일관 동작.
#### Acceptance Criteria Delta
- Added:
- CLI 가 `MessagingTransport.call_rpc(queue, payload, correlation_id, timeout)` 로 RPC 수행(주입 가능, 테스트는 fake rpc).
#### Impacted Area Delta
- Modules: `trex_core/runtime/cli.py`(transport 빌드 + call_rpc), 기존 `RabbitMQTransport.call_rpc` 재사용
- Tests: tests/runtime/test_cli_operator.py(주입 rpc)
#### Delivery Trace Delta
- Batches: BAT-020
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-020 에서 완료)
- Existing Implementation Invalidated: no (클라이언트 재사용)
- Status Recommendation: Implemented (change-req → BAT-020 재검증)
- Follow-up Action: confirm-req-implemented(BAT-020).
#### Validation Plan
- BAT-020: CLI RPC 호출 큐/payload/타임아웃 + 실패 표시 단위.
@@ -1,179 +0,0 @@
# req-027: portfolio-hub read 포트 + 어댑터 + stub
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 전략 모듈에 제공할 종목 스냅샷의 원천인 portfolio-hub universe(자신이 관리하는 portfolio 집합)를 trex-core 가 read 할 경로를 만든다. dcy-004 write 어댑터/자격증명을 재사용하고 도메인 순수성을 유지한다.
## Requirement
- `PortfolioHubReadPort.get_portfolios(daemon_id)` application 포트 + portfolio-hub read 어댑터(portfolio-hub `GET /v1/portfolios`, `list_portfolios_v1`) + 결정론 stub 를 제공한다. `/v1/portfolios` 는 토큰 스코프 조회라 `managed_by_daemon_id` 쿼리 파라미터가 없으므로, 어댑터는 응답 항목의 `managed_by_daemon_id == daemon_id` 로 **클라이언트측 필터**한다. (정정: CHG-04)
- 어댑터는 dcy-004 의 portfolio-hub 자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 재사용한다. base URL 에 스킴이 없으면 `http://` 를 기본 적용한다.
- `daemon_id` 는 portfolio-hub 자격증명/인증 토큰에서 도출한다(단일 진실원). 별도 `TREX_DAEMON_ID` 설정값을 1차 출처로 두지 않으며, `GET /v1/user_info` 응답의 `daemon_id` 로 1회 도출·캐시한다. (정정: CHG-04, `/v1/auth/whoami` 부재)
- 각 universe 항목은 symbol, asset_id(=portfolio_id), cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. 전략파라미터는 trex-core 가 해석하지 않고 통과시킨다. 단, 실 `/v1/portfolios`(`PortfolioItem`)는 `active_strategy_id`/`strategy_params` 를 제공하지 않아 두 필드는 None/`{}` 로 채워진다(실데이터 채움 이월 — CHG-04).
## Acceptance Criteria
1. Given daemon 토큰, When `get_portfolios` 를 호출, Then 토큰 스코프 응답을 `managed_by_daemon_id == daemon_id` 로 필터해 portfolio 집합을 항목당 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 형태로 반환하고 가격 필드는 없다. (실 list 응답에 전략 필드가 없으면 None/`{}` — CHG-04)
2. Given 결정론 stub 어댑터, When 호출, Then 네트워크 없이 고정된 universe 를 반환한다.
3. Given import 스윕, When `trex_core/domain` 을 검사, Then portfolio-hub/네트워크 의존이 0 이다(NFR-1).
4. Given `TREX_DAEMON_ID` 미설정, When daemon 기동·universe 조회, Then `GET /v1/user_info` 응답에서 도출한 daemon_id 로 동작한다(OQ-7, CHG-04).
## Impacted Area
- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/interface-contract.md
- Modules: trex_core/application (read 포트), trex_core/adapters/portfolio (read 어댑터 + stub), trex_core/runtime/config (daemon_id 토큰 도출)
- Tests: stub read 통합테스트, 도메인 import 스윕, daemon_id 토큰 도출
## Notes
- (정정, CHG-04) 라이브 portfolio-hub 실측 계약: `GET /v1/user_info` → `UserInfoResponse{daemon_id,…}`; `GET /v1/portfolios` → `PortfolioListResponse{portfolios:[PortfolioItem], total}` (토큰 스코프, `managed_by_daemon_id` 쿼리 없음 → 클라이언트측 필터). `PortfolioItem` 은 cash_balance/asset_quantity/managed_by_daemon_id 는 포함하나 `active_strategy_id`/`strategy_params` 는 미포함(detail 의 `strategy_set` 에만 존재). 구 가정(`/v1/auth/whoami`, `{items:[…]}`, `managed_by_daemon_id` 쿼리 스코프)은 폐기.
- 전략 필드 부재는 단일 전략(orb-v1, `strategy_key` 미사용) 단계에서 무해(REQ-029 CHG-04). 2번째 전략 도입 시 strategy_set 보강 또는 별도 change-req 로 해소.
- NFR-1(도메인 순수성)을 본 REQ 의 AC-3 로 흡수한다.
## Change Log
### CHG-20260606-01
- Date: 2026-06-06
- Author: project maintainers
#### Change Summary
- dcy-005 FR-4 + OQ-7 분해로 최초 생성(Proposed). NFR-1 흡수.
#### Intent
- portfolio-hub universe read 경로를 포트/어댑터/stub 로 정의.
#### Acceptance Criteria Delta
- Added:
- AC-1~4 신규
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs:
- dcy-005, interface-contract 참조
- Modules:
- application read 포트, adapters/portfolio
- Tests:
- stub read 통합테스트, import 스윕
#### Delivery Trace Delta
- Batches:
- none (미배치)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인 게이트 점검
#### Validation Plan
- stub read 통합테스트 + 도메인 import 스윕으로 AC 검증.
### CHG-20260606-02
- Date: 2026-06-06
- Author: project maintainers
#### Change Summary
- 승인 게이트 통과 → Proposed → Approved.
#### Acceptance Criteria Delta
- Added: none
- Updated: none
- Removed: none
#### Impacted Area Delta
- 변경 없음
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch
#### Validation Plan
- 승인 기록. 구현 전 재검증 불필요.
### CHG-20260606-03
- Date: 2026-06-06
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반.
#### Intent
- `PortfolioHubReadPort` + 어댑터(`PortfolioHubReadAdapter`, `StubPortfolioRead`) 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음
#### Delivery Trace Delta
- Batches:
- BAT-008 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: release 단계는 별도 진행
#### Validation Plan
- AC-1: tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_scoped_by_managed_by_daemon_id + test_stub_read.py.
- AC-2: tests/adapters/portfolio/test_stub_read.py 전체.
- AC-3: tests/domain/test_domain_import_sweep.py.
- AC-4: tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id.
### CHG-20260606-04
- Date: 2026-06-06
- Author: Justin Ko
#### Change Summary
- 실 portfolio-hub API 와의 계약 불일치를 정정: whoami 엔드포인트는 `/v1/auth/whoami`(부재) → **`GET /v1/user_info`**, list 응답 envelope 는 `{items:[…]}` → **`{portfolios:[…], total}`**, `/v1/portfolios` 는 토큰 스코프라 `managed_by_daemon_id` 쿼리 파라미터가 없어 **클라이언트측 필터**로 전환. 실 `PortfolioItem` 에 `active_strategy_id`/`strategy_params` 가 없어 None/`{}` 기본값으로 통과(전략 필드 실데이터 채움은 이월). 어댑터/테스트는 이미 정정 구현·재검증됨.
#### Intent
- 가정 기반(confirm-discovery 미검증)으로 작성됐던 read 계약을 라이브 API 실측에 맞춰 정정하고, 정정 구현의 재검증 evidence(신규 테스트 + 실 e2e)를 연결한다.
#### Acceptance Criteria Delta
- Added: none
- Updated:
- AC-1: list 조회는 토큰 스코프 + 클라이언트측 `managed_by_daemon_id` 필터로 동작. 반환 항목은 동일 필드 형태(가격 없음)를 유지하되, 실 `/v1/portfolios`(`PortfolioItem`)가 `active_strategy_id`/`strategy_params` 를 제공하지 않아 해당 두 필드는 None/`{}` 로 채워진다(실데이터 채움은 이월 — REQ-029 CHG-04, detail `strategy_set` 보강 또는 별도 change-req).
- AC-4: 토큰 daemon_id 도출은 `GET /v1/user_info` 응답의 `daemon_id` 로 한다(`/v1/auth/whoami` 아님).
- Removed: none
#### Impacted Area Delta
- Docs:
- 본 REQ Requirement/AC-1/AC-4/Notes 정정
- BAT-008 design.md/verification.md 계약 정정 반영(같은 변경 묶음)
- Modules:
- trex_core/adapters/portfolio/hub_read.py (정정 구현 완료, commit 43bad24)
- Tests:
- tests/adapters/portfolio/test_hub_read.py (실 계약으로 갱신, commit 43bad24)
#### Delivery Trace Delta
- Batches:
- BAT-008 (release-candidate) — 계약 정정 반영, verification 재확인 대상
- Releases:
- none (첫 릴리즈 묶음 대상)
#### Revalidation Impact
- Reverification Needed: yes — 완료. 정정 어댑터에 대해 신규 contract 테스트 통과 + 실 portfolio-hub e2e(whoami→get_portfolios→scheduler run_once `degraded=false`, 삼성전자 1건 수신) 확인.
- Existing Implementation Invalidated: no — 현재 코드가 실 계약과 일치(정정 후). 무효화된 것은 구 verification 산출물의 테스트명/엔드포인트 기술뿐이며 BAT-008 verification 갱신으로 동기화.
- Status Recommendation: Implemented 유지.
- Follow-up Action: 전략 필드 실데이터 채움(2번째 전략 도입 시) 재검토. portfolio-hub#9(user_info 500) 수정 완료로 daemon_id 도출 가능.
#### Validation Plan
- AC-1: tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_parses_envelope_and_filters_by_daemon + ::test_get_portfolios_includes_items_without_daemon_field + test_stub_read.py.
- AC-4: ::test_whoami_returns_token_daemon_id (`/v1/user_info` 단언) + ::test_base_url_without_scheme_defaults_to_http.
- 실 e2e: SnapshotScheduler.run_once() against live portfolio-hub → degraded=None, universe 1건.
@@ -1,173 +0,0 @@
# req-030: 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- universe 멤버십·전략파라미터는 일일 스냅샷으로 고정하고, 현금/포지션은 portfolio-hub 가 ledger write 응답으로 돌려주는 권위 값으로 유지해 로컬 누적 보정 drift 를 제거한다(옵션 D).
## Requirement
- 설정 가능한 시각(기본 07:30 KST, .env)에 스케줄러(신규 daemon 컴포넌트)가 `daemon_id` universe 를 페치해 스냅샷을 갱신한다. universe 멤버십·전략파라미터는 그 일일 스냅샷으로 고정한다.
- 현금/포지션은 스냅샷 baseline 에 더해, 체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값으로 해당 자산을 갱신한다.
## Acceptance Criteria
1. Given 설정 시각 도래, When 스케줄러 tick, Then universe 를 페치해 스냅샷(멤버십·전략파라미터·baseline 현금/포지션)을 갱신한다.
2. Given 체결 후 ledger write 응답이 `*_after` 를 반환, When 처리, Then 해당 자산의 현금/포지션을 그 권위 값으로 갱신한다.
3. Given `duplicate` write 응답(idempotent), When 처리, Then 현재 권위 `*_after` 값으로 동일하게 갱신한다.
## Impacted Area
- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/runtime-flows.md
- Modules: 일일 스냅샷 스케줄러(런타임 신규), ledger write 응답 소비/스냅샷 갱신 로직, config(스냅샷 페치 시각)
- Tests: 스케줄 tick 페치, `*_after` 갱신, duplicate idempotent 갱신
## Notes
- **의존: portfolio-hub#8 — ledger write 응답에 `cash_balance_after`/`asset_quantity_after` 반환(회계 단일 트랜잭션). portfolio-hub 배포(REL-003 등) 전까지 본 REQ 의 옵션 D 갱신 구현은 대기(dcy-005 A-7).** 그 전까지는 baseline + 스케줄러 페치까지만 선구현 가능.
- A-6(유일 매매 주체·장중 외부 변동 없음) 전제.
- NFR-2(일관성)를 본 REQ 로 흡수한다.
## Change Log
### CHG-20260606-01
- Date: 2026-06-06
- Author: project maintainers
#### Change Summary
- dcy-005 FR-6 + OQ-2 분해로 최초 생성(Proposed). NFR-2 흡수.
#### Intent
- 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신 정의.
#### Acceptance Criteria Delta
- Added:
- AC-1~3 신규
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs:
- dcy-005, runtime-flows
- Modules:
- 스냅샷 스케줄러, write 응답 소비 로직, config
- Tests:
- 스케줄 tick, *_after 갱신
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: portfolio-hub#8 배포 추적 후 옵션 D 구현 착수
#### Validation Plan
- 스케줄 tick 페치 + `*_after` 갱신/idempotent 갱신 테스트로 AC 검증.
### CHG-20260606-02
- Date: 2026-06-06
- Author: project maintainers
#### Change Summary
- 승인 게이트 통과 → Proposed → Approved. 옵션 D 구현은 portfolio-hub#8 배포 후 착수(Notes 의존 유지, 비블로커).
#### Acceptance Criteria Delta
- Added: none
- Updated: none
- Removed: none
#### Impacted Area Delta
- 변경 없음
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch; 옵션 D 구현은 portfolio-hub#8 배포 트래킹 후
#### Validation Plan
- 승인 기록. 스케줄러 baseline 구현 → portfolio-hub#8 배포 후 옵션 D 갱신 통합 검증.
### CHG-20260606-03
- Date: 2026-06-06
- Author: Justin Ko
#### Change Summary
- Status 유지: Approved. BAT-008 에서 AC-1(일일 스냅샷 스케줄러 baseline)은 구현 완료 + evidence 확보, AC-2/AC-3(옵션 D `*_after` 갱신)은 외부 의존(portfolio-hub#8) 배포 대기로 **deferred**. 본 REQ 는 AC 일부 충족이므로 `Implemented` 전환을 보류한다(SRS `Status Transition Evidence` 의 "미충족 Acceptance Criteria 가 있으면 Implemented 로 전환하지 않는다" 규정 준수).
#### Intent
- 스케줄러 baseline 부분 구현 완료를 추적 + 옵션 D 후속 작업의 wiring point 가 노출되어 있음을 기록.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (AC-1 baseline 은 BAT-008 에서 구현 + 검증 완료, AC-2/AC-3 wiring point 노출만 추가)
#### Delivery Trace Delta
- Batches:
- BAT-008 (release-candidate; AC-1 evidence)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: yes (portfolio-hub#8 배포 후 AC-2/AC-3 통합 검증)
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Approved 유지)
- Follow-up Action: portfolio-hub#8 배포 완료 알림 수신 시 별도 minor-change batch 로 옵션 D 구현 (LedgerWriter 의 ledger write 응답 소비 경로에서 `cash_balance_after`/`asset_quantity_after` → `SnapshotStore.update_cash_position(...)` 호출 1줄 추가) + AC-2/AC-3 통합 evidence 확보 → REQ-030 Approved → Implemented 전환.
#### Validation Plan
- AC-1 (현재 완료): tests/application/test_snapshot_scheduler.py::test_run_once_fetches_universe_and_swaps_store + ::test_swap_preserves_warning_for_carried_over_portfolio + 실 daemon boot 후 첫 fetch 동작 확인.
- AC-2 (deferred): portfolio-hub#8 배포 후 ledger write 응답 소비 경로에 wiring 연결 + integration 테스트.
- AC-3 (deferred): duplicate write 응답 idempotent 갱신 테스트.
#### Notes
- BAT-008 verification.md `Human Approval Memo` 에 deferred residual risk 로 명시되어 있고, batch 전체는 release-candidate 로 promote 됨.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-018) **Status Approved → Implemented**. 연기됐던 option D(AC-2/AC-3: ledger write 응답 `*_after` 로 현금/포지션 갱신)를 portfolio-hub#8 배포·동작 확인 후 실제 구현. `LedgerWriter` 가 `*_after` 를 `SnapshotStore.update_cash_position` 으로 write-back(REQ-019), 어댑터가 `*_after` 파싱(REQ-017). REQ-030 Change Log Follow-up(CHG-20260606-03 "portfolio-hub#8 배포 후 minor-change 로 option D 구현") 예고대로 닫음.
#### Intent
- option D 현금/포지션 갱신을 실제 활성화해 AC-2/AC-3 충족 → Approved→Implemented 전환.
#### Acceptance Criteria Delta
- (변경 없음 — AC-2/AC-3 의 deferred 해소; ledger write 응답 `*_after` 소비 경로 구현)
#### Impacted Area Delta
- Modules: `trex_core/application/ledger_outbox.py`(write-back), `trex_core/adapters/ledger/portfolio_hub.py`(*_after 파싱), `trex_core/runtime/daemon.py`(SnapshotStore 배선)
- Tests: tests/application/test_ledger_option_d.py
#### Delivery Trace Delta
- Batches: BAT-023
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-023 에서 AC-2/AC-3 통합 검증)
- Existing Implementation Invalidated: no (AC-1 baseline 불변, deferred AC 구현)
- Status Recommendation: **Implemented** (option D 활성화 완료)
- Follow-up Action: confirm-req-implemented(BAT-023) 로 Approved→Implemented 동기화.
#### Validation Plan
- BAT-023: ledger write `*_after` → SnapshotStore 갱신 단위 + 실 데몬 e2e(option D).
@@ -1,189 +0,0 @@
# req-037: forced_close_at 만기 자동 매도 루프
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
슬롯에 설정된 `forced_close_at` 만기 시각이 도달했을 때 데몬이 자율적으로 매도를 제출하는 백그라운드 루프를 추가한다. 워치독이 만료된 슬롯에 한해 동작하므로 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-1, NFR-1).
## Requirement
- 데몬은 `TREX_FORCED_CLOSE_CHECK_INTERVAL(초)` 주기로 `holding` 상태이며 **워치독이 만료된** 슬롯 중 `forced_close_at <= now(KST)`인 슬롯을 감지해야 한다.
- 조건에 해당하는 슬롯에 대해 `SlotService.sell()`(REQ-036)을 통해 내부 매도를 제출해야 한다.
- 이 루프는 daemon.py의 safety-sell worker 스레드에서 실행된다. 기존 6개 스레드에 safety-sell worker(`trex-safety-sell`)를 추가한다.
- `forced_close_at`이 `None`인 슬롯은 이 루프의 대상이 아니다.
- 워치독 활성(만료되지 않은) 슬롯은 이 루프의 대상이 아니다.
## Acceptance Criteria
1. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다.
2. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() + 1hour`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다.
3. Given `holding` 상태이고 워치독 **활성**이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다(워치독 보호).
4. Given `forced_close_at = None`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S는 만기 체크 대상에서 제외된다.
5. Given 대상 슬롯이 이미 `pending_sell`일 때, When checker 1회가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency 적용).
6. Given `TREX_FORCED_CLOSE_CHECK_INTERVAL=60`일 때, When daemon이 정상 실행 중이면, Then checker는 60±허용오차 초 주기로 반복 실행된다(NFR-1: 감지 지연 ≤ 60초).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — forced_close_at 만기 감지 루프 흐름 추가 (REQ-042 참조)
- `docs/project-structure.md` — `trex-safety-sell` 스레드 추가 반영 (REQ-042 참조)
- Modules:
- `trex_core/runtime/daemon.py` — `trex-safety-sell` worker 스레드 추가, `_safety_sell_loop()` 메서드 구현
- `trex_core/domain/slot/service.py` — forced_close_at 조건 평가 메서드(또는 checker 내 인라인)
- Tests:
- forced_close_at checker 단위 테스트 — 만기 슬롯 감지 + sell() 호출 확인
- 워치독 활성 슬롯 통과 확인
- idempotency 확인 (pending_sell 슬롯 중복 처리 없음)
## Notes
- safety-sell worker(`trex-safety-sell`)는 forced_close_at checker(REQ-037), stop-loss·take-profit checker(REQ-038), 장 마감 청산(REQ-039)을 하나의 통합 루프로 묶어 처리할 수 있다. 구체적인 스레드 분리/통합 방식은 batch planning에서 결정한다.
- 체크 로직은 DB 조회(forced_close_at 필드)와 in-memory 워치독 store 비교만 수행하므로 KIS API 호출이 없다. 낮은 연산 비용.
- `TREX_FORCED_CLOSE_CHECK_INTERVAL` env var는 REQ-041 참조.
- `SlotService.sell()` 구현은 REQ-036 참조.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-007 confirmed → REQ-037 초안 생성
#### Intent
- forced_close_at 만기 자동 매도 루프(FR-1)를 독립 Integration REQ로 정규화
#### Acceptance Criteria Delta
- Added: AC-1~6 전체
#### Impacted Area Delta
- Docs: runtime-flows.md, project-structure.md (REQ-042에서 처리)
- Modules: daemon.py (스레드 추가), service.py (forced_close 조건 평가)
- Tests: checker 단위 테스트 3종
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req)
#### Intent
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-03
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
#### Intent
- BAT-011 verification.md 의 AC↔evidence(SafetySellService forced_close checker — 만기/미만기/워치독 보호/None 제외/idempotency/캐던스)로 AC-1~6 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-011 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-017/issue #12) safety-sell 가시화: 워치독 소유 진입(전략 이탈)=WARN/이탈(복귀)=INFO(`_update_ownership`, run_once 매 tick), forced_close 발동=WARN(`_log_fire`) + in-process 카운터(`SafetySellService.counters`). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). best-effort(가시화 실패가 청산을 막지 않음). 트리거 판정 동작 불변. BAT-021 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 전략 이탈→안전망 소유, forced_close 발동을 무성에서 가시화(OQ-2 차등 매트릭스).
#### Acceptance Criteria Delta
- Added:
- 워치독 소유 진입/이탈 + forced_close 발동 로그/카운터. per-tick 스킵은 카운터만.
#### Impacted Area Delta
- Modules: `trex_core/application/safety_sell.py`(_update_ownership/_log_fire/counters/instance_key), `trex_core/runtime/daemon.py`(instance_key 배선)
- Tests: tests/application/test_safety_sell_visibility.py
#### Delivery Trace Delta
- Batches: BAT-021
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-021 에서 완료)
- Existing Implementation Invalidated: no (관측만 추가, 트리거 불변)
- Status Recommendation: Implemented (change-req → BAT-021 재검증)
- Follow-up Action: confirm-req-implemented(BAT-021).
#### Validation Plan
- BAT-021: 소유 진입 WARN/이탈 INFO/forced_close WARN + 카운터 단위(caplog).
@@ -1,276 +0,0 @@
# req-038: stop-loss·take-profit KIS 가격 polling + 트리거
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
워치독이 만료된 슬롯에 한해 KIS API(`TTTC8397R` 실전 / `VTTC8397R` 모의)로 현재가를 주기적으로 polling하고, `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 자동 매도를 제출한다. 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-2, NFR-3, A-3).
## Requirement
- 데몬은 `TREX_STOP_LOSS_POLL_INTERVAL(초)` 주기로, `stop_loss_price` 또는 `take_profit_price`가 설정되고 **워치독이 만료된** `holding` 슬롯들의 현재가를 KIS 주식현재가 시세 API(`/uapi/domestic-stock/v1/quotations/inquire-price`, tr_id `FHKST01010100`, 실전/모의 공용)로 polling해야 한다. (CHG-20260607-04: 시세 tr_id 정정 — 구 `TTTC8397R`/`VTTC8397R`는 실 KIS 에서 "없는 서비스 코드".)
- 현재가 평가 기준값은 `stck_prpr`(직전 체결가) 필드를 사용한다.
- `현재가 <= stop_loss_price` 조건 충족 시 손절 매도를 제출해야 한다.
- `현재가 >= take_profit_price` 조건 충족 시 익절 매도를 제출해야 한다.
- 두 조건은 동일 polling 루프에서 평가한다.
- 워치독 활성(만료되지 않은) 슬롯과 임계값 미설정(`None`) 슬롯은 polling 대상이 아니다.
- KIS API 호출 상한은 `워치독 만료 대상 슬롯 수 × (3600 / TREX_STOP_LOSS_POLL_INTERVAL)`로 산정한다. 이 상한이 KIS 쿼터를 초과할 경우 polling 간격 조정 AC를 이 REQ의 Notes에 명시하고 REQ 단계에서 상한 경고를 구현한다.
- 자동 매도 제출은 `SlotService.sell()`(REQ-036)을 사용한다.
## Acceptance Criteria
1. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=None`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=49000`이면, When 가격 체크가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다(손절 트리거).
2. Given `holding`, 워치독 만료, `stop_loss_price=None`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=81000`이면, When 가격 체크가 수행되면, Then S에 대해 `sell()`이 호출된다(익절 트리거).
3. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 `stck_prpr=65000`이면, When 가격 체크가 수행되면, Then `sell()`이 호출되지 않는다(두 조건 모두 미충족).
4. Given `holding`, 워치독 **활성**인 슬롯 S가 있을 때, When 가격 polling이 수행되면, Then S에 대한 KIS API 호출이 발생하지 않는다(워치독 보호).
5. Given `stop_loss_price=None`이고 `take_profit_price=None`인 슬롯 S가 있을 때, When polling이 수행되면, Then S는 polling 대상에서 제외된다.
6. Given 이미 `pending_sell` 상태인 슬롯 S에 조건이 충족될 때, When 가격 체크가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency).
7. Given `broker=kis` 일 때, When 가격 polling을 수행하면, Then KIS 주식현재가 시세 endpoint(`/uapi/domestic-stock/v1/quotations/inquire-price`)를 tr_id `FHKST01010100`(실전/모의 공용)로 호출하고 `output.stck_prpr` 를 파싱한다. 실 KIS LIVE 호출이 `rt_cd=0` + `stck_prpr` 를 반환함을 확인한다(구 `TTTC8397R` 는 `rt_cd=1` "없는 서비스 코드" 로 거부됨).
8. Given `TREX_STOP_LOSS_POLL_INTERVAL=30`이고 워치독 만료 대상 슬롯이 N개일 때, When 정상 동작 중이면, Then 1시간당 KIS API 호출 수가 `N × 120`회를 넘지 않는다(NFR-3 쿼터 산정).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — stop-loss·take-profit 가격 체크 흐름 추가 (REQ-042 참조)
- `docs/interface-contract.md` — 현재가 polling 어댑터 계약 명시 (REQ-042 참조)
- Modules:
- 신규 `trex_core/adapters/broker/kis_price.py` 또는 `kis.py` 확장 — `TTTC8397R`/`VTTC8397R` 현재가 polling, `stck_prpr` 파싱
- `trex_core/runtime/daemon.py` — stop-loss·take-profit 가격 체크를 safety-sell worker에 통합
- `trex_core/domain/slot/service.py` — stop-loss·take-profit 조건 평가 메서드(또는 worker 내 인라인)
- Tests:
- KIS 현재가 polling 어댑터 단위 테스트 — `stck_prpr` 파싱 확인, 실전/모의 분기 확인
- 가격 체크 단위 테스트(mock polling) — 손절/익절/미충족/워치독 보호 시나리오
## Notes
- KIS 현재가 polling 어댑터는 기존 `trex_core/adapters/broker/kis.py`에 `get_current_price(symbol)` 메서드로 추가하거나, 별도 모듈로 분리한다. batch planning에서 결정한다.
- paper broker 모의 환경에서는 stub 구현(`set_current_price`/`get_current_price`)으로 현재가를 반환한다(KIS 시세 호출 없음). 실 KIS(`broker=kis`) 는 `FHKST01010100` 시세 tr 사용.
- KIS 쿼터 초과 방지: 워치독 만료 슬롯 수가 임계값을 넘으면 polling 간격 자동 조정 또는 경고 로그를 구현한다. 구체적인 쿼터 수치는 KIS API 문서 기준으로 batch 설계에서 확정한다.
- `TREX_STOP_LOSS_POLL_INTERVAL` env var는 REQ-041 참조.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-007 confirmed → REQ-038 초안 생성
#### Intent
- stop-loss·take-profit KIS 가격 polling + 트리거(FR-2)를 독립 Integration REQ로 정규화
#### Acceptance Criteria Delta
- Added: AC-1~8 전체
#### Impacted Area Delta
- Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리)
- Modules: kis 현재가 polling 어댑터(신규/확장), daemon.py, service.py
- Tests: polling 어댑터 단위 + 가격 체크 시나리오 단위 (mock)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req)
#### Intent
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-03
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
#### Intent
- BAT-011 verification.md 의 AC↔evidence(KIS `get_current_price` TTTC8397R/VTTC8397R·`stck_prpr` 파싱 + 손절/익절/미충족/워치독 보호/쿼터 산정/가격 미수신 skip)로 AC-1~8 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-011 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
### CHG-20260607-04
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- 현재가 시세 tr_id 정정: `TTTC8397R`(실전)/`VTTC8397R`(모의) → `FHKST01010100`(주식현재가 시세, 실전/모의 공용). Status Implemented → Approved (AC-7 evidence 무효).
#### Intent
- 배포 전 실 KIS LIVE 진단(읽기 전용 시세 호출)에서 구 tr_id 가 `rt_cd=1` "없는 서비스 코드 입니다"(OPSQ0002) 로 거부됨을 확인. 이 경우 `get_current_price` 가 항상 None 을 반환 → stop-loss/take-profit 가 LIVE 에서 **조용히 미발동**. 표준 시세 tr `FHKST01010100` 는 `rt_cd=0` + `stck_prpr=329000`(005930) 정상 수신. mock/샌드박스로는 잡히지 않는 결함이라 LIVE 검증으로 포착.
#### Acceptance Criteria Delta
- Updated:
- AC-7: 시세 endpoint/tr_id 를 `FHKST01010100`(실전/모의 공용)로 정정. 실 KIS LIVE `rt_cd=0` + `stck_prpr` 수신 검증 추가.
- Added:
- 없음
- Removed:
- 없음 (TTTC8397R/VTTC8397R 분기 폐기)
#### Impacted Area Delta
- Docs:
- 없음 (REQ 본문 + 후속 baseline 은 verification 단계 갱신)
- Modules:
- `trex_core/adapters/broker/kis.py` (`_tr_price` → `FHKST01010100`, live/mock 분기 제거)
- Tests:
- `tests/adapters/broker/test_kis_price.py` (AC-7 tr_id 단언 정정)
#### Delivery Trace Delta
- Batches: BAT-011 (release-candidate — AC-7 재구현/재검증)
- Releases: REL-003 (confirmed — 본 정정 반영 후 재확인 필요)
#### Revalidation Impact
- Reverification Needed: yes (AC-7 한정; AC-1~6/8 은 paper/단위 evidence 유효)
- Existing Implementation Invalidated: yes (AC-7 — 구 tr_id 코드/테스트)
- Status Recommendation: Implemented → Approved (AC-7 재검증 전까지)
- Follow-up Action: kis.py 수정 + LIVE 시세 재검증 + 단위 정정 → confirm-req-implemented 로 Implemented 재전환 → REL-003 재확인.
#### Validation Plan
- 실 KIS LIVE `get_current_price('005930')` → Decimal(약 329000) 반환 확인(읽기 전용).
- `test_kis_price.py` AC-7: `_tr_price()` == `FHKST01010100` 단언 + `stck_prpr` 파싱.
- 단위 전체 회귀 + ruff.
### CHG-20260607-05
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, CHG-04 정정 재검증 완료).
#### Intent
- CHG-04 의 tr_id 정정(`FHKST01010100`)을 구현·재검증: `kis.py _tr_price()` 수정, `test_kis_price.py` AC-7 정정(7 passed), 단위 전체 236 passed/ruff clean, **실 KIS LIVE `get_current_price('005930')=Decimal('329000')`(rt_cd=0)** 확인. AC-7 evidence 복구 → 전체 AC 충족.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-011 (release-candidate)
- Releases: REL-003 (confirmed — 정정 반영 후 재확인)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: REL-003 재확인 후 배포.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-017/issue #12) SL/TP 발동 가시화: `stop_loss` 발동=WARN, `take_profit` 발동=INFO(`_log_fire`) + 카운터(`fire:stop_loss`/`fire:take_profit`). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). 판정·KIS polling 동작 불변(관측만). BAT-021 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 손절/익절 발동을 무성에서 가시화(OQ-2: stop_loss=WARN 주의사건, take_profit=INFO).
#### Acceptance Criteria Delta
- Added:
- stop_loss(WARN)/take_profit(INFO) 발동 로그 + 카운터.
#### Impacted Area Delta
- Modules: `trex_core/application/safety_sell.py`(check_prices 발동 가시화)
- Tests: tests/application/test_safety_sell_visibility.py::test_take_profit_fire_logs_info
#### Delivery Trace Delta
- Batches: BAT-021
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-021 에서 완료)
- Existing Implementation Invalidated: no (관측만 추가)
- Status Recommendation: Implemented (change-req → BAT-021 재검증)
- Follow-up Action: confirm-req-implemented(BAT-021).
#### Validation Plan
- BAT-021: take_profit 발동 INFO + 카운터 단위.
@@ -1,186 +0,0 @@
# req-039: 장 마감 강제 청산 (TREX_MARKET_CLOSE_LIQUIDATION_TIME)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
`TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST)이 설정된 경우, 해당 시각에 전략 모듈 활성 여부와 워치독 만료 여부에 관계없이 모든 `holding` 슬롯에 매도를 제출해 장 마감 전 자동 청산을 보장한다. 미설정 시에는 동작하지 않는다(dcy-007 FR-6).
## Requirement
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST 형식, optional)이 설정된 경우, 데몬은 해당 시각이 도래하면 워치독 만료 여부와 무관하게 모든 `holding` 슬롯에 `SlotService.sell()`(REQ-036)을 통해 매도를 제출해야 한다.
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않은 경우 이 기능은 동작하지 않는다.
- 장 마감 청산은 같은 거래일에 한 번만 발동해야 한다(중복 발동 방지).
- 청산 대상은 발동 시각의 `holding` 슬롯 전체이며 워치독 상태를 조건으로 사용하지 않는다.
## Acceptance Criteria
1. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 `holding` 슬롯이 N개 있을 때, When KST 15:10이 도래하면, Then N개 슬롯 모두에 `SlotService.sell()`이 호출된다.
2. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 워치독 **활성** 슬롯이 포함되어 있을 때, When KST 15:10이 도래하면, Then 워치독 활성 슬롯도 청산 대상에 포함된다(워치독 무관).
3. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않았을 때, When 어느 시각이든, Then 장 마감 청산 루프가 매도를 제출하지 않는다.
4. Given 장 마감 청산이 같은 거래일 내에 이미 발동된 적이 있을 때, When 같은 거래일 내 청산 로직이 다시 평가되면, Then 중복 발동이 발생하지 않는다.
5. Given 이미 `pending_sell` 상태인 슬롯 S가 청산 대상에 포함될 때, When 장 마감 청산이 발동되면, Then S에 대한 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency).
6. Given grace period(REQ-034) 중에 `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 도래할 때, When 장 마감 청산이 발동되면, Then grace period 여부와 무관하게 청산이 실행된다(FR-6: 워치독 무관).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — 장 마감 강제 청산 흐름 추가 (REQ-042 참조)
- Modules:
- `trex_core/runtime/daemon.py` — safety-sell worker 내 장 마감 청산 로직 추가
- `trex_core/runtime/config.py` — `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 파싱 (REQ-041 참조)
- Tests:
- 장 마감 청산 단위 테스트(시각 mock) — 설정/미설정 분기, 전체 holding 청산 확인
- 중복 발동 방지 테스트
- grace period 무관 동작 테스트
## Notes
- 장 마감 청산 발동은 safety-sell worker 루프(`trex-safety-sell`)에서 KST 시각을 주기적으로 체크하는 방식으로 구현한다. 발동 여부는 in-memory `last_liquidation_date`로 추적한다.
- 발동 시각 정밀도는 worker 루프 주기에 의존한다. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 또는 별도 설정으로 루프 주기를 결정한다. batch에서 확정한다.
- `TREX_MARKET_CLOSE_LIQUIDATION_TIME` env var는 REQ-041 참조.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-007 confirmed → REQ-039 초안 생성
#### Intent
- 장 마감 강제 청산(FR-6)을 독립 Integration REQ로 정규화
#### Acceptance Criteria Delta
- Added: AC-1~6 전체
#### Impacted Area Delta
- Docs: runtime-flows.md (REQ-042에서 처리)
- Modules: daemon.py (safety-sell worker 확장), config.py
- Tests: 단위 테스트 3종
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req)
#### Intent
- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs 후 batch 배정
### CHG-20260607-03
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate).
#### Intent
- BAT-011 verification.md 의 AC↔evidence(시각 도래 전체 holding 청산·워치독 무관·미설정 무동작·거래일 1회 가드·grace 무관·발동 전 무동작)로 AC-1~6 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-011 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: draft-release 로 BAT-011 배포 편성.
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-017/issue #12) market_close 청산 발동 가시화: `market_close_liquidation` 발동=INFO(`_log_fire`) + 카운터. 구조화 필드 포함. 청산 동작(워치독/grace 무관, 1일 1회) 불변. BAT-021 재구현·재검증 완료 → Implemented 유지.
#### Intent
- 장 마감 청산 발동을 가시화(OQ-2: market_close=INFO).
#### Acceptance Criteria Delta
- Added:
- market_close 발동 INFO 로그 + 카운터.
#### Impacted Area Delta
- Modules: `trex_core/application/safety_sell.py`(check_market_close 발동 가시화)
- Tests: tests/application/test_safety_sell_visibility.py(가시화 전반) + 기존 market_close 회귀
#### Delivery Trace Delta
- Batches: BAT-021
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (BAT-021 에서 완료)
- Existing Implementation Invalidated: no (관측만 추가)
- Status Recommendation: Implemented (change-req → BAT-021 재검증)
- Follow-up Action: confirm-req-implemented(BAT-021).
#### Validation Plan
- BAT-021: market_close 발동 시 INFO + 카운터(기존 청산 회귀 동반).
@@ -1,147 +0,0 @@
# req-050: trex.direct(topic) + trex.broadcast(fanout) exchange + 바인딩
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
인스턴스 지정 라우팅과 전역 브로드캐스트를 위해 두 exchange 를 도입한다. `trex.direct`(topic)는 strategy/운영자가 특정 인스턴스에만 메시지를 보낼 때 routing key `{instance_key}.{channel_type}` 으로 라우팅하고, `trex.broadcast`(fanout)는 global kill-switch 등 전체 인스턴스 대상 제어를 모든 control 큐에 동시 전달한다(dcy-009 FR-3, FR-4). routing key 계약을 인터페이스로 정의한다.
## Requirement
- `topology.py` 에 `DIRECT_EXCHANGE = "trex.direct"`(type topic, durable), `BROADCAST_EXCHANGE = "trex.broadcast"`(type fanout, durable) 상수와 routing key 빌더(`direct_routing_key(instance_key, channel_type)` → `{instance_key}.{channel_type}`)를 추가한다.
- `declare_topology(instance_key)` 가 두 exchange 를 선언하고, 각 인스턴스의 intent/control/snapshot 큐를 `trex.direct` 에 routing key `{instance_key}.intent`/`.control`/`.snapshot` 으로 바인딩한다(FR-3).
- 각 인스턴스의 **control 큐**를 `trex.broadcast`(fanout)에 추가 바인딩(dual binding)한다(FR-4). 전역 제어 메시지는 `trex.broadcast` 로 publish 하면 모든 인스턴스 control 큐에 전달된다.
- `default` 인스턴스 하위호환: default 의 큐(리터럴명, REQ-049)도 동일 규칙으로 `trex.direct`(`default.intent` 등) + control dual binding 된다. 기존 직접 큐 publish 경로(reply-to RPC)는 유지되어 회귀가 없어야 한다(FR-5).
- routing key 계약은 `docs/interface-contract.md` 에 명시한다(REQ-051): directed = `{instance_key}.{channel_type}`, broadcast = fanout(키 무시).
## Acceptance Criteria
1. Given `declare_topology("inst-1")`, When 선언, Then `trex.direct`(topic) + `trex.broadcast`(fanout) exchange 가 durable 로 선언된다.
2. Given `declare_topology("inst-1")`, When 바인딩 확인, Then `trex.intent.req.inst-1`/`...control...`/`...snapshot...` 가 `trex.direct` 에 routing key `inst-1.intent`/`inst-1.control`/`inst-1.snapshot` 으로 바인딩된다(FR-3).
3. Given `direct_routing_key("inst-1","control")`, When 호출, Then `"inst-1.control"` 을 반환한다.
4. Given `inst-1`/`inst-2` 두 인스턴스 큐가 선언됨, When `trex.direct` 에 `inst-1.intent` 로 publish, Then `trex.intent.req.inst-1` 에만 전달되고 `...inst-2` 에는 전달되지 않는다(S-2).
5. Given control 큐 dual binding, When `trex.broadcast` 에 publish, Then `trex.control.req.inst-1` 과 `...inst-2` control 큐 모두에 전달된다(FR-4, S-3).
6. Given `instance_key="default"`, When 전체 회귀, Then 기존 RPC(reply-to) 경로 동작 동일·회귀 없음(FR-5).
## Impacted Area
- Docs:
- `docs/interface-contract.md` — routing key 계약(`{instance_key}.{channel_type}`, broadcast) (REQ-051 처리)
- `docs/runtime-flows.md` — directed vs broadcast 라우팅 흐름 (REQ-051 처리)
- Modules:
- `trex_core/adapters/messaging/topology.py` — DIRECT/BROADCAST exchange + routing key 빌더
- `trex_core/adapters/messaging/rabbitmq.py` — exchange 선언 + 큐 바인딩(direct per-instance + control dual) + broadcast publish 경로
- Tests:
- `tests/adapters/messaging/` — exchange 선언 + 바인딩 + directed 라우팅 격리 + broadcast 전달(인프라 게이트)
## Notes
- R-3(broadcast 중복 처리): kill-switch/pause 핸들러는 idempotent(REQ-036 패턴/advisory lock)라 중복 전달 무해.
- strategy 모듈(orb-v1) publish 코드 변경은 Out of Scope — 본 REQ 는 exchange/binding/routing key 계약만 정의(CONFIRM_STRATEGY_MODULE_OUT_OF_SCOPE).
- broadcast publish helper 는 transport 에 추가하거나 기존 publish 경로 재사용 — 구현에서 결정.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-009(confirmed) → REQ-050 초안 생성.
#### Intent
- trex.direct/trex.broadcast exchange + 바인딩 + routing key 계약(FR-3/FR-4)을 Integration REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~6 전체
#### Impacted Area Delta
- Docs: interface-contract.md, runtime-flows.md (REQ-051)
- Modules: topology.py, rabbitmq.py
- Tests: tests/adapters/messaging (인프라 게이트)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-009(confirmed) FR-3/FR-4 와 일치. strategy publish 변경은 Out of Scope(계약만 정의) — blocker 아님.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성.
### CHG-20260607-03
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-013 release-candidate).
#### Intent
- BAT-013 verification 의 AC↔evidence(routing key 단위 + 인프라 게이트: trex.direct/trex.broadcast 선언 + directed 라우팅 격리 S-2 + broadcast dual S-3, 301 passed)로 AC-1~6 충족.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-013 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: draft-release 로 BAT-013 배포 편성. strategy publish 변경은 별도(Out of Scope).
@@ -1,147 +0,0 @@
# req-052: safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
`SafetySellService`가 브로커 영속 거부(휴장/잔고부족/규제) 상황에서 동일 슬롯에 매도를 무한 재제출하는 문제(2026-06-07 실 KIS LIVE SL/TP e2e 발견, REL-003 Feedback)를 막는다. 슬롯별 마지막 시도 시각·시도 횟수를 인메모리로 추적해 `min_retry_interval_sec` 이내 재시도를 건너뛰고, 반복 거부 시 운영자 경고를 발행한다. 추가 스레드·DB 스키마 변경 없음(dcy-010 FR-1~4, NFR-1/2/4).
## Requirement
- `SafetySellService`는 슬롯별 `last_attempt_at: dict[UUID, datetime]`, `attempt_count: dict[UUID, int]`를 인메모리로 추적한다.
- 각 트리거(`forced_close`/`stop_loss`/`take_profit`)가 `_safe_sell()`을 호출하기 전, 해당 슬롯이 `now - last_attempt_at < min_retry_interval_sec`이면 **건너뛴다**(재시도 안 함). market_close 는 `_last_liquidation_date`로 이미 하루 1회 보호되므로 쿨다운 대상에서 제외(dcy-010 A-3).
- 쿨다운 만료(`>= min_retry_interval_sec`) 후 트리거 조건이 여전히 충족되면 재시도하고, 성공 여부와 무관하게 `last_attempt_at`을 갱신하고 `attempt_count`를 증가시킨다.
- `attempt_count >= max_retry_warn_count`인 슬롯은 재시도 직전 `WARNING` 로그를 발행한다(포함: `slot_id`, `attempt_count`, `reason`, `last_attempt_at`).
- 슬롯이 `_holding()` 목록에서 사라지면(CLOSED/IDLE 등) 해당 슬롯의 `last_attempt_at`/`attempt_count` 항목을 제거한다(상태 누수 방지).
- `min_retry_interval_sec`/`max_retry_warn_count`는 `SafetySellConfig`(REQ-053)에서 주입받는다. 단일 `trex-safety-sell` 스레드 호출이라 별도 락 없음.
## Acceptance Criteria
1. Given paper 브로커 `reject_symbols`로 거부 시뮬레이션 + stop-loss 트리거 슬롯, When `run_once()`를 쿨다운 간격 이내에 N회 호출, Then `_safe_sell`(또는 `SlotService.sell`) 재호출이 첫 1회 이후 발생하지 않는다(S-1).
2. Given 슬롯 `last_attempt_at`을 `min_retry_interval_sec` 이전 과거로 설정 + 트리거 조건 유지, When `run_once()`/`check_*` 호출, Then 재시도 1회 실행 + `last_attempt_at` 갱신(S-2).
3. Given `attempt_count = max_retry_warn_count - 1` 상태에서 다음 재시도, When 재시도 직전, Then `WARNING` 로그가 발행되고 `slot_id`/`attempt_count`/`reason` 포함(S-3).
4. Given 슬롯이 CLOSED 로 전환되어 `_holding()`에서 사라짐, When 다음 `run_once()`, Then `last_attempt_at`/`attempt_count`에서 해당 슬롯 항목이 제거된다(S-4).
5. Given market_close 트리거, When 쿨다운 로직 평가, Then market_close 는 쿨다운 적용 대상이 아니다(하루 1회 가드 유지).
6. Given 가격 회복 등으로 트리거 조건이 해소된 슬롯, When `check_prices()`, Then `_safe_sell` 미호출 + `attempt_count` 미증가(R-1).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — safety-sell tick 에 쿨다운 체크 단계 (REQ-054 처리)
- Modules:
- `trex_core/application/safety_sell.py` — `__init__`(`_last_attempt`/`_attempt_count`), `check_forced_close`/`check_prices`(쿨다운 check/update), `run_once`(holding 이탈 슬롯 정리), 경고 로그
- Tests:
- `tests/application/test_safety_sell_worker.py`(또는 신규) — 쿨다운 skip/만료 재시도/경고 로그/정리/market_close 제외/트리거 해소(paper reject_symbols 시뮬레이션)
## Notes
- dcy-010 OQ-3 Answered: 단일 per-slot 쿨다운(트리거별 분리 안 함).
- ExecutionService→SafetySellService 거부 사유 피드백 채널은 Out of Scope — `_holding()` 재등장으로 암묵적 거부 감지(A-2).
- 쿨다운 상태는 재시작 시 초기화(허용, R-2) — DB 영속 Out of Scope.
- NFR-1(인메모리)/NFR-2(무락)/NFR-4(paper reject 시뮬 테스트)는 본 REQ AC 로 흡수.
## Change Log
### CHG-20260608-01
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- dcy-010(confirmed) → REQ-052 초안 생성.
#### Intent
- safety-sell 영속 거부 per-slot 쿨다운 + 경고(FR-1~4)를 Integration REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~6 전체
#### Impacted Area Delta
- Docs: runtime-flows.md (REQ-054)
- Modules: safety_sell.py
- Tests: test_safety_sell_worker.py(쿨다운)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260608-02
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC(1~6)·Impacted Area·Change Log 완비. dcy-010(confirmed) FR-1~4/OQ 와 모순 없음. ExecutionService 피드백 채널·DB 영속은 Out of Scope(비핵심).
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-batch 로 delivery 편성(batch-lite).
### CHG-20260608-03
- Date: 2026-06-08
- Author: Justin Ko
#### Change Summary
- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-014 release-candidate).
#### Intent
- BAT-014 verification 의 AC↔evidence(쿨다운 6 단위: skip/만료 재시도/경고/prune/market_close 제외/트리거 해소)로 AC-1~6 충족. 단위 278 passed, ruff clean.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: BAT-014 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: draft-release 로 BAT-014 배포 편성. 실 운영 거부 시나리오는 차기 거래일 관찰.
@@ -1,135 +0,0 @@
# req-058: universe 기반 슬롯 lifecycle reconcile (생성/soft-retire/tombstone)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 슬롯 집합을 portfolio-hub universe 의 projection 으로 유지하기 위해, universe fetch 결과로 슬롯을 생성/은퇴하는 reconcile 경로를 정의한다. 일일 preallocation(dcy-014)을 폐기하고 universe 동기화로 대체한다. (dcy-020 FR-1/OQ-2/NFR-3)
## Requirement
- 스냅샷 refresh(`get_portfolios` 성공 직후)에 슬롯 reconcile 을 수행한다: (1) universe 신규 자산 → 영구 슬롯 즉시 생성(비활성·멱등, `UNIQUE(instance_key, portfolio_id)`), 이 때 `PortfolioItem.symbol` 을 슬롯의 `symbol` 컬럼에 함께 저장한다; (2) universe 제거 + 슬롯 비활성(holding/pending 아님) → **soft-retire**(`retired_at` 마킹, 행 보존, hard delete 금지); (3) universe 제거 + 보유 중/pending → **tombstone**(은퇴 보류, safety-sell/watchdog 유지, 신규 매수 후보 제외, 닫힌 뒤 은퇴). reconcile 쓰기는 인스턴스 스코프 advisory lock 안에서 단일 writer 로 수행한다. cadence 는 기존 부팅 + 일 1회 + 운영자 on-demand. portfolio-hub 불가/degraded 또는 universe N→0/급격 대량 감소면 생성·은퇴를 보류하고 경고한다. `symbol IS NULL` 인 기존 슬롯은 다음 reconcile 시 backfill 한다.
## Acceptance Criteria
1. Given universe 에 신규 자산, When reconcile, Then 그 자산의 영구 슬롯이 비활성으로 생성되고 재실행해도 중복 생성되지 않는다(멱등).
2. Given universe 에서 빠진 비활성 슬롯, When reconcile, Then soft-retire(`retired_at` set, 행 보존)되고, 같은 자산이 재등장하면 동일 슬롯이 재활성화된다.
3. Given universe 에서 빠졌으나 holding/pending 인 슬롯, When reconcile, Then 은퇴되지 않고 tombstone(신규 매수 제외·safety-sell 유지)되며, 매도 체결 후에야 은퇴된다.
4. Given portfolio-hub degraded 또는 universe 가 N→0, When reconcile, Then 슬롯 생성·은퇴가 보류되고 마지막 집합이 유지되며 경고가 남는다.
5. Given reconcile 실행, When 슬롯 생성, Then `daily_slot.symbol` 이 `PortfolioItem.symbol` 값으로 채워진다. Given 기존 슬롯의 `symbol` 이 NULL, When reconcile, Then 해당 슬롯의 `symbol` 이 backfill 된다.
## Impacted Area
- Docs: docs/runtime-flows.md(reconcile lifecycle)
- Modules: trex_core/application/snapshot_scheduler.py, trex_core/domain/slot/repository.py, trex_core/adapters/persistence/postgresql.py, trex_core/application/preallocation_scheduler.py(폐기)
- Tests: tests/application/test_snapshot_scheduler.py(reconcile), tests/application/test_preallocation_scheduler.py(폐기/대체)
## Notes
- 기존 REQ-005/REQ-048(일일 preallocation·instance config) change-req 동반(preallocation 폐기·재해석). trading_date KST 계산(REQ-005)은 일일 회계 경계용으로 부분 존속.
- 출처: dcy-020 FR-1, OQ-2, NFR-3.
## Change Log
### CHG-20260610-01
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- dcy-020 FR-1/OQ-2/NFR-3 로부터 최초 생성(Proposed). universe reconcile(생성/soft-retire/tombstone) + degrade·대량소멸 보류 + 일일 preallocation 폐기.
#### Intent
- 슬롯 집합을 universe projection 으로 유지하는 lifecycle 을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added:
- 생성 멱등/비활성 soft-retire 재활성/tombstone/degrade·대량소멸 보류 4개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: docs/runtime-flows.md
- Modules: snapshot_scheduler.py, repository.py, postgresql.py, preallocation_scheduler.py(폐기)
- Tests: test_snapshot_scheduler.py, test_preallocation_scheduler.py
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req. REQ-005/048 change-req(preallocation 폐기) 동반.
#### Validation Plan
- reconcile 생성/은퇴/tombstone/degrade 단위·통합 테스트 green.
### CHG-20260610-02
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음.
#### Intent
- delivery input 으로 승격.
#### Acceptance Criteria Delta
- Added: 없음 / Updated: 없음 / Removed: 없음
#### Impacted Area Delta
- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음
#### Delivery Trace Delta
- Batches: none / Releases: none
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch.
#### Validation Plan
- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용.
### CHG-20260611-01
- Date: 2026-06-11
- Author: Justin Ko
#### Change Summary
- `reconcile_universe` 가 슬롯 생성 시 `symbol` 을 저장하지 않던 구현 누락 수정. `PortfolioItem.symbol` 이 슬롯 INSERT 에 포함되도록 시그니처를 `set[str]` → `dict[str, str]` (portfolio_id → symbol) 로 변경. 기존 `symbol IS NULL` 슬롯을 다음 reconcile 시 자동 backfill 하는 로직 추가.
#### Intent
- 슬롯 생성 시 symbol 이 누락되어 집행 루프에서 심볼을 조회할 수 없는 상태가 발생. `reconcile_universe` 인터페이스가 portfolio_id 집합만 전달하고 symbol 을 버리는 구조적 누락이었음.
#### Acceptance Criteria Delta
- Added:
- AC-5: 슬롯 생성 시 `symbol` 컬럼이 `PortfolioItem.symbol` 로 채워진다. 기존 `symbol IS NULL` 슬롯은 다음 reconcile 시 backfill 된다.
- Updated:
- Requirement 본문에 "(이 때 `PortfolioItem.symbol` 을 슬롯의 `symbol` 컬럼에 함께 저장한다)" 및 backfill 조건 추가.
- Removed: 없음
#### Impacted Area Delta
- Modules:
- `trex_core/application/snapshot_scheduler.py` — `_reconcile_slots`: `set` → `dict` comprehension
- `trex_core/domain/slot/repository.py` — `reconcile_universe` 시그니처
- `trex_core/domain/slot/in_memory_repository.py` — 생성 시 symbol 전달 + backfill
- `trex_core/adapters/persistence/postgresql.py` — INSERT에 symbol 추가 + backfill UPDATE
- Tests: 29개 테스트 파일 `reconcile_universe` 호출부 `set` → `dict` 일괄 갱신 (420 passed)
- Docs: 없음 (runtime-flows.md 는 reconcile lifecycle 자체 변경 없음)
#### Delivery Trace Delta
- Batches: BAT-028(구현 근거), BAT-030(e2e 검증 근거) — 기존 evidence 유효, 신규 AC-5 는 커밋 24a6017 으로 검증
- Releases: rel-006 배포 후 운영 중 발견(슬롯 symbol NULL 확인) — 핫픽스 아닌 즉시 수정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no (AC 1~4 는 기존 evidence 그대로 유효)
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: 없음. 420개 테스트 통과 + DB 확인(23개 슬롯 전부 symbol 채움)으로 AC-5 검증 완료.
#### Validation Plan
- `reconcile_universe` 호출 후 생성된 슬롯의 `symbol` 이 NULL 이 아닌지 단위 테스트 확인.
- 기존 `symbol IS NULL` 슬롯 보유 DB 에서 데몬 재시작 후 전 슬롯 symbol 채움 확인 (test 인스턴스 23/23 confirmed).
### CHG-20260610-IMPL
- Date: 2026-06-10
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-028(+BAT-030 default 단일 경로·e2e) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음.
@@ -1,133 +0,0 @@
# req-062: 수량 hub 정본+로컬 미러 + holding↔broker/hub 불일치 해소
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 보유 수량의 정본을 portfolio-hub 로 두고 슬롯은 미러만 유지하며, 코어 로컬 holding 과 브로커/hub 가 비정상적으로 어긋날 때의 해소 정책을 정의한다. (dcy-020 FR-4/OQ-5/NFR-5)
## Requirement
- 보유 **수량(asset_quantity)의 정본은 portfolio-hub** 이며, 슬롯은 수량을 정본 소유하지 않고 로컬 best-effort 미러(option D write-back/추정)로 둔다. 실제 청산 시 매도 수량은 **브로커 잔고(`get_balance().hldg_qty`)를 정본**으로 조회한다. 불일치 감지는 `reconcile_positions`(REQ-031)를 확장해 **브로커 잔고 + hub 수량 + 코어 슬롯 holding** 3자를 비교하며, 브로커를 ground truth 로 한다. 해소: A(코어 holding/브로커 0) → 슬롯 `error` + 운영자 resolve; B(코어 flat/브로커 보유, 최위험) → WARN/alert + 격리 + 운영자 결정(자동 청산·자동 편입 금지); C(수량만 불일치) → 미러 갱신 + warning, 상태 변경 없음; D(hub 만 지연) → 기존 write-back. **조용한 자동 교정 금지·표면화**, 실주문은 운영자 게이트(issue #15). 감지 reconcile 은 일일 refresh 와 분리해 더 자주 수행 가능(브로커 잔고+슬롯 상태만 필요).
## Acceptance Criteria
1. Given 매도 청산, When 매도 수량 결정, Then 로컬 미러가 stale 해도 브로커 잔고 기준으로 정확히 청산된다. **부분 매도(dcy-021)**: 집행 수량 = `min(intent qty, 브로커 hldg_qty)`; intent 가 잔고 이상이면 전량 매도(→inactive).
2. Given 코어 holding 인데 브로커 0, When reconcile, Then 슬롯이 `error` 로 격리되고 safety-sell 대상에서 빠지며 운영자 resolve 가 요구된다.
3. Given 코어 flat 인데 브로커 보유, When reconcile, Then WARN/alert + 격리되고 자동 청산·자동 편입 없이 운영자 결정으로 넘어간다.
4. Given 둘 다 보유, 수량만 불일치, When reconcile, Then 미러만 갱신되고 상태 전이는 없으며 hub 지속 불일치는 warning 으로 노출된다.
5. Given 사이징 주문(증액/부분 매도)이 일부만 체결(dcy-021), When 집행, Then 실제 체결분만 ledger 기록·슬롯 반영하고 미체결 잔량은 core 가 추적/재시도하지 않는다(재수렴은 전략).
## Impacted Area
- Docs: docs/runtime-flows.md(불일치 해소·write-back)
- Modules: trex_core/application/reconciliation.py, trex_core/application/ledger_outbox.py, trex_core/domain/slot/models.py(qty 미러 의미)
- Tests: tests/application/test_reconciliation.py, tests/application/test_ledger_option_d.py
## Notes
- 기존 REQ-017/019/029/030/031 와 연계(option D write-back 재사용). 슬롯 qty 정본 소유 제거는 REQ-002/006 change-req 와 정합.
- 출처: dcy-020 FR-4, OQ-5, NFR-5.
## Change Log
### CHG-20260610-01
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- dcy-020 FR-4/OQ-5/NFR-5 로부터 최초 생성(Proposed). 수량 hub 정본+미러·청산=브로커·불일치 해소(A/B/C/D, 표면화·운영자 게이트).
#### Intent
- 수량 정본 분리와 holding↔broker/hub 불일치 해소 정책을 backlog 정규화.
#### Acceptance Criteria Delta
- Added: 청산=브로커/A error/B 격리·운영자/C 미러갱신 4개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: docs/runtime-flows.md
- Modules: application/reconciliation.py, ledger_outbox.py, domain/slot/models.py
- Tests: test_reconciliation.py, test_ledger_option_d.py
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no (REQ-031/017/019 연계·확장)
- Status Recommendation: keep-current
- Follow-up Action: confirm-req. REQ-002/006(qty 미러) change-req 정합.
#### Validation Plan
- 청산 브로커 기준 + A/B/C 불일치 해소 단위·통합 테스트 green.
### CHG-20260610-02
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음.
#### Intent
- delivery input 으로 승격.
#### Acceptance Criteria Delta
- Added: 없음 / Updated: 없음 / Removed: 없음
#### Impacted Area Delta
- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음
#### Delivery Trace Delta
- Batches: none / Releases: none
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch.
#### Validation Plan
- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용.
### CHG-20260610-IMPL
- Date: 2026-06-10
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-029(M2a 불일치 reconcile)+BAT-030(M2b 매도 수량 브로커 정본) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음.
### CHG-20260610-03
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- dcy-021(포지션 사이징) 반영: 부분 매도 집행 수량 = **`min(intent qty, 브로커 hldg_qty)`**(과매도 방지, 잔고 이상이면 전량→inactive). 사이징 주문 부분 체결 시 **실제 체결분만** ledger/슬롯 반영, 미체결 잔량 미추적(전략 재수렴). 기존 "매도 수량=브로커 정본"의 부분 매도 확장.
#### Intent
- 보유 수량 부분 감액을 브로커 정본 하에 과매도 없이 집행하고, 부분 체결은 실제 거래만 원장에 남긴다(dcy-021 OQ-3, dcy-013 부분 체결 처리 정합).
#### Acceptance Criteria Delta
- Added: AC-5(사이징 부분 체결 → 실제 체결만 반영·미추적)
- Updated: AC-1 에 부분 매도 집행 수량 = min(intent, 브로커 잔고)·이상이면 전량
- Removed: 없음
#### Impacted Area Delta
- Docs: docs/runtime-flows.md, docs/interface-contract.md (REQ-067 정합)
- Modules: trex_core/application/execution.py(부분 매도 dispatch qty), reconciliation.py(불변)
- Tests: tests/application/test_execution*.py(부분 매도 min·부분 체결)
#### Delivery Trace Delta
- Batches: dcy-021 신규 배치(예정)
- Releases: none
#### Revalidation Impact
- Reverification Needed: yes (부분 매도 집행 수량 정책 신규)
- Existing Implementation Invalidated: no (전량 매도=브로커 정본 기존 동작 보존, 부분은 확장)
- Status Recommendation: revert-to-approved (부분 매도 집행 미구현 — dcy-021 배치에서 구현·검증)
- Follow-up Action: dcy-021 배치 구현 후 Implemented 복귀.
#### Validation Plan
- 부분 매도 min(intent,잔고)·잔고 이상 전량·부분 체결 실제만 ledger 단위 + 회귀.
### CHG-20260610-IMPL
- Date: 2026-06-10
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음.
@@ -1,95 +0,0 @@
# req-069: 주문 timeout 집행 — timeout 초과 시 cancel_order 송신·롤백·부분 보존
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 집행 루프가 dispatched-pending 슬롯의 `timeout_sec` 를 추적해 초과 시 `broker.cancel_order` 를 송신(best-effort)하고, 취소 확인 후 롤백하며 이미 체결된 부분 수량을 보존한다. 취소-체결 race 는 기존 poll 경로로 자연 수렴한다. (dcy-022 FR-2, FR-4)
## Requirement
- `_poll` 루프에서 dispatched-pending 슬롯이 `timeout_sec` 를 가지고 경과 시간이 초과하면 `broker.cancel_order(broker_order_id, ...)` 를 best-effort 로 송신한다(송신 실패는 WARNING 로그, 슬롯 상태 불변). 취소 요청 송신 직후 슬롯을 즉시 전이하지 않는다 — 다음 poll 틱에서 `is_terminal_reject`(전량 취소)·`is_partial_cancel`(부분 체결 후 취소) 경로로 수렴한다(기존 롤백/부분 보존 경로 재사용, REQ-013 CHG-03). `timeout_sec` 없는 슬롯은 기존 stale-alert 만(`_check_stale_pending`) 실행(Alert-only, REQ-014 회귀 없음).
## Acceptance Criteria
1. Given dispatched-pending 슬롯에 `timeout_sec=10` 설정, When 경과 시간 10초 초과, Then 집행 루프가 `broker.cancel_order` 를 송신하고 `timeout_cancelled` 카운터가 증가한다.
2. Given cancel_order 응답 True(브로커 수락), When 다음 poll, Then 슬롯은 `is_terminal_reject` 경로로 rollback 된다(pending_buy→inactive 또는 pending_sell→holding).
3. Given timeout 취소 전 일부 체결(is_partial_cancel), When 다음 poll, Then 기존 `_on_partial_cancel` 경로 — 체결분 원장 기록·잔량 holding 복귀(부분 보존, dcy-013 선례).
4. Given cancel_order 가 False 반환(이미 체결 — 취소-체결 race), When 다음 poll, Then `is_filled` 경로로 정상 체결 처리(임의 롤백 없음). (FR-4 race 안전)
5. Given `timeout_sec` 없는 dispatched-pending 슬롯, When 경과 시간 초과, Then cancel_order 미송신 — 기존 stale_pending WARN 만(Alert-only, REQ-014 회귀 없음).
6. Given cancel_order 송신 실패(예외), When 처리, Then WARNING 로그 + `timeout_cancelled` 미증가, 슬롯 상태 불변(비차단, NFR-2).
## Impacted Area
- Docs: docs/runtime-flows.md(timeout 취소 흐름 추가)
- Modules: trex_core/application/execution.py(`_poll` timeout cancel, ExecutionResult +timeout_cancelled)
- Tests: tests/application/test_execution.py(timeout→cancel), test_execution_cancel.py(timeout 부분 보존)
## Notes
- `updated_at` 를 dispatch 시각 proxy 로 사용(mark_dispatched 가 updated_at 갱신 — `_check_stale_pending` 과 동일 방법).
- best-effort 원칙: 송신 실패 시 다음 틱 재시도(threshold 넘으면 재송신). 중복 cancel_order 는 브로커가 멱등 처리(기존 KIS/paper 어댑터 계약).
- 출처: dcy-022 FR-2, FR-4, NFR-2.
## Change Log
### CHG-20260610-01
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- dcy-022 FR-2/FR-4 로부터 최초 생성(Proposed). timeout 취소·race 처리·부분 보존.
#### Intent
- dispatched-pending 주문 timeout 집행 + 취소-체결 race 안전 처리를 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~6 (timeout cancel, 전량/부분 취소 수렴, race 안전, 하위호환, 비차단 6개 기준)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Modules: execution.py(_poll), ExecutionResult
- Tests: test_execution, test_execution_cancel
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 BAT-032.
#### Validation Plan
- timeout → cancel_order 송신 + 롤백 수렴 단위. race(cancel→filled 수렴) 단위.
### CHG-20260610-02
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req).
#### Intent
- delivery input 으로 승격.
#### Acceptance Criteria Delta
- Added: 없음 / Updated: 없음 / Removed: 없음
#### Impacted Area Delta
- Docs: docs/srs/index.md (Register Approved)
#### Delivery Trace Delta
- Batches: none
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032.
#### Validation Plan
- 승인 후 batch planning 에서 AC 검증 기준으로 사용.
### CHG-20260611-IMPL
- Date: 2026-06-11
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음.
@@ -1,143 +0,0 @@
# req-070: slippage 보호 지정가 집행 — entry_price 기준 실효 지정가 산출
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- `slippage_pct` 가 지정된 주문을 dispatch 할 때 `entry_price*(1±pct)` 실효 지정가로 제출해 과도한 불리 체결을 막는다. `entry_price` 없으면 무시(순수 시장가는 슬리피지 기준 없음). (dcy-022 FR-3)
## Requirement
- `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 실효 지정가를 산출한 뒤 **KRX 호가단위(REQ-083)로 스냅**한다. 방향은 **상한 준수**: 매수 = `floor_to_tick(limit_price * (1 + slippage_pct/100))` (내림 — slippage 상한 미초과), 매도 = `ceil_to_tick(limit_price * (1 - slippage_pct/100))` (올림 — 매도 하한 미초과). band<1tick 엣지에서 미체결을 막기 위해 하한/상한을 보장한다: 매수 실효가 = `max(floor_to_tick(...), ceil_to_tick(limit_price))`, 매도 실효가 = `min(ceil_to_tick(...), floor_to_tick(limit_price))`. `limit_price`(= `entry_price`)가 None 이면 `slippage_pct` 를 무시하고 시장가 주문 제출(기존 동작 보존, 스냅 없음). `slippage_pct` 없는 슬롯은 기존 `limit_price` 직접 사용(회귀 없음). 주식 전용 — ETF/ETN 비주식 호가체계는 범위 밖(dcy-030 OQ-1).
## Acceptance Criteria
1. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=buy, When dispatch, Then OrderRequest.limit_price = `floor_to_tick(10000*1.01)` = `floor_to_tick(10100)` = 10100 (상한 준수 내림).
2. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=sell, When dispatch, Then OrderRequest.limit_price = `ceil_to_tick(10000*0.99)` = `ceil_to_tick(9900)` = 9900 (하한 준수 올림).
3. Given `limit_price=None`(시장가), `slippage_pct=1.0`, When dispatch, Then OrderRequest.limit_price = None(시장가 — slippage·스냅 무시).
4. Given `slippage_pct=None`, `limit_price=10000`, When dispatch, Then OrderRequest.limit_price = 10000(기존 동작, 회귀 없음).
5. Given `slippage_pct=0.0`, When dispatch, Then 실효 지정가 = `floor_to_tick(limit_price)`(0% 슬리피지 = 기준가의 유효 호가).
6. Given 기존 매수/매도 슬롯(slippage_pct 없음), When dispatch, Then 기존 동작과 동일(전체 회귀 없음).
7. Given `limit_price=147700`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(148438.5)` = 148400 (issue #28 케이스, 유효 호가·상한 미초과).
8. Given `limit_price=54500`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(54772.5)` = 54700 (issue #28 케이스).
9. Given band<1tick (예: `limit_price=147700`, `slippage_pct=0.01`, buy), When dispatch, Then 매수 실효가 = `max(floor_to_tick(147714.77), ceil_to_tick(147700))` ≥ entry 유효 호가 (미체결 방지), 그리고 slippage 상한 미초과.
10. Given 임의 매수/매도 + slippage, When dispatch, Then 결과는 유효 KRX 호가단위 배수이고 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)`.
## Impacted Area
- Docs: docs/runtime-flows.md(slippage dispatch + 호가단위 스냅 흐름), docs/project-structure.md(신규 tick 유틸 — REQ-083)
- Modules: trex_core/application/execution.py(`_dispatch` slippage 계산 + 호가단위 스냅), REQ-083 tick 유틸 소비
- Tests: tests/application/test_execution.py(slippage dispatch + 스냅 방향·경계·band<tick 하한 보장 단위)
## Notes
- `entry_price` = `limit_price` 로 슬롯에 저장되는 설계를 그대로 사용(REQ-013 CHG-20260608-01 선례). 슬리피지는 dispatch-time 변환만 — 슬롯의 `limit_price` 자체를 덮어쓰지 않는다.
- (CHG-20260616-01, dcy-030) KRX 정밀 호가단위 스냅을 REQ-083 유틸로 구현. 반올림 방향 = 상한 준수(매수 내림/매도 올림). 원래 메모의 "체결 보장(매수 상위/매도 하위)" 방향에서 전환 — `slippage_pct`를 리스크 상한으로 취급하기 때문(상한 1틱 초과 방지). band<1tick 하한/상한 보장으로 미체결 방지.
- 주식 전용. ETF/ETN 비주식 호가체계 및 2,000원 미만 비주식은 미지원(dcy-030 OQ-1, known limitation).
- 출처: dcy-022 FR-3, OQ-2 / dcy-030 FR-2·2b·3·4.
## Change Log
### CHG-20260610-01
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- dcy-022 FR-3 로부터 최초 생성(Proposed).
#### Intent
- slippage 보호 지정가 dispatch 집행을 backlog 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~6 (매수/매도 slippage 계산, 시장가 무시, 없을 때 회귀 6개 기준)
#### Impacted Area Delta
- Modules: execution.py(_dispatch)
- Tests: test_execution.py slippage 단위
#### Delivery Trace Delta
- Batches: none
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current
#### Validation Plan
- slippage 산출 단위 + 시장가/없을 때 회귀.
### CHG-20260610-02
- Date: 2026-06-10
- Author: Justin Ko
#### Change Summary
- Status Proposed → Approved (confirm-req).
#### Intent
- delivery input 으로 승격.
#### Acceptance Criteria Delta
- Added: 없음 / Updated: 없음 / Removed: 없음
#### Impacted Area Delta
- Docs: docs/srs/index.md (Register Approved)
#### Delivery Trace Delta
- Batches: none
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032.
#### Validation Plan
- 승인 후 batch planning 에서 AC 검증 기준으로 사용.
### CHG-20260611-IMPL
- Date: 2026-06-11
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음.
### CHG-20260616-01
- Date: 2026-06-16
- Author: Justin Ko
#### Change Summary
- slippage 실효 지정가에 **KRX 호가단위 스냅**을 추가하고, 반올림 방향을 원래 메모의 "체결 보장(매수=상위/매도=하위)"에서 **"상한 준수(매수=하위/매도=상위)"** 로 전환한다. band<1tick 엣지에 하한(매수)/상한(매도) 보장을 추가한다. (dcy-030 FR-2/FR-2b/FR-3/FR-4)
#### Intent
- 현재 구현은 `int(ceil/floor)`만 적용하고 KRX 호가단위로 스냅하지 않아, slippage_pct>0인 매수 한계가가 호가단위를 위반해 KIS `주식주문호가단위 오류`로 전량 거부됨(issue #28). 호가단위 스냅으로 거부를 제거하되, `slippage_pct`를 리스크 상한으로 취급해 스냅이 상한을 넘기지 않도록 매수=내림/매도=올림으로 정정한다.
#### Acceptance Criteria Delta
- Updated:
- AC-1: 매수 = `floor_to_tick(limit*(1+pct/100))` (상한 준수 내림). 예: `limit=10000,pct=1.0` → `floor_to_tick(10100)=10100`.
- AC-2: 매도 = `ceil_to_tick(limit*(1-pct/100))` (하한 준수 올림). 예: `limit=10000,pct=1.0` → `ceil_to_tick(9900)=9900`.
- Added:
- AC-7: `limit=147700,pct=0.5,buy` → `floor_to_tick(148438.5)=148400` (issue #28 케이스).
- AC-8: `limit=54500,pct=0.5,buy` → `floor_to_tick(54772.5)=54700` (issue #28 케이스).
- AC-9 (band<1tick 하한 보장): 매수 실효가 = `max(floor_to_tick(limit*(1+pct/100)), ceil_to_tick(limit))`; 매도 실효가 = `min(ceil_to_tick(limit*(1-pct/100)), floor_to_tick(limit))`. slippage<1tick이어도 최소 entry 유효 호가 보장.
- AC-10: 스냅 결과는 항상 유효 호가단위 배수이고, 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)` (상한·하한 미초과).
- Removed:
- (없음 — AC-3·4·5·6 시장가/무slippage/0% 회귀 기준 유지)
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md(주문 집행 슬리피지 dispatch에 호가단위 스냅 단계 추가), docs/project-structure.md(신규 tick 유틸 — REQ-083)
- Modules:
- trex_core/application/execution.py(`_dispatch` 스냅 적용), REQ-083 tick 유틸 소비
- Tests:
- tests/application/test_execution.py(슬리피지 스냅 방향·경계·band<tick 하한 보장), REQ-083 tick 단위 테스트
#### Delivery Trace Delta
- Batches: BAT-032(REQ-070 최초 구현) — 본 변경은 신규 batch(dcy-030)에서 재구현·재검증.
- Releases: REL-006(BAT-032 released) — 운영 영향(매수 거부 해소)은 dcy-030 batch 배포 시 신규 release로 추적.
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes (tick 스냅 부재 → 매수 거부; 반올림 방향도 변경)
- Status Recommendation: revert-to-approved
- Follow-up Action: dcy-030 batch에서 REQ-083 + REQ-070 재구현·검증 후 confirm-req-implemented로 재전환.
#### Validation Plan
- REQ-083 tick 유틸 단위 + execution.py 슬리피지 스냅 방향/경계/band<tick 하한 보장 단위 + 기존 AC-3~6 회귀.
### CHG-20260616-IMPL
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-044 release-candidate, dcy-030/issue #28). 근거: `execution.py` `_dispatch` 호가단위 스냅 구현(상한 준수 매수 floor/매도 ceil + band<tick 보장 + epsilon 가드, REQ-083 소비). AC-1·2·5·7·8·9·10 단위 매핑(issue #28 케이스 147700→148400/54500→54700, 35000@0.7%→35200, band<tick), AC-3·4·6 회귀. 47 passed(pricing+execution_policy), 전체 516 passed/47 skipped 회귀 0, ruff clean. Blocking Issues 없음.
@@ -1,61 +0,0 @@
# req-083: KRX 호가단위(tick) 산출 유틸 — 가격→tick 순수 함수 + 배수 내림/올림
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 가격대별 KRX 주식 호가단위(2023.1.25~)를 산출하는 순수 함수와 tick 배수 내림/올림 헬퍼를 제공해, slippage 보호 지정가 스냅(REQ-070)과 이후 호가단위가 필요한 지점이 단일 검증된 유틸을 재사용하게 한다. (dcy-030 FR-1)
## Requirement
- 가격(int/float, 원) → 해당 가격대의 KRX 주식 호가단위(tick, int)를 반환하는 순수 함수를 제공한다. 구간표(2023.1.25~):
- `< 2,000` → 1
- `2,000 ≤ p < 5,000` → 5
- `5,000 ≤ p < 20,000` → 10
- `20,000 ≤ p < 50,000` → 50
- `50,000 ≤ p < 200,000` → 100
- `200,000 ≤ p < 500,000` → 500
- `≥ 500,000` → 1,000
- 가격을 해당 tick의 **내림 배수**(`floor_to_tick`)와 **올림 배수**(`ceil_to_tick`)로 정규화하는 순수 헬퍼를 제공한다. 결과는 항상 tick의 정수배다.
- 구간 경계는 하한 포함/상한 미만(`[lo, hi)`)으로 처리한다(예: 정확히 50,000원은 100원 tick).
- 주식 전용이다. ETF/ETN 등 비주식 호가체계는 이번 범위 밖이며(dcy-030 OQ-1), 종목 유형 분기는 두지 않는다(known limitation: 2,000원 미만 비주식은 미지원).
## Acceptance Criteria
1. Given 가격 1,500, When tick 산출, Then 1.
2. Given 가격 3,000 / 12,000 / 35,000 / 147,700 / 300,000 / 800,000, When tick 산출, Then 각각 5 / 10 / 50 / 100 / 500 / 1,000.
3. Given 경계값 2,000 / 5,000 / 20,000 / 50,000 / 200,000 / 500,000, When tick 산출, Then 각각 5 / 10 / 50 / 100 / 500 / 1,000 (하한 포함).
4. Given 가격 148,439, When `floor_to_tick`, Then 148,400 (tick 100의 내림 배수).
5. Given 가격 54,773, When `floor_to_tick`, Then 54,700; When `ceil_to_tick`, Then 54,800.
6. Given 임의 가격 p, When `floor_to_tick(p)`/`ceil_to_tick(p)`, Then 결과는 tick(p)의 정수배이고 `floor ≤ p ≤ ceil`.
## Impacted Area
- Docs: docs/runtime-flows.md(주문 집행 슬리피지 스냅 단계 — REQ-070 change와 공동), docs/project-structure.md(신규 tick 유틸 모듈 책임)
- Modules: 신규 순수 모듈(후보: `trex_core/domain/pricing.py` 또는 `trex_core/application/tick_size.py` — design에서 확정), REQ-070 dispatch에서 소비
- Tests: 신규 단위 테스트(각 가격 구간 경계값 + floor/ceil 불변식)
## Notes
- 출처: dcy-030 FR-1, OQ-1(주식 전용).
- tick 표는 단일 상수 테이블로 모아 KRX 제도 변경 시 변경 지점을 1곳으로 한다(dcy-030 R-3).
- 모듈 배치는 순수 함수이므로 domain 계층 후보. 정확한 위치는 batch design에서 확정.
## Change Log
### CHG-20260616-01
- Date: 2026-06-16
- Author: Justin Ko
#### Change Summary
- dcy-030 FR-1로부터 최초 생성(Proposed).
### CHG-20260616-02
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC(경계값 6 + floor/ceil 불변식) 충족, source dcy-030(confirmed, Open OQ 0) 정합. REQ-070 change-req와 함께 dcy-030 단일 batch 편성 예정.
### CHG-20260616-03
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-044 release-candidate, dcy-030). 근거: `trex_core/domain/pricing.py` 구현 + `tests/domain/test_pricing.py` 전 AC 매핑(대표값/경계값/floor·ceil 불변식). 47 passed(pricing+execution), 전체 516 passed/47 skipped 회귀 0, ruff clean. Blocking Issues 없음.
@@ -1,56 +0,0 @@
# req-085: 주문 결과 로깅 — submit/체결/거부 일관 tailable 로그
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: project maintainers
## Intent
- 주문 결과(체결/거부)가 산발적 로그·DB(`slot_transition_log`)에만 남아 호가단위 거부(#28)를 수 시간 못 봤다. 모든 주문 submit과 그 결과(체결가·수량·수수료·세금, 또는 거부+사유)를 타임스탬프 달린 **일관(tailable) 로그 라인**으로 출력해 `grep`/`tail`로 당일 체결·거부를 즉시 전수 추출할 수 있게 한다. (dcy-029 FR-2)
## Requirement
- 집행 루프(`execution.py`)의 주문 dispatch/poll 경로에서 다음 이벤트를 일관된 안정적 키를 가진 한 줄 로그로 남긴다:
- **submit**: side, symbol, slot_id, qty, limit_price(또는 시장가), order_type.
- **fill**: side, symbol, slot_id, 체결가, 체결수량, fee, tax.
- **reject**: side, symbol, slot_id, 거부 사유.
- 로그 라인은 안정적 키(예: `event=order_submit|order_fill|order_reject`, `symbol=`, `slot_id=`)를 포함해 grep 필터가 가능해야 한다. JSON 포맷(REQ-084) 시 동일 필드가 구조화 키로 나간다.
- correlation id(REQ-084)가 설정돼 있으면 submit/fill/reject 로그가 같은 id를 공유한다.
- 기존 슬롯 전이/원장 기록 동작은 변경하지 않는다(로그 추가만, additive).
## Acceptance Criteria
1. Given 매수 주문 dispatch, When submit, Then `event=order_submit` + side/symbol/slot_id/qty/limit_price를 포함한 한 줄 로그가 남는다.
2. Given 체결, When poll fill, Then `event=order_fill` + 체결가/체결수량/fee/tax를 포함한 로그가 남는다.
3. Given 거부, When poll/ dispatch reject, Then `event=order_reject` + 사유를 포함한 로그가 남는다.
4. Given `TREX_LOG_FORMAT=json`, When 위 이벤트, Then 해당 키들이 JSON 구조화 필드로 출력된다.
5. Given 일련의 체결/거부, When `grep "event=order_reject"`, Then 당일 거부 주문을 전수 추출할 수 있다.
6. Given correlation id 설정 컨텍스트, When submit→fill, Then 두 로그가 동일 correlation id를 가진다.
## Impacted Area
- Docs: docs/runtime-flows.md(주문 집행 루프 — 결과 로깅)
- Modules: trex_core/application/execution.py(`_dispatch`/`_poll`/`_on_filled`/`_rollback` 로깅), REQ-084 로깅 기반 소비
- Tests: tests/application/test_execution* (submit/fill/reject 로그 caplog 단위)
## Notes
- FR-4(거부 ERROR 레벨)는 REQ-087에서 다룬다. 본 REQ는 submit/fill/reject의 **일관 구조·가시성**(레벨 무관)에 집중하되, reject 로그 라인은 REQ-087과 동일 지점에서 협조한다.
- 출처: dcy-029 FR-2.
## Change Log
### CHG-20260616-01
- Date: 2026-06-16
- Author: Justin Ko
#### Change Summary
- dcy-029 FR-2로부터 최초 생성(Proposed).
### CHG-20260616-02
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정.
### CHG-20260616-IMPL
- Date: 2026-06-16
- Author: Justin Ko
- Type: status
- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-045 release-candidate, dcy-029 P0). 근거: 단위 AC 매핑(logging_setup text/json/KST/cid/idempotent, execution 주문 결과 submit/fill/reject + cid=slot_id + 거부 ERROR). 35 passed(logging+execution_policy), 전체 529 passed/47 skipped 회귀 0, ruff clean. 실 Loki 적재는 release 운영 검증 위임. Blocking Issues 없음.
@@ -1,101 +0,0 @@
# req-099: trex.alerts AMQP exchange 토폴로지 + 가산·격리 불변식
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- 알람 전달 채널로 전용 `trex.alerts` topic exchange를 선언하고, 토폴로지(exchange 타입·이름·라우팅 규약)와 소비자/발행자 역할을 `docs/interface-contract.md`에 명시한다.
- 이 채널은 **가산·격리** 원칙을 따른다: 기존 exchange/큐(trex.events/trex.intent.req/trex.slots.req/trex.control.req 등) 스키마와 동작은 일절 변경하지 않으며, `trex.alerts`는 독립 추가 채널로만 존재한다.
- trex-core 데몬이 기동 시 `trex.alerts` exchange를 멱등 선언(declare)하도록 `MessagingTransport.declare_topology()`를 확장한다.
## Requirement
- trex-core `MessagingTransport.declare_topology()`는 아래 AMQP 엔티티를 멱등으로 선언해야 한다.
- exchange: `trex.alerts`, type=`topic`, durable=`True`
- 모듈(trex-core/orb/tsl/kdh)은 기존 RabbitMQ 연결에서 `trex.alerts` exchange로 알람 envelope를 발행한다. routing key는 `alarm.{severity}.{source}` (예: `alarm.critical.trex-core`).
- trex-notifier는 `trex.alerts` exchange의 단일 소비자로서, 바인딩 패턴 `alarm.#`(또는 severity별 `alarm.critical.#`)으로 자신의 큐를 바인딩해 수신한다(notifier 큐 선언·바인딩은 trex-notifier 소유).
- trex-core가 알람을 발행(publish)할 때: `mandatory=False`(소비자 없어도 unroutable 에러 없이 조용히 drop) — notifier 다운이 core 발행을 막지 않도록 한다.
- 기존 AMQP 토폴로지(trex.events topic exchange, trex.events.dlq, trex.intent.req/trex.slots.req/trex.control.req 큐, trex.direct/trex.broadcast exchange)는 수정하지 않는다.
- `docs/interface-contract.md`의 알람 채널 섹션(REQ-098)에 exchange 타입·이름·라우팅 규약·발행자/소비자 역할을 명시한다.
## Acceptance Criteria
1. Given trex-core 데몬이 기동될 때, When `declare_topology()`가 호출되면, Then `trex.alerts`(topic, durable=True) exchange가 멱등 선언되어야 한다(이미 존재하면 no-op).
2. Given `trex.alerts` exchange가 선언됐을 때, When 기존 exchange/큐 목록(trex.events, trex.events.dlq, trex.intent.req, trex.slots.req, trex.control.req, trex.direct, trex.broadcast)을 확인하면, Then 이들 엔티티의 type·durable·인수(arguments)가 변경되지 않아야 한다.
3. Given trex-core가 알람을 발행할 때, When notifier 큐가 바인딩되지 않아 unroutable인 경우, Then `mandatory=False`이므로 발행이 조용히 성공하고(silent drop) TransportUnavailableError 또는 UnroutableError가 발생하지 않아야 한다.
4. Given `docs/interface-contract.md` 알람 채널 섹션(REQ-098)이 추가됐을 때, When 토폴로지 명세를 확인하면, Then exchange 이름(`trex.alerts`), 타입(`topic`), durable 여부, routing key 형식(`alarm.{severity}.{source}`), 발행자(모든 모듈), 단일 소비자(trex-notifier)가 명시되어야 한다.
5. Given `declare_topology()`가 `trex.alerts` exchange를 포함하도록 수정됐을 때, When 기존 topology 선언 관련 단위 테스트를 실행하면, Then 기존 테스트가 모두 통과하고 신규 exchange 선언에 대한 단위 테스트가 추가되어야 한다.
6. Given trex-core가 알람을 발행할 때, When publishing 시 transport가 `disconnected` 또는 `reconnecting` 상태이면, Then 알람 발행은 silently skip되어야 하며 core 본업(슬롯 전이·매매 집행)을 블로킹하지 않아야 한다(best-effort 비차단 — REQ-100 연계).
## Impacted Area
- Docs: `docs/interface-contract.md` — 알람 채널 섹션 토폴로지 내용 추가(REQ-098 섹션 확장)
- Modules:
- `trex_core/adapters/broker/rabbitmq.py` 또는 topology 선언 담당 모듈 — `trex.alerts` exchange 선언 추가
- `trex_core/application/` messaging 관련 모듈 — 알람 발행 헬퍼(REQ-100 연계)
- Tests:
- `tests/adapters/` 또는 `tests/application/` — `declare_topology` 단위 테스트에 `trex.alerts` exchange 선언 assertion 추가
- 기존 topology 선언 테스트 회귀 확인
## Notes
- `trex.alerts` exchange는 `trex.events`와 별개로 선언된다. 둘은 다른 exchange 이름을 가지므로 바인딩·라우팅이 교차하지 않는다.
- notifier가 없는 환경(개발/테스트)에서도 core는 정상 동작해야 한다. `mandatory=False` + best-effort 발행이 이를 보장한다.
- RabbitMQ topology attribute 충돌(기존 큐·exchange와 durable/type 불일치) 시 PRECONDITION_FAILED — 이는 기존 엔티티 수정 금지 불변식을 위반한 경우로, 이 REQ의 AC-2로 사전 방지된다.
- orb/tsl/kdh는 자신의 RabbitMQ 연결에서 `trex.alerts` exchange로 직접 발행한다. core는 이들의 연결을 관여하지 않는다(각 모듈 REQ 범위).
- trex-notifier의 큐 이름·바인딩 패턴·prefetch 등은 trex-notifier repo REQ에서 정의한다.
- dcy-033 NFR-1(가산·격리) 대응 — AC-2가 직접 검증 기준.
- dcy-033 성공기준 S-3(기존 wire 계약 무회귀) 연계.
## Change Log
### CHG-20260621-02
- Date: 2026-06-21
- Author: trex-core-dev
#### Change Summary
- Status: Approved → Implemented. BAT-049 confirm-batch-verification 팀장 승인(QC PASS-WITH-NOTES blocker 0 + 팀장 실인프라 e2e 통과). 실 RabbitMQ `trex.alerts`(topic,durable) 선언·기존 topology 불변 확인.
#### Delivery Trace Delta
- Batches: BAT-049 (release-candidate)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
### CHG-20260621-01
- Date: 2026-06-21
- Author: trex-core-dev
#### Change Summary
- REQ-099 신규 생성(Proposed). dcy-033 FR-2 대응: `trex.alerts` AMQP exchange 토폴로지 + 가산·격리 불변식.
#### Intent
- `trex.alerts` 전용 exchange를 선언하고, 기존 AMQP 계약을 건드리지 않으면서 알람 채널을 가산적으로 추가한다.
#### Acceptance Criteria Delta
- Added: AC-1~6 (신규 생성)
- Updated: (없음)
- Removed: (없음)
#### Impacted Area Delta
- Docs:
- `docs/interface-contract.md` 알람 채널 섹션(REQ-098)에 토폴로지 내용 추가
- Modules:
- topology 선언 담당 모듈에 `trex.alerts` exchange 추가
- Tests:
- topology 선언 단위 테스트 신규 assertion 추가
#### Delivery Trace Delta
- Batches: (미정, M2 이후 큐잉)
- Releases: (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: REQ-098/100/101과 단일 batch 편성 예정
#### Validation Plan
- RabbitMQ mock/stub에서 `declare_topology()` 호출 후 `trex.alerts` exchange 선언 assertion
- 기존 exchange/큐 선언 테스트 회귀 통과 확인
- `mandatory=False` 발행 시 unroutable 상황에서 예외 없음 확인(단위 테스트)
@@ -1,122 +0,0 @@
# req-100: trex-core 알람 emitter — CRITICAL code 발신 배선
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- ALARM-INVENTORY.md §A trex-core 행의 CRITICAL alarm code 8종을 trex-core 소스 내 발생 지점에서 REQ-098 envelope 형식으로 `trex.alerts` exchange(REQ-099)에 발행하도록 배선한다.
- 알람 발행은 **best-effort 비차단**: 발행 실패(transport 단절·예외)가 core 본업(슬롯 전이·매매 집행·원장 기록)을 막지 않아야 한다.
- trex-core는 텔레그램 토큰 등 외부 시크릿을 보유하지 않는다. core는 `trex.alerts` exchange에 발행만 하고, 텔레그램 전달은 trex-notifier 책임이다.
## Requirement
- trex-core는 아래 8개 CRITICAL code의 발생 지점에서 REQ-098 envelope을 `trex.alerts` exchange로 발행해야 한다.
| code | 발생 위치 (ALARM-INVENTORY §A 기준) | 조건 |
|---|---|---|
| `fill_price_unresolvable` | `execution.py:261` | 체결가 미해결 → 슬롯 PENDING 고착 |
| `sell_skipped_no_balance` | `execution.py:128` | 매도 dispatch skip (브로커 잔고 0) → PENDING_SELL 고착 |
| `core_holding_broker_flat` | `reconciliation.py:47` | 슬롯 보유인데 브로커 잔고 0 (정산 불일치) |
| `ledger_delivery_failed` | `ledger_outbox.py:218-245` | 원장 영구 실패/데드레터/409 충돌 |
| `kill_switch_fired` | `killswitch.py:72` | 긴급 청산 발동 |
| `publish_unavailable_after_reconnect` / `dlq_publish_failed` | `rabbitmq.py:262,293` | 재연결 후에도 발행 불가 / DLQ 발행 실패 |
| `portfolio_hub_unavailable` | `portfolio.py:156` / `snapshot_scheduler.py:90,98` | 포트폴리오/현금 정보 부실화 |
| `schema_verification_failed` / `migration_failed` | `reset.py:152` | 리셋·마이그레이션 무결성 실패 |
- 알람 emitter는 application 레이어에 `AlarmEmitter` (또는 동등한 이름의 thin class/함수)로 구현하며, `MessagingTransport` 포트를 통해 발행한다(직접 pika 호출 금지 — 의존성 역전 유지).
- 발행 실패(TransportUnavailableError 또는 기타 예외) 시 ERROR 로그를 남기고 예외를 삼킨다(re-raise 금지). 슬롯 전이·주문 집행·원장 기록 경로는 알람 발행 실패에 영향받지 않는다.
- envelope `source` = `"trex-core"`, `severity` = `"critical"`, `schema_version` = `"1"`. `occurred_at`은 발행 시점 KST ISO-8601.
- `fields`는 code별 최소 컨텍스트를 포함한다(예: `fill_price_unresolvable`이면 `{"slot_id": "...", "symbol": "..."}`). 빈 dict도 허용.
- trex-core 코드 어디에도 텔레그램 봇 토큰, chat_id 등 외부 알람 시크릿을 import·로드·참조하지 않는다.
## Acceptance Criteria
1. Given trex-core가 기동돼 `MessagingTransport`가 연결된 상태에서, When 위 8개 code 각각의 조건이 발생하면, Then 해당 code를 담은 REQ-098 envelope(`source="trex-core"`, `severity="critical"`, `schema_version="1"`)이 routing key `alarm.critical.trex-core`로 `trex.alerts` exchange에 발행되어야 한다.
2. Given 알람 emitter가 `publish_alarm()`을 호출할 때, When `MessagingTransport`가 `disconnected` 또는 `reconnecting` 상태(TransportUnavailableError 발생)이면, Then 예외가 caller로 전파되지 않고 ERROR 로그만 남겨야 한다.
3. Given 알람 emitter가 `publish_alarm()`을 호출할 때, When 발행 중 임의 예외가 발생하면, Then 예외가 caller로 전파되지 않고 ERROR 로그만 남겨야 한다.
4. Given `kill_switch_fired`·`ledger_delivery_failed`·`fill_price_unresolvable` 등 알람 발행 지점이 슬롯 전이·매매 집행·원장 기록 경로 중간에 위치할 때, When 알람 발행이 예외로 실패해도, Then 해당 경로(슬롯 전이·집행·원장 기록)는 정상 완료되거나 기존 에러 처리 흐름을 따라야 한다(알람 발행 실패가 core 본업 실패를 유발하지 않음).
5. Given trex-core 코드베이스 전체를 탐색할 때, When 텔레그램 봇 토큰·chat_id·TELEGRAM_* 환경변수를 참조하는 코드를 찾으면, Then 해당 참조가 0건이어야 한다.
6. Given 알람 emitter 구현이 완료됐을 때, When `AlarmEmitter`(또는 동등 컴포넌트)의 단위 테스트를 실행하면, Then 8개 code 각각에 대해 (a) 정상 발행 시 올바른 envelope 필드 검증, (b) transport 예외 발생 시 re-raise 없음 + 로그 기록 두 케이스가 모두 통과해야 한다.
7. Given `AlarmEmitter`가 `MessagingTransport` 포트를 통해 구현됐을 때, When application 레이어 코드를 확인하면, Then pika 라이브러리를 직접 import하는 코드가 없어야 한다(의존성 역전 준수).
8. Given domain 레이어(`trex_core/domain/`) 코드를 확인할 때, When `AlarmEmitter` 또는 messaging import를 탐색하면, Then domain 레이어에 messaging/transport/infra 관련 import가 없어야 한다(domain 순수성 불변식).
## Impacted Area
- Docs: `docs/interface-contract.md` — 알람 채널 섹션에 core emitter source/routing key 명시(REQ-098 섹션 내용)
- Modules:
- `trex_core/application/` — `AlarmEmitter` (또는 동등 thin class/함수) 신규 추가
- `trex_core/application/execution.py` — `fill_price_unresolvable`, `sell_skipped_no_balance` 발행 배선
- `trex_core/application/reconciliation.py` — `core_holding_broker_flat` 발행 배선
- `trex_core/application/ledger_outbox.py` — `ledger_delivery_failed` 발행 배선
- `trex_core/application/killswitch.py` — `kill_switch_fired` 발행 배선
- `trex_core/adapters/broker/rabbitmq.py` — `publish_unavailable_after_reconnect` / `dlq_publish_failed` 발행 배선
- `trex_core/adapters/portfolio/` 또는 `trex_core/application/snapshot_scheduler.py` — `portfolio_hub_unavailable` 발행 배선
- `trex_core/runtime/reset.py` — `schema_verification_failed` / `migration_failed` 발행 배선
- `trex_core/runtime/daemon.py` — `AlarmEmitter` 인스턴스 생성·주입
- Tests:
- `tests/application/` — `AlarmEmitter` 단위 테스트 (8 code × 2 케이스 최소 16 테스트)
- 기존 execution/reconciliation/killswitch/ledger_outbox 테스트 회귀 확인
## Notes
- `AlarmEmitter`는 application 레이어에 두고 `MessagingTransport` 포트만 주입받는다. 테스트에서는 `InMemoryTransport` 또는 mock transport를 주입해 결정적 단위 테스트를 작성한다.
- `publish_unavailable_after_reconnect` / `dlq_publish_failed`는 rabbitmq.py 어댑터에서 발생하지만, 알람 emitter를 어댑터 내부에 두면 의존성이 역전되므로 처리 방식을 batch design에서 결정한다(예: 어댑터가 콜백을 호출하거나, 이벤트를 상위 레이어로 전달하는 방식).
- `portfolio_hub_unavailable`는 `portfolio.py:156`과 `snapshot_scheduler.py:90,98` 두 곳에서 발생 가능 — 중복 발행 억제는 notifier의 rate-limit 책임이므로 core는 각 발생 지점에서 독립 발행한다.
- `reset.py`는 daemon이 아닌 CLI 진입점(`trex-core-reset`)에서 실행된다. `MessagingTransport` 미연결 가능성 있음 — transport가 None이면 발행 skip + 로그(batch design에서 확정).
- dcy-033 성공기준 S-1(CRITICAL 조건 발생 시 텔레그램 도달) 전제 조건: core emitter가 올바른 envelope을 `trex.alerts`에 발행.
- dcy-033 NFR-3(core에 외부 시크릿 유입 없음) 대응 — AC-5가 직접 검증 기준.
- INFO·WARN code 배선은 이 REQ 범위 밖. CRITICAL 우선(dcy-033 범위 경계).
## Change Log
### CHG-20260621-02
- Date: 2026-06-21
- Author: trex-core-dev
#### Change Summary
- Status: Approved → Implemented. BAT-049 confirm-batch-verification 팀장 승인(QC PASS-WITH-NOTES blocker 0 + 팀장 실인프라 e2e 통과). AlarmEmitter 29 단위 테스트 통과(8 code × 정상+예외 케이스). 텔레그램 sendMessage HTTP 200 e2e 확인. 잔여: reset.py 2 code 간접 검증(non-blocker, 정직 공개).
#### Delivery Trace Delta
- Batches: BAT-049 (release-candidate)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
### CHG-20260621-01
- Date: 2026-06-21
- Author: trex-core-dev
#### Change Summary
- REQ-100 신규 생성(Proposed). dcy-033 FR-2(core 부분) 대응: ALARM-INVENTORY §A core CRITICAL 8종 emitter 배선.
#### Intent
- trex-core가 소유한 CRITICAL alarm code 발생 지점에서 표준 envelope을 `trex.alerts` exchange로 발행하는 알람 emitter를 구현한다.
#### Acceptance Criteria Delta
- Added: AC-1~8 (신규 생성)
- Updated: (없음)
- Removed: (없음)
#### Impacted Area Delta
- Docs:
- `docs/interface-contract.md` 알람 섹션 core emitter 내용 추가
- Modules:
- application 레이어 AlarmEmitter 신규 + 8개 발생 지점 배선
- Tests:
- AlarmEmitter 단위 테스트 신규 추가
#### Delivery Trace Delta
- Batches: (미정, M2 이후 큐잉)
- Releases: (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: REQ-098/099/101과 단일 batch 편성 예정
#### Validation Plan
- AlarmEmitter 단위 테스트 8 code × 2 케이스 통과
- 기존 execution/reconciliation/killswitch 테스트 회귀 통과
- domain 레이어 messaging import 0건 확인(grep)
- TELEGRAM_* 환경변수 참조 0건 확인(grep)
@@ -1,133 +0,0 @@
# req-103: 차단형 reconcile — B/C 불일치 슬롯 reconcile-warning 전이
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- boot/일일 reconcile(`reconcile_core_holdings`, `reconcile_positions`) 수행 중 B(슬롯↔브로커 수량 불일치) 또는 C(원장↔슬롯 수량 불일치)가 탐지되면, 해당 슬롯을 **WARN 로그만 남기던 기존 동작에서 reconcile-warning durable 차단으로 승격**한다.
- dcy-034 FR-1 대응: 불일치 슬롯이 탐지 즉시 차단되어 정합 전 잘못된 매매가 나가지 않도록 한다.
- 단기 인스턴스 정상(일치) 시나리오에서 차단이 오발동하지 않아야 한다(NFR-2/S-4).
## Requirement
- 시스템은 `reconcile_core_holdings` 실행 중 케이스 B(슬롯 보유, 브로커 수량 불일치 — qty 차이)를 탐지하면 해당 slot_id를 `ReconcileWarningStore.set_warned(slot_id, portfolio_id, case_code='B', actor='reconcile', reason=...)` 로 durable 차단해야 한다.
- 시스템은 `reconcile_positions` 실행 중 케이스 C(원장 portfolio-hub 수량 ≠ 브로커 수량)를 탐지하면 관련 슬롯(portfolio_id로 조회)을 `ReconcileWarningStore.set_warned(slot_id, portfolio_id, case_code='C', actor='reconcile', reason=...)` 로 durable 차단해야 한다.
- 케이스 A(슬롯 보유, 브로커 0)는 기존 `SlotState.ERROR` 전이를 유지하고 reconcile-warning 스토어를 사용하지 않는다(기존 동작 보존).
- `snapshot_scheduler.run_once()` 내 reconcile 순서(reconcile_positions → _reconcile_slots → reconcile_core_holdings)에서 두 함수 모두 ReconcileWarningStore를 주입받아 차단을 기록해야 한다.
- 불일치가 실제로 있을 때만 차단이 발생하며, 수량이 일치하는 슬롯에는 차단이 적용되지 않아야 한다.
## Acceptance Criteria
1. Given 슬롯 보유 수량과 브로커 잔고 수량이 다른 케이스 B 입력에서, When `reconcile_core_holdings`가 실행되면, Then 해당 slot_id가 ReconcileWarningStore에 `case_code='B'`로 기록된다.
2. Given portfolio-hub 원장 수량과 브로커 수량이 다른 케이스 C 입력에서, When `reconcile_positions`가 실행되면, Then 관련 슬롯의 slot_id가 ReconcileWarningStore에 `case_code='C'`로 기록된다.
3. Given 슬롯 수량과 브로커 수량이 일치하는 정상 케이스에서, When reconcile가 실행되면, Then ReconcileWarningStore에 해당 slot_id 행이 생기지 않는다(오발동 없음, S-4).
4. Given 케이스 A(슬롯 보유, 브로커 0)에서, When `reconcile_core_holdings`가 실행되면, Then 기존처럼 SlotState.ERROR 전이가 발생하고 ReconcileWarningStore는 사용하지 않는다.
5. Given `ReconcileWarningStore`가 주입되지 않은 구성에서, When reconcile가 실행되면, Then 기존 WARN 로그 동작이 유지되고 예외가 발생하지 않는다(하위 호환 / 테스트 더블 지원).
6. Given 단기 인스턴스(M1/M2 라이브) 정상 시나리오에서, When boot/일일 reconcile가 실행되면, Then 신규 reconcile-warning이 발생하지 않는다(회귀 0, NFR-2, S-4).
## Impacted Area
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md` (reconcile 흐름 갱신 필요 여부 batch design에서 확인)
- Modules:
- `trex_core/application/reconciliation.py` (`reconcile_core_holdings`, `reconcile_positions` — ReconcileWarningStore 주입 파라미터 추가)
- `trex_core/application/snapshot_scheduler.py` (`SnapshotScheduler.__init__`, `run_once` — ReconcileWarningStore 주입 및 전달)
- Tests:
- `tests/application/test_reconciliation.py` (신규 B/C 차단 케이스, 정상 케이스 오발동 없음, A 케이스 기존 동작 유지, store 미주입 하위 호환)
- `tests/application/test_snapshot_scheduler.py` (run_once 내 차단 흐름 통합 검증)
- 기존 reconcile 회귀 테스트
## Notes
- `reconcile_positions`는 현재 portfolio_id 단위 경고(`store.set_warning`)를 in-memory로 관리한다. C 차단은 portfolio_id에 대응하는 slot을 조회해야 하므로, `reconcile_positions`에 `slot_repo` 주입이 필요할 수 있다. 구체 구현은 batch design에서 결정한다.
- ReconcileWarningStore 파라미터는 Optional로 추가해 기존 호출부(테스트 포함)의 하위 호환을 유지한다.
- 불일치 수량 before/after 정보는 REQ-107(로깅) 범위에서 다룬다.
- dcy-034 FR-1, NFR-2, NFR-3, S-4 대응.
## Change Log
### CHG-20260626-01
- Date: 2026-06-26
- Author: trex-core-dev
- Status: Approved → Implemented
- Summary: BAT-050(PR #31, ced7eda) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. reconcile_core_holdings B케이스·reconcile_positions C케이스 차단, 정상 케이스 오발동 없음, A케이스 기존 동작 유지, 전 AC 검증 완료.
#### Acceptance Criteria Delta
- 없음
#### Impacted Area Delta
- 없음
#### Delivery Trace Delta
- Batches: BAT-050 (core-A)
- Releases: M3 (배포 대기)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: 없음.
#### Validation Plan
- BAT-050 verification.md AC 매핑 전항목 PASS 확인됨.
### CHG-20260625-02
- Date: 2026-06-25
- Author: trex-core-dev
#### Change Summary
- REQ-103 **Proposed → Approved** (confirm-req, 팀장 2026-06-25).
#### Intent
- 팀장 confirm-req로 상태 승인. 내용 변경 없음(초안 그대로 승인).
#### Acceptance Criteria Delta
- Added: 없음.
- Updated: 없음.
- Removed: 없음.
#### Impacted Area Delta
- 없음.
#### Delivery Trace Delta
- Batches: 미정 (suggest-batch-reqs 이후)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: batch 편성 후 구현.
#### Validation Plan
- 신규 B/C 차단 테스트 pass / 정상 케이스 오발동 없음 테스트 pass / 기존 reconcile 회귀 pass.
### CHG-20260625-01
- Date: 2026-06-25
- Author: trex-core-dev
#### Change Summary
- REQ-103 신규 생성 (Proposed) — dcy-034 FR-1 대응, draft-req 단계.
#### Intent
- 기존 WARN 로그 전용이던 B/C 불일치를 durable 차단으로 승격해 cold-start 시 정합 전 매매를 구조적으로 차단.
#### Acceptance Criteria Delta
- Added: AC-1~6 전부 신규.
- Updated: 없음.
- Removed: 없음.
#### Impacted Area Delta
- Docs: runtime-flows.md 갱신 예정.
- Modules: reconciliation.py, snapshot_scheduler.py.
- Tests: 신규 B/C 케이스 테스트.
#### Delivery Trace Delta
- Batches: 미정
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no (신규 REQ)
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 편성.
#### Validation Plan
- 신규 B/C 차단 테스트 pass / 정상 케이스 오발동 없음 테스트 pass / 기존 reconcile 회귀 pass.
@@ -1,116 +0,0 @@
# req-109: portfolio-hub /pnl/realized read 포트 메서드 + HTTP 어댑터
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req)
## Intent
- 현재 `PortfolioHubReadAdapter`(`hub_read.py`)에는 portfolio-hub WAC 실현 PnL 조회(`GET /v1/portfolios/{id}/pnl/realized`) 메서드가 없다. 운영 리포트가 WAC 기반 실현손익을 정확하게 표시하려면 이 엔드포인트를 소비하는 포트 메서드와 HTTP 어댑터가 필요하다. (dcy-035 FR-1)
## Requirement
- `PortfolioHubReadPort`(프로토콜, `trex_core/application/portfolio.py`)에 `get_realized_pnl(portfolio_id, from_dt, to_dt, *, symbol)` 메서드를 추가한다. 반환 타입은 `RealizedPnlResult` 데이터클래스로 정의한다.
- `RealizedPnlResult`는 다음 필드를 포함한다: `items: list[RealizedPnlItem]`, `cost_basis_from: str | None`, `data_completeness_warning: bool`, `total_realized_pnl: float`. `RealizedPnlItem`은 최소 `symbol`, `realized_pnl`, `sell_qty`를 포함한다.
- `PortfolioHubReadAdapter`(`hub_read.py`)에 위 메서드를 구현한다: `GET /v1/portfolios/{portfolio_id}/pnl/realized` 를 `from_dt`(ISO8601 date), `to_dt`(ISO8601 date), `symbol`(optional)을 query param으로 전달해 호출하고, 응답을 `RealizedPnlResult`로 파싱·반환한다. 네트워크/HTTP 오류 시 `PortfolioHubUnavailableError`를 raise 한다.
- `StubPortfolioRead`(`stub_read.py`)에도 동일 메서드를 결정론 stub로 추가한다(테스트/CI용).
- 기존 `whoami`, `get_portfolios`, `get_cost_basis` 메서드와 동작은 변경하지 않는다.
## Acceptance Criteria
1. Given portfolio-hub가 portfolio_id/from_dt/to_dt에 대해 `{"items": [...], "cost_basis_from": "...", "data_completeness_warning": false}` 형태의 200 응답을 반환할 때, When `get_realized_pnl` 호출, Then `RealizedPnlResult`가 올바른 items/cost_basis_from/data_completeness_warning/total_realized_pnl으로 반환된다.
2. Given `symbol` 파라미터가 제공될 때, When `get_realized_pnl` 호출, Then `?symbol=<값>`이 쿼리 파라미터로 포함된다.
3. Given portfolio-hub가 네트워크 오류 또는 비-2xx 응답을 반환할 때, When `get_realized_pnl` 호출, Then `PortfolioHubUnavailableError`가 raise 된다.
4. Given `stub_read.py` stub, When `get_realized_pnl` 호출, Then 네트워크 없이 미리 설정한 고정 `RealizedPnlResult`를 반환한다.
5. Given import sweep, When `trex_core/domain`을 검사, Then 신규 포트/어댑터 관련 IO/네트워크 의존이 domain에 없다(NFR: 도메인 순수성 불변).
6. Given `data_completeness_warning: true` 응답, When 파싱, Then `RealizedPnlResult.data_completeness_warning == True`로 반영된다.
## Impacted Area
- Docs: 없음(리포트 표면 갱신은 REQ-110/111)
- Modules:
- `trex_core/application/portfolio.py` — `PortfolioHubReadPort`에 `get_realized_pnl` 메서드 추가, `RealizedPnlResult`/`RealizedPnlItem` 데이터클래스 신규 정의
- `trex_core/adapters/portfolio/hub_read.py` — `get_realized_pnl` HTTP 구현 추가
- `trex_core/adapters/portfolio/stub_read.py` — stub 메서드 추가
- Tests:
- `tests/adapters/portfolio/test_hub_read.py` — 신규 AC-1~3, AC-6 케이스 추가
- `tests/adapters/portfolio/test_stub_read.py` — AC-4 stub 케이스 추가
- `tests/domain/test_domain_import_sweep.py` — AC-5 회귀(기존 sweep 재실행)
## Notes
- portfolio-hub `GET /v1/portfolios/{id}/pnl/realized` 계약(REQ-040/041): from_dt/to_dt는 **매도 기준** date-filter. cost basis는 기간 밖 매수도 WAC에 포함(멀티데이 정의). 응답 envelope는 portfolio-hub `app/api/v1_portfolios.py:712`·`app/domain/pnl.py`·schemas 기준 — 구현 시 실 계약을 `hub_read.py`의 기존 `get_cost_basis` 패턴(graceful degrade 방식)에 맞춰 구현한다.
- OQ-2(기간 파라미터 기본값 정의), OQ-3(수수료 출처), OQ-4(N콜 vs 집계) 미결 → 이 REQ는 어댑터 계층만 정의. 기간 인자는 caller(REQ-110의 CLI/report 레이어)가 결정해 전달. 어댑터는 전달된 파라미터를 그대로 쿼리에 쓴다.
- `total_realized_pnl`은 `items`의 `realized_pnl` 합산으로 어댑터 내에서 계산(portfolio-hub 응답에 합산 필드가 있으면 재사용, 없으면 클라이언트 합산).
- 기존 `get_cost_basis`(REQ-105 FR-7)는 sync CLI용 포트로 독립 유지 — 이 REQ와 중복 아님.
## Change Log
### CHG-20260626-02
- Date: 2026-06-26
- Author: trex-core-dev
#### Change Summary
- BAT-052 구현·검증 완료(PR #33, 머지 커밋 81d06d9). confirm-batch-verification + confirm-req-implemented. Approved → Implemented.
#### Intent
- dcy-035 FR-1 delivery 완료 확인 및 상태 전환.
#### Acceptance Criteria Delta
- Updated:
- none (모든 AC-1~6 검증 완료, 상태 전환만)
- Added:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs: 없음
- Modules: 변경 없음 (구현 완료 상태 유지)
- Tests: 717 passed / 47 skipped / 0 fail (BAT-052 verification)
#### Delivery Trace Delta
- Batches:
- BAT-052 (verified, PR #33, 머지 커밋 81d06d9, 2026-06-26)
- Releases:
- none (M3 배포 대기)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: none
### CHG-20260626-01
- Date: 2026-06-26
- Author: trex-core-dev
#### Change Summary
- dcy-035 FR-1로부터 최초 생성(Proposed). portfolio-hub WAC 실현 PnL read 포트 + HTTP 어댑터 + stub.
#### Intent
- 운영 리포트(REQ-110)가 WAC 실현손익을 표시하기 위한 하위 의존 어댑터 정의.
#### Acceptance Criteria Delta
- Added:
- AC-1~6 신규
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs: 없음
- Modules:
- portfolio.py 포트 확장, hub_read.py/stub_read.py 어댑터 신규 메서드
- Tests:
- test_hub_read.py, test_stub_read.py, test_domain_import_sweep.py
#### Delivery Trace Delta
- Batches:
- none (미배치)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 승인 게이트 후 suggest-batch-reqs
@@ -1,102 +0,0 @@
# req-112: portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- 현재 `PortfolioHubReadAdapter.whoami()`는 `GET /v1/user_info`에서 `daemon_id`만 읽는다. dcy-036 C1에 따라 같은 엔드포인트가 daemon-scoped 단일 `broker` 객체(`account_ref`, `app_key`, `app_secret`, `credential_version`)와 `trading_enabled` 필드를 함께 반환하도록 확장된 portfolio-hub 계약(REQ-049, dcy-010)을 소비해야 한다.
- 이 REQ는 트랜스포트 레이어 — 포트 인터페이스 정의, HTTP 어댑터 구현, 테스트용 stub — 만 다루며, KISConfig 구성 로직(REQ-113)과 로드 게이트(REQ-114)의 기반을 제공한다.
## Requirement
- `trex_core/application/portfolio.py`에 `BrokerCredentialPayload` 데이터클래스를 추가한다: `account_ref: str`, `app_key: str`, `app_secret: str`, `credential_version: int | None = None`.
- `PortfolioHubReadPort` 프로토콜에 `fetch_broker_credential() -> BrokerCredentialPayload | None` 메서드를 추가한다.
- 반환 값 None: portfolio-hub 응답 `broker: null` (해당 daemon에 인증정보 없음).
- `trading_enabled` 필드도 함께 읽어 반환해야 하므로, 포트 메서드는 `TradingContext` 데이터클래스를 반환한다: `broker: BrokerCredentialPayload | None`, `trading_enabled: bool`.
- `PortfolioHubReadAdapter`의 기존 `whoami()` 메서드를 내부적으로 `GET /v1/user_info`를 공유하는 내부 헬퍼(`_fetch_user_info()`)로 리팩토링하고, `fetch_broker_credential()` 구현이 이를 재사용하도록 한다. 외부 동작은 변경하지 않는다(`whoami()` 시그니처·예외 동작 불변, 회귀 차단 NFR-1).
- `fetch_broker_credential()`은 다음 규칙을 따른다:
- `broker: null` 응답 → `TradingContext(broker=None, trading_enabled=trading_enabled)` 반환.
- `credential_payload: null`(복호화 실패) → `TradingContext(broker=None, trading_enabled=trading_enabled)` 반환(정보 부재로 처리).
- 네트워크 오류 / 비-2xx / JSON 파싱 실패 → `PortfolioHubUnavailableError` raise.
- HTTP 403(`daemon_id` 없는 토큰) → `PortfolioHubUnavailableError` raise.
- `trex_core/adapters/portfolio/stub_read.py`의 stub에도 `fetch_broker_credential()` 결정론 stub을 추가한다.
- `app_secret` 필드는 어댑터 내에서 로그에 직접 출력하지 않는다(NFR-2 보안 — 마스킹).
## Acceptance Criteria
1. Given daemon_id에 매칭된 broker 인증정보가 있고 `trading_enabled: true`인 `/v1/user_info` 응답, When `fetch_broker_credential()` 호출, Then `TradingContext(broker=BrokerCredentialPayload(account_ref=..., app_key=..., app_secret=...), trading_enabled=True)` 반환.
2. Given `broker: null` 응답(인증정보 없음), When `fetch_broker_credential()` 호출, Then `TradingContext(broker=None, trading_enabled=<응답값>)` 반환 (예외 없음).
3. Given `credential_payload: null`(복호화 실패) 응답, When `fetch_broker_credential()` 호출, Then `TradingContext(broker=None, trading_enabled=<응답값>)` 반환 (예외 없음, 인증정보 부재로 처리).
4. Given 네트워크 오류 또는 비-2xx(403/500 등) 응답, When `fetch_broker_credential()` 호출, Then `PortfolioHubUnavailableError` raise.
5. Given `fetch_broker_credential()` 구현 이후, When `whoami()` 호출, Then 기존 `whoami()` 계약(daemon_id str 반환, PortfolioHubUnavailableError on failure)이 변경되지 않는다 (회귀 없음).
6. Given stub_read.py stub, When `fetch_broker_credential()` 호출, Then 미리 설정한 고정 `TradingContext`를 네트워크 없이 반환한다.
7. Given `fetch_broker_credential()` 구현, When 에러/성공 로그 출력 확인, Then `app_secret` 값이 평문으로 로그에 출력되지 않는다.
8. Given import sweep, When `trex_core/domain`을 검사, Then 신규 포트/어댑터 관련 IO/네트워크 의존이 domain에 없다 (도메인 순수성 불변).
## Impacted Area
- Docs: 없음 (baseline 문서 갱신은 dcy-036 scope 밖)
- Modules:
- `trex_core/application/portfolio.py` — `BrokerCredentialPayload`, `TradingContext` 데이터클래스 신규 정의; `PortfolioHubReadPort`에 `fetch_broker_credential()` 메서드 추가
- `trex_core/adapters/portfolio/hub_read.py` — `_fetch_user_info()` 내부 헬퍼 추출, `fetch_broker_credential()` 구현 추가; `whoami()` 동작 보존
- `trex_core/adapters/portfolio/stub_read.py` — `fetch_broker_credential()` stub 추가
- Tests:
- `tests/adapters/portfolio/test_hub_read.py` — AC-1~5, AC-7 케이스 추가
- `tests/adapters/portfolio/test_stub_read.py` — AC-6 stub 케이스 추가
- `tests/domain/test_domain_import_sweep.py` — AC-8 회귀(기존 sweep 재실행)
## Notes
- 소비 계약: portfolio-hub `req-049_user-info-daemon-scoped-broker-delivery.md` (Implemented, dcy-010 FR-2). 응답 스키마: `broker: { account_ref, credential_payload: { app_key, app_secret } | null, credential_version, ... } | null` + `trading_enabled: bool`.
- `TradingContext`를 별도 데이터클래스로 도입하는 이유: 포트 메서드가 broker 인증정보와 `trading_enabled`를 한 번의 호출로 함께 반환해야 불필요한 중복 HTTP 호출을 피할 수 있기 때문(원자적 읽기).
- OQ-4(그날-시작 로드 실패 알람 trex-notifier 연동): Deferred — 알람 발신은 REQ-114 Notes에서 다룬다.
- 선행 의존: portfolio-hub dcy-010 / REQ-049 배포 완료 여부에 따라 실 통합 테스트 가능 시점이 달라진다. CI 단계에서는 stub 기반 단위 테스트로 AC-1~8 검증.
- Source: dcy-036 C1, FR-1, dcy-010 OQ-2 Answered.
## Change Log
### CHG-20260628-01
- Date: 2026-06-28
- Author: trex-core-dev
#### Change Summary
- 신규 생성 (Proposed) — dcy-036 C1 산출물.
#### Intent
- dcy-036 FR-1(그날-시작 portfolio-hub `/v1/user_info`에서 KIS 인증정보 fetch)의 트랜스포트 계층을 REQ로 정규화한다. KISConfig 구성(REQ-113) 및 로드 게이트(REQ-114)의 선행 의존.
#### Acceptance Criteria Delta
- Added:
- AC-1~8 전체 (신규 초안)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules:
- `portfolio.py` 포트 확장 (BrokerCredentialPayload, TradingContext, fetch_broker_credential)
- `hub_read.py` 어댑터 확장 (_fetch_user_info 헬퍼, fetch_broker_credential 구현)
- `stub_read.py` stub 확장
- Tests:
- `test_hub_read.py`, `test_stub_read.py`, `test_domain_import_sweep.py`
#### Delivery Trace Delta
- Batches: 미정 (suggest-batch-reqs 대기)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 승인 게이트 후 REQ-112~115 일괄 batch 배정 검토.
#### Validation Plan
- pytest로 AC-1~8 각 케이스 확인. app_secret 마스킹(AC-7)은 로그 캡처 검증. domain sweep(AC-8)은 기존 test_domain_import_sweep.py 재실행.
### CHG-20260628-02
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req: `Proposed` → `Approved`. dcy-036 FR-1/C1 추적. TradingContext 원자 read(broker+trading_enabled 1회 호출) 설계 타당 — /v1/user_info 단일 응답에 정합. whoami 회귀보존(AC-5)·도메인 순수성(AC-8)·app_secret 마스킹(AC-7) 적절. blocker 없음. 선행 의존 portfolio-hub REQ-049(머지완료).
### CHG-20260628-03
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req-implemented: `Approved` → `Implemented`. 근거: BAT-053 PR #34 머지(merge commit 01655ab). fetch 포트(`fetch_broker_credential()`, `BrokerCredentialPayload`, `TradingContext`) + HTTP 어댑터 + stub AC-1~8 전 케이스 pytest 검증 완료. QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀.
@@ -1,99 +0,0 @@
# req-114: 그날-시작 로드 게이트 — fail-closed 신규 진입 차단 + 알람
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- dcy-036 C2/FR-2에 따라, 그날-시작 체크포인트(단기: 07:30 KST 스냅샷, 중장기: dcy-027 운영 프로파일 주기)에서 portfolio-hub가 불가하거나 broker 인증정보 부재(`broker: null`) 또는 `trading_enabled: false`이면 그날 **신규 진입을 개시하지 않는다(fail-closed)**. 사유를 로깅하고 운영자 알람을 발신한다.
- "그날 고정" 원칙: 로드 성공 후 그날 인증정보는 캐시로 유지되며, 장중 portfolio-hub 복구 시에도 그날 재fetch하지 않는다(결정론 — REQ-115 담당).
## Requirement
- **그날-시작 체크포인트 후크**: `SnapshotScheduler.run_once()`(단기 07:30 KST 스냅샷 트리거)의 `credential_fetch_enabled = true` 경로에서, 스냅샷 전 `fetch_broker_credential()` (REQ-112)를 호출해 `TradingContext`를 획득한다. 중장기 인스턴스는 dcy-027 운영 프로파일 리밸런싱 주기의 동등한 후크 지점에서 동일 로직을 수행한다.
- **fail-closed 조건**: 다음 중 하나라도 해당하면 그날 `entry_allowed = False` 상태를 설정한다:
1. `PortfolioHubUnavailableError` (네트워크 오류 / 비-2xx / 403)
2. `TradingContext.broker is None` (인증정보 부재)
3. `TradingContext.trading_enabled is False`
- **진입 차단**: `entry_allowed = False` 상태에서 BuyIntent(신규 진입 의도)는 admission 단계에서 거부된다. 이미 진행 중인 주문(DISPATCHED 슬롯)과 청산(SellIntent) 경로는 차단하지 않는다.
- **사유 로깅**: fail-closed 진입 시 ERROR 레벨로 사유(portfolio-hub 불가 / broker null / trading_enabled false)와 인스턴스 정보를 로깅한다. app_secret은 로그에 포함하지 않는다.
- **알람 발신**: fail-closed 진입 시 CRITICAL 알람을 `AlarmEmitter`를 통해 발신한다(`trex.alerts` exchange, REQ-099/100). 알람 코드: `DAY_START_LOAD_FAILED`. 알람 페이로드에는 사유 코드와 인스턴스 키를 포함한다.
- **성공 로깅**: 로드 성공 시 INFO 레벨로 fetch 소스, account_ref(app_secret 제외), trading_enabled를 기록한다.
## Acceptance Criteria
1. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `PortfolioHubUnavailableError` 발생, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람(`DAY_START_LOAD_FAILED`) 발신.
2. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.broker is None`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신.
3. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.trading_enabled = False`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신.
4. Given `entry_allowed = False` 상태, When BuyIntent 수신, Then BuyIntent가 admission 단계에서 거부된다 (신규 진입 차단).
5. Given `entry_allowed = False` 상태, When SellIntent 수신, Then SellIntent는 차단되지 않고 정상 처리된다 (청산 경로 영향 없음).
6. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트 성공(`broker` 있음 + `trading_enabled = True`), When 체크포인트 실행, Then `entry_allowed = True` 설정 + INFO 로그(account_ref 포함, app_secret 제외).
7. Given `credential_fetch_enabled = false`, When 그날-시작 체크포인트 실행, Then `fetch_broker_credential()` 호출 없이 기존 스냅샷 로직만 수행된다 (회귀 없음, opt-in 보존).
8. Given 알람 발신 성공, When `trex.alerts` exchange 메시지 확인, Then `DAY_START_LOAD_FAILED` 코드와 instance_key를 포함한 알람 envelope가 존재한다.
9. Given fail-closed 후 `entry_allowed = False` 상태에서 portfolio-hub가 복구됨, When 그날 중 재fetch 없이 운행, Then `entry_allowed` 상태는 그날 고정(재fetch로 자동 해제 안 됨) — 다음 그날-시작 체크포인트까지 유지.
## Impacted Area
- Docs: 없음 (baseline 문서 갱신은 별도 scope)
- Modules:
- `trex_core/application/snapshot_scheduler.py` — `run_once()` 또는 그날-시작 후크 지점에 `fetch_broker_credential()` 호출 + `entry_allowed` 상태 설정 + 알람 발신 추가
- `trex_core/application/execution.py` (또는 IntentService) — BuyIntent admission에 `entry_allowed` 게이트 추가
- `trex_core/application/alarm_emitter.py` — `DAY_START_LOAD_FAILED` 알람 코드 추가(REQ-100 패턴 재사용)
- Tests:
- `tests/application/test_snapshot_scheduler.py` — AC-1~3, AC-6~9 케이스 추가 (stub fetch_broker_credential + AlarmEmitter mock)
- `tests/application/test_execution.py` (또는 IntentService 테스트) — AC-4~5 케이스 추가
## Notes
- OQ-4(그날-시작 로드 실패 알람 trex-notifier 연동): **Deferred** — 이 REQ는 `trex.alerts` exchange 발신(REQ-099/100 계약)까지만 정의한다. trex-notifier(dcy-033) 연동은 별도 REQ/Discovery로 추적한다. 알람 등급은 CRITICAL(거래 개시 불가).
- `entry_allowed` 상태 저장소: in-process 상태 변수(process-local)로 충분하다. 재시작 시 그날-시작 체크포인트가 재실행되므로 내구성 저장소 불필요.
- 중장기 인스턴스의 "그날-시작 체크포인트" 시점은 dcy-027 운영 프로파일 리밸런싱 주기의 구체적 후크 위치에 의존한다. 구현 단계에서 확인 후 조정.
- AC-9(그날 고정 상태 불변성): 장중 재fetch 없음이 REQ-115의 보완 요건이기도 하다. 이 REQ는 "진입 차단 상태의 그날 고정"만 명시하고, "장중 장애 후 인증정보 캐시 지속 운행"은 REQ-115가 담당.
- Source: dcy-036 C2, FR-2, OQ-1 Answered (단기 07:30 / 중장기 프로파일 주기), OQ-2 Answered (그날 고정).
## Change Log
### CHG-20260628-01
- Date: 2026-06-28
- Author: trex-core-dev
#### Change Summary
- 신규 생성 (Proposed) — dcy-036 C2/FR-2 산출물.
#### Intent
- dcy-036 FR-2(그날-시작 로드 시점 fail-closed 진입 차단 + 알람)를 Integration REQ로 정규화한다.
#### Acceptance Criteria Delta
- Added:
- AC-1~9 전체 (신규 초안)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules:
- `snapshot_scheduler.py` 그날-시작 후크 + entry_allowed 상태 + 알람
- `execution.py` / IntentService BuyIntent admission 게이트
- `alarm_emitter.py` 코드 추가
- Tests:
- `test_snapshot_scheduler.py`, `test_execution.py`
#### Delivery Trace Delta
- Batches: 미정 (suggest-batch-reqs 대기)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 REQ-112~115 일괄 batch 배정 검토.
#### Validation Plan
- pytest로 AC-1~9 각 케이스 확인. stub `fetch_broker_credential`과 AlarmEmitter mock으로 I/O 격리. AC-4/5(차단 범위 경계: 매수 차단 / 매도 통과)가 가장 중요한 케이스.
### CHG-20260628-02
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req: `Proposed` → `Approved`. dcy-036 FR-2/OQ-1/OQ-2 추적. **진입 fail-closed(BuyIntent 차단) + 청산·DISPATCHED 통과(AC-4/5)** 비대칭이 사장님 fail-safe 결정 정합 — 자본 갇힘 방지. CRITICAL 알람 기존 trex.alerts(REQ-099/100) 재사용·그날 고정(AC-9) 적절. OQ-4(trex-notifier 연동) Deferred 정당. blocker 없음.
### CHG-20260628-03
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req-implemented: `Approved` → `Implemented`. 근거: BAT-053 PR #34 머지(merge commit 01655ab). fail-closed 게이트 AC-1~9 pytest 검증 완료 — BuyIntent 차단/SellIntent 통과 비대칭, CRITICAL 알람(`DAY_START_LOAD_FAILED`), opt-in off 비활성(AC-7), 그날 고정(AC-9) 확인. QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀.
@@ -1,119 +0,0 @@
# req-117: OrderRequest venue additive 필드
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- KIS NXT(대체거래소) 주문 지원을 위해 `OrderRequest` DTO에 `venue: str = "KRX"` 필드를 additive 추가한다.
- 기본값 `"KRX"`로 미지정 시 기존 orb/tsl 경로와 wire 의미론이 byte-동일하게 유지되어 하위 호환을 보장한다.
- 포트-어댑터 경계에서 venue 의미론을 확립하고, 상위 intent→venue 전달(tvr 팬-인 batch)이 이 계층 설계를 건드리지 않아도 되도록 확장 경로를 열어 둔다.
## Requirement
- `trex_core/application/broker_port.py`의 `OrderRequest` dataclass에 `venue: str = "KRX"` 필드를 추가한다.
- 필드는 frozen dataclass의 마지막 옵셔널 위치에 추가하여 기존 위치 인수 호출을 깨지 않는다.
- venue 값 의미론: `"KRX"` = 정규 KRX 거래소, `"NXT"` = 대체거래소(연장세션 포함), `"SOR"` = 최선집행(현재 비권고 — OQ-1 잔여 미확인).
- 이 필드는 포트 계층 DTO로서 venue 명시를 강제하지 않는다(기본값 `"KRX"` 허용). KIS 어댑터(REQ-118)가 이 값을 `EXCG_ID_DVSN_CD`로 변환한다.
- paper broker stub은 `venue` 필드를 수신하되 무시해도 무방하다(venue는 KIS-specific 개념, paper는 교환소 개념 없음). 단, `OrderRequest` 수신 시 venue 필드를 읽어 로깅/audit에 포함할 수 있다.
## Acceptance Criteria
1. Given `OrderRequest(slot_id="s1", side="buy", symbol="005930", qty=1)`로 venue 미지정 시, When dataclass를 생성하면, Then `req.venue == "KRX"`이다.
2. Given `OrderRequest(..., venue="NXT")`로 NXT 지정 시, When dataclass를 생성하면, Then `req.venue == "NXT"`이다.
3. Given 기존 orb/tsl 코드가 `OrderRequest` 생성 시 `venue`를 지정하지 않는 경우, When 단위 테스트를 실행하면, Then 기존 테스트가 모두 통과한다(회귀 없음).
4. Given `OrderRequest` frozen dataclass 불변식, When `venue` 필드가 추가된 후에도, Then 기존 위치 인수(`slot_id`, `side`, `symbol`, `qty`) 순서가 변경되지 않고 기존 코드가 재컴파일 없이 동작한다.
5. Given paper broker stub이 `submit_order(req: OrderRequest)` 를 수신할 때, When `req.venue`가 `"NXT"`이든 `"KRX"`이든, Then paper stub은 주문을 정상 수락한다(venue 값에 무관하게 기존 동작 유지).
## Impacted Area
- Docs: `docs/interface-contract.md` — OrderRequest DTO 스키마 섹션 venue 필드 추가 필요.
- Modules:
- `trex_core/application/broker_port.py` — `OrderRequest.venue` 필드 추가(additive).
- `trex_core/adapters/broker/paper.py` — venue 수신 허용(무시 또는 로깅), 기존 동작 유지.
- 호출 측(execution, safety_sell 등)은 venue 미지정으로 자동 KRX 기본값 적용 — 변경 불필요.
- Tests:
- `tests/` — 기존 `OrderRequest` 생성 테스트 회귀 확인.
- 신규: venue 기본값 및 NXT 지정 단위 테스트 추가.
## Notes
- **wire 계약 영향 없음**: `OrderRequest`는 AMQP wire 위를 직접 흐르지 않는다. intent 메시지 계약(wire)에서 venue를 전달하는 것은 tvr 팬-인 batch(후속)의 범위이며, 본 REQ는 브로커 어댑터 계층 DTO까지만 다룬다.
- `"SOR"` 값은 정의만 하고 현재 KIS 운영 권고 대상 아님(OQ-1 잔여: SOR 체결거래소 식별·EXCG 미전송 기본값 미확인). SOR 동작은 REQ-118 AC에서 별도 처리하지 않는다.
- frozen dataclass 확장 시 기존 `field(default_factory=...)` 패턴이 없으므로 기본값 있는 단순 필드로 충분하다.
- 출처 Discovery: trex-tvr-v1 `dcy-002_20260704_execution-cadence-slot-fetch.md` FR-6, confirmed 2026-07-06.
- 실측 근거: 2026-07-06 실전 계좌 NXT 주문 rt_cd=0 접수·취소 rt_cd=0 확인.
## Change Log
### CHG-20260707-01
- Date: 2026-07-07
- Author: 팀장(Claude) — confirm-req-implemented
#### Change Summary
- Approved → **Implemented**: bat-056 구현 완료. 독립 QC **opus** PASS-WITH-NOTES(blocker 0) — 794 passed, 기존 주문 페이로드 불변(EXCG=KRX 추가만, 키 집합 잠금), 전 호출부 정합. PR #38 사장님 결재·머지(079fc04, 2026-07-07) = confirm-batch-verification 완료.
- **배포 게이트(NXT 라이브 사용 전)**: 실전 소액 스모크(KRX+NXT 각 1건)로 poll_fill 구 TR의 NXT 체결 포함 여부 실측(AC-4 OI-1 해소). 미포함 시 신 TR(TTTC0081R) 전환 별도 batch.
#### Delivery Trace Delta
- Batches: bat-056_20260706_kis-broker-venue (merged, PR #38)
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no
- Status Recommendation: Implemented
### CHG-20260706-02
- Date: 2026-07-06
- Author: 팀장(Claude) — confirm-req
#### Change Summary
- Proposed → Approved: 팀장 검토·승인. dcy-002(tvr, confirmed 2026-07-06) NXT venue 트랙. 실측 근거(NXT 주문/취소 rt_cd=0, 2026-07-06) 확보 상태. 고위험(라이브 주문 경로) — worktree+PR+QC(opus) 플로.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no
- Status Recommendation: Approved
### CHG-20260706-01
- Date: 2026-07-06
- Author: trex-core-dev
#### Change Summary
- REQ-117 신규 생성(Proposed). trex-tvr-v1 dcy-002 FR-6 기반 OrderRequest venue additive 필드.
#### Intent
- NXT 거래소구분 지원을 위한 포트 계층 DTO 확장. additive 설계로 기존 orb/tsl 무영향.
#### Acceptance Criteria Delta
- Added: AC-1~5 신규 작성.
- Updated: 없음.
- Removed: 없음.
#### Impacted Area Delta
- Docs: docs/interface-contract.md venue 필드 섹션 추가 예정.
- Modules: broker_port.py OrderRequest 확장, paper broker 수신 허용.
- Tests: 기존 회귀 + venue 기본값/NXT 신규 단위 테스트.
#### Delivery Trace Delta
- Batches: 미할당(suggest-batch-reqs 대기).
- Releases: 미할당.
#### Revalidation Impact
- Reverification Needed: no (신규 Proposed, 기존 구현 없음)
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 suggest-batch-reqs → REQ-118과 동일 batch로 묶을 것.
#### Validation Plan
- pytest 전체 통과(기존 회귀 없음) + venue 필드 단위 테스트 신규 통과.
@@ -1,211 +0,0 @@
# req-118: KIS 브로커 어댑터 venue 파라미터화 (EXCG_ID_DVSN_CD)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- KIS 브로커 어댑터(`adapters/broker/kis.py`)에서 주문 거래소구분(`EXCG_ID_DVSN_CD`)을 `OrderRequest.venue`로부터 파라미터화하여 NXT(대체거래소) 주문·취소를 지원한다.
- 동시에 `submit_order`에서 기존 EXCG 미전송 관행을 `"KRX"` 명시 전송으로 변경하여 서버 기본값 의존을 제거한다(동작 고정, 실측상 서버 기본값=KRX로 동일하나 명시화가 더 견고함).
- `poll_fill`은 신 TR(`TTTC0081R`/`VTTC0081R`)과 `EXCG_ID_DVSN_CD=ALL`을
사용하여 KRX·NXT 체결 조회를 보장한다.
- `get_current_price`의 시장구분(`FID_COND_MRKT_DIV_CODE`) `"J"` 하드코딩을 파라미터로 전환한다(기본 `"J"`, `"UN"` 옵션).
- 이 모든 변경은 venue 미지정(기본 KRX) 경로에서 orb/tsl 기존 동작과 byte-동일하게 유지되는 additive 설계다.
## Requirement
1. `submit_order`: `body`에 `"EXCG_ID_DVSN_CD": req.venue` 항상 명시 전송한다. `req.venue` 기본값은 `"KRX"`이므로 venue 미지정 호출에서도 `EXCG_ID_DVSN_CD=KRX`가 전송된다.
2. `cancel_order`: 취소 요청 `body`에 `"EXCG_ID_DVSN_CD": <원주문 venue>` 를 전송한다. 원주문 venue는 cancel_order 시그니처에 `venue: str = "KRX"` 파라미터를 추가하거나, 호출자가 원주문 venue를 보존·전달하는 방식으로 구현한다(설계 방식은 구현 batch에 위임 — 의미론 확정: 취소는 원주문 venue와 동일한 EXCG를 사용해야 한다).
3. `poll_fill`: 일반 주문과 결과불명 주문 조회 모두 실전 `TTTC0081R`, 모의
`VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달하여 KRX·NXT 체결을
하나의 조회로 확인한다.
4. `get_current_price`: 시그니처에 `market: str = "J"` 파라미터를 추가하고, `FID_COND_MRKT_DIV_CODE`를 해당 값으로 전달한다. 기본 `"J"` 유지로 기존 safety-sell 경로 무영향. 호출자가 `"UN"` 지정 가능.
5. orb/tsl 무영향: venue 미지정 경로에서 submit_order 페이로드는 기존 대비 `EXCG_ID_DVSN_CD="KRX"` 필드 추가만 다르며, 주문 수락 동작이 동일함을 회귀 테스트로 확인한다.
## Acceptance Criteria
1. Given `OrderRequest(..., venue="KRX")`(또는 venue 미지정 기본값)로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "KRX"`가 포함된다.
2. Given `OrderRequest(..., venue="NXT")`로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다.
3. Given NXT 주문의 broker_order_id와 `venue="NXT"`로 `cancel_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다.
4. Given 일반 주문번호 또는 결과불명 주문 메타데이터를 조회할 때, When
`inquire-daily-ccld` 요청을 생성하면, Then 실전은 `TTTC0081R`, 모의는
`VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달한다.
5. Given `get_current_price(symbol, market="J")` 기본 호출(또는 market 미지정) 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=J`가 전달된다(기존 동작 동일).
6. Given `get_current_price(symbol, market="UN")` 호출 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=UN`이 전달된다.
7. Given 기존 orb/tsl가 venue 미지정으로 `submit_order`를 호출하는 경로, When 단위 테스트를 실행하면, Then 기존 submit_order 관련 테스트 전체가 통과한다(회귀 없음). 페이로드 차이는 `EXCG_ID_DVSN_CD="KRX"` 추가뿐임을 fixture 검사로 확인한다.
8. Given paper broker stub이 `cancel_order` 시그니처 변경(venue 파라미터 추가)을 반영한 후, When paper broker로 cancel_order 호출 시, Then 기존 취소 동작이 유지된다.
## Impacted Area
- Docs:
- `docs/interface-contract.md` — KIS 브로커 어댑터 EXCG 전송 명세 추가.
- `docs/runtime-flows.md` — 주문 집행 흐름 중 venue 전달 경로 명시.
- Modules:
- `trex_core/adapters/broker/kis.py` — `submit_order`, `cancel_order`, `poll_fill`, `get_current_price` 변경.
- `trex_core/application/broker_port.py` — `BrokerPort.cancel_order` 시그니처 venue 파라미터 추가(또는 호출자 보존 방식에 따라 설계 batch에서 결정).
- `trex_core/adapters/broker/paper.py` — cancel_order 시그니처 정합 유지.
- Tests:
- `tests/adapters/broker/` — submit_order EXCG 필드 포함 fixture, cancel_order venue 전달 검증, get_current_price market 파라미터 검증.
- `tests/` — 전체 회귀(기존 venue 미지정 경로 pass 확인).
## Notes
- **고위험 경로**: `submit_order`·`cancel_order`·`poll_fill`은 라이브 주문 직결 경로다. worktree+PR+QC(opus) 플로 필수.
- **cancel_order 설계 위임**: 취소 시 venue 전달 방식(cancel_order 시그니처 추가 vs. execution context 보존)은 구현 batch design에서 확정한다. 본 REQ는 의미론만 확정한다 — "취소 요청은 원주문 venue와 동일한 EXCG_ID_DVSN_CD를 사용해야 한다."
- **poll_fill TR 계약**: KIS 공식 `주식일별주문체결조회[v1_국내주식-005]`
기준 3개월 이내 실전은 `TTTC0081R`, 모의는 `VTTC0081R`이며 거래소
구분 `ALL`은 전체 거래소 조회다. 구형 `TTTC8001R`/`VTTC8001R`은
사전고지 없이 중단될 수 있으므로 사용하지 않는다.
- **모의(VTS) 범위**: 모의투자는 KRX만 제공되지만 동일 신형
`VTTC0081R` 요청 형식과 `ALL` 파라미터를 유지해 실전·가상 계약을
분기시키지 않는다.
- **토큰 캐시**: 실측(2026-07-06)에서 토큰 재요청 1분 rate-limit 확인. 현행 `kis.py` 토큰 캐시 방식과 정합 — 신 TR 전환 시에도 토큰 캐시 재사용 경로를 유지해야 한다.
- **SOR**: `"SOR"` 값 전달은 설계 경로에서 차단하지 않되, 현재 권고하지 않는다(체결거래소 식별·정정취소 EXCG 일치 보장이 미확인).
- 출처 Discovery: trex-tvr-v1 `dcy-002_20260704_execution-cadence-slot-fetch.md` FR-6, confirmed 2026-07-06.
- 실측 근거: 2026-07-06 실전 계좌 NXT 주문(TTTC0012U + EXCG=NXT) rt_cd=0, 취소(TTTC0013U + EXCG=NXT) rt_cd=0·잔여 0건 확인.
- 선행 REQ: REQ-117(OrderRequest.venue 필드) 구현 완료 후 본 REQ 구현 가능.
## Change Log
### CHG-20260729-02
- Date: 2026-07-29
- Author: Codex — confirm-req-implemented
#### Change Summary
- Approved → Implemented: BAT-063이 release-candidate가 되었고 변경으로 무효화된
AC-4를 포함해 REQ-118 전체 수용 기준이 다시 검증됐다.
#### Delivery Trace Delta
- Batches: BAT-063 release-candidate
- Verification:
`docs/batches/bat-063_20260729_kis-daily-fill-all/verification.md`
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
#### Validation Plan
- KIS broker 집중검사 59 passed/1 skipped, Core 전체 922 passed/51 skipped,
Ruff PASS.
- 공용 가상 주문 인증 1~7단계 PASS, 미체결·포지션·중복 주문 0, 원장 일치.
- 전략 신호·가격·수량 정책 변경이 없어 수익률 BT는 비대상이다.
- 저장소 FROZEN 규칙 파일과 변경 범위의 Quick Fix/TODO/FIXME/HACK 잔여 없음.
### CHG-20260729-01
- Date: 2026-07-29
- Author: Codex — 형님 승인 `남은 작업 진행하자`
#### Change Summary
- KIS 공식 신형 일별주문체결조회 계약을 Core가 직접 소유하도록
`TTTC0081R`/`VTTC0081R`과 `EXCG_ID_DVSN_CD=ALL`을 `poll_fill` 및
결과불명 주문 조회에 적용한다.
#### Intent
- NXT 체결 조회를 Docker sidecar의 거래 ID 변환에 의존하지 않고 Core KIS
transport에서 완결해 주문·체결·취소 거래시장 정본을 Core 하나로 단일화한다.
#### Acceptance Criteria Delta
- Updated: AC-4를 공식 명세 확인 단계에서 신형 거래 ID와 전체 거래소 조회를
직접 검증하는 실행 기준으로 변경한다.
- Added: 일반 주문과 결과불명 주문 조회가 같은 신형 계약을 사용하는 회귀검사.
- Removed: 구형 거래 ID 유지와 sidecar 변환 의존.
#### Impacted Area Delta
- Modules: `trex_core/adapters/broker/kis.py`
- Tests: `tests/adapters/broker/test_kis.py`,
`tests/adapters/broker/test_venue.py`
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`,
`docs/batches/bat-063_20260729_kis-daily-fill-all/`
#### Delivery Trace Delta
- Batches: BAT-063 신규
- Releases: 미할당
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes — 구형 거래 ID와 NXT 포함 여부 미확인
상태는 최신 KIS 공식 계약 및 sidecar 제거 목표와 맞지 않는다.
- Status Recommendation: Implemented → Approved, BAT-063 검증 후 Implemented 복귀
#### Validation Plan
- 일반·결과불명 `poll_fill` 요청이 실전 `TTTC0081R`, 모의
`VTTC0081R`, `EXCG_ID_DVSN_CD=ALL`을 사용하는 단위검사를 통과한다.
- 기존 KRX/NXT 주문·취소·결과불명·재시작 회귀와 Core 전체 테스트를 통과한다.
- 가상거래소 `logic`부터 `ledger_reconcile`까지 1~7단계 인증을 통과한다.
- FROZEN 영향: 저장소 내 FROZEN 목록이 없으며 공개 포트 시그니처는 변경하지 않는다.
- BT 영향: 전략 신호·가격·수량·자본 정책은 변경하지 않으므로 수익률 BT 대상이
아니다. 주문 수명주기 가상 인증으로 회귀를 검증한다.
### CHG-20260707-01
- Date: 2026-07-07
- Author: 팀장(Claude) — confirm-req-implemented
#### Change Summary
- Approved → **Implemented**: bat-056 구현 완료. 독립 QC **opus** PASS-WITH-NOTES(blocker 0) — 794 passed, 기존 주문 페이로드 불변(EXCG=KRX 추가만, 키 집합 잠금), 전 호출부 정합. PR #38 사장님 결재·머지(079fc04, 2026-07-07) = confirm-batch-verification 완료.
- **배포 게이트(NXT 라이브 사용 전)**: 실전 소액 스모크(KRX+NXT 각 1건)로 poll_fill 구 TR의 NXT 체결 포함 여부 실측(AC-4 OI-1 해소). 미포함 시 신 TR(TTTC0081R) 전환 별도 batch.
#### Delivery Trace Delta
- Batches: bat-056_20260706_kis-broker-venue (merged, PR #38)
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no
- Status Recommendation: Implemented
### CHG-20260706-02
- Date: 2026-07-06
- Author: 팀장(Claude) — confirm-req
#### Change Summary
- Proposed → Approved: 팀장 검토·승인. dcy-002(tvr, confirmed 2026-07-06) NXT venue 트랙. 실측 근거(NXT 주문/취소 rt_cd=0, 2026-07-06) 확보 상태. 고위험(라이브 주문 경로) — worktree+PR+QC(opus) 플로.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음
#### Revalidation Impact
- Reverification Needed: no / Existing Implementation Invalidated: no
- Status Recommendation: Approved
### CHG-20260706-01
- Date: 2026-07-06
- Author: trex-core-dev
#### Change Summary
- REQ-118 신규 생성(Proposed). trex-tvr-v1 dcy-002 FR-6 기반 KIS 브로커 어댑터 venue 파라미터화.
#### Intent
- NXT 거래소구분 지원을 위한 KIS 어댑터 EXCG_ID_DVSN_CD 전송 구현. 기존 orb/tsl 하위 호환 유지.
#### Acceptance Criteria Delta
- Added: AC-1~8 신규 작성.
- Updated: 없음.
- Removed: 없음.
#### Impacted Area Delta
- Docs: interface-contract.md, runtime-flows.md venue 경로 명세 추가 예정.
- Modules: kis.py 4개 메서드 변경, broker_port.py cancel_order 시그니처 정합, paper.py 정합.
- Tests: 신규 EXCG 필드 검증 + 전체 회귀.
#### Delivery Trace Delta
- Batches: 미할당(suggest-batch-reqs 대기).
- Releases: 미할당.
#### Revalidation Impact
- Reverification Needed: no (신규 Proposed, 기존 구현 없음)
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: REQ-117과 동일 batch 묶기 권고. QC opus 지정(고위험 주문 경로).
#### Validation Plan
- pytest 전체 통과(기존 회귀 없음) + 신규 AC-1~8 단위 테스트 통과.
- 배포 전 실전 소액 스모크(KRX + NXT 각 1건) 운영 협의.
@@ -1,127 +0,0 @@
# REQ-119: fetched credential 공통 broker provider와 operator CLI truth 정합
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: trex-core-dev
## Intent
- `credential_fetch_enabled=true` 인스턴스에서 daemon과 operator `sync`/`verify`가 서로 다른 credential source로 브로커를 생성하면, 같은 인스턴스의 broker truth를 신뢰할 수 없다.
- daemon과 모든 operator broker-read 경로가 동일한 portfolio-hub fetched credential authority를 해석하도록 하되, 자격증명·계좌 식별 비밀은 노출하지 않는다.
## Requirement
- 시스템은 fetched credential 모드에서 daemon, `sync`, `verify` 및 같은 broker-read provider를 사용하는 operator 경로가 공통 broker provider를 통해 동일한 portfolio-hub credential source를 해석하도록 해야 한다.
- fetch 실패 시 명시적으로 활성화된 break-glass 정책이 없는 한 env/config credential로 조용히 fallback하지 않아야 하며, `credential_fetch_enabled=false` 인스턴스의 기존 env-only 동작은 유지해야 한다.
- provider가 source/account identity 정합 증거를 노출해야 할 경우 비밀이 아닌 stable label 또는 마스킹된 fingerprint만 사용해야 한다.
## Acceptance Criteria
1. Given `credential_fetch_enabled=true`와 mocked portfolio-hub credential 응답, When daemon-equivalent broker 구성과 `sync --dry-run` 및 `verify`의 broker-read 구성을 실행하면, Then 모두 같은 fetched source identity(안전한 label/fingerprint)와 같은 broker holding set을 사용한다.
2. Given fetched credential 모드에서 portfolio-hub fetch가 실패하고 break-glass가 비활성화됨, When operator broker-read 경로를 실행하면, Then env/config credential fallback이나 broker qty 기반 판단 없이 안전한 오류로 중단한다.
3. Given fetched credential 모드에서 명시적인 break-glass 정책이 활성화됨, When fetch가 실패하면, Then fallback의 활성화 근거와 source mode는 비밀 없이 추적 가능하고, 명시적 정책이 없을 때와 구별된다.
4. Given `credential_fetch_enabled=false` env-only instance, When daemon과 기존 operator broker-read 경로를 실행하면, Then fetched credential 요청 없이 기존 동작 및 CLI 호환성이 유지된다.
5. Given CLI 출력·구조화 로그·테스트 fixture, When source 정합 검증을 수행하면, Then app key, app secret, access token, raw credential payload 및 raw account number가 포함되지 않는다.
## Impacted Area
- Docs: `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, `docs/srs/Integration/req-112_portfolio-hub-broker-credential-read-adapter.md`, `docs/srs/Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md`
- Modules: `trex_core/runtime/daemon.py`, `trex_core/runtime/cli.py`, `trex_core/application/snapshot_scheduler.py` 또는 공통 broker provider/factory, portfolio-hub credential read adapter
- Tests: daemon/provider/CLI integration regression; fetched source parity, fetch failure/no-silent-fallback, opt-in off env-only compatibility, secret-redaction assertions
## Notes
- Source Discovery: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (FR-1, NFR-1~4; GitHub #39).
- Dependencies: REQ-112 implemented credential read port; REQ-113 approved source priority/break-glass policy; REQ-115 approved day-start credential resilience.
- OQ-3의 production live sync target 선정·실행은 release gate이며 본 REQ 범위가 아니다.
## Change Log
### CHG-20260714-01
- Date: 2026-07-14
- Author: delivery-runner
#### Change Summary
- dcy-037 #39의 fetched credential daemon/operator broker truth mismatch를 독립 Proposed REQ로 초안화했다.
#### Intent
- fetched credential authority를 공통 provider 경계로 정렬하여 dry-run/verify 결과가 daemon broker truth와 동일한 source를 사용하게 한다.
#### Acceptance Criteria Delta
- Added:
- fetched provider source parity, no-silent-fallback, break-glass 구별, env-only 회귀 및 secret-safe 관찰성 기준.
- Updated:
- 없음.
- Removed:
- 없음.
#### Impacted Area Delta
- Docs:
- runtime/interface/operations baseline 및 REQ-112/113 참조 정합 대상.
- Modules:
- daemon/CLI 및 공통 broker 구성 경계.
- Tests:
- mocked fetched credential 기반 parity와 회귀 테스트.
#### Delivery Trace Delta
- Batches:
- none (draft only; dcy-037 #39/#40/#41 단일 emergency batch의 향후 입력).
- Releases:
- none (production release/live sync 승인 대상 아님).
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 전 공통 provider 경계와 REQ-113/115 의존 관계를 검토한다.
#### Validation Plan
- isolated fixture에서 daemon-equivalent, `sync --dry-run`, `verify`의 source identity와 holding set을 비교하고, fetch failure·break-glass·env-only 분기를 회귀 검증한다.
### CHG-20260714-02
- Date: 2026-07-14
- Author: Justin (TREX lead)
#### Change Summary
- Proposed에서 Approved로 승인했다.
#### Intent
- dcy-037에서 확정된 #39 broker truth 정합 범위를 독립 delivery input으로 전환한다.
#### Acceptance Criteria Delta
- Added:
- 없음.
- Updated:
- 없음.
- Removed:
- 없음.
#### Impacted Area Delta
- Docs:
- 없음.
- Modules:
- 없음.
- Tests:
- 없음.
#### Delivery Trace Delta
- Batches:
- dcy-037의 #39/#40/#41 단일 emergency batch (아직 미생성).
- Releases:
- none; `sync --live`는 별도 release approval gate 유지.
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs에서 REQ-119~122를 단일 emergency batch로 편성한다.
#### Validation Plan
- batch verification과 독립 QC에서 parity·fallback·redaction AC를 실제 fixture와 CLI 회귀로 재검증한다.
### CHG-20260715-03
- Date: 2026-07-15
- Author: TREX lead
#### Change Summary
- Approved → Implemented (`confirm-req-implemented`).
#### Delivery Trace
- BAT-057 verification confirmed; PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`.
- Independent QC: PASS-WITH-NOTES, acceptance blocker 0. Production live sync/deploy remains a separate release gate.