197 lines
8.6 KiB
Markdown
197 lines
8.6 KiB
Markdown
# REQ-026: PATCH /v1/portfolios/{portfolio_id} 신설
|
|
|
|
- Status: Implemented
|
|
- Type: Interface
|
|
- Priority: High
|
|
- Owner: Justin Ko
|
|
|
|
## Intent
|
|
- v1 API에 portfolio 부분 수정 경로(`PATCH /v1/portfolios/{portfolio_id}`)를 신설해 user/admin이 HTTP로 portfolio 속성(broker/status/name/symbol/strategy 등)을 직접 수정할 수 있도록 한다.
|
|
- 토큰 분기 + ownership 검증을 REQ-025와 동일 패턴으로 적용해 v1 portfolio CRUD 인가 일관성을 확보한다.
|
|
|
|
## Requirement
|
|
- 신규 라우트 `PATCH /v1/portfolios/{portfolio_id}` (Bearer 인증 필수).
|
|
- **토큰 분기 규칙** (REQ-025 + dcy-003 OQ-2/OQ-5 결정과 정합):
|
|
- `token_type ∈ {service, client}` → HTTP 403 fail-closed.
|
|
- 그 외(`token_type=session` 또는 env token):
|
|
- `permission='admin'` → 모든 portfolio 수정 가능.
|
|
- `permission ∈ {read, trade-write}` → 자기 소유(`owner_user_id = auth.user_id`) portfolio만 수정 가능, 불일치 시 HTTP 403.
|
|
- 비존재 portfolio는 HTTP 404.
|
|
- 수정 가능 필드는 기존 `UpdatePortfolioRequest` 범위: `broker`, `status`, `name`, `symbol`, `strategy_name`, `strategy_params`, `asset_price`, `managed_by_daemon_id` 등.
|
|
- **disabled portfolio 보호** (REQ-027 soft delete 정책 정합): `portfolios.status == 'disabled'`인 portfolio에 대해 status 전이(`'enabled'`로 복구) 외 다른 필드 변경 시 HTTP 409 또는 422로 거부.
|
|
- 응답: HTTP 200 + 갱신된 `PortfolioItem`.
|
|
- 응답의 `symbol` 값은 수정 대상 portfolio가 참조하는 `assets.symbol`을 기준으로 server-side join 결과에서 채운다.
|
|
|
|
## Acceptance Criteria
|
|
1. Given admin token + 본인 또는 타 user portfolio, When `PATCH /v1/portfolios/{id}` body `{"strategy_name": "..."}`, Then HTTP 200 + 갱신된 portfolio 반환.
|
|
2. Given session/env token with `permission ∈ {read, trade-write}` + 본인 portfolio, When `PATCH /v1/portfolios/{id}`, Then HTTP 200 + 갱신된 portfolio.
|
|
3. Given session/env token with `permission ∈ {read, trade-write}` + 타 user portfolio, When `PATCH /v1/portfolios/{id}`, Then HTTP 403 (ownership 불일치).
|
|
4. Given service token, When `PATCH /v1/portfolios/{id}`, Then HTTP 403 fail-closed.
|
|
5. Given client token(폐지 전), When `PATCH /v1/portfolios/{id}`, Then HTTP 403 fail-closed.
|
|
6. Given Bearer token 없음, When `PATCH /v1/portfolios/{id}`, Then HTTP 401.
|
|
7. Given 비존재 `portfolio_id`, When `PATCH /v1/portfolios/{id}`, Then HTTP 404.
|
|
8. Given `status='disabled'` portfolio + body `{"strategy_name": "..."}`(status 외 필드), When `PATCH`, Then HTTP 409 또는 422 (disabled write 차단).
|
|
9. Given `status='disabled'` portfolio + body `{"status": "enabled"}` (admin), When `PATCH`, Then HTTP 200 + status 복구.
|
|
10. Given portfolio가 active asset(`symbol="005930"`)을 참조, When `PATCH /v1/portfolios/{id}` 성공, Then HTTP 200 응답의 `PortfolioItem.symbol == "005930"` 이고 값은 joined asset row와 일치한다.
|
|
|
|
## Impacted Area
|
|
- Docs:
|
|
- `docs/interface-contract.md` — PATCH /v1/portfolios/{id} 명세 추가 (REQ-032)
|
|
- `docs/runtime-flows.md` — portfolio update flow + ownership 검증 + disabled 보호 (REQ-032)
|
|
- Modules:
|
|
- `app/api/v1_portfolios.py` — PATCH 라우트 추가 (REQ-025의 인증 헬퍼 재사용)
|
|
- `app/schemas/v1.py` — `PortfolioUpdateRequest` 신규 (기존 `UpdatePortfolioRequest` 재사용 또는 v1 전용 래핑)
|
|
- `app/services/portfolio_service.py:update_portfolio` — 기존 메서드 재사용 + disabled 보호 검증 추가
|
|
- Tests:
|
|
- 신규/갱신 `tests/test_v1_portfolios_crud.py` — AC-1~9 검증
|
|
|
|
## Notes
|
|
- ownership 검증은 admin/user 분기 후 service 계층 또는 라우터에서 일관 처리. 본 REQ의 검증 위치는 REQ-025 catalog 검증 위치 결정과 함께 결정한다.
|
|
- disabled portfolio 보호는 REQ-027(soft delete) 정책의 일부로, 본 REQ에서 PATCH 측면 검증을 명시. ledger trade/tag/comment 측면은 REQ-029/030/031에서 각각 처리.
|
|
- 본 REQ는 dcy-003 FR-3 산출물이며 NFR-1/NFR-3/NFR-4를 검증 기준에 흡수.
|
|
- Source: dcy-003 FR-3, OQ-2, OQ-5, NFR-1, NFR-3, NFR-4.
|
|
|
|
## Change Log
|
|
|
|
### CHG-20260524-02
|
|
- Date: 2026-05-24
|
|
- Author: Hermes Agent
|
|
|
|
#### Change Summary
|
|
- Status: Approved → Implemented
|
|
|
|
#### Intent
|
|
- BAT-014 verification 완료. PATCH 성공 응답의 `PortfolioItem.symbol` 계약이 코드/테스트/문서에 반영되어 REQ-026 acceptance criteria 전부가 다시 충족되었다.
|
|
|
|
#### 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-026 수정 응답 contract를 보강하고 상태를 Implemented → Approved로 되돌림.
|
|
|
|
#### Intent
|
|
- trex-orbit 팀이 portfolio mutation 후에도 추가 catalog lookup 없이 즉시 symbol을 사용해야 하는 요구를 제기했다. PATCH 응답이 기존 portfolio state만 되돌리고 joined asset 식별자(`symbol`)를 생략하면 CRUD 응답 contract가 비대칭이므로, 수정 응답에도 동일 필드를 포함하도록 정의를 확장한다.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- Added:
|
|
- AC-10: PATCH 성공 응답의 `PortfolioItem.symbol` 이 joined `assets.symbol` 과 일치해야 함
|
|
|
|
#### Impacted Area Delta
|
|
- Docs: `docs/interface-contract.md` 의 PATCH `/v1/portfolios/{id}` 응답 예시에 `symbol` 추가 필요 (REQ-032 후속)
|
|
- Modules: `app/schemas/v1.py`, `app/api/v1_portfolios.py` (`_portfolio_to_item`)
|
|
- Tests: `tests/test_v1_portfolios_crud.py` 에 PATCH 응답 `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에서 PATCH 응답 contract 회귀 검증 추가
|
|
|
|
#### Validation Plan
|
|
- `tests/test_v1_portfolios_crud.py` 에서 PATCH 200 응답 body의 `symbol` 존재 및 joined asset 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~9 전 케이스 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-3 산출물.
|
|
|
|
#### Intent
|
|
- v1 portfolio mutation의 부분 수정 진입점 + ownership 검증 + disabled 보호.
|
|
|
|
#### Acceptance Criteria Delta
|
|
- Added:
|
|
- AC-1~9 최초 작성
|
|
|
|
#### Impacted Area Delta
|
|
- Docs: docs/interface-contract.md, docs/runtime-flows.md (REQ-032)
|
|
- Modules: app/api/v1_portfolios.py, app/schemas/v1.py, app/services/portfolio_service.py — 최초 지정
|
|
- 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 계획 편입; REQ-025와 동일 batch 권장
|
|
|
|
#### Validation Plan
|
|
- pytest TestClient로 AC-1~9 검증; ownership·token type·disabled 보호 시나리오
|