Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
8.4 KiB
8.4 KiB
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
- 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 포함) 반환. - Given daemon_id가 바인딩된 service token으로 호출, 해당 daemon_id에 매칭된 인증정보가 없으면, When 호출, Then HTTP 200 +
broker: null반환 (graceful degrade, 크래시 없음). - Given daemon_id가 없는(None) service token으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 Forbidden 반환.
- Given 같은 user 소유의 daemon A, daemon B에 각각 다른 KIS account_ref가 등록된 상황, When daemon A의 service token으로 호출, Then
broker.account_ref가 daemon A에 등록된 account_ref와 일치함. - Given 같은 상황에서 daemon B의 service token으로 호출, When 호출, Then
broker.account_ref가 daemon B에 등록된 account_ref와 일치함 (daemon A의 계좌 반환하지 않음). - Given
UserInfoResponse응답 스키마, When 응답 JSON 확인, Thenbrokers키가 존재하지 않고broker단일 키가 존재한다. - Given 인증정보 복호화 실패(잘못된 key), When daemon_id에 매칭된 인증정보가 있으나 복호화 실패, Then HTTP 200 +
broker.credential_payload: null반환 (경고 로그 기록, 크래시 없음). - Given service 토큰이 아닌 user 토큰(web 토큰)으로 호출, When 호출, Then HTTP 403 Forbidden 반환 (기존 service-token gate 유지).
- 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—BrokerCredentialState 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-corehub_read.whoami()가daemon_id만 읽고brokers필드를 참조하지 않음이 확인되었으므로, 실질적 소비자 영향은 없다. 다만 이 REQ 구현 전 trex-core 소비자 코드 재확인을 권장한다. - daemon_id 없음 → 403 정책은 dcy-010 FR-2 및 universe 인가(REQ-028
managed_by_daemon_id일치 요구)와 동일한 규율이다. broker: nullgraceful 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 머지(mergeCommitd4acfd4), 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 근거 확보됨.