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
@@ -141,6 +141,7 @@
- A-1: service-token 인증은 기존 `token_type='service'` 메커니즘과 bearer token 헤더 파싱을 그대로 사용한다. 별도 인증 체계를 추가하지 않는다. (Q1 결정 사항)
- A-2: `runtime_id`는 service-token 발급 시 함께 생성되며, 해당 service-token에 연결된다. 동일 사용자의 다른 service-token은 각기 다른 `runtime_id`를 가진다. (Q2 결정 사항)
- A-3: `GET /v1/assets`의 `asset_id`는 기존 `portfolio_id`를 그대로 사용한다. 별도 asset 식별자 체계를 만들지 않는다.
- **[철회 — 2026-05-11 REQ-018]**: REQ-016 UUID 마이그레이션 완료 이후 `portfolio_id`는 server-generated UUID로 변경된다. 따라서 `asset_id == portfolio_id (text)` 전제는 `asset_id == portfolio_id (UUID)`로 대체되었다. 가정 A-3의 "별도 asset 식별자 체계를 만들지 않는다"는 원칙은 유지되지만, 실제 값 형식이 UUID로 바뀐다는 점에서 원문 가정의 텍스트 동일성 가정은 철회한다.
- A-4: `trading_enabled`는 현재 단계에서 `users.is_active`와 연동하는 단순 매핑으로 구현한다. 별도 trading 전용 상태 필드는 이번 반복 Out of Scope다.
- A-5: trex-basic이 사용하는 KIS 브로커만 우선 지원하며, credential_payload 스키마는 `{"app_key": "...", "app_secret": "..."}` 형식으로 고정한다.
@@ -1,14 +1,18 @@
# 0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: draft
- 상태: confirmed
- 문서 ID: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution
- 이슈명: portfolio_id UUID 교체 및 CLI 사용자 범위 기반 자산 식별 구조 전환
- 작성 시각(KST): 2026-05-11 00:00
- 마지막 갱신 시각(KST): 2026-05-11 00:00
- 마지막 갱신 시각(KST): 2026-05-11 (draft-req 완료 — REQ-016~019 생성)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음
- 생성된 REQ 참조:
- [REQ-016](../srs/Migration/req-016_portfolios-uuid-schema-migration.md) — portfolios 테이블 UUID 전환 및 데이터 마이그레이션 (FR-1, FR-5, NFR-1)
- [REQ-017](../srs/Interface/req-017_portfolio-cli-name-user-identification.md) — CLI portfolio 명령 name+user 조합 식별 방식 전환 (FR-2, FR-3, FR-3a, NFR-2)
- [REQ-018](../srs/Interface/req-018_portfolio-http-api-uuid-path-param.md) — HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 (FR-4)
- [REQ-019](../srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md) — portfolio UUID 전환 관련 baseline 문서 갱신 (FR-6, NFR-3)
# 1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
@@ -50,7 +54,7 @@
- 제약사항
- 기존 Alembic revision chain을 깨지 않는 IF NOT EXISTS 기반 migration을 추가한다(dcy-001/REQ-002 정책 준수).
- dcy-002 A-3("asset_id는 portfolio_id를 그대로 사용")은 이번 Discovery에서 철회된다. trex-basic 팀에게 breaking change를 사전 통보해야 한다.
- `name`은 owner 내에서 유일하되, 허용 문자 범위(공백, 특수문자 등)는 REQ 단계에서 결정한다.
- `name`은 owner 내에서 유일하며, 허용 문자는 `[a-zA-Z0-9._:-]`, 공백 불허, 1~100자, 대소문자 구분으로 확정됐다 (DATA_NAME_ALLOWED_CHARS Answered 2026-05-11).
## 주요 사용자 시나리오
사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.
@@ -96,6 +100,7 @@
- FR-1: `portfolios.portfolio_id`를 서버 생성 UUID(TEXT)로 교체한다. 기존 사용자 지정 portfolio_id 문자열은 `portfolios.name` TEXT 컬럼으로 이전한다. `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 보장한다.
- FR-2: `portfolio create` CLI 명령은 `name` Argument와 `--user` 옵션을 필수로 받고, `portfolio_id`는 서버가 UUID로 발급한다. 사용자는 UUID를 직접 지정하거나 조회할 수 없다.
- FR-3: CLI 뮤테이션 명령(`status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update`)은 `<name>` Argument와 `--user <username>` 옵션으로 portfolio를 식별한다. 내부에서 `(owner_user_id, name)` → `portfolio_id(UUID)`를 조회한 뒤 처리한다. `name` + `--user` 조합으로 portfolio를 찾지 못하면 명확한 오류를 반환한다.
- FR-3a: `name`은 생성 후 변경 가능하다. 단, 변경 시 동일 `owner_user_id` 내에 같은 `name`이 이미 존재하면 DB의 `(owner_user_id, name)` UNIQUE 제약으로 변경이 거부되고, 서비스 레이어는 사용자에게 명확한 오류 메시지(`이미 사용 중인 이름입니다: {name}`)를 반환한다.
- FR-4: HTTP API의 portfolio 관련 path parameter를 UUID 기반으로 전환한다. `GET /v1/assets` 응답의 `asset_id` 필드는 UUID를 반환한다. dcy-002 A-3을 이번 Discovery에서 철회한다.
- FR-5: 기존 데이터 마이그레이션 — 현재 `portfolio_id` 문자열 값을 `name` 컬럼으로 복사하고, 각 레코드에 새 UUID를 `portfolio_id`로 할당한다. `portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades` 등 FK 참조 테이블의 portfolio_id도 새 UUID로 함께 갱신한다.
- FR-6: `docs/project-structure.md`와 `docs/runtime-flows.md`를 이번 변경 내용에 맞게 갱신한다(repositories/services 인터페이스 변경, CLI 흐름, v1 API 흐름 변경 반영).
@@ -105,7 +110,7 @@
- NFR-1: 데이터 마이그레이션은 단일 Alembic revision으로 수행하며, IF NOT EXISTS 패턴을 준수해 재실행 시 오류가 없어야 한다(REQ-002 준수). FK 참조 테이블과의 갱신은 트랜잭션 내에서 atomic하게 처리한다.
- NFR-2: CLI `(owner_user_id, name)` → UUID 조회는 단일 DB 쿼리로 처리하며, 조회 실패 시 명확한 오류 메시지(`포트폴리오를 찾을 수 없습니다: {name} (user: {username})`)를 출력해야 한다.
- NFR-3: trex-basic 계약 변경(asset_id UUID 전환)은 배포 전에 trex-basic 팀에게 통보하고, 변경 일정을 docs/interface-contract.md에 기록해야 한다.
- NFR-3: trex-basic 계약 변경(asset_id UUID 전환)은 portfolio-hub와 trex-basic을 동시에 배포하며 처리한다. 변경 일정 및 동시 배포 계획을 docs/interface-contract.md에 기록해야 한다.
## 범위 경계
이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.
@@ -122,8 +127,8 @@
- Out of Scope
- trex-basic 측 코드 변경 — portfolio-hub API 변경만 담당
- `name` 변경 이력 감사 로그 — 향후 반복으로 Deferred
- `name` 허용 문자 유효성 검사 정책 — REQ 단계에서 결정
- HTTP API에서 `(username, name)` 조합으로 portfolio를 조회하는 엔드포인트 추가 — {{OUT_OF_SCOPE_API_NAME_LOOKUP}} 결정 필요
- `name` 허용 문자 유효성 검사 정책 — 확정됨. `[a-zA-Z0-9._:-]` 허용, 공백 불허, 1~100자, 대소문자 구분 (2026-05-11)
- HTTP API에서 `(username, name)` 조합 조회 엔드포인트 추가 — 추가하지 않음. HTTP API는 `portfolio_id`(UUID)만으로 조회 (OQ-3 Answered 2026-05-11)
# 6. 리스크/가정 목록
불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다.
@@ -132,9 +137,9 @@
- R-1 (High): 기존 FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)의 portfolio_id를 새 UUID로 일괄 갱신하는 마이그레이션 중 오류 발생 시 데이터 불일치가 생길 수 있다.
- 완화 방안: 마이그레이션 전 DB 전체 백업 수행. 마이그레이션 스크립트를 단일 트랜잭션으로 작성하고, 검증 쿼리(FK 고아 레코드 0건 확인)를 마이그레이션 스크립트 마지막에 실행한다.
- R-2 (High): `GET /v1/assets` `asset_id`가 UUID로 바뀌면 trex-basic 데몬의 `POST /v1/assets/{asset_id}/ledger/trades` 경로가 즉시 동작하지 않는다. 배포 타이밍 불일치 시 운영 장애가 발생한다.
- 완화 방안: trex-basic 팀과 변경 일정 사전 조율. portfolio-hub 배포 전 trex-basic 코드 업데이트가 선행되거나 동시 배포한다. 임시 호환성 레이어(기존 name으로 fallback 조회) 필요 여부를 REQ 단계에서 결정한다(OQ-1).
- R-3 (Medium): `name`을 mutable로 허용하면 CLI 스크립트나 외부 참조에서 portfolio를 name으로 식별하던 코드가 name 변경 후 깨진다.
- 완화 방안: name을 immutable(생성 후 변경 불가)로 정책을 확정하거나, 변경 시 경고와 함께 명시적 확인 절차를 요구한다. 결정은 OQ-2에서 처리한다.
- 완화 방안: trex-basic과 portfolio-hub를 동시에 배포한다(OQ-1 Answered). 임시 호환 레이어 불필요. 두 저장소의 배포 파이프라인에서 릴리즈 타이밍을 조율한다.
- R-3 (Medium): `name`이 mutable이므로 CLI 스크립트나 외부 참조에서 portfolio를 name으로 식별하던 코드가 name 변경 후 깨질 수 있다.
- 완화 방안: name 변경 CLI 명령에 명시적 확인 절차(--force 또는 confirm 프롬프트)를 요구한다. DB의 `(owner_user_id, name)` UNIQUE 제약이 같은 사용자 내 중복 name 변경을 자동으로 차단한다(OQ-2 Answered).
## 가정
아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.
@@ -142,23 +147,23 @@
- A-1: `portfolio_id`(UUID)는 서버에서 `uuid.uuid4()`로 생성하며, 사용자가 직접 지정하거나 예측할 수 없다.
- A-2: `name`은 `(owner_user_id, name)` 복합 UNIQUE 제약을 만족하면 되며, 전역 유일성은 요구하지 않는다.
- A-3: 기존 `portfolio_id` 문자열(`005930:tv` 형식)은 데이터 마이그레이션 시 `name` 컬럼 값으로 그대로 이전한다. 변환 없이 복사한다.
- A-4: HTTP API 경로에서 `(username, name)` 조합 조회 엔드포인트 추가는 이번 반복 Out of Scope로 잠정 처리하고, CLI 경로만 먼저 전환한다.
- A-4: HTTP API 경로에서 `(username, name)` 조합 조회 엔드포인트는 추가하지 않는다. HTTP API는 `portfolio_id`(UUID)만으로 portfolio를 식별한다. CLI만 `(owner_user_id, name)` → UUID 조회 경로를 사용한다(OQ-3 Answered 2026-05-11).
## 오픈 질문
답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.
- OQ-1: trex-basic 계약 호환성 전략 — `GET /v1/assets` asset_id가 UUID로 바뀔 때 trex-basic 측의 대응 준비 일정이 확정되기 전에 portfolio-hub를 먼저 배포할 수 있는지, 아니면 임시 호환 레이어(name으로 fallback 조회)가 필요한지.
- 상태: Open
- 처리 방안: trex-basic 팀과 협의 후 결정. 배포 순서 또는 호환 레이어 필요 여부를 REQ 단계 진입 전에 확정한다.
- 종료 조건: trex-basic 팀 확인 완료 또는 임시 호환 레이어 구현 방안 확정 시 Answered 처리.
- 상태: Answered
- 처리 방안: trex-basic과 portfolio-hub를 동시에 변경·배포한다. 임시 호환 레이어 불필요.
- 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리.
- OQ-2: `name` mutable 정책 — 생성 이후 name 변경을 허용할지 여부. 허용할 경우 기존 CLI 스크립트 참조 안정성 확보 방안.
- 상태: Open
- 처리 방안: 사용자(Justin) 결정. 단순성을 위해 immutable을 기본 제안으로 두고 확인한다.
- 종료 조건: 사용자 확인 후 Answered 처리. 결과를 NFR 또는 FR에 반영.
- 상태: Answered
- 처리 방안: name 변경 허용. 단, 동일 사용자 내 같은 name이 존재하면 DB의 `(owner_user_id, name)` UNIQUE 제약으로 변경 거부. FR-3a에 반영 완료.
- 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리.
- OQ-3: HTTP API에서 `(username, name)` 조합으로 portfolio를 조회하는 엔드포인트 추가 여부 — 외부 API 소비자가 UUID를 직접 관리해야 하는 부담을 줄이기 위해 `/portfolios?user=alice&name=tv` 형태의 조회 경로가 필요한지.
- 상태: Open
- 처리 방안: 사용자(Justin) 결정. 현재 trex-basic은 GET /v1/assets로 UUID를 조회하므로 별도 엔드포인트 불필요할 수 있다.
- 종료 조건: 사용자 확인 후 Answered 또는 Deferred 처리.
- 상태: Answered
- 처리 방안: 추가하지 않음. HTTP API는 `portfolio_id`(UUID)만으로 조회. A-4, Out of Scope 반영 완료.
- 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리.
# 7. 초기 성공 기준
이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다.
@@ -179,15 +184,15 @@
# 8. REQ로 넘기기 전 확인 체크
REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.
- [ ] 범위(In Scope/Out of Scope) 확정
- [x] 범위(In Scope/Out of Scope) 확정
- 기준: OQ-3(HTTP API name 조회 엔드포인트) 결정 완료, Out of Scope 경계 승인
- [ ] 성공 지표의 측정 방식/데이터 출처 확정
- [x] 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~S-4 측정 방식이 pytest 또는 코드 리뷰로 추적 가능한 형태로 기술됨
- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정
- [x] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(마이그레이션 데이터 무결성), R-2(trex-basic 계약 파괴) 완화 방안 확정 및 담당자 지정
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: Justin 또는 지정된 승인자가 confirm-discovery skill로 Freeze 확정
# 9. 파일 처리 결과
@@ -207,15 +212,15 @@ REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지
사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.
## DECIDE
- {{DECIDE_NAME_MUTABILITY}}: `name` 컬럼의 변경 허용 여부 — immutable(생성 후 변경 불가) 또는 mutable(변경 허용, 단 확인 절차 필요) 중 선택 (OQ-2)
- {{DECIDE_TREX_COMPAT_STRATEGY}}: trex-basic 계약 호환성 전략 — portfolio-hub 단독 배포 후 trex-basic 후속 배포 허용 여부, 또는 임시 호환 레이어(name fallback 조회) 구현 필요 여부 (OQ-1)
- ~~{{DECIDE_NAME_MUTABILITY}}~~: **Answered** — name mutable 허용. 동일 사용자 내 중복 name 변경은 DB UNIQUE 제약으로 거부. FR-3a 반영 완료. (OQ-2 2026-05-11)
- ~~{{DECIDE_TREX_COMPAT_STRATEGY}}~~: **Answered** — trex-basic과 portfolio-hub 동시 배포로 확정. 임시 호환 레이어 불필요. (OQ-1 2026-05-11)
## CONFIRM
- {{CONFIRM_API_NAME_LOOKUP_SCOPE}}: HTTP API에서 `(username, name)` 조합 조회 엔드포인트 추가가 이번 반복 Out of Scope임을 확인 (OQ-3)
- {{CONFIRM_MIGRATION_STRATEGY}}: 기존 portfolio_id 문자열을 name으로 변환 없이 그대로 복사하는 마이그레이션 전략 확인 (A-3)
- ~~{{CONFIRM_API_NAME_LOOKUP_SCOPE}}~~: **Answered** — `(username, name)` 조합 엔드포인트 추가 안 함. HTTP API는 `portfolio_id`(UUID)만으로 조회. (OQ-3 2026-05-11)
- ~~{{CONFIRM_MIGRATION_STRATEGY}}~~: **Answered** — 기존 `portfolio_id` 문자열을 `name` 컬럼으로 변환 없이 그대로 복사. (A-3 2026-05-11)
## DATA
- {{DATA_NAME_ALLOWED_CHARS}}: `name` 컬럼에 허용할 문자 범위 정의 — 공백 허용 여부, 특수문자 허용 범위, 최대 길이 제한 등 (REQ 단계 결정 필요)
- ~~{{DATA_NAME_ALLOWED_CHARS}}~~: **Answered** — `name` 허용 문자 `[a-zA-Z0-9._:-]`(영숫자 + `.` `_` `:` `-`), 공백 불허, 1~100자, 대소문자 구분 있음. (2026-05-11)
# 11. Discovery Freeze
REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.
@@ -228,8 +233,8 @@ REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록
- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약)
## Freeze 플래그
- Handoff Decision: 보류
- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1(trex-basic 호환성), OQ-2(name mutable 여부), OQ-3(HTTP API name 조회 엔드포인트) 3개가 Open 상태이며 REQ 초안 작성 전 사용자 확인 필요.
- Ready for REQ Drafting: false
- Confirmed By: TBD
- Confirmed At (KST): TBD
- Handoff Decision: 승인
- Handoff Rationale: 모든 OQ Answered 완료, 모든 CONFIRM/DATA 항목 확정. confirm-discovery 게이트 통과 — REQ 초안 작성으로 인계 가능.
- Ready for REQ Drafting: true
- Confirmed By: Justin
- Confirmed At (KST): 2026-05-11
+1 -1
View File
@@ -9,4 +9,4 @@
| dcy-001_20260430_real-db-migration-validation-and-operations-readiness | 20260430 | 실DB 마이그레이션 검증 및 운영 준비 | [Discovery](./dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md) | confirmed |
| dcy-002_20260502_trex-basic-consumer-api-contract | 20260502 | trex-basic Consumer-Driven Contract 반영 — service-token runtime_id 발급 및 v1 API 엔드포인트 구현 | [Discovery](./dcy-002_20260502_trex-basic-consumer-api-contract.md) | confirmed |
| dcy-003_20260502_portfolio-list-user-required | 20260502 | CLI `pfhub portfolio list` — `--user` 옵션 필수화 및 소유자 기반 조회 강제 | [Discovery](./dcy-003_20260502_portfolio-list-user-required.md) | confirmed |
| dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution | 20260511 | portfolio_id UUID 교체 및 CLI 사용자 범위 기반 자산 식별 구조 전환 | [Discovery](./dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md) | draft |
| dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution | 20260511 | portfolio_id UUID 교체 및 CLI 사용자 범위 기반 자산 식별 구조 전환 | [Discovery](./dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md) | confirmed |