feat: dcy-003/004 토큰 surface 개편 및 daemon lifecycle 정책 구현 (BAT-006/007)
BAT-006 (REQ-016/017/018): - client token 발급 경로 제거 (issue_client_token, POST /auth/tokens, 관련 스키마/테스트) - service-token rotate unique index revoked_at IS NULL 조건 추가 (Alembic CONCURRENTLY 마이그레이션) - POST /auth/service-tokens body daemon_id forward 누락 보정 (1-line fix) BAT-007 (REQ-022/023): - daemon deactivate 시 바인딩 service token 자동 revoke (CLI --force 옵션 + revoke_tokens_by_daemon_id) - NULL daemon_id legacy token 진단 CLI (migrate-null-daemon --dry-run) 거버넌스 문서: dcy-003/004 Discovery, REQ-016~023/025~032 신규, BAT-006/007 전체 산출물 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# REQ-028: GET /v1/portfolios 신설 (토큰 분기 조회)
|
||||
|
||||
- Status: Approved
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- v1 API에 portfolio 조회 경로(`GET /v1/portfolios`)를 신설해 service/user/admin 세 principal이 단일 엔드포인트로 분기 조회할 수 있도록 한다.
|
||||
- dcy-001 FR-5(REQ-013 Deprecated 처리)에서 회수된 `GET /v1/assets` 통합 endpoint 정의를 본 신규 path로 이전한다(dcy-003 옵션 B 결정).
|
||||
|
||||
## Requirement
|
||||
- 신규 라우트 `GET /v1/portfolios` (Bearer 인증 필수).
|
||||
- **토큰 분기 규칙** (dcy-003 OQ-5 결정):
|
||||
- `token_type='service'`: daemon-bound 조회. `daemon_id is None`이면 HTTP 403 fail-closed. 그 외 `managed_by_daemon_id = auth.daemon_id` 필터 + `?market=<code>` 지원.
|
||||
- `token_type='client'`: HTTP 403 fail-closed (dcy-004 REQ-016 폐지 확정 정합).
|
||||
- `token_type ∈ {session, None(env)}`:
|
||||
- `permission='admin'` → 기본 `owner_user_id = auth.user_id`(본인 portfolio) 반환. `?user_id=<id>` 지정 시 해당 user의 portfolio 반환.
|
||||
- `permission ∈ {read, trade-write}` → `owner_user_id = auth.user_id` 필터, 본인 portfolio만 반환.
|
||||
- **`?market=<code>` 필터**: 모든 토큰 타입에서 지원. `<code> = ANY(assets.markets)`인 portfolio만 반환(catalog와 join).
|
||||
- **`?include_disabled=true` 옵션** (dcy-003 R-2 완화): 기본 응답은 `status='disabled'`인 portfolio 제외. opt-in 시에만 포함.
|
||||
- 응답 항목 schema: 신규 `PortfolioItem` (`portfolio_id`, `asset_id`, `owner_user_id`, `broker`, `status`, `cash_balance`, ..., `tag_ids`, `managed_by_daemon_id`, ...). `asset_id`는 catalog의 `assets.asset_id`(UUID) 참조.
|
||||
- 응답: HTTP 200 + `PortfolioListResponse` (`portfolios: list[PortfolioItem]`, `total: int`).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given service token(daemon-bound, `daemon_id` 있음), When `GET /v1/portfolios`, Then `managed_by_daemon_id = auth.daemon_id`인 portfolio 목록만 반환.
|
||||
2. Given service token(`daemon_id` 없음), When `GET /v1/portfolios`, Then HTTP 403 fail-closed.
|
||||
3. Given session/env token + `permission ∈ {read, trade-write}`, When `GET /v1/portfolios`, Then `owner_user_id = auth.user_id`인 portfolio 목록만 반환.
|
||||
4. Given admin token (no `?user_id`), When `GET /v1/portfolios`, Then admin 본인의 portfolio 목록 반환.
|
||||
5. Given admin token + `?user_id=<target>`, When `GET /v1/portfolios`, Then `<target>` user의 portfolio 목록 반환.
|
||||
6. Given 임의 토큰 + `?market=KRX`, When `GET /v1/portfolios?market=KRX`, Then `'KRX' = ANY(assets.markets)`인 portfolio만 반환.
|
||||
7. Given disabled portfolio 1건 + active portfolio 1건 보유 user, When `GET /v1/portfolios` (`?include_disabled` 없음), Then active 1건만 반환.
|
||||
8. Given AC-7 환경 + `?include_disabled=true`, When `GET /v1/portfolios?include_disabled=true`, Then 2건 모두 반환.
|
||||
9. Given client token, When `GET /v1/portfolios`, Then HTTP 403 fail-closed.
|
||||
10. Given Bearer token 없음, When `GET /v1/portfolios`, Then HTTP 401.
|
||||
11. Given 응답 portfolio 항목, When `asset_id` 필드 확인, Then `assets.asset_id`(catalog UUID)를 참조한다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — GET /v1/portfolios 명세 + `?market`/`?user_id`/`?include_disabled` 파라미터 (REQ-032)
|
||||
- `docs/runtime-flows.md` — 토큰 분기 조회 flow + daemon-scoped 필터 (REQ-032)
|
||||
- Modules:
|
||||
- `app/api/v1_portfolios.py` — GET 라우트 (REQ-025의 인증 헬퍼 재사용 + GET 전용 service 토큰 분기 추가)
|
||||
- `app/api/v1_assets.py` — 기존 `list_assets` 함수에서 portfolio 조회 로직 제거(catalog 전용 회수). REQ-013 Deprecated 후속 정리.
|
||||
- `app/schemas/v1.py` — `PortfolioItem`, `PortfolioListResponse` 신규
|
||||
- `app/services/portfolio_service.py:list_portfolios` — token-type별 분기 + `?market`/`?include_disabled` 처리. dcy-001 REQ-013 Deprecated 후속 작업 흡수.
|
||||
- `app/repositories/postgres.py:list_portfolios_with_strategies` — `managed_by_daemon_id` 필터 + `markets` array 필터 + `include_disabled` 분기
|
||||
- Tests:
|
||||
- 신규 `tests/test_v1_portfolios_list.py` — AC-1~11 검증
|
||||
|
||||
## Notes
|
||||
- 본 REQ는 dcy-001 REQ-013 Deprecated(CHG-20260520-01)의 후속 신규 REQ. 토큰 분기 정책은 REQ-013의 본문을 path만 변경해 흡수.
|
||||
- `GET /v1/assets`(catalog 조회 의미)는 본 REQ 범위 밖이며 dcy-001 batch design 단계에서 REQ-010 보강 또는 신규 REQ로 처리.
|
||||
- service token이 NULL `daemon_id`로 호출하면 fail-closed. NULL daemon legacy token 운영 정리는 dcy-004 REQ-023(`migrate-null-daemon` CLI)로 처리.
|
||||
- `?include_disabled` 기본 false 정책은 R-2 완화의 핵심. REQ-027 soft delete와 정합.
|
||||
- Source: dcy-003 FR-5, OQ-5, NFR-1, NFR-3, R-2 완화. dcy-001 REQ-013 후속.
|
||||
|
||||
## Change Log
|
||||
|
||||
### 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-5 산출물 + REQ-013 Deprecated 후속.
|
||||
|
||||
#### Intent
|
||||
- v1 portfolio 조회 신설 + 토큰 분기 정책 path 이전 + R-2 disabled 가시성 완화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1~11 최초 작성
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs: docs/interface-contract.md, docs/runtime-flows.md (REQ-032)
|
||||
- Modules: app/api/v1_portfolios.py, app/api/v1_assets.py(catalog 회수), app/schemas/v1.py, app/services/portfolio_service.py, app/repositories/postgres.py — 최초 지정
|
||||
- Tests: tests/test_v1_portfolios_list.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-013 Deprecated 정리 후속
|
||||
|
||||
#### Validation Plan
|
||||
- pytest TestClient로 AC-1~11 검증; 토큰 분기 + `?market` + `?user_id` + `?include_disabled` 시나리오
|
||||
Reference in New Issue
Block a user