docs(dcy-011): draft-req + confirm-req — REQ-052/053/054 Approved

Add three approved requirements documents for dcy-011:
- REQ-052: User-owned credential management API (Interface)
- REQ-053: Admin daemon CRUD API (Interface)
- REQ-054: Credential management security with write-only constraint (Non-Functional)

Update discovery document to reference generated requirements and update index with new REQ IDs and Change Log.

Scope: Self-service credential management + admin daemon CRUD + security-isolated API for portfolio-hub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-28 13:55:45 +09:00
co-authored by Claude Opus 4.8
parent a1bde2cf67
commit 80d0511d7f
5 changed files with 305 additions and 4 deletions
@@ -0,0 +1,99 @@
# REQ-052: 사용자 소유 스코프 인증정보 관리 HTTP API
- Status: Approved
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- 현재 BrokerCredential 관리는 CLI(`pfhub credential`)와 service-token read 경로(REQ-051/REQ-049)만 존재하며, HTTP user-token 기반 관리 표면이 없다.
- trex-web dcy-009 사용자 self-service UI가 소비하는 백엔드 경로를 신설한다: 사용자가 자기 소유 데몬의 KIS 인증정보를 HTTP API로 등록·회전·삭제하고, 메타데이터(secret 미포함)를 조회할 수 있도록 한다.
- dcy-010 자산(BrokerCredential 모델, Fernet 암호화, `CREDENTIAL_ENCRYPTION_KEY`, PK `(daemon_id, broker)`)을 재사용하여 CLI와 동일 repository·암호화 경로를 공유한다.
## Requirement
- 시스템은 user-token으로 인증된 사용자가 **자기 소유 데몬**의 broker 인증정보를 등록·회전·삭제할 수 있는 write 엔드포인트를 제공해야 한다.
- 등록/회전: `app_key` + `app_secret` + `account_ref` 입력을 Fernet 암호화하여 `BrokerCredential`에 upsert. 회전 시 `credential_version` 증가, `last_rotated_at` 갱신.
- 삭제: 해당 `(daemon_id, broker)` 행 제거.
- 시스템은 사용자가 자기 소유 데몬의 인증정보 **메타데이터**를 조회할 수 있는 read 엔드포인트를 제공해야 한다. 응답에는 `broker`, `account_ref`, `credential_version`, `last_rotated_at`, 보유 여부(`has_credential`)만 포함하며 **복호화된 `app_key`/`app_secret`은 절대 포함하지 않는다**.
- **소유 스코프 인가**를 모든 관리 경로에 강제해야 한다: 요청자 `user_id`가 대상 데몬의 `owner_user_id`와 일치하지 않으면 HTTP 403 Forbidden을 반환한다. 인증 토큰이 없는 요청은 HTTP 401을 반환한다.
- 인증정보 write 요청이 성공하면 응답에는 메타데이터(`broker`, `account_ref`, `credential_version`, `last_rotated_at`)만 반환하고 `app_key`/`app_secret`은 포함하지 않는다.
- 엔드포인트 경로·HTTP 메서드의 구체 형태는 draft-batch-design에서 v1 컨벤션(`/v1/daemons/{daemon_id}/credential` 방향 권고, dcy-011 OQ-1 Deferred)에 맞춰 확정한다.
- 기존 CLI(`pfhub credential`)와 동일한 `BrokerCredentialRepository` 메서드 및 `app/core/encryption.py` Fernet 자산을 재사용해야 한다.
## Acceptance Criteria
1. Given user-token(소유자 A)으로 자기 소유 daemon D에 `app_key`/`app_secret`/`account_ref` 를 등록, When write 엔드포인트를 호출, Then HTTP 200/201 + 메타데이터 응답(`broker`, `account_ref`, `credential_version`, `last_rotated_at`)이 반환되고 응답 JSON에 `app_key`/`app_secret` 필드가 존재하지 않는다.
2. Given 같은 소유자 A가 동일 daemon D 인증정보를 재등록(회전), When write 엔드포인트를 호출, Then 응답의 `credential_version`이 이전보다 1 증가하고 `last_rotated_at`이 갱신된다.
3. Given 소유자 A가 daemon D 인증정보를 등록한 뒤, When service-token `/v1/user_info`(REQ-049)로 해당 daemon D의 인증정보를 fetch하면, Then `broker.account_ref`가 방금 등록한 값과 일치한다(CLI·API·service-token 경로 정합).
4. Given user-token(소유자 A)으로 자기 소유 daemon D 인증정보 메타데이터를 GET, When 조회 엔드포인트를 호출, Then HTTP 200 + `broker`, `account_ref`, `credential_version`, `last_rotated_at`, `has_credential` 반환되고 `app_key`/`app_secret` 필드는 응답에 없다.
5. Given user-token(소유자 A)으로 자기 소유 daemon D 인증정보를 삭제, When delete 엔드포인트를 호출, Then HTTP 200/204 반환 후 해당 daemon D 메타데이터 조회 시 `has_credential: false`(또는 인증정보 없음 응답).
6. Given user-token(사용자 B)으로 **사용자 A 소유** daemon D의 인증정보에 대한 write/read/delete 요청, When 엔드포인트를 호출, Then HTTP 403 Forbidden 반환(소유 스코프 위반).
7. Given 인증 토큰 없는 요청으로 인증정보 관리 엔드포인트 호출, When 호출, Then HTTP 401 반환.
8. Given 존재하지 않는 daemon_id를 대상으로 인증정보 관리 요청, When 호출, Then HTTP 404 반환.
9. Given admin 토큰으로 자기 소유 데몬이 아닌 daemon D의 인증정보 write 엔드포인트 호출, When 호출, Then HTTP 403 반환(관리 API는 소유 스코프 전용 — admin role로 타인 인증정보 조작 불가).
10. Given service-token으로 인증정보 관리 엔드포인트 호출, When 호출, Then HTTP 403 반환(service-token은 read 전용 `/v1/user_info` 경로만 허용).
## Impacted Area
- Docs:
- `docs/interface-contract.md` — 인증정보 관리 엔드포인트 경로·권한·응답 스키마(secret 미포함 정책) 신규 섹션 추가.
- Modules:
- 신규 라우터 파일(경로·파일명은 design 확정) — 인증정보 관리 write/read/delete 엔드포인트.
- `app/schemas/` — `CredentialSetRequest`(app_key/app_secret/account_ref), `CredentialMetaResponse`(메타데이터 only, secret 미포함) 스키마 신규 정의.
- `app/services/` — 소유 스코프 인가 검사 + Fernet 암호화/upsert/삭제 서비스 메서드 신규 또는 확장.
- `app/repositories/broker_credential_repository.py` — 메타데이터 조회·write 메서드 확장(CLI와 공유).
- `app/core/encryption.py` — 재사용 (변경 없음).
- `app/main.py` 또는 라우터 등록 — 신규 라우터 include.
- Tests:
- AC-1~10 커버 pytest 케이스. 특히 AC-6(소유 스코프 음성), AC-9(admin 소유 스코프 음성), AC-10(service-token 음성), AC-3(CLI·API·service-token 정합) 강제 검증.
## Notes
- OQ-1(엔드포인트 경로): draft-batch-design 위임(dcy-011 Deferred). 팀장 권고 = daemon-scoped(`/v1/daemons/{daemon_id}/credential`). v1 컨벤션·dcy-009 UI 흐름 정합 설계 단계 확정.
- **복호화 secret 미반환 불변**: 이 REQ의 어떤 응답도 `app_secret`/`app_key` 평문을 포함하지 않는다. 유일 decrypt-return 경로는 service-token `GET /v1/user_info`(REQ-051/REQ-049) — 본 REQ로 변경하지 않는다.
- AC-9(admin 소유 스코프): admin은 데몬 CRUD(REQ-053)를 담당하며, admin role로 타인 인증정보 write/read를 허용하지 않는다. 인증정보는 **사용자 본인 소유 스코프만**. 이 경계가 REQ-052(user-token, 소유 스코프)와 REQ-053(admin, 데몬 CRUD)의 책임 분리이다.
- NFR-1(보안 — secret 미반환·로그 미노출)은 REQ-054에서 독립 Non-Functional REQ로 추적한다. 이 REQ AC는 응답 schema 단의 secret 부재 확인을 포함하며, REQ-054가 전체 보안 격리를 측정한다.
- NFR-3(일관성 — CLI·API 정합): AC-3이 흡수한다. 구현 시 동일 repository 메서드 재사용 강제.
- Source: dcy-011 FR-1, FR-2, FR-3, NFR-2, NFR-3.
## Change Log
### CHG-20260628-01
- Date: 2026-06-28
- Author: Justin Ko
#### Change Summary
- 신규 생성 (Proposed) — dcy-011 FR-1/FR-2/FR-3 산출물.
#### Intent
- dcy-011 confirmed Discovery의 FR-1(인증정보 등록·회전·삭제), FR-2(메타데이터 조회, secret 미반환), FR-3(소유 스코프 인가 강제)를 단일 Interface REQ로 정규화한다. 세 FR이 동일 관리 표면(user-token + 소유 스코프 인증정보 관리)에 속하므로 하나의 REQ로 묶는다.
#### Acceptance Criteria Delta
- Added:
- AC-1 ~ AC-10 전체 (신규 초안)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs:
- `docs/srs/Interface/req-052_user-owned-credential-management-api.md` 신규 생성
- `docs/interface-contract.md` 갱신 예고
- Modules:
- 인증정보 관리 라우터(신규), 스키마(신규), 서비스·repository 확장
- Tests:
- 소유 스코프 음성(AC-6/9/10), 메타데이터 secret 부재(AC-1/4), CLI 정합(AC-3) 케이스 신규
#### 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 후 suggest-batch-reqs. REQ-053/REQ-054와 동일 batch 묶기 권고. OQ-1(경로)은 design 단계 해소.
#### Validation Plan
- pytest로 AC-1~10 각 케이스 확인. AC-6(타 사용자 소유 스코프 음성), AC-9(admin 소유 스코프 음성), AC-10(service-token 음성)은 음성 테스트로 강제 검증.
### CHG-20260628-02
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req: `Proposed` → `Approved`. dcy-011 FR-1/2/3 추적. 소유 스코프 음성(AC-6/9/10)·메타데이터 secret 부재(AC-1/4)·CLI·API·service-token 정합(AC-3) 적절. **AC-9(admin도 타인 인증정보 write→403)** = 사장님 "self-service 전용·admin 감독 없음" 결정과 정합 확인(admin은 데몬 CRUD만, 인증정보는 본인 소유 스코프). 경로(OQ-1) design 위임. blocker 없음.
@@ -0,0 +1,104 @@
# REQ-053: admin 데몬 CRUD HTTP API
- Status: Approved
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- 현재 `Daemon` 엔티티는 `GET /v1/daemons`(read 전용, REQ-045)만 HTTP로 노출되며, 생성·변경·(비)활성화 HTTP API가 없다. 관리자(admin)가 HTTP API로 데몬을 provisioning하고 owner를 할당·변경·(비)활성화할 수 있어야 한다.
- trex-web dcy-009 admin UI가 소비하는 백엔드 경로를 신설한다.
- 데몬은 운영 런타임 식별자(service-token daemon_id 바인딩, KIS 계좌 연결)로 하드 삭제는 제공하지 않으며 **비활성화로 대체**한다(dcy-011 FR-4 결정). owner 재할당은 허용하되 **owner 변경 시 해당 데몬의 기존 인증정보를 자동 무효화**한다(이전 owner의 KIS 계좌 이관 금지, dcy-011 OQ-3 결정).
## Requirement
- 시스템은 admin role로 인증된 요청자가 데몬을 **생성**하고 `owner_user_id`를 할당할 수 있는 POST 엔드포인트를 제공해야 한다.
- 입력: `daemon_slug`(고유 식별자), `display_name`(선택), `owner_user_id`.
- 성공 시 생성된 Daemon의 `id`, `daemon_slug`, `display_name`, `owner_user_id`, `is_active` 반환.
- 시스템은 admin이 기존 데몬의 `display_name` 또는 `owner_user_id`를 **변경**할 수 있는 PATCH 엔드포인트를 제공해야 한다.
- **owner 변경 시 해당 데몬의 기존 `BrokerCredential` 전체를 자동 삭제(무효화)** 해야 한다 — 이전 owner의 KIS 계좌 정보가 새 owner에게 이관되는 것을 방지한다.
- 시스템은 admin이 데몬을 **비활성화**(`is_active=false`) 및 **재활성화**(`is_active=true`)할 수 있는 엔드포인트를 제공해야 한다.
- 비활성화 시 해당 데몬에 바인딩된 service token을 자동 revoke해야 한다(기존 REQ-022 정책 재사용).
- **하드 삭제(DELETE) 엔드포인트는 제공하지 않는다.** 비활성화가 유일한 종료 경로이다.
- admin role이 아닌 요청(user-token, service-token 포함)은 HTTP 403 Forbidden을 반환해야 한다.
- 엔드포인트 경로·HTTP 메서드의 구체 형태는 draft-batch-design에서 v1 admin 컨벤션(dcy-011 OQ-2 Deferred, dcy-006 admin 경로 참고)에 맞춰 확정한다.
## Acceptance Criteria
1. Given admin 토큰으로 daemon_slug, display_name, owner_user_id를 포함한 생성 요청, When POST 엔드포인트를 호출, Then HTTP 201 + 생성된 Daemon(`id`, `daemon_slug`, `display_name`, `owner_user_id`, `is_active=true`) 반환.
2. Given admin 토큰으로 중복 `daemon_slug`로 생성 요청, When POST 엔드포인트를 호출, Then HTTP 409(또는 422) Conflict 반환(중복 슬러그 거부).
3. Given admin 토큰으로 존재하는 daemon D의 `display_name` 변경 요청(owner 변경 없음), When PATCH 엔드포인트를 호출, Then HTTP 200 + 갱신된 Daemon 반환. 기존 BrokerCredential은 유지된다.
4. Given admin 토큰으로 daemon D의 `owner_user_id`를 사용자 B로 재할당 요청(원래 소유자 A), When PATCH 엔드포인트를 호출, Then HTTP 200 + 갱신된 Daemon 반환 + 해당 daemon D의 BrokerCredential이 모두 삭제(무효화)된다.
5. Given daemon D의 owner가 변경된 직후, When 변경 전 소유자 A의 service-token으로 `/v1/user_info`를 호출, Then `broker: null` 반환(이전 owner 인증정보 이관 차단).
6. Given admin 토큰으로 활성 daemon D 비활성화 요청, When 비활성화 엔드포인트를 호출, Then HTTP 200 + `is_active: false` 반환 + daemon D에 바인딩된 service token 자동 revoke(REQ-022 정책).
7. Given admin 토큰으로 비활성 daemon D 재활성화 요청, When 재활성화 엔드포인트를 호출, Then HTTP 200 + `is_active: true` 반환.
8. Given admin 토큰으로 daemon D에 DELETE 요청(하드 삭제), When 호출, Then HTTP 405 Method Not Allowed(또는 404) 반환 — 하드 삭제 엔드포인트 미제공.
9. Given user-token(admin role 없음)으로 데몬 생성·변경·비활성화 엔드포인트 호출, When 호출, Then HTTP 403 Forbidden 반환.
10. Given service-token으로 데몬 CRUD 엔드포인트 호출, When 호출, Then HTTP 403 Forbidden 반환.
11. Given 인증 토큰 없는 요청으로 데몬 CRUD 엔드포인트 호출, When 호출, Then HTTP 401 반환.
12. Given admin 토큰으로 존재하지 않는 daemon_id에 PATCH/비활성화 요청, When 호출, Then HTTP 404 반환.
## Impacted Area
- Docs:
- `docs/interface-contract.md` — admin 데몬 CRUD 엔드포인트 경로·권한·owner 재할당 시 인증정보 무효화 정책 신규 섹션 추가.
- Modules:
- 신규 또는 확장 admin 데몬 라우터(경로·파일명은 design 확정).
- `app/schemas/` — `DaemonCreateRequest`, `DaemonUpdateRequest`, `DaemonAdminResponse` 스키마 신규 정의.
- `app/services/` — 데몬 생성·변경·비활성화 서비스 메서드 신규. owner 재할당 시 `BrokerCredential` 무효화 로직 포함.
- `app/repositories/` — `DaemonRepository` create/update/deactivate/reactivate 메서드 신규.
- `app/repositories/broker_credential_repository.py` — owner 재할당 시 credential 삭제 메서드(신규 또는 확장).
- REQ-022(`daemon deactivation service token auto-revoke`) 서비스 로직 재사용 — 비활성화 경로에서 호출.
- Tests:
- AC-1~12 커버 pytest 케이스. AC-4/5(owner 재할당 + 인증정보 무효화), AC-8(DELETE 405 음성), AC-9/10/11(권한 음성) 강제 검증.
## Notes
- OQ-2(엔드포인트 경로): draft-batch-design 위임(dcy-011 Deferred). `/v1/admin/daemons` vs `/v1/daemons`(admin 분기) — dcy-006 admin 경로 컨벤션 참고.
- **owner 재할당 + 인증정보 자동 무효화(AC-4/5)**: dcy-011 OQ-3 Answered(2026-06-28 결정). 새 owner는 자기 KIS 인증정보를 REQ-052 API로 재등록해야 한다. 이 정책은 이전 owner의 KIS 계좌 정보가 데몬에 잔류하는 보안 위험을 제거한다.
- **하드 삭제 미제공(AC-8)**: 데몬은 운영 런타임 식별자(service-token daemon_id 바인딩)이므로 참조 무결성 보호를 위해 비활성화가 유일 종료 경로. dcy-011 FR-4 결정.
- **비활성화 → service token revoke(AC-6)**: 기존 REQ-022 정책(`daemon deactivation auto-revoke`)을 이 경로에서도 호출. REQ-022 구현 재사용.
- NFR-2(권한 — admin role 강제): AC-9/10/11로 흡수한다.
- daemon_slug 중복 처리(AC-2): HTTP 409 vs 422는 design 단계에서 v1 컨벤션에 맞춰 확정.
- Source: dcy-011 FR-4, NFR-2, OQ-3 Answered.
## Change Log
### CHG-20260628-01
- Date: 2026-06-28
- Author: Justin Ko
#### Change Summary
- 신규 생성 (Proposed) — dcy-011 FR-4 산출물.
#### Intent
- dcy-011 confirmed Discovery의 FR-4(admin 데몬 생성·owner 할당·변경·(비)활성화, 삭제 제외) 및 OQ-3 결정(owner 재할당 허용 + 인증정보 자동 무효화)을 Interface REQ로 정규화한다.
#### Acceptance Criteria Delta
- Added:
- AC-1 ~ AC-12 전체 (신규 초안)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs:
- `docs/srs/Interface/req-053_admin-daemon-crud-api.md` 신규 생성
- `docs/interface-contract.md` 갱신 예고
- Modules:
- admin 데몬 라우터(신규), DaemonCreate/UpdateRequest/AdminResponse 스키마(신규), 서비스·repository 신규
- Tests:
- owner 재할당+인증정보 무효화(AC-4/5), DELETE 음성(AC-8), 권한 음성(AC-9~11) 케이스 신규
#### 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 후 suggest-batch-reqs. REQ-052/REQ-054와 동일 batch 묶기 권고. OQ-2(경로)은 design 단계 해소.
#### Validation Plan
- pytest로 AC-1~12 각 케이스 확인. AC-4/5(owner 재할당 + 인증정보 무효화) 통합 시나리오, AC-8(DELETE 음성), AC-9(admin role 강제 음성) 강제 검증.
### CHG-20260628-02
- Date: 2026-06-28
- Author: 팀장(Claude)
- confirm-req: `Proposed` → `Approved`. dcy-011 FR-4/OQ-3 추적. 데몬 생성·할당·(비)활성화, **owner 재할당 시 인증정보 자동 무효화(AC-4/5)** 보안정합, **하드 삭제 미제공(AC-8)**·비활성화→service token revoke(REQ-022 재사용, AC-6)·admin role 강제(AC-9/10/11) 적절. 경로(OQ-2)·409/422·405/404 design 위임. blocker 없음.