docs(dcy-010): draft-req + confirm-req — REQ-046~051 Approved (daemon-scoped broker credential provisioning)
Adds SRS specification documents for daemon-scoped broker credential management: - REQ-046: Broker credential daemon_id field - REQ-047: Migration PKv1→PKv2 with daemon_id - REQ-048: Backfill existing broker credentials with daemon_id - REQ-049: User info endpoint including daemon-scoped credentials - REQ-050: Credential CLI --daemon-id option - REQ-051: Security isolation per daemon boundary Updates dcy-010 discovery document with generated REQ references and register. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
- 마지막 갱신 시각(KST): 2026-06-28
|
||||
- 대체됨: 없음
|
||||
- 후속 Discovery 참조: trex-core 소비자측 day-start fetch + fail-safe 로드 = 별도 trex-core Discovery로 분리 **확정**(2026-06-28). trex-core repo에 신규 Discovery 생성 예정(미생성).
|
||||
- 생성된 REQ 참조: 없음
|
||||
- 생성된 REQ 참조: REQ-046, REQ-047, REQ-048, REQ-049, REQ-050, REQ-051
|
||||
|
||||
# 1. 계획 상태 요약
|
||||
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# REQ-046: BrokerCredential 도메인 모델 daemon_id 필드 추가
|
||||
|
||||
- Status: Approved
|
||||
- Type: Data
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- `BrokerCredential` 도메인 엔티티가 현재 `user_id` 기반으로만 인증정보를 식별한다. daemon-scoped 소유로 전환하기 위해 `daemon_id` 필드를 모델에 추가하고, repository 조회/행 변환 로직이 daemon_id 차원을 인식하도록 한다.
|
||||
- 이 REQ는 도메인 모델·repository 계층의 데이터 구조 변경을 담당하며, 마이그레이션(REQ-047/REQ-048) 및 API 전달(REQ-049)·CLI(REQ-050)의 선행 조건이다.
|
||||
|
||||
## Requirement
|
||||
- `app/domain/models.py`의 `BrokerCredential` dataclass에 `daemon_id: str | None = None` 필드를 추가한다.
|
||||
- `app/repositories/postgres.py`의 `_row_to_broker_credential` 정적 메서드가 DB 행에서 `daemon_id`를 읽어 `BrokerCredential` 인스턴스를 구성해야 한다.
|
||||
- `upsert_broker_credential` 메서드 시그니처에 `daemon_id: str | None = None` 파라미터를 추가하고, INSERT/ON CONFLICT 쿼리에 `daemon_id` 컬럼 처리를 반영한다. 단, 실제 테이블 컬럼은 REQ-047 마이그레이션이 선행 적용된 후 활성화되므로 이 REQ 단독으로는 DB 컬럼이 존재하지 않는 환경에서도 하위 호환을 고려한다.
|
||||
- `list_broker_credentials` 메서드에 `daemon_id: str | None = None` 파라미터를 추가하여, 값이 제공되면 daemon_id로 필터링한 결과를 반환하도록 한다.
|
||||
- `delete_broker_credential` 메서드 시그니처에 `daemon_id: str | None = None` 파라미터를 추가한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given `BrokerCredential` 인스턴스를 생성할 때 `daemon_id=None`이 기본값이면, When 인스턴스를 생성, Then `daemon_id` 필드가 `None`으로 존재한다.
|
||||
2. Given `BrokerCredential` 인스턴스를 생성할 때 `daemon_id="daemon-abc"` 값을 주면, When 인스턴스를 생성, Then `daemon_id` 필드가 `"daemon-abc"`로 존재한다.
|
||||
3. Given `_row_to_broker_credential` 메서드가 6컬럼 행(daemon_id 포함)을 받으면, When 변환, Then 반환 `BrokerCredential.daemon_id`가 해당 값으로 세팅된다.
|
||||
4. Given `_row_to_broker_credential` 메서드가 기존 6컬럼 행(daemon_id 없음)을 받으면, When 변환, Then `BrokerCredential.daemon_id`가 `None`으로 세팅된다(하위 호환).
|
||||
5. Given `list_broker_credentials(user_id="u1", daemon_id="d1")`를 호출하면, When DB에 `daemon_id="d1"` 행과 `daemon_id="d2"` 행이 있으면, Then `daemon_id="d1"` 행만 반환된다.
|
||||
6. Given `list_broker_credentials(user_id="u1")` (daemon_id 미지정)를 호출하면, When DB에 여러 행이 있으면, Then 모든 행을 반환한다(기존 동작 유지, 하위 호환).
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/data-model.md` — `BrokerCredential` 엔티티에 `daemon_id` 필드 추가, PK 변경 예고(`REQ-047 선행 마이그레이션 후 PK=(daemon_id, broker)`) 명시.
|
||||
- Modules:
|
||||
- `app/domain/models.py` — `BrokerCredential` dataclass에 `daemon_id: str | None = None` 필드 추가.
|
||||
- `app/repositories/postgres.py` — `_row_to_broker_credential`, `upsert_broker_credential`, `list_broker_credentials`, `delete_broker_credential` 시그니처 및 쿼리 갱신.
|
||||
- Tests:
|
||||
- `BrokerCredential` dataclass 필드 단위 테스트.
|
||||
- `_row_to_broker_credential` 행 변환 테스트(daemon_id 포함/미포함 두 케이스).
|
||||
- `list_broker_credentials` daemon_id 필터 테스트(mock 또는 메모리 repo 기반).
|
||||
|
||||
## Notes
|
||||
- REQ-047(마이그레이션) 선행 없이는 실제 DB 컬럼이 없으므로, 이 REQ의 구현은 DB 컬럼 유무에 관계없이 모델/메서드 시그니처만 정합화하는 것을 목표로 한다. 실제 DB 컬럼 처리는 REQ-047 이후 통합 시점에 완성된다.
|
||||
- `upsert_broker_credential` 쿼리의 ON CONFLICT 절 수정은 PK 변경(REQ-047) 완료 이후 정합화한다. 따라서 이 REQ에서 upsert 쿼리의 ON CONFLICT 처리는 설계 위임(REQ-047 delivery 연계).
|
||||
- `list_broker_credentials` daemon_id 필터는 `WHERE daemon_id = %s` 조건을 추가하는 방식으로 구현하며, NULL 처리는 `WHERE daemon_id IS NULL` 또는 파라미터 없음(전체 조회)으로 구분한다.
|
||||
- `data-model.md`의 `BrokerCredential` 엔티티 갱신(daemon_id 필드, PK 변경 예고)은 이 REQ 구현 시 함께 반영한다.
|
||||
- Source: dcy-010 FR-1, §4 C1.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260628-01
|
||||
- Date: 2026-06-28
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-010 FR-1 산출물.
|
||||
|
||||
#### Intent
|
||||
- dcy-010 confirmed Discovery의 FR-1(daemon_id 차원 도입)을 도메인 모델·repository 계층 변경으로 분리하여 REQ로 정규화한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1 ~ AC-6 전체 (신규 초안)
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/srs/Data/req-046_broker-credential-daemon-id-field.md` 신규 생성
|
||||
- `docs/data-model.md` 갱신 예고
|
||||
- Modules:
|
||||
- `app/domain/models.py`, `app/repositories/postgres.py` 최초 지정
|
||||
- Tests:
|
||||
- `BrokerCredential` 단위·행 변환·필터 테스트 신규
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (suggest-batch-reqs 대기)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req 후 batch 배정. REQ-047 마이그레이션과 delivery 연계.
|
||||
|
||||
#### Validation Plan
|
||||
- pytest로 AC-1~6 각 케이스 확인. `_row_to_broker_credential` 하위호환(AC-4) 포함.
|
||||
|
||||
### CHG-20260628-02
|
||||
- Date: 2026-06-28
|
||||
- Author: 팀장(Claude)
|
||||
- confirm-req: `Proposed` → `Approved`. dcy-010 FR-1 추적 확인, AC testable, 하위호환(AC-4) 적절. REQ-047 선행 의존 명시됨. blocker 없음.
|
||||
@@ -0,0 +1,94 @@
|
||||
# REQ-049: GET /v1/user_info daemon-scoped 단일 broker 전달로 확장
|
||||
|
||||
- Status: Approved
|
||||
- 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 없음.
|
||||
@@ -0,0 +1,90 @@
|
||||
# REQ-050: credential CLI --daemon-id 옵션 추가
|
||||
|
||||
- Status: Approved
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- 현재 `pfhub credential set / list / remove` CLI는 `--user` 옵션만으로 인증정보를 식별한다. daemon-scoped 소유 전환 이후 운영자가 daemon_id를 지정하여 특정 데몬의 인증정보를 등록/조회/삭제할 수 있도록 `--daemon-id` 옵션을 추가한다.
|
||||
- REQ-047 PK 전환 이후 `broker_credentials` PK가 `(daemon_id, broker)`로 변경되므로, 인증정보 등록/삭제 시 `daemon_id`가 필수 식별자가 된다.
|
||||
|
||||
## Requirement
|
||||
- `pfhub credential set` 커맨드에 `--daemon-id` 옵션(required, `--daemon-id <daemon_slug_or_id>`)을 추가한다. 이 옵션의 값을 `daemon_id`로 사용하여 `upsert_broker_credential(daemon_id=..., ...)` 호출 시 전달한다.
|
||||
- `pfhub credential list` 커맨드에 `--daemon-id` 옵션(optional)을 추가한다. 값이 제공되면 해당 daemon_id의 인증정보만 반환한다. 미제공 시 `--user` 소유 전체 인증정보를 반환한다(기존 동작 유지).
|
||||
- `pfhub credential remove` 커맨드에 `--daemon-id` 옵션(required)을 추가한다. 기존 `(user_id, broker)` 기반 삭제에서 `(daemon_id, broker)` 기반 삭제로 변경한다.
|
||||
- `--daemon-id` 옵션 값은 daemon_slug 또는 daemon UUID 중 하나를 허용한다. 내부에서 `Daemon` 조회로 daemon_id(UUID)를 해석한다. daemon이 존재하지 않으면 오류 메시지 출력 후 exit 1.
|
||||
- CLI 출력에 `daemon_id` 필드를 포함한다(기존 `user_id` 출력 유지, 추가).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given `pfhub credential set --user u1 --daemon-id d1 --broker kis --account-ref "123" --app-key "k" --app-secret "s"` 실행, When 명령 성공, Then `broker_credentials`에 `daemon_id=d1.id, broker="kis"` 행이 upsert된다.
|
||||
2. Given `pfhub credential list --user u1 --daemon-id d1` 실행, When 명령 성공, Then daemon_id=d1인 인증정보만 출력된다(daemon_id=d2 인증정보 미출력).
|
||||
3. Given `pfhub credential list --user u1` (daemon-id 미지정) 실행, When 명령 성공, Then 해당 user의 모든 인증정보가 출력된다(기존 동작 유지).
|
||||
4. Given `pfhub credential remove --user u1 --daemon-id d1 --broker kis` 실행, When 명령 성공, Then `broker_credentials`에서 `daemon_id=d1.id, broker="kis"` 행이 삭제된다.
|
||||
5. Given 존재하지 않는 daemon_slug를 `--daemon-id`에 지정, When 명령 실행, Then 오류 메시지("데몬을 찾을 수 없습니다") 출력 후 exit 1.
|
||||
6. Given `pfhub credential set` 실행 시 `--daemon-id` 옵션 미제공, When 명령 실행, Then 오류 메시지(`--daemon-id` required) 출력 후 exit 1. (REQ-047 PK 전환 이후 daemon_id 없이 등록 불가)
|
||||
7. Given `pfhub credential set` 실행 성공, When CLI 출력 확인, Then `daemon_id`, `broker`, `account_ref`, `credential_version`, `last_rotated_at` 필드가 모두 포함된다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — CLI credential surface 섹션: `--daemon-id` 옵션 추가, daemon-scoped 등록/조회/삭제 예시.
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` — `set_credential`, `list_credentials`, `remove_credential` 함수에 `--daemon-id` 파라미터 추가. daemon 조회 로직 추가.
|
||||
- `app/repositories/postgres.py` — `get_daemon_by_slug(daemon_slug)` 또는 동등한 메서드가 필요(기존 존재 여부 확인 후 추가 또는 재사용).
|
||||
- Tests:
|
||||
- `set_credential --daemon-id` 단위 테스트(mock repo).
|
||||
- `list_credentials --daemon-id` 필터 단위 테스트.
|
||||
- `remove_credential --daemon-id` 단위 테스트.
|
||||
- daemon 미존재 오류 케이스(AC-5).
|
||||
- `--daemon-id` 미제공 시 오류(AC-6).
|
||||
|
||||
## Notes
|
||||
- `--daemon-id` 옵션은 daemon_slug(사람이 읽기 쉬운 식별자) 또는 daemon UUID를 허용할지 여부는 batch design 단계에서 기존 `daemons` 테이블 스키마를 확인하여 확정한다. 기존 CLI의 `--user` 옵션이 username을 받아 내부에서 user_id로 변환하는 패턴과 동일하게 설계한다.
|
||||
- REQ-047 PK 전환 이전 단계(daemon_id 컬럼은 있으나 nullable)에서는 `--daemon-id` 미제공 시에도 set/remove를 허용할지 결정이 필요하다. 단계적 마이그레이션 기간 중 호환성 정책은 batch design에서 확정한다. 이 REQ의 AC-6은 PK 전환(REQ-047 Step 2) 완료 이후 상태를 기준으로 한다.
|
||||
- `pfhub credential list` daemon_id 미지정 동작(전체 조회, AC-3)은 PK 전환 이후 daemon_id 컬럼이 NOT NULL이 되어도 유지된다(admin 관리 목적).
|
||||
- Source: dcy-010 FR-3, §4 C3.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260628-01
|
||||
- Date: 2026-06-28
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-010 FR-3 산출물.
|
||||
|
||||
#### Intent
|
||||
- dcy-010 FR-3(인증정보 관리 경로에 daemon_id 바인딩 추가)을 CLI Interface REQ로 정규화한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1 ~ AC-7 전체 (신규 초안)
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/srs/Interface/req-050_credential-cli-daemon-id-option.md` 신규 생성
|
||||
- `docs/interface-contract.md` 갱신 예고
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` (옵션 추가), `app/repositories/postgres.py` (daemon 조회 메서드)
|
||||
- Tests:
|
||||
- credential CLI daemon-id 옵션 단위 테스트 신규
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (suggest-batch-reqs 대기)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req 후 batch 배정. REQ-046(모델), REQ-047(스키마) 선행 의존. --daemon-id 필수/선택 단계 정책은 design에서 확정.
|
||||
|
||||
#### Validation Plan
|
||||
- pytest mock repo 기반 단위 테스트 AC-1~7. daemon 미존재 오류 케이스(AC-5) 포함.
|
||||
|
||||
### CHG-20260628-02
|
||||
- Date: 2026-06-28
|
||||
- Author: 팀장(Claude)
|
||||
- confirm-req: `Proposed` → `Approved`. dcy-010 FR-3 추적. set/list/remove --daemon-id 적절. slug vs UUID 해석 + REQ-047 과도기(daemon_id nullable 구간) --daemon-id 필수/선택 정책은 batch design 위임 — AC 무효화 아님(비차단).
|
||||
@@ -0,0 +1,95 @@
|
||||
# REQ-047: broker_credentials 테이블 daemon-scoped PK 전환 마이그레이션
|
||||
|
||||
- Status: Approved
|
||||
- Type: Migration
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- 현재 `broker_credentials` 테이블의 PK는 `(user_id, broker)`로, 같은 user의 두 데몬이 서로 다른 KIS 계좌를 가질 수 없다. PK를 `(daemon_id, broker)`로 전환하여 인증정보를 daemon-scoped로 소유하게 한다.
|
||||
- dcy-010에서 결정된 단계적 마이그레이션 경로(nullable `daemon_id` 컬럼 추가 → 백필(REQ-048) → PK 전환)에서, 이 REQ는 스키마 변경 단계를 담당한다. PK 전환 전 데이터 백필은 REQ-048에서 별도로 수행한다.
|
||||
- 이 마이그레이션은 비가역 PK 변경을 포함하는 고위험 작업으로, 팀장/사장님 결재 게이트(외부 비가역 작업)를 거쳐야 한다.
|
||||
|
||||
## Requirement
|
||||
- Alembic 신규 revision을 작성한다. (down_revision은 현재 최신 head에서 파악)
|
||||
- **Step 1 (nullable 컬럼 추가)**: `broker_credentials`에 `daemon_id TEXT NULL` 컬럼을 추가한다. 기존 데이터는 `daemon_id=NULL` 상태로 남는다(백필은 REQ-048이 별도 처리).
|
||||
- **Step 2 (PK 전환 준비)**: 백필 완료(REQ-048) 이후 적용할 별도 revision을 작성한다. 이 revision에서:
|
||||
- 기존 PK 제약 `(user_id, broker)`를 삭제한다.
|
||||
- `daemon_id NOT NULL` 제약을 추가한다.
|
||||
- 새 PK 제약 `(daemon_id, broker)`를 추가한다.
|
||||
- `user_id` 컬럼은 감사/이력 목적으로 유지한다(삭제 금지).
|
||||
- `app/repositories/postgres.py`의 `ensure_schema()` 내 `broker_credentials` 정의를 최종 daemon-scoped 스키마(daemon_id NOT NULL, PK=(daemon_id, broker))와 정합하게 갱신한다. 단, `ensure_schema()`의 갱신은 PK 전환 revision 적용 이후 활성화한다.
|
||||
- downgrade 함수는 PK 전환 이전 상태로 되돌릴 수 있도록 작성한다(daemon_id 컬럼 제거, 기존 PK 복원). 단, PK 전환 revision의 downgrade는 데이터 손실 위험이 있어 실 운영 적용 전 롤백 스크립트를 별도 제공한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given Step 1 revision 적용 후, When `\d broker_credentials` 확인, Then `daemon_id TEXT NULL` 컬럼이 존재하고 기존 PK `(user_id, broker)` 제약이 그대로 유지된다.
|
||||
2. Given Step 1 revision 적용 후, When 기존 행을 조회, Then `daemon_id`가 NULL인 상태로 데이터가 보존된다.
|
||||
3. Given Step 2(PK 전환) revision 적용 후 (REQ-048 백필 완료 이후), When `\d broker_credentials` 확인, Then PK 제약이 `(daemon_id, broker)`로 변경되고 `daemon_id NOT NULL` 제약이 존재한다.
|
||||
4. Given AC-3 환경, When `user_id` 컬럼 존재 확인, Then `user_id` 컬럼이 여전히 존재한다(삭제되지 않음).
|
||||
5. Given AC-3 환경, When `ensure_schema()` 단독 실행, Then `broker_credentials` 스키마가 마이그레이션과 동일하다(daemon_id NOT NULL, PK=(daemon_id, broker), user_id 존재).
|
||||
6. Given Step 1 revision의 downgrade 실행, When `\d broker_credentials` 확인, Then `daemon_id` 컬럼이 제거되고 기존 PK `(user_id, broker)` 제약이 복원된다.
|
||||
7. Given Step 2 revision의 downgrade 실행, When 실행, Then downgrade가 실패하거나 데이터 손실 경고를 명시적으로 표시한다(안전 장치 존재).
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/data-model.md` — `BrokerCredential` 엔티티 PK 변경(`(user_id, broker)` → `(daemon_id, broker)`), `user_id` 유지 명시, daemon-scoped 소유 정책 기술.
|
||||
- Modules:
|
||||
- `migrations/versions/` — Step 1 revision (nullable daemon_id 추가), Step 2 revision (PK 전환). down_revision은 현재 최신 head.
|
||||
- `app/repositories/postgres.py` — `ensure_schema()` 내 `broker_credentials` 정의 갱신(PK 전환 revision 이후).
|
||||
- Tests:
|
||||
- alembic upgrade head 후 schema 검증(AC-1~5).
|
||||
- Step 1 downgrade 테스트(AC-6).
|
||||
- 기존 broker credential CRUD 회귀 테스트(마이그레이션 전후 데이터 보존).
|
||||
|
||||
## Notes
|
||||
- 이 마이그레이션은 **PK 변경**이라는 비가역 작업을 포함한다. 팀장/사장님 결재 게이트 대상이다(dcy-010 R-1, §범위 경계 — "DB 마이그레이션"은 외부 비가역 작업). 실 운영 DB 적용 전 반드시 결재를 받는다.
|
||||
- Step 1과 Step 2를 별도 revision으로 분리하는 이유: REQ-048 백필이 완료된 후에만 PK 전환이 가능하기 때문이다. Step 1만 먼저 적용하고, REQ-048 백필 → Step 2 순서로 진행한다.
|
||||
- `user_id` 컬럼을 유지하는 이유: 기존 인증정보의 소유자 이력 보존 및 admin 조회 기능 유지.
|
||||
- daemon-scoped 강제(user 폴백 없음): dcy-010 OQ-1 결정. `daemon_id=NULL`인 인증정보는 PK 전환 이후 INSERT 불가(NOT NULL 제약). 기존 NULL 데이터는 REQ-048 백필 이후만 PK 전환이 가능하다.
|
||||
- Step 2 downgrade는 daemon_id → user_id PK 복원 시 데이터 무결성을 보장하기 어려우므로, 운영 환경에서는 rollback 전 별도 데이터 검증이 필요하다.
|
||||
- Source: dcy-010 FR-1, R-1 완화 방안, §4 C1.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260628-01
|
||||
- Date: 2026-06-28
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-010 FR-1 산출물 (마이그레이션 계층 분리).
|
||||
|
||||
#### Intent
|
||||
- dcy-010 FR-1의 스키마 PK 전환 작업을 독립 마이그레이션 REQ로 정규화한다. Step 1(nullable 추가)과 Step 2(PK 전환)를 별도 revision으로 분리하는 설계를 반영한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1 ~ AC-7 전체 (신규 초안)
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/srs/Migration/req-047_broker-credentials-daemon-scoped-pk-migration.md` 신규 생성
|
||||
- `docs/data-model.md` 갱신 예고
|
||||
- Modules:
|
||||
- `migrations/versions/` (Step 1, Step 2 revision 신규), `app/repositories/postgres.py` 갱신 예고
|
||||
- Tests:
|
||||
- alembic 스키마 검증, downgrade, CRUD 회귀 신규
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (suggest-batch-reqs 대기)
|
||||
- Releases: 미정 (비가역 — 사장님 결재 필요)
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req 후 batch 배정. REQ-048(백필)과 순서 의존 관계 명시.
|
||||
|
||||
#### Validation Plan
|
||||
- alembic upgrade head 후 schema 검증(AC-1~5). Step 1 downgrade 검증(AC-6). 개발 DB에서 dry-run.
|
||||
|
||||
### CHG-20260628-02
|
||||
- Date: 2026-06-28
|
||||
- Author: 팀장(Claude)
|
||||
- confirm-req: `Proposed` → `Approved`. dcy-010 FR-1/R-1 추적. Step1/Step2 분리 + REQ-048 백필 사이 순서 정합. **Step2 PK 전환(실DB 적용)은 비가역 → 사장님 결재 게이트 대상**(Notes 명시 확인). blocker 없음.
|
||||
@@ -0,0 +1,91 @@
|
||||
# REQ-048: 기존 user-scoped 인증정보 daemon_id 백필
|
||||
|
||||
- Status: Approved
|
||||
- Type: Migration
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- REQ-047 Step 1 적용 이후 `broker_credentials.daemon_id`가 NULL인 기존 행들을 운영자가 지정하는 daemon_id로 백필하여, REQ-047 Step 2(PK 전환)가 적용 가능한 상태로 만든다.
|
||||
- dcy-010 FR-4(기존 user-scoped 인증정보를 손실 없이 daemon-scoped로 이전)의 데이터 이전 경로를 제공한다. 백필은 CLI 명령 또는 one-shot 마이그레이션 스크립트로 수행한다.
|
||||
|
||||
## Requirement
|
||||
- `pfhub credential backfill-daemon-id` CLI 명령(또는 동등한 alembic data migration revision)을 제공한다. 이 명령은 다음을 수행한다:
|
||||
- `broker_credentials`에서 `daemon_id IS NULL`인 행을 조회한다.
|
||||
- 각 행의 `user_id`에 대해 운영자가 제공한 매핑(`--user <username> --daemon-id <daemon_id>`)으로 `daemon_id`를 UPDATE한다.
|
||||
- 백필 대상 행이 없으면 "이미 완료됨" 메시지를 출력하고 정상 종료한다.
|
||||
- `--dry-run` 옵션을 제공하여 실제 UPDATE 없이 대상 행과 매핑 결과를 출력한다.
|
||||
- 백필 완료 후 `daemon_id IS NULL`인 행이 0건임을 검증하는 단계를 명령 결과 또는 별도 verify 서브커맨드로 제공한다.
|
||||
- 기존 행의 암호화된 인증정보(`encrypted_payload`, `account_ref`, `credential_version`, `last_rotated_at`)는 백필 과정에서 변경되지 않는다.
|
||||
- `--force` 옵션으로 확인 프롬프트를 생략할 수 있다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given `broker_credentials`에 `daemon_id=NULL`인 행이 있고 `--user u1 --daemon-id d1` 매핑을 제공하면, When `backfill-daemon-id` 명령 실행, Then 해당 `user_id=u1(user.id)`에 해당하는 행의 `daemon_id`가 `d1`로 UPDATE된다.
|
||||
2. Given AC-1 실행 후, When `broker_credentials`에서 `daemon_id IS NULL` 행 조회, Then 0건이 반환된다.
|
||||
3. Given `--dry-run` 옵션으로 실행, When 백필 대상 행이 있으면, Then DB UPDATE 없이 대상 행과 예상 매핑이 출력되고 실제 데이터는 변경되지 않는다.
|
||||
4. Given 백필 실행 중, When `encrypted_payload`, `account_ref`, `credential_version`, `last_rotated_at` 컬럼 확인, Then 백필 전후 값이 동일하다(데이터 손실 없음).
|
||||
5. Given `daemon_id IS NULL`인 행이 없을 때, When `backfill-daemon-id` 명령 실행, Then "이미 완료됨" 메시지와 함께 정상 종료(exit 0)된다.
|
||||
6. Given 존재하지 않는 user를 `--user` 옵션으로 지정, When 명령 실행, Then 오류 메시지와 함께 비정상 종료(exit 1)된다.
|
||||
7. Given 백필 완료 후 REQ-047 Step 2(PK 전환) revision 적용, When `\d broker_credentials` 확인, Then PK `(daemon_id, broker)` 제약이 적용되고 NOT NULL 위반 없이 성공한다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/data-model.md` — `BrokerCredential` 데이터 이전 절차(백필 단계) 명시.
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` — `backfill-daemon-id` 서브커맨드 추가.
|
||||
- `app/repositories/postgres.py` — `update_broker_credential_daemon_id(user_id, daemon_id)` 또는 동등한 메서드 추가(또는 CLI에서 직접 쿼리).
|
||||
- Tests:
|
||||
- `backfill-daemon-id` 명령 단위 테스트(mock repo 기반): 정상 백필, dry-run, 이미 완료, 사용자 미존재 케이스.
|
||||
- 백필 전후 `encrypted_payload` 동등성 검증(AC-4).
|
||||
|
||||
## Notes
|
||||
- 이 REQ는 **1회성 운영 작업**이다. 개발 환경에서는 테스트 목적으로 반복 실행 가능하도록 설계한다.
|
||||
- 백필 매핑은 운영자가 직접 제공한다. "어떤 user가 어떤 daemon_id에 매핑되는가"는 인프라/운영 도메인 지식이 필요하며 자동화할 수 없다.
|
||||
- CLI 구현 방식(subcommand 추가 vs alembic data migration revision)은 batch design 단계에서 확정한다. 어느 쪽이든 AC-1~7을 만족해야 한다.
|
||||
- `--user` 옵션은 username으로 받고 내부에서 `get_user_by_username()`으로 user_id를 해석한다(기존 CLI 패턴과 동일).
|
||||
- Source: dcy-010 FR-4, §4 C1(마이그레이션 단계적 경로).
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260628-01
|
||||
- Date: 2026-06-28
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-010 FR-4 산출물.
|
||||
|
||||
#### Intent
|
||||
- dcy-010 FR-4(기존 user-scoped 인증정보 손실 없는 daemon-scoped 이전)를 독립 REQ로 정규화한다. REQ-047 Step 1과 Step 2 사이의 데이터 이전 경로를 담당한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1 ~ AC-7 전체 (신규 초안)
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/srs/Migration/req-048_broker-credentials-daemon-id-backfill.md` 신규 생성
|
||||
- `docs/data-model.md` 갱신 예고
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` (backfill subcommand), `app/repositories/postgres.py` (업데이트 메서드)
|
||||
- Tests:
|
||||
- backfill CLI 단위 테스트 신규
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (suggest-batch-reqs 대기)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req 후 batch 배정. REQ-047 Step 1 이후, Step 2 이전 순서 의존 관계 준수.
|
||||
|
||||
#### Validation Plan
|
||||
- pytest mock repo 기반 단위 테스트 AC-1~6. 개발 DB 통합: backfill → REQ-047 Step 2 순서 검증(AC-7).
|
||||
|
||||
### CHG-20260628-02
|
||||
- Date: 2026-06-28
|
||||
- Author: 팀장(Claude)
|
||||
- confirm-req: `Proposed` → `Approved`. dcy-010 FR-4 추적. dry-run/verify·암호화 payload 불변(AC-4) 적절. 구현방식(CLI subcommand vs alembic data revision)은 batch design 위임 — AC 무효화 아님(비차단).
|
||||
@@ -0,0 +1,96 @@
|
||||
# REQ-051: 브로커 인증정보 보안 격리 — service-token 전용·web 토큰 차단
|
||||
|
||||
- Status: Approved
|
||||
- Type: Non-Functional
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
- 복호화된 app_secret(KIS API 비밀키)은 실계좌 실자금에 직결되는 최민감 시크릿이다. 이 정보가 복호화된 형태로 service-token 경로 외 다른 경로(web user 토큰, admin 토큰, 비인증 접근)로 유출되지 않아야 한다.
|
||||
- portfolio-hub가 M3에서 trex-web 프론트에 노출될 예정이므로, web 노출 표면과 인증정보 전달 경로의 격리를 명시적으로 요구사항으로 정의하고 테스트로 검증한다.
|
||||
- dcy-010 NFR-1(보안 격리), NFR-2(일관성), NFR-3(가용성/degrade)의 독립 측정·검증 필요성을 근거로 별도 REQ로 승격한다.
|
||||
|
||||
## Requirement
|
||||
- `GET /v1/user_info`는 service token(token_source="db", token_type="service")으로만 접근 가능하다. web user 토큰(token_type="session" 또는 client 타입), 비인증 요청, admin 토큰(token_type이 service가 아닌 경우)은 HTTP 403을 반환해야 한다.
|
||||
- 복호화된 `credential_payload`(`app_secret` 포함)는 오직 `GET /v1/user_info` 응답을 통해서만 반환되어야 하며, 다른 엔드포인트(portfolio, asset, health, admin 등)의 응답에 포함되어서는 안 된다.
|
||||
- `GET /v1/user_info`에서 daemon_id가 없는(None) 토큰은 HTTP 403으로 거부한다(REQ-049와 동일 — 보안 정책으로도 명시).
|
||||
- 인증정보 조회·복호화 실패(DB 오류, 복호화 실패)는 엔드포인트 전체를 크래시시키지 않는다. 실패 시 `broker: null`(또는 `credential_payload: null`)을 반환하고 경고 로그를 남긴다.
|
||||
- 인증정보 관련 조회 로그에는 `credential_payload`의 내용(app_key, app_secret)이 포함되어서는 안 된다. 로그에는 broker 이름, daemon_id, 오류 유형만 기록한다.
|
||||
- universe의 daemon-scoped 인가(`managed_by_daemon_id` 일치 검사, REQ-028)와 동일한 규율로, broker 인증정보 접근도 daemon_id 일치를 강제한다(daemon_id 불일치 → 403, 다른 데몬의 인증정보 반환 불가).
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given web user 토큰(token_type이 service가 아닌 token)으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환(credential_payload 미노출).
|
||||
2. Given 비인증 요청으로 GET /v1/user_info 호출, When 호출, Then HTTP 401 반환.
|
||||
3. Given daemon_id=None인 service token으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환.
|
||||
4. Given daemon_id="d1"인 service token으로 GET /v1/user_info 호출, When DB에 daemon_id="d2"의 인증정보만 존재하면, Then `broker: null` 반환 (다른 데몬의 인증정보 절대 반환하지 않음).
|
||||
5. Given GET /v1/user_info 응답 외 다른 엔드포인트(GET /v1/portfolios, GET /v1/assets, GET /health 등) 응답, When 응답 JSON 확인, Then `credential_payload`, `app_secret`, `app_key` 필드가 포함되지 않는다.
|
||||
6. Given DB 오류로 `list_broker_credentials` 실패, When GET /v1/user_info 호출, Then HTTP 200 + `broker: null` 반환 (500 크래시 없음), 경고 로그 기록.
|
||||
7. Given Fernet 복호화 실패(잘못된 `CREDENTIAL_ENCRYPTION_KEY`), When GET /v1/user_info 호출, Then HTTP 200 + `broker.credential_payload: null` 반환, 경고 로그 기록(app_secret 로그 미노출).
|
||||
8. Given GET /v1/user_info 정상 처리 중 로그 검사, When 로그 출력 확인, Then `app_key`, `app_secret` 값이 로그에 출력되지 않는다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — GET /v1/user_info 보안 정책 섹션: service-token 전용, daemon_id 없음 → 403, web 토큰 차단, credential 로그 비노출 명시.
|
||||
- Modules:
|
||||
- `app/api/v1_user_info.py` — `_require_service_token` 함수(기존 service-token gate, 변경 없거나 daemon_id 검사 보완).
|
||||
- `app/api/v1_user_info.py` — 로깅 경고 메시지에서 credential 값 제외 확인.
|
||||
- Tests:
|
||||
- web 토큰 → 403 테스트(AC-1).
|
||||
- 비인증 → 401 테스트(AC-2).
|
||||
- daemon_id=None → 403 테스트(AC-3).
|
||||
- 다른 데몬 인증정보 → null 테스트(AC-4).
|
||||
- 타 엔드포인트 credential 미노출 테스트(AC-5): 주요 엔드포인트 응답 필드 검사.
|
||||
- DB 오류 graceful degrade 테스트(AC-6).
|
||||
- 복호화 실패 degrade 테스트(AC-7): 기존 테스트 확인/보완.
|
||||
|
||||
## Notes
|
||||
- dcy-010 NFR-1(보안 격리), NFR-2(일관성), NFR-3(degrade)가 모두 독립 측정 가능하고 테스트로 검증할 수 있어 별도 REQ로 승격한다. 이 세 NFR을 단일 REQ로 묶은 것은 모두 동일한 엔드포인트(GET /v1/user_info)의 보안 속성에 관한 것이기 때문이다.
|
||||
- AC-4(다른 데몬 인증정보 반환 불가)는 REQ-049 AC-4/5와 overlap하지만, 보안 관점의 요구사항으로 이 REQ에서 독립적으로 추적한다.
|
||||
- 로그 비노출(AC-8)은 현재 구현의 `logger.warning("credential decryption failed for broker %s: %s", cred.broker, exc)` 패턴이 app_secret을 exc에 포함할 가능성을 배제해야 함을 의미한다. 구현 시 확인이 필요하다.
|
||||
- `CREDENTIAL_ENCRYPTION_KEY` 자체의 보안 관리(환경변수 주입, 키 회전 정책)는 dcy-010 A-3(외부 시크릿 매니저 Out of Scope)에 따라 이 REQ의 범위 밖이다. 현재 환경변수 기반 유지.
|
||||
- Source: dcy-010 NFR-1, NFR-2, NFR-3.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260628-01
|
||||
- Date: 2026-06-28
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-010 NFR-1/2/3 산출물.
|
||||
|
||||
#### Intent
|
||||
- dcy-010 NFR-1(보안 격리)·NFR-2(일관성)·NFR-3(degrade) 세 NFR을 독립 측정·테스트 가능한 요구사항으로 묶어 Non-Functional REQ로 정규화한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1 ~ AC-8 전체 (신규 초안)
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/srs/Non-Functional/req-051_broker-credential-security-isolation.md` 신규 생성
|
||||
- `docs/interface-contract.md` 갱신 예고
|
||||
- Modules:
|
||||
- `app/api/v1_user_info.py` 보안 게이트 및 로깅 확인
|
||||
- Tests:
|
||||
- 보안 격리 테스트 신규(AC-1~4, AC-8), degrade 테스트(AC-6/7)
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (suggest-batch-reqs 대기)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req 후 batch 배정. REQ-049와 동일 batch에 묶어 전달하는 것이 자연스럽다.
|
||||
|
||||
#### Validation Plan
|
||||
- pytest로 AC-1~8 각 케이스 확인. AC-8(로그 비노출)은 caplog fixture로 검증.
|
||||
|
||||
### CHG-20260628-02
|
||||
- Date: 2026-06-28
|
||||
- Author: 팀장(Claude)
|
||||
- confirm-req: `Proposed` → `Approved`. dcy-010 NFR-1/2/3 흡수 적절(동일 엔드포인트 보안속성). service-token 전용·web 토큰 차단·타 엔드포인트 credential 미노출(AC-5)·로그 비노출(AC-8)·degrade(AC-6/7) testable. REQ-049와 동일 batch 권고. blocker 없음.
|
||||
+9
-2
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-046`
|
||||
- Current: `REQ-052`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -126,12 +126,19 @@
|
||||
| REQ-043 | GET /v1/portfolios/{id}/holdings/cost-basis 현재 보유 평단(WAC) read 엔드포인트 신설 | Interface | Implemented | High | Justin Ko | [req-043](./Interface/req-043_holdings-cost-basis-read-endpoint.md) |
|
||||
| REQ-044 | GET /v1/assets 권한 완화 — require_admin → require_read | Interface | Implemented | High | Justin Ko | [req-044](./Interface/req-044_get-v1-assets-require-read-relaxation.md) |
|
||||
| REQ-045 | 사용자 가용 데몬 목록 read 엔드포인트 신설 | Interface | Implemented | High | Justin Ko | [req-045](./Interface/req-045_user-daemon-list-endpoint.md) |
|
||||
| REQ-046 | BrokerCredential 도메인 모델 daemon_id 필드 추가 | Data | Approved | High | Justin Ko | [req-046](./Data/req-046_broker-credential-daemon-id-field.md) |
|
||||
| REQ-047 | broker_credentials 테이블 daemon-scoped PK 전환 마이그레이션 | Migration | Approved | High | Justin Ko | [req-047](./Migration/req-047_broker-credentials-daemon-scoped-pk-migration.md) |
|
||||
| REQ-048 | 기존 user-scoped 인증정보 daemon_id 백필 | Migration | Approved | High | Justin Ko | [req-048](./Migration/req-048_broker-credentials-daemon-id-backfill.md) |
|
||||
| REQ-049 | GET /v1/user_info daemon-scoped 단일 broker 전달로 확장 | Interface | Approved | High | Justin Ko | [req-049](./Interface/req-049_user-info-daemon-scoped-broker-delivery.md) |
|
||||
| REQ-050 | credential CLI --daemon-id 옵션 추가 | Interface | Approved | High | Justin Ko | [req-050](./Interface/req-050_credential-cli-daemon-id-option.md) |
|
||||
| REQ-051 | 브로커 인증정보 보안 격리 — service-token 전용·web 토큰 차단 | Non-Functional | Approved | High | Justin Ko | [req-051](./Non-Functional/req-051_broker-credential-security-isolation.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-06-28: REQ-046~051 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-010 daemon-scoped broker credential. 전건 dcy-010 FR/NFR 추적·AC testable·blocker 0 확인. 단계적 마이그레이션(REQ-047 Step1/2)·**REQ-047 Step2 PK 전환은 비가역 결재 게이트**·2데몬 격리(REQ-049 AC-4/5)·보안 격리(REQ-051) 반영. 비차단 design 위임 2건(REQ-048 구현방식, REQ-050 slug/UUID·과도기 정책).
|
||||
- 2026-06-28: REQ-046~051 신규 (Proposed) — dcy-010 confirmed Discovery(daemon-scoped broker credential provisioning) draft-req. REQ-046(Data: 도메인 모델 daemon_id 필드), REQ-047(Migration: PK 전환 Step1/2), REQ-048(Migration: daemon_id 백필 CLI), REQ-049(Interface: GET /v1/user_info daemon-scoped 단일 broker 전달), REQ-050(Interface: credential CLI --daemon-id 옵션), REQ-051(Non-Functional: 보안 격리).
|
||||
- 2026-06-27: REQ-025 name 검증 완화(CHG-20260627-03) — `_PORTFOLIO_NAME_RE` 한글(가-힣)+공백 허용(`^[a-zA-Z0-9가-힣 ._:\-]{1,100}$`). minor-change. 기존 AC 유지, 테스트 3개 추가.
|
||||
- 2026-06-27: REQ-044/045 `Approved` → `Implemented` — BAT-019 PR #16 main 머지(`7f15c6b`) QC PASS. 472 passed/0 failed. REQ-045 env토큰(user_id=None) 처리 Notes 정정: "HTTP 403" → "빈 목록 [] HTTP 200"(change-req CHG-20260627-02).
|
||||
- 2026-06-27: REQ-044/045 신규 (Proposed) — dcy-009 FR-1(GET /v1/assets require_read 완화)·FR-2(사용자 가용 데몬 목록 엔드포인트 신설) REQ 초안 작성. 엔드포인트 경로·스키마(OQ-2) 및 가용 데몬 의미(OQ-3)는 design Deferred.
|
||||
- 2026-06-26: REQ-041 WAC 공식 change-req(CHG-20260626-01) — 매수 수수료 cost basis 편입(사장님 결정). 신 공식: `running_wac = (running_wac*qty + price*qty + buy_fee) / new_total_qty`. `calculate_realized_pnl`과 `calculate_current_wac` 양 함수 적용. AC-9/AC-10 추가. 신규 테스트 9개 포함 456 passed / 0 failed. REQ-040/REQ-043 Notes 참조 갱신.
|
||||
- 2026-06-26: REQ-043 `Approved` → `Implemented` — BAT-018 PR #14 squash-merge(d93c0bc) 팀장 QC PASS 후 origin/main 머지 완료. AC-1~11 전부 PASS(447 passed / 0 failed). 배포 별도(release-candidate).
|
||||
- 2026-06-25: REQ-043 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-008 근거 Discovery 양방향 링크 확정. OQ-1(service token 소유권 blocker) 해소: cold-start는 `managed_by_daemon_id=daemon` 포트폴리오만 조회하므로 null 케이스 blocker 아님, daemon scope 소유권 정책(REQ-029 선례) 유지 확정. OQ-2(domain 함수) 구현 단계 재량 위임. AC-1~11 유지.
|
||||
|
||||
Reference in New Issue
Block a user