Files
portfolio-hub/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md
T
genichin 3509606c7e feat(REQ-005): implement TrEx Basic V1 API contract with auth, assets, ledger, and user endpoints
- Add V1 API routes for assets, ledger, and user info with complete CRUD operations
- Implement authentication scopes and request validation via auth_routes.py
- Extend schemas with V1-specific models for accounts, holdings, and positions
- Add database queries and aggregations for P&L, performance, and account data
- Include comprehensive test coverage for all V1 endpoints and integration flows
- Update batch and release documentation with implementation and verification evidence
- Sync project structure and runtime flows per V1 API contract requirements

Closes implementation phase of BAT-001 (TrEx Basic V1 API Contract)
2026-05-02 11:37:42 +00:00

24 KiB

BAT-001 Verification

  • Status: verified
  • Batch: BAT-001
  • Last Updated (KST): 2026-05-01

Acceptance Mapping

REQ-004: GET /health 응답 스키마 확장

# Acceptance Criterion Evidence Result
AC1 status/service/version/checked_at 필드 모두 존재 tests/test_v1.py::test_health_returns_full_response — 4개 필드 전체 assert PASS
AC2 status 허용값 ok/degraded/failed test에서 "ok" 값만 검증. "degraded"/"failed" 경로는 이번 반복 OOS로 REQ Notes에 명시 PARTIAL (OOS 인정)
AC3 checked_at ISO 8601 형식 test_v1.py::test_health_returns_full_response — "+09:00" in body["checked_at"] assert PASS
AC4 service 고정값 "portfolio-hub" test_v1.py — body["service"] == "portfolio-hub" PASS
AC5 비인증 접근 200 test_v1.py::test_health_returns_full_response — 인증 헤더 없이 GET /health 호출, 200 응답 PASS

REQ-004 판정: PASS (AC2는 이번 반복 OOS — 릴리스 블로커 아님)


REQ-005: service-token 발급 시 runtime_id 동시 생성

# Acceptance Criterion Evidence Result
AC1 CLI 출력에 token + runtime_id tests/test_cli.py::test_cli_service_token_issue_supports_json PASS PASS
AC2 API 엔드포인트 응답에 token + runtime_id tests/test_v1.py::test_auth_issue_service_token_returns_runtime_id — POST /auth/service-tokens → 201 + runtime_id PASS PASS
AC3 GET /v1/user_info 응답 runtime_id 일치 tests/test_v1.py::test_v1_user_info_returns_user — body["runtime_id"] == "rt-abc" PASS PASS
AC4 두 번 발급 시 다른 runtime_id auth_service.py에서 매 발급마다 str(uuid.uuid4()) 생성 — 코드 검토 PASS PASS
AC5 auth_tokens 테이블 runtime_id 저장 postgres.py::create_auth_token INSERT에 runtime_id 포함 — 코드 검토 PASS. DB 환경 실행 미확인(PARTIAL) PARTIAL (DB 미확인)

REQ-005 판정: PASS (AC5 DB 미확인은 REQ-009와 동일 제약으로 릴리스 블로커 아님)


REQ-006: GET /v1/assets 엔드포인트

# Acceptance Criterion Evidence Result
AC1 service-token 인증 → 200 + portfolio 목록 tests/test_v1.py::test_v1_assets_returns_asset_list PASS PASS
AC2 ?status=active 쿼리 파라미터 필터 tests/test_v1.py::test_v1_assets_status_filter — ?status=active 필터 동작 확인 PASS PASS
AC3 응답 필드: asset_id, symbol, market, status, cash, quantity, broker_type, strategy_id, strategy_params, updated_at test_v1_assets_returns_asset_list — cash/quantity/broker_type/strategy_params 필드명 assert PASS PASS
AC4 strategy_params → StrategySet.config 파생 JSON tests/test_v1.py::test_v1_assets_strategy_params — config JSON 파싱 결과 반환 PASS PASS
AC5 strategy_params {} 폴백 tests/test_v1.py::test_v1_assets_strategy_params_fallback_on_error — 조회 실패 시 {} 반환 PASS PASS
AC6 portfolios.market NULL → 응답 null `AssetItem.market: str None = None, Portfolio.market: str
AC7 미인증 401 tests/test_v1.py::test_v1_assets_requires_service_token PASS PASS

REQ-006 판정: PASS


REQ-007: GET /v1/user_info 엔드포인트

# Acceptance Criterion Evidence Result
AC1 응답에 runtime_id, trading_enabled, brokers[] 포함 tests/test_v1.py::test_v1_user_info_returns_user — 3개 필드 모두 assert PASS PASS
AC2 environment 필드 없음 UserInfoResponse 스키마 검토 — environment 필드 없음 PASS PASS
AC3 is_active=true → trading_enabled=true test_v1_user_info_returns_user — body["trading_enabled"] is True PASS PASS
AC4 is_active=false → trading_enabled=false, disabled_reason 포함 tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason — trading_enabled is False, disabled_reason == "account_inactive" PASS PASS
AC5 brokers[]에 credential_payload 복호화 JSON tests/test_v1.py::test_v1_user_info_returns_decrypted_broker_credentials — Fernet 복호화 payload 반환 PASS PASS
AC6 runtime_id 없는 구형 토큰 → HTTP 404 tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id — 404 + detail PASS PASS
AC7 미인증 401 _require_service_token 의존성 + test_v1_assets_requires_service_token 패턴으로 구조 검토 PASS PASS

REQ-007 판정: PASS


REQ-008: POST /v1/assets/{asset_id}/ledger/trades

# Acceptance Criterion Evidence Result
AC1 신규 체결 → HTTP 201 + ledger_entry_id tests/test_v1.py::test_v1_ledger_trade_created — 201 + status="new" PASS PASS
AC2 동일 payload 재전송 → HTTP 200 + status="duplicate" tests/test_v1.py::test_v1_ledger_trade_idempotent_same_payload — 200 + status="duplicate" PASS PASS
AC3 동일 키 다른 payload → HTTP 409 tests/test_v1.py::test_v1_ledger_trade_conflict_different_payload — 409 PASS PASS
AC4 존재하지 않는 asset_id → 404 tests/test_v1.py::test_v1_ledger_trade_asset_not_found — 404 PASS PASS
AC5 미인증 401 _require_service_token 의존성 구조 검토 PASS PASS

REQ-008 판정: PASS


REQ-009: v1 API DB 스키마 migration

# Acceptance Criterion Evidence Result
AC1 portfolios.market 컬럼 존재 20260502_0014_v1_api_schema.py — IF NOT EXISTS 패턴 구현. 코드 검토 PASS. DB 환경 미확인 PARTIAL
AC2 auth_tokens.runtime_id 컬럼 존재 동일 migration — UNIQUE INDEX 포함. 코드 검토 PASS PARTIAL
AC3 broker_credentials 테이블 존재 동일 migration — PK(user_id, broker). 코드 검토 PASS PARTIAL
AC4 ledger_trades 테이블 존재 동일 migration — BIGSERIAL PK. 코드 검토 PASS PARTIAL
AC5 (asset_id, order_id, execution_id) UniqueViolation uq_ledger_trades_idempotency UniqueConstraint 정의 확인. DB 실행 미확인 PARTIAL
AC6 재실행 시 에러 없음 IF NOT EXISTS + _table_exists/_column_exists 패턴. 코드 검토 PASS PARTIAL
AC7 기존 행 market NULL 유지 ADD COLUMN NULLABLE, 기존 행 UPDATE 없음. 코드 검토 PASS PARTIAL

REQ-009 판정: PARTIAL — 코드 전체 PASS. 실제 DB 환경(Docker Compose) 실행 검증 미수행. 코드 전용 환경에서는 블로커 아님.


REQ-010: credential_payload Fernet 암호화 및 startup validation

# Acceptance Criterion Evidence Result
AC1 KEY 있음 → 정상 기동 tests/test_v1.py::test_startup_ok_when_encryption_key_present PASS PASS
AC2 KEY 없음 → 기동 실패 + 에러 메시지 tests/test_v1.py::test_startup_fails_when_encryption_key_missing — db_enabled=True + KEY 없음 → RuntimeError("CREDENTIAL_ENCRYPTION_KEY") PASS PASS
AC3 DB에 ciphertext 저장 tests/test_v1.py::test_encrypt_decrypt_roundtrip — encrypt→decrypt roundtrip PASS. 저장 경로는 upsert_broker_credential 메서드에서 암호화된 payload 직접 수신 (호출자 책임). DB 환경 실행 미확인 PARTIAL
AC4 GET /v1/user_info 복호화 JSON 반환 tests/test_v1.py::test_v1_user_info_returns_decrypted_broker_credentials PASS PASS
AC5 잘못된 키 → InvalidToken 예외 처리 encryption.py에서 InvalidToken re-export. v1_user_info.py에서 except 처리 후 credential_payload: null 반환 — 코드 검토 PASS PASS

REQ-010 판정: PASS (AC3 DB 미확인은 REQ-009와 동일 제약)


Architecture Impact & Baseline Docs Sync

  • docs/project-structure.md — 갱신 완료: app/core/encryption.py, v1 라우터 모듈, v1 스키마 반영
  • docs/runtime-flows.md — 갱신 완료: v1 API 요청 흐름 5개, startup 검증 흐름 추가

판정: PASS


Evidence

Automated Tests (pytest)

  • tests/test_v1.py — 19개 전부 PASS
    • test_health_returns_full_response, test_auth_issue_service_token_returns_runtime_id
    • test_v1_assets_requires_service_token, test_v1_assets_rejects_env_token, test_v1_assets_returns_asset_list
    • test_v1_assets_status_filter, test_v1_assets_strategy_params, test_v1_assets_strategy_params_fallback_on_error
    • test_v1_user_info_404_when_no_runtime_id, test_v1_user_info_returns_user, test_v1_user_info_inactive_user_has_disabled_reason
    • test_v1_user_info_returns_decrypted_broker_credentials
    • test_v1_ledger_trade_created, test_v1_ledger_trade_idempotent_same_payload, test_v1_ledger_trade_conflict_different_payload, test_v1_ledger_trade_asset_not_found
    • test_startup_fails_when_encryption_key_missing, test_startup_ok_when_encryption_key_present, test_encrypt_decrypt_roundtrip
  • tests/test_app.py — PASS (startup bootstrap 포함)
  • tests/test_auth.py, tests/test_cli.py, tests/test_domain_services.py — PASS
  • tests/test_portfolio_comments.py, tests/test_portfolio_ownership.py, tests/test_portfolio_tags.py, tests/test_users.py — PASS
  • 전체 합계: 98 passed (2026-05-01)

Code Review

  • app/core/encryption.py: Fernet 암복호화 유틸리티 정상 구현
  • app/api/auth_routes.py: POST /auth/service-tokens 엔드포인트 추가
  • app/api/v1_assets.py: status 필터, strategy_params 조회, 새 필드명 매핑
  • app/api/v1_user_info.py: trading_enabled, brokers[], Fernet 복호화, Depends(get_settings)
  • app/api/v1_ledger.py: 200/201/404/409 분기, _payloads_match 비교
  • app/repositories/postgres.py: list_broker_credentials 추가
  • app/main.py: startup validation (CREDENTIAL_ENCRYPTION_KEY)

DB 환경 실행

  • 미수행 (Docker Compose 환경 미기동). 코드 전용 환경에서는 블로커 아님.

Blocking Issues

없음.

Non-blocking Notes

  • REQ-009 AC1~AC7: 코드 검토 PASS, 실제 DB 환경 실행 미확인. 다음 릴리스 전 live DB 검증 권장.
  • REQ-004 AC2: "degraded"/"failed" 상태 경로는 이번 반복 OOS.

Result

  • Ready for Release: true

Acceptance Mapping

REQ-004: GET /health 응답 스키마 확장

# Acceptance Criterion Evidence Result
AC1 status/service/version/checked_at 필드 모두 존재 tests/test_v1.py::test_health_returns_full_response — 4개 필드 전체 assert PASS
AC2 status 허용값 ok/degraded/failed test에서 "ok" 값만 검증. "degraded"/"failed" 경로 코드 미구현(Notes에 이번 반복 Out of Scope 명시) — 스키마에 허용값으로만 선언 PARTIAL (OOS 인정)
AC3 checked_at ISO 8601 형식 test_v1.py::test_health_returns_full_response — "+09:00" in body["checked_at"] assert PASS
AC4 service 고정값 "portfolio-hub" test_v1.py — body["service"] == "portfolio-hub" PASS
AC5 비인증 접근 200 test_v1.py::test_health_returns_full_response — 인증 헤더 없이 GET /health 호출, 200 응답 PASS

REQ-004 판정: PASS (AC2는 REQ Notes에서 이번 반복 OOS로 명시 — 릴리스 블로커 아님)


REQ-005: service-token 발급 시 runtime_id 동시 생성

# Acceptance Criterion Evidence Result
AC1 CLI 출력에 token + runtime_id app/cli/commands/service_tokens.py — ServiceTokenIssueCliResponse(runtime_id=issued.auth_token.runtime_id, ...) 구현, tests/test_cli.py::test_cli_service_token_issue_supports_json PASS (FakeRepo에 runtime_id 반영) PASS
AC2 API 엔드포인트 응답에 token + runtime_id service-token 발급 API 엔드포인트 미구현 (CLI 전용으로 구현). app/api/auth_routes.py 변경 없음. REQ-005 AC2는 "POST /auth/tokens 또는 동등 경로"를 명시함 BLOCKER
AC3 GET /v1/user_info 응답 runtime_id 일치 tests/test_v1.py::test_v1_user_info_returns_user — body["runtime_id"] == "rt-abc" PASS
AC4 두 번 발급 시 다른 runtime_id auth_service.py에서 매 발급마다 str(uuid.uuid4()) 생성 — 코드 검토 통과. 전용 테스트 없음 PARTIAL
AC5 auth_tokens 테이블 runtime_id 저장 postgres.py::create_auth_token INSERT에 runtime_id 포함, _SELECT_AUTH_TOKEN에 runtime_id 컬럼 추가 — 코드 검토 통과. DB 환경 실행 미확인 PARTIAL (DB 미확인)

REQ-005 판정: BLOCKED — AC2 서비스 토큰 발급 API 엔드포인트 누락


REQ-006: GET /v1/assets 엔드포인트

# Acceptance Criterion Evidence Result
AC1 service-token 인증 → 200 + portfolio 목록 tests/test_v1.py::test_v1_assets_returns_asset_list PASS PASS
AC2 ?status=active 쿼리 파라미터 필터 v1_assets.py에 status 쿼리 파라미터 미구현. list_portfolios_with_strategies(include_disabled=False)만 호출 — active 필터 없음 BLOCKER
AC3 응답 필드: asset_id, symbol, market, status, cash, quantity, broker_type, strategy_id, strategy_params, updated_at AssetItem 스키마에 cash_balance(≠cash), asset_quantity(≠quantity), broker(≠broker_type), active_strategy_id(≠strategy_id) 사용. strategy_params 필드 아예 없음 — trex-basic contract 필드명 불일치 BLOCKER
AC4 strategy_params → StrategySet.config 파생 JSON AssetItem에 strategy_params 필드 없음. v1_assets.py에서 StrategySet 조회 로직 없음 BLOCKER
AC5 strategy_params {} 폴백 strategy_params 미구현으로 폴백 불가 BLOCKER
AC6 portfolios.market NULL → 응답 null `AssetItem.market: str None = None, Portfolio.market: str
AC7 미인증 401 tests/test_v1.py::test_v1_assets_requires_service_token — 401 반환 PASS PASS

REQ-006 판정: BLOCKED — AC2(status 필터), AC3(필드명 불일치), AC4/AC5(strategy_params 미구현)


REQ-007: GET /v1/user_info 엔드포인트

# Acceptance Criterion Evidence Result
AC1 응답에 runtime_id, trading_enabled, brokers[] 포함 UserInfoResponse에 runtime_id ✓, is_active (trading_enabled 아님) ✓, brokers[] 없음. Contract 필드명 trading_enabled 사용 안 함 BLOCKER
AC2 environment 필드 없음 UserInfoResponse에 environment 필드 없음 PASS
AC3 is_active=true → trading_enabled=true trading_enabled 필드 미구현 BLOCKER
AC4 is_active=false → trading_enabled=false, disabled_reason 포함 tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason — disabled_reason == "account_inactive" PASS. trading_enabled 미구현 BLOCKER
AC5 brokers[]에 credential_payload 복호화 JSON UserInfoResponse에 brokers[] 없음. v1_user_info.py에서 broker_credentials 조회 및 복호화 없음 BLOCKER
AC6 runtime_id 없는 구형 토큰 → HTTP 404 tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id — 404 + "runtime_id not assigned to this token" PASS PASS
AC7 미인증 401 _require_service_token 의존성에서 강제. 명시적 테스트 없으나 구조 검토 통과 PARTIAL

REQ-007 판정: BLOCKED — trading_enabled 필드명, brokers[] 배열 전체 미구현


REQ-008: POST /v1/assets/{asset_id}/ledger/trades

# Acceptance Criterion Evidence Result
AC1 신규 체결 → HTTP 201 + ledger_entry_id tests/test_v1.py::test_v1_ledger_trade_created — 201 + created=True PASS PASS
AC2 동일 payload 재전송 → HTTP 200 + status="duplicate" 현재 구현은 중복 시에도 HTTP 201 반환, 응답 필드가 status="duplicate"가 아닌 created=false 사용. test_v1_ledger_trade_idempotent는 201을 assert BLOCKER
AC3 동일 키 다른 payload → HTTP 409 insert_ledger_trade에서 UniqueViolation 시 기존 행 반환만 수행, payload 비교 없음. 409 응답 미구현 BLOCKER
AC4 존재하지 않는 asset_id → 404 v1_ledger.py에서 asset_id 존재 여부 확인 로직 없음 BLOCKER
AC5 미인증 401 _require_service_token 의존성에서 강제. 명시적 테스트 없으나 구조 검토 통과 PARTIAL

REQ-008 판정: BLOCKED — AC2(HTTP 상태 코드 및 응답 필드), AC3(409 미구현), AC4(asset_id 존재 확인 없음)


REQ-009: v1 API DB 스키마 migration

# Acceptance Criterion Evidence Result
AC1 portfolios.market 컬럼 존재 20260502_0014_v1_api_schema.py — add_column("portfolios", market TEXT NULLABLE) IF NOT EXISTS 패턴 구현 — 코드 검토 PASS. 실제 DB 실행 미확인 PARTIAL (DB 미확인)
AC2 auth_tokens.runtime_id 컬럼 존재 동일 migration — add_column("auth_tokens", runtime_id TEXT NULLABLE) + UNIQUE INDEX — 코드 검토 PASS PARTIAL (DB 미확인)
AC3 broker_credentials 테이블 존재 동일 migration — create_table("broker_credentials", ...) PK(user_id, broker) — 코드 검토 PASS PARTIAL (DB 미확인)
AC4 ledger_trades 테이블 존재 동일 migration — create_table("ledger_trades", ...) BIGSERIAL PK — 코드 검토 PASS PARTIAL (DB 미확인)
AC5 (asset_id, order_id, execution_id) UniqueViolation uq_ledger_trades_idempotency UniqueConstraint 정의 확인. DB 실행 미확인 PARTIAL (DB 미확인)
AC6 재실행 시 에러 없음 IF NOT EXISTS 패턴 + _table_exists/_column_exists 검사 구현 — 코드 검토 PASS PARTIAL (DB 미확인)
AC7 기존 행 market NULL 유지 ADD COLUMN ... NULLABLE 추가만 수행, 기존 행 UPDATE 없음 — 코드 검토 PASS PARTIAL (DB 미확인)

REQ-009 판정: PARTIAL — 코드 검토 전체 PASS. 실제 DB 환경(Docker Compose) 실행 검증 필요. 코드 전용 환경에서는 블로커 아님으로 취급하나, live DB 검증 항목을 Blocking Issues에 포함.


REQ-010: credential_payload Fernet 암호화 및 startup validation

# Acceptance Criterion Evidence Result
AC1 KEY 있음 → 정상 기동 app/core/encryption.py 신규 구현, Settings.credential_encryption_key 필드 추가. KEY 있을 때 정상 동작 — 코드 검토 PASS PASS
AC2 KEY 없음 → 기동 실패 + 에러 메시지 구현에서 lazy validation으로 변경됨: Settings 생성 시 에러 없이 통과, require_credential_encryption_key() 호출 시에만 RuntimeError 발생. startup 실패 동작이 AC2와 불일치 BLOCKER
AC3 DB에 ciphertext 저장 upsert_broker_credential 메서드 존재. 그러나 API에서 plaintext → encrypt → DB 저장 경로 미구현 (암호화 적용은 호출자 책임으로 처리됨). 전용 테스트 없음 BLOCKER
AC4 GET /v1/user_info 복호화 JSON 반환 UserInfoResponse에 brokers[] 없음 (REQ-007 BLOCKED에 포함) — 동일 blocker BLOCKER
AC5 잘못된 키 → InvalidToken 예외 encryption.py에서 cryptography.fernet.InvalidToken re-export. 전용 테스트 없음 PARTIAL

REQ-010 판정: BLOCKED — AC2(startup 실패 동작 불일치), AC3(암호화 저장 경로 미연결), AC4(REQ-007 blocker와 연동)


Architecture Impact & Baseline Docs Sync

design.md Changed Areas에 아래 baseline 문서 갱신이 명시됨:

  • docs/project-structure.md — app/core/encryption.py, app/api/v1_*.py 신규 모듈 반영
  • docs/runtime-flows.md — v1 API 요청 흐름, runtime_id 발급 흐름 추가

현재 상태: implementation.md Changed Files에 baseline 문서 갱신 없음. design의 Architecture Impact가 none이 아니므로 baseline 문서 동기화 evidence가 필요함.

판정: BLOCKER — docs/project-structure.md, docs/runtime-flows.md 미갱신


Evidence

Automated Tests (pytest)

  • tests/test_app.py::test_healthcheck — PASS (health 필드 검증 포함)
  • tests/test_v1.py::test_health_returns_full_response — PASS (4개 필드 + KST checked_at)
  • tests/test_v1.py::test_v1_assets_requires_service_token — PASS
  • tests/test_v1.py::test_v1_assets_rejects_env_token — PASS
  • tests/test_v1.py::test_v1_assets_returns_asset_list — PASS (asset_id, market 기본 필드)
  • tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id — PASS
  • tests/test_v1.py::test_v1_user_info_returns_user — PASS (runtime_id, disabled_reason=None)
  • tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason — PASS
  • tests/test_v1.py::test_v1_ledger_trade_created — PASS
  • tests/test_v1.py::test_v1_ledger_trade_idempotent — PASS
  • tests/test_auth.py 전체 — PASS (runtime_id 파라미터 추가 후)
  • tests/test_cli.py::test_cli_service_token_issue_supports_json — PASS
  • 전체 합계: 88 passed (2026-05-02)

Code Review

  • app/core/encryption.py: Fernet 암복호화 유틸리티 정상 구현
  • app/core/config.py: credential_encryption_key 필드 추가, lazy validation 전환
  • app/core/auth.py: AuthContext.runtime_id, TokenPrincipal.runtime_id 추가
  • app/domain/models.py: AuthToken.runtime_id, Portfolio.market, BrokerCredential, LedgerTrade 추가
  • app/services/auth_service.py: uuid.uuid4() runtime_id 생성·전달
  • migrations/versions/20260502_0014_v1_api_schema.py: IF NOT EXISTS 패턴 정상

DB 환경 실행

  • 미수행 (Docker Compose 환경 미기동)

Result

  • Ready for Release: false

Blocking Issues

# REQ Issue 필요 조치
B-1 REQ-005 AC2 service-token 발급 API 엔드포인트(POST /auth/service-tokens 또는 동등 경로) 미구현. CLI 전용으로 구현됨 API 엔드포인트 추가 또는 REQ-005 AC2 범위 조정(change-req)
B-2 REQ-006 AC2 ?status=active 쿼리 파라미터 필터 미구현 v1_assets.py에 status 쿼리 파라미터 추가
B-3 REQ-006 AC3 응답 필드명 불일치: cash_balance(≠cash), asset_quantity(≠quantity), broker(≠broker_type), active_strategy_id(≠strategy_id) 계약 필드명으로 응답 스키마 수정 또는 alias 추가
B-4 REQ-006 AC4/AC5 strategy_params 필드 미구현. StrategySet.config 조회 및 {} 폴백 로직 없음 v1_assets.py에 strategy_set 조회 및 strategy_params 파생 로직 추가
B-5 REQ-007 AC1/AC3/AC4 trading_enabled 필드 미구현(is_active로 대체됨). 계약 필드명 불일치 UserInfoResponse에 trading_enabled 필드 추가
B-6 REQ-007 AC5 brokers[] 배열 전체 미구현. broker_credentials 조회 + Fernet 복호화 경로 없음 v1_user_info.py에 broker_credentials 조회 + 복호화 + 응답 포함 구현
B-7 REQ-008 AC2 중복 수신 시 HTTP 201 반환(AC는 HTTP 200 요구). 응답 필드 created(bool) vs status="duplicate" 불일치 중복 시 HTTP 200 반환, 응답 스키마에 status="duplicate" 필드 추가
B-8 REQ-008 AC3 동일 키 다른 payload → HTTP 409 미구현 insert_ledger_trade에 payload 비교 로직 및 409 응답 추가
B-9 REQ-008 AC4 존재하지 않는 asset_id → HTTP 404 미구현 v1_ledger.py에 asset_id(portfolio_id) 존재 여부 확인 후 404 반환 추가
B-10 REQ-010 AC2 startup validation 동작 불일치: Settings 생성 시 에러 없음(lazy), AC는 기동 실패 요구 lifespan 또는 애플리케이션 시작 시 require_credential_encryption_key() 호출 추가
B-11 REQ-010 AC3 broker_credentials 저장 시 암호화 미적용. API 레이어에서 encrypt_payload 호출 없음 upsert_broker_credential 호출부 또는 API 레이어에서 암호화 적용
B-12 Baseline docs/project-structure.md, docs/runtime-flows.md 미갱신. design.md Architecture Impact에 갱신 필요로 명시됨 두 baseline 문서에 신규 모듈 및 v1 API 흐름 반영

Non-Blocking Observations

# REQ Observation
N-1 REQ-009 migration 파일 코드 검토 PASS. DB 환경(Docker Compose) 실행 미검증 — 배포 전 반드시 확인 필요
N-2 REQ-004 AC2 degraded/failed 상태 경로 미구현 — REQ Notes에서 이번 반복 OOS로 명시되어 릴리스 블로커 아님
N-3 REQ-005 AC4 두 번 발급 시 다른 runtime_id — 코드 검토 통과. 전용 테스트 없음
N-4 REQ-010 AC5 InvalidToken 경로 전용 테스트 없음