Files
portfolio-hub/docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md
T
genichin 829629fa8f 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
2026-05-11 09:27:57 +00:00

24 KiB

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 식별자 체계를 만들지 않는다.
    • [철회 — 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": "..."} 형식으로 고정한다.

오픈 질문

답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 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 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.

  • 범위(In Scope/Out of Scope) 확정
    • 기준: In Scope/Out of Scope 경계가 명확하고 trex-basic 계약 범위와 일치
  • 성공 지표의 측정 방식/데이터 출처 확정
    • 기준: S-1 ~ S-5 모두 pytest 검증 가능한 형태로 기술됨
  • High 영향 리스크 우선순위 및 해소 책임자 지정
    • 기준: R-1(credential_payload 암호화) OQ-3 Answered — 옵션 A(Fernet) 확정
  • 모든 오픈 질문(OQ) Open 상태 없음 확인
    • 기준: OQ-1, OQ-2, OQ-3 모두 Answered
  • 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 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.

섹션 참조

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