- 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
5.5 KiB
BAT-001 Design
- Status: draft
- Batch: BAT-001
Architecture Summary
trex-basic Consumer-Driven Contract를 충족하기 위해 4개 계층에 걸쳐 변경한다.
- DB 레이어 (REQ-009): Alembic migration 1개 추가로 4개 스키마 변경
- 보안 레이어 (REQ-010):
app/core/encryption.py신규 — Fernet 암복호화 유틸리티,app/core/config.pystartup validation - 인증 확장 (REQ-005): service-token 발급 시 UUID v4 runtime_id 동시 생성
- v1 API 레이어 (REQ-004, 006, 007, 008):
/v1prefix 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
-
v1 라우터 구조:
app/api/v1_*.py파일 3개를 각각 별도 모듈로 작성하고app/main.py에서prefix="/v1"로 일괄 등록한다. 기존/portfolios,/auth,/users라우터와 prefix가 겹치지 않는다. -
R-4 처리 (runtime_id 없는 구형 service-token): GET /v1/user_info 호출 시 해당 토큰에 runtime_id가 없으면 HTTP 404 (
detail: "runtime_id not assigned to this token")를 반환한다. 즉시 생성 방식은 채택하지 않는다 — 기존 토큰 소유자에게 재발급을 요구하는 것이 명시적이고 안전하다. -
disabled_reason:
users.is_active = false인 경우 고정값"account_inactive"를 반환한다.is_active = true인 경우disabled_reason은null로 반환한다. -
Fernet startup validation 위치:
app/core/config.py의Settings클래스model_post_init또는 FastAPIlifespan시작 시 수행한다.CREDENTIAL_ENCRYPTION_KEY가 없으면RuntimeError를 발생시켜 기동을 중단한다. 테스트 환경에서는CREDENTIAL_ENCRYPTION_KEY환경변수를 픽스처로 주입한다. -
runtime_id 포맷:
str(uuid.uuid4())— 하이픈 포함 UUID v4 문자열 (예:"550e8400-e29b-41d4-a716-446655440000"). -
broker_credentials 컬럼 타입:
user_id: INTEGER NOT NULL (users.user_id FK)broker: TEXT NOT NULLaccount_ref: TEXT NOT NULLencrypted_payload: TEXT NOT NULLcredential_version: INTEGER NOT NULL DEFAULT 1last_rotated_at: TIMESTAMPTZ NOT NULL DEFAULT NOW()- PRIMARY KEY:
(user_id, broker)
-
ledger_trades 컬럼 타입:
ledger_entry_id: BIGSERIAL PRIMARY KEYasset_id: INTEGER NOT NULL (portfolios.portfolio_id 참조, FK 선택적)order_id: TEXT NOT NULLexecution_id: TEXT NOT NULLside: TEXT NOT NULL (buy/sell)quantity: NUMERIC(20, 8) NOT NULLprice: NUMERIC(20, 8) NOT NULLfee_amount: NUMERIC(20, 8) NOT NULL DEFAULT 0tax_amount: NUMERIC(20, 8) NOT NULL DEFAULT 0executed_at: TIMESTAMPTZ NOT NULLrecorded_at: TIMESTAMPTZ NOT NULL DEFAULT NOW()- UNIQUE:
(asset_id, order_id, execution_id)
-
앱 버전:
app/core/config.py의Settings.app_version값을GET /health응답의version필드로 사용한다. 현재 값:"0.1.0". -
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 위반: psycopgUniqueViolation캐치 → 기존 행 조회 → 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에서 갱신.