diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md index 490b7e0..1845ef0 100644 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md +++ b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md @@ -27,7 +27,7 @@ 2. **masking 합성 순서**: `pending(slot 상태) OR warning(정합 실패) OR pause(수동)` → 셋 중 하나라도 true 면 마스킹. 적용은 view 집합 산출 후 균일 적용. 우선순위: 슬롯 overlay > portfolio-hub 보유 여부. -3. **daemon_id 도출**: `PortfolioHubReadPort` 초기화 시 portfolio-hub `GET /v1/auth/whoami` 1회 호출 → `daemon_id` 추출·캐시. `TREX_PORTFOLIO_HUB_TOKEN` 자격증명 재사용(REQ-027 A-1). +3. **daemon_id 도출**: `PortfolioHubReadPort` 초기화 시 portfolio-hub `GET /v1/user_info` 1회 호출 → 응답 `daemon_id` 추출·캐시. `TREX_PORTFOLIO_HUB_TOKEN` 자격증명 재사용(REQ-027 A-1). (정정: REQ-027 CHG-04 — `/v1/auth/whoami` 부재로 `/v1/user_info` 로 변경) 4. **strategy_key 필터**: 요청 payload 의 `strategy_key`(str) 를 자산의 `active_strategy_id` 와 **대소문자 구분 정확 일치**(`==`)로 필터링. 생략 시 필터 없음. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md index 6500e26..0040d63 100644 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md +++ b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md @@ -5,11 +5,11 @@ ## Acceptance Mapping -- REQ-027: - - AC-1: stub + HTTP MockTransport 통합테스트에서 `PortfolioItem` 항목당 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 필드 + `price` 부재 확인. +- REQ-027 (계약 정정 CHG-04 반영): + - AC-1: HTTP MockTransport 통합테스트에서 `{portfolios:[…]}` envelope 파싱 + `managed_by_daemon_id` 클라이언트측 필터 + `PortfolioItem` 항목 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 필드 + `price` 부재 확인. 실 list 응답에 전략 필드 없으면 None/`{}` 기본값. - AC-2: 결정론 stub 가 네트워크 없이 고정 universe 반환 확인. - AC-3: 도메인 import 스윕(`tests/domain/test_domain_import_sweep.py`)이 `pika`/`httpx`/`adapters.*` 0 import 회귀 가드. - - AC-4: HTTP MockTransport 가 `/v1/auth/whoami` 호출 + Bearer 토큰 헤더 + `daemon_id` 도출 확인. `TREX_DAEMON_ID` env 1차 출처 없음(코드 review). + - AC-4: HTTP MockTransport 가 `/v1/user_info` 호출 + Bearer 토큰 헤더 + `daemon_id` 도출 확인. 스킴 없는 base URL → `http://` 기본 적용. `TREX_DAEMON_ID` env 1차 출처 없음(코드 review). - REQ-028: - AC-1: envelope 응답이 정확히 `{assets, view, available_slots, degraded, reason, message}` 키 집합 + `assets` 가 항상 list(null 금지) 확인. @@ -48,10 +48,10 @@ ### Test - 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" TREX_POSTGRESQL_TEST_DSN=$(grep TREX_POSTGRESQL_DSN .env | cut -d= -f2-) .venv/bin/python -m pytest -q` - 결과: `185 passed, 1 skipped` (KIS sandbox token rate-limit 1건만 skip; 그 외 PG/RabbitMQ 게이트 모두 통과). -- REQ-027 AC-1: `tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_scoped_by_managed_by_daemon_id` + `test_stub_read.py::test_stub_returns_fixed_universe_without_network`. +- REQ-027 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::test_stub_returns_fixed_universe_without_network`. - REQ-027 AC-2: `tests/adapters/portfolio/test_stub_read.py::test_stub_returns_fixed_universe_without_network` + `test_stub_items_are_mutable_for_tests`. - REQ-027 AC-3: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` (BAT-007 부터 유지, `httpx`/`pika`/`adapters.*` 금지 prefix 회귀 가드). -- REQ-027 AC-4: `tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id` (URL/Bearer 헤더/응답 디코드 + daemon_id 추출). +- REQ-027 AC-4: `tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id` (`/v1/user_info` URL/Bearer 헤더/응답 디코드 + daemon_id 추출) + `::test_base_url_without_scheme_defaults_to_http`. - REQ-028 AC-1: `tests/application/test_snapshot_handler.py::test_envelope_shape_returned` + `tests/application/test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null`. - REQ-028 AC-2: `tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials` + `tests/application/test_snapshot_selection.py::test_degraded_signal_in_envelope`. 실 daemon 운영 검증에서 portfolio-hub 부재 환경에서 같은 응답 발생 확인. - REQ-028 AC-3: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`. @@ -89,6 +89,15 @@ - `.venv/bin/ruff check trex_core tests` → `All checks passed!`. - 실 RabbitMQ + PostgreSQL evidence: `127.0.0.1:5672` admin/admin + 로컬 PG 로 daemon 기동 후 snapshot RPC (view=all/buyable/held + strategy_key), pause/resume RPC, 응답 envelope 키/필드 검증 모두 통과. portfolio-hub 부재 환경에서 `degraded=true reason=portfolio_hub_unavailable` 정상 노출. +### Re-verification Addendum (2026-06-06, REQ-027 CHG-04 — release 선행 정리) +- 사유: release-candidate 확정 이후 실 portfolio-hub API 실측으로 read 계약 불일치 발견 → 어댑터/테스트 정정(commit 43bad24). 본 batch verification 을 정정 계약 기준으로 재확인. +- 정정 요지: whoami `/v1/auth/whoami`→`/v1/user_info`; list envelope `{items}`→`{portfolios,total}`; 토큰 스코프 → 클라이언트측 `managed_by_daemon_id` 필터; base URL 스킴 누락 시 `http://` 보정; 실 `PortfolioItem` 전략 필드 부재 → None/`{}`(이월). +- 재실행 결과: + - `.venv/bin/python -m pytest tests/adapters/portfolio/test_hub_read.py -q` → `6 passed`. + - `.venv/bin/python -m pytest -q` (infra env 미설정 invocation) → `165 passed, 23 skipped` (PG/RabbitMQ/KIS 게이트 skip). `ruff check trex_core tests` → `All checks passed!`. + - 실 portfolio-hub e2e (portfolio-hub#9 user_info 500 수정 반영 후): `PortfolioHubReadAdapter.whoami()`→`daemon_id=b5084542-…`, `get_portfolios()`→삼성전자(005930) 1건, `SnapshotScheduler.run_once()`→`degraded=None`, universe 1건, warning 없음. +- 영향 결론: 현재 코드가 실 계약과 일치하고 모든 AC evidence 가 정정 테스트명으로 재연결됨. REQ-027 Implemented 유지, BAT-008 release-candidate 유지. Ready for Release 변동 없음. + ## Result - Ready for Release: true - Blocking Issues: diff --git a/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md b/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md index 48b1044..1149ef3 100644 --- a/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md +++ b/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md @@ -9,16 +9,16 @@ - 전략 모듈에 제공할 종목 스냅샷의 원천인 portfolio-hub universe(자신이 관리하는 portfolio 집합)를 trex-core 가 read 할 경로를 만든다. dcy-004 write 어댑터/자격증명을 재사용하고 도메인 순수성을 유지한다. ## Requirement -- `PortfolioHubReadPort.get_portfolios(daemon_id)` application 포트 + portfolio-hub read 어댑터(`managed_by_daemon_id` 스코프 조회, portfolio-hub `list_portfolios_v1`) + 결정론 stub 를 제공한다. -- 어댑터는 dcy-004 의 portfolio-hub 자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 재사용한다. -- `daemon_id` 는 portfolio-hub 자격증명/인증 토큰에서 도출한다(단일 진실원). 별도 `TREX_DAEMON_ID` 설정값을 1차 출처로 두지 않으며, 필요 시 whoami 1회로 도출·캐시한다. -- 각 universe 항목은 symbol, asset_id(=portfolio_id), cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. 전략파라미터는 trex-core 가 해석하지 않고 통과시킨다. +- `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) 형태로 반환하고 가격 필드는 없다. +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 토큰에서 도출한 daemon_id 로 동작한다(OQ-7). +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 @@ -26,7 +26,8 @@ - Tests: stub read 통합테스트, 도메인 import 스윕, daemon_id 토큰 도출 ## Notes -- 가정: portfolio-hub `list_portfolios_v1`(`GET`)이 `managed_by_daemon_id=auth.daemon_id` 로 스코프 조회를 제공하고 `PortfolioItem` 에 cash_balance/asset_quantity 포함(confirm-discovery 에서 검증). 전략파라미터는 portfolio-hub `strategy_params` JSONB(dcy-005 A-5). +- (정정, 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 @@ -133,3 +134,46 @@ - 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건. diff --git a/docs/srs/index.md b/docs/srs/index.md index 930fe66..f1f05c3 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -117,8 +117,8 @@ ## Recent Change Log Summary +- 2026-06-06 :: REQ-027 CHG-04 — 실 portfolio-hub 계약 정정(whoami `/v1/user_info`, `{portfolios}` envelope, 클라이언트측 daemon 필터, 전략 필드 None/`{}` 이월). 정정 구현 재검증 완료·Implemented 유지. (change-req) - 2026-06-06 :: REQ-029 CHG-04 — orb-v1 소비 참고 사항 추가(strategy_key 없이 view=buyable, 전략 필드 부재 무해). 문서 전용·Implemented 유지. (change-req) - 2026-06-06 :: REQ-027~029, REQ-031~033 Approved → Implemented — BAT-008 release-candidate verification evidence 기반. REQ-030 은 AC-1 evidence 확보·AC-2/AC-3 portfolio-hub#8 배포 대기로 Approved 유지. (confirm-req-implemented) - 2026-06-06 :: REQ-027~033 Proposed → Approved — dcy-005 confirmed, 전체 승인 게이트 통과. REQ-030 옵션 D 구현은 portfolio-hub#8 배포 후 착수. (confirm-req) - 2026-06-06 :: REQ-027~033 최초 생성(Proposed) — dcy-005 FR-1~FR-9 분해(NFR-1 → REQ-027 흡수, NFR-2 → REQ-030 흡수, NFR-3 → REQ-028 흡수). (draft-req) -- 2026-06-05 :: REQ-021~026 Approved → Implemented — BAT-007 release-candidate verification evidence 기반. (confirm-req-implemented)