Files
portfolio-hub/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/design.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

5.5 KiB

BAT-001 Design

  • Status: draft
  • Batch: BAT-001

Architecture Summary

trex-basic Consumer-Driven Contract를 충족하기 위해 4개 계층에 걸쳐 변경한다.

  1. DB 레이어 (REQ-009): Alembic migration 1개 추가로 4개 스키마 변경
  2. 보안 레이어 (REQ-010): app/core/encryption.py 신규 — Fernet 암복호화 유틸리티, app/core/config.py startup validation
  3. 인증 확장 (REQ-005): service-token 발급 시 UUID v4 runtime_id 동시 생성
  4. v1 API 레이어 (REQ-004, 006, 007, 008): /v1 prefix APIRouter 신규, 기존 /health 응답 확장

Changed Areas

  • Docs:
    • docs/project-structure.md (신규 모듈 app/core/encryption.py, app/api/v1_*.py 반영)
    • docs/runtime-flows.md (v1 API 요청 흐름, runtime_id 발급 흐름 추가)
  • Modules:
    • app/core/encryption.py (신규)
    • app/core/config.py (CREDENTIAL_ENCRYPTION_KEY 필드 + startup validation)
    • app/domain/models.py (AuthToken.runtime_id, BrokerCredential, LedgerTrade 추가)
    • app/api/health.py (HealthResponse 스키마 적용, checked_at/service/version 필드 추가)
    • app/api/auth_routes.py (service-token 발급 응답에 runtime_id 포함)
    • app/api/v1_assets.py (신규)
    • app/api/v1_user_info.py (신규)
    • app/api/v1_ledger.py (신규)
    • app/main.py (v1 router include 추가)
    • app/cli/commands/service_tokens.py (runtime_id 출력 추가)
    • app/services/auth_service.py (runtime_id 생성·저장 로직)
    • app/schemas/ (HealthResponse, v1 Assets/UserInfo/Ledger 스키마 추가)
    • app/repositories/postgres.py (broker_credentials, ledger_trades, runtime_id 쿼리 추가)
    • migrations/versions/ (신규 migration 파일)
  • Interfaces:
    • GET /health: 응답 필드 추가 (service, version, checked_at) — 기존 status 필드 유지로 하위 호환
    • POST /auth/service-tokens (또는 동등 경로): 응답에 runtime_id 추가
    • GET /v1/assets: 신규
    • GET /v1/user_info: 신규
    • POST /v1/assets/{asset_id}/ledger/trades: 신규

Key Decisions

  1. v1 라우터 구조: app/api/v1_*.py 파일 3개를 각각 별도 모듈로 작성하고 app/main.py에서 prefix="/v1"로 일괄 등록한다. 기존 /portfolios, /auth, /users 라우터와 prefix가 겹치지 않는다.

  2. R-4 처리 (runtime_id 없는 구형 service-token): GET /v1/user_info 호출 시 해당 토큰에 runtime_id가 없으면 HTTP 404 (detail: "runtime_id not assigned to this token")를 반환한다. 즉시 생성 방식은 채택하지 않는다 — 기존 토큰 소유자에게 재발급을 요구하는 것이 명시적이고 안전하다.

  3. disabled_reason: users.is_active = false인 경우 고정값 "account_inactive"를 반환한다. is_active = true인 경우 disabled_reason은 null로 반환한다.

  4. Fernet startup validation 위치: app/core/config.py의 Settings 클래스 model_post_init 또는 FastAPI lifespan 시작 시 수행한다. CREDENTIAL_ENCRYPTION_KEY가 없으면 RuntimeError를 발생시켜 기동을 중단한다. 테스트 환경에서는 CREDENTIAL_ENCRYPTION_KEY 환경변수를 픽스처로 주입한다.

  5. runtime_id 포맷: str(uuid.uuid4()) — 하이픈 포함 UUID v4 문자열 (예: "550e8400-e29b-41d4-a716-446655440000").

  6. broker_credentials 컬럼 타입:

    • user_id: INTEGER NOT NULL (users.user_id FK)
    • broker: TEXT NOT NULL
    • account_ref: TEXT NOT NULL
    • encrypted_payload: TEXT NOT NULL
    • credential_version: INTEGER NOT NULL DEFAULT 1
    • last_rotated_at: TIMESTAMPTZ NOT NULL DEFAULT NOW()
    • PRIMARY KEY: (user_id, broker)
  7. ledger_trades 컬럼 타입:

    • ledger_entry_id: BIGSERIAL PRIMARY KEY
    • asset_id: INTEGER NOT NULL (portfolios.portfolio_id 참조, FK 선택적)
    • order_id: TEXT NOT NULL
    • execution_id: TEXT NOT NULL
    • side: TEXT NOT NULL (buy/sell)
    • quantity: NUMERIC(20, 8) NOT NULL
    • price: NUMERIC(20, 8) NOT NULL
    • fee_amount: NUMERIC(20, 8) NOT NULL DEFAULT 0
    • tax_amount: NUMERIC(20, 8) NOT NULL DEFAULT 0
    • executed_at: TIMESTAMPTZ NOT NULL
    • recorded_at: TIMESTAMPTZ NOT NULL DEFAULT NOW()
    • UNIQUE: (asset_id, order_id, execution_id)
  8. 앱 버전: app/core/config.py의 Settings.app_version 값을 GET /health 응답의 version 필드로 사용한다. 현재 값: "0.1.0".

  9. checked_at 타임존: KST (UTC+9) ISO 8601 문자열로 반환한다. datetime.now(timezone(timedelta(hours=9))).isoformat().

Edge Cases

  • strategy_params 파싱 실패(StrategySet.config가 null이거나 유효하지 않은 JSON): {} 폴백, 에러 전파 없음.
  • ledger_trades 중복 삽입 시 DB unique constraint 위반: psycopg UniqueViolation 캐치 → 기존 행 조회 → payload 비교 → 동일이면 200+duplicate, 다르면 409.
  • broker_credentials가 없는 사용자의 GET /v1/user_info: brokers: [] 반환.
  • CREDENTIAL_ENCRYPTION_KEY 환경변수 없이 테스트 실행 시: 테스트 픽스처에서 Fernet.generate_key()로 임시 키 주입.

Architecture Impact

  • both (project-structure + runtime-flows)

Reference Doc Update Plan

  • project-structure.md: 신규 모듈(app/core/encryption.py, app/api/v1_assets.py, app/api/v1_user_info.py, app/api/v1_ledger.py) 반영 필요. implementation 완료 후 REQ-011에서 갱신.
  • runtime-flows.md: v1 API 요청 처리 흐름(service-token 인증 → 서비스 → DB), runtime_id 발급 흐름 추가 필요. implementation 완료 후 REQ-011에서 갱신.