Files
portfolio-hub/docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md
T
genichin ce63c75c9c feat(V1-API): implement TREX-Basic consumer API contract with encryption and persistence
- Add V1 API endpoints: assets, ledger, user-info
- Implement Fernet-based credential payload encryption (req-010)
- Extend health response schema with runtime info (req-004)
- Add service token runtime ID issuance (req-005)
- Implement database schema migration for V1 API (req-009)
- Add comprehensive test coverage for V1 endpoints
- Update SRS with 10 new requirements (REQ-004 through REQ-011)
- Create batch planning, design, and verification for BAT-001
- Integrate review-discovery StagePilot skill
- Update documentation indices and baseline specs
2026-05-02 09:06:15 +00:00

240 lines
24 KiB
Markdown

# 0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: confirmed
- 문서 ID: dcy-002_20260502_trex-basic-consumer-api-contract
- 이슈명: trex-basic Consumer-Driven Contract 반영 — service-token runtime_id 발급 및 v1 API 엔드포인트 구현
- 작성 시각(KST): 2026-05-02 00:00
- 마지막 갱신 시각(KST): 2026-05-02 03:00
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011
# 1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
- 연계 문서 현황:
- 관련 계약 문서: temp/portfolio-hub-api-contract.md (trex-basic 팀 작성 Consumer-Driven Contract)
- 관련 TODO/메모: 없음
- 해석:
- trex-basic 팀이 portfolio-hub에 Consumer-Driven Contract를 전달했고, 4개 엔드포인트(GET /health, GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades)를 구현해야 한다. Q1(service-token 인증)과 Q2(runtime_id 발급 절차)는 이 Discovery에서 결정 사항으로 반영한다.
- 현재 `/health`는 `status: ok`만 반환하고 `/v1/*` 엔드포인트는 전혀 없다. service-token 메커니즘(`token_type='service'`)은 이미 구현돼 있으나 `runtime_id` 개념이 없고, 브로커 인증 정보 저장 테이블도 없다.
# 2. 요구사항 판정 결과
이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.
- 판정: 신규
- 근거
- 기존 dcy-001은 로컬 DB 마이그레이션 검증과 운영 준비를 다루며 외부 소비자 API 계약과는 완전히 별개 주제다.
- `GET /v1/assets`, `GET /v1/user_info`, `POST /v1/assets/{asset_id}/ledger/trades`는 기존 어떤 Discovery/REQ에도 등장하지 않는 신규 엔드포인트다.
# 3. 문제점의 요약
문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다.
- 현재 상태
- `GET /health`는 `app/api/health.py`에서 `{"status": "ok"}`만 반환하며 계약이 요구하는 `service`, `version`, `checked_at` 필드가 없다.
- service-token 메커니즘은 migration 0013(`20260423_0013_remove_bot_principals_service_tokens.py`)에서 `token_type='service'`로 완성됐고, CLI `pfhub service-token issue/list/revoke`도 `app/cli/commands/service_tokens.py`에 구현돼 있다. 그러나 발급 결과에 `runtime_id`가 포함되지 않는다.
- `Portfolio` 모델(`app/domain/models.py`)에 `portfolio_id`, `broker`, `status`, `symbol`, `cash_balance`, `asset_quantity`, `active_strategy_id`가 있어 `/v1/assets` 응답 필드 일부와 대응하지만, `market`(KOSPI/KOSDAQ) 필드와 `strategy_params` JSON이 없다.
- `StrategySet` 모델에 `config`(문자열) 필드가 있어 strategy_params 원본으로 사용 가능하나, Portfolio와의 연결 시 `strategy_params` 역할을 하는 구체적 구현은 없다.
- `GET /v1/user_info`가 요구하는 `runtime_id`, `trading_enabled`, `brokers[].credential_payload` 데이터는 현재 어떤 테이블에도 존재하지 않는다.
- `POST /v1/assets/{asset_id}/ledger/trades`가 요구하는 `ledger_trades` 테이블이 없다.
- 기대 상태
- `GET /health`는 `status`, `service`, `version`, `checked_at`을 모두 반환한다.
- `pfhub service-token issue` CLI와 대응 API 엔드포인트가 service-token과 함께 `runtime_id`를 생성·반환한다. 발급된 service-token으로 `/v1/*` 엔드포인트를 인증할 수 있고, `GET /v1/user_info` 응답에도 해당 `runtime_id`가 포함된다.
- `GET /v1/assets`는 service-token bearer 인증으로 관리 자산 목록(portfolio → asset 뷰)과 전략 파라미터를 반환한다.
- `GET /v1/user_info`는 service-token으로 인증한 사용자의 거래 가능 여부와 브로커별 credential_payload를 반환한다.
- `POST /v1/assets/{asset_id}/ledger/trades`는 체결 내역을 idempotent하게 기록하고, 중복 수신 시 `status=duplicate`를 반환한다.
- 이해관계자
- trex-basic 팀 (소비자, Consumer-Driven Contract 작성자)
- portfolio-hub 백엔드 유지보수 담당자 (구현 주체)
- REQ 승인자
- 제약사항
- service-token 인증은 기존 `token_type='service'` 메커니즘을 그대로 사용한다. 별도 인증 체계를 새로 만들지 않는다.
- `GET /v1/user_info`는 `environment` 필드를 노출하지 않는다. portfolio-hub는 항상 live credential을 반환하며 paper/live 분기를 두지 않는다.
- 기존 Alembic revision chain을 깨지 않는 IF NOT EXISTS 기반 migration을 추가한다(dcy-001/REQ-002 정책 준수).
## 주요 사용자 시나리오
- trex-basic 데몬이 시작될 때 `GET /health`로 portfolio-hub 가용성을 확인하고, `GET /v1/assets?status=active`로 관리 자산 목록과 전략 파라미터를 읽은 뒤, `GET /v1/user_info`로 거래 가능 여부와 브로커 인증 정보를 획득해 거래를 시작한다.
- 운영자가 `pfhub service-token issue <name> --user <username>` CLI로 service-token과 runtime_id를 한 번에 발급하고, 결과를 trex-basic 데몬 환경 변수에 주입한다. 또는 API 엔드포인트를 통해 동일한 발급을 수행한다.
- trex-basic이 KIS 브로커로부터 체결을 확인한 뒤 `POST /v1/assets/{asset_id}/ledger/trades`로 체결 내역을 전송한다. 네트워크 오류로 재전송하더라도 동일 `(asset_id, order_id, execution_id)` 조합이면 중복 기록 없이 `status=duplicate`를 응답 받는다.
# 4. 이번에 정의할 변경
이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다.
- `GET /health` 응답 스키마 확장: `service`, `version`, `checked_at` 필드 추가
- service-token 발급 시 `runtime_id` 동시 생성 및 반환: CLI(`pfhub service-token issue`)와 API 엔드포인트 양쪽에서 `runtime_id`를 함께 발급. `runtime_id`는 발급된 service-token에 연결되어 `GET /v1/user_info` 응답에서 그대로 반환
- `/v1/assets`, `/v1/user_info`, `/v1/assets/{asset_id}/ledger/trades` 엔드포인트 신규 구현: 브로커 인증 정보 저장 테이블(`broker_credentials`), 거래 내역 테이블(`ledger_trades`), `portfolios` 테이블에 `market` 컬럼 추가를 포함한 DB 변경과 함께 구현
## 영향 범위
변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.
- 코드 영향:
- `app/api/health.py`: 응답 스키마 확장
- `app/api/` 신규 라우터: `v1_assets.py`, `v1_user_info.py`, `v1_ledger.py` (또는 통합 `v1.py`)
- `app/cli/commands/service_tokens.py`: `issue` 커맨드에 `runtime_id` 생성·출력 추가
- `app/domain/models.py`: `BrokerCredential`, `LedgerTrade` 도메인 모델 추가
- `app/repositories/postgres.py`: 브로커 인증 정보 및 ledger 관련 DB 연산 추가
- `app/services/`: `v1_service.py` 또는 기존 서비스에 assets/user_info/ledger 메서드 추가
- `app/schemas/`: v1 응답 스키마 추가
- `migrations/versions/`: `market` 컬럼, `runtime_id` 연결, `broker_credentials`, `ledger_trades` 테이블 migration 추가
- `app/main.py`: `/v1` prefix 라우터 등록
- 문서 영향:
- `docs/interface-contract.md`: trex-basic 소비 엔드포인트 계약 기준 갱신 필요
- `docs/data-model.md`: `broker_credentials`, `ledger_trades`, `runtime_id` 연결 모델 추가 필요
- 운영 영향:
- service-token 발급 절차 변경 — `runtime_id`가 함께 반환되므로 기존 운영 가이드 갱신 필요
- 브로커 인증 정보(credential_payload)는 민감 데이터이므로 DB 수준 보안과 접근 정책 검토 필요
# 5. 요구사항 목록
실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.
## 기능 요구사항
- FR-1: `GET /health` 응답에 `status`, `service`(고정값 `"portfolio-hub"`), `version`(앱 버전), `checked_at`(ISO 8601 KST) 필드가 포함되어야 한다. `status` 허용값은 `ok`, `degraded`, `failed`이며 정상 응답은 `ok`를 반환해야 한다.
- FR-2: `pfhub service-token issue <name> --user <username>` CLI 커맨드와 대응하는 API 엔드포인트(POST /auth/service-tokens 또는 기존 POST /auth/tokens 확장)가 service-token 문자열과 함께 `runtime_id`(고유 식별자)를 생성·반환해야 한다. `runtime_id`는 service-token에 연결되어 DB에 저장되고, 해당 토큰으로 인증한 `GET /v1/user_info` 호출에서 그대로 반환되어야 한다.
- FR-3: `GET /v1/assets`는 service-token bearer 인증으로 토큰 소유자(user)의 portfolio 목록을 asset 뷰로 반환해야 한다. 응답 항목은 `asset_id`, `symbol`, `market`, `status`, `cash`, `quantity`, `broker_type`, `strategy_id`, `strategy_params`, `updated_at`을 포함해야 하며, `status=active` query parameter로 필터링이 가능해야 한다. `strategy_params`는 해당 portfolio의 StrategySet config에서 파생한다. `strategy_id`는 서버에서 허용값 검증을 수행하지 않으며, 등록된 모든 값을 그대로 반환한다(OQ-1 Answered).
- FR-4: `GET /v1/user_info`는 service-token bearer 인증으로 해당 사용자의 거래 가능 여부(`trading_enabled`, `disabled_reason`)와 브로커별 인증 정보 목록(`brokers[]`)을 반환해야 한다. `brokers[]`의 각 항목은 `broker`, `account_ref`, `credential_payload`, `credential_version`, `last_rotated_at`을 포함해야 한다. `runtime_id`는 인증 service-token에 연결된 값을 반환해야 한다.
- FR-5: `POST /v1/assets/{asset_id}/ledger/trades`는 service-token bearer 인증으로 체결 내역을 기록해야 한다. 요청 본문(`execution_id`, `order_id`, `side`, `quantity`, `price`, `fee_amount`, `tax_amount`, `executed_at`)을 `ledger_trades` 테이블에 저장하고 201을 반환해야 한다. `(asset_id, order_id, execution_id)` 동일 조합의 재수신은 신규 행 없이 `status=duplicate`와 기존 `ledger_entry_id`를 200으로 반환해야 한다. 동일 키에 payload가 다른 경우 409를 반환해야 한다.
## 비기능 요구사항
성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다.
- NFR-1: `/v1/user_info`는 `environment` 필드를 포함하지 않는다. portfolio-hub는 항상 live credential을 반환하며, paper/live 분기 로직을 서버에서 관리하지 않는다.
- NFR-2: `POST /v1/assets/{asset_id}/ledger/trades`의 idempotency 키는 `(asset_id, order_id, execution_id)` 복합 유니크 제약으로 DB에서 보장해야 한다. 애플리케이션 로직만으로 보장하지 않는다.
- NFR-3: 브로커 인증 정보(`credential_payload`)는 DB에 평문 저장을 금지한다. Python `cryptography` 패키지의 Fernet(AES-128-CBC + HMAC-SHA256)으로 저장 전 암호화하고 조회 시 복호화한다. 암호화 키는 환경변수 `CREDENTIAL_ENCRYPTION_KEY`(base64 인코딩 32바이트)로 주입하며, 키가 없으면 서비스 기동을 거부해야 한다(OQ-3 Answered).
- NFR-4: 신규 migration은 기존 `ensure_schema()` 런타임 경로와 충돌하지 않도록 IF NOT EXISTS 패턴을 사용해야 한다(REQ-002 준수).
## 범위 경계
이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.
- In Scope
- `GET /health` 응답 스키마 확장
- service-token 발급에 `runtime_id` 동시 생성 (CLI + API 양쪽)
- `GET /v1/assets` 엔드포인트 구현 (portfolio → asset 뷰 매핑, strategy_params 포함)
- `GET /v1/user_info` 엔드포인트 구현 (runtime_id, trading_enabled, brokers[].credential_payload 포함)
- `POST /v1/assets/{asset_id}/ledger/trades` 엔드포인트 구현 (idempotency 포함)
- 필요한 DB migration 추가 (market 컬럼, runtime_id 연결, broker_credentials 테이블, ledger_trades 테이블)
- Out of Scope
- trex-basic 측 코드 변경 — portfolio-hub API 구현만 담당
- paper/live 환경 분기 로직 — portfolio-hub는 live credential만 반환
- rate limiting 구현 — trex-basic 계약의 Q6이지만 이번 반복에서 요구되지 않음; 향후 반복으로 Deferred
- `strategy_id` 허용값 검증 로직 — 검증 없이 통과하는 것으로 확정(OQ-1 Answered). 화이트리스트 불필요
- KIS 외 다른 브로커의 credential_payload 스키마 정의 — Q4, 향후 반복으로 Deferred
- mock 서버 구현 — trex-basic 팀 자체 `scripts/mock_portfolio_hub.py` 활용
# 6. 리스크/가정 목록
불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다.
## 리스크
- R-1 (High): `broker_credentials`의 `credential_payload`(KIS app_key/app_secret)는 민감 데이터다. DB에 평문 저장 시 보안 사고 위험이 있다.
- 완화 방안: 앱 레벨 Fernet 암호화(옵션 A)로 확정(OQ-3 Answered). `CREDENTIAL_ENCRYPTION_KEY` 환경변수 없이 서비스가 기동되지 않도록 startup validation을 구현한다.
- R-2 (Medium): `GET /v1/assets`에서 `strategy_params`를 `StrategySet.config` JSON에서 파생할 때, config 스키마가 전략별로 다르면 응답 일관성이 깨질 수 있다.
- 완화 방안: 설계 단계에서 strategy_params 파생 규칙을 확정한다. config가 없거나 파싱 실패 시 빈 객체(`{}`)를 반환하고 로그를 남기는 안전한 폴백을 구현한다.
- R-3 (Medium): `portfolios` 테이블에 `market` 컬럼이 없어 기존 portfolio 데이터에 market 값이 없다. migration 후 기존 row의 market 값이 NULL 상태로 남는다.
- 완화 방안: migration에서 `market` 컬럼을 nullable로 추가한다(기본값 없음, DECIDE_MARKET_DEFAULT Answered). NULL인 경우 API 응답에서 `null`을 그대로 반환하며, trex-basic 클라이언트가 null을 처리하도록 한다.
- R-4 (Low): `GET /v1/user_info` 호출 시 service-token에 연결된 `runtime_id`가 없는 경우(기존 발급 토큰), 응답 처리 방식이 결정되지 않았다.
- 완화 방안: 기존 service-token은 `runtime_id`가 없으므로 404 또는 409 응답으로 처리하거나, 런타임에 즉시 `runtime_id`를 생성해 연결하는 방안을 설계 단계에서 결정한다.
## 가정
아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.
- 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 식별자 체계를 만들지 않는다.
- A-4: `trading_enabled`는 현재 단계에서 `users.is_active`와 연동하는 단순 매핑으로 구현한다. 별도 trading 전용 상태 필드는 이번 반복 Out of Scope다.
- A-5: trex-basic이 사용하는 KIS 브로커만 우선 지원하며, credential_payload 스키마는 `{"app_key": "...", "app_secret": "..."}` 형식으로 고정한다.
## 오픈 질문
답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.
- OQ-1: `strategy_id` 허용값 목록 — trex-basic 첫 실전 전략 ID가 확정되지 않았다. 현재 StrategySet에 있는 전략 ID 값을 검증 없이 통과시킬 것인지, 화이트리스트를 만들 것인지.
- 상태: Answered
- 처리 방안: `strategy_id`는 어떤 값을 사용해도 가능하도록 서버에서 검증 없이 통과한다. 화이트리스트를 두지 않는다. FR-3에 반영.
- 종료 조건: 완료.
- OQ-2: `GET /health` `degraded` 상태 정의 — degraded 시 후속 엔드포인트를 계속 시도할지 여부. portfolio-hub 내부 degraded 판단 기준이 없다.
- 상태: Answered
- 처리 방안: `degraded` 값은 응답 스키마에 유효한 상태로 포함한다(FR-1 허용값 유지). 단, 이번 반복에서는 해당 상태를 반환하는 트리거 조건을 정의하지 않으며, 트리거 조건은 추후 구현 시 정의한다.
- 종료 조건: 완료.
- OQ-3: `credential_payload` DB 저장 암호화 방식 — `broker_credentials.credential_payload`는 KIS `app_key`/`app_secret`을 담는 JSON으로, portfolio-hub PostgreSQL DB에 저장된다. 평문 저장 시 DB 덤프·직접 접근 경로에서 인증 정보가 노출된다(R-1). 아래 세 가지 방식 중 하나를 선택해야 한다.
- **옵션 A — 앱 레벨 AES 암호화 (Fernet)**: Python `cryptography` 패키지로 저장 전 암호화, 조회 시 복호화. 암호화 키는 환경변수 1개(`CREDENTIAL_ENCRYPTION_KEY`)로 주입. 인프라 변경 없음. 구현 복잡도 낮음. psql에서 payload 직접 읽기 불가. 현재 스택에 가장 단순하게 추가 가능.
- **옵션 B — pgcrypto 컬럼 암호화**: PostgreSQL `pgcrypto` extension 활성화 후 `pgp_sym_encrypt`/`pgp_sym_decrypt`로 DB 내 암/복호화. 키는 쿼리 파라미터로 전달하거나 환경변수에서 주입. Docker Compose 환경에서 extension 활성화 필요. 앱 레벨 처리 없이 DB에서 완결되지만, 쿼리마다 키를 파라미터로 전달하는 패턴이 필요해 구현 복잡도가 옵션 A보다 높음.
- **옵션 C — 외부 시크릿 관리자 (Vault, AWS Secrets Manager 등)**: DB에는 시크릿 참조 URI만 저장하고, 실제 credential은 외부 서비스에서 조회. 가장 강력하지만 별도 인프라 운영이 필요하며 현재 프로젝트 규모 대비 과잉 투자.
- 상태: Answered
- 처리 방안: **옵션 A(앱 레벨 AES/Fernet)** 채택. Python `cryptography` 패키지로 저장 전 암호화, 조회 시 복호화. 암호화 키는 환경변수 `CREDENTIAL_ENCRYPTION_KEY`로 주입. NFR-3과 R-1 완화 방안에 반영.
- 종료 조건: 완료.
# 7. 초기 성공 기준
이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다.
- S-1: `GET /health` 응답에 `status`, `service`, `version`, `checked_at` 필드가 모두 존재하고, `checked_at`이 ISO 8601 형식을 따른다.
- S-2: `pfhub service-token issue <name> --user <username>` 실행 결과에 `token`과 `runtime_id`가 모두 포함되고, 대응 API 엔드포인트로도 동일하게 발급된다. 발급된 token으로 `GET /v1/user_info`를 호출하면 동일 `runtime_id`가 응답에 포함된다.
- S-3: `GET /v1/assets`가 service-token 인증으로 portfolio 목록을 asset 뷰로 반환하고, 각 항목에 `strategy_params` 객체가 포함된다. `status=active` 필터가 정상 동작한다.
- S-4: `GET /v1/user_info`가 `trading_enabled`, `runtime_id`, `brokers[].credential_payload`를 반환하며 `environment` 필드를 포함하지 않는다.
- S-5: 동일 `(asset_id, order_id, execution_id)` 조합으로 `POST /v1/assets/{asset_id}/ledger/trades`를 두 번 호출할 때 첫 번째는 201, 두 번째는 200+`status=duplicate`를 반환하고 DB에는 단일 행만 존재한다.
## 측정 방식 및 데이터 출처
- S-1 측정 방식: 자동화 테스트(pytest)로 `/health` 응답 JSON 필드 검증 / 데이터 출처: `tests/test_app.py` 또는 신규 `tests/test_health.py`
- S-2 측정 방식: CLI 출력 파싱 + API 응답 JSON 검증 / 데이터 출처: `tests/test_service_tokens.py`(신규) 또는 `tests/test_auth.py` 확장
- S-3 측정 방식: `GET /v1/assets` 응답 스키마 및 필터 동작 pytest 검증 / 데이터 출처: `tests/test_v1_assets.py`(신규)
- S-4 측정 방식: `GET /v1/user_info` 응답 JSON 필드 존재·비존재 검증 / 데이터 출처: `tests/test_v1_user_info.py`(신규)
- S-5 측정 방식: 동일 payload 2회 POST 후 응답 코드·status 및 DB row count 검증 / 데이터 출처: `tests/test_v1_ledger.py`(신규)
# 8. REQ로 넘기기 전 확인 체크
REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.
- [x] 범위(In Scope/Out of Scope) 확정
- 기준: In Scope/Out of Scope 경계가 명확하고 trex-basic 계약 범위와 일치
- [x] 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1 ~ S-5 모두 pytest 검증 가능한 형태로 기술됨
- [x] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(credential_payload 암호화) OQ-3 Answered — 옵션 A(Fernet) 확정
- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ-1, OQ-2, OQ-3 모두 Answered
- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: Confirmed By = Justin, confirm-discovery skill 확인 완료
# 9. 파일 처리 결과
이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다.
- 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md
- 참조 문서:
- temp/portfolio-hub-api-contract.md (trex-basic Consumer-Driven Contract 원문)
- docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md (선행 Discovery)
- app/api/health.py
- app/cli/commands/service_tokens.py
- app/domain/models.py
- migrations/versions/20260423_0013_remove_bot_principals_service_tokens.py
# 10. 사용자 결정 필요 항목 요약
사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.
## DECIDE
- ~~{{DECIDE_CREDENTIAL_ENCRYPTION}}~~: 옵션 A(앱 레벨 Fernet/AES) 채택 확정 (OQ-3 Answered)
- ~~{{DECIDE_MARKET_DEFAULT}}~~: `portfolios.market` nullable 컬럼으로 추가, 기본값 없음, API 응답에서 null 그대로 반환 (Answered)
## CONFIRM
- ~~{{CONFIRM_RUNTIME_ID_SCOPE}}~~: `runtime_id`는 service-token 1개당 1개로 발급되며 해당 service-token에 연결된다. 동일 사용자의 다른 service-token은 각기 다른 `runtime_id`를 가진다. (A-2 Confirmed)
- ~~{{CONFIRM_TRADING_ENABLED_MAPPING}}~~: `trading_enabled`는 `users.is_active`와 단순 매핑으로 구현한다. 별도 trading 전용 상태 필드는 이번 반복 Out of Scope. (A-4 Confirmed)
## DATA
- ~~{{DATA_STRATEGY_ID_LIST}}~~: `strategy_id` 화이트리스트 불필요 — 검증 없이 통과하는 것으로 확정 (OQ-1 Answered)
- ~~{{DATA_BROKER_CREDENTIAL_SCHEMA_OTHER}}~~: KIS 외 다른 브로커의 `credential_payload` 스키마 — 이번 반복 Out of Scope로 확정. 향후 반복에서 별도 Discovery로 처리.
# 11. Discovery Freeze
REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.
## 섹션 참조
- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록)
- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록)
- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준)
- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크)
- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약)
## Freeze 플래그
- Handoff Decision: 승인
- Handoff Rationale: 모든 OQ(OQ-1, OQ-2, OQ-3)가 Answered 상태로 해소됨. CONFIRM 항목(runtime_id 발급 방식, trading_enabled 매핑)은 가정(A-2, A-4)으로 문서화되어 있으며 REQ 초안 작성 시 세부 검토 가능.
- Ready for REQ Drafting: true
- Confirmed By: Justin
- Confirmed At (KST): 2026-05-02 02:00