# 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에서 갱신.