_PORTFOLIO_NAME_RE + CLI _NAME_RE를 ^[a-zA-Z0-9가-힣 ._:-]{1,100}$로 완화. name은 (owner,name) 조회 키일 뿐 wire 식별자 아님. 475 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# REQ-025: POST /v1/portfolios 신설 (토큰 분기 + catalog 검증)
|
|
|
|
- Status: Implemented
|
|
- Type: Interface
|
|
- Priority: High
|
|
- Owner: Justin Ko
|
|
|
|
## Intent
|
|
- v1 API에 portfolio 생성 경로(`POST /v1/portfolios`)를 신설해 user/admin이 HTTP로 본인 또는 타 user의 portfolio를 직접 프로비저닝할 수 있도록 한다.
|
|
- dcy-001 FR-4의 catalog 검증(asset_id active)을 portfolio 생성 시점에 통합 적용해 dcy-001 asset catalog 모델 위에서만 portfolio가 생성되도록 강제한다.
|
|
|
|
## Requirement
|
|
- 신규 라우트 `POST /v1/portfolios` (Bearer 인증 필수).
|
|
- **토큰 분기 규칙** (dcy-003 OQ-2 + OQ-5 결정, dcy-004 정책과 정합):
|
|
- `token_type ∈ {service, client}` → HTTP 403 fail-closed (`detail="service token cannot create portfolios"` 또는 client 대응 메시지). service는 daemon-bound로 portfolio CRUD 주체가 아니며 client는 dcy-004 REQ-016에서 폐지 확정.
|
|
- 그 외(`token_type=session` 또는 env token):
|
|
- `permission='admin'` → admin 분기. body `user_id` non-null이면 해당 user의 portfolio로, null이면 인증 주체의 user_id로 생성.
|
|
- `permission ∈ {read, trade-write}` → user 분기. 인증 주체의 user_id로 생성. body `user_id` 지정은 무시 또는 HTTP 422.
|
|
- **catalog 검증** (dcy-001 FR-4 통합): `asset_id`가 `assets` 테이블에 존재하고 `status='active'`인지 검증. 미등록/inactive면 dcy-001 FR-4와 일관된 응답 코드로 거부(HTTP 422 또는 403; REQ 단계 확정은 본 REQ 또는 후속 보강).
|
|
- **body 스키마** (`PortfolioCreateRequest` 신규):
|
|
- 필수: `asset_id`, `broker`
|
|
- 선택: `name`, `cash_balance`, `asset_quantity`, `asset_price`, `profit_buffer`, `strategy_name`, `strategy_params`, `managed_by_daemon_id`, `user_id`(admin 분기 전용)
|
|
- 응답: HTTP 201 + 생성된 portfolio의 `PortfolioItem`(`portfolio_id`, `asset_id`, `symbol`, `owner_user_id`, ...).
|
|
- 응답의 `symbol` 값은 생성된 `portfolios.asset_id`가 가리키는 `assets.symbol`을 기준으로 server-side join 결과에서 채운다. 클라이언트가 별도 catalog lookup으로 복원하는 방식은 본 REQ 범위의 정식 계약이 아니다.
|
|
- catalog 검증 위치(서비스 계층 공통 진입점 vs v1 라우터 레이어 한정)는 본 REQ 구현 시점에 결정. dcy-003 R-5 트레이드오프 reference 참조.
|
|
|
|
## Acceptance Criteria
|
|
1. Given admin token + body `{"asset_id": "<catalog-uuid>", "broker": "kiwoom", "user_id": "<target-user-uuid>"}`, When `POST /v1/portfolios`, Then HTTP 201 + 생성된 portfolio의 `owner_user_id == <target-user-uuid>` 반환.
|
|
2. Given session/env token with `permission ∈ {read, trade-write}` + body `{"asset_id": ..., "broker": ...}`, When `POST /v1/portfolios`, Then HTTP 201 + `owner_user_id == auth.user_id` 반환.
|
|
3. Given service token, When `POST /v1/portfolios`, Then HTTP 403 fail-closed + `detail="service token cannot create portfolios"`.
|
|
4. Given client token(폐지 전 운영 단계), When `POST /v1/portfolios`, Then HTTP 403 fail-closed.
|
|
5. Given body `asset_id`가 catalog에 미존재 또는 `status='inactive'`, When `POST /v1/portfolios`, Then HTTP 422 또는 403로 거부되고 portfolio row가 생성되지 않는다.
|
|
6. Given Bearer token 없음, When `POST /v1/portfolios`, Then HTTP 401.
|
|
7. Given non-admin token + body `user_id` 지정, When `POST /v1/portfolios`, Then `user_id`는 무시되어 `owner_user_id == auth.user_id`로 생성되거나 HTTP 422.
|
|
8. Given active catalog asset(`asset_id=<uuid>`, `symbol="005930"`), When `POST /v1/portfolios`, Then HTTP 201 응답의 `PortfolioItem.symbol == "005930"` 이고 값은 해당 asset row join 결과와 일치한다.
|
|
|
|
## Impacted Area
|
|
- Docs:
|
|
- `docs/interface-contract.md` — POST /v1/portfolios 명세 추가 (REQ-032 baseline 작업)
|
|
- `docs/runtime-flows.md` — portfolio create flow + catalog 검증 단계 (REQ-032)
|
|
- Modules:
|
|
- `app/api/v1_portfolios.py` (신규) — POST /v1/portfolios 라우트, 토큰 분기 인증 헬퍼(`_require_portfolio_access`)
|
|
- `app/main.py` — v1_portfolios_router include
|
|
- `app/schemas/v1.py` — `PortfolioCreateRequest`, `PortfolioItem` 신규
|
|
- `app/services/portfolio_service.py:create_portfolio` — catalog 검증 통합(dcy-001 FR-4 정책)
|
|
- `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py` — `get_asset_by_id(asset_id, only_active=True)` 헬퍼(또는 직접 query) 추가
|
|
- Tests:
|
|
- 신규 `tests/test_v1_portfolios_crud.py` — AC-1~7 검증
|
|
|
|
## Notes
|
|
- 본 REQ는 dcy-003 FR-1(생성) + FR-2(catalog 검증)을 통합. 생성 시점에 catalog 검증이 자연 적용되므로 단일 REQ로 분리.
|
|
- catalog 검증 위치: dcy-003 §5 FR-2 본문에 "REQ 작성 단계에서 결정" 위임. R-5(CLI 회귀) 완화 방안을 참조해 본 REQ 구현 시 결정.
|
|
- 토큰 분기 게이트는 dcy-004 REQ-016 client 폐지 이후 client 분기가 자연 사라지나, 폐지 release 이전·이후 모두 정합하도록 게이트 자체는 유지(env token 침투 방어).
|
|
- daemon이 portfolio를 직접 생성하지 못함(OQ-2). v1 API에서 daemon의 권한은 조회(REQ-028)와 ledger 기록(REQ-029)에 한정.
|
|
- Source: dcy-003 FR-1, FR-2, OQ-2, OQ-5, NFR-1, NFR-3, NFR-4. dcy-001 FR-4 통합.
|
|
|
|
## Change Log
|
|
|
|
### CHG-20260627-03
|
|
- Date: 2026-06-27
|
|
- Author: portfolio-hub-dev
|
|
|
|
#### Change Summary
|
|
- `_PORTFOLIO_NAME_RE` 정규식 완화: 한글(가-힣) + 공백 허용 추가.
|
|
|
|
#### Intent
|
|
- 포트폴리오 name은 `(owner_user_id, name)` 조회 키로만 쓰이며 wire 식별자가 아니므로 UTF-8 한글이 안전하다. 사용자 가독성 향상을 위해 한글 이름과 공백을 허용한다.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- No change (기존 AC 그대로 유지)
|
|
|
|
#### Impacted Area Delta
|
|
- `app/schemas/portfolio.py`: `_PORTFOLIO_NAME_RE` → `^[a-zA-Z0-9가-힣 ._:\-]{1,100}$`, `validate_name` 에러 메시지 갱신.
|
|
- `tests/test_portfolio_uuid.py`: 한글 name 통과(`test_name_validation_accepts_korean`), 기존 ASCII name 통과(`test_name_validation_accepts_ascii_unchanged`), 101자 초과 거부(`test_name_validation_rejects_over_100_chars`) 케이스 추가.
|
|
|
|
#### Delivery Trace Delta
|
|
- Batches: minor-change (batch-lite 불필요 단일 저위험 변경)
|
|
- Releases: pending
|
|
|
|
#### Revalidation Impact
|
|
- Reverification Needed: no (기존 AC 충족 범위 확장만이며 거부 규칙 약화 없음 — `!` 등 비허용 문자는 여전히 거부)
|
|
- Existing Implementation Invalidated: no
|
|
- Status Recommendation: keep-implemented
|
|
- Follow-up Action: 없음
|
|
|
|
### CHG-20260524-02
|
|
- Date: 2026-05-24
|
|
- Author: Hermes Agent
|
|
|
|
#### Change Summary
|
|
- Status: Approved → Implemented
|
|
|
|
#### Intent
|
|
- BAT-014 verification 완료. `PortfolioItem.symbol` 응답 계약이 코드, 테스트, baseline 문서에 반영되었고 REQ-025 acceptance criteria 전부가 trace 가능해졌다.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- No change
|
|
|
|
#### Impacted Area Delta
|
|
- No change
|
|
|
|
#### Delivery Trace Delta
|
|
- Batches: BAT-014 (release-candidate); BAT-009 response-contract evidence superseded by BAT-014
|
|
- Releases: pending
|
|
|
|
#### Revalidation Impact
|
|
- Reverification Needed: no
|
|
- Existing Implementation Invalidated: no
|
|
- Status Recommendation: promote-to-implemented
|
|
- Follow-up Action: release 단계에서 smoke 확인
|
|
|
|
### CHG-20260524-01
|
|
- Date: 2026-05-24
|
|
- Author: Hermes Agent
|
|
|
|
#### Change Summary
|
|
- `PortfolioItem.symbol` 응답 계약 추가에 따라 REQ-025 생성 응답 contract를 보강하고 상태를 Implemented → Approved로 되돌림.
|
|
|
|
#### Intent
|
|
- trex-orbit 팀 이슈(`docs/issues/20260524_portfolio-hub-ticket-symbol-in-portfolios.md`) 후속. 생성 응답만 `asset_id`를 반환하고 `symbol`을 생략하면 클라이언트가 별도 asset catalog 조회/캐시를 유지해야 하므로, 생성 직후 응답에서도 joined asset의 `symbol`을 즉시 반환하도록 계약을 확장한다.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- Added:
|
|
- AC-8: POST 응답의 `PortfolioItem.symbol` 이 `assets.symbol` join 결과와 일치해야 함
|
|
|
|
#### Impacted Area Delta
|
|
- Docs: `docs/interface-contract.md` 의 POST `/v1/portfolios` 응답 예시에 `symbol` 추가 필요 (REQ-032 후속)
|
|
- Modules: `app/schemas/v1.py`, `app/api/v1_portfolios.py` (`_portfolio_to_item`), `app/repositories/postgres.py:get()` join 응답 확인
|
|
- Tests: `tests/test_v1_portfolios_crud.py` 에 POST 응답 `symbol` 검증 추가 필요
|
|
|
|
#### Delivery Trace Delta
|
|
- Batches: BAT-009 evidence partially superseded; follow-up BAT-014 예정
|
|
- Releases: REL-001 response-contract evidence 일부 재검토 필요
|
|
|
|
#### Revalidation Impact
|
|
- Reverification Needed: yes
|
|
- Existing Implementation Invalidated: yes (response schema evidence partial)
|
|
- Status Recommendation: revert-to-approved
|
|
- Follow-up Action: BAT-014에서 POST/PATCH/DELETE/GET portfolio 응답 `symbol` 회귀 검증 추가
|
|
|
|
#### Validation Plan
|
|
- `tests/test_v1_portfolios_crud.py` 에서 POST 201 응답 body의 `symbol` 존재 및 `asset_id`가 가리키는 asset row의 `symbol` 일치 여부를 검증한다.
|
|
|
|
### CHG-20260521-01
|
|
- Date: 2026-05-21
|
|
- Author: Justin Ko
|
|
|
|
#### Change Summary
|
|
- Status: Approved → Implemented
|
|
|
|
#### Intent
|
|
- BAT-009 confirm-batch-verification 완료. tests/test_v1_portfolios_crud.py AC-1~7 전 케이스 pytest 통과 (2026-05-21, 298 PASSED).
|
|
|
|
#### Acceptance Criteria Delta
|
|
- No change
|
|
|
|
#### Impacted Area Delta
|
|
- No change
|
|
|
|
#### Delivery Trace Delta
|
|
- Batches: BAT-009 (release-candidate)
|
|
- Releases: TBD
|
|
|
|
#### Revalidation Impact
|
|
- Reverification Needed: no
|
|
- Existing Implementation Invalidated: no
|
|
- Status Recommendation: promote-to-implemented
|
|
- Follow-up Action: 없음
|
|
|
|
### 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: none (미정)
|
|
- Releases: none (미정)
|
|
|
|
#### Revalidation Impact
|
|
- Reverification Needed: no
|
|
- Existing Implementation Invalidated: no
|
|
- Status Recommendation: promote-to-approved
|
|
- Follow-up Action: batch 계획 편입
|
|
|
|
### CHG-20260520-01
|
|
- Date: 2026-05-20
|
|
- Author: Justin Ko
|
|
|
|
#### Change Summary
|
|
- 최초 생성 — dcy-003 confirm-discovery 후 draft-req 실행. dcy-003 FR-1 + FR-2 통합 산출물.
|
|
|
|
#### Intent
|
|
- v1 portfolio mutation의 핵심 진입점(생성) + dcy-001 catalog 검증 통합.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- Added:
|
|
- AC-1~7 최초 작성
|
|
|
|
#### Impacted Area Delta
|
|
- Docs: docs/interface-contract.md, docs/runtime-flows.md (REQ-032에서 처리)
|
|
- Modules: app/api/v1_portfolios.py (신규), app/main.py, app/schemas/v1.py, app/services/portfolio_service.py, app/repositories/* — 최초 지정
|
|
- Tests: tests/test_v1_portfolios_crud.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 후 batch 계획 편입; dcy-001 REQ-009/011/012 선행 의존성 확인(assets 테이블 + portfolios asset_id FK)
|
|
|
|
#### Validation Plan
|
|
- pytest TestClient로 AC-1~7 검증; 토큰 타입별·permission별 시나리오 + catalog hit/miss
|