# REQ-029: POST /v1/portfolios/{portfolio_id}/ledger/trades 이전 - Status: Implemented - Type: Interface - Priority: High - Owner: Justin Ko ## Intent - 기존 `POST /v1/assets/{asset_id}/ledger/trades`(`app/api/v1_ledger.py`)의 path param 이름이 `asset_id`이나 실제 값은 `portfolios.portfolio_id`(UUID)로 의미 혼용되는 결함을 해소한다. - dcy-003 옵션 B(path 명명 분리)에 따라 신 path `POST /v1/portfolios/{portfolio_id}/ledger/trades`로 이전하고 구 path는 동일 release에서 제거한다. 동작·인가·idempotency는 그대로 보존. ## Requirement - 신규 라우트 `POST /v1/portfolios/{portfolio_id}/ledger/trades` (Bearer 인증 필수). - **동작은 기존 `POST /v1/assets/{asset_id}/ledger/trades`와 동일하게 보존**: - **daemon assignment 3단계 fail-closed 인가**: (1) token `daemon_id IS NULL` → HTTP 403, (2) portfolio `managed_by_daemon_id IS NULL`(미배정 asset) → HTTP 403, (3) `portfolio.managed_by_daemon_id != auth.daemon_id`(불일치) → HTTP 403. - **idempotency**: `(portfolio_id, order_id, execution_id)` unique constraint (기존 `uq_ledger_trades_idempotency` 재사용). - **duplicate/conflict 응답**: 동일 키 + 동일 payload → HTTP 200 + `status="duplicate"`. 동일 키 + 다른 payload → HTTP 409. - **새 ledger row** → HTTP 201 + `status="new"`. - **disabled portfolio 차단** (REQ-027 정합): `portfolios.status='disabled'`인 portfolio에 대해서는 HTTP 409 또는 422로 차단. - 구 path `POST /v1/assets/{asset_id}/ledger/trades` 제거: 동일 batch release에서 라우트 삭제. 호출 시 HTTP 404. - **trex-basic 동시 배포 필수** (dcy-003 OQ-3 + NFR-2): 신 path 단일 서빙. 한시적 별칭/redirect 없음. release plan에 배포 순서 잠금(trex-basic 클라이언트 사전 빌드 → portfolio-hub 배포 → trex-basic 배포) + rollback 시 trex-basic 동반 rollback 전제 명시. ## Acceptance Criteria 1. Given service token + 본인 daemon에 배정된 active portfolio + 신규 trade payload, When `POST /v1/portfolios/{portfolio_id}/ledger/trades`, Then HTTP 201 + `status="new"` + 생성된 ledger row. 2. Given AC-1 실행 후 동일 key + 동일 payload 재호출, When 호출, Then HTTP 200 + `status="duplicate"` + 단일 ledger row 유지. 3. Given AC-1 실행 후 동일 key + 다른 payload 호출, When 호출, Then HTTP 409 + ledger row 변경 없음. 4. Given service token `daemon_id IS NULL`, When `POST /v1/portfolios/{portfolio_id}/ledger/trades`, Then HTTP 403 fail-closed. 5. Given service token + `portfolio.managed_by_daemon_id IS NULL`(미배정 asset), When 호출, Then HTTP 403 fail-closed. 6. Given service token + `portfolio.managed_by_daemon_id != auth.daemon_id`(불일치), When 호출, Then HTTP 403 fail-closed. 7. Given service token + `status='disabled'` portfolio, When 호출, Then HTTP 409 또는 422 (disabled write 차단). 8. Given 비존재 `portfolio_id`, When 호출, Then HTTP 404. 9. Given 구 path `POST /v1/assets/{asset_id}/ledger/trades` 호출, When 본 REQ 구현 후 동일 batch release, Then HTTP 404. 10. Given Bearer token 없음, When 호출, Then HTTP 401. ## Impacted Area - Docs: - `docs/interface-contract.md` — POST /v1/portfolios/{id}/ledger/trades 명세 + 구 path 제거 (REQ-032) - `docs/runtime-flows.md` — daemon assignment 3단계 인가 + idempotency + path 이전 흐름 (REQ-032) - Modules: - `app/api/v1_portfolios.py` — `POST /v1/portfolios/{portfolio_id}/ledger/trades` 라우트 추가 (또는 별도 `v1_ledger.py`를 신 path로 이전) - `app/api/v1_ledger.py` — 구 path 제거 또는 신 path로 라우트 prefix 이전 - `app/main.py` — 라우터 include 갱신 (구 ledger 라우터 제거) - `app/services/portfolio_service.py` 또는 `app/services/ledger_service.py` — 기존 ledger trade 로직 재사용 + disabled 차단 검증 추가 - Tests: - 갱신 `tests/test_v1.py` — 구 path 케이스를 신 path로 갱신, 구 path 호출 시 HTTP 404 확인 - 신규 `tests/test_v1_portfolios_ledger.py` — AC-1~10 검증 ## Notes - 본 REQ는 기능 추가가 아닌 **path 이전**이라 기존 동작·테스트 시나리오를 그대로 재사용한다. 다만 disabled portfolio 차단(AC-7)은 REQ-027 soft delete 정책 정합 검증으로 신규. - dcy-003 NFR-2(trex-basic 동시 배포)는 본 REQ의 가장 큰 운영 리스크(R-1)이며, release plan에 배포 순서 잠금·rollback 전제를 명시해야 함. release plan은 batch design/release 단계 작업. - ledger_trades 테이블의 `asset_id` 컬럼명은 본 REQ에서 변경하지 않는다(dcy-001 `assets.asset_id`와 의미 혼용이 잔존하나, ledger_trades 컬럼은 portfolio_id를 가리킨다는 점이 path 이름 정합화로 충분히 명확해짐). 컬럼명 변경은 별도 migration REQ로 분리 가능. - Source: dcy-003 FR-6, OQ-3, NFR-1, NFR-2, R-1 완화. ## Change Log ### CHG-20260521-01 - Date: 2026-05-21 - Author: Justin Ko #### Change Summary - Status: Approved → Implemented #### Intent - BAT-011 구현 완료: `app/api/v1_portfolios.py`에 `POST /{portfolio_id}/ledger/trades` 신설 (daemon 인가 3단계 + disabled 409 + idempotency 보존); `app/api/v1_ledger.py` 삭제; `app/main.py` include 정리; `tests/test_v1_portfolios_ledger.py` AC-1~10 신규; `tests/test_v1.py` + `tests/test_daemon_assignment.py` 구 path 갱신 #### Acceptance Criteria Delta - No change #### Impacted Area Delta - No change #### Delivery Trace Delta - Batches: BAT-011 (release-candidate) - Releases: pending #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: implemented - Follow-up Action: BAT-011 release candidate → draft-release; trex-basic 동시 배포 순서 잠금 필수 ### CHG-20260520-02 - Date: 2026-05-20 - Author: Justin Ko #### Change Summary - Status: Proposed → Approved #### Intent - 승인 게이트 통과: 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 모두 충족, AC 구체성 확인 완료, blocker 없음 #### Acceptance Criteria Delta - No change #### Impacted Area Delta - No change #### Delivery Trace Delta - Batches: BAT-011 (in-delivery) - Releases: none (미정) #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: promote-to-approved - Follow-up Action: BAT-011 draft-batch-planning 완료 → draft-batch-design 진행 ### CHG-20260520-01 - Date: 2026-05-20 - Author: Justin Ko #### Change Summary - 최초 생성 — dcy-003 confirm-discovery 후 draft-req 실행. dcy-003 FR-6 산출물. #### Intent - ledger trade path 명명 정합화 + 동시 배포 정책 명시 + disabled 차단 추가. #### Acceptance Criteria Delta - Added: - AC-1~10 최초 작성 #### Impacted Area Delta - Docs: docs/interface-contract.md, docs/runtime-flows.md (REQ-032) - Modules: app/api/v1_portfolios.py, app/api/v1_ledger.py(제거 또는 이전), app/main.py, app/services/* — 최초 지정 - Tests: tests/test_v1.py(갱신), tests/test_v1_portfolios_ledger.py(신규) — 최초 지정 #### Delivery Trace Delta - Batches: none (미정) - Releases: none (미정). trex-basic 동시 배포 release plan은 batch design 단계에서 작성. #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: confirm-req 후 batch 계획 편입; trex-basic 측 release readiness(DATA_TREX_BASIC_RELEASE_WINDOW) 외부 확인 필요 #### Validation Plan - pytest TestClient로 AC-1~10 검증; 신·구 path 회귀 + daemon 인가 3단계 + idempotency·conflict + disabled 차단