feat: add messaging transport rollout

This commit is contained in:
2026-06-06 00:02:52 +09:00
parent 4b91343ee8
commit 6c7a1cf6f6
383 changed files with 99045 additions and 487 deletions
@@ -1,13 +1,13 @@
# 0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: confirmed
- 상태: confirmed (transport 부분은 dcy-006/BAT-007 에서 RabbitMQ 로 대체됨; logical intent/admission 계약은 유효)
- 문서 ID: dcy-002_20260603_slot-core-daemon-http-api
- 이슈명: trex-core 슬롯 코어 데몬화 + HTTP intent API
- 작성 시각(KST): 2026-06-03
- 마지막 갱신 시각(KST): 2026-06-03 (draft-req)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 마지막 갱신 시각(KST): 2026-06-05 (BAT-007 transport supersede)
- 대체됨: 본 Discovery 의 **HTTP `/v1` API transport** 가정은 dcy-006(`strategy-module-rabbitmq-transport`, BAT-007)에서 RabbitMQ messaging 으로 대체됨. logical 계약(intent admission, idempotency via `intent_log`, 단일 writer 불변식)은 그대로 유효하며 transport 만 교체. `adapters/api/` HTTP 어댑터, `GET /v1/healthz`, `POST /v1/intents/buy|sell`, `GET /v1/slots`, `GET /v1/capacity` 엔드포인트는 BAT-007 에서 제거됨.
- 후속 Discovery 참조: dcy-006_20260605_strategy-module-rabbitmq-transport (transport 대체)
- 생성된 REQ 참조: REQ-005, REQ-006, REQ-007, REQ-008, REQ-009
# 1. 계획 상태 요약
@@ -5,7 +5,7 @@
- 문서 ID: dcy-005_20260604_portfolio-hub-asset-snapshot-api
- 이슈명: portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공
- 작성 시각(KST): 2026-06-04 22:46
- 마지막 갱신 시각(KST): 2026-06-05 22:36
- 마지막 갱신 시각(KST): 2026-06-05 23:57
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음
@@ -18,7 +18,7 @@
- 관련 TODO/메모: dcy-004(portfolio-hub ledger write 통합, BAT-006 진행 중)의 Out of Scope `asset master 동기화·get_assets/get_portfolios 연동`. orbit `app/clients/portfolio_client.py`(read 계약 get_assets/get_portfolios 패턴). **dcy-006(confirmed) — trex-core ↔ 전략 모듈 통신 transport 를 RabbitMQ 로 확정(HTTP 표면 0). 본 Discovery 의 스냅샷 read 는 그 transport(RabbitMQ request-reply RPC) 위에서 전달된다.**
- 해석:
- dcy-004 는 체결을 portfolio-hub 원장에 기록하는 **write 방향**이었고, 본 Discovery 는 그 반대인 **read 방향** — portfolio-hub 종목 스냅샷을 trex-core 가 전략 모듈에 제공한다. dcy-004 가 명시적으로 후속으로 미룬 영역을 이어받는다.
- **transport/계약 분리**: 본 Discovery 는 스냅샷의 **logical 계약**(응답 단위·필드·마스킹·view 모드·strategy_id 필터·신선도)을 정의하며 **transport-무관**이다. 전달 방식(HTTP 아님 — dcy-006 의 RabbitMQ RPC)은 dcy-006 소관이고, 본 문서는 그 transport 위에 logical 계약을 얹는다.
- **transport/계약 분리**: 본 Discovery 는 스냅샷의 **logical 계약**(응답 단위·필드·마스킹·view 모드·strategy_key 필터·신선도)을 정의하며 **transport-무관**이다. 전달 방식(HTTP 아님 — dcy-006 의 RabbitMQ RPC)은 dcy-006 소관이고, 본 문서는 그 transport 위에 logical 계약을 얹는다.
- active-cycle 판정(BAT-006 draft 진행 중)이지만 명시적 신규 입력이고, BAT-006(write)과 read 방향이 분리되어 범위 충돌이 없으므로 병렬 독립 주기로 진행한다. core 경유의 핵심 가치는 단순 패스스루가 아니라 **core 의 권위 슬롯 상태를 겹쳐(slot overlay) 전략 실행 대상 자산을 선별(pending·warning·pause 마스킹)** 해 제공하는 것이다.
# 2. 요구사항 판정 결과
@@ -35,15 +35,15 @@
- 현재 상태
- trex-core 의 read 경로는 슬롯/capacity 조회와 intent 조회뿐이고 portfolio-hub 종목을 노출하는 read 경로가 없다. (현재 이 read 들은 HTTP `/v1` 로 제공되나, dcy-006 confirmed 로 통신 transport 가 RabbitMQ 로 대체되고 HTTP 표면은 0 이 된다. 따라서 본 스냅샷 read 는 신규 HTTP 엔드포인트가 아니라 dcy-006 의 RabbitMQ RPC 로 노출한다.)
- portfolio-hub read 어댑터(get_assets/get_portfolios)가 trex-core 에 없다. dcy-004/BAT-006 은 write 방향 `LedgerPort` + portfolio-hub 어댑터만 추가하며, read 포트는 다루지 않는다.
- 결과적으로 매수 전략 모듈이 "무엇을 살지" 정할 portfolio-hub 종목 정보를 trex-core 를 통해 받을 경로가 없다.
- 결과적으로 전략 모듈이 "무엇을 매매할지" 정할 portfolio-hub 종목 정보를 trex-core 를 통해 받을 경로가 없다.
- 기대 상태
- trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env)에 일일 스케줄로 portfolio-hub 에서 자신의 `daemon_id` universe(= `managed_by_daemon_id` 가 해당 daemon 인 portfolio 집합)를 가져와 스냅샷으로 보유한다. universe 항목은 portfolio 단위로 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 **가격(price)은 제외**한다.
- 전략 모듈 요청 시 trex-core 가 이 스냅샷을 기반으로 **portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 담되 pending·warning·pause 상태 자산은 마스킹한 전략 실행 대상 자산 리스트**를 반환한다(OQ-4).
- 가용 슬롯이 없을 때(capacity 소진)의 응답 시맨틱은 holding 포함 변경에 맞춰 재정의 대상이다(OQ-11).
- 응답은 요청 `view` 파라미터(`all` 기본 | `buyable` | `held`)로 base 집합을 고르고 envelope(`assets[]` + `available_slots`·`degraded` 등 meta)로 반환한다(OQ-11/OQ-12/OQ-6). 가용 슬롯이 0 이어도 보유 자산은 계속 반환되며 신규 매수 가용성은 `available_slots` meta 로 신호한다.
- 이해관계자
- 문서 작성자/구현자(AI agent)
- 리뷰어/승인자(운영자, genichin@gmail.com)
- 매수 전략 모듈(trex-core 외부 소비자) 및 portfolio-hub(외부 자산/포지션 서비스)
- 전략 모듈(매수/매도/position, trex-core 외부 소비자) 및 portfolio-hub(외부 자산/포지션 서비스)
- 제약사항
- 도메인 순수성 — `trex_core.domain` 은 portfolio-hub/네트워크 의존 0(read 어댑터 격리).
- 단일 통합 경계 — portfolio-hub 자격증명/계약 접점은 trex-core 어댑터(dcy-004 재사용)로 한정하며, 매수 전략 등 모든 소비자는 core 스냅샷 계약을 경유한다(portfolio-hub 직접 호출 금지, OQ-3).
@@ -54,10 +54,10 @@
사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.
- 전략 모듈이 자기 portfolio(전략 실행 대상 자산)를 trex-core 스냅샷 API 로 요청 → portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 받되, pending·warning·pause 상태 자산은 마스킹되어 빠진 리스트를 받는다.
- 모든 슬롯이 차 있어 더 살 수 없는 상황 → (holding 포함 변경으로 이 시맨틱 재정의 필요 — OQ-11) 신규 매수 가능 여부 신호 방식 확정 전까지 보유 자산은 계속 응답에 포함될 수 있다.
- 모든 슬롯이 차 있어 더 살 수 없는 상황 → 보유 자산은 계속 응답에 포함되고(`view=all`/`held`), 신규 매수 가능 여부는 `available_slots` meta 로 신호한다. `view=buyable` 은 `available_slots=0` 이면 `[]` 를 반환한다(OQ-11/OQ-12).
- 막 매수 주문되어 슬롯이 pending(미체결)인 종목 → 마스킹되어 전략 모듈 응답에서 빠져 중복 매수를 막고, 체결되어 holding 이 되면 보유 자산으로 응답에 포함된다.
- 스냅샷 페치 시 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 어긋난 자산 → `warning` 으로 표시되어 그 자산은 응답 리스트에서 마스킹되고 매매 전략이 수행되지 않는다.
- 전략 모듈이 자기 `strategy_id` 를 파라미터로 넣어 포트폴리오를 요청 → 그 전략 ID 가 등록된 자산만 받는다. 파라미터를 생략하면 전체 자산을 받는다(여러 전략 모듈이 하나의 스냅샷 API 를 공유하되 자기 책임 자산만 조회).
- 전략 모듈이 자기 `strategy_key` 를 파라미터로 넣어 포트폴리오를 요청 → 그 `strategy_key` 가 등록된 자산만 받는다. 파라미터를 생략하면 전체 자산을 받는다(여러 전략 모듈이 하나의 스냅샷 API 를 공유하되 자기 책임 자산만 조회).
- 운영자가 특정 자산을 런타임에 pause → 전략 모듈이 포트폴리오를 요청해도 그 자산은 응답에서 마스킹되어 매매가 멈추고, resume 하면 다시 응답에 포함된다.
# 4. 이번에 정의할 변경
@@ -67,18 +67,18 @@
- 일일 스냅샷 갱신: 설정 시각(기본 07:30 KST, .env)에 스케줄러(신규 daemon 컴포넌트)가 universe 를 페치해 스냅샷을 갱신한다. 현금/포지션은 baseline 에 더해, 체결 시 portfolio-hub ledger write 응답이 반환하는 변경 후 권위 cash/position 으로 갱신한다(옵션 D — 당초 옵션 C 의 core 로컬 체결 replay 보정을 대체).
- 포지션 정합 검증 + warning: 페치 시 portfolio-hub 포지션 vs 실제 브로커 계좌 포지션을 대조하고, 불일치 자산은 `warning` 상태로 응답 리스트에서 마스킹한다.
- 종목 스냅샷 read 계약: trex-core 가 보유 스냅샷을 read 계약(요청-응답, transport 는 dcy-006 RabbitMQ RPC)으로 제공하고, **holding 포함·pending·warning·pause 마스킹**한 전략 실행 대상 자산 리스트를 반환한다(slot overlay, OQ-4).
- 전략 ID 필터: 스냅샷 read 요청이 선택적 `strategy_id` 파라미터(요청 payload, dcy-006 OQ-2)를 받아, 주어지면 해당 전략 ID 가 등록된(= `active_strategy_id` 일치) 자산만 반환하고, 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. 전략 ID 의 식별자 형태("이름?")와 "등록" 판정 기준은 OQ-9.
- strategy_key 필터: 스냅샷 read 요청이 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2)를 받아, 주어지면 해당 `strategy_key` 가 등록된(= `active_strategy_id` 일치) 자산만 반환하고, 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. `strategy_key` 는 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 단일 `active_strategy_id` 정확 일치(OQ-9).
- 런타임 pause/resume: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있고, pause 된 항목은 전략 모듈의 포트폴리오 요청 결과에서 제외한다. pause 단위·제어 방식·영속성은 OQ-10.
- capacity 0 응답 시맨틱: 가용 슬롯이 0 일 때의 응답은 holding 포함 변경에 맞춰 재정의한다(OQ-11; 빈/표현 형식은 OQ-6).
- 응답 view 모드 + envelope: 응답 base 집합을 요청 `view`(`all` 기본|`buyable`|`held`)로 선택하고 envelope(`assets[]` + `view`·`available_slots`·`degraded`·`reason` meta)로 반환한다(OQ-11/OQ-12/OQ-6). capacity 0 이어도 보유 자산은 반환, `buyable` 만 `available_slots=0` 시 `[]`.
- baseline 갱신: interface-contract(read 포트/메시지 계약)·runtime-flows(스냅샷 조회 흐름, RabbitMQ RPC)·project-structure(read 어댑터) 갱신. (dcy-006 의 transport baseline 갱신과 정합.)
## 영향 범위
변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.
- 코드 영향:
- 신규: `trex_core/application` read 포트, 신규 `adapters/portfolio` read 어댑터 + stub, 스냅샷 read 를 dcy-006 messaging transport(RabbitMQ RPC, `adapters/messaging`)로 노출 + 응답 스키마(transport-무관), slot overlay(점유 종목 제외) + 현금/포지션 write-응답 갱신 로직(`cash_balance_after`/`asset_quantity_after`, 옵션 D), 일일 스냅샷 스케줄러(런타임 신규), 포지션 정합 검증 + `warning` 상태, `config` 에 `daemon_id` + 스냅샷 페치 시각 주입, `strategy_id` 필터(요청 payload → `active_strategy_id` 매칭), 스냅샷 항목 pause/resume 상태 저장 + 제어 표면(제어 메시지/CLI) + 응답 산출 시 pause 마스킹.
- 신규: `trex_core/application` read 포트, 신규 `adapters/portfolio` read 어댑터 + stub, 스냅샷 read 를 dcy-006 messaging transport(RabbitMQ RPC, `adapters/messaging`)로 노출 + 응답 스키마(transport-무관), slot overlay(점유 종목 제외) + 현금/포지션 write-응답 갱신 로직(`cash_balance_after`/`asset_quantity_after`, 옵션 D), 일일 스냅샷 스케줄러(런타임 신규), 포지션 정합 검증 + `warning` 상태, `config` 에 스냅샷 페치 시각 주입(`daemon_id` 는 portfolio-hub 토큰에서 도출 — 별도 config 불필요, OQ-7), `strategy_key` 필터(요청 payload → `active_strategy_id` 매칭), 스냅샷 항목 pause/resume 상태 저장(Postgres, portfolio_id 키, refresh 무관) + 제어 표면(CLI + dcy-006 RPC 제어 메시지) + 응답 산출 시 pause 마스킹(전역).
- (주의: dcy-006 가 `adapters/api` HTTP 어댑터를 제거하므로, 스냅샷 read 는 신규 HTTP 엔드포인트가 아니라 dcy-006 의 RPC consumer 로 구현한다.)
- 기존: capacity/slot 조회 재사용(점유 종목·가용 슬롯 판정), 브로커 계좌 포지션 조회(BrokerPort 잔고조회 — 정합 검증용) 재사용/확장.
- 기존: capacity/slot 조회 재사용(점유 종목·가용 슬롯 판정), 브로커 계좌 포지션 조회(BrokerPort 잔고조회 = KIS 주식잔고조회 `TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`, symbol 집계 — 정합 검증용, OQ-8) 재사용/확장.
- 문서 영향:
- interface-contract/runtime-flows/project-structure 갱신.
- 운영 영향:
@@ -88,22 +88,22 @@
실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.
## 기능 요구사항
- FR-1: trex-core 가 종목 스냅샷을 read 계약(요청-응답)으로 제공한다. transport 는 dcy-006 의 RabbitMQ request-reply RPC 이며(HTTP 아님), 매수 전략 모듈이 메시지로 소비한다. 스냅샷 단위는 daemon 이 관리하는 portfolio universe 이며, 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. (OQ-1 Answered)
- FR-2: 스냅샷 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 에 보유된 자산을 기본 포함하며, holding 슬롯의 자산도 포함**한다(전략 모듈이 보유 포지션까지 받아 관리). 단 다음 상태의 자산은 **마스킹**하여 응답에서 제외한다: (a) 슬롯 상태 pending(미체결 in-flight), (b) 자산 상태 warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 포함/마스킹 판정은 core 권위 슬롯 상태 + 정합 검증 + pause 상태를 사용하며 portfolio-hub 보유 여부보다 우선한다(slot overlay). 본 조항은 기본(default) view 의 구성 규칙이며, 응답 베이스 집합은 요청 view 모드 파라미터(OQ-11/OQ-12)로 선택될 수 있고 마스킹은 선택된 베이스 위에 적용된다. (변경 2026-06-05, OQ-4 결정: 종전 'pending·holding 점유 종목 제외, 매수 후보 리스트' → 'holding 포함, pending·warning·pause 만 마스킹'.)
- FR-3: 가용 슬롯이 0(capacity 소진)일 때의 응답은 **고정값이 아니라 전략 모듈이 요청 시 지정하는 view 모드 파라미터**에 따른다(OQ-11 결정). 최소 3개 모드: (a) 빈 리스트 `[]`, (b) 슬롯 점유 자산만, (c) portfolio 스냅샷 − 슬롯 점유 자산. 파라미터 이름/enum·기본 모드·마스킹(OQ-4)·strategy_id 필터(FR-8)와의 합성은 OQ-12, 빈/표현 형식은 OQ-6 에서 확정한다. (종전 'capacity 0 → 매수 후보 빈 리스트'는 OQ-4 holding 포함 + 본 결정으로 대체.)
- FR-4: portfolio-hub read 접근을 `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 기준) + 결정론 stub 로 제공하고, dcy-004 의 portfolio-hub 어댑터/자격증명을 재사용한다. 전략파라미터는 opaque JSON 으로 통과시킨다(trex-core 가 스키마를 해석하지 않음).
- FR-1: trex-core 가 종목 스냅샷을 read 계약(요청-응답)으로 제공한다. transport 는 dcy-006 의 RabbitMQ request-reply RPC 이며(HTTP 아님), 전략 모듈이 메시지로 소비한다. 스냅샷 단위는 daemon 이 관리하는 portfolio universe 이며, 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. (OQ-1 Answered)
- FR-2: 스냅샷 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 에 보유된 자산을 기본 포함하며, holding 슬롯의 자산도 포함**한다(전략 모듈이 보유 포지션까지 받아 관리). 단 다음 상태의 자산은 **마스킹**하여 응답에서 제외한다: (a) 슬롯 상태 pending(미체결 in-flight), (b) 자산 상태 warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 포함/마스킹 판정은 core 권위 슬롯 상태 + 정합 검증 + pause 상태를 사용하며 portfolio-hub 보유 여부보다 우선한다(slot overlay). 본 조항은 기본(default) view(`all`)의 구성 규칙이며, 응답 베이스 집합은 요청 `view` 파라미터(`all`/`buyable`/`held`, OQ-11/OQ-12)로 선택될 수 있고 마스킹은 선택된 베이스 위에 균일 적용된다. (변경 2026-06-05, OQ-4 결정: 종전 'pending·holding 점유 종목 제외, 매수 후보 리스트' → 'holding 포함, pending·warning·pause 만 마스킹'.)
- FR-3: 응답은 **envelope** 로 제공한다(자산은 항상 `assets: []` 배열, null 금지; meta: `view`·`available_slots`·`degraded`·`reason`) (OQ-6). 자산 base 집합은 요청 `view` 파라미터로 선택한다(OQ-11/OQ-12) — **`all`(기본, universe 전체) | `buyable`(universe − 슬롯 점유) | `held`(holding 슬롯만)**. 마스킹(pending·warning·pause)은 전 모드 균일 적용(OQ-4; pending 은 모든 모드 마스킹이라 `held`=holding 만). capacity 는 `available_slots` meta 로 신호하며 `buyable` 은 `available_slots=0` 이면 `[]`. (종전 'capacity 0 → 매수 후보 빈 리스트'는 OQ-4 holding 포함 + 본 결정으로 대체.)
- FR-4: portfolio-hub read 접근을 `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 기준) + 결정론 stub 로 제공하고, dcy-004 의 portfolio-hub 어댑터/자격증명을 재사용한다. `daemon_id` 는 portfolio-hub 자격증명/토큰에서 도출하며(OQ-7, 단일 진실원) 별도 config 값이 아니다(필요 시 whoami 1회 도출·캐시). 전략파라미터는 opaque JSON 으로 통과시킨다(trex-core 가 스키마를 해석하지 않음).
- FR-5: baseline 문서(interface-contract/runtime-flows/project-structure)를 read 포트·스냅샷 read 계약(RabbitMQ RPC)·read 어댑터·일일 스케줄러에 맞게 갱신한다(dcy-006 transport baseline 과 정합).
- FR-6: trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env 로 지정)에 일일 스케줄로 `daemon_id` universe 를 페치해 스냅샷을 보유한다. universe 멤버십·전략파라미터는 일일 스냅샷으로 고정하고, 현금/포지션은 스냅샷 baseline 에 더해 **체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값으로 갱신한다(옵션 D)**. (포지션/현금의 ground truth 는 portfolio-hub 이며 응답값으로 덮어써 로컬 누적 보정 drift 를 제거. portfolio-hub 가 ledger write 를 회계 단일 권위로 단일 트랜잭션 처리하고 `*_after` 를 반환하기로 확정 — A-7, portfolio-hub#8. portfolio-hub 배포(REL-003 등) 시점까지 구현 대기.)
- FR-7: 스냅샷 페치 시 각 portfolio 의 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 일치하는지 검증한다. 불일치하는 자산은 `warning` 상태로 표시하고 응답 리스트에서 마스킹해 해당 자산에 매매 전략을 수행하지 않는다.
- FR-8: 스냅샷 read 요청은 선택적 `strategy_id` 파라미터(요청 payload, dcy-006 OQ-2 매핑)를 받는다. `strategy_id` 가 주어지면 해당 전략 ID 가 등록된(= 자산의 `active_strategy_id` 와 일치) 자산만 응답 리스트에 포함하고, `strategy_id` 가 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. 여러 전략 모듈이 같은 스냅샷 API 를 공유하되 각자 자기 책임 자산만 받는다. 전략 ID 의 식별자 형태(ID vs 이름)와 "등록" 판정 기준(단일 `active_strategy_id` 일치 vs 다중 등록)은 OQ-9.
- FR-9: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있다. pause 된 항목은 전략 모듈의 포트폴리오 요청 결과(FR-2)에서 제외한다. 응답 리스트는 holding 포함을 기본으로 (pending 마스킹) ∧ (warning 마스킹) ∧ (pause 마스킹) ∧ (strategy_id 필터, 주어진 경우)의 합성으로 산출된다. pause 단위(portfolio/asset/symbol), 제어 방식(제어 API vs CLI vs config), 영속성(스냅샷 갱신·daemon 재시작 후 유지 여부), 전략별 vs 전역 적용은 OQ-10.
- FR-7: 스냅샷 페치 시 각 portfolio 의 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 **수량 정확 일치**하는지 검증한다(symbol 단위 집계, 허용 오차 없음 — OQ-8). 불일치 자산은 `warning` 상태로 표시·응답 리스트에서 마스킹하고 해당 자산에 매매 전략을 수행하지 않으며, **다음 정합 성공 시 자동 해제**한다(영속 hold 는 pause/FR-9). 브로커 포지션은 KIS 주식잔고조회(`TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`)를 BrokerPort 로 조회한다.
- FR-8: 스냅샷 read 요청은 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2 매핑)를 받는다. `strategy_key` 가 주어지면 해당 `strategy_key` 가 등록된(= 자산의 `active_strategy_id` 와 일치) 자산만 응답 리스트에 포함하고, `strategy_key` 가 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. 여러 전략 모듈이 같은 스냅샷 API 를 공유하되 각자 자기 책임 자산만 받는다. `strategy_key` 는 사람이 읽을 수 있는 안정 유일 slug(예: `target-value-v1`)이며 자산의 단일 `active_strategy_id` 와 대소문자 구분 정확 일치로 매칭한다(OQ-9; 다중 등록은 범위 밖). `strategy_key` 필터는 `view` 파라미터와 **직교 합성**된다(OQ-12).
- FR-9: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있다. pause 된 항목은 전략 모듈의 포트폴리오 요청 결과(FR-2)에서 제외한다. 응답 리스트는 holding 포함을 기본으로 (pending 마스킹) ∧ (warning 마스킹) ∧ (pause 마스킹) ∧ (strategy_key 필터, 주어진 경우)의 합성으로 산출된다. pause 단위는 `portfolio_id`(=asset_id), 제어는 **CLI + dcy-006 RPC 제어 메시지**, **Postgres 영속**(재시작·일일 refresh 후 유지), **전역 적용**, resume 시 다음 요청부터 즉시 복귀(OQ-10). pause(수동·영속) vs warning(자동·일시)은 독립 overlay·OR 합성.
## 비기능 요구사항
성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다.
- NFR-1: 도메인 순수성 유지 — read 포트는 application 계약, 어댑터는 adapters 레이어 격리. `trex_core/domain` 은 portfolio-hub/네트워크 의존 0(import 스윕).
- NFR-2: 일관성 — 점유 종목 제외/가용 슬롯 판정은 core 의 권위 슬롯 상태(강한 일관성)를 사용한다. 현금/포지션은 일일 baseline + 체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값 갱신으로 유지하고(옵션 D), 스냅샷 페치 시 브로커 계좌와 정합 검증한다(불일치 자산 `warning` 제외).
- NFR-3: 가용성 degrade — portfolio-hub 불가 시 스냅샷 read 의 동작(에러 vs 캐시 fallback vs 빈 응답)은 OQ-5 정책을 따른다.
- NFR-3: 가용성 degrade — portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 **명시적 degraded 신호(reason code + sanitize 된 message 포함) + 신규 전략 매매 보수적 중단**(OQ-5; 빈 리스트/silent stale 금지). core 자율 보호 매도는 유지(ledger 는 outbox 로 결국 기록). dcy-006 OQ-4 와 공통 '보수적 안전' 철학.
## 범위 경계
이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.
@@ -123,19 +123,19 @@
- 완화 방안(OQ-2 Answered, 옵션 D): 현금/포지션을 baseline + 체결 시 portfolio-hub ledger write 응답의 권위 cash/position 으로 갱신해 유지. daemon 이 유일 매매 주체이고 장중 입출금이 없다는 전제(A-6)로 정확. 추가로 페치 시 브로커 계좌 정합 검증(FR-7). 잔여 갭: 체결→write flush 사이에는 baseline 값이라 현금이 잠시 stale 할 수 있으나, 중복 매수 배제는 slot overlay(FR-2)가 즉시 담당.
- R-2 (Medium): in-flight race — dcy-004 ledger outbox 가 eventually-consistent 라, 막 체결한 종목이 portfolio-hub 포지션에 아직 반영되기 전 중복 노출될 수 있다.
- 완화 방안(OQ-4 Answered): pending 슬롯 마스킹으로 미체결 in-flight 종목을 응답에서 제외해 중복 노출을 차단한다. 체결 완료(holding)된 자산은 보유 자산으로 포함되며, 전략 모듈이 보유 상태를 보고 재매수하지 않는다(holding 자산의 보유 수량 표기 정합은 core 권위 슬롯 상태 기준 — 설계 시 확정). (종전 'pending·holding 동시 제외'에서 'pending 만 마스킹, holding 포함'으로 변경.)
- R-3 (Medium): 가용성 결합 — portfolio-hub 장애가 매수 전략 read 를 막는다.
- 완화 방안(OQ-5): 캐시 fallback 또는 명시적 degrade 응답 정책. 어댑터 격리(NFR-1).
- R-3 (Medium): 가용성 결합 — portfolio-hub 장애가 전략 read 를 막는다.
- 완화 방안(OQ-5 Answered): 권위 cash/position 유지 불가 시 명시적 degraded 신호로 신규 매매 보수적 중단(stale 매매·누적 drift 차단). core 보호 매도는 유지(outbox 로 ledger 결국 기록). 어댑터 격리(NFR-1).
- R-4 (Low): 책임 경계 확장 — core 가 portfolio-hub read gateway 로 비대해질 위험.
- 완화 방안: 모든 소비자가 core 를 경유하되(OQ-3 Answered), core 의 부가가치(slot overlay·현금/포지션 보정·정합 검증)로 범위를 한정하고 단순 패스스루 프록시는 지양한다.
- R-5 (Medium): 포지션 drift — portfolio-hub 가 실제 브로커 계좌와 어긋나면(과거 누락·정합 오류) 스냅샷 baseline 자체가 틀린다. (옵션 D 로 전환해 core 로컬 replay 누적 drift 는 제거되었으나, portfolio-hub ↔ 브로커 계좌 간 drift 는 여전히 남는다.)
- 완화 방안(FR-7): 스냅샷 페치 시 portfolio-hub 포지션 vs 브로커 계좌 포지션 정합 검증, 불일치 자산은 `warning` 으로 매매 제외(보수적 안전). 일치 기준·회복 규칙은 OQ-8.
- 완화 방안(FR-7, OQ-8 Answered): 스냅샷 페치 시 portfolio-hub 포지션 vs 브로커 계좌 포지션 **수량 정확 일치**(symbol 집계) 검증, 불일치 자산은 `warning` 으로 매매 제외(보수적 안전), 다음 정합 성공 시 자동 해제. 브로커 출처 = KIS 주식잔고조회.
## 가정
아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.
- A-1: portfolio-hub 는 daemon 인증(`auth_tokens.daemon_id`)으로 자신이 관리하는 portfolio 집합을 조회하는 read 계약을 제공한다. (근거: portfolio-hub `app/api/v1_portfolios.py`·`v1_user_info.py` 의 `auth.daemon_id` 스코프)
- A-2: trex-core 는 슬롯/capacity 의 권위 단일 소스이며, 점유 종목·가용 슬롯 판정에 자기 상태를 사용할 수 있다.
- A-3: 매수 전략 모듈은 trex-core 외부 소비자이며 dcy-006 transport(RabbitMQ request-reply RPC)로 스냅샷을 받는다(HTTP 아님). logical 계약(필드/마스킹/view 모드/strategy_id)은 transport-무관.
- A-3: 전략 모듈은 trex-core 외부 소비자이며 dcy-006 transport(RabbitMQ request-reply RPC)로 스냅샷을 받는다(HTTP 아님). logical 계약(필드/마스킹/view 모드/strategy_key)은 transport-무관.
- A-4: dcy-004 의 portfolio-hub 어댑터/자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 read 방향에서도 재사용한다.
- A-5: portfolio-hub 의 `daemon` 은 1급 엔티티이며 portfolio 가 `managed_by_daemon_id` 로 daemon 에 귀속된다(1 daemon : N portfolio, 각 portfolio ≈ 1 asset, asset_id == portfolio_id). 전략파라미터는 portfolio-hub 가 `strategy_params` JSONB 로 보관한다. trex-core daemon 의 universe = 자신이 관리하는 portfolio 집합이다. (근거: portfolio-hub `app/domain/models.py` Daemon/Portfolio, `strategy_params` JSONB migration)
- A-6: 이 trex-core daemon 은 자신이 관리하는 portfolio 의 유일한 매매 주체이며, 장중 거래 외 현금 변동(입출금)은 없다. 따라서 일일 baseline + 체결 시 portfolio-hub write 응답 갱신으로 현금/포지션을 정확히 유지할 수 있다(옵션 D 전제). (사용자 확인 2026-06-04)
@@ -161,39 +161,70 @@
- 결정: 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 보유 자산을 기본 포함하고 holding 슬롯 자산도 포함**한다. 다음 상태만 **마스킹**(제외): (a) 슬롯 pending(미체결 in-flight), (b) warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 우선순위: core 권위 슬롯 상태 + 정합 검증 + pause 가 portfolio-hub 보유 여부보다 우선(마스킹 조건 충족 시 보유 여부와 무관하게 제외). in-flight race(R-2)는 pending 마스킹으로 차단; 체결 완료분은 holding 으로 포함. (종전 'pending·holding 동시 제외' → 'holding 포함, pending·warning·pause 마스킹'으로 변경.)
- 반영 위치: `# 3` 시나리오, `# 5` FR-2·FR-9, `# 6` R-2, `# 7` S-2.
- 잔여: holding 자산의 보유 수량 표기를 core 권위 슬롯 vs 스냅샷 중 무엇 기준으로 할지(holding→ledger flush 갭의 수량 정합)는 설계 시 확정. 종전 FR-3(가용 슬롯 0 → 빈 리스트)은 'holding 포함' 변경과 충돌 → OQ-11 로 재정의.
- OQ-5: 가용성 degrade 정책 — portfolio-hub 불가 시 read 응답을 에러로 반환하는가, 캐시 fallback 하는가, 빈 응답을 반환하는가?
- 상태: Open
- 처리 방안: 운영자 결정. 매수 전략의 안전 기본값(보수적 매수 중단) 우선 검토.
- 종료 조건: degrade 동작이 NFR-3/R-3 에 반영되면 Answered.
- OQ-6: 빈 리스트 표현 시맨틱 — 응답 리스트가 비었을 때 빈 배열(`[]`)을 반환하는가, null 을 반환하는가, 별도 메타 필드(예: `available_slots: 0`)로 신호하는가?
- 상태: Open
- 처리 방안: 매수 전략 모듈 소비 계약에 맞춰 결정(메시지 응답 관례상 빈 배열 + 메타 필드 권장안 검토).
- 종료 조건: 표현 방식이 FR-3 에 반영되면 Answered.
- OQ-7: trex-core daemon 의 `daemon_id` 주입 방식 — .env 설정값으로 직접 주입하는가, portfolio-hub 인증 토큰(`auth_tokens.daemon_id`)에서 도출하는가?
- 상태: Open
- 처리 방안: 운영자 결정. dcy-004 자격증명 주입(.env `TREX_PORTFOLIO_HUB_*`) 패턴과 정합성 검토.
- 종료 조건: 주입 방식이 config/FR-4 에 반영되면 Answered.
- OQ-8: 포지션 정합 검증 세부 — 일치 판정 기준(정확 수량 일치 vs 허용 오차), `warning` 상태 회복 조건(다음 정합 성공 시 자동 해제 여부), 브로커 계좌 포지션 출처(KIS 잔고조회 재사용 범위).
- 상태: Open
- 처리 방안: 설계 시 확정. BrokerPort 잔고조회 계약 + `warning` 상태 저장/회복 규칙 정의.
- 종료 조건: 정합 기준·회복 규칙·포지션 출처가 FR-7 에 반영되면 Answered.
- OQ-9: 전략 ID 필터의 식별자/등록 시맨틱 — 전략 모듈이 넘기는 파라미터가 안정적 식별자(ID)인가 사람이 읽는 이름인가(사용자 표기 "이름?"), 그리고 "등록" 판정이 자산의 단일 `active_strategy_id` 일치인가 자산당 다중 전략 등록(여러 전략이 한 자산을 공유) 개념인가? portfolio-hub `strategy_params`/`active_strategy_id` 와의 매핑도 함께 확정한다.
- 상태: Open
- 처리 방안: 운영자/전략 모듈 계약 확인. 1차안은 안정적 `strategy_id` 식별자 + 단일 `active_strategy_id` 동등 비교. 다중 등록이 필요하면 등록 목록 필드 도입 검토.
- 종료 조건: 식별자 형태와 등록 판정 기준이 FR-8 에 반영되면 Answered.
- OQ-10: 런타임 pause/resume 메커니즘 — pause 단위(portfolio/asset/symbol), 제어 방식(전용 제어 메시지(dcy-006 RPC) vs CLI vs config 리로드), 영속성(스냅샷 일일 갱신·daemon 재시작 후 pause 유지 여부와 저장소), 적용 범위(전역 vs 특정 전략 요청에만), resume 시 즉시 응답 복귀 규칙.
- 상태: Open
- 처리 방안: 운영자 결정. 1차안은 자산(=portfolio_id) 단위 pause + 제어 API + Postgres 영속(재시작·일일 갱신 후 유지) + 전역 적용. 사용 시나리오(운영 중 특정 종목만 일시 정지)에 맞춰 확정.
- 종료 조건: pause 단위·제어 방식·영속성·적용 범위가 FR-9 에 반영되면 Answered.
- OQ-5: 가용성 degrade 정책 — portfolio-hub 불가 시 스냅샷 read 동작.
- 상태: Answered (2026-06-05)
- 결정: portfolio-hub 가 불가해 **권위 cash/position 을 유지할 수 없는 상태**(옵션 D 의 ledger write `*_after` 갱신 불가, 또는 일일 universe fetch 실패)면 **신규 전략 매매를 보수적으로 중단**한다. 차단 사유는 'ledger 기록 불가'가 아니라(dcy-004 outbox 가 유실 없이 결국 기록) **stale cash/position 으로 매매 시 잘못된 사이징·누적 drift 위험**이다.
- read 응답: 빈 리스트(`[]` — '후보 없음'과 모호)나 silent stale 캐시 서빙 대신 **명시적 degraded 신호**를 실어 전략이 신규 매매를 보류하게 한다. 신호는 `degraded=true` + **degraded 사유**를 포함한다 — machine-readable reason code(예: `portfolio_hub_unavailable` / `snapshot_fetch_failed` / `cash_position_stale`)로 전략이 원인별 분기 가능하게 하고, 사람용 message 로 운영 진단을 돕는다. 단 raw 예외/자격증명 등 민감정보는 노출하지 않고 sanitize 한다.
- core 자율 보호 매도(손절·시간 만기, dcy-006 A-5)는 **계속 동작** — 그 체결의 ledger 도 outbox 가 복구 후 기록하므로 outage 중에도 열린 포지션은 보호된다. 즉 '중단'은 신규 전략 매매에 한정한다.
- dcy-006 OQ-4(RabbitMQ degrade)와 **공통 '보수적 안전' 철학**: 임의의 핵심 의존성(portfolio-hub OR RabbitMQ) degrade 시 신규 매매 중단 + core 보호 매도 유지.
- 반영 위치: `# 5` NFR-3, `# 6` R-3.
- 종료 조건(충족): degrade 동작이 NFR-3/R-3 에 반영됨.
- OQ-6: 빈 리스트 표현 시맨틱 — 응답이 비었을 때의 표현.
- 상태: Answered (2026-06-05)
- 결정: 응답은 **envelope** 로 감싸고 자산 목록은 항상 배열(`assets: []`, **null 금지**)로 준다. 빈 배열은 '항목 없음'을 그대로 의미하고, '왜 비었는지'는 envelope 의 meta 로 구분한다 — `view`(에코된 view 모드, OQ-11/12), `available_slots`(capacity 신호), `degraded`+`reason`(OQ-5). 근거: 리스트 필드에 null 을 섞으면 소비자 분기·버그 유발 → 항상 배열. 빈 리스트만으로는 capacity 0 / 전부 마스킹 / 필터 0매칭 / degrade 를 구분 못 하므로 envelope 필요(OQ-5 가 이미 `degraded`/`reason` 을 요구해 envelope 는 사실상 강제). transport 가 메시지 payload(dcy-006 RPC)라 REST status-code 가 아니라 구조화 envelope 가 자연스럽다.
- 반영 위치: `# 5` FR-3 (자산 항목(FR-1)·마스킹(FR-2)은 envelope 의 `assets[]` 원소로 위치).
- 종료 조건(충족): 표현 방식(envelope + `[]`)이 FR-3 에 반영됨. 정확한 envelope 필드 스키마는 OQ-12 view 파라미터와 함께 REQ/설계 단계 확정.
- OQ-7: trex-core daemon 의 `daemon_id` 출처.
- 상태: Answered (2026-06-05)
- 결정: `daemon_id` 는 **portfolio-hub 자격증명/인증 토큰(`auth_tokens.daemon_id`)에서 도출**한다(단일 진실원). 별도 `TREX_DAEMON_ID` .env 설정값을 1차 출처로 두지 않는다 — 자격증명과 분리 설정 시 dual-source drift(잘못된 universe·auth 오류) 위험. portfolio-hub read 는 토큰으로 daemon 을 스코프하므로(A-1) trex-core 는 daemon_id 를 명시적으로 들 필요가 가급적 없고, 값이 필요하면 startup 때 whoami(portfolio-hub `v1_user_info` 류) 1회로 도출·캐시한다. .env override 는 테스트/stub 한정이며 startup 에서 토큰 daemon_id 와 일치 검증(불일치 시 fail-closed)한다. dcy-004 의 `TREX_PORTFOLIO_HUB_*` 자격증명을 재사용하므로 신규 `TREX_DAEMON_ID` 주입은 불필요.
- 반영 위치: `# 4` 영향 범위(config), `# 5` FR-4.
- 종료 조건(충족): daemon_id 출처가 FR-4/영향 범위에 반영됨.
- OQ-8: 포지션 정합 검증 세부.
- 상태: Answered (2026-06-05)
- 결정:
1. **일치 기준 = 수량 정확 일치**(정수 shares, 허용 오차 없음). A-6(유일 매매 주체·장중 외부 변동 없음)상 portfolio-hub 포지션과 브로커 잔고는 동일 체결에서 파생되어 정확히 일치해야 하며, tolerance 는 실제 이상(누락 체결·외부 거래)을 가린다. 현금은 FR-7 범위 밖(수수료·세금·T+2 결제로 단순 비교 불가) — 수량만 대조.
2. **warning 회복 = 다음 정합 성공 시 자동 해제**(매 정합 사이클 재계산되는 파생 플래그, sticky 아님). raise 는 매번 로깅/alert. 영속 hold 가 필요하면 운영자 pause(FR-9)로 분리한다(자동 warning=일시 / 수동 pause=영속).
3. **브로커 포지션 출처 = KIS 주식잔고조회**(실전 `TTTC8434R` / 모의 `VTTC8434R`, `GET /uapi/domestic-stock/v1/trading/inquire-balance`)를 BrokerPort 잔고조회로 재사용. 항목당 `pdno`(종목코드) + `hldg_qty`(보유수량). paper broker 는 결정론 stub.
- **symbol 단위 집계 대조**: 브로커는 단일 계좌(dcy-003 불변식 B)라 잔고가 symbol 단위 집계 → portfolio-hub per-portfolio 포지션을 symbol 로 합산해 broker per-symbol `hldg_qty` 와 대조(보통 1 portfolio:1 symbol 이나 안전하게 집계 기준).
- 주의: 당일 전량매도분이 `hldg_qty=0` 으로 D-2 까지 잔고에 남을 수 있음(015 문서) → 0 수량 잔고 처리 규칙은 설계 시 확정.
- 반영 위치: `# 4` 영향 범위, `# 5` FR-7, `# 6` R-5.
- 참고: docs/broker/kis/rest/국내주식/주문/계좌/015_v1_국내주식-006_주식잔고조회.md
- 종료 조건(충족): 정합 기준·회복 규칙·포지션 출처가 FR-7 에 반영됨.
- OQ-9: 전략 ID 필터의 식별자/등록 시맨틱.
- 상태: Answered (2026-06-05)
- 결정:
- **식별자 형태**: 전략 모듈이 넘기는 `strategy_key` 는 **사람이 읽을 수 있으면서 ID 역할을 하는 유일·안정 값**(human-readable stable slug, 예: `target-value-v1`)이다. 'display name vs opaque ID' 이원화 없이 이 slug 자체가 유일 키다. 매칭은 대소문자 구분 정확 일치(exact).
- **명명(2026-06-05)**: 파라미터명을 `strategy_id` → **`strategy_key`** 로 변경한다. portfolio-hub 의 `strategy_id`(그쪽 내부 전략 엔티티 ID)와 이름이 겹쳐 혼동되므로 분리한 것이다. `strategy_key` 는 trex-core 요청 파라미터(전략 모듈 자기 식별자)이며, 그 **값은 자산의 portfolio-hub `active_strategy_id` 와 매칭**된다.
- **등록 판정**: 자산의 **단일 `active_strategy_id` 와 동등 비교**(`strategy_key == active_strategy_id`). 한 자산은 한 시점에 하나의 active 전략에 귀속한다(portfolio-hub `active_strategy_id` 단일, A-5). 자산당 다중 전략 등록(예: buy/sell 분리, trex-core#1)은 **현재 범위 밖**이며 필요해지면 '등록 목록' 필드로 확장(후속).
- 반영 위치: `# 5` FR-8, `# 3` 시나리오.
- 종료 조건(충족): 식별자 형태·등록 판정이 FR-8 에 반영됨.
- OQ-10: 런타임 pause/resume 메커니즘.
- 상태: Answered (2026-06-05)
- 결정:
- **단위**: `portfolio_id`(= asset_id) — universe 항목 키. (symbol 은 다중 portfolio 공유 시 모호하므로 portfolio_id 기준.)
- **제어 방식**: 운영자 admin 액션으로 **CLI + dcy-006 RPC 제어 메시지** 둘 다 제공(로컬 CLI + 원격 ops). config 리로드는 비채택.
- **영속성**: **Postgres 영속**(기존 단일 store), portfolio_id 키. daemon 재시작 후 유지, 일일 스냅샷 refresh 와 무관(refresh 는 universe/포지션만 갱신, pause overlay 불변).
- **적용 범위**: **전역** — 모든 전략 요청에서 마스킹(운영자 안전 액션, 어느 전략이 묻든 거래 금지).
- **resume**: pause 는 request-time mask 라 resume 시 **다음 요청부터 즉시 응답 복귀**(일일 갱신 대기 불요).
- pause(수동·영속) vs warning(자동·일시, OQ-8)은 독립 overlay 이며 OR 합성(둘 중 하나라도면 마스킹).
- 반영 위치: `# 4` 영향 범위, `# 5` FR-9.
- 종료 조건(충족): pause 단위·제어 방식·영속성·적용 범위가 FR-9 에 반영됨.
- OQ-11: capacity 소진 시 응답 시맨틱.
- 상태: Answered (2026-06-05) — 방향 확정, 파라미터 설계는 OQ-12 로 분리.
- 결정: capacity(가용 슬롯)가 모두 소진됐을 때의 응답은 **고정값이 아니라 전략 모듈이 요청 시 지정하는 view 모드(파라미터)에 따라 달라진다.** 최소 3개 모드: (a) **빈 리스트 `[]`** (신규 매수 후보가 목적인 전략 — 살 게 없음), (b) **슬롯 점유 자산만** (보유 포지션 관리·매도/조절 전략 — 자기가 들고 있는 것만), (c) **portfolio 스냅샷 − 슬롯 점유 자산** (점유 안 된 잔여 universe). 같은 view 모드 개념은 capacity 0 이 아닐 때도 응답 구성을 좌우한다(이 파라미터는 strategy_id 필터(FR-8)와 별개의 두 번째 요청 파라미터).
- 결정: capacity(가용 슬롯)가 모두 소진됐을 때의 응답은 **고정값이 아니라 전략 모듈이 요청 시 지정하는 view 모드(파라미터)에 따라 달라진다.** 최소 3개 모드: (a) **빈 리스트 `[]`** (신규 매수 후보가 목적인 전략 — 살 게 없음), (b) **슬롯 점유 자산만** (보유 포지션 관리·매도/조절 전략 — 자기가 들고 있는 것만), (c) **portfolio 스냅샷 − 슬롯 점유 자산** (점유 안 된 잔여 universe). 같은 view 모드 개념은 capacity 0 이 아닐 때도 응답 구성을 좌우한다(이 파라미터는 strategy_key 필터(FR-8)와 별개의 두 번째 요청 파라미터). (→ OQ-12 에서 enum `all`/`buyable`/`held` 으로 확정.)
- 반영 위치: `# 5` FR-2·FR-3, `# 6` OQ-12.
- 잔여(→ OQ-12): 파라미터 이름/enum 값, 기본 모드(생략 시), 각 모드와 마스킹(pending·warning·pause, OQ-4)·strategy_id 필터의 합성 규칙, capacity 신호 메타 필드(OQ-6) 동반 여부.
- OQ-12: 응답 view/scope 파라미터 설계 — OQ-11 의 모드 선택을 어떤 요청 파라미터로 노출하는가.
- 상태: Open
- 처리 방안: 전략 모듈 소비 계약에 맞춰 결정. 정의 대상: (1) 요청 파라미터 이름·enum(예: `view` = buyable|held|remainder|all, 요청 payload — dcy-006 OQ-2 매핑), (2) 생략 시 기본 모드, (3) 각 모드 베이스 집합에 OQ-4 마스킹(pending·warning·pause)과 FR-8 strategy_id 필터를 어떻게 합성하는지(특히 'held' 모드에서 pending 포함 여부), (4) capacity 가용성 메타 필드(OQ-6) 동반 여부. 매수/매도/position 전략 구분(trex-core#1)과도 연계 — view 모드가 전략 타입별 뷰를 제공.
- 종료 조건: 파라미터 스펙·기본 모드·합성 규칙이 FR-2/FR-3/FR-8 에 반영되면 Answered.
- 잔여(→ OQ-12): 파라미터 이름/enum 값, 기본 모드(생략 시), 각 모드와 마스킹(pending·warning·pause, OQ-4)·strategy_key 필터의 합성 규칙, capacity 신호 메타 필드(OQ-6) 동반 여부.
- OQ-12: 응답 view/scope 파라미터 설계.
- 상태: Answered (2026-06-05)
- 결정:
- **파라미터**: 요청 payload `view`(dcy-006 OQ-2 매핑), enum = **`all` | `buyable` | `held`**. base 집합 — `all` = universe 전체(portfolio-hub 보유 + holding 슬롯), `buyable` = universe − 슬롯 점유(미점유 신규 진입 후보), `held` = holding 슬롯 자산만. (1차안의 `remainder` 는 `buyable` 과 동일이라 drop.)
- **기본 모드**: 생략 시 **`all`**(OQ-4/FR-2 holding 포함·FR-8 전체와 정합).
- **마스킹 직교·균일**: 모드는 base 집합만 고르고, 마스킹(pending·warning·pause)은 전 모드 균일 적용. **pending 은 모든 모드 마스킹** → `held` = holding 만(미체결 제외). warning/pause 도 전 모드 마스킹.
- **strategy_key 직교**: `view` 와 `strategy_key`(FR-8)는 독립 합성 — `assets = base(view) − mask(pending·warning·pause) − (strategy_key 필터, 주어지면)`.
- **capacity**: envelope 에 `available_slots` 항상 포함(OQ-6). `buyable` 은 capacity-gated — `available_slots=0` 이면 `[]`(OQ-11 정합). `held` 엔 정보성.
- **전략 타입 매핑(trex-core#1)**: `buyable`=매수, `held`=position/매도, `all`=통합.
- 반영 위치: `# 5` FR-2·FR-3·FR-8.
- 종료 조건(충족): 파라미터 스펙·기본 모드·합성 규칙이 FR-2/FR-3/FR-8 에 반영됨.
### 오픈 질문 처리 기준
- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.
@@ -209,12 +240,16 @@
- S-1: stub portfolio-hub read 로, 스냅샷 API 가 portfolio-hub 종목을 포함한 전략 실행 대상 자산 리스트를 반환한다.
- S-2: holding 슬롯 자산은 응답에 포함되고, pending 슬롯 자산은 마스킹되어 제외된다(slot overlay).
- S-3: (재정의 대기 — OQ-11) 가용 슬롯 0 일 때의 응답 시맨틱(holding 포함 여부·신규 매수 불가 신호)이 확정된 대로 동작한다.
- S-3: 가용 슬롯 0 일 때 `view=all`/`held` 는 보유 자산을 계속 반환하고 `view=buyable` 은 `[]` 를 반환하며, `available_slots=0` meta 가 신호된다(OQ-11/OQ-12).
- S-4: 도메인 import 스윕에서 portfolio-hub/네트워크 의존이 0 으로 유지된다.
- S-5: interface-contract/runtime-flows/project-structure 가 read 레이어와 정합한다.
- S-6: 스냅샷 페치 시 portfolio-hub 포지션과 브로커 계좌 포지션이 불일치하는 자산이 `warning` 으로 표시되고 응답 리스트에서 마스킹된다.
- S-7: `strategy_id` 를 넣어 호출하면 해당 전략 ID 가 등록된 자산만, 생략하면 전체 자산이 반환된다.
- S-7: `strategy_key` 를 넣어 호출하면 해당 `strategy_key` 가 등록된 자산만, 생략하면 전체 자산이 반환된다.
- S-8: 자산을 런타임에 pause 하면 응답 리스트에서 마스킹되고, resume 하면 다시 포함된다.
- S-9: portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 응답에 `degraded=true` + reason code 가 실려 신규 매매 보류를 신호한다(OQ-5).
- S-10: 요청 `view`(`all`/`buyable`/`held`)별로 base 집합이 다르고 마스킹(pending·warning·pause)은 전 모드 균일 적용된다(OQ-12).
- S-11: 응답이 `assets`(항상 배열, 비면 `[]`·null 아님) + meta(`view`·`available_slots`·`degraded`·`reason`) envelope 형태다(OQ-6).
- S-12: 별도 `TREX_DAEMON_ID` 없이 portfolio-hub 토큰에서 도출한 daemon_id 로 universe 를 조회한다(OQ-7).
## 측정 방식 및 데이터 출처
각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다.
@@ -225,8 +260,12 @@
- S-4 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록.
- S-5 측정 방식: 문서-코드 정합 점검(리뷰) / 데이터 출처: baseline 문서 vs 실제 메시지 계약(RPC)/read 포트.
- S-6 측정 방식: 포지션 불일치 주입 후 스냅샷 페치 테스트 / 데이터 출처: 자산 `warning` 상태·응답 리스트.
- S-7 측정 방식: `strategy_id` 유/무 호출 비교 테스트 / 데이터 출처: 응답 리스트 vs 자산 `active_strategy_id`.
- S-7 측정 방식: `strategy_key` 유/무 호출 비교 테스트 / 데이터 출처: 응답 리스트 vs 자산 `active_strategy_id`.
- S-8 측정 방식: pause→호출→resume→호출 시퀀스 테스트 / 데이터 출처: 응답 리스트 포함/제외 여부.
- S-9 측정 방식: portfolio-hub 불가(권위 유지 불가) 주입 후 호출 테스트 / 데이터 출처: 응답 `degraded`·`reason` 필드.
- S-10 측정 방식: `view` 값별 호출 비교 테스트 / 데이터 출처: 응답 base 집합 vs 슬롯 상태·마스킹.
- S-11 측정 방식: 응답 스키마 검증(빈 응답 포함) / 데이터 출처: 응답 envelope 구조(`assets` 타입·meta 필드).
- S-12 측정 방식: `TREX_DAEMON_ID` 미설정 기동 후 universe 조회 테스트 / 데이터 출처: 토큰 도출 daemon_id vs 조회된 portfolio 집합.
# 8. REQ로 넘기기 전 확인 체크
REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.
@@ -234,11 +273,11 @@ REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지
- [ ] 범위(In Scope/Out of Scope) 확정 — 특히 전략 의사결정 로직·reconciliation 제외
- 기준: 운영자가 read 슬라이스 경계를 확인했다.
- [ ] 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7).
- 기준: S-1~S-12 측정 방식·출처 명시(본 문서 # 7).
- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: 본 Discovery 의 리스크는 현재 전부 Medium/Low. 신선도(R-1)·race(R-2)·가용성(R-3) 처리 방침을 운영자가 확인.
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ-1~OQ-12 이 전부 Answered/Deferred 처리(현재 OQ-1·OQ-2·OQ-3·OQ-4·OQ-11 Answered, OQ-5~OQ-10·OQ-12 Open).
- 기준: OQ-1~OQ-12 이 전부 Answered/Deferred 처리(현재 OQ-1~OQ-12 전부 Answered).
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다.
@@ -259,18 +298,18 @@ REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지
## DECIDE
- ~~스냅샷 응답 단위/필드~~ → 결정됨: daemon 관리 portfolio universe(symbol/asset_id/현금/포지션/active_strategy_id/전략파라미터 opaque JSON), 가격 제외 (OQ-1, 2026-06-04)
- ~~신선도 정책~~ → 결정됨: universe·전략파라미터 설정 시각 일일 캐시 + 현금/포지션 baseline + 체결 시 portfolio-hub write 응답의 권위 cash/position 갱신(옵션 D) (OQ-2, 2026-06-04 / 옵션 C→D 개정 2026-06-05)
- {{DECIDE_DAEMON_ID_INJECTION}} — trex-core daemon 의 daemon_id 주입(.env vs 인증 토큰) (OQ-7)
- {{DECIDE_EMPTY_SEMANTICS}} — 가용 슬롯 0 시 빈 배열 vs null vs 메타 필드 표현 (OQ-6)
- ~~daemon_id 주입~~ → 결정됨: portfolio-hub 자격증명/토큰에서 도출(단일 진실원), 별도 `TREX_DAEMON_ID` 불필요; .env override 는 테스트 한정 + 토큰 일치 fail-closed (OQ-7, 2026-06-05)
- ~~빈 표현 시맨틱~~ → 결정됨: envelope + `assets: []`(null 금지), meta(`view`·`available_slots`·`degraded`·`reason`)로 의미 분리 (OQ-6, 2026-06-05)
- ~~capacity 0 시 응답 재정의~~ → 결정됨: 응답 형태를 전략 모듈 요청 view 모드 파라미터로 선택(빈 `[]` / 슬롯 점유 자산만 / portfolio−점유) (OQ-11, 2026-06-05)
- {{DECIDE_VIEW_PARAM}} — 응답 view/scope 파라미터 설계: 이름·enum·기본 모드·마스킹/strategy_id 합성·capacity 메타 (OQ-12)
- {{DECIDE_STRATEGY_ID_SEMANTICS}} — 전략 ID 파라미터의 식별자 형태(ID vs 이름)와 등록 판정(단일 active vs 다중 등록) (OQ-9)
- {{DECIDE_PAUSE_MECHANISM}} — pause/resume 단위·제어 방식(API/CLI/config)·영속성·적용 범위 (OQ-10)
- ~~응답 view/scope 파라미터~~ → 결정됨: `view`=`all`(기본)|`buyable`|`held`, strategy_key 직교 합성, 마스킹 전 모드 균일(`held`=holding만), `available_slots` 메타(buyable은 0이면 `[]`) (OQ-12, 2026-06-05)
- ~~전략 ID 시맨틱~~ → 결정됨: 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 자산의 단일 `active_strategy_id` 와 정확 일치; 다중 등록은 범위 밖 (OQ-9, 2026-06-05)
- ~~pause/resume 메커니즘~~ → 결정됨: portfolio_id 단위, CLI + dcy-006 RPC 제어, Postgres 영속(refresh 무관), 전역, 즉시 복귀 (OQ-10, 2026-06-05)
## CONFIRM
- {{CONFIRM_READ_SCOPE}} — read 슬라이스 범위 경계(전략 로직·reconciliation 제외) 확정
- ~~core 경유 정책~~ → 결정됨: 모든 소비자 trex-core 경유, portfolio-hub 직접 호출 금지 (OQ-3, 2026-06-04)
- ~~in-flight 합성/제외 규칙~~ → 결정됨: holding 포함, pending·warning·pause 마스킹, core 권위 우선 (OQ-4, 2026-06-05)
- {{CONFIRM_AVAILABILITY_DEGRADE}} — portfolio-hub 불가 시 가용성 degrade 정책 (OQ-5)
- ~~가용성 degrade 정책~~ → 결정됨: 권위 cash/position 유지 불가 시 명시적 degraded 신호 + 신규 매매 보수적 중단, core 보호 매도 유지 (OQ-5, 2026-06-05). dcy-006 OQ-4 와 공통 보수적 안전.
## DATA
- {{DATA_PORTFOLIO_HUB_READ_CONTRACT}} — portfolio-hub read API 계약(`managed_by_daemon_id` 로 daemon universe 조회) 및 응답 필드(cash_balance/asset_quantity/strategy_params JSONB) 확인
@@ -288,7 +327,7 @@ REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록
## Freeze 플래그
- Handoff Decision: 보류
- Handoff Rationale: OQ-1(스냅샷 정의)·OQ-2(신선도, 옵션 D — portfolio-hub#8 write 응답 `*_after` 확정)·OQ-3(core 경유 정책)·OQ-4(in-flight 합성 — holding 포함, pending·warning·pause 마스킹)·OQ-11(capacity 0 응답 — view 모드 파라미터) Answered. OQ-5~OQ-10·OQ-12(가용성 degrade·빈 표현 시맨틱·daemon_id 주입·포지션 정합 세부·전략 ID 필터 시맨틱·런타임 pause/resume·응답 view 파라미터 설계)이 미확정이며, confirm-discovery 에서 운영자 결정 후 REQ Drafting 으로 인계한다.
- Handoff Rationale: OQ-1(스냅샷 정의)·OQ-2(신선도, 옵션 D — portfolio-hub#8 write 응답 `*_after` 확정)·OQ-3(core 경유 정책)·OQ-4(in-flight 합성 — holding 포함, pending·warning·pause 마스킹)·OQ-5(가용성 degrade — 권위 cash/position 유지 불가 시 degraded 신호+보수적 중단)·OQ-6(빈 표현 — envelope + `assets: []`/meta)·OQ-7(daemon_id — 토큰 도출 단일 진실원)·OQ-8(정합 — 수량 정확 일치·symbol 집계·자동 해제·KIS 잔고조회)·OQ-9(전략 ID — 사람이 읽는 안정 slug·단일 active 일치)·OQ-10(pause — portfolio_id·CLI+MQ·Postgres 영속·전역·즉시 복귀)·OQ-11(capacity 0 응답 — view 모드 파라미터)·OQ-12(view 파라미터 — `all`/`buyable`/`held` + envelope) **전부 Answered**. 남은 인계 전 항목은 범위 경계 확인(운영자)과 CONFIRMED_BY 지정이며, confirm-discovery 에서 확정 후 REQ Drafting 으로 인계한다.
- Ready for REQ Drafting: false
- Confirmed By: TBD
- Confirmed At (KST): TBD
@@ -5,10 +5,10 @@
- 문서 ID: dcy-006_20260605_strategy-module-rabbitmq-transport
- 이슈명: trex-core ↔ 전략 모듈 통신 transport — RabbitMQ 도입 (스냅샷 read + intent 제출 + 이벤트)
- 작성 시각(KST): 2026-06-05 21:23
- 마지막 갱신 시각(KST): 2026-06-05 22:30
- 마지막 갱신 시각(KST): 2026-06-05 22:45
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음
- 생성된 REQ 참조: REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026
# 1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.