feat: implement portfolio UUID migration and v1 API finalization (BAT-006, BAT-001 re-verified)

Core Changes:
- Migrate portfolio_id from string to UUID in schema and repository layer
- Update portfolio CLI commands to use name+user combination for identification
- Update HTTP API to accept portfolio_id as UUID path parameter
- Implement portfolio ownership validation in repository queries

Requirements Completed (REQ-016 to REQ-019):
- REQ-016: portfolios table UUID schema migration with data migration script
- REQ-017: CLI portfolio commands with name+user identification
- REQ-018: HTTP API portfolio path parameters and v1 asset_id UUID returns
- REQ-019: Portfolio UUID baseline documentation updates

BAT-001 Re-verification:
- REQ-004~010 marked as Implemented after verification evidence validation
- All v1 API endpoints verified with comprehensive test coverage

Documentation Updates:
- Update data-model.md with portfolio UUID structure
- Update interface-contract.md with UUID path parameters
- Update runtime-flows.md with ownership-based resolution flows
- Update project-structure.md with database layer changes
- Add migration script for portfolio UUID transition

Testing:
- Add test_portfolio_uuid.py for migration validation
- Update test_cli.py and test_portfolio_ownership.py for ownership checks
This commit is contained in:
2026-05-11 09:27:57 +00:00
parent 9ed307555a
commit 829629fa8f
46 changed files with 3260 additions and 220 deletions
+61 -2
View File
@@ -4,8 +4,8 @@
- Baseline Seed: .stagepilot/bootstrap/baseline.yaml
- Status: confirmed
- Owner: repository-maintainers
- Last Updated (KST): 2026-05-01 00:00
- Source Discovery / Batch: bootstrap-baseline, bat-001
- Last Updated (KST): 2026-05-11 00:00
- Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-007, bat-008
- Primary Runtime: mixed
## Project Summary
@@ -50,6 +50,17 @@
4. Read commands print table or JSON output, while write commands require explicit confirmation unless --force is supplied.
5. Command failures surface as validation errors, feature-not-ready errors, or persistence and auth exceptions.
## Flow: `CLI Portfolio Command — Name+User Resolution`
1. All portfolio mutation commands (`status`, `create`, `update`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`) accept a `<name>` positional argument and `--user <username>` option.
2. `_resolve_portfolio_id(repository, name, user)` is called before any mutation:
a. Calls `get_user_by_username(user)` → raises ValueError if user not found.
b. Calls `get_portfolio_by_name_and_user(owner_user_id, name)` → raises ValueError if portfolio not found.
c. Returns `portfolio.portfolio_id` (UUID) for downstream use.
3. `create` command takes `<name>` as the portfolio identifier; portfolio_id (UUID) is server-generated and returned in the response.
4. `update --new-name <new_name>` renames the portfolio; `--force` is required to confirm the rename.
5. `comment` and `tag` sub-commands continue to accept `<portfolio_id>` (UUID) directly; they are outside the name+user resolution flow.
## Flow Constraints
- Persistence-dependent HTTP and CLI flows require PORTFOLIOHUB_DB_ENABLED=true plus valid TimescaleDB connection settings.
@@ -71,6 +82,7 @@
3. Optional `?status=<value>` query param filters portfolios by status field.
4. For portfolios with `active_strategy_id`, `get_strategy_set()` fetches config and JSON-parses `strategy_params`; failures fall back to `{}`.
5. Returns `AssetsResponse` with `total` and `assets[]` using new field names: `cash`, `quantity`, `broker_type`, `strategy_params`.
6. `asset_id` in each asset item equals `portfolio.portfolio_id` which is a server-generated UUID after BAT-006 migration (dcy-002 A-3 updated: text identity assumption replaced by UUID).
## Flow: `v1 API — GET /v1/user_info`
@@ -102,6 +114,50 @@
2. If `db_enabled=True`, calls `settings.require_credential_encryption_key()`.
3. If `CREDENTIAL_ENCRYPTION_KEY` is not configured, raises `RuntimeError` and aborts startup.
## Flow: `Local DB Initialization and Validation`
### Scenario A — Fresh DB (DB 미존재 상태)
1. `docker compose -f docker-compose.local-test.yml up -d` — TimescaleDB 컨테이너 시작.
2. 컨테이너 준비 확인: `docker compose -f docker-compose.local-test.yml ps` → `portfolio-hub-timescaledb` 상태가 `healthy` 인지 확인.
3. `pfhub_DB_ENABLED=true pfhub init-db` — 대상 DB 생성 + Alembic `upgrade head` 실행.
- 기대 출력: `database: portfolio_hub`, `created: True`, `schema_initialized: True`, `migration_applied: head`
- pass 기준: exit code 0, 위 필드 모두 출력
- fail 기준: connection refused, permission denied, Alembic revision 오류
4. `pfhub_DB_ENABLED=true pfhub health-check` — DB ping 성공 확인.
- 기대 출력: `db_enabled: True`, `repository: PostgresPortfolioRepository`
- pass 기준: exit code 0, `db_enabled: True`
5. Smoke flow: 사용자 생성 → portfolio 생성 → `portfolio list --user <username>` 확인.
- pass 기준: 생성된 portfolio가 list 출력에 포함됨
### Scenario B — runtime-precreated schema (ensure_schema() 선행)
1. DB 컨테이너 시작 (Scenario A 1~2 동일).
2. `pfhub_DB_ENABLED=true uvicorn app.main:app` — App 기동. `ensure_schema()`가 핵심 테이블 생성.
3. `pfhub_DB_ENABLED=true pfhub migrate-db` — `bootstrap_or_upgrade_to_head()` 실행.
- 내부 동작: `alembic_version` 테이블 없음 → 기존 핵심 테이블(portfolios 등) 존재 → legacy stamp → head로 stamp + 미보유 객체(broker_credentials, ledger_trades, runtime_id 등) upgrade
- 기대 출력: `migration_applied: head`
- pass 기준: exit code 0, 이후 `pfhub health-check` exit code 0
4. `pfhub_DB_ENABLED=true pfhub health-check` 재확인.
### Repeatable Run Criteria
- 모든 시나리오는 컨테이너 볼륨 초기화(`docker compose down -v`) 후 재실행 가능.
- 출력 형식: `pfhub --output json <command>` 로 JSON 출력 수집 시 필드명이 고정됨.
- 로그 수집 위치: `docker compose logs portfolio-hub-timescaledb` — DB 측 오류 시 참조.
- Evidence 형식: exit code + 주요 필드 값 스크린샷 또는 텍스트 캡처 → batch verification.md에 기록.
## Flow: `Alembic vs ensure_schema() Schema Ownership Boundary`
`ensure_schema()` (postgres.py)와 Alembic revision 양쪽이 다루는 객체가 존재한다. 충돌은 `bootstrap_or_upgrade_to_head()`의 legacy stamp 경로 + 각 revision의 IF NOT EXISTS guard로 처리된다.
### 경계 규칙
1. **ensure_schema() 선행 가능 객체**: users, auth_tokens, strategy_sets, portfolios, portfolio_ledger, portfolio_comments, portfolio_tags + 관련 인덱스. 모두 IF NOT EXISTS로 보호됨.
2. **Alembic만 소유 (ensure_schema()에 없음)**: `portfolios.market`, `auth_tokens.runtime_id`, `idx_auth_tokens_runtime_id_unique`, `broker_credentials`, `ledger_trades`. ensure_schema()만 실행한 환경에서 v1 API 사용 불가.
3. **허용 실행 순서**: `init-db` 선행 권장. 불가피하게 ensure_schema() 선행 시, 반드시 `migrate-db` 후속 실행.
4. **금지**: `alembic_version` 테이블 수동 삭제 후 migrate-db 재실행 (legacy stamp 오판).
## Current Gaps / Planned Changes
- End-to-end validation against a real PostgreSQL or TimescaleDB deployment is still pending.
@@ -110,5 +166,8 @@
## Change Log
- 2026-05-11 KST: bat-008 — REQ-011 AC-3 confirmed: v1 API flows and runtime_id issuance flow already present from bat-001; Source Discovery/Batch header updated.
- 2026-05-11 KST: bat-007 — Added "Local DB Initialization and Validation" flow (Scenario A/B, repeatable run criteria); added "Alembic vs ensure_schema() Schema Ownership Boundary" flow.
- 2026-05-11 KST: bat-006 — Added CLI portfolio name+user resolution flow; updated GET /v1/assets flow to note asset_id is UUID after BAT-006 migration.
- 2026-05-01 KST: bat-001 — Added v1 API flows (service-token auth, GET /v1/assets, GET /v1/user_info, POST ledger/trades, POST /auth/service-tokens), startup validation flow.
- 2026-04-30 KST: Bootstrap baseline created from repository observation for initial StagePilot adoption.