chore: restructure SRS for asset catalog and tag model redesign (dcy-001/002, REQ-001~008)

Architecture Redesign (May 19, 2026):
- Separate admin-managed asset catalog from user portfolios
- Eliminate portfolio as asset definition; clarify portfolio as user position
- Introduce new asset_catalog + portfolio structure

Discovery Documents:
- dcy-001: asset-catalog-and-user-portfolio-model — admin asset management and user portfolio separation
- dcy-002: tag-ownership-and-applicability-policy — tag model implementation strategy

New Requirements (REQ-001~008):
- REQ-001: tags master table introduction (Data, Proposed)
- REQ-002: assets.tag_ids UUID[] column addition (Data, Proposed)
- REQ-003: portfolios.tag_ids and portfolio_tags removal (Data, Proposed)
- REQ-004: tags master HTTP API (Interface, Proposed)
- REQ-005: tag apply/remove HTTP API (Interface, Proposed)
- REQ-006: portfolio_tags data migration (Migration, Proposed)
- REQ-007: pfhub tag CLI commands (Interface, Proposed)
- REQ-008: tag model baseline docs update (Documentation, Proposed)

Cleanup:
- Remove archived SRS documents (REQ-001~029 old structure)
- Remove archived batch documentation (BAT-001~010)
- Remove archived discovery documents (dcy-001~006 old structure)
- Remove archived release documentation (REL-001~006)

Data Model Updates:
- Add Deployment category to SRS structure
- Add Data category for new data model requirements
- Update app/cli/core/utils.py for new tag handling

Documentation:
- Create new SRS index with consolidated requirement structure
- Update docs/discovery/index.md with new discovery references
- Migrate from requirement-centric to domain-model-centric organization

Rationale:
- Existing single-portfolio model lacks admin catalog governance
- New asset catalog enables central management of tradeable symbols
- Tag system supports user-driven asset organization and discovery
- Cleaner separation of concerns between admin operations and user trading
This commit is contained in:
2026-05-19 13:54:25 +00:00
parent 47c4f9bfde
commit 1082f041a1
96 changed files with 1133 additions and 9303 deletions
@@ -1,124 +0,0 @@
# req-004: GET /health 응답 스키마 확장
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- trex-basic Consumer-Driven Contract는 GET /health가 status, service, version, checked_at을 반환할 것을 요구한다.
- 현재 구현은 `{"status": "ok"}`만 반환하므로 계약 스키마를 충족하도록 응답을 확장한다.
## Requirement
- GET /health 엔드포인트는 status, service(고정값 "portfolio-hub"), version(앱 버전), checked_at(ISO 8601 KST) 필드를 포함한 JSON을 반환해야 한다.
- status 허용값은 ok, degraded, failed이며 정상 응답은 ok를 반환해야 한다.
## Acceptance Criteria
1. Given 서비스가 정상 동작 중일 때, When GET /health를 요청하면, Then 응답 JSON에 status, service, version, checked_at 필드가 모두 존재해야 한다.
2. Given GET /health 응답을 받을 때, When status 값을 확인하면, Then "ok", "degraded", "failed" 중 하나여야 한다.
3. Given GET /health 응답을 받을 때, When checked_at 형식을 검증하면, Then ISO 8601 형식을 만족해야 한다.
4. Given GET /health 응답을 받을 때, When service 필드를 확인하면, Then 고정값 "portfolio-hub"여야 한다.
5. Given 인증 없이 GET /health를 요청해도, Then 200 응답이 반환되어야 한다(비인증 엔드포인트).
## Impacted Area
- Docs: docs/interface-contract.md (REQ-011에서 갱신 예정), docs/runtime-flows.md
- Modules: app/api/health.py, app/schemas/ (HealthResponse 스키마 추가)
- Tests: tests/test_health.py (신규) 또는 tests/test_app.py 확장
## Notes
- degraded 상태를 반환하는 트리거 조건은 이번 반복에서 정의하지 않는다(OQ-2 Answered). 스키마에 허용값으로만 포함한다.
- 앱 버전은 pyproject.toml의 version 필드 또는 settings에서 읽어 반환한다. 설계 단계에서 구체적 방법을 확정한다.
- Source Discovery: dcy-002 (FR-1)
## Change Log
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-001 re-verified verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-001 verification.md (re-verified, 2026-05-03) AC-1~5 모두 PASS. Blocking Issues 없음. AC2(degraded/failed 경로)는 REQ Notes에서 이번 반복 OOS로 명시 — 릴리스 블로커 아님.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-001 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-001 기반 release 편성 검토)
#### Validation Plan
- BAT-001 verification.md (re-verified) AC-1~5 evidence 연결 확인 완료
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-004 confirm-req 승인 전환 (Status: Proposed → Approved)
#### Intent
- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 5개, Impacted Area, Change Log) 모두 충족 확인 후 승인
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs → draft-batch
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-004 최초 생성 (source Discovery: dcy-002 FR-1)
#### Intent
- trex-basic Consumer-Driven Contract의 GET /health 응답 스키마 요구사항을 REQ backlog로 정규화
#### Acceptance Criteria Delta
- Added:
- 전체 Acceptance Criteria 신규 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md (REQ-011 에서 갱신 예정)
- Modules:
- app/api/health.py
- Tests:
- tests/test_health.py 신규
#### Delivery Trace Delta
- Batches:
- none (batch 미생성 상태)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 배정
@@ -0,0 +1,83 @@
# REQ-004: tags 마스터 HTTP API
- Status: Proposed
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- tags 마스터 테이블에 대한 HTTP API를 제공하여 태그 생성, 목록 조회, 비활성화 기능을 권한 기반으로 제어한다.
- 태그 이름은 저장 전 소문자로 정규화하여 대소문자 변형 중복을 방지한다.
- admin 토큰으로 생성된 태그는 is_admin_tag=TRUE로 기록되며, 이는 asset 태그 적용 정책의 핵심 조건이 된다.
## Requirement
- `POST /v1/tags` — 태그 생성; trade-write 이상 권한 필요; name을 str.lower()로 정규화 후 저장; 중복 name(정규화 후) 시 HTTP 409 반환
- `GET /v1/tags` — 태그 목록 조회; read 이상 권한 필요; `?is_active=true|false` 필터 지원
- `PATCH /v1/tags/{tag_id}/deactivate` — 태그 비활성화; admin 또는 태그 생성자 본인만 가능; 이미 비활성화된 태그에 멱등 처리(재요청 시 200 반환); 존재하지 않는 tag_id는 HTTP 404
## Acceptance Criteria
1. Given trade-write 토큰, When `POST /v1/tags {"name": "TestTag"}`, Then HTTP 201, name="testtag"(소문자 정규화)로 저장된다.
2. Given admin 토큰, When `POST /v1/tags {"name": "adminTag"}`, Then HTTP 201, is_admin_tag=TRUE인 태그가 생성된다.
3. Given non-admin trade-write 토큰, When `POST /v1/tags {"name": "userTag"}`, Then HTTP 201, is_admin_tag=FALSE인 태그가 생성된다.
4. Given "testtag" 태그가 이미 존재, When `POST /v1/tags {"name": "TestTag"}` (대소문자 변형), Then HTTP 409 Conflict가 반환된다.
5. Given read 토큰, When `GET /v1/tags`, Then HTTP 200과 활성 태그 목록이 반환된다.
6. Given read 토큰, When `GET /v1/tags?is_active=false`, Then 비활성 태그만 반환된다.
7. Given admin 토큰, When `PATCH /v1/tags/{tag_id}/deactivate` (본인 생성 또는 타인 생성 무관), Then HTTP 200, is_active=FALSE로 설정된다.
8. Given 태그 생성자(non-admin) 토큰, When `PATCH /v1/tags/{tag_id}/deactivate` (본인 생성 태그), Then HTTP 200 반환된다.
9. Given non-admin 비생성자 토큰, When `PATCH /v1/tags/{tag_id}/deactivate`, Then HTTP 403 Forbidden이 반환된다.
10. Given 존재하지 않는 tag_id, When `PATCH /v1/tags/{tag_id}/deactivate`, Then HTTP 404가 반환된다.
11. Given 이미 is_active=FALSE인 태그, When `PATCH /v1/tags/{tag_id}/deactivate` 재요청, Then HTTP 200 반환(멱등).
## Impacted Area
- Docs: docs/interface-contract.md (/v1/tags 엔드포인트 명세 추가)
- Modules:
- app/api/ — v1_tags.py 라우터 추가 (또는 기존 라우터에 통합)
- app/schemas/v1.py — TagItem, TagCreateRequest, TagListResponse 스키마 추가
- app/services/ — 태그 생성/조회/비활성화 서비스 로직
- app/repositories/portfolio_repository.py — insert_tag, get_tags, deactivate_tag 프로토콜
- app/repositories/postgres.py — 태그 CRUD SQL 구현
- app/core/auth.py — permission 검증 (기존 require_read, require_trade_write, require_admin 재사용)
- Tests: 엔드포인트별 permission 검증, 소문자 정규화, 중복 거부, 비활성화 멱등성
## Notes
- NFR-1 흡수: name 정규화는 서비스/라우터 레이어에서 str.lower() 호출, DB는 TEXT UNIQUE 그대로.
- NFR-3 흡수: 권한 미충족 시 HTTP 403은 기존 permission_dependency 패턴 재사용.
- `POST /v1/tags` 권한: trade-write 이상이면 일반 사용자도 태그 생성 가능하며 is_admin_tag는 admin 여부에 따라 자동 결정.
- `GET /v1/tags` 기본 필터: is_active=true (활성 태그만). ?is_active=false로 비활성 태그 조회 가능.
- Source: dcy-002 FR-4(CRUD 부분), NFR-1, NFR-3
## Change Log
### CHG-20260519-01
- Date: 2026-05-19
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성
#### Intent
- dcy-002 FR-4의 tags 마스터 CRUD API를 Interface REQ로 분리
#### Acceptance Criteria Delta
- Added:
- AC-1~11 최초 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md — /v1/tags 명세 추가
- Modules:
- app/api/, app/schemas/v1.py, app/services/, app/repositories/ — 최초 지정
- Tests:
- 최초 지정
#### Delivery Trace Delta
- Batches:
- none (미정)
- Releases:
- none (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; REQ-001 선행 의존성 확인
@@ -1,215 +0,0 @@
# req-005: service-token 발급 시 runtime_id 동시 생성
- Status: Approved
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- trex-basic 계열 consumer는 `runtime_id`를 런타임 세대 식별자로 사용하되, asset assignment와 운영 provenance는 stable `daemon_id`를 기준으로 추적해야 한다.
- service-token 발급 시 runtime_id를 새로 생성하면서도, 토큰은 기존 daemon registry에 존재하는 `daemon_id`에 바인딩되어 저장되어야 한다.
- CLI(`pfhub service-token issue`)와 API 엔드포인트 양쪽에서 발급 결과에 `runtime_id`와 `daemon_id`가 함께 포함되어야 한다.
## Requirement
- pfhub service-token issue CLI 커맨드와 대응 API 엔드포인트(`POST /auth/service-tokens`)는 service-token 발급 시 고유 `runtime_id`를 생성하고, 기존 daemon registry에 존재하는 `daemon_id`에 토큰을 바인딩해 auth_tokens에 함께 저장한 뒤 발급 결과(`token`, `runtime_id`, `daemon_id`)를 반환해야 한다.
- 발급 경로는 unknown daemon 자동 생성 또는 upsert를 허용하면 안 되며, binding 대상은 사전에 생성된 daemon registry entry여야 한다.
- `runtime_id`는 발급마다 새로 생성되는 런타임 식별자이고, `daemon_id`는 rotate/재기동과 무관하게 유지되는 stable logical identity다.
## Acceptance Criteria
1. Given `pfhub service-token issue <name> --user <username> --daemon-id <daemon_id>` 실행 시, When 발급이 성공하면, Then 출력에 token 문자열, `runtime_id`, `daemon_id`가 모두 포함되어야 한다.
2. Given API 엔드포인트(`POST /auth/service-tokens`)로 existing `daemon_id`를 지정해 service-token 발급 시, When 성공 응답을 받으면, Then 응답 JSON에 token, `runtime_id`, `daemon_id`가 포함되어야 한다.
3. Given `runtime_id`와 `daemon_id`가 발급된 service-token으로 `GET /v1/user_info`를 호출하면, Then 응답의 `runtime_id`, `daemon_id`가 발급 시 저장된 값과 동일해야 한다.
4. Given 동일 daemon에 대해 두 번의 service-token 발급을 수행하면, Then 각각 다른 `runtime_id`가 반환되어야 하고 `daemon_id`는 요청한 binding 값을 유지해야 한다.
5. Given auth_tokens 테이블을 직접 조회하면, Then 발급된 service-token 행에 `runtime_id`와 `daemon_id` 값이 모두 저장되어 있어야 한다.
6. Given 존재하지 않는 `daemon_id`로 service-token 발급을 요청하면, Then 명시적 validation 오류가 반환되어야 하고 auth_tokens 행은 생성되지 않아야 한다.
## Impacted Area
- Docs: docs/runtime-flows.md, docs/interface-contract.md (REQ-011에서 갱신 예정), README.md token provenance 절차 (REQ-021에서 갱신 예정)
- Modules: app/cli/commands/service_tokens.py, app/schemas/auth.py, app/api/auth_routes.py, app/domain/models.py, app/services/auth_service.py, app/repositories/portfolio_repository.py, app/repositories/postgres.py
- Tests: tests/test_auth.py, tests/test_cli.py, tests/test_v1.py
## Notes
- runtime_id 포맷은 UUID v4를 권장한다. 설계 단계에서 확정한다.
- auth_tokens 테이블의 `runtime_id`, `daemon_id` 컬럼 및 daemon registry 스키마는 REQ-009 migration에서 처리한다.
- daemon provisioning(create/update)은 별도 관리 경로에서 수행하며, issue 경로는 binding만 담당한다.
- 기존 daemon-less service-token은 transition 구간에서 `daemon_id = NULL`로 남을 수 있고, daemon-scoped API에서는 fail-closed로 처리한다.
- Source Discovery: dcy-002 (FR-2, A-2), dcy-006 (FR-1, FR-5, NFR-1)
## Change Log
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- runtime_id-only service-token 발급 요구사항을 existing daemon binding + `daemon_id` diagnostics 포함 구조로 확장하고, 기존 Implemented 상태를 Approved로 되돌림
#### Intent
- dcy-006에서 `runtime_id`는 runtime identity, `daemon_id`는 stable logical identity로 역할이 분리됐다. 현재 BAT-001/REL-001 구현은 runtime_id만 저장·노출하므로 revised issuance contract와 daemon-scoped authorization 전제를 충족하지 못한다.
#### Acceptance Criteria Delta
- Added:
- AC-6: unknown `daemon_id` 발급 요청 거부
- Updated:
- AC-1~5: CLI/API issue, `/v1/user_info`, DB persistence 기준을 `daemon_id` binding 포함으로 재정의
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/runtime-flows.md`, `docs/interface-contract.md` — issue flow와 response contract에 `daemon_id` 추가 필요
- `README.md` — daemon-aware provenance 절차 반영 필요
- Modules:
- `app/cli/commands/service_tokens.py`, `app/schemas/auth.py`, `app/api/auth_routes.py`, `app/services/auth_service.py`, `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py`
- Tests:
- `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_v1.py`
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate; REL-001 released input) — runtime_id-only issuance evidence는 daemon binding 요구사항 기준으로 재설계 및 재검증 필요
- Releases:
- REL-001 (released) — service-token issuance release evidence가 revised REQ를 충족하지 않으므로 후속 app-service release가 필요
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: rollback-to-approved
- Follow-up Action: dcy-006 후속 batch에서 daemon registry binding, issue API/CLI contract, `/v1/user_info` diagnostics를 함께 구현하고 재검증한다.
#### Validation Plan
- dcy-006 FR-1/FR-5와 `app/services/auth_service.py`, `app/api/auth_routes.py`, `app/cli/commands/service_tokens.py`의 현재 구현 차이를 코드 리뷰로 대조한다.
- 구현 후 `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_v1.py`에 daemon-bound issuance/user_info 케이스를 추가해 targeted validation을 수행한다.
### CHG-20260511-02
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- REQ-005 AC-2의 API 경로를 실제 구현 경로인 `POST /auth/service-tokens`로 명확화
#### Intent
- CONFLICT-4 분석 결과를 반영해 "POST /auth/tokens 또는 동등 경로"라는 모호한 표현을 제거하고, BAT-001 verification과 runtime flow에 이미 반영된 실제 엔드포인트로 REQ를 정렬한다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- AC-2: API 발급 경로를 `POST /auth/service-tokens`로 구체화
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/srs/Interface/req-005_service-token-runtime-id-issuance.md`: AC-2 경로 문구 명확화
- Modules:
- none
- Tests:
- none
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate) — verification evidence는 이미 `POST /auth/service-tokens` 기준으로 정렬되어 있어 추가 재검증은 불필요
- Releases:
- REL-001 (draft) — release scope 문서도 동일 경로를 사용 중이라 추가 수정 없음
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: none
#### Validation Plan
- REQ-005 AC-2, BAT-001 verification evidence, runtime-flows.md의 API 경로가 모두 `POST /auth/service-tokens`로 일치하는지 코드 리뷰로 확인
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-001 re-verified verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-001 verification.md (re-verified, 2026-05-03) AC-1~5 모두 PASS. Blocking Issues 없음. AC5 DB 미확인은 REQ-009와 동일 제약으로 non-blocker 처리.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-001 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-001)
#### Validation Plan
- BAT-001 verification.md (re-verified) AC-1~5 evidence 연결 확인 완료
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-005 confirm-req 승인 전환 (Status: Proposed → Approved)
#### Intent
- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 5개, Impacted Area, Change Log) 모두 충족 확인 후 승인
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs → draft-batch
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-005 최초 생성 (source Discovery: dcy-002 FR-2)
#### Intent
- service-token 발급 시 runtime_id 동시 생성 요구사항을 REQ backlog로 정규화
#### Acceptance Criteria Delta
- Added:
- 전체 Acceptance Criteria 신규 작성
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md (REQ-011에서 갱신 예정)
- Modules:
- app/cli/commands/service_tokens.py
- app/api/auth_routes.py
- app/domain/models.py
- Tests:
- tests/test_auth.py 확장 또는 tests/test_service_tokens.py 신규
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 배정. REQ-009 migration과 함께 delivery 단위 구성 권장.
@@ -0,0 +1,83 @@
# REQ-005: 태그 적용·해제 HTTP API
- Status: Proposed
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- asset(portfolios)과 portfolio에 태그를 적용하고 해제하는 HTTP API를 제공하며, asset에는 admin 생성 태그만, portfolio에는 admin 또는 소유자 생성 태그만 적용 가능하도록 정책을 적용한다.
- 정책 위반 시 HTTP 403을 반환하여 명확한 오류 피드백을 제공한다.
- 존재하지 않는 tag_id 적용 시도는 HTTP 404로 처리한다.
## Requirement
- `POST /v1/assets/{asset_id}/tags {"tag_id": "..."}` — asset에 태그 적용; admin 권한 필요; 적용 조건: tags.is_admin_tag=TRUE AND tags.is_active=TRUE; 조건 불만족 시 HTTP 403; 존재하지 않는 tag_id 시 HTTP 404
- `DELETE /v1/assets/{asset_id}/tags/{tag_id}` — asset에서 태그 해제; admin 권한 필요
- `POST /v1/portfolios/{portfolio_id}/tags {"tag_id": "..."}` — portfolio에 태그 적용; trade-write 이상 권한 필요; 적용 조건: tags.is_active=TRUE AND (tags.is_admin_tag=TRUE OR tags.created_by_user_id = portfolio 소유자); 조건 불만족 시 HTTP 403; 존재하지 않는 tag_id 시 HTTP 404
- `DELETE /v1/portfolios/{portfolio_id}/tags/{tag_id}` — portfolio에서 태그 해제; trade-write 이상, 소유자 본인 또는 admin
## Acceptance Criteria
1. Given admin 토큰, admin이 생성한 활성 tag_id, When `POST /v1/assets/{asset_id}/tags`, Then HTTP 200, 해당 tag_id가 asset의 tag_ids 배열에 추가된다.
2. Given admin 토큰, is_admin_tag=FALSE인 tag_id, When `POST /v1/assets/{asset_id}/tags`, Then HTTP 403 Forbidden이 반환된다.
3. Given admin 토큰, is_active=FALSE인 tag_id, When `POST /v1/assets/{asset_id}/tags`, Then HTTP 403 Forbidden이 반환된다.
4. Given non-admin 토큰, When `POST /v1/assets/{asset_id}/tags`, Then HTTP 403 Forbidden이 반환된다.
5. Given admin 토큰, When `DELETE /v1/assets/{asset_id}/tags/{tag_id}`, Then HTTP 200, tag_id가 asset의 tag_ids 배열에서 제거된다.
6. Given trade-write 토큰(portfolio 소유자), 소유자 본인이 생성한 tag_id (is_admin_tag=FALSE, is_active=TRUE), When `POST /v1/portfolios/{portfolio_id}/tags`, Then HTTP 200, 태그가 portfolio의 tag_ids에 추가된다.
7. Given trade-write 토큰(portfolio 소유자), admin이 생성한 활성 tag_id, When `POST /v1/portfolios/{portfolio_id}/tags`, Then HTTP 200, 태그가 추가된다.
8. Given trade-write 토큰(user A, 소유자), 다른 user B가 생성한 non-admin tag_id, When `POST /v1/portfolios/{portfolio_id}/tags`, Then HTTP 403 Forbidden이 반환된다.
9. Given 소유자 또는 admin 토큰, When `DELETE /v1/portfolios/{portfolio_id}/tags/{tag_id}`, Then HTTP 200, tag_id가 portfolio의 tag_ids에서 제거된다.
10. Given 유효한 권한, 존재하지 않는 tag_id, When 적용 요청, Then HTTP 404 Not Found가 반환된다.
11. Given 존재하지 않는 asset_id 또는 portfolio_id, When 태그 적용 요청, Then HTTP 404가 반환된다.
## Impacted Area
- Docs: docs/interface-contract.md (/v1/assets/{id}/tags, /v1/portfolios/{id}/tags 엔드포인트 명세 추가)
- Modules:
- app/api/v1_assets.py — POST/DELETE /v1/assets/{asset_id}/tags 라우트 추가
- app/api/portfolios.py — POST/DELETE /v1/portfolios/{portfolio_id}/tags 라우트 추가 (또는 별도 파일)
- app/schemas/v1.py — TagApplyRequest 스키마 추가
- app/services/ — 태그 적용/해제 서비스 로직, 정책 검증
- app/repositories/postgres.py — tag_ids 배열 append/remove SQL (array_append, array_remove 또는 동등)
- Tests: 정책 위반 시 403, 존재하지 않는 tag_id 시 404, admin/non-admin 구분 검증
## Notes
- NFR-3 흡수: 정책 위반 시 HTTP 403은 이 REQ의 핵심 동작.
- REQ-001(tags 테이블), REQ-002(assets.tag_ids), REQ-003(portfolios.tag_ids)이 선행되어야 한다.
- asset에 태그 적용 권한이 admin 전용인 이유: assets는 admin 관리 카탈로그이며 asset 태그에는 is_admin_tag=TRUE만 허용 (dcy-002 FR-2).
- portfolio 소유자 확인: portfolios.owner_user_id = auth_context.user_id 비교.
- Source: dcy-002 FR-2, FR-3, FR-4(apply/remove 부분), NFR-3
## Change Log
### CHG-20260519-01
- Date: 2026-05-19
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성
#### Intent
- dcy-002 FR-4의 태그 적용·해제 엔드포인트와 FR-2/FR-3의 정책 검증을 Interface REQ로 분리
#### Acceptance Criteria Delta
- Added:
- AC-1~11 최초 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md — apply/remove 엔드포인트 추가
- Modules:
- app/api/v1_assets.py, app/api/portfolios.py, app/schemas/v1.py, app/services/, app/repositories/postgres.py — 최초 지정
- Tests:
- 최초 지정
#### Delivery Trace Delta
- Batches:
- none (미정)
- Releases:
- none (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; REQ-001, REQ-002, REQ-003 선행 의존성 확인
@@ -1,260 +0,0 @@
# req-006: GET /v1/assets 엔드포인트 구현
- Status: Approved
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- trex-basic 데몬이 자신에게 배정된 자산 목록과 전략 파라미터만 조회할 수 있도록 portfolio 데이터를 daemon-scoped asset 뷰로 제공한다.
- 동일 owner user 아래 여러 daemon이 존재해도, 다른 daemon에 배정된 asset은 응답에 섞이면 안 된다.
## Requirement
- GET /v1/assets는 service-token bearer 인증으로 토큰 소유 user 범위 안에서, 인증된 token의 `daemon_id`에 배정된 portfolio 목록만 `asset_id`, `symbol`, `market`, `status`, `cash`, `quantity`, `broker_type`, `strategy_id`, `strategy_type`, `strategy_params`, `updated_at` 필드로 반환해야 한다. 다른 사용자 소유 portfolio와 다른 daemon에 배정된 portfolio는 응답에 포함되면 안 된다.
- `status=active` 쿼리 파라미터는 daemon-scoped 결과 집합 위에서 필터링되어야 한다.
- strategy_params는 해당 portfolio의 active_strategy_id로 조회한 StrategySet.config에서 파생한다. strategy_id는 허용값 검증 없이 그대로 반환한다(OQ-1 Answered).
- strategy_type은 해당 portfolio의 active_strategy_id로 조회한 StrategySet.name을 반환한다. active_strategy_id가 없거나 StrategySet 조회 실패 시 null을 반환한다.
- `daemon_id`가 없는 legacy service-token은 transition 구간 동안 `GET /v1/assets`에서 fail-closed(`403 Forbidden`)로 처리되어야 한다.
## Acceptance Criteria
1. Given 유효한 daemon-bound service-token bearer 인증으로 `GET /v1/assets` 요청 시, When 토큰이 유효하면, Then HTTP 200과 해당 daemon에 배정된 portfolio 목록만 반환되어야 하며 다른 사용자 소유 portfolio나 다른 daemon에 배정된 portfolio는 포함되면 안 된다.
2. Given `GET /v1/assets?status=active` 요청 시, Then status가 active인 항목만 daemon-scoped 결과 안에서 반환되어야 한다.
3. Given 응답의 각 항목을 확인하면, Then asset_id, symbol, market, status, cash, quantity, broker_type, strategy_id, strategy_type, strategy_params, updated_at 필드가 모두 포함되어야 한다.
4. Given portfolio에 연결된 StrategySet이 있을 때, When strategy_params를 확인하면, Then StrategySet.config 값에서 파생된 JSON 객체여야 한다.
5. Given StrategySet config가 없거나 파싱 실패 시, Then strategy_params는 빈 객체({})를 반환하고 에러가 전파되지 않아야 한다.
6. Given portfolios.market이 NULL인 경우, Then 응답의 market 필드도 null로 반환되어야 한다(DECIDE_MARKET_DEFAULT Answered).
7. Given 인증 없이 GET /v1/assets 요청 시, Then HTTP 401이 반환되어야 한다.
8. Given portfolio에 active_strategy_id로 연결된 StrategySet이 있을 때, Then strategy_type은 해당 StrategySet.name 값을 반환해야 한다.
9. Given portfolio에 active_strategy_id가 없거나 StrategySet 조회 실패 시, Then strategy_type은 null을 반환해야 한다.
10. Given `daemon_id`가 없는 legacy service-token으로 `GET /v1/assets` 요청 시, Then HTTP 403이 반환되어야 하고 daemon-scoped asset 목록은 비노출이어야 한다.
## Impacted Area
- Docs: docs/interface-contract.md (REQ-011에서 갱신 예정), docs/project-structure.md (신규 라우터 파일 반영 필요)
- Modules: app/api/v1_assets.py, app/schemas/v1.py, app/repositories/portfolio_repository.py, app/repositories/postgres.py, app/core/auth.py, app/main.py
- Tests: tests/test_v1.py, tests/test_portfolio_ownership.py 또는 신규 daemon assignment 회귀 테스트
## Notes
- broker_type은 portfolios.broker 컬럼을 그대로 매핑한다(m-2 finding 반영).
- strategy_id는 portfolios.active_strategy_id를 그대로 반환한다.
- `GET /v1/assets`의 최종 authorization 스코프는 service-token 소유자 범위 위에 `managed_by_daemon_id = auth.daemon_id` 조건을 추가한 daemon scope다.
- exact-one asset assignment는 `portfolios.managed_by_daemon_id`를 통해 표현되며 스키마 제약은 REQ-009 change-req에서 다룬다.
- Source Discovery: dcy-002 (FR-3, A-3, OQ-1), dcy-006 (FR-2, FR-3, NFR-2)
## Change Log
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- owner-user scope 기반 `/v1/assets` 요구사항을 daemon-scoped asset visibility와 fail-closed legacy token 규칙으로 갱신하고, Implemented 상태를 Approved로 되돌림
#### Intent
- dcy-006에서 각 asset은 정확히 1개 daemon에 배정되고, `GET /v1/assets`는 owner user 전체가 아니라 인증 daemon 범위만 반환해야 한다. 현재 구현은 `owner_user_id=auth.user_id`만 사용하므로 revised authorization model과 맞지 않는다.
#### Acceptance Criteria Delta
- Added:
- AC-10: daemon-less legacy token fail-closed(403)
- Updated:
- AC-1, AC-2: owner scope를 daemon scope 기준으로 재정의
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/interface-contract.md`, `docs/runtime-flows.md` — daemon-scoped asset list contract 반영 필요
- Modules:
- `app/api/v1_assets.py`, `app/schemas/v1.py`, `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py`, `app/core/auth.py`
- Tests:
- `tests/test_v1.py`, daemon assignment 관련 신규 회귀 테스트
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate; REL-001 released input) — owner-user scope verification evidence는 daemon-scoped visibility 기준으로 전면 재검증 필요
- Releases:
- REL-001 (released) — released contract는 owner-user scope 기준이라 revised REQ와 불일치
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: rollback-to-approved
- Follow-up Action: dcy-006 후속 batch에서 daemon assignment query, 403 fail-closed path, daemon-scoped regression test를 구현한다.
#### Validation Plan
- `app/api/v1_assets.py`가 현재 `owner_user_id=auth.user_id`만 적용하는지 확인하고, daemon-aware query 설계를 REQ-009와 함께 정리한다.
- 구현 후 daemon A/B 분리 시나리오와 daemon-less token 403 시나리오를 targeted pytest로 검증한다.
### CHG-20260511-02
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- owner scope 회귀 수정 반영 — GET /v1/assets는 service-token 소유자의 portfolio만 반환함을 REQ와 verification evidence에 명시
#### Intent
- CONFLICT-1 분석 결과와 2026-05-11 사용자 결정에 따라 trex-basic은 전체 사용자의 portfolio를 조회하면 안 되며, 서비스 토큰 소유자 범위로만 조회되어야 한다는 설계를 REQ 본문과 검증 기준에 명확히 남긴다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- AC1: 반환 범위를 "해당 사용자 소유 portfolio 목록만"으로 명확화하고, 다른 사용자 소유 portfolio 비포함 조건을 추가
- Removed:
- none
#### Impacted Area Delta
- Docs:
- docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md: REQ-006 AC1 evidence를 owner scope 기준으로 재정렬
- Modules:
- app/api/v1_assets.py: `owner_user_id=auth.user_id` 적용으로 owner scope 강제
- Tests:
- tests/test_v1.py: `test_v1_assets_returns_asset_list`에 `owner_user_id="user-1"` 전달 assert 추가
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate) — REQ-006 targeted re-verification 필요 및 evidence 갱신
- Releases:
- REL-001 (draft) — release 입력 전 owner scope 기준 반영 여부 재확인 필요
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: keep-current
- Follow-up Action: owner scope 수정 코드와 targeted pytest 재실행 결과를 BAT-001 verification에 반영하고 Implemented 상태 유지
#### Validation Plan
- `app/api/v1_assets.py`에서 `owner_user_id=auth.user_id` 적용 여부 확인
- `tests/test_v1.py -k 'v1_assets'` 재실행으로 GET /v1/assets 회귀 테스트 PASS 확인
- BAT-001 verification.md의 REQ-006 AC1 evidence를 최신 테스트와 맞춤
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-001 re-verified verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-001 verification.md (re-verified, 2026-05-03, CHG-20260503-01 strategy_type 재검증 포함) AC-1~9 모두 PASS. Blocking Issues 없음.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-001 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-001)
#### Validation Plan
- BAT-001 verification.md (re-verified) AC-1~9 evidence 연결 확인 완료 (strategy_type 필드 재검증 포함)
### CHG-20260503-01
- Date: 2026-05-03
- Author: repository-maintainers
#### Change Summary
- GET /v1/assets 응답 필드에 `strategy_type` 추가 — StrategySet.name 값을 반환
#### Intent
- trex-basic 데몬이 asset 뷰에서 전략 이름(StrategySet.name)을 직접 확인할 수 있도록 `strategy_type` 필드를 응답에 추가한다.
- DB 변경 없이 기존 StrategySet.name 컬럼을 활용하는 순수 응답 스키마 확장이다.
#### Acceptance Criteria Delta
- Added:
- AC-8: Given portfolio에 active_strategy_id로 연결된 StrategySet이 있을 때, Then strategy_type은 StrategySet.name 값을 반환해야 한다.
- AC-9: Given portfolio에 active_strategy_id가 없거나 StrategySet 조회 실패 시, Then strategy_type은 null을 반환해야 한다.
- Modified:
- AC-3: `strategy_type` 필드를 필수 응답 필드 목록에 추가
#### Impacted Area Delta
- Modules:
- app/schemas/v1.py: AssetItem에 strategy_type 필드 추가
- app/api/v1_assets.py: strategy_type 매핑 로직 추가 (StrategySet.name 조회)
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate) — REQ-006 포함, strategy_type 추가로 재검증 필요
- Releases:
- 없음 (아직 draft-release 미수행)
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes (BAT-001 verification은 strategy_type 없이 수행됨)
- Status Recommendation: keep-current (Approved 유지 — 구현 전 단계이므로 상태 되돌림 불필요)
- Follow-up Action: 구현 후 BAT-001 verification 재수행 또는 BAT-006 신규 생성 결정 필요
#### Validation Plan
- app/schemas/v1.py AssetItem에 strategy_type 필드 추가
- app/api/v1_assets.py에서 portfolio.active_strategy_id로 StrategySet.name 조회 후 매핑
- tests/test_v1_assets.py 또는 test_app.py에서 strategy_type 필드 존재 및 null 케이스 검증
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-006 confirm-req 승인 전환 (Status: Proposed → Approved)
#### Intent
- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 7개, Impacted Area, Change Log) 모두 충족 확인 후 승인
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs → draft-batch
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-006 최초 생성 (source Discovery: dcy-002 FR-3)
#### Intent
- trex-basic Consumer-Driven Contract의 GET /v1/assets 요구사항을 REQ backlog로 정규화
#### Acceptance Criteria Delta
- Added:
- 전체 Acceptance Criteria 신규 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md (REQ-011에서 갱신 예정)
- Modules:
- app/api/v1_assets.py 신규
- app/main.py (/v1 라우터 등록)
- Tests:
- tests/test_v1_assets.py 신규
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 배정. REQ-009 migration(market 컬럼 추가)과 함께 delivery 단위 구성 권장.
@@ -0,0 +1,82 @@
# REQ-007: pfhub tag CLI 명령어
- Status: Proposed
- Type: Interface
- Priority: Medium
- Owner: Justin Ko
## Intent
- tags 마스터 테이블 기반의 태그 관리 CLI 명령어를 `pfhub tag` 최상위 서브커맨드로 추가하여 운영자·사용자가 터미널에서 태그 생성, 조회, 비활성화, 적용, 해제를 수행할 수 있도록 한다.
- 기존 `pfhub portfolio tag add/list/remove` 명령어(portfolio_tags 자유 문자열 기반)는 이 REQ 구현과 함께 제거한다.
## Requirement
- `pfhub tag create <name>` — 태그 생성 (trade-write 이상 권한)
- `pfhub tag list [--inactive]` — 태그 목록; --inactive 없으면 활성 태그만, --inactive 플래그 시 비활성 태그 포함 전체 조회
- `pfhub tag deactivate <tag_id>` — 태그 비활성화; `--force` 확인 필요 (admin 또는 생성자 본인)
- `pfhub tag apply asset <asset_id> <tag_id>` — asset에 태그 적용 (admin 권한)
- `pfhub tag apply portfolio <portfolio_id> <tag_id>` — portfolio에 태그 적용 (trade-write 이상, 소유자 또는 admin)
- `pfhub tag remove asset <asset_id> <tag_id>` — asset에서 태그 해제; `--force` 확인 필요 (admin)
- `pfhub tag remove portfolio <portfolio_id> <tag_id>` — portfolio에서 태그 해제; `--force` 확인 필요 (소유자 또는 admin)
- 기존 `pfhub portfolio tag add`, `pfhub portfolio tag list`, `pfhub portfolio tag remove` 명령어를 제거해야 한다.
## Acceptance Criteria
1. Given trade-write 토큰, When `pfhub tag create "코스피200"`, Then 태그가 생성되고 `pfhub tag list`에 나타난다.
2. Given 활성 태그 존재, When `pfhub tag list`, Then 활성 태그 목록이 출력된다.
3. Given 활성·비활성 태그 모두 존재, When `pfhub tag list --inactive`, Then 비활성 태그가 포함된 전체 목록이 출력된다.
4. Given admin 또는 생성자 토큰, When `pfhub tag deactivate <tag_id> --force`, Then 태그가 비활성화되고 `pfhub tag list --inactive`에서 비활성 상태로 확인된다.
5. Given admin 토큰, admin 생성 활성 tag_id, When `pfhub tag apply asset <asset_id> <tag_id>`, Then asset에 태그가 적용된다.
6. Given trade-write 토큰(소유자 또는 admin), When `pfhub tag apply portfolio <portfolio_id> <tag_id>` (정책 충족 태그), Then portfolio에 태그가 적용된다.
7. Given admin 토큰, When `pfhub tag remove asset <asset_id> <tag_id> --force`, Then asset에서 태그가 제거된다.
8. Given 소유자 또는 admin 토큰, When `pfhub tag remove portfolio <portfolio_id> <tag_id> --force`, Then portfolio에서 태그가 제거된다.
9. Given 기존 CLI 실행 환경, When `pfhub portfolio tag add/list/remove`를 실행, Then 명령어가 존재하지 않는다(Typer error 또는 명령 없음).
## Impacted Area
- Docs: docs/project-structure.md (pfhub tag 커맨드 그룹 추가, pfhub portfolio tag 제거), docs/runtime-flows.md (tag CLI flow 추가)
- Modules:
- app/cli/commands/ — tag.py 신규 파일 (pfhub tag 커맨드 그룹)
- app/cli/main.py — app.add_typer(tag_app, name="tag") 추가
- app/cli/commands/portfolio.py — tag_app, tag_add, tag_list, tag_remove 제거
- app/services/ — 태그 CLI 서비스 메서드 (create, list, deactivate, apply, remove) 추가
- Tests: pfhub tag 커맨드 각 AC 기능 테스트, pfhub portfolio tag 제거 확인
## Notes
- runtime-flows.md의 CLI 흐름 제약 준수: AppContext 통해 settings와 repository 주입.
- `pfhub tag apply`/`remove`는 REST API(REQ-005)를 통해 정책 검증이 이루어지므로 CLI는 HTTP 오류를 그대로 사용자에게 표시.
- REQ-004, REQ-005가 선행 구현되어야 한다.
- Source: dcy-002 FR-6
## Change Log
### CHG-20260519-01
- Date: 2026-05-19
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성
#### Intent
- dcy-002 FR-6를 Interface REQ로 분리; 기존 pfhub portfolio tag 명령어 교체 명시
#### Acceptance Criteria Delta
- Added:
- AC-1~9 최초 작성
#### Impacted Area Delta
- Docs:
- docs/project-structure.md, docs/runtime-flows.md — 최초 지정
- Modules:
- app/cli/commands/tag.py (신규), app/cli/main.py, app/cli/commands/portfolio.py, app/services/ — 최초 지정
- Tests:
- 최초 지정
#### Delivery Trace Delta
- Batches:
- none (미정)
- Releases:
- none (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; REQ-004, REQ-005 선행 의존성 확인
@@ -1,177 +0,0 @@
# req-007: GET /v1/user_info 엔드포인트 구현
- Status: Approved
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- trex-basic 데몬이 사용자 거래 가능 여부와 브로커 인증 정보를 조회할 수 있도록 한다.
- `runtime_id`는 인증 service-token의 런타임 세대 식별자이고, `daemon_id`는 stable logical identity로 함께 반환되어야 한다.
- credential_payload는 DB에서 Fernet 복호화 후 반환한다(NFR-3/REQ-010 의존).
## Requirement
- GET /v1/user_info는 service-token bearer 인증으로 `runtime_id`, `daemon_id`, `trading_enabled`(users.is_active 매핑), `disabled_reason`, `brokers[]`(broker, account_ref, credential_payload, credential_version, last_rotated_at)를 반환해야 한다.
- environment 필드를 응답에 포함하지 않는다.
- credential_payload는 Fernet으로 복호화된 JSON을 반환한다.
- transition 구간의 legacy token은 `daemon_id = null`을 진단용으로 반환할 수 있어야 하며, `runtime_id`가 없는 구형 token 처리 규칙은 기존 v1 contract를 유지한다.
## Acceptance Criteria
1. Given `runtime_id`가 연결된 service-token으로 `GET /v1/user_info` 호출 시, Then HTTP 200과 응답 JSON에 `runtime_id`, `daemon_id`, `trading_enabled`, `brokers[]` 필드가 포함되어야 한다.
2. Given GET /v1/user_info 응답을 확인하면, Then environment 필드가 존재하지 않아야 한다.
3. Given users.is_active가 true인 사용자의 service-token으로 호출 시, Then 응답의 trading_enabled가 true여야 한다.
4. Given users.is_active가 false인 사용자의 service-token으로 호출 시, Then 응답의 trading_enabled가 false여야 한다.
5. Given broker_credentials에 KIS credential이 저장된 사용자의 토큰으로 호출 시, When 응답의 brokers[] 항목을 확인하면, Then credential_payload가 복호화된 JSON({app_key, app_secret})으로 반환되어야 한다.
6. Given runtime_id가 없는 구형 service-token으로 호출 시, Then 설계 단계에서 확정된 오류 응답이 반환되어야 한다(R-4, 404 또는 즉시 runtime_id 생성 중 선택).
7. Given 인증 없이 요청 시, Then HTTP 401이 반환되어야 한다.
8. Given `daemon_id = NULL`로 backfill된 legacy service-token으로 호출 시, Then HTTP 200 응답이 유지되고 `daemon_id` 필드는 `null`로 노출되어야 한다.
## Impacted Area
- Docs: docs/interface-contract.md (REQ-011에서 갱신 예정), docs/data-model.md (REQ-011에서 갱신 예정)
- Modules: app/api/v1_user_info.py, app/schemas/v1.py, app/core/auth.py, app/domain/models.py, app/services/, app/repositories/postgres.py, app/main.py
- Tests: tests/test_v1.py, tests/test_auth.py
## Notes
- trading_enabled는 users.is_active 단순 매핑이다(A-4 Confirmed). 별도 trading 전용 상태 필드는 이번 반복 Out of Scope.
- disabled_reason: trading_enabled가 false일 때 이유 문자열. 이번 반복에서는 is_active가 false인 경우 "account_inactive" 고정값을 사용하는 방안을 설계 단계에서 확정한다.
- `daemon_id = NULL` legacy token은 transition diagnostics 대상으로 허용되지만, daemon-scoped read/write endpoint에서는 fail-closed가 적용된다.
- R-4(기존 service-token에 runtime_id 없는 경우 처리): 404 또는 즉시 생성 중 설계 단계에서 결정. 이 REQ의 Acceptance Criteria 6번은 runtime_id 부재 처리 규칙에 한해 유지한다.
- Fernet 복호화는 REQ-010에서 정의한 암호화 유틸리티를 사용한다.
- Source Discovery: dcy-002 (FR-4, NFR-1, A-4), dcy-006 (FR-1, FR-5, NFR-1)
## Change Log
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- `/v1/user_info` 응답 요구사항에 stable `daemon_id` diagnostics를 추가하고, runtime_id-only 구현 증거를 무효화하여 Status를 Approved로 되돌림
#### Intent
- dcy-006은 `/v1/user_info`를 runtime 세대 식별과 daemon binding diagnostics를 함께 제공하는 endpoint로 재정의한다. 현재 구현/verification은 `runtime_id`만 기준으로 삼아 revised contract를 충족하지 못한다.
#### Acceptance Criteria Delta
- Added:
- AC-8: legacy token의 `daemon_id = null` diagnostics
- Updated:
- AC-1: 응답 필수 필드에 `daemon_id` 추가
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`
- Modules:
- `app/api/v1_user_info.py`, `app/schemas/v1.py`, `app/core/auth.py`, `app/repositories/postgres.py`
- Tests:
- `tests/test_v1.py`, `tests/test_auth.py`
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate; REL-001 released input) — `/v1/user_info` evidence를 daemon diagnostics 기준으로 재검증해야 함
- Releases:
- REL-001 (released) — released smoke는 `runtime_id`만 확인했으므로 revised REQ 충족 증거로 재사용 불가
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: rollback-to-approved
- Follow-up Action: dcy-006 후속 batch에서 `daemon_id` schema/API contract와 legacy diagnostic behavior를 구현한다.
#### Validation Plan
- `app/api/v1_user_info.py`, `app/schemas/v1.py` 현재 구현에 `daemon_id`가 없는지 코드 리뷰로 확인한다.
- 구현 후 daemon-bound token, legacy daemon-less token, runtime_id-missing token 시나리오를 분리해 targeted pytest를 추가한다.
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-001 re-verified verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-001 verification.md (re-verified, 2026-05-03) AC-1~7 모두 PASS. Blocking Issues 없음.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-001 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-001)
#### Validation Plan
- BAT-001 verification.md (re-verified) AC-1~7 evidence 연결 확인 완료
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-007 confirm-req 승인 전환 (Status: Proposed → Approved)
#### Intent
- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 7개, Impacted Area, Change Log) 모두 충족 확인 후 승인
- 설계 의존성 주의: AC #6(runtime_id 없는 구형 토큰 R-4 처리: 404 또는 즉시 생성)은 설계 단계에서 확정 필요. 핵심 Requirement가 명확하고 Notes에 명시적으로 deferred 처리됨 — 승인 게이트 통과 판단.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs → draft-batch. 설계 단계에서 R-4 처리 방식 확정 후 AC #6 구체화.
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-007 최초 생성 (source Discovery: dcy-002 FR-4)
#### Intent
- trex-basic Consumer-Driven Contract의 GET /v1/user_info 요구사항을 REQ backlog로 정규화
#### Acceptance Criteria Delta
- Added:
- 전체 Acceptance Criteria 신규 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md (REQ-011에서 갱신 예정)
- Modules:
- app/api/v1_user_info.py 신규
- app/domain/models.py BrokerCredential 추가
- Tests:
- tests/test_v1_user_info.py 신규
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 배정. REQ-009(broker_credentials 테이블), REQ-010(Fernet 암호화), REQ-005(runtime_id)와 함께 delivery 단위 구성 권장.
@@ -1,213 +0,0 @@
# req-008: POST /v1/assets/{asset_id}/ledger/trades 엔드포인트 구현
- Status: Approved
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- trex-basic이 체결 내역을 portfolio-hub에 idempotent하게 전송할 수 있도록 한다.
- 동일 (asset_id, order_id, execution_id) 조합의 중복 수신은 DB unique constraint로 감지하고 status=duplicate를 반환한다(NFR-2).
- 동일 키에 다른 payload가 오면 충돌로 처리하며, daemon assignment mismatch 또는 미배정 asset write는 저장 전에 거부한다.
## Requirement
- POST /v1/assets/{asset_id}/ledger/trades는 service-token bearer 인증으로, 인증된 token의 `daemon_id`에 배정된 asset에 대해서만 체결 내역(execution_id, order_id, side, quantity, price, fee_amount, tax_amount, executed_at)을 ledger_trades 테이블에 저장하고 HTTP 201을 반환해야 한다.
- 동일 (asset_id, order_id, execution_id) 재수신은 신규 행 생성 없이 status=duplicate와 기존 ledger_entry_id를 HTTP 200으로 반환해야 한다.
- 동일 키에 payload가 다른 경우 HTTP 409를 반환해야 한다.
- idempotency는 DB unique constraint로 보장하며, 애플리케이션 로직만으로 보장하지 않는다.
- daemon assignment mismatch, 미배정 asset, `daemon_id = NULL` legacy token은 모두 HTTP 403으로 fail-closed 처리해야 하며 ledger row가 생성되면 안 된다.
## Acceptance Criteria
1. Given 유효한 daemon-bound service-token과 해당 daemon에 배정된 asset의 신규 체결 데이터로 POST /v1/assets/{asset_id}/ledger/trades 요청 시, Then HTTP 201과 생성된 ledger_entry_id가 반환되어야 한다.
2. Given 동일 (asset_id, order_id, execution_id)와 동일 payload로 두 번째 요청 시, Then HTTP 200과 status="duplicate", 기존 ledger_entry_id가 반환되어야 하며 DB에 단일 행만 존재해야 한다.
3. Given 동일 (asset_id, order_id, execution_id)에 다른 payload로 요청 시, Then HTTP 409가 반환되어야 한다.
4. Given asset_id가 존재하지 않는 portfolio_id인 경우, Then HTTP 404가 반환되어야 한다.
5. Given 인증 없이 요청 시, Then HTTP 401이 반환되어야 한다.
6. Given asset이 다른 daemon에 배정되어 있는 경우, When 요청 daemon이 asset owner daemon과 다르면, Then HTTP 403이 반환되어야 하고 ledger row는 생성되지 않아야 한다.
7. Given asset이 아직 daemon에 배정되지 않았거나 토큰의 `daemon_id`가 NULL인 경우, Then HTTP 403이 반환되어야 하고 ledger row는 생성되지 않아야 한다.
## Impacted Area
- Docs: docs/interface-contract.md (REQ-011에서 갱신 예정), docs/data-model.md (REQ-011에서 갱신 예정)
- Modules: app/api/v1_ledger.py, app/domain/models.py, app/services/, app/schemas/, app/repositories/portfolio_repository.py, app/repositories/postgres.py, app/main.py
- Tests: tests/test_v1.py 또는 신규 daemon assignment / ledger authorization 회귀 테스트
## Notes
- NFR-2(idempotency DB unique constraint): (asset_id, order_id, execution_id) unique constraint는 REQ-009 migration에서 정의한다.
- ledger_trades 테이블과 기존 portfolio_ledger 테이블은 별개다. portfolio_ledger는 내부 원장이고 ledger_trades는 trex-basic이 외부에서 기록하는 체결 내역이다.
- 중복 감지 구현: DB unique constraint 위반(psycopg UniqueViolation) 캐치 → 기존 행 조회 → payload 동일 여부 비교 → duplicate 또는 409 응답.
- daemon authorization은 기존 permission(`trade-write`) 위에 추가되는 두 번째 authorization 축이다.
- Source Discovery: dcy-002 (FR-5, NFR-2), dcy-006 (FR-2, FR-4, NFR-2)
## Change Log
### CHG-20260518-01
- Date: 2026-05-18
- Author: repository-maintainers
#### Change Summary
- `insert_ledger_trade()` 미구현 결함 수정 — PostgresPortfolioRepository에 메서드가 존재하지 않아 모든 POST /v1/assets/{asset_id}/ledger/trades 요청이 HTTP 500으로 실패하던 결함을 수정. Status Approved 유지.
#### Intent
- `app/api/v1_ledger.py:78`에서 `repo.insert_ledger_trade()`를 호출하지만 PostgresPortfolioRepository에 구현이 없어 AttributeError → HTTP 500이 발생했다. FakeRepo 기반 테스트는 통과했으나 실 DB 환경에서는 endpoint 전체가 불동작 상태였다. trex-basic의 write-back이 전면 차단된 High 심각도 결함이었으며, 구현을 추가해 정상화했다.
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules:
- `app/repositories/postgres.py`: `insert_ledger_trade(asset_id, order_id, execution_id, side, quantity, price, executed_at, fee_amount, tax_amount)` 추가. `INSERT INTO ledger_trades ... ON CONFLICT ON CONSTRAINT uq_ledger_trades_idempotency DO NOTHING RETURNING *` 패턴으로 신규/중복 여부를 `(LedgerTrade, bool)` 튜플로 반환.
- `app/repositories/portfolio_repository.py`: Protocol에 `insert_ledger_trade` 선언 추가, `LedgerTrade` import 추가
- Tests: 기존 FakeRepo 기반 테스트는 영향 없음. 실 DB 통합 테스트(AC-1~7)는 별도 재검증 필요.
#### Delivery Trace Delta
- Batches: BAT-010 (release-candidate) — 결함 수정이 BAT-010 delivery scope에 포함
- Releases: REL-006 (draft) — 배포 전 결함 수정 완료. 실 DB 통합 재검증 후 배포 권장.
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: no (AC 정의는 유효, postgres 구현만 누락되어 있었음)
- Status Recommendation: keep-current (Approved; CHG-20260513-01에 의해 Approved 상태)
- Follow-up Action: 실 DB 환경에서 AC-1(신규 체결 201), AC-2(중복 200+duplicate), AC-3(payload 불일치 409), AC-6~7(daemon mismatch/미배정 403) 통합 시나리오 검증 후 Implemented 전환 가능
#### Validation Plan
- `POST /v1/assets/{asset_id}/ledger/trades`에 유효 daemon-bound service-token으로 요청 시 HTTP 201 반환 확인
- 동일 (asset_id, order_id, execution_id) 재요청 시 HTTP 200 + status=duplicate 확인
- daemon mismatch, 미배정 asset 요청 시 HTTP 403 확인
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- ledger write REQ에 daemon assignment authorization과 403 fail-closed 규칙을 추가하고, 기존 Implemented 상태를 Approved로 되돌림
#### Intent
- dcy-006은 `trade-write` permission만으로는 충분하지 않고, asset이 인증 daemon에 배정됐는지까지 검증하도록 요구한다. 현재 구현은 asset 존재성만 확인하므로 revised authorization contract를 충족하지 못한다.
#### Acceptance Criteria Delta
- Added:
- AC-6: daemon mismatch 403
- AC-7: 미배정 asset 또는 daemon-less token 403
- Updated:
- AC-1: 성공 조건을 daemon-assigned asset으로 제한
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/data-model.md`
- Modules:
- `app/api/v1_ledger.py`, `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py`
- Tests:
- `tests/test_v1.py` 또는 신규 daemon assignment ledger authorization 테스트
#### Delivery Trace Delta
- Batches:
- BAT-001 (release-candidate; REL-001 released input) — ledger verification evidence는 404/409/idempotency 중심이라 daemon authz 케이스를 추가해 재검증해야 함
- Releases:
- REL-001 (released) — released trade-write contract는 daemon mismatch 403 정책을 포함하지 않음
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: rollback-to-approved
- Follow-up Action: dcy-006 후속 batch에서 asset assignment lookup과 ledger authorization guard를 구현하고 duplicate/conflict 회귀와 함께 재검증한다.
#### Validation Plan
- `app/api/v1_ledger.py`가 현재 asset 존재성만 확인하는지 코드 리뷰로 확인한다.
- 구현 후 success/duplicate/conflict/404와 daemon mismatch·unassigned 403 시나리오를 함께 검증한다.
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-001 re-verified verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-001 verification.md (re-verified, 2026-05-03) AC-1~5 모두 PASS. Blocking Issues 없음.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-001 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-001)
#### Validation Plan
- BAT-001 verification.md (re-verified) AC-1~5 evidence 연결 확인 완료
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-008 confirm-req 승인 전환 (Status: Proposed → Approved)
#### Intent
- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 5개, Impacted Area, Change Log) 모두 충족 확인 후 승인
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs → draft-batch
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-008 최초 생성 (source Discovery: dcy-002 FR-5)
#### Intent
- trex-basic Consumer-Driven Contract의 POST /v1/assets/{asset_id}/ledger/trades 요구사항을 REQ backlog로 정규화
#### Acceptance Criteria Delta
- Added:
- 전체 Acceptance Criteria 신규 작성
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md (REQ-011에서 갱신 예정)
- Modules:
- app/api/v1_ledger.py 신규
- app/domain/models.py LedgerTrade 추가
- Tests:
- tests/test_v1_ledger.py 신규
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 배정. REQ-009(ledger_trades 테이블 + unique constraint)와 함께 delivery 단위 구성 필수.
@@ -1,211 +0,0 @@
# req-012: service-token rotate CLI 명령 구현
- Status: Approved
- Type: Interface
- Priority: Medium
- Owner: repository-maintainers
## Intent
- 발급된 service-token의 원문이 노출되었거나 주기적 교체가 필요한 경우, token을 revoke 없이 in-place로 갱신할 수 있는 CLI 명령이 필요하다.
- 기존 `revoke → re-issue` 방식은 `token_id`, `user_id`, `permission`, `expires_at`, `created_at` 등 메타데이터가 변경되므로, 운영 시스템에서 동일한 이름으로 참조하는 통합 구성을 다시 설정해야 하는 불편이 있다.
- rotate는 `token_hash`, `token_prefix`, `runtime_id`만 교체하고 `daemon_id`를 포함한 나머지 메타데이터를 유지하여, 동일한 token 이름과 daemon binding을 유지한 채 새 token 원문만 배포할 수 있게 한다. 단, `runtime_id`를 소비하는 시스템은 rotate 후 새 토큰 기준으로 runtime_id를 재조회해야 한다.
## Requirement
- `pfhub service-token rotate <name> [--force]` CLI 명령을 구현한다.
- rotate는 기존 auth_token 행의 `token_hash`, `token_prefix`, `runtime_id`를 새 값으로 UPDATE하고, `daemon_id`는 유지해야 한다.
- `id`, `user_id`, `name`, `permission`, `token_type`, `expires_at`, `created_at`, `daemon_id`는 변경하지 않는다.
- rotate 대상 token이 존재하지 않으면 오류를 반환한다.
- rotate 대상 token이 이미 revoked 상태이면 오류를 반환한다.
- rotate 대상 token의 `expires_at`이 NULL이 아닌 경우(만료일이 설정된 토큰) 오류를 반환한다. rotate는 `--no-expiry`로 발급된 영구 토큰에만 허용된다.
- `--force` 없이 실행하면 확인 프롬프트를 표시한다.
- rotate 성공 시 새 token 원문을 1회 출력하고, 이후 list/조회에서는 token_prefix만 표시된다. rotate 출력 메타데이터에는 `daemon_id`가 포함되어야 한다.
## Acceptance Criteria
1. Given `expires_at IS NULL`인 유효한 service-token 이름, When `pfhub service-token rotate <name> --force`를 실행, Then DB 행의 `token_hash`, `token_prefix`, `runtime_id`가 새 값으로 변경되고 `id`, `permission`, `expires_at`(NULL), `daemon_id`는 기존과 동일하다.
2. Given rotate 성공, When CLI 출력, Then 새 token 원문이 화면에 1회 출력되고 "이 토큰은 지금 1회만 표시" 안내 메시지와 함께 `daemon_id` 메타데이터가 표시되어야 한다.
3. Given 존재하지 않는 token 이름, When `rotate`, Then 오류 메시지를 출력하고 종료 코드 1을 반환한다.
4. Given revoked 상태의 token, When `rotate`, Then 오류 메시지를 출력하고 종료 코드 1을 반환한다.
5. Given `expires_at`이 NULL이 아닌(만료일이 설정된) token, When `rotate`, Then "만료일이 설정된 토큰은 rotate할 수 없습니다" 오류를 출력하고 종료 코드 1을 반환한다.
6. Given `--force` 없이 실행, When CLI, Then 확인 프롬프트를 표시하고 거절 시 변경 없이 종료한다.
## Impacted Area
- Docs: docs/project-structure.md (변경 없음), docs/runtime-flows.md (변경 없음)
- Modules:
- `app/repositories/portfolio_repository.py` — `rotate_service_token` Protocol stub 추가
- `app/repositories/postgres.py` — `rotate_service_token` UPDATE 구현 추가
- `app/services/auth_service.py` — `rotate_service_token(name)` 서비스 메서드 추가
- `app/cli/commands/service_tokens.py` — `rotate` 명령 및 `daemon_id` 출력 추가
- `tests/test_cli.py` — stub repo에 `rotate_service_token` 추가 및 테스트 케이스 추가
- Tests:
- `tests/test_cli.py`: rotate 성공, 없는 이름, revoked 상태, `daemon_id` 유지/출력 케이스
## Notes
- `expires_at`은 rotate 시 변경하지 않는다. 만료일 변경이 필요하면 revoke + re-issue를 사용한다.
- rotate는 `--no-expiry`로 발급된 영구 토큰(`expires_at IS NULL`)에만 허용된다. 만료일이 설정된 토큰은 rotate 대상이 아니다. 해당 토큰은 만료 후 re-issue하는 것이 권장 패턴이다.
- rotate는 `runtime_id`도 새 값으로 갱신하지만 `daemon_id`는 유지한다. trex-basic 등 `runtime_id`를 소비하는 시스템은 rotate 후 새 토큰으로 다시 인증하고 `GET /v1/user_info`를 재호출해 새 `runtime_id`를 재인식해야 한다.
- 기존 `runtime_id`를 캐싱한 상태로 계속 사용하면 consumer 식별 불일치가 발생할 수 있다. rotate는 "토큰 원문 교체 시 token name과 메타데이터를 유지한다"는 의미이지, `runtime_id`까지 불변이라는 뜻은 아니다.
- rotate는 daemon provisioning 경로가 아니며, unknown daemon 자동 생성/업서트를 수행하면 안 된다.
- DB 스키마 변경(migration)은 없다. 기존 `auth_tokens` 테이블의 기존 컬럼만 UPDATE한다.
- 구조/인터페이스/런타임 흐름 변경 없음 → minor-change fast path 적용.
## Change Log
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- rotate REQ에 `daemon_id` 불변식과 CLI diagnostic 출력 요구를 추가하고, 기존 Implemented 상태를 Approved로 되돌림
#### Intent
- dcy-006은 rotate 이후에도 `daemon_id`가 stable identity로 유지되어야 하고, 운영자는 rotate 출력과 provenance 절차에서 `daemon_id`를 확인할 수 있어야 한다. BAT-002/REL-002 구현은 runtime_id lifecycle만 다루므로 revised REQ를 충족하지 못한다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- AC-1: `daemon_id` 유지 불변식 추가
- AC-2: CLI output에 `daemon_id` metadata 추가
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `README.md`, token provenance 문서, `docs/runtime-flows.md`
- Modules:
- `app/cli/commands/service_tokens.py`, `app/services/auth_service.py`, `app/repositories/postgres.py`
- Tests:
- `tests/test_cli.py`, 필요한 경우 auth/v1 provenance 회귀 테스트
#### Delivery Trace Delta
- Batches:
- BAT-002 (release-candidate; REL-002 released input) — rotate verification evidence는 `daemon_id` 유지/출력 기준으로 재검증 필요
- Releases:
- REL-002 (released) — rotate 운영 메모는 runtime_id 재조회만 다루며 daemon-aware provenance는 미반영
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes
- Status Recommendation: rollback-to-approved
- Follow-up Action: dcy-006 후속 batch에서 rotate response metadata와 daemon binding invariants를 구현하고, REL-002 후속 tooling release evidence를 갱신한다.
#### Validation Plan
- current CLI/service/repository 구현이 `daemon_id`를 보존·출력하지 않는지 코드 리뷰로 확인한다.
- 구현 후 rotate 전후 `daemon_id` 불변, `runtime_id` 갱신, CLI output metadata를 targeted CLI 테스트로 검증한다.
### CHG-20260511-02
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-002 verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-002 verification.md에서 REQ-012 AC-1~6이 모두 PASS이고 blocking issue가 없음을 근거로 Implemented로 승격한다.
#### Acceptance Criteria Delta
- Added: none
- Updated: none
- Removed: none
#### Impacted Area Delta
- Docs: none
- Modules: none
- Tests: none
#### Delivery Trace Delta
- Batches:
- BAT-002 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: tooling release에 포함 시 rotate 후 runtime_id 재조회 운영 메모 반영
#### Validation Plan
- BAT-002 verification.md의 REQ-012 AC-1~6 evidence와 Ready for Release: true 확인
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- rotate 후 `runtime_id` lifecycle 명시 추가 — downstream consumer는 새 토큰 배포 후 `runtime_id`를 재조회해야 함
#### Intent
- CONFLICT-3 분석 결과를 반영해 `runtime_id`가 rotate 시 변경된다는 사실과, trex-basic 같은 consumer가 이를 어떻게 재인식해야 하는지 REQ-012 문서에 명시한다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs:
- `docs/srs/Interface/req-012_service-token-rotate-cli.md`: Intent/Notes에 rotate 후 `runtime_id` 재조회 lifecycle 추가
- Modules:
- none
- Tests:
- none
#### Delivery Trace Delta
- Batches:
- BAT-002 (release-candidate) — verification evidence는 유지 가능하나, rotate 후 consumer 재인식 절차를 운영 메모로 함께 검토해야 한다.
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: trex-basic 연동 runbook 또는 후속 REQ에서 rotate 후 `runtime_id` 재조회 절차를 운영 문서에 반영 검토
#### Validation Plan
- REQ-005, REQ-007, REQ-012 문구를 대조해 rotate 시 `runtime_id` 변경과 `GET /v1/user_info` 재조회 필요성이 일관되게 설명되는지 코드 리뷰로 확인
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-012 신규 작성 (minor-change fast path, Approved 직행)
- rotate 허용 대상을 `expires_at IS NULL`(no-expiry) 토큰으로 제한
#### Intent
- service-token rotate CLI 명령 필요성 확인 후 즉시 구현 진행
#### Acceptance Criteria Delta
- Added:
- 6개 기준 신규 정의 (AC-5: expires_at 설정 토큰 rotate 거부 추가)
#### Impacted Area Delta
- Modules:
- postgres.py, auth_service.py, service_tokens.py, test_cli.py 추가
- Tests:
- test_cli.py 3개 케이스 신규
#### Delivery Trace Delta
- Batches:
- BAT-002 (batch-lite)
- Releases:
- none (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: BAT-002 planning → implementation → verification 진행
#### Validation Plan
- test_cli.py 3개 케이스 통과 + 기존 service-token 테스트 회귀 없음으로 완료 판단
@@ -1,143 +0,0 @@
# REQ-013: CLI `portfolio list --user` 필수화 및 소유자 기반 조회
- Status: Implemented
- Type: Interface
- Priority: Medium
- Owner: repository-maintainers
## Intent
- CLI `pfhub portfolio list`는 현재 `--user` 옵션이 없어 `owner_user_id=None`으로 호출되며 전체 포트폴리오를 반환한다. 이 동작은 소유자 구분 없이 DB 전체를 노출하는 문제가 있다.
- `--user <username>` 옵션을 필수로 지정하여 CLI 실행 시 항상 특정 사용자의 포트폴리오만 조회하도록 강제한다.
- username을 `owner_user_id`(UUID)로 변환하는 로직을 CLI 내부에 추가하여, `PortfolioService.list_portfolios` 호출 시 소유자 필터가 항상 적용되게 한다.
## Requirement
- CLI `pfhub portfolio list` 명령은 `--user <username>` 옵션을 필수로 받아야 한다.
- `--user` 없이 실행하면 오류 메시지를 출력하고 exit code 1로 종료해야 한다.
- `--user`로 전달된 username을 user repository(`get_user_by_username`)로 조회하여 `owner_user_id`(UUID)를 얻고, 이를 `PortfolioService.list_portfolios(owner_user_id=...)` 호출에 전달해야 한다.
- 존재하지 않는 username이 전달되면 기존 CLI 오류 출력 형식(Rich Console 또는 typer.echo)과 일관된 오류 메시지를 출력하고 비정상 종료해야 한다.
## Acceptance Criteria
1. Given `--user` 옵션 없이 `pfhub portfolio list`를 실행할 때, When 명령이 실행되면, Then 오류 메시지("--user 옵션이 필요합니다" 또는 동등한 내용)가 출력되고 exit code 1로 종료된다.
2. Given 유효한 username `alice`로 `pfhub portfolio list --user alice`를 실행할 때, When 명령이 실행되면, Then `alice`의 `owner_user_id`로 필터된 포트폴리오 목록만 반환되고 다른 사용자 소유 포트폴리오는 포함되지 않는다.
3. Given 존재하지 않는 username으로 `pfhub portfolio list --user unknown`을 실행할 때, When 명령이 실행되면, Then "사용자를 찾을 수 없습니다" 형태의 오류 메시지가 출력되고 exit code 1로 종료된다.
4. Given in-memory 모드(`pfhub_DB_ENABLED=false`)에서 `pfhub portfolio list --user alice`를 실행할 때, When 명령이 실행되면, Then DB 모드와 동일하게 `alice` 소유 포트폴리오만 반환된다.
5. Given 오류 발생 시, When 오류 메시지가 출력될 때, Then 메시지 형식은 기존 CLI 오류 출력 형식(Rich Console stderr 또는 typer.echo)과 일관성을 유지한다.
## Impacted Area
- Docs:
- `README.md` — `pfhub portfolio list` 예제 라인에 `--user <username>` 옵션 추가 및 breaking change 안내
- Modules:
- `app/cli/commands/portfolio.py` — `list_portfolios` 명령 시그니처에 `--user` 필수 옵션 추가, username → owner_user_id 변환 로직 추가, `PortfolioService.list_portfolios` 호출 시 `owner_user_id` 전달
- `app/repositories/portfolio_repository.py` — `get_user_by_username` Protocol stub 기존 존재 확인(line 16), 추가 변경 불필요
- `app/repositories/postgres.py` — `get_user_by_username` 기존 구현 확인(line 419), 추가 변경 불필요
- Tests:
- `tests/test_cli.py` — AC 1~5 커버 테스트 케이스 추가:
- `test_cli_portfolio_list_without_user_exits_error`
- `test_cli_portfolio_list_with_valid_user_filters_by_owner`
- `test_cli_portfolio_list_with_unknown_user_exits_error`
- `test_cli_portfolio_list_in_memory_mode_works`
## Notes
- `PortfolioService.list_portfolios` 시그니처는 변경하지 않는다. CLI 호출부에서만 `owner_user_id`를 전달한다 (A-3, dcy-003).
- HTTP API `GET /portfolios`의 접근 통제는 이번 REQ 범위 밖이다 (Out of Scope).
- `--all` 관리자 전체 조회 옵션은 이번 REQ에 포함하지 않는다. 후속 Discovery로 분리한다.
- username → owner_user_id 변환은 `repository.get_user_by_username(username)` 직접 호출로 구현한다. `UserService`에는 해당 메서드가 없음을 확인하였다.
- 기존 `_with_service` 헬퍼는 `PortfolioService`만 생성한다. username 조회를 위해 별도로 repository를 직접 사용하거나 `_with_service` callback 안에서 repository 접근이 필요하다. 구현 시 `app_context.repository_factory(settings)` 반환 객체를 통해 `get_user_by_username`을 호출하는 방식으로 처리한다.
- Source Discovery: [dcy-003](../../discovery/dcy-003_20260502_portfolio-list-user-required.md)
## Change Log
### CHG-20260502-03
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- Status Approved → Implemented. BAT-003(release-candidate) verification evidence 확인 완료.
#### Intent
- confirm-req-implemented 실행. AC 1~5 전원 evidence 연결 확인, Blocking Issues 없음.
#### Evidence Batch
- Batch: BAT-003 (release-candidate)
- Verification: docs/batches/bat-003_20260502_portfolio-list-user-required/verification.md
- Test result: 41 passed (pytest tests/test_cli.py)
#### Acceptance Criteria Delta
- 없음
#### Impacted Area Delta
- Docs: 없음
- Modules: 없음
- Tests: 없음
#### Delivery Trace Delta
- Batches: BAT-003 (release-candidate)
- Releases: none (draft-release 대기 중)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-release
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- Status Proposed → Approved. 승인 게이트 통과. 필수 필드, AC 5개, Impacted Area, Change Log 모두 충족.
#### Intent
- confirm-req 실행으로 구현 delivery 입력 준비 확정.
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules: 없음
- Tests: 없음
#### Delivery Trace Delta
- Batches: none
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: batch 생성 진행 (draft-batch)
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-013 최초 생성 (Proposed). dcy-003 FR-1, FR-2, FR-3 통합. NFR-1, NFR-2는 AC 5에 흡수.
#### Intent
- dcy-003 확정 Discovery를 REQ backlog로 정규화.
#### Acceptance Criteria Delta
- Added:
- AC 1~5 신규 작성
#### Impacted Area Delta
- Docs:
- README.md breaking change 안내 추가 항목
- Modules:
- app/cli/commands/portfolio.py 신규 변경
- Tests:
- tests/test_cli.py 4개 테스트 케이스 신규 추가
#### Delivery Trace Delta
- Batches: none (batch 미생성)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 실행 후 batch 계획 수립
@@ -1,78 +0,0 @@
# REQ-014: CLI `portfolio create --user` 필수화 및 소유자 설정
- Status: Implemented
- Type: Interface
- Priority: Medium
- Owner: repository-maintainers
## Intent
- CLI `pfhub portfolio create`는 현재 `--user` 옵션이 없어 `owner_user_id="system"` 고정값으로 포트폴리오가 생성된다.
- 생성 시 `--user <username>` 을 필수로 받아 실제 소유자의 `owner_user_id`(UUID)로 포트폴리오가 생성되도록 강제한다.
- REQ-013(`portfolio list --user` 필수화)과 대칭을 이루는 변경으로, 소유자 기반 포트폴리오 관리 정책을 CLI 전반에 일관되게 적용한다.
## Requirement
- CLI `pfhub portfolio create` 명령은 `--user <username>` 옵션을 필수로 받아야 한다.
- `--user` 없이 실행하면 오류 메시지를 출력하고 exit code 1로 종료해야 한다.
- `--user`로 전달된 username을 `repository.get_user_by_username(username)`으로 조회하여 `owner_user_id`(UUID)를 얻고, 이를 `PortfolioService.create_portfolio(owner_user_id=...)` 호출에 전달해야 한다.
- 존재하지 않는 username이 전달되면 기존 CLI 오류 출력 형식과 일관된 오류 메시지를 출력하고 비정상 종료해야 한다.
## Acceptance Criteria
1. Given `--user` 옵션 없이 `pfhub portfolio create <id> --symbol X --force`를 실행할 때, When 명령이 실행되면, Then 오류 메시지가 출력되고 exit code 1로 종료된다.
2. Given 유효한 username `alice`로 `pfhub portfolio create <id> --symbol X --user alice --force`를 실행할 때, When 명령이 실행되면, Then 생성된 포트폴리오의 `owner_user_id`가 alice의 UUID로 설정된다.
3. Given 존재하지 않는 username으로 실행할 때, When 명령이 실행되면, Then "사용자를 찾을 수 없습니다" 형태의 오류 메시지가 출력되고 exit code 1로 종료된다.
4. Given in-memory 모드에서 `--user alice`로 실행할 때, When 명령이 실행되면, Then DB 모드와 동일하게 alice의 UUID가 `owner_user_id`로 설정된다.
5. Given 오류 발생 시, When 오류 메시지가 출력될 때, Then 메시지 형식은 기존 CLI 오류 출력 형식(Rich Console)과 일관성을 유지한다.
## Impacted Area
- Docs:
- `README.md` — `pfhub portfolio create` 예제 라인에 `--user <username>` 옵션 추가
- Modules:
- `app/cli/commands/portfolio.py` — `create_portfolio` 명령 시그니처에 `--user` 필수 옵션 추가, `_with_service_and_repo` 콜백 사용, `owner_user_id` 전달
- Tests:
- `tests/test_cli.py` — 기존 `test_cli_create_buy_and_delete_flow` 수정 + AC 1~4 커버 케이스 추가
## Notes
- `PortfolioService.create_portfolio` 시그니처(`owner_user_id: str = "system"`)는 변경하지 않는다. CLI 호출부에서만 실제 UUID를 전달한다.
- REQ-013에서 도입한 `_with_service_and_repo` 헬퍼를 그대로 사용한다. 추가 헬퍼 불필요.
- minor-change fast path 적용: Discovery 생략, Approved 직행. 범위는 CLI 옵션 1개 추가이며 기존 서비스/repository 인터페이스 변경 없음.
- Source Discovery: 없음 (minor-change fast path)
## Change Log
### CHG-20260502-01
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- REQ-014 최초 생성 (Approved 직행). minor-change fast path 적용. REQ-012, REQ-013 선례 동일 경로.
#### Intent
- `pfhub portfolio create`의 소유자 설정 누락 문제를 CLI 레벨에서 해결.
### CHG-20260502-02
- Date: 2026-05-02
- Author: repository-maintainers
#### Change Summary
- BAT-004 verification(44 passed) 기반으로 Approved → Implemented 전환.
#### Intent
- 구현 및 검증 증거 확보 완료 후 상태 동기화.
#### Acceptance Criteria Delta
- Added: AC 1~5 신규 작성
#### Impacted Area Delta
- Docs: README.md create 예제 갱신
- Modules: app/cli/commands/portfolio.py create_portfolio 수정
- Tests: tests/test_cli.py 케이스 추가/수정
#### Delivery Trace Delta
- Batches: none (batch 미생성)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: draft-batch 진행
@@ -1,143 +0,0 @@
# req-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
- Status: Implemented
- Type: Interface
- Priority: Medium
- Owner: repository-maintainers
## Intent
- 운영자가 CLI(`pfhub credential`)를 통해 KIS 브로커 인증 정보를 안전하게 등록, 조회, 삭제할 수 있어야 한다.
- `broker_credentials` 테이블(migration 0014)과 기존 Fernet 암호화 인프라(`app/core/encryption.py`, `CREDENTIAL_ENCRYPTION_KEY`)를 재사용해 CLI 레이어만 추가한다.
## Requirement
- 시스템은 `pfhub credential set --user <username> --broker <broker> --account-ref <ref> --app-key <key> --app-secret <secret>` 명령을 제공해야 한다. 이 명령은 `{"app_key": ..., "app_secret": ...}` JSON을 Fernet으로 암호화한 뒤 `broker_credentials` 테이블에 upsert한다.
- 시스템은 `pfhub credential list --user <username>` 명령을 제공해야 한다. 이 명령은 사용자의 브로커 인증 정보 목록을 반환하되, `encrypted_payload`는 출력하지 않는다.
- 시스템은 `pfhub credential remove --user <username> --broker <broker> [--force]` 명령을 제공해야 한다. 이 명령은 해당 인증 정보 레코드를 삭제한다.
- 존재하지 않는 사용자를 지정하면 에러 메시지와 함께 exit code 1로 종료해야 한다.
## Acceptance Criteria
1. Given alice 사용자와 유효한 `CREDENTIAL_ENCRYPTION_KEY`가 설정된 환경에서, When `pfhub credential set --user alice --broker kis --account-ref REF --app-key KEY --app-secret SECRET`을 실행하면, Then `broker_credentials` 테이블에 암호화된 payload로 레코드가 생성(또는 갱신)된다.
2. Given `broker_credentials`에 alice의 kis 레코드가 있을 때, When `pfhub credential list --user alice`를 실행하면, Then broker, account_ref, credential_version, last_rotated_at이 출력되고 `encrypted_payload`는 포함되지 않는다.
3. Given 존재하지 않는 username을 지정할 때, When `pfhub credential set --user unknown ...`을 실행하면, Then 에러 메시지와 함께 exit code 1로 종료된다.
4. Given alice의 kis 인증 정보가 있을 때, When `pfhub credential remove --user alice --broker kis --force`를 실행하면, Then 해당 레코드가 삭제되고 `pfhub credential list --user alice`에서 더 이상 표시되지 않는다.
5. Given `CREDENTIAL_ENCRYPTION_KEY`가 설정되지 않은 환경에서, When `pfhub credential set ...`을 실행하면, Then 설정 미비 에러 메시지와 함께 exit code 1로 종료된다.
## Impacted Area
- Docs: `docs/project-structure.md` (CLI 모듈 목록 갱신)
- Modules:
- `app/cli/commands/credential.py` (신규 — `credential_app`, `set`, `list`, `remove` 명령)
- `app/cli/main.py` (`credential_app` 등록 추가)
- `app/repositories/postgres.py` (`delete_broker_credential` 메서드 추가)
- Tests: `tests/test_cli.py` (credential 명령 통합 테스트 추가)
## Notes
- `broker_credentials` 테이블 DDL과 `upsert_broker_credential`, `list_broker_credentials`, `get_broker_credential` 메서드는 migration 0014에서 이미 구현됨. `delete_broker_credential` 메서드는 이번 REQ에서 추가.
- `app/core/encryption.py`의 `encrypt_payload(key, plaintext)` 사용.
- 기존 CLI 패턴(`_with_service` 헬퍼, `execute_command`, `render_output`, `_confirm`) 동일하게 적용.
- `--broker` 기본값은 `kis`.
## Change Log
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-005 verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-005 verification.md에서 REQ-015 AC-1~5가 모두 passed이고 regression 없음이 확인되어 Implemented로 승격한다.
#### Acceptance Criteria Delta
- Added: none
- Updated: none
- Removed: none
#### Impacted Area Delta
- Docs: none
- Modules: none
- Tests: none
#### Delivery Trace Delta
- Batches:
- BAT-005 (release-candidate)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: tooling release에 포함 시 credential smoke 절차 포함
#### Validation Plan
- BAT-005 verification.md의 AC-1~5 passed evidence와 residual risk 없음 확인
### CHG-20260503-02
- Date: 2026-05-03
- Author: repository-maintainers
#### Change Summary
- REQ-015 Approved 전환 — minor-change fast path (단일 저위험 REQ, CLI 레이어만 추가, 구조/런타임 영향 없음)
#### Intent
- Acceptance Criteria 5개 모두 구현 판단에 충분하며 blocking assumption 없음
#### Acceptance Criteria Delta
- Added: none
- Updated: none
- Removed: none
#### Impacted Area Delta
- Docs: none
- Modules: none
- Tests: none
#### Delivery Trace Delta
- Batches: BAT-005 (batch-lite)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: run-batch-implementation 진행
#### Validation Plan
- BAT-005 verification 통과 확인
---
### CHG-20260503-01
- Date: 2026-05-03
- Author: repository-maintainers
#### Change Summary
- REQ-015 최초 작성 — CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
#### Intent
- 운영자가 KIS 브로커 인증 정보를 CLI로 관리할 수 있도록 Interface 레이어 추가
#### Acceptance Criteria Delta
- Added:
- 위 Acceptance Criteria 1~5 전체
- Updated: none
- Removed: none
#### Impacted Area Delta
- Docs: `docs/project-structure.md`
- Modules: `app/cli/commands/credential.py` (신규), `app/cli/main.py`, `app/repositories/postgres.py`
- Tests: `tests/test_cli.py`
#### Delivery Trace Delta
- Batches: 미정 (BAT-005 예정)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: 없음
#### Validation Plan
- `tests/test_cli.py`에 credential 명령 통합 테스트 작성 후 `pytest tests/test_cli.py` 전체 통과 확인
@@ -1,186 +0,0 @@
# req-017: CLI portfolio 명령 — name+user 조합 식별 방식 전환
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- 현재 `portfolio_id` Argument로만 portfolio를 식별하는 CLI 뮤테이션 명령을 `<name>` + `--user <username>` 조합으로 전환해 소유권 검증을 명령 실행 경로에 포함한다.
- CLI 내부에서 `(owner_user_id, name)` → UUID 단일 쿼리 조회 후 처리하고, 조회 실패 시 명확한 오류 메시지를 출력한다.
## Requirement
- `portfolio create` 명령은 `name` Argument와 `--user <username>` 옵션을 필수로 받는다. `portfolio_id`는 서버가 UUID로 발급하며 사용자가 직접 지정하거나 조회할 수 없다.
- `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update` 명령은 `<name>` Argument와 `--user <username>` 옵션으로 portfolio를 식별한다. 내부에서 `(owner_user_id, name)` → `portfolio_id(UUID)`를 단일 DB 쿼리로 조회한 뒤 처리한다.
- `<name>` + `--user` 조합으로 portfolio를 찾지 못하면 오류 메시지 `포트폴리오를 찾을 수 없습니다: {name} (user: {username})`를 출력하고 명령을 중단한다.
- `name`은 `update` 명령으로 변경 가능하다. 변경 시 동일 `owner_user_id` 내에 같은 `name`이 이미 존재하면 오류 메시지 `이미 사용 중인 이름입니다: {name}`를 반환한다.
- `name` 변경 명령에는 명시적 확인 절차(`--force` 또는 confirm 프롬프트)를 요구한다.
- `name` 허용 문자는 `[a-zA-Z0-9._:-]`이며, 공백 불허, 1~100자, 대소문자 구분이 있다. 이 범위를 벗어나면 유효성 검사 오류를 반환한다.
## Acceptance Criteria
1. Given `pfhub portfolio create <name> --user <username> ...`을 실행하면, When 서버가 UUID를 발급하면, Then CLI가 portfolio_id를 직접 지정하거나 조회하지 않고 UUID가 DB에 저장된다.
2. Given `pfhub portfolio buy <name> --user alice ...`를 실행하면, When `(alice, <name>)` 조합으로 DB를 단일 쿼리로 조회해 UUID를 찾으면, Then 해당 portfolio에 buy 작업이 수행된다.
3. Given `--user bob`으로 alice 소유의 portfolio `<name>`을 조작하면, When `(bob, <name>)` 조합으로 조회해 레코드가 없으면, Then `포트폴리오를 찾을 수 없습니다: {name} (user: bob)` 오류가 출력되고 명령이 중단된다.
4. Given alice와 bob이 동일 name `tv`로 각각 portfolio를 생성하면, Then 두 레코드가 독립적으로 저장되고 각자 자신의 `tv` portfolio를 조작할 수 있다.
5. Given alice의 portfolio name을 `tv2`로 변경할 때 alice에게 이미 `tv2` 이름의 portfolio가 존재하면, When `update` 명령이 DB의 `(owner_user_id, name)` UNIQUE 제약 위반을 감지하면, Then `이미 사용 중인 이름입니다: tv2` 오류를 출력한다.
6. Given `--force` 없이 `update` 명령으로 name을 변경하려 하면, Then `--force`가 없으면 변경이 중단되거나 confirm 프롬프트가 표시된다.
7. Given `name` 허용 문자 범위(`[a-zA-Z0-9._:-]`) 밖의 문자가 포함된 이름을 `create` 시 입력하면, Then 유효성 검사 오류가 반환된다.
8. Given `name` 길이가 0이거나 100자를 초과하면, Then 유효성 검사 오류가 반환된다.
## Impacted Area
- Docs: `docs/runtime-flows.md` (CLI 명령 흐름 변경 반영 — REQ-019에서 처리)
- Modules:
- `app/cli/commands/portfolio.py`: `create`, `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update` 명령의 Argument/Option 전환
- `app/domain/services.py`: `(user_id, name)` → UUID 해석 로직 추가
- `app/repositories/postgres.py`: `get_portfolio_by_name_and_user()` 단일 쿼리 구현 (REQ-016과 공유)
- Tests:
- `tests/test_cli.py`: 소유권 검증 케이스, 오류 메시지 검증, name 유효성 검사, name 중복 변경 거부 테스트
## Notes
- `(owner_user_id, name)` → UUID 조회는 REQ-016에서 추가하는 `get_portfolio_by_name_and_user()` 메서드를 사용한다.
- `name` 대소문자 구분 있음 — `TV`와 `tv`는 다른 이름으로 처리.
- Source Discovery: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution (FR-2, FR-3, FR-3a, NFR-2)
## Change Log
### CHG-20260518-01
- Date: 2026-05-18
- Author: repository-maintainers
#### Change Summary
- `get_portfolio_by_name_and_user()` 미구현 결함 수정 — PostgresPortfolioRepository에 메서드가 존재하지 않아 실 DB 환경에서 모든 name+user 식별 경로가 AttributeError로 실패하던 결함을 수정. Status Implemented 유지.
#### Intent
- REQ-017 AC-2~4(name+user 조합으로 portfolio 조회)를 구현한 CLI 코드와 FakeRepo는 완성되어 있었으나, PostgresPortfolioRepository에 `get_portfolio_by_name_and_user()`가 누락되어 실 DB 환경에서 AttributeError가 발생했다. 결함을 수정해 Implemented 근거를 복원한다.
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules:
- `app/repositories/postgres.py`: `get_portfolio_by_name_and_user(owner_user_id, name)` 추가 (`_SELECT_PORTFOLIO WHERE owner_user_id = %s AND name = %s`)
- `app/repositories/portfolio_repository.py`: Protocol에 `get_portfolio_by_name_and_user` 선언 추가
- Tests: `tests/test_portfolio_uuid.py` 14 passed (기존 FakeRepo 기반 테스트는 영향 없음)
#### Delivery Trace Delta
- Batches: BAT-006 (release-candidate) — 결함 수정이 BAT-006 delivery scope에 포함
- Releases: REL-006 (draft) — 배포 전 결함 수정 완료
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current (Implemented)
- Follow-up Action: 없음 (실 DB 통합 테스트로 AC-2~4 재확인 권장)
#### Validation Plan
- `pfhub portfolio status <name> --user <user>`, `pfhub portfolio buy <name> --user <user>` 등 name+user 식별 명령이 실 DB 환경에서 정상 응답하는지 확인
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- 최초 생성 — dcy-004 draft-req에 의해 생성
#### Intent
- dcy-004 Discovery의 FR-2(portfolio create 명령 name+user 전환), FR-3(CLI 뮤테이션 명령 식별 방식 전환), FR-3a(name mutable + 중복 변경 거부), NFR-2(단일 DB 쿼리, 오류 메시지) 반영
#### Acceptance Criteria Delta
- Added:
- 1~8 (최초 생성)
#### Impacted Area Delta
- Docs:
- docs/runtime-flows.md: CLI 명령 흐름 변경 (REQ-019에서 처리)
- Modules:
- app/cli/commands/portfolio.py: 명령 Argument/Option 전면 교체
- app/domain/services.py: (user_id, name) → UUID 해석 추가
- app/repositories/postgres.py: get_portfolio_by_name_and_user() 추가
- Tests:
- tests/test_cli.py: 소유권 검증, 오류 메시지, name 유효성 검사 케이스 추가
#### Delivery Trace Delta
- Batches: none (batch 미결정)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req → suggest-batch-reqs → draft-batch
#### Validation Plan
- pytest: 소유권 검증 케이스, 오류 메시지 검증, name 유효성 검사, name 중복 변경 거부, alice/bob 독립 portfolio 생성 검증
### CHG-20260511-02
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Approved 전환 — confirm-req 게이트 통과
#### Intent
- Delivery 입력으로 사용할 수 있도록 REQ-017을 승인 상태로 승격한다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs:
- none
- Modules:
- none
- Tests:
- none
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs로 batch grouping 검토
#### Validation Plan
- confirm-req 게이트 재평가 결과: 필수 메타데이터, 구체적 Acceptance Criteria, Impacted Area, Change Log, blocker 없음 확인
### CHG-20260511-03
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-006 verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-006 verification.md의 REQ-017 AC-1~8 모두 test evidence 포함. Blocking Issues 없음. 구현 완료 상태로 전환한다.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-006 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-006)
#### Validation Plan
- BAT-006 verification.md AC-1~8 모두 test evidence 연결 확인 (tests/test_portfolio_uuid.py, tests/test_cli.py 전체 통과)
@@ -1,152 +0,0 @@
# req-018: HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- HTTP API의 portfolio 관련 path parameter를 기존 문자열 `portfolio_id`에서 UUID 기반으로 전환해 REQ-016의 스키마 변경과 일관성을 맞춘다.
- `GET /v1/assets` 응답의 `asset_id` 필드를 UUID로 반환해 trex-basic 계약을 갱신하고, dcy-002 A-3을 철회한다.
## Requirement
- `app/api/portfolios.py`의 portfolio 관련 path parameter를 UUID 기반으로 변경한다.
- `GET /v1/assets` 응답의 `asset_id` 필드는 `portfolios.portfolio_id`(UUID)를 반환한다.
- `app/api/v1_assets.py`의 `AssetItem` 직렬화에서 `asset_id`는 `portfolio_id`(UUID) 값을 사용한다.
- `POST /v1/assets/{asset_id}/ledger/trades` 경로의 `asset_id` path parameter는 UUID를 받아 `repo.get(asset_id)`로 portfolio를 조회한다.
- dcy-002 문서의 A-3("asset_id는 portfolio_id를 그대로 사용") 조항은 이번 REQ 구현으로 공식 철회된다. `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md`에 철회 주석을 추가한다.
## Acceptance Criteria
1. Given `GET /v1/assets`를 유효한 service-token으로 호출하면, When 응답 JSON의 `assets[].asset_id` 필드를 확인하면, Then 값이 UUID 형식(`re.match(r'^[0-9a-f-]{36}$', ...)`)이다.
2. Given UUID를 `asset_id`로 사용해 `POST /v1/assets/{asset_id}/ledger/trades`를 호출하면, When 해당 UUID의 portfolio가 존재하면, Then HTTP 201(`status="new"`) 또는 HTTP 200(`status="duplicate"`)으로 정상 처리된다.
3. Given UUID를 `asset_id`로 사용해 `POST /v1/assets/{asset_id}/ledger/trades`를 호출하면, When 해당 UUID의 portfolio가 존재하지 않으면, Then HTTP 404가 반환된다.
4. Given portfolio 관련 HTTP API path(`/portfolios/{portfolio_id}`)를 UUID로 호출하면, When 해당 UUID의 portfolio가 존재하면, Then 정상 응답이 반환된다.
5. Given `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md`를 확인하면, Then A-3 항목에 이번 REQ 구현으로 철회되었다는 주석이 추가되어 있다.
## Impacted Area
- Docs:
- `docs/interface-contract.md` (GET /v1/assets asset_id 변경 반영 — REQ-019에서 처리)
- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` (A-3 철회 주석 추가)
- Modules:
- `app/api/portfolios.py`: path parameter UUID 기반 전환
- `app/api/v1_assets.py`: asset_id 직렬화 UUID 사용
- `app/schemas/portfolio.py`, `app/schemas/v1.py`: asset_id/portfolio_id 필드 타입 문서화
- Tests:
- `tests/test_app.py` 또는 `tests/test_v1.py`: GET /v1/assets asset_id UUID 형식 검증, POST /v1/assets/{asset_id}/ledger/trades UUID 호출 정상 동작 확인
## Notes
- trex-basic 측 코드 변경은 이번 REQ 범위 밖. portfolio-hub API 변경만 담당.
- trex-basic과 portfolio-hub는 동시에 배포한다(NFR-3 — 동시 배포 계획 기록은 REQ-019에서 `docs/interface-contract.md`에 처리).
- dcy-002 A-3 철회 주석 추가는 이 REQ 구현 시 함께 수행한다.
- `GET /v1/assets` flow의 `asset_id` 직렬화: `docs/runtime-flows.md` Flow `v1 API — GET /v1/assets` 갱신 필요 (REQ-019에서 처리).
- Source Discovery: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution (FR-4)
## Change Log
### CHG-20260511-01
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- 최초 생성 — dcy-004 draft-req에 의해 생성
#### Intent
- dcy-004 Discovery의 FR-4(HTTP API path parameter UUID 전환, GET /v1/assets asset_id UUID 반환, dcy-002 A-3 철회) 반영
#### Acceptance Criteria Delta
- Added:
- 1~5 (최초 생성)
#### Impacted Area Delta
- Docs:
- docs/interface-contract.md: GET /v1/assets asset_id 변경 반영 (REQ-019에서 처리)
- docs/discovery/dcy-002_*: A-3 철회 주석 추가
- Modules:
- app/api/portfolios.py: path parameter UUID 전환
- app/api/v1_assets.py: asset_id UUID 직렬화
- app/schemas/portfolio.py, app/schemas/v1.py: 필드 타입 변경
- Tests:
- tests/test_app.py 또는 tests/test_v1.py: GET /v1/assets asset_id UUID 형식 검증
#### Delivery Trace Delta
- Batches: none (batch 미결정)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req → suggest-batch-reqs → draft-batch
#### Validation Plan
- pytest: GET /v1/assets asset_id UUID 형식 검증, POST /v1/assets/{asset_id}/ledger/trades UUID 호출 동작 검증, HTTP 404 반환 확인
### CHG-20260511-02
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Approved 전환 — confirm-req 게이트 통과
#### Intent
- Delivery 입력으로 사용할 수 있도록 REQ-018을 승인 상태로 승격한다.
#### Acceptance Criteria Delta
- Added:
- none
- Updated:
- none
- Removed:
- none
#### Impacted Area Delta
- Docs:
- none
- Modules:
- none
- Tests:
- none
#### Delivery Trace Delta
- Batches:
- none
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs로 batch grouping 검토
#### Validation Plan
- confirm-req 게이트 재평가 결과: 필수 메타데이터, 구체적 Acceptance Criteria, Impacted Area, Change Log, blocker 없음 확인
### CHG-20260511-03
- Date: 2026-05-11
- Author: repository-maintainers
#### Change Summary
- Implemented 전환 — BAT-006 verification passed, confirm-req-implemented 게이트 통과
#### Intent
- BAT-006 verification.md의 REQ-018 AC-1~5 모두 evidence 포함 (AC-1~4: 구현 코드 검증, AC-5: dcy-002 A-3 철회 주석 문서 확인). Blocking Issues 없음.
#### Acceptance Criteria Delta
- none
#### Impacted Area Delta
- none
#### Delivery Trace Delta
- Batches: BAT-006 (release-candidate)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: draft-release (BAT-006); trex-basic 동시 배포 조정 필요
#### Validation Plan
- BAT-006 verification.md AC-1~5 evidence 연결 확인; dcy-002 A-3 철회 주석 직접 확인 완료
@@ -1,143 +0,0 @@
# REQ-023: Daemon Registry 관리 경로
- Status: Approved
- Type: Interface
- Priority: High
- Owner: repository-maintainers
## Intent
- service-token issue/rotate가 기존 daemon에만 binding할 수 있으려면, daemon을 사전에 생성·수정할 수 있는 별도 관리 경로(daemon registry CRUD)가 필요하다.
- daemon provisioning은 service-token issuance와 분리된 경로에서 수행되어야 하며, REQ-005(issuance binding)와 REQ-012(rotate daemon_id 유지)의 전제 조건이 된다.
## Requirement
- 시스템은 daemon을 생성·수정할 수 있는 별도 관리 경로를 제공해야 한다.
- daemon은 owner user 내에서 unique한 `daemon_slug`(lowercase kebab-case)와 `daemon_display_name`(non-empty human-readable label)을 가진다.
- 동일 owner user 내에서 중복 `daemon_slug`로 생성 시 명시적으로 거부한다.
- daemon list 조회는 owner user 범위로 제한된다.
## Acceptance Criteria
1. Given admin context에서 유효한 `daemon_slug`와 `daemon_display_name`으로 daemon create를 호출할 때, Then 새 daemon entry가 registry에 생성되고 `daemon_id`가 반환된다.
2. Given 기존 daemon에 대해 `daemon_display_name` 수정을 요청할 때, Then daemon entry가 갱신된다.
3. Given 동일 owner user 내에 이미 존재하는 `daemon_slug`로 daemon create를 요청할 때, Then 명시적 오류로 거부되고 중복 entry가 생성되지 않는다.
4. Given daemon list 조회를 요청할 때, Then 인증된 user 소유의 daemon만 반환된다.
5. Given `pfhub portfolio update <name> --user <username> --daemon-id <daemon_id>`를 실행할 때, Then 해당 portfolio의 `managed_by_daemon_id`가 지정한 daemon_id로 갱신된다.
6. Given `pfhub portfolio list --user <username>`을 실행할 때, Then `managed_by_daemon_id`가 설정된 portfolio에는 해당 daemon_id가 출력에 포함되고, 미설정 portfolio에는 해당 항목이 출력되지 않는다.
## Impacted Area
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`
- Modules: `app/domain/models.py`, `app/repositories/postgres.py`, `app/api/auth_routes.py` 또는 별도 daemon management route, `app/cli/commands/` (daemon subcommand 신규)
- Modules (AC-5~6 추가): `app/cli/commands/portfolio.py` (`update` 명령 `--daemon-id` 옵션), `app/schemas/portfolio.py` (`UpdatePortfolioRequest.managed_by_daemon_id`, `PortfolioSummary.managed_by_daemon_id`), `app/services/portfolio_service.py` (`_to_portfolio_summary` 필드 전달), `app/cli/core/utils.py` (`print_portfolio_list` daemon 표시)
- Tests: 신규 `tests/test_daemon_registry.py`
## Notes
- service-token issuance의 unknown daemon 거부(REQ-005 AC-6), rotate daemon_id 유지 불변식(REQ-012 CHG-20260513-01)은 이 REQ의 daemon registry가 사전 구축된 상태를 전제한다.
- daemon registry CRUD의 구체적인 transport 형태(HTTP endpoint vs CLI subcommand)는 구현 시 결정한다.
- canonical 운영 표기: `<username>/<daemon_slug>`
## Change Log
### CHG-20260518-01
- Date: 2026-05-18
- Author: repository-maintainers
#### Change Summary
- AC-5(`portfolio update --daemon-id`), AC-6(`portfolio list` daemon-id 표시) 추가 및 Impacted Area 보강. 구현 완료. Status Approved 유지.
#### Intent
- daemon provisioning runbook에서 "asset → daemon 배정" 단계가 CLI로 수행 가능해야 한다. 기존 AC-1~4는 daemon registry CRUD만 다루며 portfolio-daemon 배정 경로가 누락되어 있었다. 운영팀 요청(2026-05-18)에 따라 `portfolio update --daemon-id` CLI 옵션과 `portfolio list` 결과의 daemon-id 표시를 REQ 범위에 추가한다.
#### Acceptance Criteria Delta
- Added:
- AC-5: `pfhub portfolio update <name> --user <username> --daemon-id <daemon_id>`로 `managed_by_daemon_id` 갱신
- AC-6: `pfhub portfolio list --user <username>` 결과에 `managed_by_daemon_id` 조건부 표시
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Modules:
- `app/cli/commands/portfolio.py`: `update` 명령에 `--daemon-id` 옵션 추가
- `app/schemas/portfolio.py`: `UpdatePortfolioRequest.managed_by_daemon_id`, `PortfolioSummary.managed_by_daemon_id` 필드 추가
- `app/services/portfolio_service.py`: `_to_portfolio_summary`에 `managed_by_daemon_id` 전달 추가
- `app/repositories/postgres.py`: `update_portfolio` allowed dict에 `managed_by_daemon_id` 추가
- `app/cli/core/utils.py`: `print_portfolio_list`에 daemon 줄 조건부 출력 추가
#### Delivery Trace Delta
- Batches: BAT-010 (release-candidate) — AC-5~6 구현이 BAT-010 delivery scope에 포함
- Releases: REL-006 (draft)
#### Revalidation Impact
- Reverification Needed: yes (AC-5~6 신규이므로 검증 증거 추가 필요)
- Existing Implementation Invalidated: no (AC-1~4 기존 구현 유효)
- Status Recommendation: keep-current (Approved)
- Follow-up Action: BAT-010 verification.md에 AC-5~6 검증 증거 추가 후 Implemented 전환 가능
#### Validation Plan
- `pfhub portfolio update <name> --user <user> --daemon-id <id> --force` 실행 후 DB `portfolios.managed_by_daemon_id` 값 확인
- `pfhub portfolio list --user <user>` 출력에서 daemon-id 유무 항목별 표시 확인
### CHG-20260513-02
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- Status: Proposed → Approved (confirm-req 게이트 통과)
#### Intent
- 필수 필드, AC, blocker 없음 확인 후 승인 전환
#### Acceptance Criteria Delta
- Added: 없음
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: 없음
- Modules: 없음
- Tests: 없음
#### Delivery Trace Delta
- Batches: 미정
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: 없음
#### Validation Plan
- 없음 (상태 전환만)
### CHG-20260513-01
- Date: 2026-05-13
- Author: repository-maintainers
#### Change Summary
- dcy-006 FR-1의 daemon provisioning 관리 경로 부분만 추출해 신규 생성 (issuance binding·rotate invariant·fail-closed는 REQ-005/012/006/008에 이미 반영)
#### Intent
- service-token issuance(REQ-005)와 rotate(REQ-012)의 전제가 되는 daemon registry CRUD 경로를 별도 REQ로 정의
#### Acceptance Criteria Delta
- Added:
- AC-1~4 전체 신규 추가
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`
- Modules: daemon registry route, CLI subcommand
- Tests: `tests/test_daemon_registry.py`
#### Delivery Trace Delta
- Batches: 미정
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: 없음 (신규 REQ)
#### Validation Plan
- pytest로 daemon create/duplicate reject/update/list 시나리오 통과 확인
@@ -1,69 +0,0 @@
# REQ-029: CLI `portfolio list` 출력 — 테두리 없는 리스트 형식
- Status: Approved
- Type: Interface
- Priority: Low
- Owner: repository-maintainers
## Intent
- `pfhub portfolio list` 기본 출력을 Rich Table(테두리 포함) 대신 항목별 텍스트 블록으로 변경해 터미널 폭에 구애받지 않는 가독성을 확보한다.
- JSON 출력(`--format json`)은 기존과 동일하게 유지한다.
## Requirement
- `pfhub portfolio list --user <username>` 명령의 기본 출력(text 형식)은 테두리·구분선 없는 리스트 형태로 각 portfolio를 표시해야 한다.
- 각 항목은 3줄 블록으로 구성한다: (1) name/symbol/status/broker [tags], (2) cash/qty/price/buffer/total 수치, (3) portfolio_id.
- `managed_by_daemon_id`가 설정된 항목은 (3) 다음 줄에 daemon_id를 추가로 표시하고, 미설정 항목은 해당 줄을 생략한다.
- 항목 간에 빈 줄 한 줄로 구분한다.
- `--format json` 옵션 사용 시 기존 JSON 응답을 그대로 반환한다.
## Acceptance Criteria
1. Given `pfhub portfolio list --user <username>` 실행 시, Then Rich Table 형식(헤더·테두리)이 아닌 텍스트 블록 리스트가 출력된다.
2. 각 portfolio 항목은 name, symbol, status, broker 정보와 cash/qty/price/buffer/total 수치, portfolio_id를 포함한다.
3. Given `managed_by_daemon_id`가 설정된 portfolio가 있을 때, Then 해당 항목에 daemon_id 줄이 추가로 표시된다.
4. Given `pfhub portfolio list --user <username> --format json` 실행 시, Then JSON 배열이 반환되며 텍스트 블록 출력이 표시되지 않는다.
## Impacted Area
- Docs: 없음
- Modules:
- `app/cli/core/utils.py`: `print_portfolio_table` 제거, `print_portfolio_list` 신규 구현, `render_output`에서 `print_portfolio_list` 호출
- Tests: 기존 `tests/test_portfolio_uuid.py` 출력 내용 미검증이므로 별도 영향 없음
## Notes
- 이 변경은 CLI 출력 형식만 변경하며 API, 데이터 모델, DB 스키마에는 영향을 주지 않는다.
- REQ-023 AC-6(managed_by_daemon_id 표시)와 구현을 공유한다.
## Change Log
### CHG-20260518-01
- Date: 2026-05-18
- Author: repository-maintainers
#### Change Summary
- 최초 생성 및 Approved 전환 — 운영팀 요청(2026-05-18)에 따라 `portfolio list` 출력 형식을 테두리 없는 리스트 형태로 변경 완료. 구현 및 사용자 확인 완료.
#### Intent
- Rich Table 형식은 터미널 폭이 좁거나 항목 수가 많을 때 가독성이 떨어진다. 텍스트 블록 리스트는 항목별 스캔이 쉽고 터미널 폭 의존성이 없다.
#### Acceptance Criteria Delta
- Added: AC-1~4 (최초 생성)
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Modules:
- `app/cli/core/utils.py`: `print_portfolio_table` → `print_portfolio_list` 교체, REQ-023 AC-6 daemon 줄 출력 포함
#### Delivery Trace Delta
- Batches: 미정 (minor-change fast path로 batch-lite 생성 예정)
- Releases: 미정
#### Revalidation Impact
- Reverification Needed: no (신규 REQ, 기존 구현 무효화 없음)
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: batch-lite 생성 → 검증 → confirm-req-implemented
#### Validation Plan
- `pfhub portfolio list --user <username>` 실행 결과에 Rich Table 헤더·테두리가 없는지 확인
- daemon-id 있는 항목과 없는 항목 혼재 시 조건부 표시 확인
- `--format json` 옵션 시 JSON 반환 확인