# REQ-049: GET /v1/user_info daemon-scoped 단일 broker 전달로 확장 - Status: Implemented - Type: Interface - Priority: High - Owner: Justin Ko ## Intent - 기존 `GET /v1/user_info`가 `user_id` 기반으로 `brokers: list[BrokerItem]`을 반환한다. 이를 service token의 `auth.daemon_id`로 매칭된 단일 broker 인증정보를 반환하는 daemon-scoped 전달로 확장한다. - "1 데몬 = 1 계좌" 불변식(dcy-010 A-2 confirmed)에 따라 응답을 `broker: BrokerItem | None`(단일)으로 정규화하여, 같은 user의 두 데몬이 서로 다른 KIS 계좌를 각각 수신할 수 있게 한다. - 유일 소비자인 trex-core `hub_read.whoami()`는 `daemon_id`만 읽으며 `brokers` 필드를 참조하지 않으므로, 응답 형태 변경이 소비자에게 무영향임이 확인된 안전한 변경이다(dcy-010 OQ-2 Answered). ## Requirement - `GET /v1/user_info`의 응답 스키마를 다음과 같이 변경한다: - `brokers: list[BrokerItem]` 필드를 제거하고 `broker: BrokerItem | None` 단일 필드로 대체한다. - `broker`는 service token의 `auth.daemon_id`에 매칭된 인증정보 1건을 복호화하여 반환한다. `daemon_id`에 매칭된 인증정보가 없으면 `broker: null`을 반환한다(graceful degrade — NFR-3). - service token에 `daemon_id`가 없는 경우(None), 엔드포인트는 HTTP 403 Forbidden을 반환한다. 기존 "daemon_id 없어도 200 반환" 동작을 `broker` 필드에 대해서는 적용하지 않으며, `daemon_id` 자체가 없으면 403으로 거부한다. - `app/schemas/v1.py`의 `UserInfoResponse` 모델을 `broker: BrokerItem | None = None` 필드로 변경하고 `brokers: list[BrokerItem]`를 제거한다. - `app/api/v1_user_info.py`의 엔드포인트 구현이 `auth.daemon_id`로 `list_broker_credentials(daemon_id=auth.daemon_id)` 호출 후 결과에서 단일 항목을 복호화·반환하도록 변경한다. - 인증정보 복호화 실패(잘못된 키, 페이로드 손상)는 기존 graceful degrade 정책을 유지한다: 경고 로깅 후 `credential_payload: null`을 포함한 `BrokerItem`을 반환한다(응답을 크래시시키지 않음). ## Acceptance Criteria 1. Given daemon_id가 바인딩된 service token으로 GET /v1/user_info 호출, 해당 daemon_id에 매칭된 broker 인증정보가 존재하면, When 호출, Then HTTP 200 + `broker` 필드에 단일 `BrokerItem`(account_ref, credential_payload, credential_version, last_rotated_at 포함) 반환. 2. Given daemon_id가 바인딩된 service token으로 호출, 해당 daemon_id에 매칭된 인증정보가 없으면, When 호출, Then HTTP 200 + `broker: null` 반환 (graceful degrade, 크래시 없음). 3. Given daemon_id가 없는(None) service token으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 Forbidden 반환. 4. Given 같은 user 소유의 daemon A, daemon B에 각각 다른 KIS account_ref가 등록된 상황, When daemon A의 service token으로 호출, Then `broker.account_ref`가 daemon A에 등록된 account_ref와 일치함. 5. Given 같은 상황에서 daemon B의 service token으로 호출, When 호출, Then `broker.account_ref`가 daemon B에 등록된 account_ref와 일치함 (daemon A의 계좌 반환하지 않음). 6. Given `UserInfoResponse` 응답 스키마, When 응답 JSON 확인, Then `brokers` 키가 존재하지 않고 `broker` 단일 키가 존재한다. 7. Given 인증정보 복호화 실패(잘못된 key), When daemon_id에 매칭된 인증정보가 있으나 복호화 실패, Then HTTP 200 + `broker.credential_payload: null` 반환 (경고 로그 기록, 크래시 없음). 8. Given service 토큰이 아닌 user 토큰(web 토큰)으로 호출, When 호출, Then HTTP 403 Forbidden 반환 (기존 service-token gate 유지). 9. Given `runtime_id`, `daemon_id`, `trading_enabled`, `user_id`, `username`, `permission` 필드, When GET /v1/user_info 호출, Then 이 필드들은 기존과 동일하게 반환된다 (broker 변경이 나머지 필드에 영향 없음, 회귀 없음). ## Impacted Area - Docs: - `docs/interface-contract.md` — `GET /v1/user_info` 섹션: `brokers: list` → `broker: object|null` 응답 스키마 갱신, daemon_id 없음 → 403 정책 명시. - `docs/data-model.md` — `BrokerCredential` State Rules 갱신: 전달 경로에 daemon-scoped 언급 추가. - Modules: - `app/schemas/v1.py` — `UserInfoResponse` 모델: `brokers: list[BrokerItem]` → `broker: BrokerItem | None = None`. - `app/api/v1_user_info.py` — 엔드포인트 구현: user_id 기반 list → daemon_id 기반 단일 조회, daemon_id None → 403. - Tests: - `GET /v1/user_info` 단위/통합 테스트 갱신: daemon_id 있음/없음, broker 있음/없음, 2데몬 격리(AC-4/5), 복호화 실패(AC-7), 회귀(AC-9). - 기존 `brokers` 필드 참조 테스트 → `broker` 단일 필드로 변경. ## Notes - **파괴적 변경(breaking change) 해당 여부**: `brokers: list` → `broker: object|null`는 API 응답 스키마 변경이다. 그러나 dcy-010 OQ-2에서 유일 소비자 trex-core `hub_read.whoami()`가 `daemon_id`만 읽고 `brokers` 필드를 참조하지 않음이 확인되었으므로, **실질적 소비자 영향은 없다**. 다만 이 REQ 구현 전 trex-core 소비자 코드 재확인을 권장한다. - daemon_id 없음 → 403 정책은 dcy-010 FR-2 및 universe 인가(REQ-028 `managed_by_daemon_id` 일치 요구)와 동일한 규율이다. - `broker: null` graceful degrade(AC-2)는 dcy-010 NFR-3(인증정보 부재가 `daemon_id`, `trading_enabled` 필수 필드를 크래시시키지 않음)을 구현한다. `broker: null`은 그날 거래 개시 차단 신호로 소비자(trex-core)가 해석한다. 이 해석 로직은 trex-core Discovery(별도)에서 구현한다. - OQ-3(부트스트랩 통합 — universe+인증정보 단일 응답): dcy-010 Deferred. 이 REQ는 인증정보 전달 형태만 확정하며, 부트스트랩 통합은 다루지 않는다. - Source: dcy-010 FR-2, OQ-2 Answered, A-2, NFR-3. ## Change Log ### CHG-20260628-01 - Date: 2026-06-28 - Author: Justin Ko #### Change Summary - 신규 생성 (Proposed) — dcy-010 FR-2 산출물. #### Intent - dcy-010 FR-2(기존 GET /v1/user_info daemon-scoped 확장, 단일 broker 정규화)를 Interface REQ로 정규화한다. #### Acceptance Criteria Delta - Added: - AC-1 ~ AC-9 전체 (신규 초안) - Updated: 없음 - Removed: 없음 #### Impacted Area Delta - Docs: - `docs/srs/Interface/req-049_user-info-daemon-scoped-broker-delivery.md` 신규 생성 - `docs/interface-contract.md`, `docs/data-model.md` 갱신 예고 - Modules: - `app/schemas/v1.py` (`UserInfoResponse` 변경), `app/api/v1_user_info.py` (로직 변경) - Tests: - user_info 엔드포인트 테스트 전면 갱신 신규 #### Delivery Trace Delta - Batches: 미정 (suggest-batch-reqs 대기) - Releases: 미정 #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no (기존 `brokers` 필드 구현은 이 REQ 구현 시 대체됨) - Status Recommendation: keep-current - Follow-up Action: confirm-req 후 batch 배정. REQ-046(모델), REQ-047(스키마) 선행 의존. trex-core 소비자 코드 재확인. #### Validation Plan - pytest로 AC-1~9 각 케이스 확인. 2데몬 격리(AC-4/5) 특히 중요. ### CHG-20260628-02 - Date: 2026-06-28 - Author: 팀장(Claude) - confirm-req: `Proposed` → `Approved`. dcy-010 FR-2/OQ-2/A-2/NFR-3 추적. brokers→단일 broker reshape, daemon_id 없음 403, 2데몬 격리(AC-4/5), graceful degrade(AC-2) 적절. 구현 전 trex-core 소비자 재확인 권고(Notes) 유효. blocker 없음. ### CHG-20260628-03 - Date: 2026-06-28 - Author: 팀장(Claude) #### Change Summary - confirm-req-implemented: `Approved` → `Implemented`. PR #17 머지(mergeCommit d4acfd4), BAT-020, QC PASS-WITH-NOTES(blocker 0), 테스트 통과. #### Intent - BAT-020 delivery(PR #17 머지)에서 GET /v1/user_info daemon-scoped 단일 broker 전달 구현 및 AC-1~9 테스트 통과가 확인되어 Implemented로 전환한다. #### Acceptance Criteria Delta - Added: 없음 - Updated: 없음 - Removed: 없음 #### Impacted Area Delta - 없음 (상태 전환만) #### Delivery Trace Delta - Batches: BAT-020 (PR #17, mergeCommit d4acfd4) - Releases: 미정 #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: implemented #### Validation Plan - BAT-020 verification 문서 및 QC PASS-WITH-NOTES 결과(blocker 0)로 AC-1~9 근거 확보됨.