# 0. 문서 상태 이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - 상태: draft - 문서 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 - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 생성된 REQ 참조: 없음 # 1. 계획 상태 요약 현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - 연계 문서 현황: - 관련 계약 문서: docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md (A-3: asset_id는 portfolio_id를 그대로 사용) - 관련 TODO/메모: 없음 - 해석: - 현재 `portfolio_id`는 사용자가 직접 지정하는 전역 고유 문자열이어서 다중 사용자 환경에서 이름 충돌이 발생하고, CLI 뮤테이션 명령이 소유권 검증 없이 portfolio_id만으로 실행된다. - 이번 Discovery는 `portfolio_id`를 서버 생성 UUID로 교체하고 사용자 표시용 `name`을 `(owner_user_id, name)` 복합 유니크 제약으로 관리하는 구조 전환 전체 범위를 정의한다. # 2. 요구사항 판정 결과 이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - 판정: 신규 - 근거 - 기존 dcy-001은 DB 마이그레이션 검증, dcy-002는 trex-basic 소비자 API 계약, dcy-003은 portfolio list --user 필수화를 다루며, portfolio_id 식별자 체계 전환을 다루는 Discovery가 없다. - `portfolio_id`를 UUID로 교체하고 `(owner_user_id, name)` 복합 유니크 제약을 도입하는 것은 DB 스키마, 도메인 모델, CLI 인터페이스, HTTP API 경로, 소비자 계약 전반에 영향하는 독립적인 구조 변경 주제다. # 3. 문제점의 요약 문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - 현재 상태 - `portfolios.portfolio_id`는 TEXT PRIMARY KEY로 사용자가 직접 지정하는 전역 단일 네임스페이스다. alice가 `005930:tv`를 먼저 생성하면 bob은 동일 이름을 사용할 수 없다. - CLI 뮤테이션 명령(`buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `status`, `update`)은 `portfolio_id`를 Argument로만 받고 소유자 검증을 하지 않는다. 권한 있는 운영자가 다른 사용자 소유의 portfolio를 portfolio_id만 알면 조작할 수 있다. - `ledger_entries`(portfolio_ledger), `portfolio_comments`, `portfolio_tags`, `ledger_trades` 테이블이 `portfolio_id` TEXT를 FK로 참조한다. - dcy-002 A-3에 따라 `GET /v1/assets`의 `asset_id` 응답 필드가 `portfolio_id`를 그대로 반환하므로, trex-basic 데몬이 `asset_id`로 ledger 기록 등 후속 API를 호출한다. - `Portfolio` dataclass(`app/domain/models.py`)와 PostgreSQL 레포지터리 전반이 `portfolio_id` 문자열을 기준으로 설계되어 있다. - 기대 상태 - `portfolios.portfolio_id`는 서버가 발급하는 UUID(TEXT 또는 UUID 타입)로 교체된다. 사용자는 create 시 `name`만 지정하면 된다. - `name` 컬럼은 `(owner_user_id, name)` 복합 UNIQUE 제약으로 같은 사용자 내에서 유일하게 관리된다. 다른 사용자는 동일 `name`을 사용할 수 있다. - CLI 뮤테이션 명령은 `--user ` 조합으로 portfolio를 식별하고, 내부적으로 UUID를 조회해 처리한다. 소유권 검증이 명령 실행 경로에 포함된다. - HTTP API 경로는 UUID를 path parameter로 사용하며, `GET /v1/assets`의 `asset_id`도 UUID를 반환한다. - 기존 데이터는 마이그레이션으로 새 UUID를 할당받고, 기존 `portfolio_id` 문자열은 `name` 컬럼으로 이전된다. - 이해관계자 - portfolio-hub 유지보수 담당자 (구현 주체) - trex-basic 팀 (GET /v1/assets asset_id 필드 변경 영향을 받는 소비자) - CLI 운영자 (명령 인터페이스 변경 대상) - 제약사항 - 기존 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 단계에서 결정한다. ## 주요 사용자 시나리오 사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - 운영자가 `pfhub portfolio create tv --symbol 005930 --user alice --cash-balance 10000000 --force`로 포트폴리오를 생성하면 서버가 UUID를 부여하고, 이후 `pfhub portfolio buy tv --user alice --quantity 10 --price 71000 --force`로 alice의 `tv` 포트폴리오를 정확히 조작한다. bob이 같은 이름 `tv`를 가진 포트폴리오를 생성해도 독립된 레코드로 관리된다. - trex-basic 데몬이 `GET /v1/assets`를 호출하면 `asset_id`로 UUID를 수신하고, 이후 `POST /v1/assets/{asset_id}/ledger/trades`에 UUID를 사용해 체결 내역을 기록한다. UUID 도입으로 asset_id가 전역 유일하게 보장된다. - 기존에 `005930:tv`라는 portfolio_id로 운영 중인 데이터가 있을 때, 마이그레이션 후 기존 레코드의 `name`이 `005930:tv`로 이전되고 새 UUID가 `portfolio_id`로 할당된다. ledger, comments 등 FK 참조도 새 UUID로 함께 갱신된다. # 4. 이번에 정의할 변경 이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - `portfolios.portfolio_id` PRIMARY KEY를 서버 생성 UUID로 교체하고 `name` TEXT 컬럼을 신규 추가: `(owner_user_id, name)` 복합 UNIQUE 제약 적용, 기존 portfolio_id 문자열을 name으로 이전하는 데이터 마이그레이션 포함 - CLI 뮤테이션 명령 식별 방식 전환: 모든 뮤테이션 명령에서 `portfolio_id` Argument를 `` + `--user ` 조합으로 교체하고, 내부에서 `(owner_user_id, name)` → UUID 조회 후 처리 - HTTP API 경로 및 GET /v1/assets asset_id 전환: path parameter와 응답 필드를 UUID 기반으로 변경, dcy-002 A-3 철회 및 trex-basic 계약 갱신 ## 영향 범위 변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - 코드 영향: - `migrations/versions/`: portfolios PK 교체, name 컬럼 추가, (owner_user_id, name) UNIQUE 제약, 기존 데이터 이전 마이그레이션 - `app/domain/models.py`: Portfolio dataclass portfolio_id → UUID, name 필드 명확화 - `app/repositories/postgres.py`: create_portfolio(서버 UUID 생성), get_portfolio_by_name_and_user() 신규, 모든 portfolio_id 참조 업데이트 - `app/domain/services.py`: transfer, buy, sell 등 서비스 메서드 시그니처 — (user_id, name) → UUID 해석 추가 - `app/cli/commands/portfolio.py`: status, buy, sell, invest, transfer, reset, delete, update 명령의 Argument 교체 - `app/api/portfolios.py` (HTTP API): path parameter UUID 기반 전환 - `app/api/v1_assets.py`: asset_id 응답 필드를 UUID로 반환 - `app/schemas/portfolio.py`, `app/schemas/v1.py`: asset_id/portfolio_id 필드 타입 문서화 - 문서 영향: - `docs/data-model.md`: portfolios 테이블 스키마 변경 반영 - `docs/interface-contract.md`: GET /v1/assets asset_id 변경 반영 - `docs/project-structure.md`: 갱신 필요 (repositories, services 변경 반영) - `docs/runtime-flows.md`: 갱신 필요 (CLI 명령 흐름, v1 API 흐름 변경 반영) - `docs/discovery/dcy-002_*`: A-3 철회 주석 추가 - 운영 영향: - trex-basic 팀에게 asset_id 필드값이 UUID로 바뀜을 사전 통보 필요 - CLI 운영 스크립트에서 portfolio_id 직접 지정 방식을 `--user + name` 방식으로 교체 필요 - 기존 운영 runbook 갱신 필요 # 5. 요구사항 목록 실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. ## 기능 요구사항 - 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`)은 `` Argument와 `--user ` 옵션으로 portfolio를 식별한다. 내부에서 `(owner_user_id, name)` → `portfolio_id(UUID)`를 조회한 뒤 처리한다. `name` + `--user` 조합으로 portfolio를 찾지 못하면 명확한 오류를 반환한다. - 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 흐름 변경 반영). ## 비기능 요구사항 성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - 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에 기록해야 한다. ## 범위 경계 이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - In Scope - `portfolios.portfolio_id` UUID 교체 및 `name` 컬럼 추가 - `(owner_user_id, name)` 복합 UNIQUE 제약 - 기존 데이터 마이그레이션 (FK 참조 포함) - CLI 뮤테이션 명령 식별 방식 전환 (`--user + name`) - HTTP API path parameter UUID 전환 - `GET /v1/assets` asset_id UUID 반환 - `docs/project-structure.md`, `docs/runtime-flows.md` 갱신 - `docs/interface-contract.md`, `docs/data-model.md` 갱신 - Out of Scope - trex-basic 측 코드 변경 — portfolio-hub API 변경만 담당 - `name` 변경 이력 감사 로그 — 향후 반복으로 Deferred - `name` 허용 문자 유효성 검사 정책 — REQ 단계에서 결정 - HTTP API에서 `(username, name)` 조합으로 portfolio를 조회하는 엔드포인트 추가 — {{OUT_OF_SCOPE_API_NAME_LOOKUP}} 결정 필요 # 6. 리스크/가정 목록 불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. ## 리스크 - 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에서 처리한다. ## 가정 아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - 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 경로만 먼저 전환한다. ## 오픈 질문 답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - OQ-1: trex-basic 계약 호환성 전략 — `GET /v1/assets` asset_id가 UUID로 바뀔 때 trex-basic 측의 대응 준비 일정이 확정되기 전에 portfolio-hub를 먼저 배포할 수 있는지, 아니면 임시 호환 레이어(name으로 fallback 조회)가 필요한지. - 상태: Open - 처리 방안: trex-basic 팀과 협의 후 결정. 배포 순서 또는 호환 레이어 필요 여부를 REQ 단계 진입 전에 확정한다. - 종료 조건: trex-basic 팀 확인 완료 또는 임시 호환 레이어 구현 방안 확정 시 Answered 처리. - OQ-2: `name` mutable 정책 — 생성 이후 name 변경을 허용할지 여부. 허용할 경우 기존 CLI 스크립트 참조 안정성 확보 방안. - 상태: Open - 처리 방안: 사용자(Justin) 결정. 단순성을 위해 immutable을 기본 제안으로 두고 확인한다. - 종료 조건: 사용자 확인 후 Answered 처리. 결과를 NFR 또는 FR에 반영. - OQ-3: HTTP API에서 `(username, name)` 조합으로 portfolio를 조회하는 엔드포인트 추가 여부 — 외부 API 소비자가 UUID를 직접 관리해야 하는 부담을 줄이기 위해 `/portfolios?user=alice&name=tv` 형태의 조회 경로가 필요한지. - 상태: Open - 처리 방안: 사용자(Justin) 결정. 현재 trex-basic은 GET /v1/assets로 UUID를 조회하므로 별도 엔드포인트 불필요할 수 있다. - 종료 조건: 사용자 확인 후 Answered 또는 Deferred 처리. # 7. 초기 성공 기준 이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - S-1: `pfhub portfolio create tv --symbol 005930 --user alice --force` 실행 시 UUID가 portfolio_id로 발급되고, alice와 bob이 동일 name `tv`로 각각 portfolio를 생성할 수 있다. - S-2: `pfhub portfolio buy tv --user alice --quantity 10 --price 71000 --force`가 alice 소유의 `tv` portfolio에만 적용되고, `--user bob`으로는 alice의 portfolio를 조작할 수 없다. - S-3: 마이그레이션 후 기존 데이터의 `portfolio_ledger`, `portfolio_comments` 등 FK 참조 테이블에 고아 레코드(orphan)가 0건이고, `GET /v1/assets` 응답의 `asset_id`가 UUID 형식이다. - S-4: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`, `docs/interface-contract.md`가 변경 내용을 반영해 갱신된다. ## 측정 방식 및 데이터 출처 각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - S-1 측정 방식: pytest — portfolio create 후 portfolio_id가 UUID 형식(`re.match(r'^[0-9a-f-]{36}$', ...)`)임을 검증. alice/bob 동일 name 독립 생성 테스트 / 데이터 출처: `tests/test_cli.py` 또는 신규 `tests/test_portfolio_uuid.py` - S-2 측정 방식: pytest — `--user bob`으로 alice 소유 portfolio 조작 시 오류 반환 검증 / 데이터 출처: `tests/test_cli.py` 소유권 검증 케이스 - S-3 측정 방식: 마이그레이션 스크립트 내 검증 쿼리 실행 결과 0건 확인 + pytest `test_v1.py::test_v1_assets_*` asset_id UUID 형식 검증 / 데이터 출처: `migrations/versions/` 마이그레이션 스크립트, `tests/test_v1.py` - S-4 측정 방식: 각 baseline 문서에 CLI 흐름, repositories/services 인터페이스, v1 API asset_id UUID 내용이 포함됐는지 코드 리뷰로 확인 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`, `docs/interface-contract.md` # 8. REQ로 넘기기 전 확인 체크 REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - [ ] 범위(In Scope/Out of Scope) 확정 - 기준: OQ-3(HTTP API name 조회 엔드포인트) 결정 완료, Out of Scope 경계 승인 - [ ] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: S-1~S-4 측정 방식이 pytest 또는 코드 리뷰로 추적 가능한 형태로 기술됨 - [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: R-1(마이그레이션 데이터 무결성), R-2(trex-basic 계약 파괴) 완화 방안 확정 및 담당자 지정 - [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 - [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 기준: Justin 또는 지정된 승인자가 confirm-discovery skill로 Freeze 확정 # 9. 파일 처리 결과 이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - 처리 결과: 생성 - 생성 경로: docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md - 참조 문서: - docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md (A-3 철회 대상) - docs/discovery/dcy-003_20260502_portfolio-list-user-required.md (--user 필수화 선행 작업) - app/domain/models.py - app/cli/commands/portfolio.py - app/repositories/postgres.py - migrations/versions/20260403_0001_initial_portfolio_schema.py # 10. 사용자 결정 필요 항목 요약 사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. ## DECIDE - {{DECIDE_NAME_MUTABILITY}}: `name` 컬럼의 변경 허용 여부 — immutable(생성 후 변경 불가) 또는 mutable(변경 허용, 단 확인 절차 필요) 중 선택 (OQ-2) - {{DECIDE_TREX_COMPAT_STRATEGY}}: trex-basic 계약 호환성 전략 — portfolio-hub 단독 배포 후 trex-basic 후속 배포 허용 여부, 또는 임시 호환 레이어(name fallback 조회) 구현 필요 여부 (OQ-1) ## CONFIRM - {{CONFIRM_API_NAME_LOOKUP_SCOPE}}: HTTP API에서 `(username, name)` 조합 조회 엔드포인트 추가가 이번 반복 Out of Scope임을 확인 (OQ-3) - {{CONFIRM_MIGRATION_STRATEGY}}: 기존 portfolio_id 문자열을 name으로 변환 없이 그대로 복사하는 마이그레이션 전략 확인 (A-3) ## DATA - {{DATA_NAME_ALLOWED_CHARS}}: `name` 컬럼에 허용할 문자 범위 정의 — 공백 허용 여부, 특수문자 허용 범위, 최대 길이 제한 등 (REQ 단계 결정 필요) # 11. Discovery Freeze REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. ## 섹션 참조 - 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#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