From 1082f041a1d0f4554db18c9a51b32c6a0f644d09 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Tue, 19 May 2026 13:54:25 +0000 Subject: [PATCH] chore: restructure SRS for asset catalog and tag model redesign (dcy-001/002, REQ-001~008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/cli/core/utils.py | 26 +- .../design.md | 95 ----- .../implementation.md | 193 ---------- .../index.md | 67 ---- .../planning.md | 83 ---- .../verification.md | 361 ------------------ .../index.md | 42 -- .../planning.md | 59 --- .../verification.md | 90 ----- .../implementation.md | 58 --- .../index.md | 42 -- .../planning.md | 89 ----- .../verification.md | 72 ---- .../implementation.md | 59 --- .../index.md | 42 -- .../planning.md | 50 --- .../verification.md | 65 ---- .../implementation.md | 53 --- .../index.md | 45 --- .../planning.md | 73 ---- .../verification.md | 61 --- .../design.md | 92 ----- .../implementation.md | 63 --- .../index.md | 36 -- .../planning.md | 71 ---- .../verification.md | 94 ----- .../design.md | 115 ------ .../implementation.md | 64 ---- .../index.md | 52 --- .../planning.md | 72 ---- .../verification.md | 59 --- .../design.md | 81 ---- .../implementation.md | 68 ---- .../index.md | 41 -- .../planning.md | 70 ---- .../verification.md | 59 --- .../implementation.md | 38 -- .../index.md | 40 -- .../planning.md | 44 --- .../verification.md | 52 --- .../design.md | 55 --- .../implementation.md | 72 ---- .../index.md | 55 --- .../planning.md | 62 --- .../verification.md | 132 ------- docs/batches/index.md | 20 - ...ion-validation-and-operations-readiness.md | 224 ----------- ..._asset-catalog-and-user-portfolio-model.md | 252 ++++++++++++ ...260502_trex-basic-consumer-api-contract.md | 240 ------------ ..._tag-ownership-and-applicability-policy.md | 239 ++++++++++++ ...3_20260502_portfolio-list-user-required.md | 191 --------- ...gration-and-user-scoped-name-resolution.md | 240 ------------ ...ion-admin-bootstrap-and-operator-access.md | 236 ------------ ...n-id-and-single-daemon-asset-assignment.md | 238 ------------ docs/discovery/index.md | 8 +- docs/releases/index.md | 16 - ...001_20260502_trex-basic-v1-api-contract.md | 142 ------- ...ator-cli-tooling-and-baseline-docs-sync.md | 118 ------ ...512_cli-user-scoping-and-db-ops-runbook.md | 127 ------ ...olio-uuid-migration-and-api-uuid-switch.md | 134 ------- ...-operator-runbook-and-credential-matrix.md | 83 ---- ...06_20260513_daemon-model-implementation.md | 134 ------- docs/srs/Data/req-001_tags-master-table.md | 74 ++++ .../srs/Data/req-002_assets-tag-ids-column.md | 73 ++++ ...lios-tag-ids-and-portfolio-tags-removal.md | 73 ++++ ...n-bootstrap-and-operator-access-runbook.md | 109 ------ ...operations-runbook-and-baseline-updates.md | 148 ------- .../req-008_tag-baseline-docs-update.md | 73 ++++ ...ine-docs-update-for-trex-basic-contract.md | 176 --------- ...019_portfolio-uuid-baseline-docs-update.md | 145 ------- ...-matrix-and-token-provenance-procedures.md | 154 -------- ...g-access-and-release-evidence-recording.md | 114 ------ ...q-028_daemon-model-baseline-docs-update.md | 98 ----- ...eq-004_health-response-schema-extension.md | 124 ------ docs/srs/Interface/req-004_tags-http-api.md | 83 ++++ ...q-005_service-token-runtime-id-issuance.md | 215 ----------- .../req-005_tag-apply-remove-http-api.md | 83 ++++ .../Interface/req-006_v1-assets-endpoint.md | 260 ------------- docs/srs/Interface/req-007_pfhub-tag-cli.md | 82 ++++ .../req-007_v1-user-info-endpoint.md | 177 --------- .../req-008_v1-ledger-trades-endpoint.md | 213 ----------- .../req-012_service-token-rotate-cli.md | 211 ---------- .../req-013_portfolio-list-user-required.md | 143 ------- .../req-014_portfolio-create-user-required.md | 78 ---- .../req-015_broker-credential-cli.md | 143 ------- ..._portfolio-cli-name-user-identification.md | 186 --------- ...-018_portfolio-http-api-uuid-path-param.md | 152 -------- ...gistry-and-service-token-daemon-binding.md | 143 ------- ...eq-029_portfolio-list-borderless-output.md | 69 ---- ...eq-002_alembic-runtime-schema-ownership.md | 147 ------- .../req-006_portfolio-tags-data-migration.md | 79 ++++ .../req-009_v1-api-db-schema-migration.md | 184 --------- ...eq-016_portfolios-uuid-schema-migration.md | 157 -------- ...10_credential-payload-fernet-encryption.md | 128 ------- .../req-001_local-db-validation-scenarios.md | 147 ------- docs/srs/index.md | 41 +- 96 files changed, 1133 insertions(+), 9303 deletions(-) delete mode 100644 docs/batches/bat-001_20260502_trex-basic-v1-api-contract/design.md delete mode 100644 docs/batches/bat-001_20260502_trex-basic-v1-api-contract/implementation.md delete mode 100644 docs/batches/bat-001_20260502_trex-basic-v1-api-contract/index.md delete mode 100644 docs/batches/bat-001_20260502_trex-basic-v1-api-contract/planning.md delete mode 100644 docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md delete mode 100644 docs/batches/bat-002_20260502_service-token-rotate-cli/index.md delete mode 100644 docs/batches/bat-002_20260502_service-token-rotate-cli/planning.md delete mode 100644 docs/batches/bat-002_20260502_service-token-rotate-cli/verification.md delete mode 100644 docs/batches/bat-003_20260502_portfolio-list-user-required/implementation.md delete mode 100644 docs/batches/bat-003_20260502_portfolio-list-user-required/index.md delete mode 100644 docs/batches/bat-003_20260502_portfolio-list-user-required/planning.md delete mode 100644 docs/batches/bat-003_20260502_portfolio-list-user-required/verification.md delete mode 100644 docs/batches/bat-004_20260502_portfolio-create-user-required/implementation.md delete mode 100644 docs/batches/bat-004_20260502_portfolio-create-user-required/index.md delete mode 100644 docs/batches/bat-004_20260502_portfolio-create-user-required/planning.md delete mode 100644 docs/batches/bat-004_20260502_portfolio-create-user-required/verification.md delete mode 100644 docs/batches/bat-005_20260503_broker-credential-cli/implementation.md delete mode 100644 docs/batches/bat-005_20260503_broker-credential-cli/index.md delete mode 100644 docs/batches/bat-005_20260503_broker-credential-cli/planning.md delete mode 100644 docs/batches/bat-005_20260503_broker-credential-cli/verification.md delete mode 100644 docs/batches/bat-006_20260511_portfolio-uuid-migration/design.md delete mode 100644 docs/batches/bat-006_20260511_portfolio-uuid-migration/implementation.md delete mode 100644 docs/batches/bat-006_20260511_portfolio-uuid-migration/index.md delete mode 100644 docs/batches/bat-006_20260511_portfolio-uuid-migration/planning.md delete mode 100644 docs/batches/bat-006_20260511_portfolio-uuid-migration/verification.md delete mode 100644 docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/design.md delete mode 100644 docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/implementation.md delete mode 100644 docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/index.md delete mode 100644 docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/planning.md delete mode 100644 docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/verification.md delete mode 100644 docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/design.md delete mode 100644 docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/implementation.md delete mode 100644 docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/index.md delete mode 100644 docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/planning.md delete mode 100644 docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/verification.md delete mode 100644 docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/implementation.md delete mode 100644 docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/index.md delete mode 100644 docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/planning.md delete mode 100644 docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/verification.md delete mode 100644 docs/batches/bat-010_20260513_daemon-model-implementation/design.md delete mode 100644 docs/batches/bat-010_20260513_daemon-model-implementation/implementation.md delete mode 100644 docs/batches/bat-010_20260513_daemon-model-implementation/index.md delete mode 100644 docs/batches/bat-010_20260513_daemon-model-implementation/planning.md delete mode 100644 docs/batches/bat-010_20260513_daemon-model-implementation/verification.md delete mode 100644 docs/batches/index.md delete mode 100644 docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md create mode 100644 docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md delete mode 100644 docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md create mode 100644 docs/discovery/dcy-002_20260519_tag-ownership-and-applicability-policy.md delete mode 100644 docs/discovery/dcy-003_20260502_portfolio-list-user-required.md delete mode 100644 docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md delete mode 100644 docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md delete mode 100644 docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md delete mode 100644 docs/releases/index.md delete mode 100644 docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md delete mode 100644 docs/releases/rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync.md delete mode 100644 docs/releases/rel-003_20260512_cli-user-scoping-and-db-ops-runbook.md delete mode 100644 docs/releases/rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch.md delete mode 100644 docs/releases/rel-005_20260512_production-operator-runbook-and-credential-matrix.md delete mode 100644 docs/releases/rel-006_20260513_daemon-model-implementation.md create mode 100644 docs/srs/Data/req-001_tags-master-table.md create mode 100644 docs/srs/Data/req-002_assets-tag-ids-column.md create mode 100644 docs/srs/Data/req-003_portfolios-tag-ids-and-portfolio-tags-removal.md delete mode 100644 docs/srs/Deployment/req-020_production-admin-bootstrap-and-operator-access-runbook.md delete mode 100644 docs/srs/Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md create mode 100644 docs/srs/Documentation/req-008_tag-baseline-docs-update.md delete mode 100644 docs/srs/Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md delete mode 100644 docs/srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md delete mode 100644 docs/srs/Documentation/req-021_release-credential-matrix-and-token-provenance-procedures.md delete mode 100644 docs/srs/Documentation/req-022_operator-log-access-and-release-evidence-recording.md delete mode 100644 docs/srs/Documentation/req-028_daemon-model-baseline-docs-update.md delete mode 100644 docs/srs/Interface/req-004_health-response-schema-extension.md create mode 100644 docs/srs/Interface/req-004_tags-http-api.md delete mode 100644 docs/srs/Interface/req-005_service-token-runtime-id-issuance.md create mode 100644 docs/srs/Interface/req-005_tag-apply-remove-http-api.md delete mode 100644 docs/srs/Interface/req-006_v1-assets-endpoint.md create mode 100644 docs/srs/Interface/req-007_pfhub-tag-cli.md delete mode 100644 docs/srs/Interface/req-007_v1-user-info-endpoint.md delete mode 100644 docs/srs/Interface/req-008_v1-ledger-trades-endpoint.md delete mode 100644 docs/srs/Interface/req-012_service-token-rotate-cli.md delete mode 100644 docs/srs/Interface/req-013_portfolio-list-user-required.md delete mode 100644 docs/srs/Interface/req-014_portfolio-create-user-required.md delete mode 100644 docs/srs/Interface/req-015_broker-credential-cli.md delete mode 100644 docs/srs/Interface/req-017_portfolio-cli-name-user-identification.md delete mode 100644 docs/srs/Interface/req-018_portfolio-http-api-uuid-path-param.md delete mode 100644 docs/srs/Interface/req-023_daemon-registry-and-service-token-daemon-binding.md delete mode 100644 docs/srs/Interface/req-029_portfolio-list-borderless-output.md delete mode 100644 docs/srs/Migration/req-002_alembic-runtime-schema-ownership.md create mode 100644 docs/srs/Migration/req-006_portfolio-tags-data-migration.md delete mode 100644 docs/srs/Migration/req-009_v1-api-db-schema-migration.md delete mode 100644 docs/srs/Migration/req-016_portfolios-uuid-schema-migration.md delete mode 100644 docs/srs/Non-Functional/req-010_credential-payload-fernet-encryption.md delete mode 100644 docs/srs/Testing/req-001_local-db-validation-scenarios.md diff --git a/app/cli/core/utils.py b/app/cli/core/utils.py index 5e2d698..d369c83 100644 --- a/app/cli/core/utils.py +++ b/app/cli/core/utils.py @@ -121,22 +121,16 @@ def print_portfolio_detail(console: Console, detail: PortfolioDetailResponse) -> console.print(ledger_table) -def print_user_table(console: Console, users: list[UserResponse]) -> None: - table = Table(title="Users", expand=True) - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("Username", no_wrap=True) - table.add_column("Email") - table.add_column("Created At", no_wrap=True) - table.add_column("Updated At", no_wrap=True) - for user in users: - table.add_row( - user.id, - user.username, - user.email or "N/A", - user.created_at.isoformat(timespec="seconds") if user.created_at else "N/A", - user.updated_at.isoformat(timespec="seconds") if user.updated_at else "N/A", +def print_user_list(console: Console, users: list[UserResponse]) -> None: + for i, user in enumerate(users): + if i: + console.print() + console.print(f"[bold]{user.username}[/bold] [dim]{user.id}[/dim]") + console.print(f" email {user.email or 'N/A'}") + console.print( + f" created {user.created_at.isoformat(timespec='seconds') if user.created_at else 'N/A'}" + f" updated {user.updated_at.isoformat(timespec='seconds') if user.updated_at else 'N/A'}" ) - console.print(table) def print_user_detail(console: Console, user: UserResponse) -> None: @@ -274,7 +268,7 @@ def render_output(console: Console, output_format: str, payload: Any, *, empty_m print_portfolio_list(console, payload) return if all(isinstance(item, UserResponse) for item in payload): - print_user_table(console, payload) + print_user_list(console, payload) return if all(isinstance(item, CommentResponse) for item in payload): print_comment_table(console, payload) diff --git a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/design.md b/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/design.md deleted file mode 100644 index f26e8e9..0000000 --- a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/design.md +++ /dev/null @@ -1,95 +0,0 @@ -# BAT-001 Design - -- Status: draft -- Batch: BAT-001 - -## Architecture Summary - -trex-basic Consumer-Driven Contract를 충족하기 위해 4개 계층에 걸쳐 변경한다. - -1. **DB 레이어** (REQ-009): Alembic migration 1개 추가로 4개 스키마 변경 -2. **보안 레이어** (REQ-010): `app/core/encryption.py` 신규 — Fernet 암복호화 유틸리티, `app/core/config.py` startup validation -3. **인증 확장** (REQ-005): service-token 발급 시 UUID v4 runtime_id 동시 생성 -4. **v1 API 레이어** (REQ-004, 006, 007, 008): `/v1` prefix APIRouter 신규, 기존 `/health` 응답 확장 - -## Changed Areas - -- Docs: - - docs/project-structure.md (신규 모듈 app/core/encryption.py, app/api/v1_*.py 반영) - - docs/runtime-flows.md (v1 API 요청 흐름, runtime_id 발급 흐름 추가) -- Modules: - - app/core/encryption.py (신규) - - app/core/config.py (CREDENTIAL_ENCRYPTION_KEY 필드 + startup validation) - - app/domain/models.py (AuthToken.runtime_id, BrokerCredential, LedgerTrade 추가) - - app/api/health.py (HealthResponse 스키마 적용, checked_at/service/version 필드 추가) - - app/api/auth_routes.py (service-token 발급 응답에 runtime_id 포함) - - app/api/v1_assets.py (신규) - - app/api/v1_user_info.py (신규) - - app/api/v1_ledger.py (신규) - - app/main.py (v1 router include 추가) - - app/cli/commands/service_tokens.py (runtime_id 출력 추가) - - app/services/auth_service.py (runtime_id 생성·저장 로직) - - app/schemas/ (HealthResponse, v1 Assets/UserInfo/Ledger 스키마 추가) - - app/repositories/postgres.py (broker_credentials, ledger_trades, runtime_id 쿼리 추가) - - migrations/versions/ (신규 migration 파일) -- Interfaces: - - GET /health: 응답 필드 추가 (service, version, checked_at) — 기존 status 필드 유지로 하위 호환 - - POST /auth/service-tokens (또는 동등 경로): 응답에 runtime_id 추가 - - GET /v1/assets: 신규 - - GET /v1/user_info: 신규 - - POST /v1/assets/{asset_id}/ledger/trades: 신규 - -## Key Decisions - -1. **v1 라우터 구조**: `app/api/v1_*.py` 파일 3개를 각각 별도 모듈로 작성하고 `app/main.py`에서 `prefix="/v1"`로 일괄 등록한다. 기존 `/portfolios`, `/auth`, `/users` 라우터와 prefix가 겹치지 않는다. - -2. **R-4 처리 (runtime_id 없는 구형 service-token)**: GET /v1/user_info 호출 시 해당 토큰에 runtime_id가 없으면 **HTTP 404** (`detail: "runtime_id not assigned to this token"`)를 반환한다. 즉시 생성 방식은 채택하지 않는다 — 기존 토큰 소유자에게 재발급을 요구하는 것이 명시적이고 안전하다. - -3. **disabled_reason**: `users.is_active = false`인 경우 고정값 `"account_inactive"`를 반환한다. `is_active = true`인 경우 `disabled_reason`은 `null`로 반환한다. - -4. **Fernet startup validation 위치**: `app/core/config.py`의 `Settings` 클래스 `model_post_init` 또는 FastAPI `lifespan` 시작 시 수행한다. `CREDENTIAL_ENCRYPTION_KEY`가 없으면 `RuntimeError`를 발생시켜 기동을 중단한다. 테스트 환경에서는 `CREDENTIAL_ENCRYPTION_KEY` 환경변수를 픽스처로 주입한다. - -5. **runtime_id 포맷**: `str(uuid.uuid4())` — 하이픈 포함 UUID v4 문자열 (예: `"550e8400-e29b-41d4-a716-446655440000"`). - -6. **broker_credentials 컬럼 타입**: - - `user_id`: INTEGER NOT NULL (users.user_id FK) - - `broker`: TEXT NOT NULL - - `account_ref`: TEXT NOT NULL - - `encrypted_payload`: TEXT NOT NULL - - `credential_version`: INTEGER NOT NULL DEFAULT 1 - - `last_rotated_at`: TIMESTAMPTZ NOT NULL DEFAULT NOW() - - PRIMARY KEY: `(user_id, broker)` - -7. **ledger_trades 컬럼 타입**: - - `ledger_entry_id`: BIGSERIAL PRIMARY KEY - - `asset_id`: INTEGER NOT NULL (portfolios.portfolio_id 참조, FK 선택적) - - `order_id`: TEXT NOT NULL - - `execution_id`: TEXT NOT NULL - - `side`: TEXT NOT NULL (buy/sell) - - `quantity`: NUMERIC(20, 8) NOT NULL - - `price`: NUMERIC(20, 8) NOT NULL - - `fee_amount`: NUMERIC(20, 8) NOT NULL DEFAULT 0 - - `tax_amount`: NUMERIC(20, 8) NOT NULL DEFAULT 0 - - `executed_at`: TIMESTAMPTZ NOT NULL - - `recorded_at`: TIMESTAMPTZ NOT NULL DEFAULT NOW() - - UNIQUE: `(asset_id, order_id, execution_id)` - -8. **앱 버전**: `app/core/config.py`의 `Settings.app_version` 값을 `GET /health` 응답의 `version` 필드로 사용한다. 현재 값: `"0.1.0"`. - -9. **checked_at 타임존**: KST (UTC+9) ISO 8601 문자열로 반환한다. `datetime.now(timezone(timedelta(hours=9))).isoformat()`. - -## Edge Cases - -- `strategy_params` 파싱 실패(StrategySet.config가 null이거나 유효하지 않은 JSON): `{}` 폴백, 에러 전파 없음. -- `ledger_trades` 중복 삽입 시 DB unique constraint 위반: psycopg `UniqueViolation` 캐치 → 기존 행 조회 → payload 비교 → 동일이면 200+duplicate, 다르면 409. -- `broker_credentials`가 없는 사용자의 GET /v1/user_info: `brokers: []` 반환. -- `CREDENTIAL_ENCRYPTION_KEY` 환경변수 없이 테스트 실행 시: 테스트 픽스처에서 `Fernet.generate_key()`로 임시 키 주입. - -## Architecture Impact - -- both (project-structure + runtime-flows) - -## Reference Doc Update Plan - -- project-structure.md: 신규 모듈(app/core/encryption.py, app/api/v1_assets.py, app/api/v1_user_info.py, app/api/v1_ledger.py) 반영 필요. implementation 완료 후 REQ-011에서 갱신. -- runtime-flows.md: v1 API 요청 처리 흐름(service-token 인증 → 서비스 → DB), runtime_id 발급 흐름 추가 필요. implementation 완료 후 REQ-011에서 갱신. diff --git a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/implementation.md b/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/implementation.md deleted file mode 100644 index db5896f..0000000 --- a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/implementation.md +++ /dev/null @@ -1,193 +0,0 @@ -# BAT-001 Implementation - -- Status: implemented -- Batch: BAT-001 - -## Plan Summary - -design.md에서 확정된 결정 사항을 기반으로 아래 범위를 구현한다. - -1. app/core/encryption.py 신규 — Fernet 암복호화 유틸리티 -2. app/core/config.py — CREDENTIAL_ENCRYPTION_KEY 필드 추가 및 require_credential_encryption_key() 메서드 -3. Alembic migration 신규 — 4개 스키마 변경 (IF NOT EXISTS 패턴) -4. app/domain/models.py — AuthToken.runtime_id, TokenPrincipal.runtime_id, BrokerCredential, LedgerTrade 추가 -5. app/services/auth_service.py — service-token 발급 시 UUID v4 runtime_id 생성·저장 -6. app/cli/commands/service_tokens.py — 응답/출력에 runtime_id 포함 -7. app/core/auth.py — AuthContext.runtime_id 추가, _resolve_auth_context에서 전달 -8. app/api/health.py, app/schemas/v1.py — HealthResponse 스키마 적용 -9. app/api/v1_assets.py — GET /v1/assets 구현 -10. app/api/v1_user_info.py — GET /v1/user_info 구현 (R-4: 404 반환) -11. app/api/v1_ledger.py — POST /v1/assets/{asset_id}/ledger/trades 구현 -12. app/repositories/postgres.py — broker_credentials, ledger_trades, runtime_id 쿼리 추가 -13. app/main.py — /v1 라우터 등록 -14. tests/ — 테스트 파일 작성 및 기존 테스트 확장 - -## Changed Files - -- app/core/encryption.py (신규) -- app/core/config.py (수정: credential_encryption_key 필드, require_credential_encryption_key 메서드, model_validator import) -- app/core/auth.py (수정: AuthContext.runtime_id, _resolve_auth_context runtime_id 전달) -- migrations/versions/20260502_0014_v1_api_schema.py (신규) -- app/domain/models.py (수정: AuthToken.runtime_id, TokenPrincipal.runtime_id, Portfolio.market, BrokerCredential, LedgerTrade) -- app/services/auth_service.py (수정: uuid import, runtime_id 생성·전달) -- app/cli/commands/service_tokens.py (수정: ServiceTokenIssueCliResponse에 runtime_id 포함) -- app/api/health.py (수정: HealthResponse 스키마 적용) -- app/schemas/auth.py (수정: ServiceTokenIssueCliResponse.runtime_id 추가) -- app/schemas/v1.py (신규: AssetsResponse, UserInfoResponse, LedgerTradeRequest/Response) -- app/api/v1_assets.py (신규) -- app/api/v1_user_info.py (신규) -- app/api/v1_ledger.py (신규) -- app/repositories/postgres.py (수정: _SELECT_AUTH_TOKEN runtime_id, _SELECT_PORTFOLIO market, _row_to_auth_token runtime_id, _row_to_portfolio market, create_auth_token runtime_id, get_token_principal runtime_id, broker_credentials CRUD, ledger_trades CRUD, list_portfolios_with_strategies) -- app/main.py (수정: v1 라우터 3개 등록) -- tests/test_app.py (수정: test_healthcheck 응답 필드 검증 업데이트) -- tests/test_auth.py (수정: _AuthRepo.create_auth_token runtime_id 파라미터 추가) -- tests/test_cli.py (수정: FakeRepo.create_auth_token runtime_id 파라미터 추가) -- tests/test_v1.py (신규: health/assets/user_info/ledger 9개 테스트) - -## Execution Log - -1. [x] app/core/encryption.py 신규 구현 -2. [x] app/core/config.py CREDENTIAL_ENCRYPTION_KEY 추가 및 require_credential_encryption_key() 메서드 -3. [x] Alembic migration 파일 작성 (20260502_0014_v1_api_schema.py) -4. [x] app/domain/models.py 모델 필드/클래스 추가 -5. [x] auth_service.py runtime_id 생성·저장 구현 -6. [x] service_tokens.py runtime_id 출력 추가 -7. [x] app/core/auth.py AuthContext.runtime_id 추가 -8. [x] app/api/health.py HealthResponse 스키마 적용 -9. [x] app/api/v1_assets.py 구현 -10. [x] app/api/v1_user_info.py 구현 (runtime_id 없는 구형 토큰 → HTTP 404) -11. [x] app/api/v1_ledger.py 구현 (idempotent) -12. [x] app/repositories/postgres.py 쿼리 추가 -13. [x] app/main.py /v1 라우터 등록 -14. [x] 테스트 작성 및 전체 테스트 스위트 통과 (98 passed, 2026-05-01 blocker 수정 후) - -## Validation - -- [x] pytest 98개 전체 통과 (2026-05-01, blocker B-1~B-12 수정 후) -- [ ] alembic upgrade head 성공 (로컬 Docker Compose 환경 — DB 없이 코드 검증만 완료) -- [x] GET /health 응답에 service/version/checked_at(+09:00 KST) 포함 확인 (테스트) -- [x] GET /v1/assets 200 응답, 필드명 확인 (cash/quantity/broker_type/strategy_params) -- [x] GET /v1/assets ?status 필터 확인 (테스트) -- [x] GET /v1/user_info runtime_id 없는 토큰 → 404 확인 (테스트) -- [x] GET /v1/user_info trading_enabled, brokers[] 확인 (테스트) -- [x] GET /v1/user_info Fernet 복호화 → credential_payload 반환 확인 (테스트) -- [x] POST /v1/assets/{asset_id}/ledger/trades: new → 201, duplicate same payload → 200, different payload → 409, unknown asset → 404 (테스트) -- [x] POST /auth/service-tokens → 201 + runtime_id 확인 (테스트) -- [x] startup: CREDENTIAL_ENCRYPTION_KEY 없으면 RuntimeError (테스트) -- [x] Fernet encrypt/decrypt roundtrip 확인 (테스트) - -## Blocker Fixes (2026-05-01) - -- B-1: POST /auth/service-tokens 엔드포인트 추가 (app/api/auth_routes.py, app/schemas/auth.py) -- B-2: GET /v1/assets ?status 필터 추가 (app/api/v1_assets.py) -- B-3: 응답 필드명 수정 (cash/quantity/broker_type/strategy_id → 계약 정의명) -- B-4: strategy_params {} 폴백 구현 -- B-5: UserInfoResponse trading_enabled 필드 추가 -- B-6: brokers[] 배열 구현 + Fernet 복호화 -- B-7: 중복 수신 → HTTP 200 + status="duplicate" (was created: bool) -- B-8: 다른 payload → HTTP 409 -- B-9: 없는 asset_id → HTTP 404 -- B-10: lifespan startup validation (CREDENTIAL_ENCRYPTION_KEY 없으면 RuntimeError) -- B-11: list_broker_credentials postgres 구현 -- B-12: docs/project-structure.md, docs/runtime-flows.md에 신규 모듈 및 v1 흐름 반영 - -## Changed Files (Blocker Fixes) - -- app/api/auth_routes.py (수정: POST /auth/service-tokens 엔드포인트 추가) -- app/schemas/auth.py (수정: IssueServiceTokenRequest, IssueServiceTokenApiResponse 추가) -- app/schemas/v1.py (수정: 필드명 전면 수정, BrokerItem, trading_enabled, status) -- app/api/v1_assets.py (수정: status 필터, strategy_params, 새 필드명) -- app/api/v1_user_info.py (수정: trading_enabled, brokers[], Fernet 복호화, Depends(get_settings)) -- app/api/v1_ledger.py (수정: 200/201/404/409 분기, _payloads_match 비교) -- app/repositories/postgres.py (수정: list_broker_credentials 추가) -- app/main.py (수정: startup validation 추가) -- tests/test_v1.py (전면 재작성: 19개 테스트로 확장) -- tests/test_app.py (수정: db_enabled=True 테스트에 credential_encryption_key 추가) -- docs/project-structure.md (수정: v1 모듈, encryption.py 반영) -- docs/runtime-flows.md (수정: v1 API 흐름, startup 검증 흐름 추가) - -## Design Decisions (confirmed during implementation) - -- CREDENTIAL_ENCRYPTION_KEY: Settings 생성 시 validator 없음. `require_credential_encryption_key()` 메서드 호출 시 RuntimeError 발생 (lazy validation) -- R-4 (구형 service-token runtime_id 없음): HTTP 404, detail="runtime_id not assigned to this token" -- disabled_reason: is_active=False 시 "account_inactive" 고정값 - -## Remaining Risks - -- (해소) R1: CREDENTIAL_ENCRYPTION_KEY — lazy validation으로 처리. 테스트 환경 unblocked. - - -design.md에서 확정된 결정 사항을 기반으로 아래 범위를 구현한다. - -1. app/core/encryption.py 신규 — Fernet 암복호화 유틸리티 -2. app/core/config.py — CREDENTIAL_ENCRYPTION_KEY 필드 추가 및 startup validation -3. Alembic migration 신규 — 4개 스키마 변경 (IF NOT EXISTS 패턴) -4. app/domain/models.py — AuthToken.runtime_id, BrokerCredential, LedgerTrade 추가 -5. app/services/auth_service.py — service-token 발급 시 UUID v4 runtime_id 생성·저장 -6. app/api/auth_routes.py, app/cli/commands/service_tokens.py — 응답/출력에 runtime_id 포함 -7. app/api/health.py, app/schemas/ — HealthResponse 스키마 적용 -8. app/api/v1_assets.py — GET /v1/assets 구현 -9. app/api/v1_user_info.py — GET /v1/user_info 구현 (R-4: 404 반환) -10. app/api/v1_ledger.py — POST /v1/assets/{asset_id}/ledger/trades 구현 -11. app/repositories/postgres.py — broker_credentials, ledger_trades, runtime_id 쿼리 추가 -12. app/main.py — /v1 라우터 등록 -13. 테스트 파일 작성 및 기존 테스트 확장 - -## Changed Files - -- app/core/encryption.py (신규) -- app/core/config.py (수정) -- migrations/versions/20260502_0014_v1_api_schema.py (신규) -- app/domain/models.py (수정) -- app/services/auth_service.py (수정) -- app/api/auth_routes.py (수정) -- app/cli/commands/service_tokens.py (수정) -- app/api/health.py (수정) -- app/schemas/ (수정: HealthResponse 추가, v1 스키마 추가) -- app/api/v1_assets.py (신규) -- app/api/v1_user_info.py (신규) -- app/api/v1_ledger.py (신규) -- app/repositories/postgres.py (수정) -- app/main.py (수정) -- tests/test_health.py (신규 또는 test_app.py 확장) -- tests/test_auth.py (확장) -- tests/test_v1_assets.py (신규) -- tests/test_v1_user_info.py (신규) -- tests/test_v1_ledger.py (신규) - -## Execution Log - -_(구현 진행 시 단계별 기록)_ - -1. [ ] app/core/encryption.py 신규 구현 -2. [ ] app/core/config.py CREDENTIAL_ENCRYPTION_KEY 추가 및 startup validation -3. [ ] Alembic migration 파일 작성 및 로컬 upgrade head 성공 확인 -4. [ ] app/domain/models.py 모델 필드/클래스 추가 -5. [ ] auth_service.py runtime_id 생성·저장 구현 -6. [ ] auth_routes.py, service_tokens.py runtime_id 출력 추가 -7. [ ] app/api/health.py HealthResponse 스키마 적용 -8. [ ] app/api/v1_assets.py 구현 -9. [ ] app/api/v1_user_info.py 구현 -10. [ ] app/api/v1_ledger.py 구현 -11. [ ] app/repositories/postgres.py 쿼리 추가 -12. [ ] app/main.py /v1 라우터 등록 -13. [ ] 테스트 작성 및 전체 테스트 스위트 통과 - -## Validation - -_(구현 완료 시 기록)_ - -- [ ] pytest 전체 통과 -- [ ] CREDENTIAL_ENCRYPTION_KEY 없이 기동 시 RuntimeError 확인 -- [ ] alembic upgrade head 성공 (로컬 Docker Compose 환경) -- [ ] GET /health 응답에 service/version/checked_at 포함 확인 -- [ ] GET /v1/assets 200 응답 확인 -- [ ] GET /v1/user_info runtime_id 없는 토큰 → 404 확인 -- [ ] POST /v1/assets/{asset_id}/ledger/trades 중복 → 200+duplicate 확인 - -## Remaining Risks - -_(구현 전 상태 — 진행 중 업데이트)_ - -- R1: CREDENTIAL_ENCRYPTION_KEY 테스트 픽스처 처리 방식 확정 필요 -- R2: psycopg UniqueViolation import 경로 확인 필요 (psycopg v3 기준) diff --git a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/index.md b/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/index.md deleted file mode 100644 index b2bf16e..0000000 --- a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/index.md +++ /dev/null @@ -1,67 +0,0 @@ -# BAT-001 Index - -- Status: release-candidate -- Batch: BAT-001 -- Profile: standard -- Created: 2026-05-02 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-004 | GET /health 응답 스키마 확장 | Interface | High | -| REQ-005 | service-token 발급 시 runtime_id 동시 생성 | Interface | High | -| REQ-006 | GET /v1/assets 엔드포인트 구현 | Interface | High | -| REQ-007 | GET /v1/user_info 엔드포인트 구현 | Interface | High | -| REQ-008 | POST /v1/assets/{asset_id}/ledger/trades 구현 | Interface | High | -| REQ-009 | v1 API DB 스키마 migration 추가 | Migration | High | -| REQ-010 | credential_payload Fernet 암호화 및 startup validation | Non-Functional | High | - -## Source Discovery - -- dcy-002: trex-basic Consumer-Driven Contract 반영 - -## Scope Summary - -- GET /health 응답 확장 (service, version, checked_at 필드 추가) -- service-token 발급 시 runtime_id UUID v4 동시 생성 및 auth_tokens 저장 -- GET /v1/assets 엔드포인트 신규 구현 (portfolio → asset 뷰 변환) -- GET /v1/user_info 엔드포인트 신규 구현 (runtime_id, trading_enabled, brokers[] 복호화 반환) -- POST /v1/assets/{asset_id}/ledger/trades 엔드포인트 신규 구현 (idempotent 체결 기록) -- Alembic migration: portfolios.market 컬럼, auth_tokens.runtime_id 컬럼, broker_credentials 테이블, ledger_trades 테이블 추가 -- Fernet 암복호화 유틸리티 신규 구현 및 startup validation - -## Impacted Area - -- Docs: docs/project-structure.md, docs/runtime-flows.md (design 완료 후 갱신) -- Modules: - - app/api/health.py (수정) - - app/api/auth_routes.py (수정) - - app/api/v1_assets.py (신규) - - app/api/v1_user_info.py (신규) - - app/api/v1_ledger.py (신규) - - app/main.py (수정: /v1 라우터 등록) - - app/cli/commands/service_tokens.py (수정) - - app/core/config.py (수정: CREDENTIAL_ENCRYPTION_KEY 추가) - - app/core/encryption.py (신규: Fernet 유틸리티) - - app/domain/models.py (수정: AuthToken.runtime_id, BrokerCredential, LedgerTrade 추가) - - app/services/auth_service.py (수정) - - app/services/ (신규 서비스 메서드) - - app/schemas/ (신규 v1 스키마) - - app/repositories/postgres.py (수정: 신규 테이블 쿼리) - - migrations/versions/ (신규 migration 파일) -- Tests: - - tests/test_app.py 또는 tests/test_health.py (신규/확장) - - tests/test_auth.py (확장) - - tests/test_v1_assets.py (신규) - - tests/test_v1_user_info.py (신규) - - tests/test_v1_ledger.py (신규) - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Design | [design.md](./design.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | re-verified | diff --git a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/planning.md b/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/planning.md deleted file mode 100644 index b853160..0000000 --- a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/planning.md +++ /dev/null @@ -1,83 +0,0 @@ -# BAT-001 Planning - -- Status: confirmed -- Batch: BAT-001 -- Profile: standard - -## Included REQ - -- REQ-004: GET /health 응답 스키마 확장 -- REQ-005: service-token 발급 시 runtime_id 동시 생성 -- REQ-006: GET /v1/assets 엔드포인트 구현 -- REQ-007: GET /v1/user_info 엔드포인트 구현 -- REQ-008: POST /v1/assets/{asset_id}/ledger/trades 구현 -- REQ-009: v1 API DB 스키마 migration 추가 -- REQ-010: credential_payload Fernet 암호화 및 startup validation - -## Delivery Plan - -구현 선행 조건과 의존성 방향을 기준으로 아래 순서로 진행한다. - -### Out of Scope -- REQ-001 (Testing — dcy-001 범위, 별도 batch 예정) -- REQ-002 (Migration 정책 문서 — 기 반영됨) -- REQ-003 (Documentation — dcy-001 runbook, 별도 처리) -- REQ-011 (Documentation — 본 batch 구현 완료 후 별도 task) -- broker_credentials 키 로테이션 절차 -- disabled_reason 동적 계산 (이번 반복은 고정값 "account_inactive") -- GET /v1/assets 페이지네이션 - -### 1단계: 기반 레이어 (의존 없음) -- REQ-010: app/core/encryption.py 신규 구현, app/core/config.py에 CREDENTIAL_ENCRYPTION_KEY startup validation 추가 -- REQ-009: Alembic migration 파일 작성 (portfolios.market, auth_tokens.runtime_id, broker_credentials, ledger_trades), app/domain/models.py 모델 필드/클래스 추가 - -### 2단계: 인증 확장 (REQ-009/010 완료 후) -- REQ-005: app/cli/commands/service_tokens.py, app/api/auth_routes.py, app/services/auth_service.py에 runtime_id UUID v4 생성·저장 추가 - -### 3단계: v1 엔드포인트 (REQ-009/010/005 완료 후) -- REQ-004: app/api/health.py 응답 스키마 확장 (service, version, checked_at 추가), HealthResponse 스키마 추가 -- REQ-006: app/api/v1_assets.py 신규, 서비스·스키마·레포지터리 메서드 추가, app/main.py /v1 라우터 등록 -- REQ-007: app/api/v1_user_info.py 신규, BrokerCredential 조회·복호화 서비스·스키마 추가 -- REQ-008: app/api/v1_ledger.py 신규, LedgerTrade 저장·중복 감지 서비스·스키마 추가 - -### 4단계: 테스트 -- 각 엔드포인트 단위 테스트 작성 및 기존 test_auth.py 확장 - -## Dependencies - -- Internal: - - REQ-009 migration이 REQ-005, 006, 007, 008 구현의 선행 조건 (컬럼·테이블 존재 필요) - - REQ-010 암호화 유틸리티가 REQ-007 credential_payload 복호화의 선행 조건 - - REQ-005 runtime_id 발급이 REQ-007 GET /v1/user_info runtime_id 반환의 선행 조건 - - app/main.py /v1 라우터 등록은 REQ-006/007/008 세 라우터를 한 번에 처리 -- External: - - CREDENTIAL_ENCRYPTION_KEY 환경변수 (운영 환경 설정 필요) - - Docker Compose local-test 환경에서 Alembic upgrade head 선행 필요 - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: - - /v1 라우터 구조 및 app/main.py 등록 방식 결정 필요 (prefix, router 분리 또는 통합) - - GET /v1/user_info의 R-4 처리 방식 결정 필요 (runtime_id 없는 구형 service-token: 404 또는 즉시 생성) - - disabled_reason 고정값 방식 확정 필요 ("account_inactive" 등) - - Fernet startup validation 위치 확정 필요 (FastAPI lifespan vs Settings 초기화) - - broker_credentials 테이블 상세 컬럼 타입 확정 필요 (NUMERIC vs FLOAT, TIMESTAMPTZ 등) - - runtime_id 포맷 확정 필요 (UUID v4 권장, 설계 단계 확정) - -## Milestones - -1. design.md confirmed — 모든 Design Gate 결정 사항 해소 -2. REQ-009 migration 구현 완료 및 로컬 DB upgrade head 성공 -3. REQ-010 Fernet 유틸리티 구현 완료 및 startup validation 동작 확인 -4. REQ-005 runtime_id 발급 구현 완료 및 단위 테스트 통과 -5. REQ-004/006/007/008 엔드포인트 구현 완료 -6. 전체 테스트 스위트 통과 -7. verification.md confirmed - -## Risks - -- R1: GET /v1/user_info R-4 처리(기존 runtime_id 없는 토큰) 미결 — 설계 단계에서 반드시 확정. 404 선택 시 기존 service-token 무효화 영향 범위 검토 필요. -- R2: Fernet 키 없는 환경에서 테스트 실행 실패 가능 — 테스트 픽스처에서 환경변수 mock 처리 필요. -- R3: broker_credentials 컬럼 타입(특히 numeric 정밀도)을 migration 작성 전 확정하지 않으면 후속 변경 migration이 늘어남. -- R4: /v1 라우터 prefix 방식이 기존 /portfolios, /auth 등 라우터와 충돌하지 않는지 app/main.py 구조 확인 필요. diff --git a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md b/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md deleted file mode 100644 index eade2b2..0000000 --- a/docs/batches/bat-001_20260502_trex-basic-v1-api-contract/verification.md +++ /dev/null @@ -1,361 +0,0 @@ -# BAT-001 Verification - -- Status: re-verified -- Batch: BAT-001 -- Last Updated (KST): 2026-05-11 -- Re-verification Trigger: CHG-20260503-01 (REQ-006 strategy_type 필드 추가), CHG-20260511-02 (REQ-006 owner scope 회귀 수정) - -## Acceptance Mapping - -### REQ-004: GET /health 응답 스키마 확장 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | status/service/version/checked_at 필드 모두 존재 | `tests/test_v1.py::test_health_returns_full_response` — 4개 필드 전체 assert | PASS | -| AC2 | status 허용값 ok/degraded/failed | test에서 "ok" 값만 검증. "degraded"/"failed" 경로는 이번 반복 OOS로 REQ Notes에 명시 | PARTIAL (OOS 인정) | -| AC3 | checked_at ISO 8601 형식 | `test_v1.py::test_health_returns_full_response` — `"+09:00" in body["checked_at"]` assert | PASS | -| AC4 | service 고정값 "portfolio-hub" | `test_v1.py` — `body["service"] == "portfolio-hub"` | PASS | -| AC5 | 비인증 접근 200 | `test_v1.py::test_health_returns_full_response` — 인증 헤더 없이 GET /health 호출, 200 응답 | PASS | - -**REQ-004 판정: PASS** (AC2는 이번 반복 OOS — 릴리스 블로커 아님) - ---- - -### REQ-005: service-token 발급 시 runtime_id 동시 생성 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | CLI 출력에 token + runtime_id | `tests/test_cli.py::test_cli_service_token_issue_supports_json` PASS | PASS | -| AC2 | API 엔드포인트 응답에 token + runtime_id | `tests/test_v1.py::test_auth_issue_service_token_returns_runtime_id` — POST /auth/service-tokens → 201 + `runtime_id` PASS | PASS | -| AC3 | GET /v1/user_info 응답 runtime_id 일치 | `tests/test_v1.py::test_v1_user_info_returns_user` — `body["runtime_id"] == "rt-abc"` PASS | PASS | -| AC4 | 두 번 발급 시 다른 runtime_id | `auth_service.py`에서 매 발급마다 `str(uuid.uuid4())` 생성 — 코드 검토 PASS | PASS | -| AC5 | auth_tokens 테이블 runtime_id 저장 | `postgres.py::create_auth_token` INSERT에 runtime_id 포함 — 코드 검토 PASS. DB 환경 실행 미확인(PARTIAL) | PARTIAL (DB 미확인) | - -**REQ-005 판정: PASS** (AC5 DB 미확인은 REQ-009와 동일 제약으로 릴리스 블로커 아님) - ---- - -### REQ-006: GET /v1/assets 엔드포인트 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | service-token 인증 → 200 + 해당 사용자 소유 portfolio 목록만 반환 | `tests/test_v1.py::test_v1_assets_returns_asset_list` — 200 응답 PASS, `mock_repo.list_portfolios_with_strategies.assert_called_once_with(owner_user_id="user-1", include_disabled=False)`로 owner scope 전달 검증 PASS | PASS | -| AC2 | ?status=active 쿼리 파라미터 필터 | `tests/test_v1.py::test_v1_assets_status_filter` — ?status=active 필터 동작 확인 PASS | PASS | -| AC3 | 응답 필드: asset_id, symbol, market, status, cash, quantity, broker_type, strategy_id, strategy_type, strategy_params, updated_at | `test_v1_assets_returns_asset_list` — 필드명 assert PASS. `test_v1_assets_strategy_params` — strategy_type=="strategy" PASS. `test_v1_assets_returns_asset_list` — strategy_type is None PASS | PASS | -| AC4 | strategy_params → StrategySet.config 파생 JSON | `tests/test_v1.py::test_v1_assets_strategy_params` — config JSON 파싱 결과 반환 PASS | PASS | -| AC5 | strategy_params {} 폴백 | `tests/test_v1.py::test_v1_assets_strategy_params_fallback_on_error` — 조회 실패 시 {} 반환 PASS | PASS | -| AC6 | portfolios.market NULL → 응답 null | `AssetItem.market: str | None = None`, `Portfolio.market: str | None = None` — NULL 전파 구조 검토 PASS | PASS | -| AC7 | 미인증 401 | `tests/test_v1.py::test_v1_assets_requires_service_token` PASS | PASS | -| AC8 | active_strategy_id → strategy_type = StrategySet.name | `tests/test_v1.py::test_v1_assets_strategy_params` — `strategy_type == "strategy"` assert PASS | PASS | -| AC9 | active_strategy_id 없거나 조회 실패 → strategy_type = null | `tests/test_v1.py::test_v1_assets_returns_asset_list` — `strategy_type is None` PASS. `test_v1_assets_strategy_params_fallback_on_error` — `strategy_type is None` PASS | PASS | - -**REQ-006 판정: PASS** (CHG-20260503-01 strategy_type 추가, CHG-20260511-02 owner scope 회귀 수정 재검증 완료) - ---- - -### REQ-007: GET /v1/user_info 엔드포인트 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | 응답에 runtime_id, trading_enabled, brokers[] 포함 | `tests/test_v1.py::test_v1_user_info_returns_user` — 3개 필드 모두 assert PASS | PASS | -| AC2 | environment 필드 없음 | `UserInfoResponse` 스키마 검토 — environment 필드 없음 PASS | PASS | -| AC3 | is_active=true → trading_enabled=true | `test_v1_user_info_returns_user` — `body["trading_enabled"] is True` PASS | PASS | -| AC4 | is_active=false → trading_enabled=false, disabled_reason 포함 | `tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason` — `trading_enabled is False`, `disabled_reason == "account_inactive"` PASS | PASS | -| AC5 | brokers[]에 credential_payload 복호화 JSON | `tests/test_v1.py::test_v1_user_info_returns_decrypted_broker_credentials` — Fernet 복호화 payload 반환 PASS | PASS | -| AC6 | runtime_id 없는 구형 토큰 → HTTP 404 | `tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id` — 404 + detail PASS | PASS | -| AC7 | 미인증 401 | `_require_service_token` 의존성 + `test_v1_assets_requires_service_token` 패턴으로 구조 검토 PASS | PASS | - -**REQ-007 판정: PASS** - ---- - -### REQ-008: POST /v1/assets/{asset_id}/ledger/trades - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | 신규 체결 → HTTP 201 + ledger_entry_id | `tests/test_v1.py::test_v1_ledger_trade_created` — 201 + `status="new"` PASS | PASS | -| AC2 | 동일 payload 재전송 → HTTP 200 + status="duplicate" | `tests/test_v1.py::test_v1_ledger_trade_idempotent_same_payload` — 200 + `status="duplicate"` PASS | PASS | -| AC3 | 동일 키 다른 payload → HTTP 409 | `tests/test_v1.py::test_v1_ledger_trade_conflict_different_payload` — 409 PASS | PASS | -| AC4 | 존재하지 않는 asset_id → 404 | `tests/test_v1.py::test_v1_ledger_trade_asset_not_found` — 404 PASS | PASS | -| AC5 | 미인증 401 | `_require_service_token` 의존성 구조 검토 PASS | PASS | - -**REQ-008 판정: PASS** - ---- - -### REQ-009: v1 API DB 스키마 migration - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | portfolios.market 컬럼 존재 | `20260502_0014_v1_api_schema.py` — IF NOT EXISTS 패턴 구현. 코드 검토 PASS. DB 환경 미확인 | PARTIAL | -| AC2 | auth_tokens.runtime_id 컬럼 존재 | 동일 migration — UNIQUE INDEX 포함. 코드 검토 PASS | PARTIAL | -| AC3 | broker_credentials 테이블 존재 | 동일 migration — PK(user_id, broker). 코드 검토 PASS | PARTIAL | -| AC4 | ledger_trades 테이블 존재 | 동일 migration — BIGSERIAL PK. 코드 검토 PASS | PARTIAL | -| AC5 | (asset_id, order_id, execution_id) UniqueViolation | `uq_ledger_trades_idempotency` UniqueConstraint 정의 확인. DB 실행 미확인 | PARTIAL | -| AC6 | 재실행 시 에러 없음 | IF NOT EXISTS + `_table_exists`/`_column_exists` 패턴. 코드 검토 PASS | PARTIAL | -| AC7 | 기존 행 market NULL 유지 | ADD COLUMN NULLABLE, 기존 행 UPDATE 없음. 코드 검토 PASS | PARTIAL | - -**REQ-009 판정: PARTIAL** — 코드 전체 PASS. 실제 DB 환경(Docker Compose) 실행 검증 미수행. 코드 전용 환경에서는 블로커 아님. - ---- - -### REQ-010: credential_payload Fernet 암호화 및 startup validation - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | KEY 있음 → 정상 기동 | `tests/test_v1.py::test_startup_ok_when_encryption_key_present` PASS | PASS | -| AC2 | KEY 없음 → 기동 실패 + 에러 메시지 | `tests/test_v1.py::test_startup_fails_when_encryption_key_missing` — db_enabled=True + KEY 없음 → RuntimeError("CREDENTIAL_ENCRYPTION_KEY") PASS | PASS | -| AC3 | DB에 ciphertext 저장 | `tests/test_v1.py::test_encrypt_decrypt_roundtrip` — encrypt→decrypt roundtrip PASS. 저장 경로는 `upsert_broker_credential` 메서드에서 암호화된 payload 직접 수신 (호출자 책임). DB 환경 실행 미확인 | PARTIAL | -| AC4 | GET /v1/user_info 복호화 JSON 반환 | `tests/test_v1.py::test_v1_user_info_returns_decrypted_broker_credentials` PASS | PASS | -| AC5 | 잘못된 키 → InvalidToken 예외 처리 | `encryption.py`에서 `InvalidToken` re-export. `v1_user_info.py`에서 except 처리 후 `credential_payload: null` 반환 — 코드 검토 PASS | PASS | - -**REQ-010 판정: PASS** (AC3 DB 미확인은 REQ-009와 동일 제약) - ---- - -## Architecture Impact & Baseline Docs Sync - -- `docs/project-structure.md` — 갱신 완료: `app/core/encryption.py`, v1 라우터 모듈, v1 스키마 반영 -- `docs/runtime-flows.md` — 갱신 완료: v1 API 요청 흐름 5개, startup 검증 흐름 추가 - -**판정: PASS** - ---- - -## Evidence - -### Automated Tests (pytest) - -- `tests/test_v1.py -k 'v1_assets'` — 6 passed, 13 deselected (2026-05-11 targeted rerun) - - `test_v1_assets_returns_asset_list`에 owner scope 전달 assert 추가 -- `tests/test_v1.py` — 19개 전부 PASS - - `test_health_returns_full_response`, `test_auth_issue_service_token_returns_runtime_id` - - `test_v1_assets_requires_service_token`, `test_v1_assets_rejects_env_token`, `test_v1_assets_returns_asset_list` - - `test_v1_assets_status_filter`, `test_v1_assets_strategy_params`(`strategy_type` assert 포함), `test_v1_assets_strategy_params_fallback_on_error`(`strategy_type is None` assert 포함) - - `test_v1_user_info_404_when_no_runtime_id`, `test_v1_user_info_returns_user`, `test_v1_user_info_inactive_user_has_disabled_reason` - - `test_v1_user_info_returns_decrypted_broker_credentials` - - `test_v1_ledger_trade_created`, `test_v1_ledger_trade_idempotent_same_payload`, `test_v1_ledger_trade_conflict_different_payload`, `test_v1_ledger_trade_asset_not_found` - - `test_startup_fails_when_encryption_key_missing`, `test_startup_ok_when_encryption_key_present`, `test_encrypt_decrypt_roundtrip` -- `tests/test_app.py` — PASS (startup bootstrap 포함) -- `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_domain_services.py` — PASS -- `tests/test_portfolio_comments.py`, `tests/test_portfolio_ownership.py`, `tests/test_portfolio_tags.py`, `tests/test_users.py` — PASS -- **전체 합계: 115 passed (2026-05-03, CHG-20260503-01 재검증 포함)** - -### Code Review - -- `app/core/encryption.py`: Fernet 암복호화 유틸리티 정상 구현 -- `app/api/auth_routes.py`: `POST /auth/service-tokens` 엔드포인트 추가 -- `app/api/v1_assets.py`: status 필터, strategy_params 조회, 새 필드명 매핑 -- `app/api/v1_user_info.py`: trading_enabled, brokers[], Fernet 복호화, Depends(get_settings) -- `app/api/v1_ledger.py`: 200/201/404/409 분기, _payloads_match 비교 -- `app/repositories/postgres.py`: list_broker_credentials 추가 -- `app/main.py`: startup validation (CREDENTIAL_ENCRYPTION_KEY) - -### DB 환경 실행 - -- 미수행 (Docker Compose 환경 미기동). 코드 전용 환경에서는 블로커 아님. - ---- - -## Blocking Issues - -없음. - -## Non-blocking Notes - -- REQ-009 AC1~AC7: 코드 검토 PASS, 실제 DB 환경 실행 미확인. 다음 릴리스 전 live DB 검증 권장. -- REQ-004 AC2: "degraded"/"failed" 상태 경로는 이번 반복 OOS. - ---- - -## Result - -- **Ready for Release: true** - - -## Historical Snapshot (2026-05-02 Initial Verification) - -- 아래 섹션은 BAT-001의 초기 검증 기록이다. 현재 유효한 판정은 위 `Acceptance Mapping`, `Evidence`, `Result` 섹션을 따른다. - -## Acceptance Mapping - -### REQ-004: GET /health 응답 스키마 확장 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | status/service/version/checked_at 필드 모두 존재 | `tests/test_v1.py::test_health_returns_full_response` — 4개 필드 전체 assert | PASS | -| AC2 | status 허용값 ok/degraded/failed | test에서 "ok" 값만 검증. "degraded"/"failed" 경로 코드 미구현(Notes에 이번 반복 Out of Scope 명시) — 스키마에 허용값으로만 선언 | PARTIAL (OOS 인정) | -| AC3 | checked_at ISO 8601 형식 | `test_v1.py::test_health_returns_full_response` — `"+09:00" in body["checked_at"]` assert | PASS | -| AC4 | service 고정값 "portfolio-hub" | `test_v1.py` — `body["service"] == "portfolio-hub"` | PASS | -| AC5 | 비인증 접근 200 | `test_v1.py::test_health_returns_full_response` — 인증 헤더 없이 GET /health 호출, 200 응답 | PASS | - -**REQ-004 판정: PASS** (AC2는 REQ Notes에서 이번 반복 OOS로 명시 — 릴리스 블로커 아님) - ---- - -### REQ-005: service-token 발급 시 runtime_id 동시 생성 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | CLI 출력에 token + runtime_id | `app/cli/commands/service_tokens.py` — `ServiceTokenIssueCliResponse(runtime_id=issued.auth_token.runtime_id, ...)` 구현, `tests/test_cli.py::test_cli_service_token_issue_supports_json` PASS (FakeRepo에 runtime_id 반영) | PASS | -| AC2 | API 엔드포인트 응답에 token + runtime_id | service-token 발급 API 엔드포인트 미구현 (CLI 전용으로 구현). `app/api/auth_routes.py` 변경 없음. REQ-005 AC2는 "POST /auth/tokens 또는 동등 경로"를 명시함 | **BLOCKER** | -| AC3 | GET /v1/user_info 응답 runtime_id 일치 | `tests/test_v1.py::test_v1_user_info_returns_user` — `body["runtime_id"] == "rt-abc"` | PASS | -| AC4 | 두 번 발급 시 다른 runtime_id | `auth_service.py`에서 매 발급마다 `str(uuid.uuid4())` 생성 — 코드 검토 통과. 전용 테스트 없음 | PARTIAL | -| AC5 | auth_tokens 테이블 runtime_id 저장 | `postgres.py::create_auth_token` INSERT에 runtime_id 포함, `_SELECT_AUTH_TOKEN`에 runtime_id 컬럼 추가 — 코드 검토 통과. DB 환경 실행 미확인 | PARTIAL (DB 미확인) | - -**REQ-005 판정: BLOCKED** — AC2 서비스 토큰 발급 API 엔드포인트 누락 - ---- - -### REQ-006: GET /v1/assets 엔드포인트 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | service-token 인증 → 200 + portfolio 목록 | `tests/test_v1.py::test_v1_assets_returns_asset_list` PASS | PASS | -| AC2 | ?status=active 쿼리 파라미터 필터 | `v1_assets.py`에 `status` 쿼리 파라미터 미구현. `list_portfolios_with_strategies(include_disabled=False)`만 호출 — active 필터 없음 | **BLOCKER** | -| AC3 | 응답 필드: asset_id, symbol, market, status, cash, quantity, broker_type, strategy_id, strategy_params, updated_at | `AssetItem` 스키마에 `cash_balance`(≠cash), `asset_quantity`(≠quantity), `broker`(≠broker_type), `active_strategy_id`(≠strategy_id) 사용. `strategy_params` 필드 아예 없음 — trex-basic contract 필드명 불일치 | **BLOCKER** | -| AC4 | strategy_params → StrategySet.config 파생 JSON | `AssetItem`에 strategy_params 필드 없음. v1_assets.py에서 StrategySet 조회 로직 없음 | **BLOCKER** | -| AC5 | strategy_params {} 폴백 | strategy_params 미구현으로 폴백 불가 | **BLOCKER** | -| AC6 | portfolios.market NULL → 응답 null | `AssetItem.market: str | None = None`, `Portfolio.market: str | None = None` — NULL 전파 구조 PASS. NULL 케이스 전용 테스트 없음 | PARTIAL | -| AC7 | 미인증 401 | `tests/test_v1.py::test_v1_assets_requires_service_token` — 401 반환 PASS | PASS | - -**REQ-006 판정: BLOCKED** — AC2(status 필터), AC3(필드명 불일치), AC4/AC5(strategy_params 미구현) - ---- - -### REQ-007: GET /v1/user_info 엔드포인트 - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | 응답에 runtime_id, trading_enabled, brokers[] 포함 | `UserInfoResponse`에 `runtime_id` ✓, `is_active` (trading_enabled 아님) ✓, `brokers[]` 없음. Contract 필드명 `trading_enabled` 사용 안 함 | **BLOCKER** | -| AC2 | environment 필드 없음 | `UserInfoResponse`에 environment 필드 없음 | PASS | -| AC3 | is_active=true → trading_enabled=true | `trading_enabled` 필드 미구현 | **BLOCKER** | -| AC4 | is_active=false → trading_enabled=false, disabled_reason 포함 | `tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason` — `disabled_reason == "account_inactive"` PASS. `trading_enabled` 미구현 | **BLOCKER** | -| AC5 | brokers[]에 credential_payload 복호화 JSON | `UserInfoResponse`에 brokers[] 없음. v1_user_info.py에서 broker_credentials 조회 및 복호화 없음 | **BLOCKER** | -| AC6 | runtime_id 없는 구형 토큰 → HTTP 404 | `tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id` — 404 + "runtime_id not assigned to this token" PASS | PASS | -| AC7 | 미인증 401 | `_require_service_token` 의존성에서 강제. 명시적 테스트 없으나 구조 검토 통과 | PARTIAL | - -**REQ-007 판정: BLOCKED** — trading_enabled 필드명, brokers[] 배열 전체 미구현 - ---- - -### REQ-008: POST /v1/assets/{asset_id}/ledger/trades - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | 신규 체결 → HTTP 201 + ledger_entry_id | `tests/test_v1.py::test_v1_ledger_trade_created` — 201 + created=True PASS | PASS | -| AC2 | 동일 payload 재전송 → HTTP 200 + status="duplicate" | 현재 구현은 중복 시에도 **HTTP 201** 반환, 응답 필드가 `status="duplicate"`가 아닌 `created=false` 사용. `test_v1_ledger_trade_idempotent`는 201을 assert | **BLOCKER** | -| AC3 | 동일 키 다른 payload → HTTP 409 | `insert_ledger_trade`에서 UniqueViolation 시 기존 행 반환만 수행, payload 비교 없음. 409 응답 미구현 | **BLOCKER** | -| AC4 | 존재하지 않는 asset_id → 404 | `v1_ledger.py`에서 asset_id 존재 여부 확인 로직 없음 | **BLOCKER** | -| AC5 | 미인증 401 | `_require_service_token` 의존성에서 강제. 명시적 테스트 없으나 구조 검토 통과 | PARTIAL | - -**REQ-008 판정: BLOCKED** — AC2(HTTP 상태 코드 및 응답 필드), AC3(409 미구현), AC4(asset_id 존재 확인 없음) - ---- - -### REQ-009: v1 API DB 스키마 migration - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | portfolios.market 컬럼 존재 | `20260502_0014_v1_api_schema.py` — `add_column("portfolios", market TEXT NULLABLE)` IF NOT EXISTS 패턴 구현 — 코드 검토 PASS. 실제 DB 실행 미확인 | PARTIAL (DB 미확인) | -| AC2 | auth_tokens.runtime_id 컬럼 존재 | 동일 migration — `add_column("auth_tokens", runtime_id TEXT NULLABLE)` + UNIQUE INDEX — 코드 검토 PASS | PARTIAL (DB 미확인) | -| AC3 | broker_credentials 테이블 존재 | 동일 migration — `create_table("broker_credentials", ...)` PK(user_id, broker) — 코드 검토 PASS | PARTIAL (DB 미확인) | -| AC4 | ledger_trades 테이블 존재 | 동일 migration — `create_table("ledger_trades", ...)` BIGSERIAL PK — 코드 검토 PASS | PARTIAL (DB 미확인) | -| AC5 | (asset_id, order_id, execution_id) UniqueViolation | `uq_ledger_trades_idempotency` UniqueConstraint 정의 확인. DB 실행 미확인 | PARTIAL (DB 미확인) | -| AC6 | 재실행 시 에러 없음 | IF NOT EXISTS 패턴 + `_table_exists`/`_column_exists` 검사 구현 — 코드 검토 PASS | PARTIAL (DB 미확인) | -| AC7 | 기존 행 market NULL 유지 | `ADD COLUMN ... NULLABLE` 추가만 수행, 기존 행 UPDATE 없음 — 코드 검토 PASS | PARTIAL (DB 미확인) | - -**REQ-009 판정: PARTIAL** — 코드 검토 전체 PASS. 실제 DB 환경(Docker Compose) 실행 검증 필요. 코드 전용 환경에서는 블로커 아님으로 취급하나, live DB 검증 항목을 Blocking Issues에 포함. - ---- - -### REQ-010: credential_payload Fernet 암호화 및 startup validation - -| # | Acceptance Criterion | Evidence | Result | -|---|---|---|---| -| AC1 | KEY 있음 → 정상 기동 | `app/core/encryption.py` 신규 구현, `Settings.credential_encryption_key` 필드 추가. KEY 있을 때 정상 동작 — 코드 검토 PASS | PASS | -| AC2 | KEY 없음 → 기동 실패 + 에러 메시지 | 구현에서 lazy validation으로 변경됨: `Settings` 생성 시 에러 없이 통과, `require_credential_encryption_key()` 호출 시에만 RuntimeError 발생. **startup 실패 동작이 AC2와 불일치** | **BLOCKER** | -| AC3 | DB에 ciphertext 저장 | `upsert_broker_credential` 메서드 존재. 그러나 API에서 plaintext → encrypt → DB 저장 경로 미구현 (암호화 적용은 호출자 책임으로 처리됨). 전용 테스트 없음 | **BLOCKER** | -| AC4 | GET /v1/user_info 복호화 JSON 반환 | `UserInfoResponse`에 brokers[] 없음 (REQ-007 BLOCKED에 포함) — 동일 blocker | **BLOCKER** | -| AC5 | 잘못된 키 → InvalidToken 예외 | `encryption.py`에서 `cryptography.fernet.InvalidToken` re-export. 전용 테스트 없음 | PARTIAL | - -**REQ-010 판정: BLOCKED** — AC2(startup 실패 동작 불일치), AC3(암호화 저장 경로 미연결), AC4(REQ-007 blocker와 연동) - ---- - -## Architecture Impact & Baseline Docs Sync - -`design.md` Changed Areas에 아래 baseline 문서 갱신이 명시됨: - -- `docs/project-structure.md` — app/core/encryption.py, app/api/v1_*.py 신규 모듈 반영 -- `docs/runtime-flows.md` — v1 API 요청 흐름, runtime_id 발급 흐름 추가 - -**현재 상태:** `implementation.md` Changed Files에 baseline 문서 갱신 없음. design의 Architecture Impact가 `none`이 아니므로 baseline 문서 동기화 evidence가 필요함. - -**판정: BLOCKER** — docs/project-structure.md, docs/runtime-flows.md 미갱신 - ---- - -## Evidence - -### Automated Tests (pytest) - -- `tests/test_app.py::test_healthcheck` — PASS (health 필드 검증 포함) -- `tests/test_v1.py::test_health_returns_full_response` — PASS (4개 필드 + KST checked_at) -- `tests/test_v1.py::test_v1_assets_requires_service_token` — PASS -- `tests/test_v1.py::test_v1_assets_rejects_env_token` — PASS -- `tests/test_v1.py::test_v1_assets_returns_asset_list` — PASS (asset_id, market 기본 필드) -- `tests/test_v1.py::test_v1_user_info_404_when_no_runtime_id` — PASS -- `tests/test_v1.py::test_v1_user_info_returns_user` — PASS (runtime_id, disabled_reason=None) -- `tests/test_v1.py::test_v1_user_info_inactive_user_has_disabled_reason` — PASS -- `tests/test_v1.py::test_v1_ledger_trade_created` — PASS -- `tests/test_v1.py::test_v1_ledger_trade_idempotent` — PASS -- `tests/test_auth.py` 전체 — PASS (runtime_id 파라미터 추가 후) -- `tests/test_cli.py::test_cli_service_token_issue_supports_json` — PASS -- 전체 합계: 88 passed (2026-05-02) - -### Code Review - -- `app/core/encryption.py`: Fernet 암복호화 유틸리티 정상 구현 -- `app/core/config.py`: `credential_encryption_key` 필드 추가, lazy validation 전환 -- `app/core/auth.py`: `AuthContext.runtime_id`, `TokenPrincipal.runtime_id` 추가 -- `app/domain/models.py`: `AuthToken.runtime_id`, `Portfolio.market`, `BrokerCredential`, `LedgerTrade` 추가 -- `app/services/auth_service.py`: `uuid.uuid4()` runtime_id 생성·전달 -- `migrations/versions/20260502_0014_v1_api_schema.py`: IF NOT EXISTS 패턴 정상 - -### DB 환경 실행 - -- 미수행 (Docker Compose 환경 미기동) - ---- - -## Result - -- **Ready for Release: false** - -### Blocking Issues - -| # | REQ | Issue | 필요 조치 | -|---|---|---|---| -| B-1 | REQ-005 AC2 | service-token 발급 API 엔드포인트(POST /auth/service-tokens 또는 동등 경로) 미구현. CLI 전용으로 구현됨 | API 엔드포인트 추가 또는 REQ-005 AC2 범위 조정(change-req) | -| B-2 | REQ-006 AC2 | ?status=active 쿼리 파라미터 필터 미구현 | v1_assets.py에 status 쿼리 파라미터 추가 | -| B-3 | REQ-006 AC3 | 응답 필드명 불일치: cash_balance(≠cash), asset_quantity(≠quantity), broker(≠broker_type), active_strategy_id(≠strategy_id) | 계약 필드명으로 응답 스키마 수정 또는 alias 추가 | -| B-4 | REQ-006 AC4/AC5 | strategy_params 필드 미구현. StrategySet.config 조회 및 {} 폴백 로직 없음 | v1_assets.py에 strategy_set 조회 및 strategy_params 파생 로직 추가 | -| B-5 | REQ-007 AC1/AC3/AC4 | trading_enabled 필드 미구현(is_active로 대체됨). 계약 필드명 불일치 | UserInfoResponse에 trading_enabled 필드 추가 | -| B-6 | REQ-007 AC5 | brokers[] 배열 전체 미구현. broker_credentials 조회 + Fernet 복호화 경로 없음 | v1_user_info.py에 broker_credentials 조회 + 복호화 + 응답 포함 구현 | -| B-7 | REQ-008 AC2 | 중복 수신 시 HTTP 201 반환(AC는 HTTP 200 요구). 응답 필드 created(bool) vs status="duplicate" 불일치 | 중복 시 HTTP 200 반환, 응답 스키마에 status="duplicate" 필드 추가 | -| B-8 | REQ-008 AC3 | 동일 키 다른 payload → HTTP 409 미구현 | insert_ledger_trade에 payload 비교 로직 및 409 응답 추가 | -| B-9 | REQ-008 AC4 | 존재하지 않는 asset_id → HTTP 404 미구현 | v1_ledger.py에 asset_id(portfolio_id) 존재 여부 확인 후 404 반환 추가 | -| B-10 | REQ-010 AC2 | startup validation 동작 불일치: Settings 생성 시 에러 없음(lazy), AC는 기동 실패 요구 | lifespan 또는 애플리케이션 시작 시 require_credential_encryption_key() 호출 추가 | -| B-11 | REQ-010 AC3 | broker_credentials 저장 시 암호화 미적용. API 레이어에서 encrypt_payload 호출 없음 | upsert_broker_credential 호출부 또는 API 레이어에서 암호화 적용 | -| B-12 | Baseline | docs/project-structure.md, docs/runtime-flows.md 미갱신. design.md Architecture Impact에 갱신 필요로 명시됨 | 두 baseline 문서에 신규 모듈 및 v1 API 흐름 반영 | - -### Non-Blocking Observations - -| # | REQ | Observation | -|---|---|---| -| N-1 | REQ-009 | migration 파일 코드 검토 PASS. DB 환경(Docker Compose) 실행 미검증 — 배포 전 반드시 확인 필요 | -| N-2 | REQ-004 AC2 | degraded/failed 상태 경로 미구현 — REQ Notes에서 이번 반복 OOS로 명시되어 릴리스 블로커 아님 | -| N-3 | REQ-005 AC4 | 두 번 발급 시 다른 runtime_id — 코드 검토 통과. 전용 테스트 없음 | -| N-4 | REQ-010 AC5 | InvalidToken 경로 전용 테스트 없음 | - diff --git a/docs/batches/bat-002_20260502_service-token-rotate-cli/index.md b/docs/batches/bat-002_20260502_service-token-rotate-cli/index.md deleted file mode 100644 index 34a840b..0000000 --- a/docs/batches/bat-002_20260502_service-token-rotate-cli/index.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-002 Index - -- Status: release-candidate -- Batch: BAT-002 -- Profile: batch-lite -- Created: 2026-05-02 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-012 | service-token rotate CLI 명령 구현 | Interface | Medium | - -## Source Discovery - -- minor-change fast path (Discovery 없음) - -## Scope Summary - -- `pfhub service-token rotate [--force]` CLI 명령 신규 추가 -- `auth_tokens` 행의 `token_hash`, `token_prefix`, `runtime_id` in-place UPDATE -- 메타데이터(`id`, `permission`, `expires_at` 등) 유지 -- DB 스키마 변경 없음 - -## Impacted Area - -- Docs: 없음 (구조/인터페이스/런타임 흐름 변경 없음) -- Modules: - - `app/repositories/portfolio_repository.py` (Protocol stub 추가) - - `app/repositories/postgres.py` (UPDATE 메서드 추가) - - `app/services/auth_service.py` (rotate_service_token 메서드 추가) - - `app/cli/commands/service_tokens.py` (rotate 명령 추가) -- Tests: - - `tests/test_cli.py` (rotate 성공/없는이름/revoked 3개 케이스) - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Implementation | implementation.md | pending | -| Verification | [verification.md](./verification.md) | verified | diff --git a/docs/batches/bat-002_20260502_service-token-rotate-cli/planning.md b/docs/batches/bat-002_20260502_service-token-rotate-cli/planning.md deleted file mode 100644 index 1daa23d..0000000 --- a/docs/batches/bat-002_20260502_service-token-rotate-cli/planning.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-002 Planning - -- Status: confirmed -- Batch: BAT-002 -- Profile: batch-lite - -## Included REQ - -- REQ-012: service-token rotate CLI 명령 구현 - -## Delivery Plan - -### Out of Scope -- `expires_at` 변경 옵션 (이번 rotate는 secret 교체만) -- HTTP API rotate 엔드포인트 (CLI 전용) - -### 구현 순서 - -#### 1단계: Repository 레이어 -- `app/repositories/portfolio_repository.py` — `rotate_service_token(token_id, token_hash, token_prefix, runtime_id) -> AuthToken` Protocol stub 추가 -- `app/repositories/postgres.py` — `rotate_service_token` UPDATE 구현 - ```sql - UPDATE auth_tokens - SET token_hash = %s, token_prefix = %s, runtime_id = %s - WHERE id = %s AND revoked_at IS NULL - RETURNING id, user_id, name, token_prefix, ... - ``` - - 대상 행이 없거나 revoked이면 ValueError 반환 - -#### 2단계: Service 레이어 -- `app/services/auth_service.py` — `rotate_service_token(name) -> IssuedServiceToken` 추가 - - `get_service_token_by_name(name)` 조회 → 없으면 ValueError - - revoked 상태이면 ValueError - - `expires_at is not None`이면 ValueError ("만료일이 설정된 토큰은 rotate할 수 없습니다") - - `generate_token()` + `hash_token()` + `token_prefix()` + `uuid.uuid4()` 로 새 값 생성 - - `repository.rotate_service_token(...)` 호출 - - `IssuedServiceToken(token=raw_token, auth_token=updated_token)` 반환 - -#### 3단계: CLI 명령 -- `app/cli/commands/service_tokens.py` — `rotate` 명령 추가 - - `--force` 없으면 확인 프롬프트 - - issue 명령과 동일한 1회 원문 출력 패턴 적용 - -#### 4단계: 테스트 -- `tests/test_cli.py` — 인메모리 repo에 `rotate_service_token` stub 추가 및 케이스 추가 - - rotate 성공 (no-expiry 토큰, 새 token 출력 확인) - - 없는 이름 → 오류 - - revoked 토큰 → 오류 - - expires_at가 설정된 토큰 → 오류 - -## Dependencies - -- Internal: 없음 (기존 `get_service_token_by_name`, `generate_token`, `hash_token` 활용) -- External: 없음 (DB 스키마 변경 없음) - -## Design Gate - -- Design Required Before Implementation: no -- Reason: DB 스키마 변경 없음. 기존 패턴(issue/revoke) 동일하게 적용. 결정 사항 없음. diff --git a/docs/batches/bat-002_20260502_service-token-rotate-cli/verification.md b/docs/batches/bat-002_20260502_service-token-rotate-cli/verification.md deleted file mode 100644 index 1bf95fd..0000000 --- a/docs/batches/bat-002_20260502_service-token-rotate-cli/verification.md +++ /dev/null @@ -1,90 +0,0 @@ -# BAT-002 Verification - -- Status: verified -- Batch: BAT-002 -- Profile: batch-lite -- Verified: 2026-05-02 - -## Included REQ - -- REQ-012: service-token rotate CLI 명령 구현 - -## Architecture Impact - -- Design Gate (from planning.md): Design Required Before Implementation — **no** -- Reason: DB 스키마 변경 없음. 기존 패턴(issue/revoke) 동일하게 적용. 결정 사항 없음. -- Confirmed: 구조/인터페이스/런타임 흐름 변경 없음 — **no architecture impact confirmed** -- Baseline Docs Update Required: no - -## Acceptance Mapping - -### REQ-012 AC-1 -> Given `expires_at IS NULL`인 유효한 service-token 이름, When `pfhub service-token rotate --force`를 실행, Then DB 행의 `token_hash`, `token_prefix`, `runtime_id`가 새 값으로 변경되고 `id`, `permission`, `expires_at`(NULL)은 기존과 동일하다. - -- Evidence: `test_cli_service_token_rotate_no_expiry_succeeds` - - `rotated.token_prefix != "oldpref"` 검증 - - `rotated.runtime_id != "old-runtime-id"` 검증 - - `expires_at=None` 유지 검증 (rotate 후 기존 token 조회 → expires_at 변경 없음) -- Result: **PASS** - -### REQ-012 AC-2 -> Given rotate 성공, When CLI 출력, Then 새 token 원문이 화면에 1회 출력되고 "이 토큰은 지금 1회만 표시" 안내 메시지가 함께 표시된다. - -- Evidence: `test_cli_service_token_rotate_no_expiry_succeeds` - - `result.stdout`에 `"token:"` 포함 확인 - - CLI 출력 패턴: `⚠ 갱신된 토큰 원문은 지금 1회만 표시됩니다.` + `token: ` -- Result: **PASS** - -### REQ-012 AC-3 -> Given 존재하지 않는 token 이름, When `rotate`, Then 오류 메시지를 출력하고 종료 코드 1을 반환한다. - -- Evidence: `test_cli_service_token_rotate_unknown_name_exits_error` - - `result.exit_code == 1` 검증 - - `"nonexistent" in result.stdout` 검증 -- Result: **PASS** - -### REQ-012 AC-4 -> Given revoked 상태의 token, When `rotate`, Then 오류 메시지를 출력하고 종료 코드 1을 반환한다. - -- Evidence: `test_cli_service_token_rotate_revoked_token_exits_error` - - `result.exit_code == 1` 검증 - - `"old-token" in result.stdout` 검증 -- Result: **PASS** - -### REQ-012 AC-5 -> Given `expires_at`이 NULL이 아닌(만료일이 설정된) token, When `rotate`, Then "만료일이 설정된 토큰은 rotate할 수 없습니다" 오류를 출력하고 종료 코드 1을 반환한다. - -- Evidence: `test_cli_service_token_rotate_with_expiry_exits_error` - - `result.exit_code == 1` 검증 - - `"expiring" in result.stdout` 검증 -- Result: **PASS** - -### REQ-012 AC-6 -> Given `--force` 없이 실행, When CLI, Then 확인 프롬프트를 표시하고 거절 시 변경 없이 종료한다. - -- Evidence: `test_cli_service_token_rotate_can_abort` - - `input="n\n"` 입력으로 abort 시뮬레이션 - - `result.exit_code == 0` 검증 - - `token.token_prefix == "pref"` (변경 없음) 검증 -- Result: **PASS** - -## Test Summary - -``` -tests/test_cli.py — 37 passed (신규 5개 포함, 회귀 없음) - -rotate 관련: - test_cli_service_token_rotate_no_expiry_succeeds PASSED - test_cli_service_token_rotate_unknown_name_exits_error PASSED - test_cli_service_token_rotate_revoked_token_exits_error PASSED - test_cli_service_token_rotate_with_expiry_exits_error PASSED - test_cli_service_token_rotate_can_abort PASSED -``` - -## Blocking Issues - -없음. - -## Ready for Release - -- true diff --git a/docs/batches/bat-003_20260502_portfolio-list-user-required/implementation.md b/docs/batches/bat-003_20260502_portfolio-list-user-required/implementation.md deleted file mode 100644 index ca2694c..0000000 --- a/docs/batches/bat-003_20260502_portfolio-list-user-required/implementation.md +++ /dev/null @@ -1,58 +0,0 @@ -# BAT-003 Implementation - -- Status: completed -- Batch: BAT-003 -- Profile: batch-lite - -## Plan Summary - -- CLI `pfhub portfolio list`에 `--user ` 필수 옵션 추가 -- `_with_service_and_repo` 헬퍼로 repository 직접 접근 패턴 구현 -- `repository.get_user_by_username(user)` 호출 → None이면 ValueError → exit code 1 -- `service.list_portfolios(owner_user_id=owner.id, ...)` 소유자 필터 항상 전달 -- README 예제 라인 갱신, breaking change 반영 -- 기존 테스트 2개 수정 + 신규 4개 추가 - -## Changed Files - -| File | Change | -|------|--------| -| `app/cli/commands/portfolio.py` | `_with_service_and_repo` 헬퍼 추가, `list_portfolios` `--user` 필수화 및 소유자 필터 적용 | -| `tests/test_cli.py` | `test_cli_list_renders_table`, `test_cli_list_supports_tag_filters` 수정; AC 1~4 커버 4개 케이스 추가 | -| `README.md` | `pfhub portfolio list` → `pfhub portfolio list --user ` 갱신 | - -## Execution Log - -### 구현 결정 사항 - -- `--user` 옵션: `typer.Option(None, ...)` + 수동 None 체크로 exit code 1 보장 (typer required 옵션은 exit code 2이므로 AC-1 불일치) -- username → user_id 조회: `_with_service_and_repo` 콜백 내 `repository.get_user_by_username(user)` 직접 호출 -- 존재하지 않는 username: `ValueError` raise → `execute_command`가 exit code 1로 처리 -- `_with_service_and_repo`: 기존 `_with_service`와 동일 패턴, callback 인자에 `repository` 추가 - -### 기존 테스트 수정 - -- `test_cli_list_renders_table`: `FakeRepo()` → user alice 생성 + `portfolio.owner_user_id` 설정 + `--user alice` 추가 -- `test_cli_list_supports_tag_filters`: 동일 패턴 적용 - -## Validation - -``` -pytest tests/test_cli.py -k "test_cli_list_renders_table or test_cli_list_supports_tag_filters or test_cli_portfolio_list" -v -``` - -``` -6 passed -``` - -``` -pytest tests/test_cli.py -v -``` - -``` -41 passed in 1.22s (전체 회귀, 기존 테스트 전원 통과) -``` - -## Remaining Risks - -- 없음 diff --git a/docs/batches/bat-003_20260502_portfolio-list-user-required/index.md b/docs/batches/bat-003_20260502_portfolio-list-user-required/index.md deleted file mode 100644 index 94f6f4c..0000000 --- a/docs/batches/bat-003_20260502_portfolio-list-user-required/index.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-003 Index - -- Status: release-candidate -- Batch: BAT-003 -- Profile: batch-lite -- Created: 2026-05-02 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-013 | CLI `portfolio list --user` 필수화 및 소유자 기반 조회 | Interface | Medium | - -## Source Discovery - -- [dcy-003_20260502_portfolio-list-user-required](../../discovery/dcy-003_20260502_portfolio-list-user-required.md) - -## Scope Summary - -- CLI `pfhub portfolio list` 명령에 `--user ` 필수 옵션 추가 -- username → owner_user_id(UUID) 변환 로직 추가 (repository.get_user_by_username 직접 호출) -- `PortfolioService.list_portfolios(owner_user_id=...)` 호출 시 소유자 필터 항상 전달 -- `--user` 미입력 시 exit code 1 오류 종료, 존재하지 않는 username 시 오류 종료 -- README 예제 라인 갱신 (breaking change 안내 포함) -- DB 스키마 변경 없음, HTTP API 변경 없음 - -## Impacted Area - -- Docs: - - `README.md` — `pfhub portfolio list` 예제 라인 `--user ` 옵션 추가, breaking change 안내 추가 -- Modules: - - `app/cli/commands/portfolio.py` — `list_portfolios` 명령 시그니처 및 서비스 호출부 수정 -- Tests: - - `tests/test_cli.py` — AC 1~5 커버 테스트 케이스 4개 추가 - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | verified | diff --git a/docs/batches/bat-003_20260502_portfolio-list-user-required/planning.md b/docs/batches/bat-003_20260502_portfolio-list-user-required/planning.md deleted file mode 100644 index 6d54301..0000000 --- a/docs/batches/bat-003_20260502_portfolio-list-user-required/planning.md +++ /dev/null @@ -1,89 +0,0 @@ -# BAT-003 Planning - -- Status: draft -- Batch: BAT-003 -- Profile: batch-lite - -## Included REQ - -- REQ-013: CLI `portfolio list --user` 필수화 및 소유자 기반 조회 - -## Out of Scope - -- `--all` 관리자 전체 조회 옵션 (후속 Discovery로 분리) -- HTTP API `GET /portfolios` 접근 통제 변경 (현행 유지) -- CLI에 `_ownership_required` 설정 플래그 도입 - -## 구현 순서 - -### 1단계: CLI 명령 수정 - -- `app/cli/commands/portfolio.py` — `list_portfolios` 명령 수정 - - 현재 시그니처: - ```python - @portfolio_app.command("list") - def list_portfolios( - ctx: typer.Context, - broker: str | None = ..., - status: str | None = ..., - include_tags: ..., - exclude_tags: ..., - match_mode: ..., - ) - ``` - - 변경 내용: - - `user: str = typer.Option(..., "--user", help="Filter by username (required).")` 필수 옵션 추가 - - `_with_service` callback 안에서 `repository.get_user_by_username(user)` 호출 - - user가 `None`이면 오류 출력 + `raise typer.Exit(code=1)` (typer의 `required=True` 또는 직접 검증) - - 조회 결과가 `None`이면 "사용자를 찾을 수 없습니다" 메시지 + `raise typer.Exit(code=1)` - - `service.list_portfolios(owner_user_id=found_user.id, ...)` 로 호출 - -- 참고: `_with_service` 헬퍼는 `PortfolioService`만 생성하므로, username → user_id 조회는 callback 내부에서 `repository.get_user_by_username(user)`를 직접 호출한다. callback 인자로 `(service, app_context)` 대신 repository도 사용 가능 — 기존 `_with_service`에서 `repository = app_context.repository_factory(settings)` 반환 객체를 callback에 노출하는 방식으로 처리한다. - - 구현 패턴 (예시): - ```python - def _with_service_and_repo(ctx, callback): - app_context = ctx.obj - repository = app_context.repository_factory(app_context.settings) - service = PortfolioService(repository) - try: - result = execute_command( - app_context.console, - callback=lambda: callback(service, repository, app_context), - ) - return result - finally: - if callable(getattr(type(repository), "close", None)): - repository.close() - ``` - 또는 기존 `_with_service` callback 안에서 `service._repository`를 통해 접근하는 방법도 검토한다. 구현 시 결정. - -### 2단계: README 수정 - -- `README.md` — CLI 예제 섹션의 `pfhub portfolio list` 라인을 `pfhub portfolio list --user `으로 변경 -- breaking change 안내 문구 추가 (Service token issue policy 아래 또는 CLI 섹션 내) - -### 3단계: 테스트 추가 - -- `tests/test_cli.py` — 4개 테스트 케이스 추가: - 1. `test_cli_portfolio_list_without_user_exits_error` - - FakeRepo 준비, `--user` 없이 호출 → `exit_code == 1`, 오류 메시지 포함 확인 - 2. `test_cli_portfolio_list_with_valid_user_filters_by_owner` - - FakeRepo에 복수 사용자 + 포트폴리오 준비, `--user alice` 호출 → alice 소유 항목만 반환 - 3. `test_cli_portfolio_list_with_unknown_user_exits_error` - - 존재하지 않는 username으로 호출 → `exit_code == 1`, "찾을 수 없습니다" 포함 확인 - 4. `test_cli_portfolio_list_in_memory_mode_filters_owner` - - in-memory 모드에서도 동일하게 소유자 필터 적용 확인 (AC 4) - -## Dependencies - -- Internal: - - `app/repositories/portfolio_repository.py` — `get_user_by_username` Protocol stub 존재 확인 완료 (line 16) - - `app/repositories/postgres.py` — `get_user_by_username` 구현 존재 확인 완료 (line 419) - - `tests/test_cli.py` — `FakeRepo.get_user_by_username` 존재 확인 완료 (line 109) -- External: 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: DB 스키마 변경 없음, HTTP API 변경 없음. CLI 옵션 추가 + repository 메서드 직접 호출 패턴은 기존 service-token issue 구현과 동일 수준. 구조적 결정 사항 없음. diff --git a/docs/batches/bat-003_20260502_portfolio-list-user-required/verification.md b/docs/batches/bat-003_20260502_portfolio-list-user-required/verification.md deleted file mode 100644 index 73e3a9b..0000000 --- a/docs/batches/bat-003_20260502_portfolio-list-user-required/verification.md +++ /dev/null @@ -1,72 +0,0 @@ -# BAT-003 Verification - -- Status: verified -- Batch: BAT-003 -- Profile: batch-lite - -## Architecture Impact Check - -- Design Gate (from planning.md): no — DB 스키마 변경 없음, HTTP API 변경 없음 -- Actual Architecture Impact: none confirmed — `app/cli/commands/portfolio.py` 내부 헬퍼 추가, Protocol stub/구현체 변경 없음 -- Baseline Document Sync Required: no - -## Acceptance Mapping - -### AC 1: `--user` 없이 실행 → exit code 1 + 오류 메시지 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_list_without_user_exits_error` | -| Verification | `runner.invoke(app, ["portfolio", "list"])` → `exit_code == 1`, `"--user" in result.stdout` | -| Result | ✅ PASSED | - -### AC 2: `--user alice` → alice 소유 포트폴리오만 반환 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_list_with_valid_user_filters_by_owner` | -| Verification | alice 포트폴리오만 등록 → alice 조회 시 `005930:tv` 포함, bob 조회 시 미포함 확인 | -| Result | ✅ PASSED | - -### AC 3: 존재하지 않는 username → 오류 메시지 + exit code 1 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_list_with_unknown_user_exits_error` | -| Verification | `--user nobody` → `exit_code == 1`, `"nobody" in result.stdout` | -| Result | ✅ PASSED | - -### AC 4: in-memory 모드에서도 동일 동작 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_list_in_memory_mode_filters_owner` | -| Verification | FakeRepo(in-memory) 사용, `--user alice` → alice 소유 포트폴리오 반환 확인 | -| Result | ✅ PASSED | - -### AC 5: 오류 형식이 기존 CLI와 일관성 유지 - -| 항목 | 내용 | -|------|------| -| Evidence | 코드 검토 — `execute_command`의 `ValueError` 핸들러(`console.print + raise typer.Exit(code=1)`)와 동일 패턴 사용 | -| Verification | `--user` 미입력 → `ctx.obj.console.print(...)` + `raise typer.Exit(code=1)`, 미등록 user → `ValueError` → `execute_command` 핸들러 동일 경로 | -| Result | ✅ 충족 | - -## Test Execution - -``` -pytest tests/test_cli.py -v -41 passed in 1.22s -``` - -- 신규 케이스 6개 (`test_cli_portfolio_list_*`, 수정된 `test_cli_list_renders_table`, `test_cli_list_supports_tag_filters`) 포함 전체 통과 -- 기존 37개 케이스 회귀 없음 - -## Blocking Issues - -- 없음 - -## Result - -- Release Ready: yes -- Notes: batch-lite, design 없음, planning의 Design Gate = no, architecture impact = none confirmed diff --git a/docs/batches/bat-004_20260502_portfolio-create-user-required/implementation.md b/docs/batches/bat-004_20260502_portfolio-create-user-required/implementation.md deleted file mode 100644 index 41015e9..0000000 --- a/docs/batches/bat-004_20260502_portfolio-create-user-required/implementation.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-004 Implementation - -- Status: completed -- Batch: BAT-004 -- Profile: batch-lite - -## Plan Summary - -- CLI `pfhub portfolio create`에 `--user ` 필수 옵션 추가 -- BAT-003에서 도입한 `_with_service_and_repo` 헬퍼 재사용 -- `repository.get_user_by_username(user)` 호출 → None이면 ValueError → exit code 1 -- `service.create_portfolio(payload, owner_user_id=owner.id)` 실제 user UUID 전달 -- README 예제 라인 갱신 -- 기존 테스트 3개 수정 + 신규 3개 추가 - -## Changed Files - -| File | Change | -|------|--------| -| `app/cli/commands/portfolio.py` | `create_portfolio` 함수에 `--user` 옵션 추가, `_with_service` → `_with_service_and_repo` 교체, username→UUID 조회 후 `owner_user_id` 전달 | -| `tests/test_cli.py` | `test_cli_create_buy_and_delete_flow`, `test_cli_write_command_prompts_before_create`, `test_cli_write_command_can_abort` 수정; AC 1~3 커버 신규 3개 케이스 추가 | -| `README.md` | `pfhub portfolio create` → `pfhub portfolio create ... --user ` 갱신 | - -## Execution Log - -### 구현 결정 사항 - -- `--user` 옵션: `typer.Option(None, "--user", ...)` + 수동 None 체크로 exit code 1 보장 (BAT-003과 동일 패턴) -- `_confirm` 호출 전에 None 체크를 배치하여 owner 미지정 시 확인 프롬프트 없이 즉시 종료 -- callback 내 username → user_id 조회: `repository.get_user_by_username(user)` → None이면 `ValueError` -- `service.create_portfolio(payload, owner_user_id=owner.id)`: `portfolio_service.py` 시그니처 변경 불필요 (기존 키워드 인자 그대로 활용) - -### 기존 테스트 수정 - -- `test_cli_create_buy_and_delete_flow`: alice user 생성 + `--user alice` 추가, `owner_user_id` 검증 BUY 전에 배치 (delete 후 `_portfolio`가 None이 되므로) -- `test_cli_write_command_prompts_before_create`: alice user 생성 + `--user alice` 추가 -- `test_cli_write_command_can_abort`: alice user 생성 + `--user alice` 추가 - -## Validation - -``` -pytest tests/test_cli.py -k "test_cli_portfolio_create or test_cli_create or test_cli_write_command" -v -``` - -``` -6 passed -``` - -``` -pytest tests/test_cli.py -v -``` - -``` -44 passed in 1.37s (전체 회귀, 기존 테스트 전원 통과) -``` - -## Remaining Risks - -- 없음 diff --git a/docs/batches/bat-004_20260502_portfolio-create-user-required/index.md b/docs/batches/bat-004_20260502_portfolio-create-user-required/index.md deleted file mode 100644 index b74c363..0000000 --- a/docs/batches/bat-004_20260502_portfolio-create-user-required/index.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-004 Index - -- Status: release-candidate -- Batch: BAT-004 -- Profile: batch-lite -- Created: 2026-05-02 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-014 | CLI `portfolio create --user` 필수화 및 소유자 설정 | Interface | Medium | - -## Source Discovery - -- minor-change fast path (Discovery 없음) - -## Scope Summary - -- CLI `pfhub portfolio create` 명령에 `--user ` 필수 옵션 추가 -- `_with_service_and_repo` 헬퍼(REQ-013 도입) 재사용, `repository.get_user_by_username(user)` 호출 -- `PortfolioService.create_portfolio(owner_user_id=owner.id, ...)` 실제 UUID 전달 -- `--user` 미입력 시 exit code 1, 미존재 username 시 exit code 1 -- README 예제 갱신 -- DB 스키마 변경 없음, HTTP API 변경 없음 - -## Impacted Area - -- Docs: - - `README.md` — `pfhub portfolio create` 예제 라인 `--user ` 추가 -- Modules: - - `app/cli/commands/portfolio.py` — `create_portfolio` 명령 수정 -- Tests: - - `tests/test_cli.py` — 기존 `test_cli_create_buy_and_delete_flow` 수정 + AC 1~4 커버 케이스 추가 - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | verified | diff --git a/docs/batches/bat-004_20260502_portfolio-create-user-required/planning.md b/docs/batches/bat-004_20260502_portfolio-create-user-required/planning.md deleted file mode 100644 index 3be2b33..0000000 --- a/docs/batches/bat-004_20260502_portfolio-create-user-required/planning.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-004 Planning - -- Status: confirmed -- Batch: BAT-004 -- Profile: batch-lite - -## Included REQ - -- REQ-014: CLI `portfolio create --user` 필수화 및 소유자 설정 - -## Out of Scope - -- HTTP API `POST /portfolios` owner 설정 변경 -- `owner_user_id` 기본값(`"system"`) 서비스 레이어 변경 - -## 구현 순서 - -### 1단계: CLI 명령 수정 - -- `app/cli/commands/portfolio.py` — `create_portfolio` 명령 수정 - - `user: str | None = typer.Option(None, "--user", help="Owner username (required).")` 추가 - - `_with_service_and_repo` 콜백으로 교체 - - callback 내부: - - `user is None` → `console.print(...)` + `raise typer.Exit(code=1)` - - `repository.get_user_by_username(user)` → `None`이면 `raise ValueError(...)` - - `service.create_portfolio(payload, owner_user_id=owner.id)` 호출 - -### 2단계: README 수정 - -- `README.md` — `pfhub portfolio create` 예제 라인에 `--user alice` 추가 - -### 3단계: 테스트 - -- `tests/test_cli.py` - - `test_cli_create_buy_and_delete_flow`: `--user alice` 추가, repo에 alice 생성 - - `test_cli_write_command_prompts_before_create`: 동일 수정 - - `test_cli_write_command_can_abort`: 동일 수정 - - `test_cli_portfolio_create_without_user_exits_error` (신규) - - `test_cli_portfolio_create_with_valid_user_sets_owner` (신규) - - `test_cli_portfolio_create_with_unknown_user_exits_error` (신규) - -## Dependencies - -- Internal: `_with_service_and_repo` (BAT-003 도입, 이미 존재) -- External: 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: 기존 `_with_service_and_repo` 패턴 재사용. 구조/스키마 변경 없음. diff --git a/docs/batches/bat-004_20260502_portfolio-create-user-required/verification.md b/docs/batches/bat-004_20260502_portfolio-create-user-required/verification.md deleted file mode 100644 index 402db9c..0000000 --- a/docs/batches/bat-004_20260502_portfolio-create-user-required/verification.md +++ /dev/null @@ -1,65 +0,0 @@ -# BAT-004 Verification - -- Status: verified -- Batch: BAT-004 -- Profile: batch-lite - -## Architecture Impact Check - -- Design Gate (from planning.md): no — DB 스키마 변경 없음, HTTP API 변경 없음 -- Actual Architecture Impact: none confirmed — `app/cli/commands/portfolio.py` 내 기존 `_with_service_and_repo` 헬퍼 재사용, Protocol stub/구현체 변경 없음 -- Baseline Document Sync Required: no - -## Acceptance Mapping - -### AC 1: `--user` 없이 실행 → exit code 1 + 오류 메시지 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_create_without_user_exits_error` | -| Verification | `runner.invoke(app, ["portfolio", "create", "005930:tv", "--symbol", "005930", "--force"])` → `exit_code == 1`, `"--user" in result.stdout` | -| Result | ✅ PASSED | - -### AC 2: `--user alice` → 생성된 포트폴리오의 `owner_user_id`가 alice UUID로 설정됨 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_create_with_valid_user_sets_owner` | -| Verification | alice user 생성 → `--user alice --force` 실행 → `repo._portfolio.owner_user_id == user.id` 확인 | -| Result | ✅ PASSED | - -### AC 3: 존재하지 않는 username → 오류 메시지 + exit code 1 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_create_with_unknown_user_exits_error` | -| Verification | `--user nobody` → `exit_code == 1`, `"nobody" in result.stdout` | -| Result | ✅ PASSED | - -### AC 4: in-memory 모드에서도 동일 동작 - -| 항목 | 내용 | -|------|------| -| Evidence | `test_cli_portfolio_create_with_valid_user_sets_owner` (FakeRepo 사용) | -| Verification | FakeRepo(in-memory) 환경에서 alice UUID가 `owner_user_id`로 설정됨 확인 | -| Result | ✅ PASSED | - -### AC 5: 오류 형식이 기존 CLI와 일관성 유지 - -| 항목 | 내용 | -|------|------| -| Evidence | 코드 검토 — BAT-003 `list_portfolios` 구현과 동일한 패턴 사용 | -| Verification | `--user` 미입력 → `ctx.obj.console.print(...)` + `raise typer.Exit(code=1)`, 미등록 user → `ValueError` → `execute_command` 핸들러 동일 경로 | -| Result | ✅ 충족 | - -## Test Execution - -``` -pytest tests/test_cli.py -v -44 passed in 1.37s -``` - -## Regression - -- BAT-003 추가 테스트(portfolio list 관련 4개) 전원 PASSED — 회귀 없음 -- 기존 `test_cli_create_buy_and_delete_flow`, `test_cli_write_command_prompts_before_create`, `test_cli_write_command_can_abort` 수정 후 전원 PASSED diff --git a/docs/batches/bat-005_20260503_broker-credential-cli/implementation.md b/docs/batches/bat-005_20260503_broker-credential-cli/implementation.md deleted file mode 100644 index 600d668..0000000 --- a/docs/batches/bat-005_20260503_broker-credential-cli/implementation.md +++ /dev/null @@ -1,53 +0,0 @@ -# BAT-005 Implementation Log - -- Status: completed -- Batch: BAT-005 -- Profile: batch-lite -- Completed: 2026-05-03 - -## 구현 내역 - -### 1. `app/repositories/postgres.py` — `delete_broker_credential` 추가 - -```python -def delete_broker_credential(self, user_id: str, broker: str) -> bool: - with self._connect().cursor() as cur: - cur.execute( - "DELETE FROM broker_credentials WHERE user_id = %s AND broker = %s", - (user_id, broker), - ) - return cur.rowcount > 0 -``` - -### 2. `app/cli/commands/credential.py` — 신규 생성 - -- `credential_app = typer.Typer(help="Broker credential management commands.")` -- `_with_service(ctx, callback)`: repository 생성 → execute_command → finally close. RuntimeError도 캐치해 stdout 출력 후 exit 1. -- `set` 명령: `--user`, `--broker` (기본값 `kis`), `--account-ref`, `--app-key`, `--app-secret`, `--force` -- `list` 명령: `--user` — encrypted_payload 제외 dict 리스트 출력 -- `remove` 명령: `--user`, `--broker`, `--force` — delete_broker_credential 호출 - -### 3. `app/cli/main.py` — `credential_app` 등록 - -```python -from app.cli.commands.credential import credential_app -... -app.add_typer(credential_app, name="credential") -``` - -### 4. `tests/test_cli.py` - -- `FakeRepo.__init__`: `_broker_credentials: dict[tuple[str, str], BrokerCredential] = {}` 추가 -- `FakeRepo.upsert/list/get/delete_broker_credential` 메서드 추가 -- `_settings_with_enc_key()`, `build_runner_with_enc_key()` 헬퍼 추가 -- 신규 테스트 5개 추가 - -### 5. `docs/project-structure.md` — CLI 모듈 목록 갱신 - -- `app/cli/` 섹션에 `credential` 명령 그룹 추가 -- primary runtime 설명에 `broker credentials` 추가 - -## 설계 결정 - -- dict 리스트 출력은 `render_output`의 타입 매칭을 우회해 `console.print(item)` 직접 호출 (기존 render_output이 등록된 타입만 처리함) -- `RuntimeError`(`CREDENTIAL_ENCRYPTION_KEY` 미설정)는 `_with_service` 헬퍼에서 캐치해 stdout으로 출력 후 exit 1 처리 diff --git a/docs/batches/bat-005_20260503_broker-credential-cli/index.md b/docs/batches/bat-005_20260503_broker-credential-cli/index.md deleted file mode 100644 index 0611e6b..0000000 --- a/docs/batches/bat-005_20260503_broker-credential-cli/index.md +++ /dev/null @@ -1,45 +0,0 @@ -# BAT-005 Index - -- Status: release-candidate -- Batch: BAT-005 -- Profile: batch-lite -- Created: 2026-05-03 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-015 | CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) | Interface | Medium | - -## Source Discovery - -- minor-change fast path (Discovery 없음) - -## Scope Summary - -- CLI `pfhub credential set/list/remove` 명령 그룹 신규 추가 -- `app/cli/commands/credential.py` 신규 생성 (`credential_app`) -- `app/cli/main.py`에 `credential_app` 등록 -- `app/repositories/postgres.py`에 `delete_broker_credential` 메서드 추가 -- 기존 `broker_credentials` 테이블 / `upsert_broker_credential` / `list_broker_credentials` 재사용 -- 기존 Fernet 암호화 인프라 (`app/core/encryption.py`) 재사용 -- DB 스키마 변경 없음, HTTP API 변경 없음 - -## Impacted Area - -- Docs: - - `docs/project-structure.md` — CLI 모듈 목록에 `credential.py` 추가 -- Modules: - - `app/cli/commands/credential.py` (신규) - - `app/cli/main.py` (`credential_app` 등록) - - `app/repositories/postgres.py` (`delete_broker_credential` 추가) -- Tests: - - `tests/test_cli.py` — credential 명령 통합 테스트 추가 - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | passed | diff --git a/docs/batches/bat-005_20260503_broker-credential-cli/planning.md b/docs/batches/bat-005_20260503_broker-credential-cli/planning.md deleted file mode 100644 index 7b6573a..0000000 --- a/docs/batches/bat-005_20260503_broker-credential-cli/planning.md +++ /dev/null @@ -1,73 +0,0 @@ -# BAT-005 Planning - -- Status: confirmed -- Batch: BAT-005 -- Profile: batch-lite - -## Included REQ - -- REQ-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) - -## Out of Scope - -- HTTP API 엔드포인트 추가 (별도 REQ 대상) -- 브로커 인증 정보 복호화 출력 (보안 정책상 CLI에서 plain text 노출 금지) -- 브로커 종류 확장 (현재는 `kis` 기본값만 처리) - -## 구현 순서 - -### 1단계: repository 메서드 추가 - -- `app/repositories/postgres.py` - - `delete_broker_credential(user_id: str, broker: str) -> bool` 추가 - - 레코드 존재 시 삭제 후 `True`, 미존재 시 `False` 반환 - -### 2단계: CLI 명령 모듈 신규 생성 - -- `app/cli/commands/credential.py` - - `credential_app = typer.Typer(help="Broker credential management commands.")` - - `_with_service(ctx, callback)` 헬퍼: repository 생성 → callback 실행 → finally close - - `set` 명령: `--user`, `--broker` (기본값 `kis`), `--account-ref`, `--app-key`, `--app-secret` - - `get_user_by_username` → `None`이면 `ValueError` - - `settings.require_credential_encryption_key()` → Fernet 암호화 - - `upsert_broker_credential` 호출 - - `list` 명령: `--user` - - `get_user_by_username` → `None`이면 `ValueError` - - `list_broker_credentials` 호출, `encrypted_payload` 제외 출력 - - `remove` 명령: `--user`, `--broker`, `--force` - - `get_user_by_username` → `None`이면 `ValueError` - - `_confirm(force, ...)` 확인 후 `delete_broker_credential` 호출 - -### 3단계: CLI 등록 - -- `app/cli/main.py` - - `from app.cli.commands.credential import credential_app` import 추가 - - `app.add_typer(credential_app, name="credential")` 등록 - -### 4단계: 문서 갱신 - -- `docs/project-structure.md` — CLI 모듈 목록에 `credential.py` 항목 추가 - -### 5단계: 테스트 - -- `tests/test_cli.py` - - `test_cli_credential_set_creates_record` (신규): alice 생성 후 set 실행 → list에 표시 - - `test_cli_credential_list_hides_payload` (신규): list 출력에 `encrypted_payload` 미포함 확인 - - `test_cli_credential_set_with_unknown_user_exits_error` (신규): 미존재 user → exit code 1 - - `test_cli_credential_remove_deletes_record` (신규): set 후 remove → list에서 사라짐 - - `test_cli_credential_set_requires_encryption_key` (신규): CREDENTIAL_ENCRYPTION_KEY 미설정 → exit code 1 - -## Dependencies - -- Internal: - - `broker_credentials` 테이블 (migration 0014 — 이미 존재) - - `upsert_broker_credential`, `list_broker_credentials`, `get_broker_credential` (이미 존재) - - `app/core/encryption.py` — `encrypt_payload` (이미 존재) - - `app/core/config.py` — `settings.require_credential_encryption_key()` (이미 존재) - - `_confirm`, `execute_command`, `render_output` (기존 CLI 헬퍼 — 이미 존재) -- External: 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: 기존 `_with_service` 헬퍼 패턴 재사용 (`user.py` 동일 구조). 구조/런타임 흐름 변경 없음. diff --git a/docs/batches/bat-005_20260503_broker-credential-cli/verification.md b/docs/batches/bat-005_20260503_broker-credential-cli/verification.md deleted file mode 100644 index fda22ef..0000000 --- a/docs/batches/bat-005_20260503_broker-credential-cli/verification.md +++ /dev/null @@ -1,61 +0,0 @@ -# BAT-005 Verification - -- Status: passed -- Batch: BAT-005 -- Profile: batch-lite -- Verified: 2026-05-03 - -## REQ Coverage - -### REQ-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) - -| AC # | 기준 | 검증 방법 | 결과 | -|------|------|-----------|------| -| AC-1 | alice 사용자 + 유효 enc key → `credential set` 실행 시 broker_credentials 레코드 생성(upsert) | `test_cli_credential_set_creates_record` | ✅ passed | -| AC-2 | `credential list` 출력에 broker, account_ref, credential_version 포함 / encrypted_payload 미포함 | `test_cli_credential_list_hides_payload` | ✅ passed | -| AC-3 | 존재하지 않는 username → exit code 1 + 에러 메시지 | `test_cli_credential_set_with_unknown_user_exits_error` | ✅ passed | -| AC-4 | `credential remove --force` 후 list에서 사라짐 | `test_cli_credential_remove_deletes_record` | ✅ passed | -| AC-5 | CREDENTIAL_ENCRYPTION_KEY 미설정 → exit code 1 + 에러 메시지 포함 | `test_cli_credential_set_requires_encryption_key` | ✅ passed | - -## Test Evidence - -### 실행 명령 -```bash -python -m pytest tests/test_cli.py -q -``` - -### 결과 -``` -49 passed in 1.49s -``` - -- 신규 테스트 5개 모두 passed -- 기존 테스트 44개 regression 없음 - -### 신규 테스트 목록 -| 테스트 | AC | -|--------|----| -| `test_cli_credential_set_creates_record` | AC-1 | -| `test_cli_credential_list_hides_payload` | AC-2 | -| `test_cli_credential_set_with_unknown_user_exits_error` | AC-3 | -| `test_cli_credential_remove_deletes_record` | AC-4 | -| `test_cli_credential_set_requires_encryption_key` | AC-5 | - -## Implementation Summary - -| 파일 | 변경 내용 | -|------|-----------| -| `app/cli/commands/credential.py` | 신규 생성 — `credential_app`, `set`/`list`/`remove` 명령 | -| `app/cli/main.py` | `credential_app` import 및 `app.add_typer(credential_app, name="credential")` 등록 | -| `app/repositories/postgres.py` | `delete_broker_credential(user_id, broker) -> bool` 메서드 추가 | -| `tests/test_cli.py` | `FakeRepo.upsert/list/get/delete_broker_credential`, 신규 테스트 5개 추가 | -| `docs/project-structure.md` | CLI 명령 그룹 목록에 `credential` 추가 | - -## Baseline Document Sync - -- `docs/project-structure.md`: `app/cli/` 섹션에 `credential` 명령 그룹 추가 완료 -- 구조/런타임 흐름/인터페이스 계약 변경 없음 → `runtime-flows.md`, `interface-contract.md` 갱신 불필요 - -## Residual Risk - -- 없음 diff --git a/docs/batches/bat-006_20260511_portfolio-uuid-migration/design.md b/docs/batches/bat-006_20260511_portfolio-uuid-migration/design.md deleted file mode 100644 index e33b128..0000000 --- a/docs/batches/bat-006_20260511_portfolio-uuid-migration/design.md +++ /dev/null @@ -1,92 +0,0 @@ -# BAT-006 Design - -- Status: confirmed -- Batch: BAT-006 - -## Architecture Summary - -`portfolios.portfolio_id`를 사용자 지정 TEXT PK에서 서버 생성 UUID(TEXT)로 교체한다. 기존 `portfolio_id` 문자열은 `name` 컬럼으로 이전하고, `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. CLI는 `` + `--user` 조합으로 portfolio를 식별하고 내부에서 `get_portfolio_by_name_and_user()`를 통해 UUID를 조회한다. HTTP API path parameter와 `GET /v1/assets` `asset_id` 반환은 UUID 기반으로 전환된다. - -## Baseline vs Batch 변경점 - -### docs/project-structure.md (현재 baseline) -- `app/repositories/postgres.py`: SQL 세부사항 담당으로만 기술 — `get_portfolio_by_name_and_user()` 미기재 -- `app/domain/models.py`: `portfolio_id` 필드 의미 변경 미기재 - -### docs/runtime-flows.md (현재 baseline) -- `CLI Operational Command Execution` flow: name+user 식별 방식 미기재 -- `v1 API — GET /v1/assets` flow: `asset_id` 필드 언급 없음 - -### docs/data-model.md (현재 baseline) -- `portfolio_id :: text :: stable portfolio primary key` — UUID 의미 변경 미기재 -- `name` 컬럼 별도 기재 없음 -- `(owner_user_id, name)` UNIQUE 제약 미기재 - -### docs/interface-contract.md (현재 baseline) -- `GET /v1/assets` 응답 명세 없음 (bat-001에서 추가 예정이었으나 아직 반영 안 됨) -- trex-basic 동시 배포 계획 없음 - -## Changed Areas - -- Docs: - - `docs/project-structure.md` - - `docs/runtime-flows.md` - - `docs/data-model.md` - - `docs/interface-contract.md` - - `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` (A-3 철회 주석) -- Modules: - - `migrations/versions/20260511_0015_portfolio_uuid_migration.py` (신규 Alembic revision) - - `app/domain/models.py` (주석 없음, Portfolio dataclass 구조 변경 없음 — 기존 `portfolio_id` + `name` 필드 유지) - - `app/repositories/postgres.py`: - - `create_portfolio()`: `portfolio_id` 파라미터 제거, 서버 UUID 생성 - - `get_portfolio_by_name_and_user()` 신규 메서드 - - `ensure_schema()`: `(owner_user_id, name)` UNIQUE 제약 추가 - - `update_portfolio()`: UniqueViolation 시 ValueError("이미 사용 중인 이름입니다") 반환 - - `app/domain/services.py`: `PortfolioDomainRepository` Protocol 업데이트 - - `app/schemas/portfolio.py`: - - `CreatePortfolioRequest`: `portfolio_id` 필드 제거, `name: str` 필수로 변경, name 유효성 validator 추가 - - `app/services/portfolio_service.py`: `create_portfolio()` 호출부 업데이트 - - `app/cli/commands/portfolio.py`: - - `create`: `portfolio_id` Argument → `name` Argument (필수), `--name` Option 제거 - - `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`: `portfolio_id` → `name` Argument + `--user` Option - - `update`: `portfolio_id` → `name` Argument + `--user` Option + `--new-name` Option (이름 변경용) - - `_resolve_portfolio_id()` helper 추가 - - `_validate_portfolio_name()` helper 추가 -- Interfaces: - - CLI: `portfolio ` → `portfolio --user ` - - HTTP API: `POST /portfolios` request body: `portfolio_id` 필드 제거, `name` 필수화 - - HTTP API: `GET /v1/assets` `assets[].asset_id` → UUID 형식 반환 - -## Key Decisions - -1. **portfolio_id 컬럼 타입 TEXT 유지**: PostgreSQL UUID 타입 변환은 이번 범위 밖. `uuid.uuid4()` 문자열을 TEXT로 저장. -2. **get_portfolio_by_name_and_user() 공유**: CLI와 HTTP API 식별 모두 이 단일 repository 메서드 사용. Protocol에도 추가. -3. **이름 대소문자 구분**: `TV`와 `tv`는 다른 이름. DB UNIQUE 제약도 case-sensitive. -4. **단일 Alembic revision**: name 컬럼 추가 (IF NOT EXISTS), UUID 교체, FK 4개 테이블 갱신, UNIQUE 제약을 하나의 revision에서 수행. IF NOT EXISTS 가드로 재실행 안전성 보장. -5. **update 명령 name 변경**: CLI `update` 명령은 `--new-name` Option으로 이름 변경. `--force` 없으면 confirm 프롬프트. DB UNIQUE 위반 시 `이미 사용 중인 이름입니다: {name}` 오류. -6. **name 유효성**: `[a-zA-Z0-9._:-]`, 1~100자. Pydantic validator + CLI 레이어에서 이중 검사. -7. **comment/tag sub-commands**: REQ-017 명시 범위 밖. 이번 batch에서 변경하지 않음 (UUID 직접 전달 필요). -8. **dcy-002 A-3 공식 철회**: REQ-018 구현 시 해당 discovery 문서에 주석 추가. - -## Edge Cases - -- **기존 portfolio_id 값이 UUID 형식인 경우**: migration이 이미 적용된 것으로 판단하는 guard (UNIQUE constraint 존재 여부로 판단). 재실행 시 skip. -- **FK 고아 레코드**: migration revision 내 트랜잭션 후 orphan 검증 쿼리. 고아 발견 시 RuntimeError 발생. -- **name 허용 문자 범위 외 입력**: CLI validation + Pydantic validator에서 조기 거부. -- **동일 owner_user_id + 동일 name**: DB UNIQUE 위반 → `create_portfolio()`에서 ValueError, `update_portfolio()`에서 UniqueViolation catch → ValueError. -- **존재하지 않는 name+user 조합**: `get_portfolio_by_name_and_user()` None 반환 → `포트폴리오를 찾을 수 없습니다: {name} (user: {username})`. -- **revision 재실행**: `already_migrated` guard (UNIQUE constraint 유무 확인). 이미 적용됐으면 UUID 교체 블록 전체 skip. -- **migration 중 portfolio_tags PK 업데이트**: PostgreSQL은 PK 값 UPDATE 허용. FK 제약 없으므로 안전. - -## Architecture Impact - -both (project-structure + runtime-flows) - -## Reference Doc Update Plan - -- project-structure.md: - - `app/repositories/postgres.py` 항목: `get_portfolio_by_name_and_user(owner_user_id, name) → Portfolio | None` 추가 - - `app/domain/models.py` 항목: `Portfolio.portfolio_id`가 서버 생성 UUID임을 명시 -- runtime-flows.md: - - `CLI Operational Command Execution` flow: `` + `--user` → `get_portfolio_by_name_and_user()` → UUID 조회 흐름 추가 - - `v1 API — GET /v1/assets` flow: `asset_id = portfolio_id (UUID)` 반환 명시 diff --git a/docs/batches/bat-006_20260511_portfolio-uuid-migration/implementation.md b/docs/batches/bat-006_20260511_portfolio-uuid-migration/implementation.md deleted file mode 100644 index ee2492d..0000000 --- a/docs/batches/bat-006_20260511_portfolio-uuid-migration/implementation.md +++ /dev/null @@ -1,63 +0,0 @@ -# BAT-006 Implementation - -- Status: completed -- Batch: BAT-006 - -## Plan Summary - -- REQ-016: 신규 Alembic revision(name 컬럼 추가, portfolio_id UUID 교체, FK 4개 테이블 atomic 갱신, UNIQUE 제약 추가), Portfolio dataclass `name` 필드 추가, `create_portfolio()` UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드 -- REQ-017: CLI portfolio 명령 argument/option 전면 전환(`portfolio_id` → ` --user`), `(user_id, name)` → UUID 서비스 레이어, name 유효성 검사, update name guard -- REQ-018: HTTP API path parameter UUID 전환, `GET /v1/assets` `asset_id` UUID 직렬화, dcy-002 A-3 철회 주석 -- REQ-019: baseline 문서 4개 갱신(project-structure.md, runtime-flows.md, data-model.md, interface-contract.md) - -## Changed Files - -### New Files -- `migrations/versions/20260511_0015_portfolio_uuid_migration.py` — Alembic revision; idempotency guard via uq_portfolios_owner_name constraint presence check; atomic per-portfolio FK update across portfolio_ledger/portfolio_comments/portfolio_tags/ledger_trades -- `tests/test_portfolio_uuid.py` — 14 tests covering UUID creation, name+user resolution, alice/bob isolation, buy/delete flow, name validation, unknown user/portfolio errors, update rename - -### Modified Files -- `app/repositories/postgres.py` - - `ensure_schema()`: replaced `ADD CONSTRAINT IF NOT EXISTS` (not valid PostgreSQL syntax) with `DO $$ IF NOT EXISTS ... ALTER TABLE ADD CONSTRAINT $$` pattern for uq_portfolios_owner_name - - `create_portfolio()`: signature changed from `(portfolio_id: str, *, ..., name: str, ...)` to `(name: str, *, ...)` with server-generated UUID; catches psycopg UniqueViolation → ValueError("이미 사용 중인 이름입니다: {name}") - - Added `get_portfolio_by_name_and_user(owner_user_id, name) -> Portfolio | None` - - `update_portfolio()`: catches UniqueViolation on name update → ValueError("이미 사용 중인 이름입니다: {new_name}") -- `app/domain/services.py` — `PortfolioDomainRepository` Protocol updated: `create_portfolio` new signature, `get_portfolio_by_name_and_user` added, `list_portfolios` Protocol params aligned -- `app/schemas/portfolio.py` — `CreatePortfolioRequest`: removed `portfolio_id` field; `name: str` now required with regex validation `[a-zA-Z0-9._:-]{1,100}` -- `app/services/portfolio_service.py` — `create_portfolio()`: call site updated to `self._repository.create_portfolio(payload.name, owner_user_id=...)` -- `app/cli/commands/portfolio.py` — Full rewrite for REQ-017: `_NAME_RE`, `_validate_portfolio_name()`, `_resolve_portfolio_id()` helpers; all 8 mutation commands switched to ` --user`; `update` uses `--new-name` with `--force` guard; `comment`/`tag` sub-commands keep `portfolio_id` (UUID) directly -- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` — A-3 철회 주석 추가 -- `docs/data-model.md` — portfolios.portfolio_id → UUID, name column, uq_portfolios_owner_name constraint documented -- `docs/project-structure.md` — Added `get_portfolio_by_name_and_user()` to repositories section -- `docs/runtime-flows.md` — Added CLI portfolio name+user resolution flow; GET /v1/assets asset_id UUID note -- `docs/interface-contract.md` — Added v1 consumer API section with simultaneous deployment constraint; CLI portfolio inputs updated -- `tests/test_cli.py` — FakeRepo: `create_portfolio` new signature (generates UUID), `get_portfolio_by_name_and_user` added; `_portfolio()`: name changed to "005930:tv"; 5 tests updated to pass --user and adjust assertions -- `tests/test_portfolio_ownership.py` — Fixed POST body to use `name` instead of `portfolio_id` - -## Execution Log - -1. Created Alembic migration `20260511_0015_portfolio_uuid_migration.py` (REQ-016) -2. Updated `app/repositories/postgres.py`: ensure_schema UNIQUE constraint (DO $$ pattern), create_portfolio UUID generation, get_portfolio_by_name_and_user, update_portfolio UniqueViolation catch (REQ-016) -3. Updated `app/domain/services.py` Protocol (REQ-016) -4. Updated `app/schemas/portfolio.py`: removed portfolio_id, name required + validated (REQ-016, REQ-017) -5. Updated `app/services/portfolio_service.py`: create_portfolio call site (REQ-016) -6. Rewrote `app/cli/commands/portfolio.py`: name+user pattern for all mutation commands (REQ-017) -7. Added dcy-002 A-3 철회 주석 (REQ-018) -8. Updated 4 baseline docs (REQ-019) -9. Created `tests/test_portfolio_uuid.py` with 14 tests -10. Fixed `tests/test_cli.py` FakeRepo + 5 test cases -11. Fixed `tests/test_portfolio_ownership.py` POST body field -12. Fixed `ensure_schema()` syntax: `ADD CONSTRAINT IF NOT EXISTS` → DO $$ block (PostgreSQL compatibility) - -## Validation - -- Full test suite: **129 passed, 0 failed** -- `tests/test_portfolio_uuid.py`: 14/14 passed (UUID generation, name+user resolution, alice/bob isolation, name validation, error messages) -- `tests/test_cli.py`: 49/49 passed (including updated tests for new CLI interface) -- `tests/test_portfolio_ownership.py`: 4/4 passed - -## Remaining Risks - -- Alembic migration `20260511_0015` not verified against real PostgreSQL instance (unit tests use FakeRepo only) -- trex-basic simultaneous deployment required: GET /v1/assets asset_id changes from TEXT to UUID format -- Existing `comment` and `tag` sub-commands still take `portfolio_id` (UUID) directly; operators must obtain UUID from `portfolio list --user` output diff --git a/docs/batches/bat-006_20260511_portfolio-uuid-migration/index.md b/docs/batches/bat-006_20260511_portfolio-uuid-migration/index.md deleted file mode 100644 index aefba2c..0000000 --- a/docs/batches/bat-006_20260511_portfolio-uuid-migration/index.md +++ /dev/null @@ -1,36 +0,0 @@ -# BAT-006 Index - -- BAT ID: BAT-006 -- Profile: standard -- Status: release-candidate -- Source Discovery: dcy-004 -- Date Created: 2026-05-11 -- Folder: docs/batches/bat-006_20260511_portfolio-uuid-migration/ - -## Included REQ - -| REQ | Title | Type | Priority | -| --- | --- | --- | --- | -| REQ-016 | portfolios 테이블 UUID 전환 및 데이터 마이그레이션 | Migration | High | -| REQ-017 | CLI portfolio 명령 — name+user 조합 식별 방식 전환 | Interface | High | -| REQ-018 | HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 | Interface | High | -| REQ-019 | portfolio UUID 전환 관련 baseline 문서 갱신 | Documentation | Medium | - -## Scope Summary - -- `portfolios.portfolio_id`를 사용자 지정 TEXT에서 서버 생성 UUID(TEXT)로 교체하고, 기존 `portfolio_id` 문자열을 `name` 컬럼으로 이전한다. -- `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. -- 기존 데이터와 FK 참조 테이블(portfolio_ledger, portfolio_comments, portfolio_tags, ledger_trades)을 단일 Alembic revision으로 atomic하게 마이그레이션한다. -- CLI portfolio 뮤테이션 명령을 `` + `--user ` 조합 식별 방식으로 전환하고, 내부에서 `(owner_user_id, name)` → UUID 단일 쿼리 조회로 처리한다. -- HTTP API path parameter 및 `GET /v1/assets` `asset_id` 반환을 UUID 기반으로 전환하고, dcy-002 A-3 조항을 공식 철회한다. -- 위 변경 사항을 baseline 문서(docs/project-structure.md, docs/runtime-flows.md, docs/data-model.md, docs/interface-contract.md)에 반영한다. - -## Delivery Documents - -| Stage | File | Status | -| --- | --- | --- | -| Index | [index.md](./index.md) | release-candidate | -| Planning | [planning.md](./planning.md) | confirmed | -| Design | [design.md](./design.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | passed | diff --git a/docs/batches/bat-006_20260511_portfolio-uuid-migration/planning.md b/docs/batches/bat-006_20260511_portfolio-uuid-migration/planning.md deleted file mode 100644 index ac66887..0000000 --- a/docs/batches/bat-006_20260511_portfolio-uuid-migration/planning.md +++ /dev/null @@ -1,71 +0,0 @@ -# BAT-006 Planning - -- Status: confirmed -- Batch: BAT-006 -- Profile: standard - -## Included REQ - -- REQ-016: portfolios 테이블 UUID 전환 및 데이터 마이그레이션 -- REQ-017: CLI portfolio 명령 — name+user 조합 식별 방식 전환 -- REQ-018: HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 -- REQ-019: portfolio UUID 전환 관련 baseline 문서 갱신 - -## Delivery Plan - -### 구현 순서 - -1. **REQ-016** — Alembic revision 추가 및 도메인/레포지터리 수정 - - `migrations/versions/`: 신규 revision (name 컬럼 추가, 기존 portfolio_id → name 복사, UUID 교체, FK 갱신, UNIQUE 제약 추가, 고아 레코드 검증 쿼리 포함) - - `app/domain/models.py`: Portfolio dataclass `name` 필드 명확 선언, `portfolio_id` UUID 문자열 의미 변경 - - `app/repositories/postgres.py`: `create_portfolio()` 서버 UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드 추가 - -2. **REQ-017** — CLI 명령 식별 방식 전환 (REQ-016의 repository 메서드 선행 필요) - - `app/cli/commands/portfolio.py`: `create`, `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update` Argument/Option 전환 (`portfolio_id` argument → `` + `--user`) - - `app/domain/services.py`: `(user_id, name)` → UUID 해석 로직 추가 - - name 유효성 검사(`[a-zA-Z0-9._:-]`, 1~100자), `update` name 변경 시 `--force` 또는 confirm 프롬프트, 중복 name 오류 메시지 처리 포함 - -3. **REQ-018** — HTTP API 전환 (REQ-016의 UUID 스키마 전제) - - `app/api/portfolios.py`: path parameter UUID 기반 전환 - - `app/api/v1_assets.py`: `asset_id` 직렬화에서 `portfolio_id`(UUID) 사용 - - `app/schemas/portfolio.py`, `app/schemas/v1.py`: asset_id/portfolio_id 필드 타입 문서화 - - `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md`: A-3 철회 주석 추가 - -4. **REQ-019** — 문서 갱신 (REQ-016~018 구현 흐름 내 또는 완료 후) - - `docs/project-structure.md`: `get_portfolio_by_name_and_user()` 메서드 및 `portfolio_id` UUID 의미 반영 - - `docs/runtime-flows.md`: CLI `` + `--user` 식별 흐름, `GET /v1/assets` `asset_id` UUID 반환 흐름 반영 - - `docs/data-model.md`: `portfolios` 테이블 `portfolio_id`(UUID TEXT PK), `name`(TEXT), `(owner_user_id, name)` UNIQUE 제약 반영 - - `docs/interface-contract.md`: `GET /v1/assets` `asset_id` UUID 반환 명시, trex-basic 동시 배포 계획 기록 - -### 선행 조건 - -- 로컬 docker compose 기반 PostgreSQL 환경이 실행 가능해야 한다. -- `alembic upgrade head`가 현재 revision까지 적용된 상태에서 신규 revision을 추가 적용할 수 있어야 한다. - -## Dependencies - -- Internal: - - REQ-017과 REQ-018은 REQ-016의 `get_portfolio_by_name_and_user()` 메서드와 UUID schema 전환 전제 - - REQ-019는 REQ-016~018 구현 내용을 기준으로 문서화 -- External: - - trex-basic과 portfolio-hub의 동시 배포 필요 (REQ-018 NFR-3). trex-basic 측 코드 변경은 이번 범위 밖이나 배포 시점 조율 필요. 계획은 REQ-019에서 `docs/interface-contract.md`에 기록. - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: DB schema migration, CLI 인터페이스 전면 전환, HTTP API path parameter 변경이 포함되어 구조/인터페이스/런타임 흐름 영향이 크다. design.md에서 변경 경계, 공유 메서드 설계(`get_portfolio_by_name_and_user()`), edge case(name 유효성, FK 고아, IF NOT EXISTS)를 먼저 확정해야 구현 혼선을 방지할 수 있다. - -## Milestones - -1. REQ-016 구현 완료 및 pytest 기반 UUID 형식·FK 고아 0건·UNIQUE 제약·IF NOT EXISTS 재실행 검증 통과 -2. REQ-017 구현 완료 및 CLI 소유권 검증·name 유효성·중복 변경 거부·alice/bob 독립 portfolio 테스트 통과 -3. REQ-018 구현 완료 및 `GET /v1/assets` asset_id UUID 형식·`POST /v1/assets/{asset_id}/ledger/trades` UUID 호출·HTTP 404 검증 통과 -4. REQ-019 문서 갱신 완료 및 코드 리뷰 기반 검증 (4개 baseline 문서 내용 확인) -5. 전체 acceptance criteria mapping 완료, verification.md 확정, release 준비 상태 확인 - -## Risks - -- **DB migration 안전성**: FK 4개 테이블 atomic 갱신 실패 시 데이터 불일치 위험. 대응: 단일 트랜잭션 + revision 내 고아 레코드 검증 쿼리 + 로컬 환경 rollback 테스트 포함. -- **CLI 하위 호환 단절**: 기존 `portfolio_id` argument 기반 사용 흐름이 즉시 중단됨. 대응: migration 완료 후 CLI 명령 end-to-end 검증 suite 실행. -- **trex-basic 동시 배포 조율 실패**: portfolio-hub만 배포 시 `asset_id` 형식 불일치로 trex-basic 연동 오류 가능. 대응: 배포 조율 계획을 `docs/interface-contract.md`에 명시(REQ-019), 배포 전 trex-basic 팀 협의. -- **IF NOT EXISTS 재실행 안전성 누락**: revision 재실행 오류 발생 가능. 대응: REQ-016 AC-5 검증 포함(단순 재실행 확인). diff --git a/docs/batches/bat-006_20260511_portfolio-uuid-migration/verification.md b/docs/batches/bat-006_20260511_portfolio-uuid-migration/verification.md deleted file mode 100644 index baa8237..0000000 --- a/docs/batches/bat-006_20260511_portfolio-uuid-migration/verification.md +++ /dev/null @@ -1,94 +0,0 @@ -# BAT-006 Verification - -- Status: passed -- Batch: BAT-006 - -## Acceptance Mapping - -- REQ-016: - - AC-1: 기존 portfolio_id → name 복사 및 portfolio_id UUID 형식 교체 검증 (`re.match(r'^[0-9a-f-]{36}$', ...)`) - - Evidence: `test_create_portfolio_assigns_uuid_id` — `uuid.UUID(portfolio.portfolio_id)` 성공 확인 ✓ - - AC-2: portfolio_ledger, portfolio_comments, portfolio_tags, ledger_trades FK 고아 레코드 0건 확인 - - Evidence: `migrations/versions/20260511_0015_portfolio_uuid_migration.py` — 각 portfolio에 대해 FK 4개 테이블 UPDATE 후 orphan 레코드 zero-check RuntimeError 포함 ✓ - - AC-3: 동일 owner_user_id + 동일 name 중복 삽입 시 DB UNIQUE 제약 위반 오류 발생 확인 - - Evidence: `postgres.py` `create_portfolio()` psycopg UniqueViolation catch → ValueError("이미 사용 중인 이름입니다: {name}"); FakeRepo 동일 동작 ✓ - - AC-4: 다른 owner_user_id + 동일 name 정상 삽입 확인 - - Evidence: `test_alice_and_bob_can_have_same_portfolio_name` — alice, bob 각각 "SAMSUNG" 이름으로 독립 portfolio 생성 성공; portfolio_ids 2개 모두 유효한 UUID ✓ - - AC-5: 신규 revision 재실행 시 오류 없음 확인 (IF NOT EXISTS 준수) - - Evidence: Alembic migration — uq_portfolios_owner_name constraint 존재 여부를 information_schema 쿼리로 확인 후 조건부 ADD CONSTRAINT ✓ - - AC-6: `create_portfolio()` 실행 시 서버 `uuid.uuid4()` 생성 UUID가 portfolio_id로 저장됨 확인 - - Evidence: `test_create_returns_uuid_in_response` — JSON 응답에서 `uuid.UUID(portfolio_id)` 파싱 성공 ✓ - -- REQ-017: - - AC-1: `portfolio create --user ` 실행 시 portfolio_id를 사용자가 직접 지정하지 않고 UUID가 DB에 저장됨 - - Evidence: `test_create_portfolio_assigns_uuid_id` — create 후 `uuid.UUID(portfolio.portfolio_id)` 검증 ✓ - - AC-2: `portfolio buy --user alice` 실행 시 `(alice, )` 단일 쿼리 UUID 조회 후 처리 확인 - - Evidence: `test_buy_resolves_portfolio_by_name_and_user` — `get_portfolio_by_name_and_user()` 경유 BUY 처리, `repo._entries[0].action == "BUY"` ✓ - - AC-3: `--user bob`으로 alice 소유 portfolio 접근 시 `포트폴리오를 찾을 수 없습니다: {name} (user: bob)` 출력 및 명령 중단 확인 - - Evidence: `test_alice_cannot_delete_bobs_portfolio` — alice가 bob 소유 "SAMSUNG" 삭제 시도 → exit_code=1, portfolio 잔존 ✓; `test_status_fails_when_portfolio_name_not_found_for_user` — 오류 메시지에 name 포함 ✓ - - AC-4: alice/bob 동일 name으로 독립 portfolio 생성 및 각자 조작 가능 확인 - - Evidence: `test_alice_and_bob_can_have_same_portfolio_name` ✓ - - AC-5: alice의 portfolio name을 이미 존재하는 name으로 `update` 시 `이미 사용 중인 이름입니다: {name}` 오류 출력 확인 - - Evidence: `postgres.py` `update_portfolio()` UniqueViolation catch → ValueError; `ensure_schema()` UNIQUE 제약으로 DB 레벨 보장 ✓ - - AC-6: `--force` 없는 `update` name 변경 시 명령 중단 또는 confirm 프롬프트 표시 확인 - - Evidence: `update_portfolio` command — `if new_name is not None: _confirm(force, ...)` 로직; `test_update_new_name_renames_portfolio`는 `--force` 있을 때만 성공 ✓ - - AC-7: `[a-zA-Z0-9._:-]` 범위 외 문자 포함 name `create` 시 유효성 검사 오류 반환 확인 - - Evidence: `test_name_validation_rejects_invalid_characters` — "invalid name!" → exit_code=1 ✓ - - AC-8: name 길이 0 또는 100자 초과 시 유효성 검사 오류 반환 확인 - - Evidence: `test_name_validation_rejects_empty_name` — 빈 문자열 → exit_code != 0; `_NAME_RE = re.compile(r"^[a-zA-Z0-9._:\-]{1,100}$")` regex로 1~100자 강제 ✓ - -- REQ-018: - - AC-1: `GET /v1/assets` 응답 `assets[].asset_id`가 UUID 형식 확인 - - Evidence: `v1_assets.py` — `asset_id=p.portfolio_id`; REQ-016 이후 portfolio_id는 UUID; full test suite 129 passed ✓ - - AC-2: UUID `asset_id`로 `POST /v1/assets/{asset_id}/ledger/trades` 호출 시 정상 처리 확인 - - Evidence: `v1_ledger.py` — `repo.get(asset_id)` 로 포트폴리오 존재 확인; UUID portfolio_id로 정상 동작 ✓ - - AC-3: 존재하지 않는 UUID `asset_id`로 ledger/trades 호출 시 HTTP 404 반환 확인 - - Evidence: `repo.get()` 실패 → ValueError → HTTP 404 처리 (기존 패턴 유지) ✓ - - AC-4: `/portfolios/{portfolio_id}` UUID 호출 시 정상 응답 반환 확인 - - Evidence: HTTP API path는 `str` 수용; UUID 형식도 통과; `tests/test_portfolio_ownership.py` 4/4 passed ✓ - - AC-5: `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` A-3 항목에 철회 주석 존재 확인 - - Evidence: dcy-002 A-3 항목 아래 **[철회 — 2026-05-11 REQ-018]** 주석 추가 완료 ✓ - -- REQ-019: - - AC-1: `docs/project-structure.md`에 `get_portfolio_by_name_and_user()` 메서드 및 `portfolio_id` UUID 의미 반영 확인 - - Evidence: app/repositories/ 섹션에 "Key methods added in bat-006: `get_portfolio_by_name_and_user()`" 추가 ✓ - - AC-2: `docs/runtime-flows.md`에 CLI `` + `--user` 식별 흐름 및 `GET /v1/assets` `asset_id` UUID 반환 반영 확인 - - Evidence: "Flow: CLI Portfolio Command — Name+User Resolution" 섹션 신규 추가; GET /v1/assets flow step 6 UUID 명시 ✓ - - AC-3: `docs/data-model.md`에 `portfolios` 테이블 `portfolio_id`(UUID PK), `name`(TEXT), `(owner_user_id, name)` UNIQUE 제약 반영 확인 - - Evidence: Key Fields에 `portfolio_id :: uuid`, `name :: text` 추가; Uniqueness Constraints 섹션 신규 추가 ✓ - - AC-4: `docs/interface-contract.md`에 `GET /v1/assets` `asset_id` UUID 반환 명시 및 trex-basic 동시 배포 계획 기록 확인 - - Evidence: "Interface: v1 Consumer API (trex-basic)" 섹션 신규 추가; Deployment Constraint (bat-006) 명시 ✓ - -## Evidence - -- Test: - - `tests/test_portfolio_uuid.py`: 14/14 passed - - `test_create_portfolio_assigns_uuid_id` — UUID PK 생성 검증 - - `test_create_returns_uuid_in_response` — API 응답 UUID 파싱 검증 - - `test_status_resolves_by_name_and_user` — name+user 조회 성공 - - `test_status_fails_when_user_not_found` — 없는 user 오류 - - `test_status_fails_when_portfolio_name_not_found_for_user` — 없는 name 오류 - - `test_create_without_user_fails` — --user 누락 오류 - - `test_create_with_unknown_user_fails` — 없는 user 오류 - - `test_name_validation_rejects_invalid_characters` — 특수문자 거부 - - `test_name_validation_rejects_empty_name` — 빈 문자열 거부 - - `test_alice_and_bob_can_have_same_portfolio_name` — 동일 name 독립 UUID - - `test_buy_resolves_portfolio_by_name_and_user` — name+user BUY 처리 - - `test_delete_resolves_portfolio_by_name_and_user` — name+user DELETE 처리 - - `test_alice_cannot_delete_bobs_portfolio` — 소유권 격리 - - `test_update_new_name_renames_portfolio` — --new-name rename - - `tests/test_cli.py`: 49/49 passed (FakeRepo 및 5개 테스트 업데이트 포함) - - `tests/test_portfolio_ownership.py`: 4/4 passed - - Full suite: **129/129 passed** -- Manual: - - Alembic migration SQL 코드 리뷰: name 컬럼 추가, FK UPDATE, UUID PK 교체, UNIQUE 제약 추가, orphan check 포함 - - dcy-002 A-3 철회 주석 문서 내 직접 확인 - - baseline 4개 문서 항목별 내용 확인 - -## Result - -- Ready for Release: true -- Blocking Issues: 없음 -- Notes: - - Alembic migration은 FakeRepo 기반 단위 테스트만 수행됨; 실제 PostgreSQL 인스턴스 검증은 release 시 수행 권장 - - trex-basic 동시 배포 조정 필요 (asset_id TEXT → UUID 형식 변경) diff --git a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/design.md b/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/design.md deleted file mode 100644 index 7cafd27..0000000 --- a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/design.md +++ /dev/null @@ -1,115 +0,0 @@ -# BAT-007 Design - -- Status: confirmed -- Batch: BAT-007 -- Profile: batch-lite - -## Architecture Summary - -이 batch는 코드 구조 변경 없이 분석 및 문서화만 수행한다. 주요 설계 작업은 `ensure_schema()`와 Alembic revision 간 schema ownership 경계를 코드에서 직접 분석하고 그 결과를 baseline 문서에 반영하는 것이다. - -코드 변경이 필요한 경우는 분석 결과 ownership 충돌이 발견될 때뿐이며, 이 batch 범위에서는 해당 충돌이 기존 IF NOT EXISTS/stamp 메커니즘으로 이미 처리되고 있음이 확인되었다. - -## Changed Areas - -| 대상 | 변경 유형 | REQ | -|------|-----------|-----| -| `README.md` | 로컬 DB 검증 절차, runbook 추가 | REQ-001, REQ-003 | -| `docs/runtime-flows.md` | Local DB 초기화 흐름, Alembic/ensure_schema() 경계 흐름 추가 | REQ-001, REQ-002, REQ-003 | -| `docs/project-structure.md` | Schema ownership 경계 섹션 추가 | REQ-002, REQ-003 | -| `docker-compose.local-test.yml` | 변경 없음 — 현행 구성이 검증 시나리오 요건 충족 | — | -| `app/cli/commands/health.py` | 변경 없음 — init-db/migrate-db/health-check 출력 구조 확인 완료 | — | -| `app/db/migrations.py` | 변경 없음 — bootstrap_or_upgrade_to_head() 흐름 분석으로 coverage 충족 | — | -| `app/repositories/postgres.py` | 변경 없음 — ensure_schema() ownership 분석으로 coverage 충족 | — | - -## Key Decisions - -### D-1: design gate = no 유지, 최소 design.md 작성 - -Planning의 `Design Gate: no` 결정을 유지한다. 사용자가 draft-batch-design을 명시적으로 요청했으므로 이 문서를 생성하되, 신규 인터페이스/코드 구조 결정 사항이 없음을 확인하고 기록하는 용도로 사용한다. - -### D-2: ensure_schema() ownership 유지 — Alembic으로 이전 불필요 - -`ensure_schema()`는 runtime bootstrap fallback으로 유지한다. fresh DB → app 시작 → migrate-db 순서에서 `bootstrap_or_upgrade_to_head()`의 legacy stamp 경로가 이 충돌을 처리한다. 이번 batch에서 ensure_schema()를 제거하거나 Alembic으로 완전 이전하는 작업은 범위 밖이다. - -### D-3: 검증 시나리오를 README.md + docs/runtime-flows.md에 배분 - -검증 매트릭스(REQ-001)를 별도 파일로 생성하지 않고, 운영자가 참조하는 README.md와 기술적 흐름을 담는 docs/runtime-flows.md에 나누어 반영한다. 이 방식이 docs/project-structure.md 정책(docs/ = SDLC source-of-truth)과 일관된다. - -## Schema Ownership Analysis - -### ensure_schema()가 생성하는 객체 - -| 객체 | 타입 | IF NOT EXISTS 보호 | -|------|------|-------------------| -| `users` | TABLE | ✓ | -| `auth_tokens` | TABLE | ✓ | -| `idx_auth_tokens_user_created` | INDEX | ✓ | -| `idx_auth_tokens_hash_active` | INDEX | ✓ | -| `idx_auth_tokens_service_name_unique` | UNIQUE INDEX (partial) | ✓ | -| `strategy_sets` | TABLE | ✓ | -| `portfolios` | TABLE | ✓ | -| `portfolios.name` | COLUMN | ADD COLUMN IF NOT EXISTS | -| `portfolios.owner_user_id` | COLUMN | ADD COLUMN IF NOT EXISTS | -| `uq_portfolios_owner_name` | UNIQUE CONSTRAINT | DO $$...IF NOT EXISTS$$ | -| `portfolio_ledger` | TABLE | ✓ | -| `idx_portfolio_ledger_portfolio_time` | INDEX | ✓ | -| `idx_portfolios_broker_status_updated` | INDEX | ✓ | -| `portfolio_comments` | TABLE | ✓ | -| `idx_portfolio_comments_portfolio_created` | INDEX | ✓ | -| `portfolio_tags` | TABLE | ✓ | -| `idx_portfolio_tags_tag_portfolio` | INDEX | ✓ | -| TimescaleDB hypertable | HYPERTABLE | if_not_exists=TRUE (조건부) | - -### Alembic만 소유하는 객체 (ensure_schema()에 없음) - -| 객체 | 타입 | Revision | -|------|------|----------| -| `portfolios.market` | COLUMN | 0014 | -| `auth_tokens.runtime_id` | COLUMN | 0014 | -| `idx_auth_tokens_runtime_id_unique` | UNIQUE INDEX (partial) | 0014 | -| `broker_credentials` | TABLE | 0014 | -| `ledger_trades` | TABLE | 0014 | -| `uq_portfolios_owner_name` | UNIQUE CONSTRAINT | 0015 (Alembic + ensure_schema 공유) | - -### 충돌 지점 및 해결 메커니즘 - -| 충돌 지점 | 충돌 조건 | 해결 방식 | -|-----------|-----------|-----------| -| 핵심 테이블(users, auth_tokens 등) 중복 생성 | ensure_schema() 선행 후 Alembic 실행 | `bootstrap_or_upgrade_to_head()` legacy stamp → 이미 존재하면 head로 stamp | -| `portfolios.name` / `owner_user_id` 컬럼 중복 | ensure_schema() 선행 후 Alembic 0008/0010 실행 | Alembic revision이 `_column_exists()` guard 사용 | -| `uq_portfolios_owner_name` 중복 | ensure_schema() 선행 후 Alembic 0015 실행 | Alembic 0015의 `_constraint_exists()` guard 사용 | -| v1 API 전용 스키마 누락 | ensure_schema()만 실행한 환경 | `migrate-db` 실행 필요 — ensure_schema()는 v1 API 스키마를 포함하지 않음 | - -### 허용/금지 실행 순서 - -**허용 (safe):** -1. `init-db` → App 시작 → 정상 운영 (Alembic이 완전 스키마 생성, ensure_schema()는 no-op) -2. App 시작 (ensure_schema() 선행) → `init-db` 또는 `migrate-db` (legacy stamp + upgrade 경로) -3. 이미 Alembic 관리 DB → `migrate-db` (증분 upgrade) -4. 검증 후 `migrate-db` 재실행 (모든 revision이 IF NOT EXISTS 보호) - -**주의 (caution):** -1. ensure_schema()만 실행한 환경 → v1 API(`broker_credentials`, `ledger_trades`, `runtime_id`) 미동작 → 반드시 `migrate-db` 필요 -2. `migrate-db` 단독 실행 → DB 미존재 시 실패 → `init-db` 우선 실행 필요 - -**금지 (forbidden):** -1. `alembic_version` 테이블 수동 삭제 후 `migrate-db` 재실행 → legacy stamp 오판 위험 - -## Edge Cases - -- **TimescaleDB 미설치 환경**: ensure_schema()의 TimescaleDB hypertable 생성은 `pg_extension`에서 timescaledb 존재 여부를 먼저 확인한다. 미설치 시 포트폴리오 운영은 가능하나 시계열 최적화 없이 작동한다. -- **ensure_schema()의 v1 스키마 미포함**: `broker_credentials`, `ledger_trades`, `runtime_id` 컬럼은 ensure_schema()에 없다. ensure_schema()만 실행한 환경에서 GET /v1/user_info 또는 POST .../ledger/trades를 호출하면 DB 오류가 발생한다. `migrate-db`로 해결한다. -- **비어있는 alembic_version**: `bootstrap_or_upgrade_to_head()`는 먼저 `alembic_version` 테이블 존재를 확인한 뒤, 없으면 legacy 테이블 존재를 확인한다. fresh DB이면서 alembic_version 없으면 full upgrade 경로를 탄다. - -## Architecture Impact - -`both` — docs/project-structure.md와 docs/runtime-flows.md 모두 갱신 - -## Reference Doc Update Plan - -| 문서 | 추가/수정 내용 | REQ | -|------|----------------|-----| -| `README.md` | Local DB 검증 절차 섹션 추가 (준비, init-db, health-check, smoke flow, 반복 실행 기준) | REQ-001, REQ-003 | -| `docs/runtime-flows.md` | "Flow: Local DB Initialization and Validation" 추가 (init-db/migrate-db 흐름, Alembic/ensure_schema() 경계) | REQ-001, REQ-002, REQ-003 | -| `docs/project-structure.md` | "Schema Ownership Boundary" 섹션 추가 (객체별 소유 경로, 허용/금지 순서, 범위 제외 명시) | REQ-002, REQ-003 | diff --git a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/implementation.md b/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/implementation.md deleted file mode 100644 index 702c5cc..0000000 --- a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/implementation.md +++ /dev/null @@ -1,64 +0,0 @@ -# BAT-007 Implementation - -- Status: completed -- Batch: BAT-007 - -## Plan Summary - -- REQ-002: `app/repositories/postgres.py`와 `migrations/versions/` 코드를 직접 분석해 ensure_schema() vs Alembic ownership 경계를 확인하고 `docs/project-structure.md`에 반영 -- REQ-001: 로컬 docker compose 기반 검증 시나리오(Scenario A/B, 반복 기준, release gate)를 `README.md`와 `docs/runtime-flows.md`에 반영 -- REQ-003: README runbook + docs/project-structure.md, docs/runtime-flows.md baseline 갱신 - -코드 변경 없음. 분석 결과 기존 IF NOT EXISTS guard와 `bootstrap_or_upgrade_to_head()` 메커니즘이 ownership 충돌을 이미 처리하고 있어 코드 수정 불필요. - -## Changed Files - -| 파일 | 변경 내용 | REQ | -|------|-----------|-----| -| `README.md` | "Local DB Validation Runbook" 섹션 추가 — Scenario A/B, 반복 실행 기준, rollback 판단 기준, release gate criteria | REQ-001, REQ-003 | -| `docs/runtime-flows.md` | "Flow: Local DB Initialization and Validation" 추가 (Scenario A/B, repeatable run); "Flow: Alembic vs ensure_schema() Schema Ownership Boundary" 추가; bat-007 Change Log 기록 | REQ-001, REQ-002, REQ-003 | -| `docs/project-structure.md` | "Schema Ownership Boundary" 섹션 추가 (소유 경계 표, 허용 순서, 운영 제약, legacy 범위 제외); bat-007 Change Log 기록 | REQ-002, REQ-003 | - -## Analysis Results (REQ-002) - -### ensure_schema() 전용 객체 (Alembic에 없음) -- `idx_auth_tokens_service_name_unique` (partial unique index, WHERE token_type='service') - -### Alembic 전용 객체 (ensure_schema()에 없음) -- `portfolios.market` (revision 0014) -- `auth_tokens.runtime_id` + `idx_auth_tokens_runtime_id_unique` (revision 0014) -- `broker_credentials` table (revision 0014) -- `ledger_trades` table (revision 0014) - -### 공유 객체 (양쪽 모두 관리) -- users, auth_tokens, strategy_sets, portfolios, portfolio_ledger, portfolio_comments, portfolio_tags + 대부분의 인덱스 -- 모두 IF NOT EXISTS 또는 inspector guard로 보호됨 - -### 충돌 해결 메커니즘 확인 -- `bootstrap_or_upgrade_to_head()` (app/db/migrations.py): alembic_version 테이블 여부 → legacy 테이블 여부로 stamp 분기. 코드 확인 완료. -- Alembic revisions 0005, 0008, 0009, 0010, 0012, 0014, 0015 모두 `_column_exists()` / `_table_exists()` / `_constraint_exists()` inspector guard 사용. 확인 완료. - -## Execution Log - -1. `app/repositories/postgres.py` ensure_schema() 전체 분석 완료 — 생성 객체 18종 목록화 -2. `migrations/versions/` 15개 revision 분석 완료 — 소유 경계 분류 -3. `bootstrap_or_upgrade_to_head()` 로직 확인 — legacy stamp 경로 정상 동작 확인 -4. `docker-compose.local-test.yml` 확인 — 검증 시나리오 실행 환경 충족 (TimescaleDB healthcheck 설정 완비) -5. `app/cli/commands/health.py` 확인 — init-db/migrate-db/health-check 출력 필드 고정 확인 -6. README.md, docs/runtime-flows.md, docs/project-structure.md 갱신 완료 - -## Validation - -코드 변경 없음 → pytest 재실행 불필요. 문서 변경 only. - -``` -# 문서 변경 범위 확인 -git diff --name-only -# 기대: README.md, docs/runtime-flows.md, docs/project-structure.md, -# docs/batches/bat-007_.../design.md, planning.md, implementation.md -``` - -## Remaining Risks - -- Scenario B (ensure_schema() 선행) 흐름은 실제 로컬 환경에서 수동 검증이 필요하다. 이 batch는 코드 분석 + 문서화 기반 evidence이며 실제 DB 기반 실행 검증은 release 전 수행 권장. -- `idx_auth_tokens_service_name_unique`가 ensure_schema()에만 있고 Alembic에 없어 fresh Alembic 설치 환경에서 해당 인덱스가 생성되지 않는다. 기능 영향: service token name unique 보장 불완전. 후속 batch 검토 필요. diff --git a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/index.md b/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/index.md deleted file mode 100644 index 7c86518..0000000 --- a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/index.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-007 Index - -- Status: release-candidate -- Batch: BAT-007 -- Profile: batch-lite -- Created: 2026-05-11 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-001 | 로컬 DB 초기화 및 마이그레이션 검증 시나리오 정의 | Testing | High | -| REQ-002 | Alembic와 runtime schema ownership 경계 정리 | Migration | High | -| REQ-003 | 로컬 DB 운영 runbook 및 baseline 문서 갱신 | Documentation | Medium | - -## Source Discovery - -- [dcy-001_20260430_real-db-migration-validation-and-operations-readiness](../../discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md) - -## Scope Summary - -- 로컬 docker compose 기반 DB 환경에서 init-db, migrate-db, health-check, smoke flow 검증 매트릭스 정의 (REQ-001) -- Alembic migration 경로와 runtime `ensure_schema()` 경로의 schema ownership 경계 분석 및 문서화 (REQ-002) -- README, docs/runtime-flows.md, docs/project-structure.md에 로컬 DB 검증 절차, release gate 기준, rollback 판단 기준 반영 (REQ-003) -- 구조/인터페이스/런타임 흐름 신규 추가 없음 — 분석 결과 기반 문서화 및 기존 코드 경계 명시 - -## Impacted Area - -- Docs: - - `README.md` — 로컬 DB 준비 절차, init-db/migrate-db 실행 순서, smoke flow 검증 기준 추가 - - `docs/runtime-flows.md` — Alembic/ensure_schema() ownership 경계 흐름, CLI health-check flow 갱신 - - `docs/project-structure.md` — schema ownership 경계 및 local DB 운영 제약 반영 - - `docs/data-model.md` — Alembic ownership 객체 목록 명시 (필요 시) - - `docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md` — 검증 시나리오 결과 링크 추가 (필요 시) -- Modules: - - `docker-compose.local-test.yml` — 검증 시나리오 실행 환경 확인 (변경 필요 시 반영) - - `app/cli/commands/health.py` — health-check 출력 형식 확인 (변경 필요 시 반영) - - `app/db/migrations.py` — ownership 경계 확인 (변경 필요 시 반영) - - `app/repositories/postgres.py` — ensure_schema() ownership 확인 (변경 필요 시 반영) - - `migrations/versions/*.py` — Alembic revision 대상 객체 확인 -- Tests: - - 로컬 docker compose 기반 수동 검증 체크리스트 (REQ-001 AC-1~3) - - Alembic/ensure_schema() 충돌 시나리오 검증 절차 문서 (REQ-002 AC-1~3) - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Design | [design.md](./design.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | passed | diff --git a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/planning.md b/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/planning.md deleted file mode 100644 index 8aeee6d..0000000 --- a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/planning.md +++ /dev/null @@ -1,72 +0,0 @@ -# BAT-007 Planning - -- Status: confirmed -- Batch: BAT-007 -- Profile: batch-lite - -## Included REQ - -- REQ-001: 로컬 DB 초기화 및 마이그레이션 검증 시나리오 정의 -- REQ-002: Alembic와 runtime schema ownership 경계 정리 -- REQ-003: 로컬 DB 운영 runbook 및 baseline 문서 갱신 - -## Out of Scope - -- shared dev DB, staging, production-like 환경 검증 (REQ-001 Notes 참고) -- legacy DB 지원 및 legacy stamp 경로 검증 (REQ-002 Notes 참고) -- baseline 문서 전체 복제 또는 로컬 DB 검증과 무관한 구조 변경 (REQ-003 Notes 참고) -- trex-basic 연동 환경 검증 (이번 batch 범위 외) - -## 구현 순서 - -### 1단계: 현황 파악 (REQ-002 선행) - -- `app/repositories/postgres.py`의 `ensure_schema()` 메서드가 다루는 테이블/인덱스 목록 확인 -- `migrations/versions/*.py` revision들이 다루는 객체 목록 확인 -- 충돌 가능 지점 식별: 동일 객체를 두 경로가 모두 CREATE/ALTER하는 경우 -- 허용 순서와 금지 순서, 사전 점검 규칙 초안 작성 - -### 2단계: 검증 시나리오 정의 (REQ-001) - -- fresh DB 시나리오: DB 컨테이너 초기화 후 `init-db` → `migrate-db` → `health-check` → smoke flow 실행 순서와 기대 결과 정의 -- runtime-precreated schema 시나리오: app 실행 후 `ensure_schema()` 적용 상태에서 `migrate-db` 실행 기대 결과 정의 -- 각 시나리오별 pass/fail 기준, 남겨야 할 로그, 증거 수집 위치 명시 -- 반복 실행 시 출력 형식 일관성 기준 정의 - -### 3단계: 문서 갱신 (REQ-003 + REQ-001, REQ-002 산출물 통합) - -- `README.md` — docker compose 기반 준비 절차, init-db/migrate-db 실행 순서, health-check 및 smoke flow 검증 기준 추가 -- `docs/runtime-flows.md` — Alembic/ensure_schema() ownership 경계 흐름, local DB 검증 흐름 갱신 -- `docs/project-structure.md` — schema ownership 경계 및 local DB 운영 제약 반영 -- rollback 또는 재시도 판단 기준과 검증 로그 수집 위치 runbook에 명시 - -### 4단계: 코드/설정 확인 및 필요 시 최소 수정 - -- `docker-compose.local-test.yml` — 검증 시나리오 실행 환경 점검 (변경 필요 시 최소 반영) -- `app/cli/commands/health.py` — health-check 출력 형식이 검증 기준과 일치하는지 확인 -- `app/db/migrations.py` — IF NOT EXISTS 패턴 및 ownership 경계 준수 확인 - -## Dependencies - -- Internal: - - `app/repositories/postgres.py` — `ensure_schema()` ownership 분석 대상 - - `app/db/migrations.py` — migration 실행 entry point 확인 - - `migrations/versions/*.py` — Alembic revision 대상 객체 목록 - - `docker-compose.local-test.yml` — 로컬 검증 환경 -- External: 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: 신규 인터페이스, DB 스키마, 런타임 흐름 변경 없음. 기존 코드 분석 → 문서 작성 중심 batch. 구조적 결정 사항이 분석 과정에서 발견되면 planning을 갱신하고 필요 시 design.md를 추가한다. - -## Milestones - -1. REQ-002: Alembic vs ensure_schema() ownership 경계 분석 완료 + 문서 초안 -2. REQ-001: 검증 시나리오 매트릭스 정의 완료 -3. REQ-003: README, docs/runtime-flows.md, docs/project-structure.md 갱신 완료 - -## Risks - -- `ensure_schema()`와 Alembic revision 간 실제 충돌 지점이 분석 후 발견되면 코드 수정이 필요해질 수 있음 — 이 경우 scope를 확대해 planning 갱신 후 진행 -- `docker-compose.local-test.yml`이 현행 환경(PostgreSQL + TimescaleDB)과 맞지 않으면 수동 검증 절차 수정 필요 diff --git a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/verification.md b/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/verification.md deleted file mode 100644 index 0aeafb7..0000000 --- a/docs/batches/bat-007_20260511_local-db-validation-and-operations-runbook/verification.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-007 Verification - -- Status: passed -- Batch: BAT-007 -- Profile: batch-lite - -## Acceptance Mapping - -### REQ-001: 로컬 DB 초기화 및 마이그레이션 검증 시나리오 정의 - -| AC | 기준 | Evidence | 결과 | -|----|------|----------|------| -| AC-1 | init-db, health-check, smoke flow의 명령 순서, 기대 출력, pass/fail 기준, 로그 수집 위치가 문서로 정의됨 | README.md "Local DB Validation Runbook" — Scenario A 섹션: 명령 3단계, 기대 출력, pass/fail 기준, 로그 수집 위치 명시 | pass | -| AC-2 | fresh DB 및 runtime-precreated schema 시나리오별 기대 결과, 실패 조건, 확인 증거가 구분되어 문서화됨 | README.md Scenario A (fresh DB) 및 Scenario B (App 선행 기동), docs/runtime-flows.md "Flow: Local DB Initialization and Validation" — 두 시나리오 별도 절차 기재 | pass | -| AC-3 | 출력 형식과 로그 수집 위치가 반복 실행 시에도 일관되게 정의됨 | README.md "반복 실행 (재현성 확인)" 섹션 — `down -v` 후 재실행 절차, JSON 출력 수집 방법, `docker compose logs` 위치 명시 | pass | - -### REQ-002: Alembic와 runtime schema ownership 경계 정리 - -| AC | 기준 | Evidence | 결과 | -|----|------|----------|------| -| AC-1 | Alembic과 ensure_schema()가 다루는 주요 객체와 충돌 가능 지점이 식별됨 | `docs/project-structure.md` "Schema Ownership Boundary" 섹션 — 소유 경계 표(ensure_schema() 전용, Alembic 전용, 공유), 충돌 지점 명시; `docs/batches/bat-007.../design.md` "Schema Ownership Analysis" 상세 표 | pass | -| AC-2 | 허용 순서, 금지 순서, 사전 점검, 보정 규칙이 문서로 정리됨 | `docs/project-structure.md` "허용 실행 순서" 및 "운영 제약" 항목; `docs/runtime-flows.md` "Alembic vs ensure_schema() Schema Ownership Boundary" 흐름 (경계 규칙 4항) | pass | -| AC-3 | legacy DB 지원 제외 명시 | `docs/project-structure.md` "운영 제약" 마지막 항목: "legacy DB(bot_principals 등 구 revision 0006~0013 구조)와 신규 revision 간 호환성 검증은 이번 batch 범위 밖이다." | pass | - -### REQ-003: 로컬 DB 운영 runbook 및 baseline 문서 갱신 - -| AC | 기준 | Evidence | 결과 | -|----|------|----------|------| -| AC-1 | README에 docker compose 기반 준비 절차, init-db/migrate-db 실행 순서, health-check 및 smoke flow 검증 기준이 포함됨 | README.md "Local DB Validation Runbook" 섹션 — 준비, Scenario A (init-db + health-check + smoke flow), Scenario B (migrate-db), 반복 실행, rollback 판단 기준, release gate criteria | pass | -| AC-2 | docs/project-structure.md와 docs/runtime-flows.md에 구조 제약과 실행 흐름 제약이 반영됨 | docs/project-structure.md "Schema Ownership Boundary" 섹션; docs/runtime-flows.md "Flow: Local DB Initialization and Validation" 및 "Flow: Alembic vs ensure_schema() Schema Ownership Boundary" | pass | -| AC-3 | rollback/재시도 판단 기준과 검증 로그 수집 위치가 runbook에 명시됨 | README.md "Rollback / 재시도 판단 기준" 표 (4개 증상별 조치), "Release Gate Criteria" (3개 필수 조건), 로그 수집: `docker compose logs portfolio-hub-timescaledb` | pass | - -## Architecture Impact Confirmation - -- batch-lite profile, Design Gate: no -- Architecture Impact: `both` — docs/project-structure.md, docs/runtime-flows.md 갱신 -- 신규 코드 인터페이스/모듈 경계 변경 없음 확인 -- baseline 문서 동기화 evidence: 위 AC mapping에서 docs/project-structure.md, docs/runtime-flows.md, README.md 변경 내용 직접 확인 - -## Evidence Summary - -| 문서/항목 | 변경 여부 | 확인 방법 | -|-----------|-----------|-----------| -| `README.md` — "Local DB Validation Runbook" 섹션 | 신규 추가 | 직접 확인 완료 | -| `docs/runtime-flows.md` — "Flow: Local DB Initialization and Validation" | 신규 추가 | 직접 확인 완료 | -| `docs/runtime-flows.md` — "Flow: Alembic vs ensure_schema() Schema Ownership Boundary" | 신규 추가 | 직접 확인 완료 | -| `docs/project-structure.md` — "Schema Ownership Boundary" | 신규 추가 | 직접 확인 완료 | -| `app/repositories/postgres.py` | 변경 없음 | 코드 분석으로 확인 — ensure_schema() IF NOT EXISTS 보호 완비 | -| `app/db/migrations.py` | 변경 없음 | 코드 분석으로 확인 — bootstrap_or_upgrade_to_head() 정상 동작 | -| `docker-compose.local-test.yml` | 변경 없음 | 검증 시나리오 요건 충족 확인 | - -## Blocking Issues - -없음. - -## Remaining Risks (Post-verification) - -- `idx_auth_tokens_service_name_unique` (partial index)가 ensure_schema()에만 존재하고 Alembic revision에는 없다. Fresh Alembic 설치 환경에서 이 인덱스가 생성되지 않을 수 있다. 기능 영향: service token name uniqueness 보장 불완전. 후속 batch에서 Alembic revision 추가 검토 권장. -- Scenario B (App 선행 기동 → migrate-db) 흐름은 실제 DB 연결 환경에서의 수동 검증 없이 코드 분석 기반 evidence로 처리되었다. Release 전 실제 docker compose 환경에서 확인 권장. diff --git a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/design.md b/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/design.md deleted file mode 100644 index 0923fda..0000000 --- a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/design.md +++ /dev/null @@ -1,81 +0,0 @@ -# BAT-008 Design - -- Status: confirmed -- Batch: BAT-008 -- Profile: batch-lite - -## Architecture Summary - -BAT-008은 신규 코드/스키마/런타임 변경이 없는 doc-only batch다. REQ-004~010 구현(BAT-001)과 BAT-006(UUID migration) 완료 후 미반영 상태인 baseline 문서 3개를 실제 구현 결과와 동기화한다. - -- **Architecture Impact: none** (코드, DB 스키마, API 인터페이스 변경 없음) -- 설계 결정 사항 없음 — 실제 구현 코드와 migration revision을 기준으로 문서 작성 - -## Changed Areas - -### docs/interface-contract.md (AC-1) - -현재 상태: -- `v1 Consumer API (trex-basic)` 섹션에 Key Endpoints 3개와 Deployment Constraint(bat-006)만 있음 -- GET /health extended schema 미반영 (REQ-004) -- 각 엔드포인트 요청/응답 스키마, HTTP 코드, 에러 계약 미반영 - -추가 필요: -- GET /health 응답 스키마: `{status, service, version, checked_at}` (HealthResponse from app/api/health.py) -- GET /v1/assets 상세 명세: query param, 응답 스키마(AssetsResponse/AssetItem), HTTP 200 -- GET /v1/user_info 상세 명세: 응답 스키마(UserInfoResponse/BrokerItem), HTTP 200/404 -- POST /v1/assets/{asset_id}/ledger/trades 상세 명세: 요청 스키마(LedgerTradeRequest), 응답 스키마(LedgerTradeResponse), HTTP 201/200/409/404 -- service-token bearer 인증 방식 명시 (v1 전용) - -### docs/data-model.md (AC-2) - -현재 상태: -- Entity: Portfolio — `market` 컬럼 누락 -- Entity: User — `auth_tokens` 관련 AuthToken entity 없음 -- broker_credentials 테이블 미문서화 -- ledger_trades 테이블 미문서화 - -추가 필요: -- Portfolio Key Fields에 `market :: text|null` 추가 -- Entity: `AuthToken` 추가 — `id`, `user_id`, `token_type`, `runtime_id`, `permission`, `expires_at` 등 핵심 필드 -- Entity: `BrokerCredential` 추가 — 스키마 기준: `(user_id, broker)` PK, `account_ref`, `encrypted_payload`, `credential_version`, `last_rotated_at` -- Entity: `LedgerTrade` 추가 — 스키마 기준: `ledger_entry_id` BigInt Identity PK, `asset_id`, `order_id`, `execution_id`, `side`, `quantity`, `price`, `fee_amount`, `tax_amount`, `executed_at`, `recorded_at`; `uq_ledger_trades_idempotency` unique constraint - -### docs/runtime-flows.md (AC-3) - -현재 상태 (bat-001에서 이미 갱신): -- Flow: v1 API — Service-Token Authentication ✓ -- Flow: v1 API — GET /v1/assets ✓ -- Flow: v1 API — GET /v1/user_info ✓ -- Flow: v1 API — POST /v1/assets/{asset_id}/ledger/trades ✓ -- Flow: v1 API — POST /auth/service-tokens (runtime_id 발급 흐름 포함) ✓ -- Flow: Application Startup Validation ✓ - -추가 필요: Source Discovery/Batch 헤더에 bat-008 추가 + Change Log 항목 추가 (AC-3 충족 확인 기록) - -## Key Decisions - -| 결정 | 내용 | 근거 | -|------|------|------| -| runtime-flows.md 내용 보완 불필요 | bat-001에서 v1 API 처리 흐름과 runtime_id 발급 흐름이 이미 추가됨 | 현행 파일 확인 | -| interface-contract.md 갱신 방식 | `v1 Consumer API (trex-basic)` 섹션 내에 endpoint별 상세 명세 subsection 추가 | 기존 섹션 구조 유지 | -| data-model.md 갱신 방식 | 기존 Portfolio entity에 컬럼 추가; 신규 entity(BrokerCredential, LedgerTrade) 추가; AuthToken entity 신규 추가 | migration 20260502_0014 기준 | -| broker_credentials encrypted_payload | 문서에 "Fernet-encrypted ciphertext (TEXT)" 으로 명시 — 복호화는 REQ-010 참조 | 민감정보 취급 기준 | - -## Edge Cases - -- `docs/runtime-flows.md`의 bat-001 추가 내용과 bat-007 추가 내용이 공존 — 섹션 충돌 없음. bat-008은 Change Log와 Source Discovery만 갱신. -- `auth_tokens` 테이블은 bootstrap baseline에 Users entity와 함께 암묵적으로 존재하나 명시적 entity 섹션이 없음 — bat-008에서 AuthToken entity 추가. -- `ledger_trades.asset_id`는 BAT-006 이후 UUID portfolio_id를 참조하나 TEXT 타입 — 문서에 "TEXT (UUID-format after BAT-006)" 으로 명시. - -## Architecture Impact - -- none - -## Reference Doc Update Plan - -| 문서 | 변경 유형 | 적용 REQ AC | -|------|----------|-------------| -| docs/interface-contract.md | 기존 섹션 내 endpoint 상세 명세 추가 | REQ-011 AC-1 | -| docs/data-model.md | Portfolio entity 컬럼 추가, 신규 entity 3개 추가 | REQ-011 AC-2 | -| docs/runtime-flows.md | Source Discovery/Batch 헤더 + Change Log만 갱신 (내용은 bat-001 기준 이미 충족) | REQ-011 AC-3 | diff --git a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/implementation.md b/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/implementation.md deleted file mode 100644 index a821bbc..0000000 --- a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/implementation.md +++ /dev/null @@ -1,68 +0,0 @@ -# BAT-008 Implementation - -- Status: completed -- Batch: BAT-008 -- Profile: batch-lite - -## Plan Summary - -REQ-011 AC-1~3에 대응하는 3개 baseline 문서 갱신. 코드/스키마/런타임 변경 없음. - -## Changed Files - -| 파일 | 변경 유형 | 대응 AC | -|------|----------|---------| -| `docs/interface-contract.md` | 기존 섹션 내 endpoint 상세 명세 추가 | REQ-011 AC-1 | -| `docs/data-model.md` | portfolios.market 컬럼 추가; AuthToken, BrokerCredential, LedgerTrade entity 추가 | REQ-011 AC-2 | -| `docs/runtime-flows.md` | Source Discovery/Batch 헤더 + Change Log 갱신 (내용은 bat-001에서 이미 충족) | REQ-011 AC-3 | - -## Execution Log - -### docs/interface-contract.md - -- Source Discovery/Batch 헤더: `bootstrap-baseline, bat-006` → `bootstrap-baseline, bat-001, bat-006, bat-008` -- GET /health 응답 스키마 갱신: `{"status": "ok"}` 단순 설명 → `{status, service, version, checked_at}` 명시 (HealthResponse 기준) -- `v1 Consumer API (trex-basic)` 섹션 확장: - - `### Authentication` 추가: service-token bearer, DB-issued 조건, trade-write 권한 명시 - - `### Endpoint: GET /v1/assets` 추가: query param, AssetsResponse/AssetItem 스키마, HTTP 200/401/403 - - `### Endpoint: GET /v1/user_info` 추가: UserInfoResponse/BrokerItem 스키마, HTTP 200/401/403/404 - - `### Endpoint: POST /v1/assets/{asset_id}/ledger/trades` 추가: LedgerTradeRequest/LedgerTradeResponse 스키마, HTTP 201/200/409/404/401/403 - - `### Idempotency Contract` 추가: deduplication key, duplicate/conflict 처리 방식 -- Change Log 항목 추가 - -### docs/data-model.md - -- Source Discovery/Batch 헤더: `bootstrap-baseline, bat-006` → `bootstrap-baseline, bat-001, bat-006, bat-008` -- Model Summary: AuthToken, BrokerCredential, LedgerTrade 추가 -- Entity: Portfolio — `market :: text|null` 필드 추가 (migration 20260502_0014 기준) -- Entity: `AuthToken` 신규 추가: - - Key Fields: id, user_id, token_type, token_source, permission, service_name, runtime_id, expires_at, created_at - - Uniqueness Constraints: idx_auth_tokens_service_name_unique, idx_auth_tokens_runtime_id_unique - - State Rules: runtime_id 없는 토큰 v1 거부 명시 -- Entity: `BrokerCredential` 신규 추가: - - Key Fields: user_id, broker, account_ref, encrypted_payload, credential_version, last_rotated_at - - PK: (user_id, broker) - - State Rules: 평문 저장 금지, 복호화 실패 처리 명시 -- Entity: `LedgerTrade` 신규 추가: - - Key Fields: ledger_entry_id, asset_id, order_id, execution_id, side, quantity, price, fee_amount, tax_amount, executed_at, recorded_at - - Unique Constraint: uq_ledger_trades_idempotency - - Index: idx_ledger_trades_asset_executed - - State Rules: immutable rows, payload conflict → 409 -- Change Log 항목 추가 - -### docs/runtime-flows.md - -- Source Discovery/Batch 헤더: `bat-007` 뒤에 `bat-008` 추가 -- Change Log 항목 추가: "REQ-011 AC-3 confirmed: v1 API flows and runtime_id issuance flow already present from bat-001" -- 내용 변경 없음 — bat-001에서 추가된 v1 API 처리 흐름 5개, Application Startup Validation, bat-007에서 추가된 Local DB 흐름 2개 모두 현행 유지 - -## Validation - -- `docs/interface-contract.md`: REQ-011 AC-1 대상 4개 엔드포인트 명세 + 인증 방식 포함 확인 ✓ -- `docs/data-model.md`: broker_credentials, ledger_trades, portfolios.market, auth_tokens.runtime_id 모두 반영 확인 ✓ -- `docs/runtime-flows.md`: v1 API 처리 흐름(4개) + runtime_id 발급 흐름(POST /auth/service-tokens) 존재 확인 ✓ -- 코드 변경 없음 — lint/typecheck 불필요 - -## Remaining Risks - -- `docs/runtime-flows.md` v1 API flows는 bat-001에서 최초 작성됨. 이후 구현 변경 시 해당 흐름도 갱신 필요 (현재 기준 코드와 일치 확인됨). diff --git a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/index.md b/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/index.md deleted file mode 100644 index eab1b6d..0000000 --- a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/index.md +++ /dev/null @@ -1,41 +0,0 @@ -# BAT-008 Index - -- Status: release-candidate -- Batch: BAT-008 -- Profile: batch-lite -- Created: 2026-05-11 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-011 | trex-basic 계약 반영을 위한 baseline 문서 갱신 | Documentation | Medium | - -## Source Discovery - -- [dcy-002](../../discovery/) (trex-basic v1 API contract — FR/NFR 구현 완료 후 documentation closure) - -## Scope Summary - -- docs/interface-contract.md: GET /health, GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades 엔드포인트 명세 및 service-token bearer 인증 방식 추가 (REQ-011 AC-1) -- docs/data-model.md: broker_credentials, ledger_trades 테이블, portfolios.market, auth_tokens.runtime_id 컬럼 추가 (REQ-011 AC-2) -- docs/runtime-flows.md: v1 API 처리 흐름, runtime_id 발급 흐름 추가 (REQ-011 AC-3) -- 신규 코드/모듈/스키마 변경 없음 — 구현 완료(REQ-004~010 Implemented) 후 문서 동기화 - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — v1 API 엔드포인트 명세, service-token 인증 방식 추가 - - `docs/data-model.md` — broker_credentials, ledger_trades 테이블 및 컬럼 추가 - - `docs/runtime-flows.md` — v1 API 처리 흐름 및 runtime_id 발급 흐름 추가 -- Modules: 없음 -- Tests: 없음 - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Design | [design.md](./design.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | passed | diff --git a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/planning.md b/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/planning.md deleted file mode 100644 index 1988229..0000000 --- a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/planning.md +++ /dev/null @@ -1,70 +0,0 @@ -# BAT-008 Planning - -- Status: confirmed -- Batch: BAT-008 -- Profile: batch-lite - -## Included REQ - -- REQ-011: trex-basic 계약 반영을 위한 baseline 문서 갱신 - -## Out of Scope - -- REQ-004~010 구현 자체 변경 (이미 Implemented 상태) -- 신규 API 엔드포인트 또는 스키마 추가 -- runtime-flows.md 중 BAT-007에서 이미 갱신한 Alembic/ensure_schema() 경계 흐름 재작성 -- docs/project-structure.md (BAT-007 범위) - -## 구현 순서 - -### 1단계: 현황 파악 - -- `docs/interface-contract.md` 현재 상태 확인 — v1 API 엔드포인트 명세 누락 여부 점검 -- `docs/data-model.md` 현재 상태 확인 — broker_credentials, ledger_trades, portfolios.market, auth_tokens.runtime_id 누락 여부 점검 -- `docs/runtime-flows.md` 현재 상태 확인 — v1 API 처리 흐름 및 runtime_id 발급 흐름 누락 여부 점검 - -### 2단계: docs/interface-contract.md 갱신 (REQ-011 AC-1) - -- GET /health 응답 스키마 (extended) 추가 -- GET /v1/assets 엔드포인트 명세 추가 -- GET /v1/user_info 엔드포인트 명세 추가 -- POST /v1/assets/{asset_id}/ledger/trades 엔드포인트 명세 추가 -- service-token bearer 인증 방식 섹션 추가 또는 갱신 - -### 3단계: docs/data-model.md 갱신 (REQ-011 AC-2) - -- broker_credentials 테이블 스키마 추가 (user_id, broker, account_ref, encrypted_payload, credential_version, last_rotated_at) -- ledger_trades 테이블 스키마 추가 (ledger_entry_id, asset_id, order_id, execution_id, side, quantity, price, fee_amount, tax_amount, executed_at, recorded_at; unique constraint 포함) -- portfolios.market 컬럼 추가 -- auth_tokens.runtime_id 컬럼 추가 - -### 4단계: docs/runtime-flows.md 갱신 (REQ-011 AC-3) - -- v1 API 요청 처리 흐름(인증 → 서비스 → DB) 추가 (GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades) -- service-token 발급 시 runtime_id 동시 생성 흐름 추가 (REQ-005 반영) -- BAT-007에서 추가된 기존 섹션과 충돌 없이 통합 - -## Dependencies - -- Internal: - - REQ-004~010: 모두 Implemented 상태 확인됨 (BAT-001 release-candidate) — 선행 조건 충족 - - `app/api/v1_assets.py`, `app/api/v1_user_info.py`, `app/api/v1_ledger.py` — 실제 구현 기준으로 명세 작성 - - `app/domain/models.py` — broker_credentials, ledger_trades 모델 기준으로 data-model 작성 - - `migrations/versions/` — Alembic revision 기준으로 컬럼/테이블 정의 확인 -- External: 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: 신규 코드/인터페이스/DB 스키마/런타임 흐름 변경 없음. 구현 완료된 REQ-004~010 결과를 기존 3개 문서에 반영하는 doc-only 작업. 구조적 결정 사항 없음. - -## Milestones - -1. docs/interface-contract.md 갱신 완료 (v1 API 엔드포인트 4개 + 인증 방식) -2. docs/data-model.md 갱신 완료 (테이블 2개 + 컬럼 2개) -3. docs/runtime-flows.md 갱신 완료 (v1 API 처리 흐름 + runtime_id 발급 흐름) - -## Risks - -- `docs/runtime-flows.md`에 BAT-007에서 추가된 섹션과 중복 또는 충돌이 생길 수 있음 — 1단계 현황 파악에서 확인 후 통합 방식 결정 -- 실제 구현 코드와 문서 내용 불일치 시 코드 기준으로 작성 diff --git a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/verification.md b/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/verification.md deleted file mode 100644 index ead5d06..0000000 --- a/docs/batches/bat-008_20260511_trex-basic-baseline-docs-update/verification.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-008 Verification - -- Status: passed -- Batch: BAT-008 -- Profile: batch-lite -- Architecture Impact: none (confirmed — doc-only, no code/schema/runtime changes) -- Design Gate: no (planning.md confirmed — no structural decisions required) - -## Acceptance Mapping - -### REQ-011 AC-1: docs/interface-contract.md에 4개 엔드포인트 명세 포함 - -| Criterion | Evidence | Result | -|-----------|----------|--------| -| GET /health 응답 스키마 포함 | `docs/interface-contract.md` Interface: Primary HTTP API — Outputs 섹션에 `{status, service, version, checked_at}` 명시 (HealthResponse from app/api/health.py 기준) | PASS | -| GET /v1/assets 요청/응답 명세 포함 | `docs/interface-contract.md` Endpoint: GET /v1/assets — query param, AssetsResponse/AssetItem 스키마, HTTP 200/401/403 명시 | PASS | -| GET /v1/user_info 요청/응답 명세 포함 | `docs/interface-contract.md` Endpoint: GET /v1/user_info — UserInfoResponse/BrokerItem 스키마, HTTP 200/401/403/404 명시 | PASS | -| POST /v1/assets/{asset_id}/ledger/trades 요청/응답 명세 포함 | `docs/interface-contract.md` Endpoint: POST /v1/assets/{asset_id}/ledger/trades — LedgerTradeRequest/LedgerTradeResponse 스키마, HTTP 201/200/409/404/401/403 명시 | PASS | -| service-token bearer 인증 방식 포함 | `docs/interface-contract.md` Authentication 섹션 — DB-issued service token 조건, trade-write 권한 명시 | PASS | - -**REQ-011 AC-1: PASS** - -### REQ-011 AC-2: docs/data-model.md에 신규 테이블/컬럼 문서화 - -| Criterion | Evidence | Result | -|-----------|----------|--------| -| broker_credentials 테이블 문서화 | `docs/data-model.md` Entity: BrokerCredential — (user_id, broker) PK, account_ref, encrypted_payload, credential_version, last_rotated_at; migration 20260502_0014 기준 | PASS | -| ledger_trades 테이블 문서화 | `docs/data-model.md` Entity: LedgerTrade — ledger_entry_id (BigInt Identity), asset_id, order_id, execution_id, side, quantity, price, fee_amount, tax_amount, executed_at, recorded_at; uq_ledger_trades_idempotency unique constraint 명시 | PASS | -| portfolios.market 컬럼 문서화 | `docs/data-model.md` Entity: Portfolio Key Fields — `market :: text\|null` 추가 | PASS | -| auth_tokens.runtime_id 컬럼 문서화 | `docs/data-model.md` Entity: AuthToken — `runtime_id :: text\|null` 및 idx_auth_tokens_runtime_id_unique 명시 | PASS | - -**REQ-011 AC-2: PASS** - -### REQ-011 AC-3: docs/runtime-flows.md에 v1 API 처리 흐름과 runtime_id 발급 흐름 포함 - -| Criterion | Evidence | Result | -|-----------|----------|--------| -| v1 API 처리 흐름 포함 | `docs/runtime-flows.md` — Flow: v1 API — Service-Token Authentication, GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades (bat-001에서 추가, 현행 코드와 일치) | PASS | -| runtime_id 발급 흐름 포함 | `docs/runtime-flows.md` — Flow: v1 API — POST /auth/service-tokens: "Calls AuthService.issue_service_token() which creates a new DB token with runtime_id" (bat-001에서 추가) | PASS | - -**REQ-011 AC-3: PASS** - -## Architecture Impact Confirmation - -- batch-lite, Design Gate = no -- 코드/DB 스키마/런타임 흐름 변경 없음 -- architecture impact: none ✓ - -## Blocking Issues - -- 없음 - -## Ready for Release - -- true - -## Notes - -- docs/runtime-flows.md의 v1 API flows는 bat-001 (BAT-001 scope) 에서 이미 작성됨. REQ-011이 명시한 "v1 API 처리 흐름과 runtime_id 발급 흐름" 조건은 bat-001 결과로 충족되어 있었으며, bat-008은 이를 공식 확인하고 Source Discovery를 갱신함. diff --git a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/implementation.md b/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/implementation.md deleted file mode 100644 index 108f93d..0000000 --- a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/implementation.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-009 Implementation - -- Status: completed -- Batch: BAT-009 - -## Plan Summary - -README.md `Production Operator Runbook` 섹션에서 REQ-020/021/022의 AC gap 3개 항목을 추가. 신규 코드 변경 없음. - -## Changed Files - -| 파일 | 변경 내용 | -|---|---| -| `README.md` | Section 2에 deployment secret custodian / production operator/release approver 역할 레이블 추가 (REQ-020 AC-3) | -| `README.md` | Section 3 (신규) release credential matrix 테이블 추가 — public/read/trade-write/admin 4개 credential 용도/발급/보관/소유/폐기 구분 (REQ-021 AC-1) | -| `README.md` | Section 6 (구 Section 5) 확장 — canonical smoke 환경 명시(production canonical, staging optional preflight), 401/403/404 오류 구분 판독 추가, release evidence 기록 필드 명시 (REQ-022 AC-2, AC-4) | - -## Execution Log - -1. README.md 현재 Production Operator Runbook 섹션 내용 파악 (Section 1~5 확인) -2. `app/main.py` `_bootstrap_initial_user()` — `if repository.list_users(): return` 코드로 bootstrap idempotency 확인 (REQ-020 AC-2) -3. Gap 1: Section 2에 역할 레이블(deployment secret custodian, production operator/release approver) 추가 -4. Gap 2: Section 3을 신규 삽입해 credential matrix 테이블 작성 (기존 Section 3~5는 4~6으로 번호 이동) -5. Gap 3: Section 6(구 Section 5)에 canonical smoke 환경 문장, 401/403/404 구분 판독, release evidence 기록 필드 명시 - -## Validation - -- README.md Section 1: bootstrap path / steady-state canonical path / break-glass fallback 3-tier 구분 존재 확인 ✓ -- README.md Section 2: deployment secret custodian / production operator/release approver 역할 레이블 존재 확인 ✓ -- README.md Section 3: credential matrix 테이블 4개 행(public/read/trade-write/admin) 존재 확인 ✓ -- README.md Section 4: /auth/login → POST /auth/service-tokens 흐름 존재 확인 ✓ -- README.md Section 5: service-token list / rotate / user get / GET /v1/user_info provenance 절차 존재 확인 ✓ -- README.md Section 6: journalctl 명령 존재 확인 ✓, 401/403/404 구분 추가 확인 ✓, canonical production / optional staging 명시 확인 ✓, evidence 필드 명시 확인 ✓ -- app/main.py bootstrap idempotency: `if repository.list_users(): return` 확인 ✓ - -## Remaining Risks - -- `/auth/login` 엔드포인트가 현재 저장소에 구현되어 있는지 확인 필요. steady-state canonical path(REQ-020 AC-4)의 smoke 재현성은 해당 엔드포인트 구현 여부에 의존한다. 현재는 문서화만 가능한 상태로 처리. diff --git a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/index.md b/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/index.md deleted file mode 100644 index 57d5222..0000000 --- a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/index.md +++ /dev/null @@ -1,40 +0,0 @@ -# BAT-009 Index - -- Status: release-candidate -- Batch: BAT-009 -- Profile: batch-lite -- Created: 2026-05-12 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-020 | 실배포 admin bootstrap 및 steady-state operator access runbook | Deployment | High | -| REQ-021 | release 검증 credential matrix 및 token provenance 운영 절차 | Documentation | High | -| REQ-022 | 운영 로그 접근 및 release evidence 기록 절차 | Documentation | Medium | - -## Source Discovery - -- [dcy-005](../../discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md) (실배포 admin bootstrap 및 operator access 경로 정립) - -## Scope Summary - -- README.md `Production Operator Runbook` 섹션 보강 - - admin bootstrap path / steady-state canonical path / break-glass fallback 3-tier 경로 구분 및 조건 명시 (REQ-020) - - public/read/trade-write/admin credential matrix, token provenance CLI 절차, release evidence 기록 필드 정의 (REQ-021) - - journalctl 기반 로그 접근 절차, 401/403/404 구분 판독, canonical smoke 환경 명시 (REQ-022) -- 신규 코드/모듈/스키마 변경 없음 — 운영 문서화 전용 - -## Impacted Area - -- Docs: - - `README.md` — Production Operator Runbook 섹션 (bootstrap/credential/log 절차 보강) -- Modules: 없음 (app/main.py bootstrap idempotency는 기존 구현 확인으로 대체) -- Tests: 없음 - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | diff --git a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/planning.md b/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/planning.md deleted file mode 100644 index d070110..0000000 --- a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/planning.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-009 Planning - -- Status: confirmed -- Batch: BAT-009 -- Profile: batch-lite - -## Included REQ - -- REQ-020: 실배포 admin bootstrap 및 steady-state operator access runbook -- REQ-021: release 검증 credential matrix 및 token provenance 운영 절차 -- REQ-022: 운영 로그 접근 및 release evidence 기록 절차 - -## Delivery Plan - -1. README.md 현재 `Production Operator Runbook` 섹션 내용 확인 -2. REQ-020: bootstrap path / steady-state canonical path / break-glass fallback 3-tier 구분이 명시되어 있는지 확인 및 보강 -3. REQ-021: credential matrix (public/read/trade-write/admin), token provenance CLI 절차 (`pfhub service-token list` 등), release evidence 기록 필드가 명시되어 있는지 확인 및 보강 -4. REQ-022: `journalctl -u portfoliohub`, `journalctl -u caddy` 명령, 401/403/404 구분 판독, canonical acceptance 환경(production) 명시가 있는지 확인 및 보강 -5. app/main.py bootstrap idempotency 코드 리뷰로 REQ-020 AC-2 검증 - -## Dependencies - -- Internal: - - `README.md` — 현재 Production Operator Runbook 섹션 존재 (REL-001 이후 초안 추가됨) - - `app/main.py` — bootstrap idempotency 로직 확인 대상 - - `app/core/config.py` — `PORTFOLIOHUB_BOOTSTRAP_*` 환경 변수 확인 대상 -- External: - - 없음 - -## Design Gate - -- Design Required Before Implementation: no -- Reason: README.md 단일 문서 보강 작업이며 구조/인터페이스/런타임 흐름 변경이 없음. 현재 섹션 내용 확인 후 누락 항목만 추가하는 방식으로 진행 가능. - -## Milestones - -1. README.md 현재 Operator Runbook 섹션 내용 파악 및 gap 식별 -2. REQ-020/021/022 AC 기준 누락 항목 보강 완료 -3. verification.md 작성 및 AC 매핑 완료 - -## Risks - -- README.md에 이미 대부분의 내용이 존재할 수 있음 — 현재 내용 확인 후 실제 gap이 작으면 구현 범위 축소 가능 -- `/auth/login` 엔드포인트가 실제 구현되어 있지 않으면 REQ-020 AC-4(steady-state canonical path rehearsal)는 문서화만 가능하고 실제 smoke 재현 불가 — Notes에 전제 명시로 대응 diff --git a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/verification.md b/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/verification.md deleted file mode 100644 index 7d794d1..0000000 --- a/docs/batches/bat-009_20260512_production-operator-runbook-and-credential-matrix/verification.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-009 Verification - -- Status: passed -- Batch: BAT-009 - -## Acceptance Mapping - -- REQ-020: - - AC-1: Given README.md Production Operator Runbook이 존재할 때, bootstrap path / steady-state canonical path / break-glass fallback이 각각 사용 조건과 함께 구분되어 기술되어 있어야 한다. - - Evidence: README.md Section 1 — steady-state(line 351), bootstrap path(line 353), break-glass fallback(line 354) 각각 사용 조건과 함께 존재 ✓ - - AC-2: users 테이블이 비어 있지 않은 환경에서 bootstrap 로직이 기존 사용자를 덮어쓰지 않아야 한다. - - Evidence: `app/main.py` `_bootstrap_initial_user()` — `if repository.list_users(): return` (line 33) — 사용자 존재 시 즉시 반환, idempotency 보장 ✓ - - AC-3: deployment secret custodian과 production operator/release approver 역할이 구분되어 있고 secret 보관 위치가 명시되어 있어야 한다. - - Evidence: README.md Section 2 — **deployment secret custodian** 역할 레이블(line 360), **production operator / release approver** 역할 레이블(line 361), 보관 위치 `/opt/portfoliohub/.env` 명시 ✓ - - AC-4: steady-state canonical path `/auth/login` → `POST /auth/service-tokens` 흐름이 runbook에 기술되어 있어야 한다. - - Evidence: README.md Section 4 — 5단계 smoke flow에 admin session token 확보 → service-token 발급 흐름 및 curl 예시 포함 ✓ - -- REQ-021: - - AC-1: public/read/trade-write/admin 4개 credential의 용도, 발급 주체, 보관 위치, 공동 소유 역할이 구분되어 기술되어 있어야 한다. - - Evidence: README.md Section 3 credential matrix 테이블 — 4개 credential 행(public/no-auth, read service-token, trade-write service-token, admin auth context), 용도/발급/보관/소유/폐기 열 구분 ✓ - - AC-2: release 문서에 name/token_prefix/permission/runtime_id/issued_at/revoked_at만 기록하고 raw token 값은 포함되지 않아야 한다. - - Evidence: README.md Section 2 — "release 문서와 운영 메모에는 token name, token_prefix, permission, runtime_id, issued_at, revoked_at만 남기고 raw secret은 남기지 않는다"(line 368) ✓; Section 6 evidence 기록 필드에도 동일 제약 명시 ✓ - - AC-3: `pfhub service-token list` 실행 시 필드가 출력되고 runtime_id 대조 가능해야 한다. - - Evidence: README.md Section 5 — `pfhub service-token list --user `으로 name/token_prefix/token_id/user_id/permission/created_at/last_used_at/revoked_at 확인, `GET /v1/user_info`로 runtime_id 대조 절차 명시 ✓ - - AC-4: admin context는 개인 소유, read/trade-write는 release approver 역할과 공동 소유임이 문서에 명시되어 있어야 한다. - - Evidence: README.md Section 2 — admin 개인 소유(line 363), read/trade-write 공동 소유(release approver/repository-maintainers)(line 364) ✓; Section 3 credential matrix 소유 형태 열 ✓ - -- REQ-022: - - AC-1: `journalctl -u portfoliohub`와 `journalctl -u caddy` 각각의 용도가 명시되어 있어야 한다. - - Evidence: README.md Section 6 — portfoliohub: "startup 및 애플리케이션 로그"(line 418), caddy: "reverse proxy 또는 access 수준 로그"(line 419) ✓ - - AC-2: 401/403/404 의미가 분리되어 재현 가능하게 설명되어 있어야 한다. - - Evidence: README.md Section 6 HTTP 오류 분류 — 401(인증 정보 없음/만료), 403(권한 부족), 404(리소스 없음) 각각 정의 및 예시 포함(line 424~426) ✓ - - AC-3: release evidence에 시각/endpoint/HTTP status code/source system 필드가 포함되고 raw secret이 제외되어야 한다. - - Evidence: README.md Section 6 Release evidence 기록 필드 — 포함 필드(시각, endpoint, HTTP status code, source system, token name, token_prefix, runtime_id) 및 제외(raw secret, raw token) 명시 ✓ - - AC-4: production이 canonical acceptance이고 staging은 optional preflight임이 명시되어 있어야 한다. - - Evidence: README.md Section 6 — "production을 canonical acceptance 환경으로 사용한다. staging smoke는 optional preflight로만 사용하며 release acceptance 기준이 되지 않는다"(line 414) ✓ - -## Evidence - -- Manual: - - README.md Section 1~6 항목별 내용 직접 확인 - - `app/main.py` `_bootstrap_initial_user()` 코드 리뷰 — `if repository.list_users(): return` idempotency 확인 - - credential matrix 테이블 4개 행 및 6개 열 구조 확인 - - 401/403/404 구분 판독 텍스트 존재 확인 - - canonical smoke environment 및 staging optional preflight 문장 존재 확인 - -## Result - -- Ready for Release: true -- Blocking Issues: 없음 -- Notes: - - `/auth/login` 엔드포인트 구현 여부 미확인. REQ-020 AC-4는 runbook에 문서화되어 있으나, steady-state path의 실제 smoke 재현은 해당 엔드포인트 구현 후 가능. release gate blocker는 아니며 후속 운영 환경에서 확인 권장. diff --git a/docs/batches/bat-010_20260513_daemon-model-implementation/design.md b/docs/batches/bat-010_20260513_daemon-model-implementation/design.md deleted file mode 100644 index 7085b76..0000000 --- a/docs/batches/bat-010_20260513_daemon-model-implementation/design.md +++ /dev/null @@ -1,55 +0,0 @@ -# BAT-010 Design - -- Status: confirmed -- Batch: BAT-010 - -## Architecture Summary - -daemon identity 모델을 도입해 service-token과 asset에 stable `daemon_id`를 부여하고, `/v1/assets`와 `/v1/assets/{asset_id}/ledger/trades`의 authorization 축을 user-scope에서 daemon-scope로 전환한다. 기존 service-token bearer auth와 permission 모델은 유지하고, daemon assignment는 그 위에 추가되는 두 번째 authorization 축이다. 전환 구간에서는 NULL backfill을 허용하고 fail-closed로 처리한다. - -## Changed Areas - -- Docs: - - `docs/interface-contract.md` — daemon binding, daemon-scoped contract, 403 mismatch policy - - `docs/data-model.md` — daemons 테이블, auth_tokens.daemon_id, portfolios.managed_by_daemon_id - - `docs/runtime-flows.md` — daemon registry binding 흐름, rotate invariant, daemon-scoped v1 API 흐름 - - `README.md` — daemon provisioning runbook, enforcement enablement gate -- Modules: - - `migrations/versions/` — daemons, auth_tokens.daemon_id, portfolios.managed_by_daemon_id migration - - `app/domain/models.py` — Daemon entity 추가 - - `app/repositories/postgres.py` — daemon CRUD methods - - `app/repositories/portfolio_repository.py` — daemon-scoped asset query - - `app/services/auth_service.py` — daemon binding at issue/rotate - - `app/api/auth_routes.py` (또는 신규 daemon route) — daemon registry management endpoint - - `app/api/v1_assets.py` — daemon-scoped GET, daemon assignment auth for ledger write - - `app/api/v1_user_info.py` — daemon_id 필드 노출 - - `app/schemas/v1.py` — daemon_id 필드 추가 - - `app/cli/commands/service_tokens.py` — daemon_id binding at issue, daemon_id invariant at rotate, daemon_id in output -- Interfaces: - - Daemon registry management: HTTP endpoint(`POST /daemons`, `GET /daemons`, `PATCH /daemons/{daemon_id}`) 또는 CLI subcommand(`pfhub daemon create/list/update`) — **아래 Key Decisions 참조** - - `POST /auth/service-tokens` request body: `daemon_id` 필드 추가 - - `GET /v1/assets` response: daemon-scoped 필터 - - `GET /v1/user_info` response: `daemon_id` 필드 추가 - - `POST /v1/assets/{asset_id}/ledger/trades`: daemon assignment 검증 추가 - -## Key Decisions - -1. **Daemon registry 관리 경로 형태**: CLI subcommand(`pfhub daemon create/list/update`)를 1차 관리 경로로 채택. 운영자 중심 도구이고, HTTP endpoint로 노출 시 별도 admin-only auth guard 설계가 필요하다. HTTP endpoint는 향후 필요 시 추가한다. -2. **daemon_slug uniqueness**: owner user 내에서 unique. DB unique constraint(`daemons.owner_user_id + daemons.daemon_slug` composite unique)로 강제. -3. **enforcement enablement gate**: migration level에서 별도 flag 없이 진행. NULL backfill token/asset에 대한 fail-closed는 코드 레벨에서 처리. production cutover는 inventory + dry-run + release approver 승인 후 수동으로 진행. -4. **NULL backfill fail-closed boundary**: `daemon_id = NULL`인 token이 daemon-scoped endpoint(`GET /v1/assets`, `POST /v1/assets/{asset_id}/ledger/trades`)를 호출하면 403 Forbidden. `GET /v1/user_info`는 NULL이어도 200 OK + `daemon_id: null` 진단 노출. - -## Edge Cases - -- 동일 user 아래 daemon이 없는 상태에서 service-token 발급 시: daemon registry에 daemon이 없으면 발급 불가. 운영자가 먼저 daemon create를 수행해야 한다. -- rotate 중 daemon이 삭제된 경우: daemon FK 참조 무결성 위반으로 rotate 거부. 소프트 삭제(is_active flag) 방식으로 daemon을 비활성화하는 것이 안전하다. -- managed_by_daemon_id가 설정된 asset을 다른 daemon으로 재배정: 명시적 reassignment API/CLI 또는 migration-level 절차로만 허용. 자동 재배정 없음. - -## Architecture Impact - -- runtime-flows - -## Reference Doc Update Plan - -- project-structure.md: none (파일 구조 변경 없음) -- runtime-flows.md: 갱신 필요 — daemon registry binding 흐름, daemon-scoped v1 API 처리 흐름, rotate `runtime_id`/`daemon_id` 역할 차이 반영 (REQ-011 범위) diff --git a/docs/batches/bat-010_20260513_daemon-model-implementation/implementation.md b/docs/batches/bat-010_20260513_daemon-model-implementation/implementation.md deleted file mode 100644 index aad02fe..0000000 --- a/docs/batches/bat-010_20260513_daemon-model-implementation/implementation.md +++ /dev/null @@ -1,72 +0,0 @@ -# BAT-010 Implementation - -- Status: completed -- Batch: BAT-010 - -## Plan Summary - -Implemented the full daemon model: Daemon entity (registry CRUD), daemon_id binding on service-token issuance, daemon_id rotate-invariant on token rotation, daemon-scoped authorization on GET /v1/assets and POST /v1/assets/{asset_id}/ledger/trades (fail-closed for NULL daemon_id), daemon_id diagnostic on GET /v1/user_info (NULL → 200+null), CLI daemon subcommand (create/list/update), CLI service-token rotate command, Alembic migration 20260513_0016, and docs updates across data-model.md, interface-contract.md, runtime-flows.md, and README.md. - -## Changed Files - -### New Files -- `migrations/versions/20260513_0016_daemon_model.py` — Alembic revision; creates daemons table, adds auth_tokens.daemon_id and portfolios.managed_by_daemon_id -- `app/cli/commands/daemon.py` — CLI daemon subcommand (create, list, update) -- `tests/test_daemon_registry.py` — 12 unit tests (REQ-023, REQ-005, REQ-012) -- `tests/test_daemon_assignment.py` — 8 integration tests (REQ-006, REQ-007, REQ-008) - -### Modified Files -- `app/domain/models.py` — Added Daemon dataclass; added daemon_id to AuthToken, TokenPrincipal, Portfolio -- `app/core/auth.py` — Added daemon_id to AuthContext; propagated from principal -- `app/schemas/v1.py` — UserInfoResponse: runtime_id optional, daemon_id added -- `app/schemas/auth.py` — IssueServiceTokenRequest/ApiResponse/CliResponse: daemon_id added -- `app/repositories/portfolio_repository.py` — Protocol: create_auth_token(daemon_id), list_portfolios(managed_by_daemon_id), list_portfolios_with_strategies, rotate_service_token, daemon CRUD methods -- `app/repositories/postgres.py` — _SELECT_AUTH_TOKEN/TOKEN_PRINCIPAL/PORTFOLIO queries updated; create_auth_token(daemon_id), get_token_principal(daemon_id), list_portfolios(managed_by_daemon_id), list_portfolios_with_strategies, rotate_service_token, ensure_schema (daemons table + daemon_id columns), _row_to_auth_token/portfolio/daemon, daemon CRUD -- `app/services/auth_service.py` — issue_service_token(daemon_id with validation), rotate_service_token -- `app/api/v1_assets.py` — NULL daemon_id guard (HTTP 403); daemon-scoped list_portfolios_with_strategies -- `app/api/v1_user_info.py` — Removed 404 for null runtime_id; added daemon_id to response -- `app/api/v1_ledger.py` — 3-step daemon assignment authorization (NULL token, unassigned asset, mismatch) -- `app/cli/commands/service_tokens.py` — issue: added --daemon-id option; added rotate command -- `app/cli/main.py` — Registered daemon_app typer -- `tests/test_auth.py` — FakeRepo.create_auth_token: added daemon_id param -- `tests/test_v1.py` — Mock repos: added daemon_id; all asset/ledger TokenPrincipal: added daemon_id="d-1"; Portfolio mocks: added managed_by_daemon_id="d-1"; user_info null-runtime_id tests updated (404→200+null) -- `tests/test_cli.py` — FakeRepo.create_auth_token: added daemon_id param -- `docs/data-model.md` — Added Daemon entity; updated Portfolio/AuthToken fields; updated Change Log -- `docs/interface-contract.md` — Added Daemon Authorization Model; updated all v1 endpoints; updated CLI surface; updated Change Log -- `docs/runtime-flows.md` — Added daemon registry and rotate invariant flows; updated v1 endpoint flows; updated Alembic boundary; updated Change Log -- `docs/batches/bat-010_20260513_daemon-model-implementation/design.md` — Status: confirmed - -## Execution Log - -1. Added `Daemon` dataclass to `app/domain/models.py`; added `daemon_id` fields to `AuthToken`, `TokenPrincipal`, `Portfolio`. -2. Updated `AuthContext` in `app/core/auth.py` to carry `daemon_id`; propagated from `_resolve_auth_context`. -3. Extended `UserInfoResponse` in `app/schemas/v1.py`: `runtime_id` optional, `daemon_id` added. -4. Extended auth schemas in `app/schemas/auth.py`: `daemon_id` on issue request and responses. -5. Extended `PortfolioRepository` protocol with daemon CRUD, `rotate_service_token`, and updated signatures. -6. Implemented full daemon support in `app/repositories/postgres.py`: updated SELECT queries, ensure_schema DDL, row mappers, create_auth_token, get_token_principal, list_portfolios, list_portfolios_with_strategies, rotate_service_token, and all daemon CRUD methods. -7. Updated `AuthService.issue_service_token` to accept and validate `daemon_id`; added `rotate_service_token`. -8. Updated `v1_assets.py`: NULL daemon_id → 403; `list_portfolios_with_strategies` with `managed_by_daemon_id=auth.daemon_id`. -9. Updated `v1_user_info.py`: removed 404 for null runtime_id; added `daemon_id=auth.daemon_id` to response. -10. Updated `v1_ledger.py`: 3-step daemon assignment authorization before `insert_ledger_trade`. -11. Created `app/cli/commands/daemon.py` with `create`, `list`, `update` subcommands. -12. Updated `app/cli/commands/service_tokens.py`: `--daemon-id` option on issue; added `rotate` command. -13. Registered `daemon_app` in `app/cli/main.py`. -14. Created `migrations/versions/20260513_0016_daemon_model.py` with idempotent DDL guards. -15. Fixed `tests/test_auth.py` and `tests/test_cli.py` FakeRepo.create_auth_token signatures. -16. Fixed `tests/test_v1.py`: all asset/ledger TokenPrincipal + Portfolio mocks updated; null-runtime_id tests renamed and corrected. -17. Created `tests/test_daemon_registry.py` (12 tests: daemon CRUD + token binding + rotate invariant). -18. Created `tests/test_daemon_assignment.py` (8 tests: v1 daemon-scoped auth). -19. Updated `docs/data-model.md`, `docs/interface-contract.md`, `docs/runtime-flows.md`, `README.md`. - -## Validation - -- `pytest` run: **128 passed, 7 failed** (7 failures are pre-existing in test_cli.py; confirmed via git stash baseline check before BAT-010 changes). -- All 20 new daemon tests pass (test_daemon_registry.py: 12, test_daemon_assignment.py: 8). -- All existing tests in test_auth.py, test_v1.py, test_portfolios.py, test_strategy_sets.py continue to pass. - -## Remaining Risks - -- Pre-existing 7 test failures in test_cli.py (out of BAT-010 scope; tracked separately). -- Production enforcement gate requires manual operator action: NULL token inventory + dry-run + release approver approval before deployment (documented in README Daemon Provisioning Runbook). -- `managed_by_daemon_id` assignment to existing portfolios requires DB migration or manual update; no CLI path yet for daemon assignment on existing portfolios. -- Real DB integration test against TimescaleDB with migration 20260513_0016 not yet run (env not available in this batch). diff --git a/docs/batches/bat-010_20260513_daemon-model-implementation/index.md b/docs/batches/bat-010_20260513_daemon-model-implementation/index.md deleted file mode 100644 index 73757ca..0000000 --- a/docs/batches/bat-010_20260513_daemon-model-implementation/index.md +++ /dev/null @@ -1,55 +0,0 @@ -# BAT-010 Index - -- Status: release-candidate -- Batch: BAT-010 -- Profile: standard -- Created: 2026-05-13 - -## Included REQ - -| REQ | 제목 | Type | Priority | -|-----|------|------|----------| -| REQ-009 | v1 API 지원을 위한 DB 스키마 migration 추가 (daemon schema 포함) | Migration | High | -| REQ-023 | Daemon Registry 관리 경로 | Interface | High | -| REQ-005 | service-token 발급 시 runtime_id + daemon_id binding | Interface | High | -| REQ-012 | service-token rotate CLI — daemon_id 불변식 및 출력 | Interface | Medium | -| REQ-006 | GET /v1/assets daemon-scoped 엔드포인트 | Interface | High | -| REQ-007 | GET /v1/user_info daemon_id 필드 및 NULL diagnostic | Interface | High | -| REQ-008 | POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization | Interface | High | -| REQ-011 | baseline docs daemon-aware 갱신 (interface-contract, data-model, runtime-flows) | Documentation | Medium | -| REQ-028 | README.md Daemon Provisioning Runbook 반영 | Documentation | Medium | - -## Source Discovery - -- [dcy-006](../../discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md) (daemon_id 도입 및 1자산 1데몬 자산 배정 모델 정립) — 주 출처 -- dcy-002 (REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-012 원출처 — CHG-20260513-01로 범위 재정의) - -## Scope Summary - -- Daemon 모델 전체를 한 번에 구현·검증·배포하는 단일 batch - - `daemons` 테이블, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id` Alembic migration (REQ-009) - - daemon registry CRUD 관리 경로 — daemon create/update/list (REQ-023) - - service-token 발급 daemon binding + unknown daemon 거부 (REQ-005) - - service-token rotate daemon_id 불변식 + CLI daemon_id 출력 (REQ-012) - - GET /v1/assets daemon-scoped 응답 + NULL token fail-closed (REQ-006) - - GET /v1/user_info daemon_id 필드 + NULL diagnostic 노출 (REQ-007) - - POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization (REQ-008) - - interface-contract, data-model, runtime-flows daemon-aware 갱신 (REQ-011) - - README.md daemon provisioning runbook + enforcement enablement gate (REQ-028) - -## Impacted Area - -- Migrations: `migrations/versions/` (daemons table, auth_tokens.daemon_id, portfolios.managed_by_daemon_id) -- Modules: `app/domain/models.py`, `app/repositories/postgres.py`, `app/repositories/portfolio_repository.py`, `app/services/auth_service.py`, `app/api/auth_routes.py`, `app/api/v1_assets.py`, `app/api/v1_user_info.py`, `app/cli/commands/service_tokens.py` -- Schemas: `app/schemas/v1.py` -- Docs: `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`, `README.md` -- Tests: `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_v1.py`, 신규 `tests/test_daemon_registry.py`, 신규 `tests/test_daemon_assignment.py` - -## Stage Documents - -| Stage | File | Status | -|-------|------|--------| -| Planning | [planning.md](./planning.md) | confirmed | -| Design | [design.md](./design.md) | confirmed | -| Implementation | [implementation.md](./implementation.md) | completed | -| Verification | [verification.md](./verification.md) | completed | diff --git a/docs/batches/bat-010_20260513_daemon-model-implementation/planning.md b/docs/batches/bat-010_20260513_daemon-model-implementation/planning.md deleted file mode 100644 index 1a45ab1..0000000 --- a/docs/batches/bat-010_20260513_daemon-model-implementation/planning.md +++ /dev/null @@ -1,62 +0,0 @@ -# BAT-010 Planning - -- Status: confirmed -- Batch: BAT-010 -- Profile: standard - -## Included REQ -- REQ-009: v1 API DB 스키마 migration (daemon schema 포함) -- REQ-023: Daemon Registry 관리 경로 -- REQ-005: service-token 발급 daemon binding -- REQ-012: service-token rotate daemon_id 불변식 -- REQ-006: GET /v1/assets daemon-scoped -- REQ-007: GET /v1/user_info daemon_id 필드 -- REQ-008: POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization -- REQ-011: baseline docs daemon-aware 갱신 -- REQ-028: README.md daemon provisioning runbook - -## Delivery Plan - -의존 순서에 따라 아래 순서로 구현한다. - -1. **REQ-009** — Alembic migration: `daemons` 테이블 신규 생성, `auth_tokens.daemon_id` FK 컬럼 추가, `portfolios.managed_by_daemon_id` FK 컬럼 추가, 기존 레코드 NULL backfill. 기존 revision chain 및 `IF NOT EXISTS` 정책 준수. -2. **REQ-023** — Daemon registry CRUD: domain model(`Daemon` entity), repository(`create_daemon`, `get_daemon_by_slug`, `list_daemons_for_user`, `update_daemon`), management route 또는 CLI subcommand. daemon_slug owner-unique validation. -3. **REQ-005** — service-token issuance daemon binding: `POST /auth/service-tokens`와 `pfhub service-token issue`가 existing daemon registry entry에 1:1 binding. unknown daemon 입력 명시적 거부. -4. **REQ-012** — service-token rotate daemon_id 불변식: rotate 시 `token_hash`, `token_prefix`, `runtime_id`만 갱신, `daemon_id` 유지. CLI rotate 출력에 `daemon_id` 포함. -5. **REQ-006** — GET /v1/assets daemon-scoped: `managed_by_daemon_id = token.daemon_id` 필터 적용. NULL token 403 fail-closed. -6. **REQ-007** — GET /v1/user_info daemon_id 필드: 응답 스키마에 `daemon_id` 추가. NULL backfill token은 `daemon_id: null` 진단 노출. -7. **REQ-008** — POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization: `managed_by_daemon_id = token.daemon_id` 검증. mismatch/미배정/NULL token 403 fail-closed. -8. **REQ-011** — baseline docs 갱신: `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`에 daemon binding/assignment/403 policy/rotate invariant 반영. -9. **REQ-028** — README.md 갱신: daemon provisioning 절차, enforcement enablement gate 4종 evidence, runtime_id/daemon_id 역할 구분 반영. - -## Dependencies - -- Internal: - - REQ-009 migration은 REQ-023~028 구현 전에 schema가 존재해야 한다. - - REQ-023 daemon registry가 구축된 후 REQ-005/012 binding 구현이 가능하다. - - REQ-005/012가 daemon binding을 제공한 후 REQ-006/007/008 daemon-scoped authorization이 가능하다. - - REQ-011/028 문서화는 구현 단계 마지막 또는 구현 완료 직후 수행한다. -- External: - - 기존 Alembic revision chain (최신 head revision과 연결 필요) - - 기존 service-token bearer auth 모델 유지 (read/trade-write/admin permission 불변) - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: 신규 `Daemon` 엔티티의 API transport 형태(HTTP endpoint vs CLI subcommand vs 혼합), daemon_slug owner-unique constraint 설계, enforcement enablement gate 활성화 방식, NULL backfill 기간 동안의 fail-closed boundary 정의 등 구현 착수 전 확정이 필요한 설계 결정이 있다. - -## Milestones - -1. Design confirmed — daemon registry interface 형태, enforcement gate 활성화 방식 확정 -2. REQ-009 migration 적용 + pytest 통과 -3. REQ-023 daemon registry CRUD 구현 + targeted test 통과 -4. REQ-005/012 daemon binding/rotate 구현 + targeted test 통과 -5. REQ-006/007/008 daemon-scoped API 구현 + daemon A/B 분리 시나리오 테스트 통과 -6. REQ-011/028 문서 갱신 완료 + 전체 pytest 통과 -7. Verification — 전체 AC evidence 매핑 완료, ready for release - -## Risks - -- R-1 (High): NULL backfill 상태에서 daemon-scoped enforcement를 켜면 기존 service-token/asset이 연쇄 403에 직면해 운영 중단 발생 가능. 완화: enforcement는 inventory 완료 + dry-run zero-active-NULL/zero-mismatch + release approver 승인 이후에만 활성화. -- R-2 (Medium): daemon registry interface 형태 결정이 늦어지면 REQ-005/023 구현이 지연됨. 완화: design 단계에서 먼저 확정. -- R-3 (Medium): 기존 Alembic chain과 migration 충돌. 완화: 신규 revision을 기존 head에 선형으로 연결, `alembic history` 정상 여부 migration test에서 확인. diff --git a/docs/batches/bat-010_20260513_daemon-model-implementation/verification.md b/docs/batches/bat-010_20260513_daemon-model-implementation/verification.md deleted file mode 100644 index 25df9b4..0000000 --- a/docs/batches/bat-010_20260513_daemon-model-implementation/verification.md +++ /dev/null @@ -1,132 +0,0 @@ -# BAT-010 Verification - -- Status: completed -- Batch: BAT-010 - -## Test Suite Results - -``` -7 failed, 128 passed in 4.88s -``` - -- 7 failures: all pre-existing in `tests/test_cli.py` (confirmed via git stash baseline run before BAT-010 changes; not introduced by this batch) -- 20 new daemon tests: all pass (test_daemon_registry.py: 12, test_daemon_assignment.py: 8) -- All prior tests in test_auth.py, test_v1.py, test_portfolios.py, test_strategy_sets.py: pass - -## Acceptance Mapping - -### REQ-009: v1 API DB 스키마 migration (daemon schema 포함) - -- AC-1: `daemons` 테이블이 신규 생성된다. - - Evidence: `migrations/versions/20260513_0016_daemon_model.py` — `op.execute("CREATE TABLE IF NOT EXISTS daemons ...")`; `app/repositories/postgres.py` ensure_schema() DDL guard -- AC-2: `auth_tokens.daemon_id` TEXT 컬럼이 추가된다. - - Evidence: migration `ADD COLUMN IF NOT EXISTS daemon_id TEXT`; ensure_schema() `ALTER TABLE auth_tokens ADD COLUMN IF NOT EXISTS daemon_id TEXT` -- AC-3: `portfolios.managed_by_daemon_id` TEXT 컬럼이 추가된다. - - Evidence: migration `ADD COLUMN IF NOT EXISTS managed_by_daemon_id TEXT`; ensure_schema() `ALTER TABLE portfolios ADD COLUMN IF NOT EXISTS managed_by_daemon_id TEXT` -- AC-4: 기존 레코드는 NULL backfill로 처리된다 (명시적 UPDATE 불필요 — nullable column default). - - Evidence: 컬럼 DDL에 DEFAULT 없음 → 기존 레코드 NULL (PostgreSQL nullable column behavior) -- AC-5: migration revision이 기존 chain에 선형 연결된다. - - Evidence: `down_revision = "20260511_0015"` in 20260513_0016_daemon_model.py - -### REQ-023: Daemon Registry 관리 경로 - -- AC-1: `pfhub daemon create --user ` 가 daemon을 생성하고 daemon_id를 반환한다. - - Evidence: `app/cli/commands/daemon.py` create_daemon(); `tests/test_daemon_registry.py::test_create_daemon_returns_daemon_id` ✓ -- AC-2: daemon_slug는 동일 owner_user_id 내에서 unique하다. - - Evidence: `uq_daemons_owner_slug` UNIQUE (owner_user_id, daemon_slug) in migration + ensure_schema; `tests/test_daemon_registry.py::test_create_daemon_duplicate_slug_raises` ✓ -- AC-3: `pfhub daemon list --user ` 가 해당 user 소유 daemon만 반환한다. - - Evidence: `app/repositories/postgres.py` list_daemons_for_user(); `tests/test_daemon_registry.py::test_list_daemons_scoped_to_user` ✓ -- AC-4: `pfhub daemon update --display-name ` 가 display_name을 업데이트한다. - - Evidence: `app/cli/commands/daemon.py` update_daemon(); `tests/test_daemon_registry.py::test_update_daemon_display_name` ✓ -- AC-5: `--deactivate` 로 daemon을 비활성화할 수 있다. - - Evidence: `app/cli/commands/daemon.py` update_daemon `--deactivate` option → `update_daemon(daemon_id, is_active=False)` - -### REQ-005: service-token 발급 daemon binding - -- AC-1: `pfhub service-token issue --daemon-id ` 가 daemon_id를 token에 binding한다. - - Evidence: `app/cli/commands/service_tokens.py` issue() `--daemon-id` option; `app/services/auth_service.py` issue_service_token(daemon_id=...); `tests/test_daemon_registry.py::test_issue_service_token_with_daemon_id` ✓ -- AC-2: 존재하지 않는 daemon_id를 지정하면 ValueError로 거부된다. - - Evidence: `AuthService.issue_service_token` — `get_daemon_by_id(daemon_id)` None → raise ValueError("존재하지 않거나 비활성"); `tests/test_daemon_registry.py::test_issue_service_token_unknown_daemon_raises` ✓ -- AC-3: 비활성(`is_active=False`) daemon에 대한 발급은 거부된다. - - Evidence: `AuthService.issue_service_token` — `not daemon.is_active` → raise ValueError; `tests/test_daemon_registry.py::test_issue_service_token_inactive_daemon_raises` ✓ -- AC-4: `--daemon-id` 없이 발급하면 daemon_id=None 토큰이 정상 발급된다. - - Evidence: `tests/test_daemon_registry.py::test_issue_service_token_without_daemon_id_succeeds` ✓ - -### REQ-012: service-token rotate daemon_id 불변식 - -- AC-1: `pfhub service-token rotate ` 시 daemon_id가 유지된다. - - Evidence: `app/repositories/postgres.py` rotate_service_token() — new token has same daemon_id as old; `tests/test_daemon_registry.py::test_rotate_service_token_preserves_daemon_id` ✓ -- AC-2: rotate 시 runtime_id는 새로 발급된다. - - Evidence: rotate_service_token() — new `runtime_id = str(uuid4())`; `tests/test_daemon_registry.py::test_rotate_service_token_preserves_daemon_id` — asserts `rotated.auth_token.runtime_id != original_runtime_id` ✓ -- AC-3: rotate 시 raw token 값이 변경된다. - - Evidence: `tests/test_daemon_registry.py::test_rotate_service_token_generates_new_raw_token` ✓ -- AC-4: 존재하지 않는 token name rotate는 ValueError로 거부된다. - - Evidence: `tests/test_daemon_registry.py::test_rotate_nonexistent_token_raises` ✓ - -### REQ-006: GET /v1/assets daemon-scoped - -- AC-1: daemon_id=NULL 토큰으로 GET /v1/assets 호출 시 HTTP 403 반환. - - Evidence: `app/api/v1_assets.py` — `if auth.daemon_id is None: raise HTTPException(403, ...)`; `tests/test_daemon_assignment.py::test_assets_403_when_token_has_no_daemon_id` ✓ -- AC-2: daemon_id가 설정된 토큰으로 호출 시 `managed_by_daemon_id = token.daemon_id` 필터가 적용된 asset만 반환. - - Evidence: `app/api/v1_assets.py` — `list_portfolios_with_strategies(managed_by_daemon_id=auth.daemon_id)`; `tests/test_daemon_assignment.py::test_assets_200_with_daemon_scoped_filter` ✓ (mock assertion on called args) - -### REQ-007: GET /v1/user_info daemon_id 필드 - -- AC-1: 응답에 `daemon_id` 필드가 포함된다 (bound token). - - Evidence: `app/schemas/v1.py` UserInfoResponse.daemon_id; `app/api/v1_user_info.py` — `daemon_id=auth.daemon_id`; `tests/test_daemon_assignment.py::test_user_info_returns_daemon_id_in_response` ✓ -- AC-2: NULL daemon_id 토큰은 HTTP 200 + `daemon_id: null` 반환 (진단, 오류 아님). - - Evidence: `app/api/v1_user_info.py` — no guard on daemon_id; `tests/test_daemon_assignment.py::test_user_info_null_daemon_id_returns_200_with_null` ✓ -- AC-3: NULL runtime_id 토큰도 HTTP 200 + `runtime_id: null` 반환 (기존 404 제거). - - Evidence: `app/api/v1_user_info.py` — 404 guard removed; `tests/test_v1.py::test_v1_user_info_null_runtime_id_returns_200_with_null` ✓ - -### REQ-008: POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization - -- AC-1: daemon_id=NULL 토큰으로 호출 시 HTTP 403. - - Evidence: `app/api/v1_ledger.py` — check 1: `if auth.daemon_id is None: raise HTTPException(403, ...)`; `tests/test_daemon_assignment.py::test_ledger_trade_403_when_token_has_no_daemon_id` ✓ -- AC-2: asset의 `managed_by_daemon_id=NULL` (미배정)이면 HTTP 403. - - Evidence: `app/api/v1_ledger.py` — check 2: `if portfolio.managed_by_daemon_id is None: raise HTTPException(403, ...)`; `tests/test_daemon_assignment.py::test_ledger_trade_403_when_asset_has_no_daemon_assignment` ✓ -- AC-3: token.daemon_id ≠ asset.managed_by_daemon_id (mismatch)이면 HTTP 403. - - Evidence: `app/api/v1_ledger.py` — check 3: `if portfolio.managed_by_daemon_id != auth.daemon_id: raise HTTPException(403, ...)`; `tests/test_daemon_assignment.py::test_ledger_trade_403_when_daemon_mismatch` ✓ -- AC-4: token.daemon_id == asset.managed_by_daemon_id이면 HTTP 201 + status="new". - - Evidence: `tests/test_daemon_assignment.py::test_ledger_trade_201_when_daemon_matches` ✓ - -### REQ-011: baseline docs daemon-aware 갱신 - -- AC-1: `docs/interface-contract.md`에 Daemon Authorization Model, 각 v1 엔드포인트 daemon 조건, CLI daemon subcommand가 반영된다. - - Evidence: `docs/interface-contract.md` — "Daemon Authorization Model" 섹션, GET /v1/assets / GET /v1/user_info / POST ledger/trades 엔드포인트 갱신, CLI surface에 daemon subcommand 추가 -- AC-2: `docs/data-model.md`에 Daemon entity, portfolios.managed_by_daemon_id, auth_tokens.daemon_id가 문서화된다. - - Evidence: `docs/data-model.md` — "Entity: Daemon" 섹션 신규, Portfolio Key Fields에 managed_by_daemon_id, AuthToken Key Fields에 daemon_id + State Rules 갱신 -- AC-3: `docs/runtime-flows.md`에 daemon registry provisioning flow, rotate invariant flow, 갱신된 v1 API flows가 추가된다. - - Evidence: `docs/runtime-flows.md` — "Daemon Registry — Create and Bind Service Token", "Service-Token Rotate — daemon_id Invariant" 신규; GET /v1/assets / GET /v1/user_info / POST ledger/trades flows 갱신 - -### REQ-028: README.md daemon provisioning runbook - -- AC-1: README에 daemon 등록, service-token 바인딩, asset 할당 절차가 포함된다. - - Evidence: `README.md` — "Daemon Provisioning Runbook" 섹션 1~3 (daemon create, service-token issue --daemon-id, managed_by_daemon_id SQL) -- AC-2: enforcement enablement gate 4종 evidence 항목이 명시된다. - - Evidence: `README.md` — 섹션 4: NULL token inventory, dry-run zero-active-NULL, dry-run zero-mismatch, release approver 승인 -- AC-3: runtime_id와 daemon_id 역할 구분이 명시된다. - - Evidence: `README.md` — "runtime_id vs daemon_id" 섹션 - -## Evidence - -### Test -- `pytest tests/ -q` 결과: **128 passed, 7 failed** (2026-05-13) -- 7 failures: `tests/test_cli.py` — pre-existing (test_cli_list_renders_table, test_cli_status_supports_json_output, test_cli_create_buy_and_delete_flow, test_cli_write_command_prompts_before_create, test_cli_write_command_can_abort, test_cli_user_update_and_delete_flow, test_cli_list_supports_tag_filters) — not introduced by BAT-010 -- New daemon tests: `tests/test_daemon_registry.py` (12), `tests/test_daemon_assignment.py` (8) — all pass - -### Manual -- 실제 TimescaleDB 인스턴스에 대한 통합 테스트: 미수행 (환경 미구성 — 잔여 리스크로 기록) -- ensure_schema() 및 migration 20260513_0016 DDL: `IF NOT EXISTS` guard 확인 (코드 리뷰) - -### Logs -- N/A (unit test 기반 검증; DB integration 미수행) - -## Result - -- Ready for Release: true -- Blocking Issues: 없음 -- Notes: - - Pre-existing 7 test failures in test_cli.py는 이 batch scope 밖이며 별도 배치에서 처리한다. - - 프로덕션 enforcement 활성화는 배포 전 README 섹션 4의 4종 evidence 확보 후 수행한다. - - migration 20260513_0016 실제 DB 적용 검증은 배포 시 수행한다. diff --git a/docs/batches/index.md b/docs/batches/index.md deleted file mode 100644 index 4e78ae2..0000000 --- a/docs/batches/index.md +++ /dev/null @@ -1,20 +0,0 @@ -# Batch Index - -## 목적 -- 이 문서는 batch delivery 단위의 전역 인덱스다. -- 각 batch는 `docs/batches//` 아래에서 profile(`standard` | `batch-lite`)에 맞는 산출물을 관리한다. - -## Batch Register - -| BAT ID | Profile | Status | Included REQ | Discovery | Folder | -| --- | --- | --- | --- | --- | --- | -| BAT-001 | standard | release-candidate | REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010 | dcy-002 | [bat-001_20260502_trex-basic-v1-api-contract](./bat-001_20260502_trex-basic-v1-api-contract/) | -| BAT-002 | batch-lite | release-candidate | REQ-012 | minor-change | [bat-002_20260502_service-token-rotate-cli](./bat-002_20260502_service-token-rotate-cli/) | -| BAT-003 | batch-lite | release-candidate | REQ-013 | dcy-003 | [bat-003_20260502_portfolio-list-user-required](./bat-003_20260502_portfolio-list-user-required/) | -| BAT-004 | batch-lite | release-candidate | REQ-014 | minor-change | [bat-004_20260502_portfolio-create-user-required](./bat-004_20260502_portfolio-create-user-required/) | -| BAT-005 | batch-lite | release-candidate | REQ-015 | minor-change | [bat-005_20260503_broker-credential-cli](./bat-005_20260503_broker-credential-cli/) | -| BAT-006 | standard | release-candidate | REQ-016, REQ-017, REQ-018, REQ-019 | dcy-004 | [bat-006_20260511_portfolio-uuid-migration](./bat-006_20260511_portfolio-uuid-migration/) | -| BAT-007 | batch-lite | release-candidate | REQ-001, REQ-002, REQ-003 | dcy-001 | [bat-007_20260511_local-db-validation-and-operations-runbook](./bat-007_20260511_local-db-validation-and-operations-runbook/) | -| BAT-008 | batch-lite | release-candidate | REQ-011 | dcy-002 | [bat-008_20260511_trex-basic-baseline-docs-update](./bat-008_20260511_trex-basic-baseline-docs-update/) | -| BAT-009 | batch-lite | release-candidate | REQ-020, REQ-021, REQ-022 | dcy-005 | [bat-009_20260512_production-operator-runbook-and-credential-matrix](./bat-009_20260512_production-operator-runbook-and-credential-matrix/) | -| BAT-010 | standard | release-candidate | REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-011, REQ-012, REQ-023, REQ-028 | dcy-006 | [bat-010_20260513_daemon-model-implementation](./bat-010_20260513_daemon-model-implementation/) | \ No newline at end of file diff --git a/docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md b/docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md deleted file mode 100644 index 90c31f4..0000000 --- a/docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md +++ /dev/null @@ -1,224 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-001_20260430_real-db-migration-validation-and-operations-readiness -- 이슈명: 실DB 마이그레이션 검증 및 운영 준비 -- 작성 시각(KST): 2026-04-30 22:27 -- 마지막 갱신 시각(KST): 2026-04-30 23:01 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: docs/srs/Testing/req-001_local-db-validation-scenarios.md, docs/srs/Migration/req-002_alembic-runtime-schema-ownership.md, docs/srs/Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: temp/plan.md -- 해석: - - bootstrap-baseline 완료 이후 active Discovery, REQ, Batch, Release가 없는 no-history 상태이므로 첫 real Discovery를 새로 생성한다. - - 현재 저장소에는 init-db, migrate-db, Alembic migration 경로가 구현돼 있지만 로컬 docker compose 기반 DB 검증 기준과 legacy DB 비지원 범위는 아직 REQ 수준으로 정리되지 않았다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - docs/discovery/ 아래에는 전역 index만 있고 실DB 마이그레이션 검증 또는 운영 준비를 다루는 기존 Discovery 문서가 없다. - - baseline 문서와 README는 실DB 검증이 남은 과제라고만 적고 있어, 이번 주제는 후속 Delivery 산출물로 연결될 독립 Discovery로 추적하는 편이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - app/cli/commands/health.py 에는 init-db, migrate-db, health-check 명령이 구현돼 있고, app/db/migrations.py 는 Alembic upgrade head 와 legacy schema stamp 분기를 제공한다. - - app/repositories/postgres.py 는 다수의 DB 호출 전에 ensure_schema() 를 실행해 런타임 중 테이블을 선제 생성하며, migrations/versions/20260410_0009_portfolio_comments_table.py 는 이 경로와 충돌하지 않도록 IF NOT EXISTS 기반으로 작성돼 있다. - - docs/runtime-flows.md 와 docs/data-model.md 는 실DB 검증, 운영 절차, schema ownership 경계가 아직 확정되지 않았다고 기록한다. -- 기대 상태 - - 로컬 docker compose 로 기동한 PostgreSQL 또는 TimescaleDB 환경을 기준으로 init-db 와 migrate-db 의 기대 동작, 실패 조건, 검증 증거가 정의돼 있어야 한다. - - Alembic migration 책임과 runtime ensure_schema() 책임 경계가 명확해지고, 운영자가 어떤 절차로 DB를 준비하고 실패 시 어떻게 복구할지 문서로 추적 가능해야 한다. -- 이해관계자 - - 백엔드 유지보수 담당자 - - DB 운영 또는 배포 담당자 - - REQ 승인자 및 release 검토자 -- 제약사항 - - 기존 Alembic revision chain 을 깨거나 임의 schema reset 을 전제로 한 해결책은 허용하지 않는다. - - 검증 대상은 현재 코드와 migration 이 지원하는 PostgreSQL 또는 TimescaleDB 경로여야 하며, Redis 와 Loki 운영 확장은 이번 Discovery의 직접 범위가 아니다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 새 PostgreSQL 또는 TimescaleDB 인스턴스를 준비한 뒤 pfhub init-db 와 health-check 로 서비스 기동 가능 상태를 검증하고 싶다. -- 유지보수 담당자가 기존 schema 를 가진 환경에서 pfhub migrate-db 를 실행할 때 어떤 migration 이 안전한지와 어떤 사전 점검이 필요한지 알고 싶다. -- release 검토자가 DB migration 실패 시 롤백 또는 재시도 판단 기준을 문서와 검증 로그로 확인하고 싶다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 로컬 docker compose 기반 실DB 기준 init-db, migrate-db, health-check 검증 시나리오와 pass or fail 판단 기준을 정의한다. -- Alembic migration 과 PostgresPortfolioRepository.ensure_schema() 사이의 schema ownership 경계를 정리하고, 로컬 runtime bootstrap 흔적과 충돌할 수 있는 revision 및 운영 리스크를 식별한다. -- 검증 결과를 README, docs/runtime-flows.md, docs/project-structure.md 에 반영할 수 있도록 운영 준비 산출물과 문서 갱신 요구를 정의한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - app/cli/commands/health.py 의 init-db, migrate-db, health-check 명령 - - app/db/migrations.py 의 bootstrap_or_upgrade_to_head 와 legacy schema 판정 로직 - - app/repositories/postgres.py 및 migrations/versions/*.py 의 schema 생성과 migration 충돌 지점 -- 문서 영향: - - docs/runtime-flows.md 의 DB 준비 및 migration 실행 흐름 - - docs/project-structure.md 의 migration ownership 및 runtime bootstrap 책임 경계 - - README.md 의 운영 절차, 사전 조건, 검증 예시 -- 운영 영향: - - fresh DB 초기화 절차, 기존 DB 업그레이드 절차, 실패 시 복구 기준 - - release 전 DB 점검 체크리스트와 검증 로그 수집 방식 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 로컬 docker compose 로 기동한 PostgreSQL 또는 TimescaleDB 환경에서 init-db, migrate-db, health-check, 핵심 인증 또는 portfolio smoke flow 까지 이어지는 최소 검증 시나리오를 정의해야 한다. -- FR-2: 로컬 docker compose 환경에서 fresh DB 또는 app runtime 이 일부 schema 를 먼저 만든 상태를 기준으로 Alembic upgrade 와 runtime ensure_schema() 가 충돌할 수 있는 지점을 식별하고, 사전 점검 또는 보정 규칙을 정의해야 한다. -- FR-3: 검증 결과에 따라 app/repositories/postgres.py, app/db/migrations.py, migrations/versions/ 의 책임 경계를 문서화하고 docs/project-structure.md 와 docs/runtime-flows.md 갱신 요구를 명시해야 한다. -- FR-4: 운영자가 DB 준비, migration 실행, 실패 대응, 재시도 여부를 판단할 수 있는 최소 runbook 초안을 정의해야 한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 검증 절차는 로컬 docker compose 검증 환경에서 반복 실행 가능하고, 명령 순서와 기대 결과가 자동화 가능 수준으로 명확해야 한다. -- NFR-2: migration 실패 시 원인 파악에 필요한 revision, 선행 조건, 보정 필요 사항이 로그 또는 문서 증거로 남아야 한다. -- NFR-3: 운영 준비 요구사항은 현재 release 전 검토 체크리스트로 이어질 수 있도록 rollback 또는 재시도 기준을 포함해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 로컬 docker compose 로 기동한 DB를 기준으로 init-db, migrate-db, health-check, bootstrap admin 생성, 로그인 또는 토큰 발급, portfolio 기본 조회 수준의 DB smoke validation 정의 - - Alembic revision chain 과 ensure_schema() 의 책임 경계 정리 및 관련 문서 갱신 요구 정의 -- Out of Scope - - 공유 dev DB, staging, production-like 환경을 대상으로 한 검증 범위 확장 - - legacy DB schema support, legacy stamp 경로 검증, 과거 운영 DB 보정 절차 정의 - - Redis, Loki, 캐시, 로그 수집 파이프라인의 production hardening - - ownership 기반 권한 모델 재설계나 외부 API versioning 정책 확정 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): runtime ensure_schema() 와 Alembic migration 이 동일 객체를 다른 순서로 다루면 실환경에서 migration 재실행 실패 또는 drift 가 발생할 수 있다. - - 완화 방안: 충돌 가능 revision 을 식별하고, fresh/legacy 시나리오별 허용 순서와 금지 순서를 명시한다. -- R-2 (Medium): 현재 코드에 남아 있는 legacy schema stamp 분기가 이번 범위에서도 지원되는 경로처럼 오해되면 범위 밖 검증이나 운영 판단이 섞일 수 있다. - - 완화 방안: 이번 Discovery 와 후속 REQ 에서 legacy DB 는 비지원 범위임을 명시하고, 필요 시 별도 Discovery 로 분리한다. -- R-3 (Medium): 운영 준비 범위가 너무 넓어지면 DB migration 검증 Discovery 가 Redis, Loki, 배포 자동화까지 확장되어 첫 반복 집중도가 떨어질 수 있다. - - 완화 방안: DB 준비와 migration 실행 경로만 이번 Discovery의 핵심 대상으로 고정하고 주변 운영 항목은 후속 Discovery 후보로 분리한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 주 검증 대상 DB 는 PostgreSQL 호환 TimescaleDB 또는 PostgreSQL 이며, 현행 psycopg 연결 방식과 Alembic 환경 설정을 그대로 사용한다. -- A-2: init-db 와 migrate-db 는 운영자가 직접 실행하거나 automation 에서 호출하는 공식 DB 준비 진입점으로 유지한다. -- A-3: legacy DB support 는 이번 반복의 성공 기준에 포함하지 않으며, 실DB 검증 결과가 현재 baseline 문서의 runtime 또는 structure 설명과 다르면 후속 Delivery 에서 해당 baseline 문서를 함께 갱신한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 첫 검증 반복에서 대상으로 삼을 환경 범위는 어디까지인가 - - 상태: Answered - - 처리 방안: 사용자 결정에 따라 첫 검증 반복의 대상 환경은 로컬 docker compose 기반 DB로 고정한다. - - 종료 조건: 완료. 이후 REQ 와 batch planning 에서는 shared dev DB, staging, production-like 환경을 기본 범위에서 제외한다. -- OQ-2: legacy schema support 의 허용 범위를 어디까지 문서화해야 하는가 - - 상태: Answered - - 처리 방안: 사용자 결정에 따라 legacy DB 는 이번 Discovery 와 후속 REQ 범위에서 지원하지 않는다. - - 종료 조건: 완료. legacy DB 지원이 필요해지면 별도 Discovery 또는 change-req 로 분리한다. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 로컬 docker compose 환경에서 fresh DB 와 app runtime 이 일부 schema 를 먼저 만든 DB 시나리오별로 실행 명령, 기대 결과, 실패 조건이 포함된 검증 시나리오 매트릭스가 완성된다. -- S-2: Alembic 과 ensure_schema() 의 책임 경계, 위험 revision, 사전 점검 규칙이 REQ 로 인계 가능한 수준으로 정리된다. -- S-3: 운영자가 release 전 참고할 수 있는 DB 준비 또는 rollback 판단 기준이 문서 초안으로 정의된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 시나리오별 명령 순서와 기대 산출물 표 작성 여부 확인 / 데이터 출처: README.md, app/cli/commands/health.py, app/db/migrations.py -- S-2 측정 방식: 충돌 지점과 ownership 경계가 파일 단위로 추적 가능한지 검토 / 데이터 출처: app/repositories/postgres.py, migrations/versions/*.py, docs/project-structure.md, docs/runtime-flows.md -- S-3 측정 방식: 운영 준비 체크리스트와 rollback 판단 기준의 초안 존재 여부 확인 / 데이터 출처: Discovery 본문, README.md, 후속 batch planning 입력 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 실DB 검증 대상 환경 범위는 로컬 docker compose 기반 DB로 확정되고, legacy support 는 제외 범위로 확정되어야 한다. -- [ ] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: 시나리오 매트릭스, command log, baseline 문서, README 중 어떤 문서가 근거가 되는지 합의돼야 한다. - - REQ handoff 판정: 차단 사항 아님. 현재 문서의 `# 7. 초기 성공 기준`에 측정 방식과 데이터 출처 초안이 정리돼 있어 REQ 초안 작성은 가능하고, 세부 artifact 형식은 REQ와 batch planning에서 확정한다. -- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1, R-2 의 해소 주체와 REQ 또는 batch 에서 검증할 방법이 지정돼야 한다. - - REQ handoff 판정: 차단 사항 아님. 리스크 자체와 완화 방향은 확정돼 있으므로 REQ 초안 작성은 가능하고, 구체 owner 지정은 REQ 승인 또는 batch planning에서 확정한다. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Discovery 승인 또는 REQ handoff 를 확인할 담당자로 Justin 이 식별돼야 한다. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md -- 참조 문서: - - README.md - - app/cli/commands/health.py - - app/db/migrations.py - - docs/project-structure.md - - docs/runtime-flows.md - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 - -## CONFIRM -- 로컬 docker compose 기반 DB에서 init-db, migrate-db, health-check, 최소 인증 또는 portfolio smoke flow 성공과 검증 로그 기록을 이번 Discovery의 release gate로 확정한다. - -## DATA -- 없음 - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 실DB 검증 범위, legacy DB 제외 범위, 승인자 Justin, release gate 기준이 정리되었고 Open 질문과 사용자 결정 항목이 해소됐다. 남은 체크 항목은 REQ drafting을 막지 않는 후속 확정 사항으로 분리했다. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-04-30 22:56 - -# 12. 작성 가이드 -템플릿 사용 시의 기본 규칙을 적는다. 실제 문서를 만들 때는 이 가이드를 기준으로 플레이스홀더를 치환한다. - -- 이 파일은 템플릿이 아니라 실제 초안 문서다. 남은 플레이스홀더는 사용자 또는 승인자 확인이 필요한 항목만 유지한다. -- 필요 없는 항목은 삭제하고, 부족한 항목은 같은 형식으로 추가한다. -- Open Questions는 가정으로 대체하지 말고 그대로 유지한다. -- `대체됨`은 현재 Discovery가 더 이상 기준 문서가 아닐 때 대체 Discovery 경로를 기록하고, 해당 사항이 없으면 `없음`으로 둔다. -- `후속 Discovery 참조`는 범위를 이어받는 별도 Discovery가 생성된 경우 그 Discovery 경로를 기록하고, 없으면 `없음`으로 둔다. -- Discovery Freeze는 문서 하단에 반드시 유지한다. \ No newline at end of file diff --git a/docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md b/docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md new file mode 100644 index 0000000..3858bdf --- /dev/null +++ b/docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md @@ -0,0 +1,252 @@ +# 0. 문서 상태 +이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. + +- 상태: draft +- 문서 ID: dcy-001_20260519_asset-catalog-and-user-portfolio-model +- 이슈명: 자산 관리 구조 재설계 — admin 관리 asset catalog와 user portfolio 분리 모델 도입 +- 작성 시각(KST): 2026-05-19T00:00+09:00 +- 마지막 갱신 시각(KST): 2026-05-19 (review-discovery 반영 — FR-2 URL prefix /v1/ 추가, S-6 측정 방식 추가, Section 3 삭제→비활성화, Section 8 S 범위 갱신, Handoff Rationale 갱신) +- 대체됨: 없음 +- 후속 Discovery 참조: 없음 +- 생성된 REQ 참조: 없음 + +# 1. 계획 상태 요약 +현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. + +- 연계 문서 현황: + - 관련 점수 파일: 미생성 + - 관련 TODO/메모: 없음 +- 해석: + - 현재 `portfolios` 테이블은 asset 정의(symbol, broker)와 user position(cash_balance, quantity)을 하나의 엔티티로 혼합하여 관리한다. 이로 인해 admin이 거래 가능한 종목을 중앙 관리하는 것이 불가능하고, user가 임의 symbol/broker 조합으로 portfolio를 생성할 수 있어 거버넌스 공백이 생긴다. + - 이 Discovery는 portfolio-hub의 핵심 도메인 모델을 재설계하는 첫 번째 단계로, admin이 관리하는 asset catalog와 user가 구성하는 portfolio(포지션)를 명확히 분리하는 새로운 아키텍처를 정의한다. + +# 2. 요구사항 판정 결과 +이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. + +- 판정: 신규 +- 근거 + - 기존 SDLC 문서 전체(dcy-001~008, REQ-001~029)가 현재 단일 `portfolios` 모델 위에 정의되어 있었으며, 2026-05-19 아키텍처 전환 결정에 따라 폐기되었다. 새 모델은 기존 어떤 Discovery와도 중복되지 않는다. + - asset catalog + user portfolio 분리 모델은 기존 SDLC에서 다룬 적 없는 신규 도메인 구조다. + +# 3. 문제점의 요약 +문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. + +- 현재 상태 + - `portfolios` 테이블이 asset 정의(symbol, market/markets)와 user 포지션(broker, cash_balance, asset_quantity, strategy, tags, comments)을 하나의 엔티티로 관리한다. "portfolio를 만든다" = "거래 가능한 종목을 등록한다"와 동일한 행위다. + - admin이 거래 가능한 종목 목록을 중앙 통제할 방법이 없다. user 또는 admin 누구나 임의 symbol/broker 조합으로 portfolio를 생성할 수 있다. + - v1 API의 `asset_id`는 실제로는 `portfolio_id`(user 포지션 ID)로, "자산"과 "포지션"의 개념이 혼용된다. + - daemon은 portfolio(포지션) 단위로 배정되어 있어 동일 종목을 여러 user가 운용할 때 daemon 관리 구조가 복잡해진다. +- 기대 상태 + - `assets` 테이블(admin 관리 catalog)에 거래 가능한 종목(symbol, market)이 등록되며, admin만 이 목록을 생성·수정·비활성화(deactivate)할 수 있다. broker는 catalog 속성이 아니다. + - user는 `assets` catalog에서 종목을 선택하고 거래에 사용할 broker를 지정하여 자신의 `portfolio`(포지션)를 구성한다. 포지션에는 broker, cash_balance, asset_quantity, strategy, tags, comments, 거래 이력이 귀속된다. + - v1 API의 `asset_id`는 catalog의 실제 asset을 참조하며 "자산"과 "포지션"의 개념이 명확히 분리된다. + - daemon은 portfolio(포지션) 레벨로 배정된다. 하나의 daemon이 특정 user의 특정 portfolio들을 담당하며, 동일 종목이라도 user별로 별도 daemon 배정이 가능하다(`portfolios.managed_by_daemon_id` 유지). +- 이해관계자 + - admin: 거래 가능한 종목을 중앙에서 등록·관리하는 운영자 + - user: catalog에서 종목을 선택해 자신의 포지션을 구성하는 투자자 + - daemon(trex-basic 등): 배정된 자산의 거래 체결 내역을 기록하는 서비스 에이전트 + - repository-maintainers: 새 아키텍처 기반으로 API/CLI/DB를 설계·구현하는 개발팀 +- 제약사항 + - 기존 `portfolios` 테이블 데이터(운영 중인 포지션)를 새 `assets` + `portfolios` 구조로 유실 없이 migration해야 한다. + - Python/FastAPI/Typer/Postgres 기술 스택은 유지한다. + - Alembic 기반 migration 절차는 유지한다. + - dcy-002(태그 소유권 및 적용 대상 정책, confirmed)는 이 Discovery의 `assets`/`portfolios` 테이블 확정을 구현 선행 조건으로 한다. `assets.tag_ids UUID[]` 컬럼과 `portfolios.tag_ids UUID[]` 컬럼 추가는 dcy-002 구현 범위이며 이 Discovery에서 다루지 않는다. + +## 주요 사용자 시나리오 +사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. + +- admin이 `AAPL (markets: ['NASDAQ'])`, `005930.KS (markets: ['KRX', 'NXT', 'SOR'])` 등을 asset catalog에 등록한다. `markets`가 빈 배열이면 현재 어떤 시장에서도 거래 불가 상태다. 등록되지 않은 종목은 user가 portfolio에 추가할 수 없다. +- user가 catalog에 등록된 `005930.KS` 종목을 선택하고 사용할 broker(예: kiwoom)를 지정하여 자신의 portfolio(포지션)를 생성하고, cash_balance와 전략을 설정한다. +- daemon이 `GET /v1/assets`를 호출하면 자신에게 `managed_by_daemon_id`로 배정된 portfolio 목록을 반환받고(각 항목의 `asset_id`는 catalog UUID 참조), 그 `asset_id`로 `POST /v1/assets/{asset_id}/ledger/trades`를 호출해 체결 내역을 기록한다. +- admin이 catalog에서 특정 asset을 비활성화(`status='inactive'`)하면 해당 종목에 대한 신규 portfolio 생성은 차단되지만 기존 portfolio는 보존된다. +- user는 동일 asset에 대해 전략 A, B로 각각 portfolio를 구성할 수 있다(OQ-2 결정: 복수 portfolio 허용). + +# 4. 이번에 정의할 변경 +이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. + +- `assets` 테이블(admin 관리 catalog) 신규 도입 — symbol, market, status 등 종목 정의 데이터를 admin이 관리하는 독립 엔티티로 분리한다. broker는 종목 자체의 속성이 아니므로 포함하지 않는다. +- `portfolios` 테이블 재설계 — `asset_id FK → assets.asset_id`를 포함한 user 포지션 구조로 변경. symbol/market은 `assets`로 이전(market은 markets 배열로 집약)하고, broker는 portfolios에 유지하며, 나머지 포지션 데이터(cash_balance, quantity, strategy 등)는 그대로 남긴다. +- 기존 `portfolios` 데이터 migration — 현재 portfolios 행을 `assets` catalog 항목과 `portfolios` 포지션 항목으로 분리 이전한다. + +## 영향 범위 +변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. + +- 코드 영향: + - `app/domain/models.py`: `Asset` 도메인 모델 신규 추가, `Portfolio` 모델에서 symbol/market 필드 제거 및 `asset_id` 참조로 대체. broker는 Portfolio 모델에 유지. + - `app/repositories/postgres.py`: `assets` CRUD 메서드 신규 추가, `portfolios` 쿼리에서 `assets` JOIN 반영. + - `app/repositories/portfolio_repository.py`: Protocol에 asset CRUD 선언 추가 + - `app/api/v1_assets.py`: 토큰 타입(service/user/admin) 분기 로직 추가, `?user_id` 파라미터 추가(admin 전용), 응답 `asset_id`를 catalog UUID로 재정의 + - `app/api/portfolios.py`: portfolio 생성 시 `asset_id` 지정 필수화. GET 목록 조회는 `GET /v1/assets`로 흡수되며 `GET /portfolios`는 점진적 deprecated 예정(mutation 경로는 별도 Discovery에서 v1으로 이전) + - `app/services/portfolio_service.py`: create_portfolio에 `asset_id` 검증 추가. + - `app/cli/commands/asset.py`: 신규 파일 — `pfhub asset create/list/update/deactivate` 커맨드 그룹 구현 (admin 전용) + - `app/cli/main.py`: `app.add_typer(asset_app, name="asset")` 추가 + - `app/cli/commands/portfolio.py`: portfolio create 시 `--asset-id` 또는 `--symbol --broker` 조합으로 catalog 참조 + - `app/schemas/`: `AssetItem`, `CreatePortfolioRequest` 등 스키마 변경 + - `migrations/versions/`: `assets` 테이블 생성 + `portfolios` 재설계(asset_id FK 추가, symbol/market 제거) + 데이터 이전 migration + - ※ 태그 관련 변경(`Tag` 모델, `tags` 테이블, `asset_tags`/`portfolio_tags` 재설계, 태깅 검증 로직)은 모두 dcy-002 구현 범위 +- 문서 영향: + - `docs/data-model.md`: `Asset` 엔티티 추가, `Portfolio` 엔티티 재정의 + - `docs/interface-contract.md`: v1 API asset catalog 기반 재정의 + - `docs/runtime-flows.md`: admin asset 등록 → user portfolio 구성 → daemon 체결 기록 흐름 추가 +- 운영 영향: + - admin이 신규 종목을 portfolio에 추가하기 전 catalog에 먼저 등록해야 하는 운영 절차 추가 + - 기존 운영 중인 portfolios에 대해 migration 전 admin 확인 절차 필요 + +# 5. 요구사항 목록 +실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. + +## 기능 요구사항 +- FR-1: `assets` 테이블 신규 도입 — 필드: `asset_id UUID PK`, `symbol TEXT NOT NULL UNIQUE`, `markets TEXT[] NOT NULL DEFAULT '{}'`, `display_name TEXT`, `status TEXT NOT NULL DEFAULT 'active'`, `created_at`, `updated_at`. admin만 CRUD 가능. `symbol` 단독 UNIQUE constraint 적용(`symbol`이 종목 식별자). `markets` 배열에 거래 가능한 시장 코드를 복수 개 저장하며, 빈 배열은 현재 어떤 시장에서도 거래 불가를 의미한다. `markets` 컬럼에 GIN 인덱스를 적용한다. 시장 코드 허용 값은 application-layer Python Enum으로 정의하며(예: `MarketCode.KRX`, `MarketCode.NXT`, `MarketCode.SOR`, `MarketCode.NASDAQ`, `MarketCode.NYSE`, `MarketCode.UPBIT`, `MarketCode.BITTHUMB`), API 수신 시 대문자 정규화 후 Enum 검증을 수행한다. DB CHECK constraint는 사용하지 않는다. broker는 종목 자체의 속성이 아니므로 포함하지 않는다. +- FR-2: admin asset catalog CRUD 경로 제공 — admin CLI(`pfhub asset create/list/update/deactivate`) 및 HTTP API(`POST/GET/PATCH /v1/assets`, `PATCH /v1/assets/{asset_id}/deactivate`)를 통해 admin이 거래 가능한 종목을 등록·관리한다. asset 삭제는 hard DELETE 없이 `status='inactive'`로만 처리한다(soft delete). 비활성화된 asset을 참조하는 기존 portfolio는 그대로 보존되며 신규 portfolio 생성만 거부한다. +- FR-3: `portfolios` 테이블 재설계 — `asset_id UUID NOT NULL` 컬럼 추가 및 `FOREIGN KEY (asset_id) REFERENCES assets(asset_id) ON DELETE RESTRICT`(DB-level FK constraint) 적용. `symbol`/`market` 컬럼 제거. `broker` 컬럼은 portfolios에 유지한다(user가 해당 포지션을 어느 증권사로 거래하는지는 포지션 레벨 속성). `(owner_user_id, asset_id)` unique constraint는 추가하지 않는다(동일 user가 동일 asset에 복수 portfolio 허용 — OQ-2 결정). `managed_by_daemon_id`는 기존과 동일하게 nullable soft reference로 유지하며, portfolio 생성 시 지정은 선택사항이고 NULL이면 fail-closed(ledger write, asset listing 불가)다. portfolio는 asset catalog에 등록된 종목만 참조할 수 있다. +- FR-4: user portfolio 생성 시 catalog 검증 — `pfhub portfolio create` (CLI) 호출 시 지정한 `asset_id`가 catalog에 `status='active'`로 존재하는지 검증. 미등록 또는 inactive asset이면 거부한다. user portfolio CRUD HTTP API는 Out of Scope이며 별도 Discovery에서 동일한 catalog 검증 정책을 따른다. +- FR-5: v1 API `GET /v1/assets` 재정의 — 토큰 타입에 따라 아래와 같이 동작하는 통합 portfolio 조회 엔드포인트로 재정의한다. 응답 항목의 `asset_id`는 catalog의 `assets.asset_id`(UUID)를 참조한다. + - **service token (daemon)**: auth context에서 `daemon_id`를 자동 추출하여 `managed_by_daemon_id = auth.daemon_id` 필터링. `daemon_id`가 없으면 HTTP 403. + - **user token (로그인 사용자)**: `owner_user_id = auth.user_id` 필터링. 본인 portfolio만 반환. + - **admin token**: 기본은 `owner_user_id = auth.user_id`(본인 portfolio). `?user_id=` 파라미터 지정 시 해당 user의 portfolio를 반환. + - 모든 토큰 타입에서 `?market=` 파라미터를 지원하며, 지정 시 ` = ANY(assets.markets)`인 portfolio만 반환. daemon은 현재 시간 기준 활성 시장 코드를 전달하여 거래 대상 portfolio를 필터링한다. +- FR-6: 기존 `portfolios` 데이터 migration — 현재 portfolios 행의 `symbol`을 기준으로 중복 없이 `assets` catalog 항목을 생성하고, 동일 symbol의 모든 `market` 값을 `ARRAY_AGG(DISTINCT market)`으로 집약하여 `markets` 배열에 저장한다. 각 portfolio 행에 `asset_id FK`를 연결한다. `managed_by_daemon_id`는 기존 값을 그대로 유지한다. + +## 비기능 요구사항 +- NFR-1: 기존 `portfolios` 데이터 migration은 트랜잭션 내에서 원자적으로 실행되며 실패 시 롤백 가능해야 한다. +- NFR-2: 새 구조에서 portfolio 조회 시 `assets JOIN portfolios` 성능이 기존 단일 테이블 조회 대비 허용 범위 내여야 한다 (인덱스 설계 포함). +- NFR-3: admin이 아닌 user 또는 daemon이 asset catalog를 직접 생성·수정·비활성화하려 하면 HTTP 403으로 거부한다. + +## 범위 경계 +이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. + +- In Scope + - `assets` 테이블 도입 및 admin CRUD 경로 (CLI + HTTP API) + - `portfolios` 테이블 재설계 (`asset_id FK` 추가, symbol/market 제거, broker 유지) + - 기존 portfolios 데이터 migration + - v1 `GET /v1/assets`의 catalog 기반 재정의 + - `docs/data-model.md`, `docs/interface-contract.md`, `docs/runtime-flows.md` 갱신 +- Out of Scope + - user portfolio **mutation** HTTP API(POST 생성, PATCH 수정, DELETE 삭제, buy/sell/invest 등) — 별도 Discovery에서 `/v1/` 경로로 이전 예정 + - `/portfolios` GET 목록 조회의 v1 이전 이후 `GET /portfolios` 엔드포인트 제거 — 별도 Discovery에서 처리 + - 태그 소유권 모델 및 태깅 정책 전체 — dcy-002(confirmed) 구현 범위 + - `assets.tag_ids UUID[]` 컬럼 추가 — dcy-002 구현 범위 + - `portfolios.tag_ids UUID[]` 컬럼 추가 및 `portfolio_tags` 테이블 재설계/제거 — dcy-002 구현 범위 + - `tags` 마스터 테이블 생성, 태그 CRUD API(/v1/tags), pfhub tag CLI — dcy-002 구현 범위 + +# 6. 리스크/가정 목록 +불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. + +## 리스크 +- R-1 (High): 기존 `portfolios` 데이터 migration 중 동일 `symbol`을 가진 여러 portfolio 행이 서로 다른 `market` 값을 가질 수 있다. 이를 하나의 asset 행으로 병합하고 `markets` 배열로 집약하는 로직이 필요하며, 집약 규칙이 명확하지 않으면 market 누락이 발생할 수 있다. + - 완화 방안: migration 전 `SELECT symbol, ARRAY_AGG(DISTINCT market) FROM portfolios GROUP BY symbol`로 symbol별 market 집합을 파악하고, `ARRAY_AGG(DISTINCT market)` 결과를 `assets.markets`에 그대로 저장한다. market 값이 NULL인 기존 행은 빈 배열로 처리한다. +- R-2 (High): `portfolios` 테이블에서 symbol/market 컬럼 제거는 이 컬럼들을 직접 참조하는 모든 쿼리, API response, CLI 출력을 수정해야 한다. 누락된 참조가 있으면 런타임 오류가 발생한다. + - 완화 방안: migration 전 `grep -rn "\.symbol\|\.market" app/` 으로 참조 범위를 전수 조사하고 REQ Impacted Area에 명시한다. broker는 portfolios에 유지되므로 별도 조사 불필요. +- R-3 (Medium): `managed_by_daemon_id IS NULL`인 기존 portfolio가 migration 후에도 계속 동작 불가(fail-closed) 상태로 남는다. migration 자체를 막지는 않지만, daemon 미배정 portfolio의 운영 가시성 확보가 필요하다. + - 완화 방안: migration 전 `SELECT COUNT(*) FROM portfolios WHERE managed_by_daemon_id IS NULL`로 미배정 portfolio 현황을 파악하고, 필요한 경우 migration 전 daemon 배정 절차를 선행한다. + +## 가정 +- A-1: 현재 `portfolios` 테이블에서 동일 `symbol`을 가진 행들은 migration 시 하나의 `assets` catalog 항목으로 병합되며, 각 행의 `market` 값은 `ARRAY_AGG(DISTINCT market)`으로 집약되어 `assets.markets`에 저장된다. broker는 각 portfolio 행에 그대로 유지된다. +- A-2: `assets` catalog의 `status` 필드는 최초 `active`/`inactive` 두 가지로 시작하며, 비활성 asset에 대한 신규 portfolio 생성은 거부한다. +- A-3: 기존 `portfolios.name`은 user가 지정하는 포지션 이름으로 유지된다. asset catalog에는 별도 `display_name` 필드를 둔다. +- A-4: portfolio 생성 시 `managed_by_daemon_id` 지정은 선택사항(nullable)이며, 미배정 portfolio는 fail-closed 상태로 운영된다(ledger write·asset listing 불가). daemon 배정은 portfolio 생성과 분리된 admin 작업으로 처리한다. + +## 오픈 질문 +답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. + +- OQ-1: daemon 배정 단위 — daemon을 `assets` catalog 레벨(하나의 daemon이 모든 user의 해당 asset 거래 처리)에 배정할 것인가, `portfolios` 포지션 레벨(user별 별도 daemon 배정)에 배정할 것인가? + - 상태: Answered + - 결정: portfolio(포지션) 레벨 배정. `portfolios.managed_by_daemon_id` 방식 유지. 동일 종목이라도 user별 별도 daemon 배정 가능. + - 반영 위치: Section 3 기대 상태(daemon 배정 단위 확정), FR-5(`GET /v1/assets` portfolio 기준 재정의) +- OQ-2: 동일 asset에 대해 한 user가 여러 portfolio(포지션)를 가질 수 있는가? (예: 같은 종목을 전략 A, B로 각각 운용) + - 상태: Answered + - 결정: 허용. `(owner_user_id, asset_id)` unique constraint 추가하지 않는다. `managed_by_daemon_id`로 동일 asset의 복수 portfolio를 구별한다. + - 반영 위치: FR-3(unique constraint 제외 및 managed_by_daemon_id 정책 명시) +- OQ-3: asset catalog의 `DELETE` 동작 — catalog에서 asset을 삭제할 때 해당 asset을 참조하는 portfolios가 있으면 어떻게 처리하는가? (거부 / cascade 삭제 / soft delete) + - 상태: Answered + - 결정: soft delete(`status='inactive'`). hard DELETE 없음. 기존 portfolio 보존, 신규 portfolio 생성만 거부. + - 반영 위치: FR-2(delete → deactivate로 변경, soft delete 정책 명시) +- OQ-4: `assets.markets` 배열에 저장하는 시장 코드의 허용 값 목록과 정규화 방식을 어떻게 정의하는가? (예: 'KRX', 'NXT', 'SOR', 'NASDAQ', 'NYSE', 'UPBIT' 등 — 대소문자 기준, DB CHECK constraint vs application-layer 정규화 vs 참조 테이블) + - 상태: Answered + - 결정: application-layer Python Enum 정규화. 허용 값: KRX, NXT, SOR, NASDAQ, NYSE, UPBIT, BITTHUMB. API 수신 시 대문자 변환 후 Enum 검증. DB CHECK constraint 없음. 허용 값 변경 시 코드 배포만 필요. + - 반영 위치: FR-1(`markets` 컬럼 설명에 Enum 검증 방식 추가) + +### 오픈 질문 처리 기준 +- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. +- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. +- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. +- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. +- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. +- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. +- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. + +# 7. 초기 성공 기준 +이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. + +- S-1: admin이 CLI 또는 HTTP API로 asset catalog에 종목을 등록하면 해당 `asset_id`가 생성되고, user가 그 `asset_id`로 portfolio를 생성할 수 있다. +- S-2: catalog에 없거나 `status='inactive'`인 `asset_id`로 portfolio 생성을 시도하면 거부된다. +- S-3: 기존 portfolios 데이터가 migration 완료 후 `assets` + `portfolios` 구조로 유실 없이 이전되며, 기존 portfolio 기능(조회, 업데이트, 거래 기록)이 새 구조에서 동일하게 동작한다. +- S-4: v1 `GET /v1/assets`가 daemon에 `managed_by_daemon_id`로 배정된 portfolio 목록을 반환하며, 각 항목의 `asset_id`가 catalog의 UUID를 참조한다. +- S-5: admin이 asset을 비활성화(`status='inactive'`)한 후 해당 `asset_id`로 신규 portfolio 생성을 시도하면 거부되고, 해당 asset을 참조하는 기존 portfolio는 보존된다. +- S-6: user token으로 `GET /v1/assets`를 호출하면 해당 user의 portfolio 목록만 반환된다. admin token으로 `GET /v1/assets`를 호출하면 admin 본인의 portfolio 목록이 반환되고, `?user_id=` 파라미터 지정 시 해당 user의 portfolio 목록이 반환된다. + +## 측정 방식 및 데이터 출처 +각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. + +- S-1 측정 방식: pytest로 admin asset create → user portfolio create(asset_id 지정) 시나리오 검증 / 데이터 출처: 신규 `tests/test_asset_catalog.py` +- S-2 측정 방식: pytest로 미등록 asset_id로 portfolio create 시 오류 반환 확인 / 데이터 출처: 신규 `tests/test_asset_catalog.py` +- S-3 측정 방식: migration 전후 portfolio 행 수 일치 및 기존 portfolio 기능 regression 테스트 통과 확인 / 데이터 출처: migration script 실행 결과 + 기존 테스트 suite +- S-4 측정 방식: daemon service-token으로 `GET /v1/assets` 호출 시 `managed_by_daemon_id` 기반 portfolio 목록과 catalog UUID asset_id 반환 확인 / 데이터 출처: 신규 `tests/test_v1_asset_catalog.py` +- S-5 측정 방식: pytest로 admin asset 비활성화 → 해당 `asset_id`로 신규 portfolio 생성 시 거부 + 기존 portfolio 존재 보존 확인 / 데이터 출처: 신규 `tests/test_asset_catalog.py` +- S-6 측정 방식: pytest로 user token `GET /v1/assets` 호출 시 본인 portfolio만 반환 확인; admin token으로 본인 portfolio 반환 확인 + `?user_id=` 지정 시 해당 user portfolio 반환 확인 / 데이터 출처: 신규 `tests/test_v1_asset_catalog.py` + +# 8. REQ로 넘기기 전 확인 체크 +REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. + +- [x] 범위(In Scope/Out of Scope) 확정 + - 기준: OQ-1(daemon 배정 단위), OQ-2(user당 동일 asset 복수 portfolio), OQ-3(asset 삭제 동작) 모두 Answered 처리되어 scope 경계 최종화됨 +- [x] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: S-1~6 측정 방식이 검증 가능한 pytest 시나리오로 연결됨 +- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 + - 기준: R-1(migration 중복 처리 규칙), R-2(symbol/market 참조 전수 조사) 완화 방안 명시됨; draft-req 단계에서 REQ Impacted Area에 반영 예정 +- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 + - 기준: OQ-1/2/3/4 모두 Answered 처리 완료 +- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 + - 기준: repository-maintainers 중 1명이 Confirmed By에 명시 + +# 9. 파일 처리 결과 +이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. + +- 처리 결과: 생성 +- 생성 경로: docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md +- 참조 문서: + - docs/data-model.md (현재 portfolios 모델 baseline — 이 Discovery로 재정의 예정) + - docs/interface-contract.md (현재 v1 API 계약 — 이 Discovery로 재정의 예정) + - docs/runtime-flows.md (현재 runtime flow baseline — 새 아키텍처 반영 예정) + +# 10. 사용자 결정 필요 항목 요약 +사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. + +## DECIDE +- ~~{{DECIDE_DAEMON_ASSIGNMENT_LEVEL}}~~: **Decided** — portfolio(포지션) 레벨 배정. `portfolios.managed_by_daemon_id` 유지. (OQ-1 Answered 2026-05-19) +- ~~{{DECIDE_MULTI_PORTFOLIO_PER_ASSET}}~~: **Decided** — 허용. `(owner_user_id, asset_id)` unique constraint 없음. `managed_by_daemon_id`로 구별. (OQ-2 Answered 2026-05-19) +- ~~{{DECIDE_ASSET_DELETE_BEHAVIOR}}~~: **Decided** — soft delete(`status='inactive'`). hard DELETE 없음. 기존 portfolio 보존, 신규 생성 거부. (OQ-3 Answered 2026-05-19) +- ~~{{DECIDE_MARKET_CODE_NORMALIZATION}}~~: **Decided** — application-layer Python Enum 정규화. DB CHECK constraint 없음. 허용 값 변경 시 코드 배포만 필요. (OQ-4 Answered 2026-05-19) + +## CONFIRM +- {{CONFIRM_MIGRATION_DEDUP_RULE}}: 기존 portfolios에서 동일 `symbol`을 가진 행들을 하나의 asset catalog 항목으로 병합하고 각 행의 `market` 값을 `ARRAY_AGG(DISTINCT market)`으로 집약하는 migration 규칙(A-1)이 맞는지 확인. broker는 각 portfolio 행에 그대로 유지된다는 전제도 함께 확인. + +## DATA +- 없음 + +# 11. Discovery Freeze +REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. + +## 섹션 참조 +- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) +- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) +- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) +- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) +- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) + +## Freeze 플래그 +- Handoff Decision: 보류 +- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1~4 모두 Answered 처리 완료. confirm-discovery 후 REQ 초안 작성 가능. +- Ready for REQ Drafting: false +- Confirmed By: Justin +- Confirmed At (KST): TBD diff --git a/docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md b/docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md deleted file mode 100644 index e47749e..0000000 --- a/docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md +++ /dev/null @@ -1,240 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-002_20260502_trex-basic-consumer-api-contract -- 이슈명: trex-basic Consumer-Driven Contract 반영 — service-token runtime_id 발급 및 v1 API 엔드포인트 구현 -- 작성 시각(KST): 2026-05-02 00:00 -- 마지막 갱신 시각(KST): 2026-05-13 17:08 -- 대체됨: 없음 -- 후속 Discovery 참조: docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md -- 생성된 REQ 참조: REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 계약 문서: temp/portfolio-hub-api-contract.md (trex-basic 팀 작성 Consumer-Driven Contract) - - 관련 TODO/메모: 없음 -- 해석: - - trex-basic 팀이 portfolio-hub에 Consumer-Driven Contract를 전달했고, 4개 엔드포인트(GET /health, GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades)를 구현해야 한다. Q1(service-token 인증)과 Q2(runtime_id 발급 절차)는 이 Discovery에서 결정 사항으로 반영한다. - - 현재 `/health`는 `status: ok`만 반환하고 `/v1/*` 엔드포인트는 전혀 없다. service-token 메커니즘(`token_type='service'`)은 이미 구현돼 있으나 `runtime_id` 개념이 없고, 브로커 인증 정보 저장 테이블도 없다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy-001은 로컬 DB 마이그레이션 검증과 운영 준비를 다루며 외부 소비자 API 계약과는 완전히 별개 주제다. - - `GET /v1/assets`, `GET /v1/user_info`, `POST /v1/assets/{asset_id}/ledger/trades`는 기존 어떤 Discovery/REQ에도 등장하지 않는 신규 엔드포인트다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `GET /health`는 `app/api/health.py`에서 `{"status": "ok"}`만 반환하며 계약이 요구하는 `service`, `version`, `checked_at` 필드가 없다. - - service-token 메커니즘은 migration 0013(`20260423_0013_remove_bot_principals_service_tokens.py`)에서 `token_type='service'`로 완성됐고, CLI `pfhub service-token issue/list/revoke`도 `app/cli/commands/service_tokens.py`에 구현돼 있다. 그러나 발급 결과에 `runtime_id`가 포함되지 않는다. - - `Portfolio` 모델(`app/domain/models.py`)에 `portfolio_id`, `broker`, `status`, `symbol`, `cash_balance`, `asset_quantity`, `active_strategy_id`가 있어 `/v1/assets` 응답 필드 일부와 대응하지만, `market`(KOSPI/KOSDAQ) 필드와 `strategy_params` JSON이 없다. - - `StrategySet` 모델에 `config`(문자열) 필드가 있어 strategy_params 원본으로 사용 가능하나, Portfolio와의 연결 시 `strategy_params` 역할을 하는 구체적 구현은 없다. - - `GET /v1/user_info`가 요구하는 `runtime_id`, `trading_enabled`, `brokers[].credential_payload` 데이터는 현재 어떤 테이블에도 존재하지 않는다. - - `POST /v1/assets/{asset_id}/ledger/trades`가 요구하는 `ledger_trades` 테이블이 없다. -- 기대 상태 - - `GET /health`는 `status`, `service`, `version`, `checked_at`을 모두 반환한다. - - `pfhub service-token issue` CLI와 대응 API 엔드포인트가 service-token과 함께 `runtime_id`를 생성·반환한다. 발급된 service-token으로 `/v1/*` 엔드포인트를 인증할 수 있고, `GET /v1/user_info` 응답에도 해당 `runtime_id`가 포함된다. - - `GET /v1/assets`는 service-token bearer 인증으로 관리 자산 목록(portfolio → asset 뷰)과 전략 파라미터를 반환한다. - - `GET /v1/user_info`는 service-token으로 인증한 사용자의 거래 가능 여부와 브로커별 credential_payload를 반환한다. - - `POST /v1/assets/{asset_id}/ledger/trades`는 체결 내역을 idempotent하게 기록하고, 중복 수신 시 `status=duplicate`를 반환한다. -- 이해관계자 - - trex-basic 팀 (소비자, Consumer-Driven Contract 작성자) - - portfolio-hub 백엔드 유지보수 담당자 (구현 주체) - - REQ 승인자 -- 제약사항 - - service-token 인증은 기존 `token_type='service'` 메커니즘을 그대로 사용한다. 별도 인증 체계를 새로 만들지 않는다. - - `GET /v1/user_info`는 `environment` 필드를 노출하지 않는다. portfolio-hub는 항상 live credential을 반환하며 paper/live 분기를 두지 않는다. - - 기존 Alembic revision chain을 깨지 않는 IF NOT EXISTS 기반 migration을 추가한다(dcy-001/REQ-002 정책 준수). - -## 주요 사용자 시나리오 - -- trex-basic 데몬이 시작될 때 `GET /health`로 portfolio-hub 가용성을 확인하고, `GET /v1/assets?status=active`로 관리 자산 목록과 전략 파라미터를 읽은 뒤, `GET /v1/user_info`로 거래 가능 여부와 브로커 인증 정보를 획득해 거래를 시작한다. -- 운영자가 `pfhub service-token issue --user ` CLI로 service-token과 runtime_id를 한 번에 발급하고, 결과를 trex-basic 데몬 환경 변수에 주입한다. 또는 API 엔드포인트를 통해 동일한 발급을 수행한다. -- trex-basic이 KIS 브로커로부터 체결을 확인한 뒤 `POST /v1/assets/{asset_id}/ledger/trades`로 체결 내역을 전송한다. 네트워크 오류로 재전송하더라도 동일 `(asset_id, order_id, execution_id)` 조합이면 중복 기록 없이 `status=duplicate`를 응답 받는다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- `GET /health` 응답 스키마 확장: `service`, `version`, `checked_at` 필드 추가 -- service-token 발급 시 `runtime_id` 동시 생성 및 반환: CLI(`pfhub service-token issue`)와 API 엔드포인트 양쪽에서 `runtime_id`를 함께 발급. `runtime_id`는 발급된 service-token에 연결되어 `GET /v1/user_info` 응답에서 그대로 반환 -- `/v1/assets`, `/v1/user_info`, `/v1/assets/{asset_id}/ledger/trades` 엔드포인트 신규 구현: 브로커 인증 정보 저장 테이블(`broker_credentials`), 거래 내역 테이블(`ledger_trades`), `portfolios` 테이블에 `market` 컬럼 추가를 포함한 DB 변경과 함께 구현 - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `app/api/health.py`: 응답 스키마 확장 - - `app/api/` 신규 라우터: `v1_assets.py`, `v1_user_info.py`, `v1_ledger.py` (또는 통합 `v1.py`) - - `app/cli/commands/service_tokens.py`: `issue` 커맨드에 `runtime_id` 생성·출력 추가 - - `app/domain/models.py`: `BrokerCredential`, `LedgerTrade` 도메인 모델 추가 - - `app/repositories/postgres.py`: 브로커 인증 정보 및 ledger 관련 DB 연산 추가 - - `app/services/`: `v1_service.py` 또는 기존 서비스에 assets/user_info/ledger 메서드 추가 - - `app/schemas/`: v1 응답 스키마 추가 - - `migrations/versions/`: `market` 컬럼, `runtime_id` 연결, `broker_credentials`, `ledger_trades` 테이블 migration 추가 - - `app/main.py`: `/v1` prefix 라우터 등록 -- 문서 영향: - - `docs/interface-contract.md`: trex-basic 소비 엔드포인트 계약 기준 갱신 필요 - - `docs/data-model.md`: `broker_credentials`, `ledger_trades`, `runtime_id` 연결 모델 추가 필요 -- 운영 영향: - - service-token 발급 절차 변경 — `runtime_id`가 함께 반환되므로 기존 운영 가이드 갱신 필요 - - 브로커 인증 정보(credential_payload)는 민감 데이터이므로 DB 수준 보안과 접근 정책 검토 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `GET /health` 응답에 `status`, `service`(고정값 `"portfolio-hub"`), `version`(앱 버전), `checked_at`(ISO 8601 KST) 필드가 포함되어야 한다. `status` 허용값은 `ok`, `degraded`, `failed`이며 정상 응답은 `ok`를 반환해야 한다. -- FR-2: `pfhub service-token issue --user ` CLI 커맨드와 대응하는 API 엔드포인트(POST /auth/service-tokens 또는 기존 POST /auth/tokens 확장)가 service-token 문자열과 함께 `runtime_id`(고유 식별자)를 생성·반환해야 한다. `runtime_id`는 service-token에 연결되어 DB에 저장되고, 해당 토큰으로 인증한 `GET /v1/user_info` 호출에서 그대로 반환되어야 한다. -- FR-3: `GET /v1/assets`는 service-token bearer 인증으로 토큰 소유자(user)의 portfolio 목록을 asset 뷰로 반환해야 한다. 응답 항목은 `asset_id`, `symbol`, `market`, `status`, `cash`, `quantity`, `broker_type`, `strategy_id`, `strategy_params`, `updated_at`을 포함해야 하며, `status=active` query parameter로 필터링이 가능해야 한다. `strategy_params`는 해당 portfolio의 StrategySet config에서 파생한다. `strategy_id`는 서버에서 허용값 검증을 수행하지 않으며, 등록된 모든 값을 그대로 반환한다(OQ-1 Answered). -- FR-4: `GET /v1/user_info`는 service-token bearer 인증으로 해당 사용자의 거래 가능 여부(`trading_enabled`, `disabled_reason`)와 브로커별 인증 정보 목록(`brokers[]`)을 반환해야 한다. `brokers[]`의 각 항목은 `broker`, `account_ref`, `credential_payload`, `credential_version`, `last_rotated_at`을 포함해야 한다. `runtime_id`는 인증 service-token에 연결된 값을 반환해야 한다. -- FR-5: `POST /v1/assets/{asset_id}/ledger/trades`는 service-token bearer 인증으로 체결 내역을 기록해야 한다. 요청 본문(`execution_id`, `order_id`, `side`, `quantity`, `price`, `fee_amount`, `tax_amount`, `executed_at`)을 `ledger_trades` 테이블에 저장하고 201을 반환해야 한다. `(asset_id, order_id, execution_id)` 동일 조합의 재수신은 신규 행 없이 `status=duplicate`와 기존 `ledger_entry_id`를 200으로 반환해야 한다. 동일 키에 payload가 다른 경우 409를 반환해야 한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: `/v1/user_info`는 `environment` 필드를 포함하지 않는다. portfolio-hub는 항상 live credential을 반환하며, paper/live 분기 로직을 서버에서 관리하지 않는다. -- NFR-2: `POST /v1/assets/{asset_id}/ledger/trades`의 idempotency 키는 `(asset_id, order_id, execution_id)` 복합 유니크 제약으로 DB에서 보장해야 한다. 애플리케이션 로직만으로 보장하지 않는다. -- NFR-3: 브로커 인증 정보(`credential_payload`)는 DB에 평문 저장을 금지한다. Python `cryptography` 패키지의 Fernet(AES-128-CBC + HMAC-SHA256)으로 저장 전 암호화하고 조회 시 복호화한다. 암호화 키는 환경변수 `CREDENTIAL_ENCRYPTION_KEY`(base64 인코딩 32바이트)로 주입하며, 키가 없으면 서비스 기동을 거부해야 한다(OQ-3 Answered). -- NFR-4: 신규 migration은 기존 `ensure_schema()` 런타임 경로와 충돌하지 않도록 IF NOT EXISTS 패턴을 사용해야 한다(REQ-002 준수). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - `GET /health` 응답 스키마 확장 - - service-token 발급에 `runtime_id` 동시 생성 (CLI + API 양쪽) - - `GET /v1/assets` 엔드포인트 구현 (portfolio → asset 뷰 매핑, strategy_params 포함) - - `GET /v1/user_info` 엔드포인트 구현 (runtime_id, trading_enabled, brokers[].credential_payload 포함) - - `POST /v1/assets/{asset_id}/ledger/trades` 엔드포인트 구현 (idempotency 포함) - - 필요한 DB migration 추가 (market 컬럼, runtime_id 연결, broker_credentials 테이블, ledger_trades 테이블) -- Out of Scope - - trex-basic 측 코드 변경 — portfolio-hub API 구현만 담당 - - paper/live 환경 분기 로직 — portfolio-hub는 live credential만 반환 - - rate limiting 구현 — trex-basic 계약의 Q6이지만 이번 반복에서 요구되지 않음; 향후 반복으로 Deferred - - `strategy_id` 허용값 검증 로직 — 검증 없이 통과하는 것으로 확정(OQ-1 Answered). 화이트리스트 불필요 - - KIS 외 다른 브로커의 credential_payload 스키마 정의 — Q4, 향후 반복으로 Deferred - - mock 서버 구현 — trex-basic 팀 자체 `scripts/mock_portfolio_hub.py` 활용 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): `broker_credentials`의 `credential_payload`(KIS app_key/app_secret)는 민감 데이터다. DB에 평문 저장 시 보안 사고 위험이 있다. - - 완화 방안: 앱 레벨 Fernet 암호화(옵션 A)로 확정(OQ-3 Answered). `CREDENTIAL_ENCRYPTION_KEY` 환경변수 없이 서비스가 기동되지 않도록 startup validation을 구현한다. -- R-2 (Medium): `GET /v1/assets`에서 `strategy_params`를 `StrategySet.config` JSON에서 파생할 때, config 스키마가 전략별로 다르면 응답 일관성이 깨질 수 있다. - - 완화 방안: 설계 단계에서 strategy_params 파생 규칙을 확정한다. config가 없거나 파싱 실패 시 빈 객체(`{}`)를 반환하고 로그를 남기는 안전한 폴백을 구현한다. -- R-3 (Medium): `portfolios` 테이블에 `market` 컬럼이 없어 기존 portfolio 데이터에 market 값이 없다. migration 후 기존 row의 market 값이 NULL 상태로 남는다. - - 완화 방안: migration에서 `market` 컬럼을 nullable로 추가한다(기본값 없음, DECIDE_MARKET_DEFAULT Answered). NULL인 경우 API 응답에서 `null`을 그대로 반환하며, trex-basic 클라이언트가 null을 처리하도록 한다. -- R-4 (Low): `GET /v1/user_info` 호출 시 service-token에 연결된 `runtime_id`가 없는 경우(기존 발급 토큰), 응답 처리 방식이 결정되지 않았다. - - 완화 방안: 기존 service-token은 `runtime_id`가 없으므로 404 또는 409 응답으로 처리하거나, 런타임에 즉시 `runtime_id`를 생성해 연결하는 방안을 설계 단계에서 결정한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: service-token 인증은 기존 `token_type='service'` 메커니즘과 bearer token 헤더 파싱을 그대로 사용한다. 별도 인증 체계를 추가하지 않는다. (Q1 결정 사항) -- A-2: `runtime_id`는 service-token 발급 시 함께 생성되며, 해당 service-token에 연결된다. 동일 사용자의 다른 service-token은 각기 다른 `runtime_id`를 가진다. (Q2 결정 사항) -- A-3: `GET /v1/assets`의 `asset_id`는 기존 `portfolio_id`를 그대로 사용한다. 별도 asset 식별자 체계를 만들지 않는다. - - **[철회 — 2026-05-11 REQ-018]**: REQ-016 UUID 마이그레이션 완료 이후 `portfolio_id`는 server-generated UUID로 변경된다. 따라서 `asset_id == portfolio_id (text)` 전제는 `asset_id == portfolio_id (UUID)`로 대체되었다. 가정 A-3의 "별도 asset 식별자 체계를 만들지 않는다"는 원칙은 유지되지만, 실제 값 형식이 UUID로 바뀐다는 점에서 원문 가정의 텍스트 동일성 가정은 철회한다. -- A-4: `trading_enabled`는 현재 단계에서 `users.is_active`와 연동하는 단순 매핑으로 구현한다. 별도 trading 전용 상태 필드는 이번 반복 Out of Scope다. -- A-5: trex-basic이 사용하는 KIS 브로커만 우선 지원하며, credential_payload 스키마는 `{"app_key": "...", "app_secret": "..."}` 형식으로 고정한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: `strategy_id` 허용값 목록 — trex-basic 첫 실전 전략 ID가 확정되지 않았다. 현재 StrategySet에 있는 전략 ID 값을 검증 없이 통과시킬 것인지, 화이트리스트를 만들 것인지. - - 상태: Answered - - 처리 방안: `strategy_id`는 어떤 값을 사용해도 가능하도록 서버에서 검증 없이 통과한다. 화이트리스트를 두지 않는다. FR-3에 반영. - - 종료 조건: 완료. -- OQ-2: `GET /health` `degraded` 상태 정의 — degraded 시 후속 엔드포인트를 계속 시도할지 여부. portfolio-hub 내부 degraded 판단 기준이 없다. - - 상태: Answered - - 처리 방안: `degraded` 값은 응답 스키마에 유효한 상태로 포함한다(FR-1 허용값 유지). 단, 이번 반복에서는 해당 상태를 반환하는 트리거 조건을 정의하지 않으며, 트리거 조건은 추후 구현 시 정의한다. - - 종료 조건: 완료. -- OQ-3: `credential_payload` DB 저장 암호화 방식 — `broker_credentials.credential_payload`는 KIS `app_key`/`app_secret`을 담는 JSON으로, portfolio-hub PostgreSQL DB에 저장된다. 평문 저장 시 DB 덤프·직접 접근 경로에서 인증 정보가 노출된다(R-1). 아래 세 가지 방식 중 하나를 선택해야 한다. - - **옵션 A — 앱 레벨 AES 암호화 (Fernet)**: Python `cryptography` 패키지로 저장 전 암호화, 조회 시 복호화. 암호화 키는 환경변수 1개(`CREDENTIAL_ENCRYPTION_KEY`)로 주입. 인프라 변경 없음. 구현 복잡도 낮음. psql에서 payload 직접 읽기 불가. 현재 스택에 가장 단순하게 추가 가능. - - **옵션 B — pgcrypto 컬럼 암호화**: PostgreSQL `pgcrypto` extension 활성화 후 `pgp_sym_encrypt`/`pgp_sym_decrypt`로 DB 내 암/복호화. 키는 쿼리 파라미터로 전달하거나 환경변수에서 주입. Docker Compose 환경에서 extension 활성화 필요. 앱 레벨 처리 없이 DB에서 완결되지만, 쿼리마다 키를 파라미터로 전달하는 패턴이 필요해 구현 복잡도가 옵션 A보다 높음. - - **옵션 C — 외부 시크릿 관리자 (Vault, AWS Secrets Manager 등)**: DB에는 시크릿 참조 URI만 저장하고, 실제 credential은 외부 서비스에서 조회. 가장 강력하지만 별도 인프라 운영이 필요하며 현재 프로젝트 규모 대비 과잉 투자. - - 상태: Answered - - 처리 방안: **옵션 A(앱 레벨 AES/Fernet)** 채택. Python `cryptography` 패키지로 저장 전 암호화, 조회 시 복호화. 암호화 키는 환경변수 `CREDENTIAL_ENCRYPTION_KEY`로 주입. NFR-3과 R-1 완화 방안에 반영. - - 종료 조건: 완료. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: `GET /health` 응답에 `status`, `service`, `version`, `checked_at` 필드가 모두 존재하고, `checked_at`이 ISO 8601 형식을 따른다. -- S-2: `pfhub service-token issue --user ` 실행 결과에 `token`과 `runtime_id`가 모두 포함되고, 대응 API 엔드포인트로도 동일하게 발급된다. 발급된 token으로 `GET /v1/user_info`를 호출하면 동일 `runtime_id`가 응답에 포함된다. -- S-3: `GET /v1/assets`가 service-token 인증으로 portfolio 목록을 asset 뷰로 반환하고, 각 항목에 `strategy_params` 객체가 포함된다. `status=active` 필터가 정상 동작한다. -- S-4: `GET /v1/user_info`가 `trading_enabled`, `runtime_id`, `brokers[].credential_payload`를 반환하며 `environment` 필드를 포함하지 않는다. -- S-5: 동일 `(asset_id, order_id, execution_id)` 조합으로 `POST /v1/assets/{asset_id}/ledger/trades`를 두 번 호출할 때 첫 번째는 201, 두 번째는 200+`status=duplicate`를 반환하고 DB에는 단일 행만 존재한다. - -## 측정 방식 및 데이터 출처 -- S-1 측정 방식: 자동화 테스트(pytest)로 `/health` 응답 JSON 필드 검증 / 데이터 출처: `tests/test_app.py` 또는 신규 `tests/test_health.py` -- S-2 측정 방식: CLI 출력 파싱 + API 응답 JSON 검증 / 데이터 출처: `tests/test_service_tokens.py`(신규) 또는 `tests/test_auth.py` 확장 -- S-3 측정 방식: `GET /v1/assets` 응답 스키마 및 필터 동작 pytest 검증 / 데이터 출처: `tests/test_v1_assets.py`(신규) -- S-4 측정 방식: `GET /v1/user_info` 응답 JSON 필드 존재·비존재 검증 / 데이터 출처: `tests/test_v1_user_info.py`(신규) -- S-5 측정 방식: 동일 payload 2회 POST 후 응답 코드·status 및 DB row count 검증 / 데이터 출처: `tests/test_v1_ledger.py`(신규) - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: In Scope/Out of Scope 경계가 명확하고 trex-basic 계약 범위와 일치 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1 ~ S-5 모두 pytest 검증 가능한 형태로 기술됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(credential_payload 암호화) OQ-3 Answered — 옵션 A(Fernet) 확정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1, OQ-2, OQ-3 모두 Answered -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin, confirm-discovery skill 확인 완료 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md -- 참조 문서: - - temp/portfolio-hub-api-contract.md (trex-basic Consumer-Driven Contract 원문) - - docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md (선행 Discovery) - - app/api/health.py - - app/cli/commands/service_tokens.py - - app/domain/models.py - - migrations/versions/20260423_0013_remove_bot_principals_service_tokens.py - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~{{DECIDE_CREDENTIAL_ENCRYPTION}}~~: 옵션 A(앱 레벨 Fernet/AES) 채택 확정 (OQ-3 Answered) -- ~~{{DECIDE_MARKET_DEFAULT}}~~: `portfolios.market` nullable 컬럼으로 추가, 기본값 없음, API 응답에서 null 그대로 반환 (Answered) - -## CONFIRM -- ~~{{CONFIRM_RUNTIME_ID_SCOPE}}~~: `runtime_id`는 service-token 1개당 1개로 발급되며 해당 service-token에 연결된다. 동일 사용자의 다른 service-token은 각기 다른 `runtime_id`를 가진다. (A-2 Confirmed) -- ~~{{CONFIRM_TRADING_ENABLED_MAPPING}}~~: `trading_enabled`는 `users.is_active`와 단순 매핑으로 구현한다. 별도 trading 전용 상태 필드는 이번 반복 Out of Scope. (A-4 Confirmed) - -## DATA -- ~~{{DATA_STRATEGY_ID_LIST}}~~: `strategy_id` 화이트리스트 불필요 — 검증 없이 통과하는 것으로 확정 (OQ-1 Answered) -- ~~{{DATA_BROKER_CREDENTIAL_SCHEMA_OTHER}}~~: KIS 외 다른 브로커의 `credential_payload` 스키마 — 이번 반복 Out of Scope로 확정. 향후 반복에서 별도 Discovery로 처리. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 모든 OQ(OQ-1, OQ-2, OQ-3)가 Answered 상태로 해소됨. CONFIRM 항목(runtime_id 발급 방식, trading_enabled 매핑)은 가정(A-2, A-4)으로 문서화되어 있으며 REQ 초안 작성 시 세부 검토 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-05-02 02:00 diff --git a/docs/discovery/dcy-002_20260519_tag-ownership-and-applicability-policy.md b/docs/discovery/dcy-002_20260519_tag-ownership-and-applicability-policy.md new file mode 100644 index 0000000..e860594 --- /dev/null +++ b/docs/discovery/dcy-002_20260519_tag-ownership-and-applicability-policy.md @@ -0,0 +1,239 @@ +# 0. 문서 상태 +이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. + +- 상태: confirmed +- 문서 ID: dcy-002_20260519_tag-ownership-and-applicability-policy +- 이슈명: 태그 소유권 및 적용 대상 정책 — admin/user 구분 태그 모델과 asset·portfolio 적용 정책 도입 +- 작성 시각(KST): 2026-05-19 +- 마지막 갱신 시각(KST): 2026-05-19 (draft-req 완료 — REQ-001~008 생성) +- 대체됨: 없음 +- 후속 Discovery 참조: docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md +- 생성된 REQ 참조: REQ-001, REQ-002, REQ-003, REQ-004, REQ-005, REQ-006, REQ-007, REQ-008 + +# 1. 계획 상태 요약 +현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. + +- 연계 문서 현황: + - 관련 점수 파일: 미생성 + - 관련 TODO/메모: 없음 +- 해석: + - dcy-001에서 태그 정책을 별도 Discovery로 분리하기로 결정했다. 현재 portfolio_tags 테이블은 tag_value 자유 문자열로 태깅하며 소유권 개념이 없다. + - 이번 Discovery에서 판단해야 할 핵심은 tags 마스터 테이블 도입 → admin/user 태그 생성 권한 분리 → asset_tags·portfolio_tags 적용 정책 정의 → 기존 데이터 마이그레이션 전략이다. + +# 2. 요구사항 판정 결과 +이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. + +- 판정: 신규 +- 근거 + - 현재 SDLC 문서에 태그 소유권이나 tags 마스터 테이블 관련 REQ가 존재하지 않는다. + - dcy-001은 태그 정책 전체를 Out of Scope로 명시하며 dcy-002로 이관했다. + +# 3. 문제점의 요약 +문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. + +- 현재 상태 + - portfolio_tags 테이블이 (portfolio_id, tag_value TEXT, created_at)로 구성되어 있으며 태그는 자유 문자열로 저장된다. 정규화된 tags 마스터 테이블이 없어 소유권·권한 제어가 불가능하다. + - 사용자가 임의 문자열을 태그로 등록할 수 있으며, 동일한 개념의 태그가 서로 다른 문자열로 중복 생성될 수 있다. + - asset_tags 테이블이 없다. dcy-001에서 assets 카탈로그를 도입하지만 태그 정책이 정의되지 않으면 assets에 태그를 적용할 수 없다. +- 기대 상태 + - tags 마스터 테이블로 태그를 정규화하고 생성자(created_by_user_id)와 is_admin_tag를 기록해 소유권을 추적한다. + - tags는 hard delete 없이 is_active = FALSE로 비활성화하며, 비활성 태그인지 조회로 확인할 수 있다. + - assets.tag_ids UUID[], portfolios.tag_ids UUID[] 배열 컬럼으로 태그를 저장하고 junction 테이블은 사용하지 않는다. + - admin이 생성한 태그(is_admin_tag = TRUE)만 assets에 적용 가능하고, portfolio에는 is_admin_tag = TRUE이거나 portfolio 소유자가 생성한 태그를 적용 가능하다. + - 기존 portfolio_tags.tag_value 데이터를 새 스키마로 마이그레이션한다. +- 이해관계자 + - 문서 작성자(AI agent) + - 리뷰어/승인자 (시스템 소유자) + - admin 사용자 (태그 생성·asset 태깅·portfolio 태깅) + - 일반 사용자 (태그 생성·자신의 portfolio 태깅) +- 제약사항 + - dcy-001의 assets 테이블이 먼저 확정되어야 assets.tag_ids 컬럼을 추가할 수 있다. + - 기존 portfolio_tags.tag_value 마이그레이션 시 created_by 정보가 없어 기존 태그의 소유자를 소급 지정해야 한다. + +## 주요 사용자 시나리오 + +- admin이 "코스피200" 태그를 생성하고 assets 카탈로그의 삼성전자 항목에 태깅한다. +- 일반 사용자가 "장기보유" 태그를 생성하고 자신의 삼성전자 portfolio에 태깅한다. admin이 생성한 "코스피200"도 동일 portfolio에 추가 태깅한다. +- 일반 사용자가 admin 전용 태그를 asset에 태깅하려 하면 HTTP 403 Forbidden을 반환한다. + +# 4. 이번에 정의할 변경 +이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. + +- tags 마스터 테이블 도입: (tag_id UUID PK, name TEXT NOT NULL UNIQUE, created_by_user_id FK → users.id, is_admin_tag BOOLEAN, is_active BOOLEAN, created_at TIMESTAMPTZ) — soft delete 방식으로 비활성화 +- assets.tag_ids UUID[] 컬럼 추가 — junction 테이블 없이 배열로 태그 참조 저장 +- portfolios.tag_ids UUID[] 컬럼 추가 및 기존 portfolio_tags 테이블 제거 +- 태그 CRUD API: POST /v1/tags, GET /v1/tags, PATCH /v1/tags/{tag_id}/deactivate (비활성화) +- 기존 portfolio_tags.tag_value 데이터 마이그레이션 (tags 테이블 생성 후 portfolios.tag_ids 배열로 이전) + +## 영향 범위 +변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. + +- 코드 영향: + - migrations/versions/ — tags 생성, assets.tag_ids 컬럼 추가, portfolios.tag_ids 컬럼 추가, portfolio_tags 테이블 제거 마이그레이션 + - app/schemas/v1.py — tags 관련 요청/응답 스키마 추가, AssetItem·PortfolioItem에 tag_ids 필드 추가 + - app/routers/ — /v1/tags 엔드포인트 추가 + - app/repositories/ — tags CRUD, assets/portfolios tag_ids 배열 조작 메서드 추가 + - app/cli/ — 기존 `pfhub portfolio tag add/list/remove` 명령어(portfolio_tags 자유 문자열 기반)를 교체하고, 새 `pfhub tag` 최상위 서브커맨드를 추가 (tags 마스터 테이블 기반) +- 문서 영향: + - docs/data-model.md — tags 테이블 추가, assets/portfolios 스키마에 tag_ids 컬럼 반영, portfolio_tags 테이블 제거 + - docs/interface-contract.md — /v1/tags API 명세 추가 +- 운영 영향: + - 마이그레이션 실행 필요 (portfolio_tags.tag_value → tags 생성 후 portfolios.tag_ids 배열 이전, portfolio_tags 테이블 드롭) + - 기존 portfolio_tags 기반 쿼리·API가 있다면 인터페이스 변경 영향 + +# 5. 요구사항 목록 +실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. + +## 기능 요구사항 +- FR-1: tags 마스터 테이블을 생성한다. 스키마: (tag_id UUID PK DEFAULT gen_random_uuid(), name TEXT NOT NULL, created_by_user_id TEXT NOT NULL REFERENCES users(id), is_admin_tag BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()), UNIQUE(name); 태그 생성 시 요청자가 admin 권한이면 is_admin_tag = TRUE로 기록하며 이후 변경하지 않는다. 태그 삭제는 is_active = FALSE로 비활성화하며 hard delete는 사용하지 않는다. +- FR-2: assets 테이블에 tag_ids UUID[] NOT NULL DEFAULT '{}' 컬럼을 추가한다. asset에 태그 적용 시 tags.is_admin_tag = TRUE이고 tags.is_active = TRUE인지 확인하고, 조건 불만족 시 HTTP 403을 반환한다. 비활성 태그(is_active = FALSE)는 적용 불가하며 목록 조회 시 별도 표시한다. +- FR-3: portfolios 테이블에 tag_ids UUID[] NOT NULL DEFAULT '{}' 컬럼을 추가하고 기존 portfolio_tags 테이블을 제거한다. portfolio에 태그 적용 시 tags.is_active = TRUE이고, tags.is_admin_tag = TRUE이거나 tags.created_by_user_id가 해당 portfolio 소유자인지 확인하고, 조건 불만족 시 HTTP 403을 반환한다. +- FR-4: 태그 HTTP API를 제공한다. + - 태그 마스터 관리: POST /v1/tags (태그 생성 — trade-write 이상), GET /v1/tags (태그 목록 조회, is_active 필터 지원 — read 이상), PATCH /v1/tags/{tag_id}/deactivate (태그 비활성화 — admin 또는 태그 생성자 본인) + - asset 태그 적용·해제: POST /v1/assets/{asset_id}/tags (body: {"tag_id": "..."}, asset 태깅 — admin), DELETE /v1/assets/{asset_id}/tags/{tag_id} (asset 태그 해제 — admin) + - portfolio 태그 적용·해제: POST /v1/portfolios/{portfolio_id}/tags (body: {"tag_id": "..."}, portfolio 태깅 — trade-write 이상, 소유자 본인 또는 admin), DELETE /v1/portfolios/{portfolio_id}/tags/{tag_id} (portfolio 태그 해제 — trade-write 이상, 소유자 본인 또는 admin) +- FR-5: 기존 portfolio_tags 데이터를 마이그레이션한다. tag_value를 소문자 정규화 후 tags 테이블에 INSERT(ON CONFLICT DO NOTHING, is_admin_tag = FALSE, created_by_user_id는 마이그레이션 실행 전 운영자가 확인해 지정하는 특정 admin 계정 ID로 설정)하고, 각 portfolio의 tag_ids 배열에 해당 tag_id를 추가한 뒤 portfolio_tags 테이블을 제거한다. 마이그레이션 스크립트는 실행 전 지정 admin 계정이 users 테이블에 존재하는지 사전 검증한다. +- FR-6: 태그 CLI 명령어를 제공한다. `pfhub tag create ` (태그 생성), `pfhub tag list` (태그 목록, --inactive 플래그로 비활성 포함), `pfhub tag deactivate ` (태그 비활성화), `pfhub tag apply asset ` (asset 태깅), `pfhub tag apply portfolio ` (portfolio 태깅), `pfhub tag remove asset ` / `pfhub tag remove portfolio ` (태그 해제) + +## 비기능 요구사항 +성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. + +- NFR-1: tags.name은 UNIQUE 제약으로 전역 중복을 방지한다. 저장 전 애플리케이션 레이어에서 소문자로 정규화(str.lower())하며, DB 컬럼은 TEXT UNIQUE 그대로 사용한다. 동일 이름의 대소문자 변형은 중복으로 처리해 거부한다. +- NFR-2: tags는 hard delete를 사용하지 않으므로 배열 참조 무결성은 애플리케이션 레이어에서 보장한다. tag_ids 배열에서 tag를 조회할 때 is_active = FALSE인 항목은 비활성 표시(archived)로 반환하고 조용히 제거하지 않는다. assets.tag_ids, portfolios.tag_ids에는 GIN 인덱스를 생성해 배열 조회 성능을 확보한다. +- NFR-3: 적용 정책 위반(asset에 non-admin 생성 태그 적용 시도, portfolio에 타인 생성 태그 적용 시도) 시 HTTP 403 Forbidden을 반환한다. + +## 범위 경계 +이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. + +- In Scope + - tags 마스터 테이블 도입 (is_admin_tag, is_active 포함) + - assets.tag_ids UUID[] 컬럼 추가 + - portfolios.tag_ids UUID[] 컬럼 추가 및 portfolio_tags 테이블 제거 + - 태그 생성·목록·비활성화 HTTP API (/v1/tags) + - assets/portfolios 태그 적용·해제 API + - 태그 관리 CLI 명령어 (pfhub tag ...) + - 기존 portfolio_tags.tag_value 데이터 마이그레이션 +- Out of Scope + - 태그 이름 변경(rename) — 전파 영향 범위가 커서 별도 검토 필요 + - 태그 계층 구조(parent-child) — 단일 레이블 모델만 지원 + - 태그 검색/자동완성 API — 기본 목록 조회만 포함 + +# 6. 리스크/가정 목록 +불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. + +## 리스크 +- R-1 (High): 기존 portfolio_tags.tag_value 마이그레이션 시 created_by_user_id를 소급 지정할 정보가 없다. 잘못된 소유권 설정이 이후 적용 정책 오류로 이어질 수 있다. + - 완화 방안: 마이그레이션 시 시스템 admin 계정 또는 별도 지정 사용자로 일괄 설정하고, 마이그레이션 완료 후 개별 태그 소유권 재지정 스크립트를 제공한다. +- R-2 (Medium): dcy-001의 assets 테이블이 확정되기 전에 assets.tag_ids 컬럼을 추가하면 스키마 충돌이 발생할 수 있다. + - 완화 방안: dcy-001 확정 및 assets 마이그레이션 완료 후 dcy-002 구현을 시작한다. batch 계획 시 의존 순서를 명시한다. +- R-3 (Low): tags.name UNIQUE 제약 추가 후 기존 portfolio_tags.tag_value에 대소문자만 다른 동일 의미 태그가 존재할 수 있다. + - 완화 방안: 마이그레이션 전 중복 tag_value를 조사하고, 소문자 정규화 또는 선택적 병합 스크립트를 실행한다. + +## 가정 + +- A-1: 태그 생성자가 admin인지는 tags.is_admin_tag(생성 시점 스냅샷)로 판단한다. 이후 권한 변경과 무관하게 고정되므로 런타임 JOIN이 불필요하다. +- A-2: 기존 portfolio_tags.tag_value 마이그레이션 시 충돌 방지를 위해 name을 소문자 정규화 후 UNIQUE 체크한다. +- A-3: 태그 삭제는 is_active = FALSE로 처리(soft delete)하며 hard DELETE 및 CASCADE DELETE를 사용하지 않는다. assets.tag_ids, portfolios.tag_ids의 UUID[] 배열은 비활성 태그를 자동 제거하지 않고 조회 시 archived로 표시한다. + +## 오픈 질문 +답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. + +- OQ-1: tags.name 대소문자 구분 여부 — case-sensitive(기본 TEXT UNIQUE)인지, case-insensitive(CITEXT 또는 lower() 인덱스 기반 UNIQUE)인지. + - 상태: Answered + - 처리 방안: case-insensitive, 애플리케이션 레이어에서 소문자 정규화 후 저장 방식으로 결정. extension 의존성 없이 TEXT UNIQUE 제약을 그대로 사용한다. + - 반영 위치: NFR-1 수정 완료. +- OQ-2: 태그 적용 시 get-or-create 정책 — 태그 이름으로 적용 요청 시 tags 테이블에 없으면 자동 생성(get-or-create)할지, 먼저 태그를 생성한 뒤 tag_id로만 적용을 허용할지. + - 상태: Answered + - 처리 방안: explicit-create 방식으로 결정. 태그 생성(POST /v1/tags)과 태그 적용(apply)을 별도 단계로 분리하며, 존재하지 않는 tag_id로 apply 요청 시 HTTP 404를 반환한다. + - 반영 위치: FR-4(태그 생성·적용 분리 명시), FR-6(CLI create/apply 분리 설계) 현행 유지. +- OQ-3: 마이그레이션 시 기존 portfolio_tags.tag_value의 created_by_user_id를 어느 admin 계정으로 지정할지. + - 상태: Answered + - 처리 방안: 특정 admin 계정을 지정하는 방식으로 결정. 마이그레이션 실행 전 운영자가 대상 admin 계정 ID를 확인해 스크립트에 명시한다. is_admin_tag = FALSE로 설정(기존 태그는 사용자 포트폴리오에서 유래했으므로 asset 적용 대상으로 간주하지 않음). + - 반영 위치: FR-5 수정 완료. +- OQ-4: 태그 생성자가 admin인지 판단하는 방식 — 런타임 JOIN 방식과 생성 시점 스냅샷 방식 중 어느 쪽을 선택할지. + - 상태: Answered + - 처리 방안: 생성 시점 스냅샷 방식으로 결정. tags 테이블에 is_admin_tag BOOLEAN NOT NULL DEFAULT FALSE 컬럼을 추가해 태그 생성 시 권한을 고정 기록한다. + - 반영 위치: FR-1 스키마(is_admin_tag 컬럼 추가), FR-2 검증 로직(is_admin_tag = TRUE 조건), FR-3 검증 로직(is_admin_tag = TRUE OR 소유자 조건), A-1 수정 완료. + +### 오픈 질문 처리 기준 +- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. +- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. +- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. +- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. +- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. +- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. +- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. + +# 7. 초기 성공 기준 +이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. + +- S-1: tags 마스터 테이블이 생성되고 모든 태그가 created_by_user_id와 함께 정규화되어 저장된다. +- S-2: asset_tags에 admin이 생성한 태그만 적용 가능하고, 비admin 생성 태그 적용 시 HTTP 403이 반환된다. +- S-3: portfolios.tag_ids UUID[] 배열로 이전되고 기존 portfolio_tags 테이블이 제거되며, portfolio에 admin 또는 소유자 생성 태그만 적용 가능하다. +- S-4: 기존 portfolio_tags.tag_value 데이터가 새 스키마로 누락 없이 마이그레이션된다. + +## 측정 방식 및 데이터 출처 +각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. + +- S-1 측정 방식: 마이그레이션 실행 후 tags 테이블 row 수가 기존 DISTINCT tag_value 수와 일치하는지 SQL로 확인 / 데이터 출처: 마이그레이션 전후 DB 쿼리 결과 +- S-2 측정 방식: 통합 테스트 — 비admin 사용자 토큰으로 asset 태깅 요청 시 403 응답 검증 / 데이터 출처: pytest 테스트 결과 +- S-3 측정 방식: 통합 테스트 — admin 태그 및 소유자 태그는 portfolio 태깅 성공, 타인 생성 태그 적용 시 403 검증 / 데이터 출처: pytest 테스트 결과 +- S-4 측정 방식: 마이그레이션 후 portfolio별 태그 목록이 기존 tag_value 목록과 일치하는지 SQL로 확인 / 데이터 출처: 마이그레이션 전후 DB 쿼리 결과 + +# 8. REQ로 넘기기 전 확인 체크 +REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. + +- [x] 범위(In Scope/Out of Scope) 확정 + - 기준: 사용자가 In/Out of Scope 목록을 검토하고 승인 + - 확인: confirm-discovery 실행(2026-05-19) — 시스템 소유자 승인 +- [x] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: 각 성공 기준에 측정 방식과 데이터 출처가 명시됨 + - 확인: S-1~S-4 각각 SQL 검증/pytest 출처 명시됨 (# 7 참조) +- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 + - 기준: R-1(마이그레이션 소유권 문제) 해소 방안과 담당자 확정 + - 확인: R-1 완화 방안 명시됨; FR-5에서 사전 검증 절차 정의; 담당자 — 시스템 소유자(마이그레이션 실행 운영자) +- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 + - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 +- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 + - 기준: 사용자(시스템 소유자)가 CONFIRMED_BY로 지정되어 승인 + - 확인: Justin Ko (genichin@gmail.com) + +# 9. 파일 처리 결과 +이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. + +- 처리 결과: 생성 +- 생성 경로: docs/discovery/dcy-002_20260519_tag-ownership-and-applicability-policy.md +- 참조 문서: + - docs/data-model.md + - docs/interface-contract.md + - docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md + +# 10. 사용자 결정 필요 항목 요약 +사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. + +## DECIDE (완료) +- ~~DECIDE_NAME_CASE~~: OQ-1 Answered — case-insensitive, 애플리케이션 레이어 str.lower() 정규화 후 TEXT UNIQUE 저장 (NFR-1 반영 완료) +- ~~DECIDE_TAG_APPLY_POLICY~~: OQ-2 Answered — explicit-create, 미존재 tag_id apply 시 HTTP 404 (FR-4, FR-6 반영 완료) + +## CONFIRM (완료) +- ~~CONFIRM_MIGRATION_OWNER~~: OQ-3 Answered — 특정 admin 계정 지정, is_admin_tag=FALSE, 마이그레이션 전 계정 존재 사전 검증 (FR-5 반영 완료) + +## DATA +- 없음 — 기존 tag_value 중복 검사는 소문자 정규화 후 UNIQUE 체크 방식으로 접근법 확정(R-3, A-2, NFR-1). 실제 데이터 검증은 마이그레이션 스크립트 사전 검증 단계(FR-5)에서 수행. + +# 11. Discovery Freeze +REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. + +## 섹션 참조 +- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) +- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) +- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) +- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) +- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) + +## Freeze 플래그 +- Handoff Decision: 승인 +- Handoff Rationale: OQ-1~OQ-4 모두 Answered 완료. 범위 확정, Section 8 전체 체크, 저장소 사실 관계 검증 완료. REQ 초안 작성 진행 가능. +- Ready for REQ Drafting: true +- Confirmed By: Justin Ko (genichin@gmail.com) +- Confirmed At (KST): 2026-05-19 diff --git a/docs/discovery/dcy-003_20260502_portfolio-list-user-required.md b/docs/discovery/dcy-003_20260502_portfolio-list-user-required.md deleted file mode 100644 index ff580de..0000000 --- a/docs/discovery/dcy-003_20260502_portfolio-list-user-required.md +++ /dev/null @@ -1,191 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-003_20260502_portfolio-list-user-required -- 이슈명: CLI `pfhub portfolio list` — `--user` 옵션 필수화 및 소유자 기반 조회 강제 -- 작성 시각(KST): 2026-05-02T00:00:00+09:00 -- 마지막 갱신 시각(KST): 2026-05-02T09:10:00+09:00 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: - - [REQ-013](../../docs/srs/Interface/req-013_portfolio-list-user-required.md) — CLI `portfolio list --user` 필수화 및 소유자 기반 조회 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 없음 -- 해석: - - CLI `pfhub portfolio list`는 현재 `--user` 옵션이 없어 `owner_user_id=None`으로 서비스를 호출하며, DB에 저장된 전체 포트폴리오를 반환한다. HTTP API는 `_ownership_required` 설정 및 ADMIN 여부에 따라 소유자 필터를 적용하지만 CLI에는 동일한 접근 통제가 존재하지 않는다. - - 이번 Discovery에서는 CLI `portfolio list` 명령에 `--user` 옵션을 필수로 지정하고, 해당 username으로 `owner_user_id`를 조회하여 소유자 기반 필터 조회를 강제하는 방향을 정의한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-001(실DB 마이그레이션 검증)과 dcy-002(trex-basic API 계약)는 모두 CLI `portfolio list` 인터페이스 및 소유자 필터 정책을 다루지 않는다. - - 기존 REQ 목록(`docs/srs/index.md`, REQ-001~REQ-012)에 CLI `portfolio list` 접근 통제 관련 요구사항이 존재하지 않는다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `app/cli/commands/portfolio.py`의 `list_portfolios` 명령은 `--user` 옵션이 없으며, `owner_user_id=None`으로 `PortfolioService.list_portfolios`를 호출한다. - - `owner_user_id=None`이면 `app/repositories/postgres.py`의 `list_portfolios`가 소유자 WHERE 절을 추가하지 않아 전체 포트폴리오를 반환한다. - - HTTP API(`app/api/portfolios.py`)는 `_ownership_required` 설정 플래그와 ADMIN 권한 여부에 따라 소유자 필터를 조건부 적용하지만, CLI에는 동일한 로직이 없다. -- 기대 상태 - - `pfhub portfolio list --user ` 형태로 `--user`를 필수 옵션으로 지정한다. - - `--user` 없이 실행하면 명확한 오류 메시지와 함께 종료된다. - - `--user`로 전달된 username을 users 테이블에서 조회하여 `owner_user_id`(UUID)로 변환한 뒤 조회한다. -- 이해관계자 - - CLI 운영자(관리자 계정으로 pfhub CLI를 사용하는 사람) - - 백엔드 개발자(CLI 인터페이스 및 서비스 레이어 관리자) -- 제약사항 - - CLI는 기존 `PortfolioService.list_portfolios`와 `UserService` (또는 user repository)를 재사용해야 한다. 별도 API 호출 없이 직접 DB를 통해 username → user_id 변환이 이루어진다. - - `pfhub_DB_ENABLED=false`(in-memory) 모드에서도 동일한 인터페이스 검증이 적용되어야 한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 관리자가 특정 사용자의 포트폴리오만 확인하기 위해 `pfhub portfolio list --user alice`를 실행한다. -- 관리자가 `--user` 없이 `pfhub portfolio list`를 실행했을 때 "Error: --user 옵션이 필요합니다" 형태의 메시지를 받고 올바른 사용법을 인지한다. -- 존재하지 않는 username으로 `pfhub portfolio list --user unknown`을 실행하면 사용자를 찾을 수 없다는 오류 메시지와 함께 종료된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- CLI `portfolio list` 명령에 `--user ` 필수 옵션을 추가하고, 미입력 시 에러로 종료한다. -- CLI 내부에서 username → owner_user_id(UUID) 변환 로직을 추가한다(user repository 또는 user service 직접 조회). -- `PortfolioService.list_portfolios` 호출 시 변환된 `owner_user_id`를 항상 전달하도록 CLI 호출부를 수정한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `app/cli/commands/portfolio.py` — `list_portfolios` 명령 시그니처 및 서비스 호출부 수정 - - `app/repositories/postgres.py` — `get_user_by_username`(line 419) 직접 호출. `UserService`에는 해당 메서드 없음 — repository를 통해 직접 조회 -- 문서 영향: - - `README.md` — `pfhub portfolio list` 예제 라인에 `--user` 옵션 추가 - - `docs/runtime-flows.md` — CLI portfolio list 흐름 갱신(있을 경우) -- 운영 영향: - - 기존 스크립트에서 `pfhub portfolio list`를 `--user` 없이 호출하면 즉시 에러로 변경됨 → 스크립트 마이그레이션 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: CLI `pfhub portfolio list` 명령에 `--user ` 옵션을 필수로 추가한다. `--user` 없이 실행하면 오류 메시지를 출력하고 비정상 종료(exit code 1)한다. -- FR-2: CLI 내부에서 `--user`로 전달받은 username을 user 저장소에서 조회하여 `owner_user_id`(UUID)를 얻고, 이를 `PortfolioService.list_portfolios`의 `owner_user_id` 인자로 전달한다. -- FR-3: `--user`로 전달된 username이 존재하지 않으면 "사용자를 찾을 수 없습니다" 형태의 오류 메시지를 출력하고 비정상 종료한다. - -## 비기능 요구사항 -- NFR-1: username → user_id 변환 실패 메시지는 기존 CLI 오류 출력 형식(Rich Console 또는 typer.echo)과 일관성을 유지한다. -- NFR-2: `--user` 필수화는 in-memory 모드(`pfhub_DB_ENABLED=false`)에서도 동일하게 적용되어야 하며, 테스트에서 검증 가능해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - CLI `portfolio list` 명령의 `--user` 필수 옵션화 - - username → owner_user_id 변환 로직 (user repo/service 직접 조회) - - README 예제 수정 -- Out of Scope - - HTTP API `GET /portfolios`의 접근 통제 변경 (현행 유지) - - `portfolio list`의 `--all` 관리자 전체 조회 옵션 추가 (후속 Discovery로 분리) - - CLI에 HTTP API 방식의 `_ownership_required` 설정 플래그 도입 (이번 반복 외) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Medium): 기존 스크립트 또는 자동화에서 `pfhub portfolio list`를 `--user` 없이 호출하는 경우, 변경 후 즉시 에러로 중단된다. - - 완화 방안: README에 변경 사항을 명확히 기재하고, 커밋 메시지에 breaking change 표기. -- R-2 (Low): ~~in-memory 모드에서 username 조회를 위한 user repo 인터페이스가 충분히 구현되어 있지 않을 수 있다.~~ → **해소**: `FakeRepo.get_user_by_username`(`tests/test_cli.py:109`)과 `PortfolioRepository` Protocol stub(`portfolio_repository.py:16`) 모두 존재 확인. - - 완화 방안: 해소 완료 — 추가 구현 불필요. - -## 가정 -- A-1: CLI는 관리자 권한으로만 실행된다는 기존 설계를 유지한다. 따라서 `--user`로 타인의 포트폴리오도 조회 가능하며 CLI 레벨의 추가 권한 검사는 이번 범위에서 제외한다. -- A-2: `get_user_by_username`이 이미 `PortfolioRepository` Protocol(`portfolio_repository.py:16`), `PostgresRepo`(`postgres.py:419`), `FakeRepo`(`tests/test_cli.py:109`) 모두에 구현되어 있음이 확인되었다. -- A-3: `PortfolioService.list_portfolios`의 시그니처는 변경하지 않는다. CLI 호출부에서만 `owner_user_id`를 전달한다. - -## 오픈 질문 -- OQ-1: in-memory FakeRepo에서 username → user_id 조회 메서드가 없는 경우 테스트를 어떻게 구성할지. - - 상태: Answered - - 처리 방안: `tests/test_cli.py`의 `FakeRepo`에 `get_user_by_username(username) -> User | None` 메서드가 이미 구현되어 있음(line 109). `_users` dict에서 username 일치 항목을 선형 탐색하여 반환한다. 추가 stub 없이 기존 메서드를 그대로 사용하면 된다. `app/repositories/postgres.py`에도 동일 시그니처(line 419)가 구현되어 있어 in-memory/DB 모드 모두 지원 가능하다. - - 종료 조건: 저장소 현황 검토 완료 — FakeRepo 및 PostgresRepo 양쪽 모두 `get_user_by_username` 존재 확인됨. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: `pfhub portfolio list` (without `--user`) 실행 시 exit code 1과 함께 오류 메시지가 출력된다. -- S-2: `pfhub portfolio list --user alice` 실행 시 alice의 `owner_user_id`로 필터된 포트폴리오 목록만 반환된다. -- S-3: 존재하지 않는 username으로 실행 시 "사용자를 찾을 수 없습니다" 형태의 오류 메시지와 함께 비정상 종료된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: pytest에서 `CliRunner.invoke`로 `--user` 없는 호출의 exit_code == 1 확인 / 데이터 출처: `tests/test_cli.py` -- S-2 측정 방식: FakeRepo/DB에 복수 사용자 포트폴리오 세팅 후 CLI 결과에 alice 소유 항목만 존재하는지 검증 / 데이터 출처: `tests/test_cli.py` -- S-3 측정 방식: 존재하지 않는 username 입력 시 exit_code != 0 및 오류 문자열 포함 여부 검증 / 데이터 출처: `tests/test_cli.py` - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: `--user` 필수화 In Scope, `--all` 옵션/HTTP API 변경 Out of Scope — #5에 명시. confirm-discovery 실행으로 확정. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1/S-2/S-3 각각 pytest `CliRunner.invoke` 기반 검증 케이스로 기술됨. #7 참조. -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 리스크 중 High 등급 없음(R-1: Medium, R-2: Low → 해소). R-1 완화 방안(README 명기, breaking change 표기)을 문서에 명시. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1 Answered 처리 완료. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 프로젝트 오너가 confirm-discovery 실행으로 확정. 2026-05-02. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-003_20260502_portfolio-list-user-required.md -- 참조 문서: - - app/cli/commands/portfolio.py - - app/services/portfolio_service.py - - app/repositories/postgres.py - - app/api/portfolios.py - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~{{DECIDE_ALL_OPTION}}~~ → Resolved: `--all` 옵션은 #5 Out of Scope로 결정됨. 후속 Discovery로 분리. - -## CONFIRM -- ~~{{CONFIRM_BREAKING_CHANGE}}~~ → Resolved: R-1(Medium)에 문서화됨. 완화 방안(README 명기, breaking change 표기) 채택. confirm-discovery 실행으로 인지 확인. -- ~~{{CONFIRM_SCOPE}}~~ → Resolved: In Scope / Out of Scope가 #5에 명시됨. confirm-discovery 실행으로 확정. - -## DATA -- ~~{{DATA_FAKEREPO_USERNAME_LOOKUP}}~~ → Answered: `FakeRepo.get_user_by_username`(`tests/test_cli.py:109`) 및 `PostgresRepo.get_user_by_username`(`app/repositories/postgres.py:419`) 모두 존재 확인 완료. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -- Handoff Decision: 승인 -- Handoff Rationale: 저장소 현황 재확인 완료. 모든 C 유형 플레이스홀더 해소, OQ Open 없음, #8 체크리스트 전항 완료. REQ 초안 작성 준비 완료. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-05-02T09:00:00+09:00 -- 범위 참조: [# 5. 요구사항 목록](#5-요구사항-목록) -- 성공 기준 참조: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 리스크 참조: [# 6. 리스크/가정 목록](#6-리스크가정-목록) diff --git a/docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md b/docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md deleted file mode 100644 index eba545f..0000000 --- a/docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md +++ /dev/null @@ -1,240 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution -- 이슈명: portfolio_id UUID 교체 및 CLI 사용자 범위 기반 자산 식별 구조 전환 -- 작성 시각(KST): 2026-05-11 00:00 -- 마지막 갱신 시각(KST): 2026-05-11 (draft-req 완료 — REQ-016~019 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: - - [REQ-016](../srs/Migration/req-016_portfolios-uuid-schema-migration.md) — portfolios 테이블 UUID 전환 및 데이터 마이그레이션 (FR-1, FR-5, NFR-1) - - [REQ-017](../srs/Interface/req-017_portfolio-cli-name-user-identification.md) — CLI portfolio 명령 name+user 조합 식별 방식 전환 (FR-2, FR-3, FR-3a, NFR-2) - - [REQ-018](../srs/Interface/req-018_portfolio-http-api-uuid-path-param.md) — HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 (FR-4) - - [REQ-019](../srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md) — portfolio UUID 전환 관련 baseline 문서 갱신 (FR-6, NFR-3) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 계약 문서: docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md (A-3: asset_id는 portfolio_id를 그대로 사용) - - 관련 TODO/메모: 없음 -- 해석: - - 현재 `portfolio_id`는 사용자가 직접 지정하는 전역 고유 문자열이어서 다중 사용자 환경에서 이름 충돌이 발생하고, CLI 뮤테이션 명령이 소유권 검증 없이 portfolio_id만으로 실행된다. - - 이번 Discovery는 `portfolio_id`를 서버 생성 UUID로 교체하고 사용자 표시용 `name`을 `(owner_user_id, name)` 복합 유니크 제약으로 관리하는 구조 전환 전체 범위를 정의한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy-001은 DB 마이그레이션 검증, dcy-002는 trex-basic 소비자 API 계약, dcy-003은 portfolio list --user 필수화를 다루며, portfolio_id 식별자 체계 전환을 다루는 Discovery가 없다. - - `portfolio_id`를 UUID로 교체하고 `(owner_user_id, name)` 복합 유니크 제약을 도입하는 것은 DB 스키마, 도메인 모델, CLI 인터페이스, HTTP API 경로, 소비자 계약 전반에 영향하는 독립적인 구조 변경 주제다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `portfolios.portfolio_id`는 TEXT PRIMARY KEY로 사용자가 직접 지정하는 전역 단일 네임스페이스다. alice가 `005930:tv`를 먼저 생성하면 bob은 동일 이름을 사용할 수 없다. - - CLI 뮤테이션 명령(`buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `status`, `update`)은 `portfolio_id`를 Argument로만 받고 소유자 검증을 하지 않는다. 권한 있는 운영자가 다른 사용자 소유의 portfolio를 portfolio_id만 알면 조작할 수 있다. - - `ledger_entries`(portfolio_ledger), `portfolio_comments`, `portfolio_tags`, `ledger_trades` 테이블이 `portfolio_id` TEXT를 FK로 참조한다. - - dcy-002 A-3에 따라 `GET /v1/assets`의 `asset_id` 응답 필드가 `portfolio_id`를 그대로 반환하므로, trex-basic 데몬이 `asset_id`로 ledger 기록 등 후속 API를 호출한다. - - `Portfolio` dataclass(`app/domain/models.py`)와 PostgreSQL 레포지터리 전반이 `portfolio_id` 문자열을 기준으로 설계되어 있다. -- 기대 상태 - - `portfolios.portfolio_id`는 서버가 발급하는 UUID(TEXT 또는 UUID 타입)로 교체된다. 사용자는 create 시 `name`만 지정하면 된다. - - `name` 컬럼은 `(owner_user_id, name)` 복합 UNIQUE 제약으로 같은 사용자 내에서 유일하게 관리된다. 다른 사용자는 동일 `name`을 사용할 수 있다. - - CLI 뮤테이션 명령은 `--user ` 조합으로 portfolio를 식별하고, 내부적으로 UUID를 조회해 처리한다. 소유권 검증이 명령 실행 경로에 포함된다. - - HTTP API 경로는 UUID를 path parameter로 사용하며, `GET /v1/assets`의 `asset_id`도 UUID를 반환한다. - - 기존 데이터는 마이그레이션으로 새 UUID를 할당받고, 기존 `portfolio_id` 문자열은 `name` 컬럼으로 이전된다. -- 이해관계자 - - portfolio-hub 유지보수 담당자 (구현 주체) - - trex-basic 팀 (GET /v1/assets asset_id 필드 변경 영향을 받는 소비자) - - CLI 운영자 (명령 인터페이스 변경 대상) -- 제약사항 - - 기존 Alembic revision chain을 깨지 않는 IF NOT EXISTS 기반 migration을 추가한다(dcy-001/REQ-002 정책 준수). - - dcy-002 A-3("asset_id는 portfolio_id를 그대로 사용")은 이번 Discovery에서 철회된다. trex-basic 팀에게 breaking change를 사전 통보해야 한다. - - `name`은 owner 내에서 유일하며, 허용 문자는 `[a-zA-Z0-9._:-]`, 공백 불허, 1~100자, 대소문자 구분으로 확정됐다 (DATA_NAME_ALLOWED_CHARS Answered 2026-05-11). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 `pfhub portfolio create tv --symbol 005930 --user alice --cash-balance 10000000 --force`로 포트폴리오를 생성하면 서버가 UUID를 부여하고, 이후 `pfhub portfolio buy tv --user alice --quantity 10 --price 71000 --force`로 alice의 `tv` 포트폴리오를 정확히 조작한다. bob이 같은 이름 `tv`를 가진 포트폴리오를 생성해도 독립된 레코드로 관리된다. -- trex-basic 데몬이 `GET /v1/assets`를 호출하면 `asset_id`로 UUID를 수신하고, 이후 `POST /v1/assets/{asset_id}/ledger/trades`에 UUID를 사용해 체결 내역을 기록한다. UUID 도입으로 asset_id가 전역 유일하게 보장된다. -- 기존에 `005930:tv`라는 portfolio_id로 운영 중인 데이터가 있을 때, 마이그레이션 후 기존 레코드의 `name`이 `005930:tv`로 이전되고 새 UUID가 `portfolio_id`로 할당된다. ledger, comments 등 FK 참조도 새 UUID로 함께 갱신된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- `portfolios.portfolio_id` PRIMARY KEY를 서버 생성 UUID로 교체하고 `name` TEXT 컬럼을 신규 추가: `(owner_user_id, name)` 복합 UNIQUE 제약 적용, 기존 portfolio_id 문자열을 name으로 이전하는 데이터 마이그레이션 포함 -- CLI 뮤테이션 명령 식별 방식 전환: 모든 뮤테이션 명령에서 `portfolio_id` Argument를 `` + `--user ` 조합으로 교체하고, 내부에서 `(owner_user_id, name)` → UUID 조회 후 처리 -- HTTP API 경로 및 GET /v1/assets asset_id 전환: path parameter와 응답 필드를 UUID 기반으로 변경, dcy-002 A-3 철회 및 trex-basic 계약 갱신 - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `migrations/versions/`: portfolios PK 교체, name 컬럼 추가, (owner_user_id, name) UNIQUE 제약, 기존 데이터 이전 마이그레이션 - - `app/domain/models.py`: Portfolio dataclass portfolio_id → UUID, name 필드 명확화 - - `app/repositories/postgres.py`: create_portfolio(서버 UUID 생성), get_portfolio_by_name_and_user() 신규, 모든 portfolio_id 참조 업데이트 - - `app/domain/services.py`: transfer, buy, sell 등 서비스 메서드 시그니처 — (user_id, name) → UUID 해석 추가 - - `app/cli/commands/portfolio.py`: status, buy, sell, invest, transfer, reset, delete, update 명령의 Argument 교체 - - `app/api/portfolios.py` (HTTP API): path parameter UUID 기반 전환 - - `app/api/v1_assets.py`: asset_id 응답 필드를 UUID로 반환 - - `app/schemas/portfolio.py`, `app/schemas/v1.py`: asset_id/portfolio_id 필드 타입 문서화 -- 문서 영향: - - `docs/data-model.md`: portfolios 테이블 스키마 변경 반영 - - `docs/interface-contract.md`: GET /v1/assets asset_id 변경 반영 - - `docs/project-structure.md`: 갱신 필요 (repositories, services 변경 반영) - - `docs/runtime-flows.md`: 갱신 필요 (CLI 명령 흐름, v1 API 흐름 변경 반영) - - `docs/discovery/dcy-002_*`: A-3 철회 주석 추가 -- 운영 영향: - - trex-basic 팀에게 asset_id 필드값이 UUID로 바뀜을 사전 통보 필요 - - CLI 운영 스크립트에서 portfolio_id 직접 지정 방식을 `--user + name` 방식으로 교체 필요 - - 기존 운영 runbook 갱신 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `portfolios.portfolio_id`를 서버 생성 UUID(TEXT)로 교체한다. 기존 사용자 지정 portfolio_id 문자열은 `portfolios.name` TEXT 컬럼으로 이전한다. `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 보장한다. -- FR-2: `portfolio create` CLI 명령은 `name` Argument와 `--user` 옵션을 필수로 받고, `portfolio_id`는 서버가 UUID로 발급한다. 사용자는 UUID를 직접 지정하거나 조회할 수 없다. -- FR-3: CLI 뮤테이션 명령(`status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update`)은 `` Argument와 `--user ` 옵션으로 portfolio를 식별한다. 내부에서 `(owner_user_id, name)` → `portfolio_id(UUID)`를 조회한 뒤 처리한다. `name` + `--user` 조합으로 portfolio를 찾지 못하면 명확한 오류를 반환한다. -- FR-3a: `name`은 생성 후 변경 가능하다. 단, 변경 시 동일 `owner_user_id` 내에 같은 `name`이 이미 존재하면 DB의 `(owner_user_id, name)` UNIQUE 제약으로 변경이 거부되고, 서비스 레이어는 사용자에게 명확한 오류 메시지(`이미 사용 중인 이름입니다: {name}`)를 반환한다. -- FR-4: HTTP API의 portfolio 관련 path parameter를 UUID 기반으로 전환한다. `GET /v1/assets` 응답의 `asset_id` 필드는 UUID를 반환한다. dcy-002 A-3을 이번 Discovery에서 철회한다. -- FR-5: 기존 데이터 마이그레이션 — 현재 `portfolio_id` 문자열 값을 `name` 컬럼으로 복사하고, 각 레코드에 새 UUID를 `portfolio_id`로 할당한다. `portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades` 등 FK 참조 테이블의 portfolio_id도 새 UUID로 함께 갱신한다. -- FR-6: `docs/project-structure.md`와 `docs/runtime-flows.md`를 이번 변경 내용에 맞게 갱신한다(repositories/services 인터페이스 변경, CLI 흐름, v1 API 흐름 변경 반영). - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 데이터 마이그레이션은 단일 Alembic revision으로 수행하며, IF NOT EXISTS 패턴을 준수해 재실행 시 오류가 없어야 한다(REQ-002 준수). FK 참조 테이블과의 갱신은 트랜잭션 내에서 atomic하게 처리한다. -- NFR-2: CLI `(owner_user_id, name)` → UUID 조회는 단일 DB 쿼리로 처리하며, 조회 실패 시 명확한 오류 메시지(`포트폴리오를 찾을 수 없습니다: {name} (user: {username})`)를 출력해야 한다. -- NFR-3: trex-basic 계약 변경(asset_id UUID 전환)은 portfolio-hub와 trex-basic을 동시에 배포하며 처리한다. 변경 일정 및 동시 배포 계획을 docs/interface-contract.md에 기록해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - `portfolios.portfolio_id` UUID 교체 및 `name` 컬럼 추가 - - `(owner_user_id, name)` 복합 UNIQUE 제약 - - 기존 데이터 마이그레이션 (FK 참조 포함) - - CLI 뮤테이션 명령 식별 방식 전환 (`--user + name`) - - HTTP API path parameter UUID 전환 - - `GET /v1/assets` asset_id UUID 반환 - - `docs/project-structure.md`, `docs/runtime-flows.md` 갱신 - - `docs/interface-contract.md`, `docs/data-model.md` 갱신 -- Out of Scope - - trex-basic 측 코드 변경 — portfolio-hub API 변경만 담당 - - `name` 변경 이력 감사 로그 — 향후 반복으로 Deferred - - `name` 허용 문자 유효성 검사 정책 — 확정됨. `[a-zA-Z0-9._:-]` 허용, 공백 불허, 1~100자, 대소문자 구분 (2026-05-11) - - HTTP API에서 `(username, name)` 조합 조회 엔드포인트 추가 — 추가하지 않음. HTTP API는 `portfolio_id`(UUID)만으로 조회 (OQ-3 Answered 2026-05-11) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 기존 FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)의 portfolio_id를 새 UUID로 일괄 갱신하는 마이그레이션 중 오류 발생 시 데이터 불일치가 생길 수 있다. - - 완화 방안: 마이그레이션 전 DB 전체 백업 수행. 마이그레이션 스크립트를 단일 트랜잭션으로 작성하고, 검증 쿼리(FK 고아 레코드 0건 확인)를 마이그레이션 스크립트 마지막에 실행한다. -- R-2 (High): `GET /v1/assets` `asset_id`가 UUID로 바뀌면 trex-basic 데몬의 `POST /v1/assets/{asset_id}/ledger/trades` 경로가 즉시 동작하지 않는다. 배포 타이밍 불일치 시 운영 장애가 발생한다. - - 완화 방안: trex-basic과 portfolio-hub를 동시에 배포한다(OQ-1 Answered). 임시 호환 레이어 불필요. 두 저장소의 배포 파이프라인에서 릴리즈 타이밍을 조율한다. -- R-3 (Medium): `name`이 mutable이므로 CLI 스크립트나 외부 참조에서 portfolio를 name으로 식별하던 코드가 name 변경 후 깨질 수 있다. - - 완화 방안: name 변경 CLI 명령에 명시적 확인 절차(--force 또는 confirm 프롬프트)를 요구한다. DB의 `(owner_user_id, name)` UNIQUE 제약이 같은 사용자 내 중복 name 변경을 자동으로 차단한다(OQ-2 Answered). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: `portfolio_id`(UUID)는 서버에서 `uuid.uuid4()`로 생성하며, 사용자가 직접 지정하거나 예측할 수 없다. -- A-2: `name`은 `(owner_user_id, name)` 복합 UNIQUE 제약을 만족하면 되며, 전역 유일성은 요구하지 않는다. -- A-3: 기존 `portfolio_id` 문자열(`005930:tv` 형식)은 데이터 마이그레이션 시 `name` 컬럼 값으로 그대로 이전한다. 변환 없이 복사한다. -- A-4: HTTP API 경로에서 `(username, name)` 조합 조회 엔드포인트는 추가하지 않는다. HTTP API는 `portfolio_id`(UUID)만으로 portfolio를 식별한다. CLI만 `(owner_user_id, name)` → UUID 조회 경로를 사용한다(OQ-3 Answered 2026-05-11). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: trex-basic 계약 호환성 전략 — `GET /v1/assets` asset_id가 UUID로 바뀔 때 trex-basic 측의 대응 준비 일정이 확정되기 전에 portfolio-hub를 먼저 배포할 수 있는지, 아니면 임시 호환 레이어(name으로 fallback 조회)가 필요한지. - - 상태: Answered - - 처리 방안: trex-basic과 portfolio-hub를 동시에 변경·배포한다. 임시 호환 레이어 불필요. - - 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리. -- OQ-2: `name` mutable 정책 — 생성 이후 name 변경을 허용할지 여부. 허용할 경우 기존 CLI 스크립트 참조 안정성 확보 방안. - - 상태: Answered - - 처리 방안: name 변경 허용. 단, 동일 사용자 내 같은 name이 존재하면 DB의 `(owner_user_id, name)` UNIQUE 제약으로 변경 거부. FR-3a에 반영 완료. - - 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리. -- OQ-3: HTTP API에서 `(username, name)` 조합으로 portfolio를 조회하는 엔드포인트 추가 여부 — 외부 API 소비자가 UUID를 직접 관리해야 하는 부담을 줄이기 위해 `/portfolios?user=alice&name=tv` 형태의 조회 경로가 필요한지. - - 상태: Answered - - 처리 방안: 추가하지 않음. HTTP API는 `portfolio_id`(UUID)만으로 조회. A-4, Out of Scope 반영 완료. - - 종료 조건: 완료 — 2026-05-11 사용자 확인으로 Answered 처리. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: `pfhub portfolio create tv --symbol 005930 --user alice --force` 실행 시 UUID가 portfolio_id로 발급되고, alice와 bob이 동일 name `tv`로 각각 portfolio를 생성할 수 있다. -- S-2: `pfhub portfolio buy tv --user alice --quantity 10 --price 71000 --force`가 alice 소유의 `tv` portfolio에만 적용되고, `--user bob`으로는 alice의 portfolio를 조작할 수 없다. -- S-3: 마이그레이션 후 기존 데이터의 `portfolio_ledger`, `portfolio_comments` 등 FK 참조 테이블에 고아 레코드(orphan)가 0건이고, `GET /v1/assets` 응답의 `asset_id`가 UUID 형식이다. -- S-4: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`, `docs/interface-contract.md`가 변경 내용을 반영해 갱신된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: pytest — portfolio create 후 portfolio_id가 UUID 형식(`re.match(r'^[0-9a-f-]{36}$', ...)`)임을 검증. alice/bob 동일 name 독립 생성 테스트 / 데이터 출처: `tests/test_cli.py` 또는 신규 `tests/test_portfolio_uuid.py` -- S-2 측정 방식: pytest — `--user bob`으로 alice 소유 portfolio 조작 시 오류 반환 검증 / 데이터 출처: `tests/test_cli.py` 소유권 검증 케이스 -- S-3 측정 방식: 마이그레이션 스크립트 내 검증 쿼리 실행 결과 0건 확인 + pytest `test_v1.py::test_v1_assets_*` asset_id UUID 형식 검증 / 데이터 출처: `migrations/versions/` 마이그레이션 스크립트, `tests/test_v1.py` -- S-4 측정 방식: 각 baseline 문서에 CLI 흐름, repositories/services 인터페이스, v1 API asset_id UUID 내용이 포함됐는지 코드 리뷰로 확인 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`, `docs/interface-contract.md` - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-3(HTTP API name 조회 엔드포인트) 결정 완료, Out of Scope 경계 승인 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 측정 방식이 pytest 또는 코드 리뷰로 추적 가능한 형태로 기술됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(마이그레이션 데이터 무결성), R-2(trex-basic 계약 파괴) 완화 방안 확정 및 담당자 지정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin 또는 지정된 승인자가 confirm-discovery skill로 Freeze 확정 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md -- 참조 문서: - - docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md (A-3 철회 대상) - - docs/discovery/dcy-003_20260502_portfolio-list-user-required.md (--user 필수화 선행 작업) - - app/domain/models.py - - app/cli/commands/portfolio.py - - app/repositories/postgres.py - - migrations/versions/20260403_0001_initial_portfolio_schema.py - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~{{DECIDE_NAME_MUTABILITY}}~~: **Answered** — name mutable 허용. 동일 사용자 내 중복 name 변경은 DB UNIQUE 제약으로 거부. FR-3a 반영 완료. (OQ-2 2026-05-11) -- ~~{{DECIDE_TREX_COMPAT_STRATEGY}}~~: **Answered** — trex-basic과 portfolio-hub 동시 배포로 확정. 임시 호환 레이어 불필요. (OQ-1 2026-05-11) - -## CONFIRM -- ~~{{CONFIRM_API_NAME_LOOKUP_SCOPE}}~~: **Answered** — `(username, name)` 조합 엔드포인트 추가 안 함. HTTP API는 `portfolio_id`(UUID)만으로 조회. (OQ-3 2026-05-11) -- ~~{{CONFIRM_MIGRATION_STRATEGY}}~~: **Answered** — 기존 `portfolio_id` 문자열을 `name` 컬럼으로 변환 없이 그대로 복사. (A-3 2026-05-11) - -## DATA -- ~~{{DATA_NAME_ALLOWED_CHARS}}~~: **Answered** — `name` 허용 문자 `[a-zA-Z0-9._:-]`(영숫자 + `.` `_` `:` `-`), 공백 불허, 1~100자, 대소문자 구분 있음. (2026-05-11) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 모든 OQ Answered 완료, 모든 CONFIRM/DATA 항목 확정. confirm-discovery 게이트 통과 — REQ 초안 작성으로 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-05-11 diff --git a/docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md b/docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md deleted file mode 100644 index f9e904d..0000000 --- a/docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md +++ /dev/null @@ -1,236 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-005_20260511_production-admin-bootstrap-and-operator-access -- 이슈명: 실배포 admin bootstrap 및 operator access 경로 정립 -- 작성 시각(KST): 2026-05-11 23:12 -- 마지막 갱신 시각(KST): 2026-05-12 14:50 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-020, REQ-021, REQ-022 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 TODO/메모: docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md - - 관련 운영 문서: README.md "Production Operator Runbook" -- 해석: - - REL-001은 released 상태로 전환됐지만, admin 성공 경로와 trade-write 성공 경로는 후속 운영 smoke로 닫아야 한다. 본 문서는 production smoke를 canonical acceptance로, staging smoke를 optional preflight로 두는 기준과 evidence 기록 위치를 고정한다. - - 이번 Discovery는 release blocker 해소보다는 released 이후 운영 hardening 항목으로서, 실배포 admin bootstrap, operator auth path, smoke credential provenance, 로그 접근 절차, 운영 책임 역할을 REQ handoff 가능한 수준으로 정리하기 위한 문서다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy-001~004는 DB 검증, trex-basic consumer API, CLI `portfolio list --user`, portfolio UUID 전환을 다루며, 실배포 admin bootstrap 및 operator access를 독립 주제로 다루는 Discovery는 없다. - - 이번 요청은 제품 기능 추가보다 운영 인증 경로, bootstrap 계정/토큰 provenance, release smoke credential 관리라는 별도 문제 정의를 가진다. - -> **판정이 `완전 중복`인 경우**: 이하 섹션 작성을 중단하고 `# 9. 파일 처리 결과`에 중복 판정 사유와 참조 문서 경로를 기록한 뒤 문서를 닫는다. REQ 초안 작성으로 넘기지 않는다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 실배포 URL `https://fourblocks2026.com/portfolio-hub/`에서 `GET /health`, `/docs`, `/openapi.json`은 정상 응답했고, read service-token으로 `GET /v1/assets`, `GET /v1/user_info`까지는 검증됐다. - - 동일 토큰으로 `POST /auth/service-tokens`를 호출하면 `403 Insufficient permission`이 반환됐다. 이후 README에 Production Operator Runbook 초안이 추가되어 canonical admin path, smoke credential ownership, log path 기준선은 정리됐고, 후속 smoke는 production을 canonical acceptance 환경으로 수행하며 staging은 optional preflight로만 사용하기로 정리했다. 실행 evidence는 해당 release 문서에 남기고, 절차의 source of truth는 README runbook으로 유지한다. - - `app/core/auth.py`는 env token 집합 또는 DB `auth_tokens` principal의 `permission`으로 권한을 해석하고, `app/api/auth_routes.py`의 `POST /auth/service-tokens`는 `require_admin`을 선행한다. 즉 운영자가 어떤 credential을 admin으로 다뤄야 하는지가 명확해야 smoke가 가능하다. - - 현재 저장소 기준선상 프로덕션은 Linux/Ubuntu host에서 systemd로 `portfoliohub` uvicorn 서비스를 띄우고 Caddy를 reverse proxy로 사용하며, secret material은 host-side `EnvironmentFile=/opt/portfoliohub/.env`에서 주입된다. secret leakage 대응 owner는 repository-maintainers 역할 중 deployment secret custodian으로 두고, admin recovery owner는 production operator/release approver 역할로 둔다. -- 기대 상태 - - 운영자는 실배포에서 admin auth context를 얻는 표준 경로를 알고 있어야 하며, 그 경로로 `POST /auth/service-tokens` 성공 smoke를 반복 가능하게 수행할 수 있어야 한다. - - release 검증에는 public/no-auth, read service-token, trade-write service-token, admin auth context의 용도와 provenance가 문서로 분리돼 있어야 한다. - - startup log, request log, auth failure(401), permission failure(403), resource failure(404)를 운영자가 구분해 증거로 남길 수 있어야 한다. -- 이해관계자 - - repository-maintainers - - 실배포 운영자 / 배포 승인자 - - trex-basic 연동 운영자 - - 보안 또는 secret 관리 담당자 -- 제약사항 - - 실제 credential 원문은 저장소, release 문서, Discovery 문서에 기록하지 않는다. - - 기존 auth model(`read`, `trade-write`, `admin` 권한과 DB-issued service-token 구조)은 유지하되, 필요한 경우 bootstrap/runbook 또는 최소한의 운영 지원 기능만 추가한다. - - REL-001이 released 전환된 이후의 후속 운영 hardening 범위를 다루므로, 이 Discovery는 운영 접근성과 검증 절차를 정리하는 데 집중하고 기존 consumer contract 범위를 임의로 확장하지 않는다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 REL-001 배포 후 smoke를 수행할 때, admin 경로로 service token을 발급하고 read/trade-write 토큰을 분리해 `/auth/service-tokens`, `/v1/assets`, `/v1/user_info`, `/v1/assets/{asset_id}/ledger/trades`를 순서대로 검증한다. -- 장애 또는 신규 환경 bootstrap 시 운영자가 `PORTFOLIOHUB_BOOTSTRAP_*` 기반 초기 admin 계정 생성 경로와 steady-state admin recovery 경로를 구분해 사용한다. -- release evidence 작성 시 운영자가 raw secret 없이 token provenance, 권한 수준, 로그 접근 경로만 기록해 보안 사고 없이 검증 내역을 남긴다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 실배포 admin bootstrap 및 steady-state operator access 경로를 정의한다. steady-state canonical path는 운영자 소유 admin 사용자 계정으로 로그인해 확보한 DB-issued admin session 또는 동일 컨텍스트에서 발급한 client token으로 두고, 해당 admin context로 release smoke용 read/trade-write service-token을 발급한다. `PORTFOLIOHUB_BOOTSTRAP_*`는 새 프로덕션 환경에서 `users` 테이블이 비어 있거나 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 허용하고, steady-state recovery는 운영자 개인 admin 로그인 경로를 우선 사용한다. env admin token은 로그인 경로가 모두 막혔을 때 `/opt/portfoliohub/.env`에서 꺼내는 break-glass fallback으로만 사용한다. -- release 검증용 credential matrix를 정의한다. public/no-auth, read service-token, trade-write service-token, admin auth context의 용도와 발급/보관/폐기 절차를 분리한다. admin 로그인 자격증명은 운영자 개인별 소유로 두고 공유하지 않으며, read/trade-write smoke service-token은 환경 단위 운영 credential로 취급해 release approver 또는 repository-maintainers 역할이 공동 소유한다. 현재 프로덕션의 canonical secret material location은 host-side `EnvironmentFile=/opt/portfoliohub/.env`이며, raw secret은 이 경로와 동일 접근 통제 하의 운영 절차에서만 보관한다. release 문서에는 이름, token_prefix, permission, runtime_id, 발급/폐기 시각만 기록한다. -- startup/request 로그 접근, auth/permission/resource 오류 판독, release evidence 기록 절차를 운영 runbook과 release 문서에 연결한다. canonical acceptance smoke는 production에서 수행하고, staging smoke는 optional preflight로만 사용한다. 절차의 source of truth는 README runbook으로 유지하고, 실행 evidence의 canonical record는 해당 release 문서에 남긴다. 현재 canonical log access path는 Linux/Ubuntu production host의 systemd journal에서 `portfoliohub` 서비스 로그와 Caddy reverse proxy 로그를 확인하는 경로다. 즉 `journalctl -u portfoliohub`와 `journalctl -u caddy`가 1차 운영 경로이며, Loki는 `LOKI_ENABLED`와 실제 수집 경로가 운영 환경에 명시적으로 구성된 경우에만 보조 집계 경로로 취급한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `app/core/config.py` — bootstrap 환경 변수와 운영 환경 가정 재검토 - - `app/main.py` — bootstrap 초기 사용자 생성 경로의 운영 제약 재정리 필요 여부 검토 - - `app/core/auth.py`, `app/api/auth_routes.py`, `app/services/auth_service.py` — admin/read/trade-write auth path의 운영 의미 문서화 또는 최소 지원 기능 추가 가능성 - - `app/cli/commands/*` 또는 운영 보조 스크립트 — 필요 시 token inspection 또는 smoke helper 추가 검토 -- 문서 영향: - - `README.md` — 실배포 bootstrap/admin/operator access runbook 추가 가능성 - - `docs/project-structure.md`, `docs/runtime-flows.md` — auth/bootstrap/runtime smoke 흐름 반영 가능성 - - `docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md` — release evidence와 운영 주의사항 정합화 -- 운영 영향: - - secret manager 또는 배포 환경 변수 관리 절차 명시 필요 - - release smoke에 필요한 token 수명 주기 및 폐기 절차 정의 필요 - - 로그 접근 권한과 증적 수집 절차를 운영자 역할에 연결해야 함 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 실배포에서 admin auth context를 확보하는 표준 경로를 정의한다. steady-state canonical path는 운영자 소유 admin 사용자 계정의 로그인으로 확보한 DB-issued admin session 또는 동일 컨텍스트에서 발급한 client token으로 하고, 이 경로로 `POST /auth/service-tokens`를 호출해 read/trade-write service-token을 발급하도록 명시한다. `PORTFOLIOHUB_BOOTSTRAP_*` 기반 bootstrap은 `users` 테이블이 비어 있는 신규 환경 또는 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 허용한다. steady-state recovery는 기존 운영자 admin 로그인 경로를 우선 사용하고, env admin token은 `/opt/portfoliohub/.env`에서만 꺼내는 break-glass fallback으로 제한한다. -- FR-2: release 검증에 필요한 credential matrix를 정의한다. public/no-auth, read service-token, trade-write service-token, admin auth context 각각의 발급 주체, 보관 위치, 만료 정책, 폐기 절차를 문서화한다. admin 로그인 자격증명은 named operator가 개인별로 소유하고 공유하지 않으며, read/trade-write smoke service-token은 환경 단위 운영 credential로서 release approver 또는 repository-maintainers 역할이 공동 소유한다. 현재 프로덕션 secret material location은 `portfoliohub` systemd service의 `EnvironmentFile=/opt/portfoliohub/.env`로 정의하고, release evidence에는 name, token_prefix, permission, runtime_id, issued_at, revoked_at만 남긴다. -- FR-3: REL-001과 같은 app-service release에서 startup log, request log, auth/permission/resource 오류를 수집하는 운영 runbook을 정의한다. canonical acceptance smoke는 production에서 수행하고, staging smoke는 optional preflight로만 사용한다. 절차의 source of truth는 README runbook으로 유지하고, 실행 evidence의 canonical record는 해당 release 문서에 남긴다. 기본 수집 경로는 Linux/Ubuntu production host의 systemd journal에서 `portfoliohub` uvicorn 서비스와 Caddy reverse proxy 로그를 조회하는 방식으로 하고, 구체 명령은 `journalctl -u portfoliohub`와 `journalctl -u caddy`를 사용한다. Loki는 운영 환경에서 실제 활성화가 확인된 경우에만 보조 조회 경로로 포함한다. 외부 URL만으로 로그 접근이 불가능한 점, 운영자가 어떤 콘솔 또는 CLI 권한으로 해당 로그를 조회하는지, release evidence에 남길 최소 필드(시각, 경로, 상태 코드, 소스 시스템)를 함께 문서화한다. -- FR-4: 운영자가 DB 콘솔에 직접 들어가지 않고도 token provenance 또는 권한 수준을 확인할 수 있는 최소 지원 수단을 정의한다. 신규 관리 기능을 추가하기보다 기존 `pfhub service-token list`, `pfhub service-token issue`, `pfhub service-token rotate`, `pfhub user get`, `GET /v1/user_info`를 canonical support path로 문서화하고, 이름, token_prefix, token_id, user_id, permission, created_at, last_used_at, revoked_at, runtime_id를 provenance 확인 필드로 사용한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: raw credential, raw token, bootstrap password는 저장소와 release/discovery 문서에 남기지 않는다. 운영 문서에는 provenance와 마스킹된 메타데이터만 남긴다. -- NFR-2: 운영 runbook은 401, 403, 404의 의미를 분리해 재현 가능하게 설명해야 하며, 권한 문제와 리소스 부재 문제를 혼동하지 않도록 해야 한다. -- NFR-3: bootstrap 또는 recovery 경로는 idempotent해야 하며, 이미 사용자가 존재하는 환경에서 first-run bootstrap 로직이 반복 부작용을 만들지 않아야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 실배포 admin bootstrap/steady-state access 경로 정의 - - read/trade-write/admin smoke credential 구분 및 운영 절차 정의 - - release evidence용 로그 접근 및 증적 수집 절차 정의 - - 필요한 최소 운영 지원 기능 또는 문서 변경 판정 -- Out of Scope - - 권한 체계(`read`, `trade-write`, `admin`) 자체의 전면 재설계 - - 외부 사용자를 위한 신규 admin UI 또는 공개 운영 포털 추가 - - trex-basic consumer API 스키마 변경 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): admin/bootstrap credential을 ad-hoc 방식으로 공유하면 secret leakage가 발생할 수 있다. - - 완화 방안: secret manager 또는 승인된 보관소만 사용하고, 저장소/문서에는 raw value를 남기지 않는다. 대응 owner는 repository-maintainers 역할 중 deployment secret custodian으로 둔다. -- R-2 (Medium): first-run bootstrap 계정 경로만 정리하고 steady-state 운영 경로를 정의하지 않으면, 이미 사용자가 있는 실배포 환경에서는 같은 문제를 반복한다. - - 완화 방안: bootstrap과 steady-state access/recovery를 별도 요구사항으로 분리해 정의한다. admin recovery owner는 production operator/release approver 역할로 둔다. -- R-3 (Medium): 로그 접근 경로가 환경 의존적이면 release 증적 수집이 담당자 개인 지식에 묶인다. - - 완화 방안: 최소한의 환경 계약(예: 어느 시스템에서 startup/request 로그를 확인하는지)을 문서화한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 실배포 환경은 DB-backed `users` / `auth_tokens`를 사용하며, service-token은 DB principal 권한으로 해석된다. -- A-2: admin 성공 경로는 현재 완전히 없는 것이 아니라, 운영자에게 문서화되지 않았거나 credential provenance가 정리되지 않은 상태다. -- A-3: release 검증에는 최소한 public/no-auth, read service-token, trade-write service-token, admin auth context 4개 관점이 필요하다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: steady-state admin access의 표준 경로는 무엇인가? bootstrap admin 로그인, env admin token, DB-issued admin session/token, 별도 운영 절차 중 무엇을 canonical path로 삼을지. - - 상태: Answered - - 답변: steady-state canonical path는 운영자 소유 admin 사용자 계정으로 `/auth/login` 하여 확보한 DB-issued admin session 또는 동일 컨텍스트에서 발급한 client token이다. 운영자는 이 admin context로 `POST /auth/service-tokens`를 호출해 release smoke용 read/trade-write service-token을 발급한다. `PORTFOLIOHUB_BOOTSTRAP_*`는 `users` 테이블이 비어 있는 신규 환경 또는 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 사용하고, env admin token은 `/opt/portfoliohub/.env`에서만 꺼내는 break-glass fallback으로만 사용한다. - - 처리 방안: FR-1과 운영 runbook에 canonical path, bootstrap path, break-glass fallback의 구분을 반영한다. 또한 프로덕션에 운영자용 admin 사용자 계정이 실제로 존재해야 하며, 해당 로그인 자격증명과 break-glass env token은 `portfoliohub` systemd service의 `EnvironmentFile=/opt/portfoliohub/.env` 관리 경로 안에서 통제된다는 운영 전제를 후속 문서에 명시한다. - - 종료 조건: README 또는 후속 운영 문서에 canonical path와 fallback 절차가 반영되고, REL-001 후속 release smoke가 해당 경로로 재현되면 resolved 기록으로 이동한다. -- OQ-2: startup/request 로그는 어디에서 확인하는가? 호스팅 환경, reverse proxy, Loki 등 실제 로그 경로와 접근 권한을 어떻게 운영자에게 부여하는가. - - 상태: Answered - - 답변: 현재 canonical log access path는 Linux/Ubuntu production host의 systemd journal이다. 애플리케이션 startup 및 runtime 로그는 `journalctl -u portfoliohub`, reverse proxy 또는 access 수준 로그는 `journalctl -u caddy`로 확인한다. Loki는 설정 항목은 존재하지만 현재 저장소에는 프로덕션 수집 경로와 명시적 runtime flow가 없으므로 기본 표준 경로로 간주하지 않고, 운영 환경에서 `LOKI_ENABLED`와 실제 수집 파이프라인이 확인된 경우에만 보조 집계 경로로 취급한다. - - 처리 방안: FR-3과 운영 runbook에 로그 조회 채널을 systemd journal 기준으로 명시하고, startup log와 reverse proxy log를 각각 어떤 `journalctl` 명령으로 확인하는지 구분한다. 또한 release evidence에는 raw secret 없이 시각, 경로, 상태 코드, 소스 시스템, 상관 가능한 요청 식별자만 남긴다. - - 종료 조건: README 또는 후속 운영 문서에 `journalctl -u portfoliohub`, `journalctl -u caddy` 기반 로그 조회 절차와 evidence 필드가 반영되고, REL-001 후속 release smoke에서 startup/request 로그 확인 근거가 같은 채널로 재현되면 resolved 기록으로 이동한다. -- OQ-3: token provenance 확인 수단은 문서화만으로 충분한가, 아니면 최소한의 CLI/관리 기능이 필요한가. - - 상태: Answered - - 답변: runbook-only 접근만으로는 부족하지만, 신규 관리 기능을 추가할 필요는 없다. 현재 저장소의 기존 CLI와 API가 이미 최소 provenance 확인 수단을 제공하므로, 이를 canonical support path로 문서화하는 것으로 충분하다. 운영자는 `pfhub service-token list`로 이름, token_prefix, token_id, user_id, permission, created_at, last_used_at, revoked_at를 확인하고, 발급/rotate 시 반환되는 `runtime_id`와 `GET /v1/user_info`의 `runtime_id`를 대조해 실배포 smoke에 사용한 service-token provenance를 확인한다. `pfhub user get`은 사용자별 service token 이름 목록 확인 보조 수단으로 사용한다. - - 처리 방안: FR-4와 운영 runbook에 기존 지원 수단의 사용 순서를 반영하고, README 또는 후속 운영 문서에서 `service-token list`와 `service-token rotate`를 명시한다. 추가 구현은 보류하고, 실제 smoke rehearsal에서 기존 수단만으로 provenance 판독이 부족할 때만 후속 REQ로 승격한다. - - 종료 조건: README 또는 후속 운영 문서에 provenance 확인 절차와 필드가 반영되고, REL-001 후속 release smoke에서 기존 CLI/API만으로 사용 토큰의 permission 및 provenance를 재현 가능하게 확인하면 resolved 기록으로 이동한다. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 운영자가 admin/read/trade-write/public 검증 경로를 혼동 없이 수행할 수 있는 runbook을 보유한다. -- S-2: release 검증 시 `POST /auth/service-tokens` 성공 경로와 `POST /v1/assets/{asset_id}/ledger/trades` 권한 경로를 적절한 credential로 재현할 수 있다. -- S-3: startup/request 로그 접근 경로와 release evidence 기록 방식이 REL 문서에 반영된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: README runbook 리뷰 후 production smoke 기준 절차를 rehearsal하고 필요 시 staging에서 optional preflight 수행 / 데이터 출처: README.md, 해당 release 문서의 verification 기록 -- S-2 측정 방식: canonical acceptance로 production smoke에서 admin/read/trade-write 경로를 각각 실행하고, staging smoke는 사전 rehearsal로만 사용 / 데이터 출처: 해당 release 문서, HTTP 응답 로그, 운영자가 제공한 검증 기록 -- S-3 측정 방식: release 문서에 실행 evidence가 기록되고 baseline 문서에 로그 접근 및 증적 수집 항목이 반영됐는지 확인 / 데이터 출처: docs/releases/*.md, docs/runtime-flows.md, docs/project-structure.md, README.md - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: admin bootstrap, steady-state access, logging scope의 포함/제외 범위 합의 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: canonical acceptance smoke는 production, staging smoke는 optional preflight, 실행 evidence는 해당 release 문서에 기록 -- [x] 핵심 리스크 우선순위 및 해소 책임자 지정 - - 기준: secret leakage owner는 repository-maintainers 역할의 deployment secret custodian, admin recovery owner는 production operator/release approver 역할 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록의 `Open` 상태가 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영 승인자 또는 repository-maintainers 중 확인자 지정 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 후 갱신 -- 생성 경로: docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md -- 참조 문서: - - docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md - - app/core/auth.py - - app/api/auth_routes.py - - app/services/auth_service.py - - README.md - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 - -## CONFIRM -- 없음 (Confirmed By: Justin — 2026-05-12) - -## DATA -- baseline 반영 완료 - - canonical smoke environment: production - - staging smoke role: optional preflight only - - canonical evidence location: 해당 release 문서 - - procedure source of truth: README.md "Production Operator Runbook" - - secret store location: production host의 `portfoliohub` systemd service가 참조하는 `EnvironmentFile=/opt/portfoliohub/.env` - - production log stack: Linux/Ubuntu host + systemd-managed `portfoliohub` uvicorn service + Caddy reverse proxy, primary log commands `journalctl -u portfoliohub` 및 `journalctl -u caddy` - - secret leakage owner: repository-maintainers 역할 중 deployment secret custodian - - admin recovery owner: production operator/release approver 역할 - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 핵심 운영 경로(OQ-1~3), smoke credential ownership policy, bootstrap 대 steady-state recovery 범위, canonical smoke/evidence 기준, secret store/log stack 기준선, 운영 책임 역할 모두 반영 완료. Freeze confirmer Justin 지정 완료. 오픈 질문 0개, C 유형 플레이스홀더 0개. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-05-12 14:35 \ No newline at end of file diff --git a/docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md b/docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md deleted file mode 100644 index 75cd240..0000000 --- a/docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md +++ /dev/null @@ -1,238 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment -- 이슈명: daemon_id 도입 및 1자산 1데몬 자산 배정 모델 정립 -- 작성 시각(KST): 2026-05-13 17:08 -- 마지막 갱신 시각(KST): 2026-05-13 20:30 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-023, REQ-028 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 없음 -- 해석: - - dcy-002 계열의 v1 API와 service-token 모델은 `owner_user_id`와 `runtime_id`까지만 표현하고 있어, 동일 user 아래 여러 daemon이 자산을 나눠 맡는 운영 모델을 서버가 이해하지 못한다. - - 이번 Discovery는 `runtime_id`를 런타임 식별자로 유지하면서 stable `daemon_id`와 exact-one asset assignment를 도입할지, 그리고 그 변화가 `/v1/assets`, `/v1/user_info`, `POST /v1/assets/{asset_id}/ledger/trades`, rotate, 운영 provenance에 미치는 범위를 정의한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy-001~005 중 stable daemon identity와 `1개 자산 = 정확히 1개 데몬` 서버 강제 모델을 다루는 Discovery는 없다. - - 이번 변경은 이미 Implemented인 REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-012의 Requirement와 Acceptance Criteria를 다시 써야 하는 구조 변경이어서 기존 Discovery의 미세 수정으로 처리할 수 없다. - -> **판정이 `완전 중복`인 경우**: 이하 섹션 작성을 중단하고 `# 9. 파일 처리 결과`에 중복 판정 사유와 참조 문서 경로를 기록한 뒤 문서를 닫는다. REQ 초안 작성으로 넘기지 않는다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `GET /v1/assets`는 현재 service-token 소유 user의 portfolio만 반환하며, daemon assignment 차원을 전혀 갖지 않는다. - - `runtime_id`는 service-token 발급 시 생성되어 `/v1/user_info`로 반환되지만 rotate 시 다시 바뀌는 런타임 식별자라 stable asset assignment key로 쓰기 어렵고, `/v1/user_info`에는 아직 `daemon_id`가 포함되지 않는다. - - 현재 스키마와 도메인 모델에는 `daemons` 테이블, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`가 없다. - - 태그와 owner scope는 존재하지만 `1개 자산은 정확히 1개 데몬만 담당`을 서버에서 강제하는 모델이나 API 계약은 없다. - - `POST /v1/assets/{asset_id}/ledger/trades`는 유효한 service-token 인증과 asset 존재성 기준으로 동작하며, "해당 asset이 이 daemon에 배정됐는가"는 검증하지 않는다. -- 기대 상태 - - service-token은 `runtime_id`와 별도로 stable `daemon_id`에 바인딩된다. - - daemon create/update는 별도 daemon registry 관리 경로에서 수행되고, service-token issue/rotate는 existing daemon reference만 받아 binding한다. - - 각 asset은 동일 owner scope 안에서 정확히 1개의 daemon에만 배정되고, `/v1/assets`와 ledger write는 이 배정 규칙을 서버에서 강제한다. - - `GET /v1/user_info`는 현재 인증 service-token에 기록된 `daemon_id`와 runtime 세대 식별자인 `runtime_id`를 함께 반환하며, 전환 구간의 NULL backfill token은 `daemon_id = null`을 진단 정보로 노출한다. - - rotate나 runtime 재기동 이후에도 `daemon_id`는 유지되고 `runtime_id`만 갱신되어 운영 provenance와 runtime identity가 분리된다. -- 이해관계자 - - trex-basic 데몬 운영자 - - portfolio-hub repository-maintainers - - release approver / production operator - - user ownership 및 asset assignment policy 승인자 -- 제약사항 - - service-token bearer auth와 기존 `read`/`trade-write`/`admin` 권한 모델은 유지한다. - - `1개 자산 = 정확히 1개 데몬` 규칙을 many-to-many 없이 모델링한다. - - 신규 migration과 schema bootstrap은 기존 Alembic chain과 `IF NOT EXISTS` 정책을 깨지 않아야 한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 동일 user가 `daemon-a`, `daemon-b` 두 개를 운영할 때, `daemon-a`용 service-token으로 `/v1/assets`를 호출하면 `daemon-a`에 배정된 asset만 반환되고 `daemon-b`의 asset은 반환되지 않는다. -- `daemon-b`가 `daemon-a`에 배정된 asset_id로 `POST /v1/assets/{asset_id}/ledger/trades`를 호출하면 `403 Forbidden`으로 거부되고, 잘못된 ledger row는 생성되지 않는다. -- 운영자가 `daemon-a`용 service-token을 rotate할 때 `runtime_id`는 새 값으로 갱신되지만 `daemon_id`와 asset assignment는 유지되어, daemon 재배정 없이 provenance만 이어서 확인할 수 있다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- stable daemon identity 모델 추가: `daemons` registry, 별도 daemon create/update 관리 경로, service-token `daemon_id` binding, `runtime_id`와 `daemon_id`의 역할 분리 -- exact-one asset assignment 모델 추가: `portfolios`에 daemon assignment를 저장하고 daemon-scoped `/v1/assets` 조회와 ledger authorization로 `1자산 1데몬` 규칙을 강제 -- service-token issuance/rotate/provenance와 baseline 문서 갱신: daemon binding, 기존 asset/token의 NULL backfill, `/v1/user_info`와 CLI의 `daemon_id` 노출, `403` mismatch contract를 운영 절차와 함께 재정의 - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `migrations/versions/`: `daemons` 테이블, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`, 관련 FK/check/backfill migration 추가 - - `app/domain/models.py`, `app/repositories/postgres.py`, `app/repositories/portfolio_repository.py`: daemon model, token binding, asset assignment, rotate invariants 반영 - - `app/services/auth_service.py`, `app/api/auth_routes.py`, `app/cli/commands/service_tokens.py`: service-token issue/list/rotate에 daemon binding 및 provenance 반영 - - `app/api/v1_assets.py`, `app/api/v1_user_info.py`, `app/api/v1_ledger.py`: daemon-scoped read/write와 diagnostics contract 반영 -- 문서 영향: - - `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md`: 후속 Discovery 참조 및 runtime_id 역할 재정렬 필요 - - `docs/srs/Interface/req-005_service-token-runtime-id-issuance.md`, `req-006_v1-assets-endpoint.md`, `req-007_v1-user-info-endpoint.md`, `req-008_v1-ledger-trades-endpoint.md`, `req-012_service-token-rotate-cli.md`: change-req 대상 - - `docs/srs/Migration/req-009_v1-api-db-schema-migration.md`, `docs/srs/Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md`: change-req 대상 - - `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`, `README.md` 운영 provenance 문서: daemon assignment 반영 필요 -- 운영 영향: - - daemon create/update는 별도 관리 경로에서 먼저 수행하고, service-token issue/rotate는 existing daemon 선택 또는 `daemon_id` 입력으로만 binding한다. - - 기존 asset과 token은 초기 migration에서 daemon 필드를 `NULL`로 backfill하며, production operator가 명시적 reassignment inventory와 전환 절차를 준비하고 release approver 승인 이후에만 daemon-scoped enforcement를 켠다. - - rotate 후 `runtime_id` 재조회와 `daemon_id` 불변성 확인을 운영 support path에 포함해야 한다. - - daemon-scoped enforcement enablement gate는 `DATA_EXISTING_DEPLOYMENT_DAEMON_MAP` 완료, active service-token/asset의 explicit reassignment 또는 제외 처리 완료, zero-active-NULL/zero-mismatch dry-run, smoke 검증 통과를 required evidence로 삼는다. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: service-token은 steady state에서 `runtime_id`와 별도로 stable `daemon_id`에 정확히 1:1로 바인딩되어야 한다. daemon provisioning은 별도 daemon create/update 관리 경로에서 수행되며, issue/rotate는 existing daemon만 참조하고 unknown daemon 자동 생성/업서트를 허용하지 않는다. `daemon_id`는 rotate 이후에도 유지되며, `runtime_id`는 런타임 식별자로서 갱신될 수 있어야 한다. 기존 token backfill은 `daemon_id = NULL`로 시작할 수 있고, 이 상태의 token은 daemon-scoped API에서 fail-closed로 취급한다. -- FR-2: 각 asset(`portfolio`)은 steady state에서 동일 owner scope 안에 정확히 1개의 daemon에 배정되어야 한다. 스키마와 migration은 이 exact-one assignment를 DB 레벨에서 표현하고 강제해야 하며, 기존 asset backfill은 `managed_by_daemon_id = NULL`로 시작할 수 있고 이 상태의 asset은 daemon-assigned state로 간주하지 않는다. -- FR-3: `GET /v1/assets`는 service-token 소유 user의 전체 asset이 아니라, 인증된 token에 바인딩된 daemon에 배정된 asset만 반환해야 한다. -- FR-4: `POST /v1/assets/{asset_id}/ledger/trades`는 인증된 daemon에 배정된 asset에 대해서만 성공해야 하며, daemon assignment mismatch 또는 미배정 asset에는 `403 Forbidden`으로 응답하고 ledger row를 생성하지 않아야 한다. -- FR-5: `GET /v1/user_info`는 `runtime_id`와 함께 현재 인증 service-token의 `daemon_id`를 반환해야 하며, backfill로 `daemon_id = NULL`인 legacy token에는 그 NULL 값을 진단용으로 노출해야 한다. `pfhub service-token issue/list/rotate` 출력은 모두 `daemon_id` 필드를 포함해야 하며, `POST /auth/service-tokens`와 CLI issue/rotate는 기존 daemon registry에 존재하는 daemon만 바인딩 대상으로 받아야 한다. 운영 provenance 절차는 `runtime_id`와 `daemon_id`의 역할 차이를 명확히 드러내야 한다. -- FR-6: baseline 문서와 운영 runbook은 daemon provisioning/binding, asset assignment, rotate invariants, 기존 데이터 backfill 절차를 반영해야 한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: `runtime_id`는 runtime identity, `daemon_id`는 stable logical identity로 역할이 분리되어야 하며, 어떤 API나 운영 절차도 `runtime_id`를 asset assignment key로 재사용하면 안 된다. -- NFR-2: DB는 post-cutover steady state에서 `service-token -> daemon`, `asset -> daemon` 관계의 same-user consistency와 exact-one assignment를 FK/check/NOT NULL 제약으로 보장해야 한다. 전환 구간에서는 `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`의 `NULL` backfill을 허용하되, explicit reassignment와 검증을 거쳐 exact-one assignment 상태로 수렴해야 하며, 누락 또는 mismatch 상황은 fail-open이 아닌 fail-closed로 처리해야 한다. -- NFR-3: migration과 schema bootstrap은 기존 Alembic revision chain 및 `IF NOT EXISTS` 정책을 준수해야 하며, backfill이 완료되지 않은 상태에서 daemon-scoped enforcement가 부분 적용되지 않도록 해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - stable `daemon_id` 모델과 service-token binding - - exact-one asset assignment 및 daemon-scoped `/v1/assets` / ledger authorization - - service-token rotate/list/issue/provenance의 `runtime_id`/`daemon_id` 역할 재정의 - - 필요한 migration, backfill 정책, baseline 문서 및 운영 runbook 반영 -- Out of Scope - - trex-basic 측 consumer 코드 변경 자체 - - runtime heartbeat, leader election, lease/lock 같은 distributed runtime control - - `1자산 N데몬`, 예약 배정, 시간대별 assignment 같은 many-to-many 또는 temporal assignment 모델 - - 외부 사용자를 위한 daemon fleet 관리 UI 또는 공개 운영 포털 추가 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 기존 asset과 token을 `NULL`로 backfill한 뒤 explicit daemon assignment가 늦어지면 기존 daemon이 `/v1/assets`와 ledger write에서 연쇄적으로 `403`을 맞아 운영 중단이 발생할 수 있다. - - 완화 방안: production operator가 현재 배포 환경의 `user -> daemon -> asset` inventory와 explicit reassignment 계획을 준비하고, portfolio-hub repository-maintainers가 dry-run audit 결과를 검토한 뒤, release approver 승인 이후에만 daemon-scoped enforcement를 켠다. -- R-2 (High): `runtime_id`와 `daemon_id`의 의미가 문서/코드/운영 절차마다 다르게 해석되면 rotate 이후 provenance와 asset assignment가 다시 흔들릴 수 있다. - - 완화 방안: portfolio-hub repository-maintainers가 REQ와 테스트에서 "rotate는 runtime_id를 갱신하지만 daemon_id는 유지"를 불변식으로 명시하고, issue/list/rotate/user_info 출력 필드 정의를 함께 정렬한다. -- R-3 (Medium): daemon-scoped enforcement를 켠 뒤에도 미배정 asset이나 daemon-less service-token이 남아 있으면 `/v1/assets` 또는 ledger write가 예고 없이 깨질 수 있다. - - 완화 방안: production operator가 enforcement 이전 audit/report와 smoke 검증을 수행하고, release approver가 rollback approver를 겸해 on/off 판단을 승인한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 동일 user가 여러 daemon을 가질 수 있지만, 각 daemon은 정확히 1명의 owner user에 속한다. -- A-2: `runtime_id`는 per-token runtime identity로 유지되며 issue/rotate 시 갱신될 수 있다. -- A-3: service-token bearer auth와 기존 `read`/`trade-write`/`admin` 권한 모델은 유지하고, daemon assignment는 그 위에 추가되는 authorization 축이다. -- A-4: `1개 자산 = 정확히 1개 데몬` 규칙은 태그나 외부 컨벤션이 아니라 DB-backed 서버 모델로 강제한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- 현재 Open 상태 OQ는 없다. 기존 OQ-1~OQ-4의 결정 사항은 본문과 `# 10. 사용자 결정 필요 항목 요약`에 반영했다. -- OQ-4: daemon-scoped enforcement enablement gate — `NULL` backfill 상태의 daemon-scoped enforcement는 다음 조건이 모두 충족될 때만 활성화한다: `DATA_EXISTING_DEPLOYMENT_DAEMON_MAP`가 활성 user/service-token/asset 전체를 커버하고, active service-token은 모두 valid `daemon_id`에 바인딩되거나 명시적으로 revoke/disable되며, active asset은 모두 정확히 1개 daemon에 재배정되거나 명시적으로 전환 대상에서 제외되고, dry-run audit에서 zero-active-NULL/zero-mismatch가 확인되며, smoke 검증이 통과해야 한다. - - 상태: Answered - - 처리 방안: production operator가 inventory/reassignment/smoke evidence를 준비하고, portfolio-hub repository-maintainers가 dry-run audit과 contract test evidence를 검토하며, release approver가 enforcement on/off와 rollback approver를 겸해 최종 승인한다. - - 종료 조건: required evidence set, owner, approver가 운영 runbook과 후속 REQ Acceptance Criteria에 반영되면 유지 결정으로 닫는다. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 동일 user가 `daemon-a`, `daemon-b`를 운영할 때, 각 daemon의 service-token으로 호출한 `GET /v1/assets`는 해당 daemon에 배정된 asset만 반환한다. -- S-2: `daemon-a`에 배정된 asset에 대해 `daemon-b`가 `POST /v1/assets/{asset_id}/ledger/trades`를 호출하면 `403 Forbidden`으로 거부되고 DB row가 생성되지 않으며, `daemon-a` 호출만 성공한다. -- S-3: `GET /v1/user_info`와 `pfhub service-token issue/list/rotate`는 `daemon_id`를 노출하고, service-token rotate 이후 `runtime_id`는 변경되지만 `daemon_id` binding은 유지된다. -- S-4: daemon create/update는 별도 registry 관리 경로에서만 수행되고, `POST /auth/service-tokens`와 CLI issue/rotate는 존재하지 않는 daemon 입력을 명시적으로 거부하며 자동 생성/업서트를 하지 않는다. -- S-5: daemon-scoped enforcement는 inventory 완료, active token/asset reassignment 또는 제외 처리 완료, zero-active-NULL/zero-mismatch dry-run, smoke 검증 통과, release approver 승인 이후에만 활성화된다. -- S-6: baseline 문서와 운영 runbook이 daemon provisioning, asset assignment, enforcement enablement gate, rotate invariants, backfill 절차를 반영하고 더 이상 `runtime_id`를 asset assignment key처럼 설명하지 않는다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: pytest로 동일 user 아래 2개 daemon과 2개 asset을 구성해 daemon-scoped `/v1/assets` 응답을 검증 / 데이터 출처: `tests/test_v1.py` 또는 신규 `tests/test_daemon_assignment.py` -- S-2 측정 방식: pytest로 daemon mismatch ledger write를 호출해 `403` 응답과 DB row count 무변경을 검증 / 데이터 출처: `tests/test_v1.py` 또는 신규 `tests/test_daemon_assignment.py` -- S-3 측정 방식: targeted auth/CLI/API 테스트로 rotate 전후 `runtime_id`/`daemon_id` invariants와 `daemon_id` diagnostics 노출을 검증 / 데이터 출처: `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_v1.py` -- S-4 측정 방식: targeted auth/CLI/API 테스트로 unknown daemon binding 요청이 명시적으로 실패하고 daemon auto-create/upsert가 발생하지 않음을 검증 / 데이터 출처: `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_v1.py` 또는 신규 `tests/test_daemon_registry.py` -- S-5 측정 방식: cutover checklist와 dry-run/smoke evidence로 active token/asset reassignment 완료, zero-active-NULL/zero-mismatch, release approver 승인 여부를 확인 / 데이터 출처: 운영 cutover checklist, audit report, smoke test log -- S-6 측정 방식: baseline 문서와 운영 runbook에 daemon assignment, enforcement enablement gate, backfill 절차가 반영됐는지 코드 리뷰로 확인 / 데이터 출처: `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md`, `README.md` - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: daemon provisioning path를 포함한 주요 정책 결정이 범위와 함께 본문 및 section 10에 반영되어야 함 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-6이 pytest, 운영 evidence, 또는 문서 리뷰 evidence로 추적 가능한 수준으로 정리되어야 함 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1 backfill/reassignment owner는 production operator, R-2 runtime/daemon provenance owner는 portfolio-hub repository-maintainers, rollback approver는 release approver로 지정되어야 함 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: confirm-discovery 게이트에서 범위 승인자와 후속 REQ drafting owner가 지정되어야 함 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 후 갱신 -- 생성 경로: docs/discovery/dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md -- 참조 문서: - - docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md - - docs/discovery/dcy-005_20260511_production-admin-bootstrap-and-operator-access.md - - docs/srs/Interface/req-005_service-token-runtime-id-issuance.md - - docs/srs/Interface/req-006_v1-assets-endpoint.md - - docs/srs/Interface/req-007_v1-user-info-endpoint.md - - docs/srs/Interface/req-008_v1-ledger-trades-endpoint.md - - docs/srs/Migration/req-009_v1-api-db-schema-migration.md - - docs/srs/Interface/req-012_service-token-rotate-cli.md - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 - -## CONFIRM -- 없음 (Confirmed By: Justin — 2026-05-13) - -## DATA -- baseline 반영 완료 - - `DATA_EXISTING_DEPLOYMENT_DAEMON_MAP`: canonical acceptance environment는 production이고, staging은 optional preflight, local/local-test는 cutover evidence 대상에서 제외한다. 현재 저장소 기준으로 확인되는 external consumer daemon lineage는 `trex-basic` 1종이며, production operator는 실제 cutover checklist와 release evidence에 환경별 row를 `(environment, username 또는 user_id, daemon_slug, daemon_display_name, service_token_name, token_prefix, permission, asset_ids, state[active|excluded|revoked])` 형식으로 기록한다. raw secret 값은 저장소에 남기지 않고, production canonical secret material location은 host-side `EnvironmentFile=/opt/portfoliohub/.env` 운영 경로를 따른다. - - `DATA_DAEMON_NAMING_RULES`: `daemon_display_name`은 trim된 non-empty human-readable label로 두고, `daemon_slug`는 owner user 내에서 unique한 stable identifier로 관리한다. 현재 consumer lineage 기준 권장 slug prefix는 `trex-basic`이며, slug 표기는 lowercase kebab-case를 사용한다. canonical operator 표기와 cutover evidence 표기는 `/`로 통일하고, provenance 추적에는 `daemon_slug`, `service_token_name`, `token_prefix`를 함께 기록한다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 현재 저장소를 재확인한 결과 `/v1/assets`는 아직 owner-user scope만 사용하고, `/v1/user_info`는 `runtime_id`만 반환하며, `POST /v1/assets/{asset_id}/ledger/trades`는 daemon assignment 검증이 없고, service-token CLI도 daemon binding/diagnostics를 아직 다루지 않는다. 문서의 현재 상태 서술, FR/NFR, 성공 기준, section 10 DATA baseline은 이 현실과 일치하며, Open OQ와 C 유형 플레이스홀더는 0개다. cutover checklist row와 smoke evidence는 release 단계의 운영 검증 산출물로 남기고, REQ drafting handoff 자체를 막는 항목으로 두지 않는다. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-05-13 19:17 \ No newline at end of file diff --git a/docs/discovery/index.md b/docs/discovery/index.md index 41c5186..f5a36ef 100644 --- a/docs/discovery/index.md +++ b/docs/discovery/index.md @@ -6,9 +6,5 @@ | ID | 날짜 | 이슈명 | 문서 | 상태 | |---|---|---|---|---| -| dcy-001_20260430_real-db-migration-validation-and-operations-readiness | 20260430 | 실DB 마이그레이션 검증 및 운영 준비 | [Discovery](./dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md) | confirmed | -| dcy-002_20260502_trex-basic-consumer-api-contract | 20260502 | trex-basic Consumer-Driven Contract 반영 — service-token runtime_id 발급 및 v1 API 엔드포인트 구현 | [Discovery](./dcy-002_20260502_trex-basic-consumer-api-contract.md) | confirmed | -| dcy-003_20260502_portfolio-list-user-required | 20260502 | CLI `pfhub portfolio list` — `--user` 옵션 필수화 및 소유자 기반 조회 강제 | [Discovery](./dcy-003_20260502_portfolio-list-user-required.md) | confirmed | -| dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution | 20260511 | portfolio_id UUID 교체 및 CLI 사용자 범위 기반 자산 식별 구조 전환 | [Discovery](./dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution.md) | confirmed | -| dcy-005_20260511_production-admin-bootstrap-and-operator-access | 20260511 | 실배포 admin bootstrap 및 operator access 경로 정립 | [Discovery](./dcy-005_20260511_production-admin-bootstrap-and-operator-access.md) | confirmed | -| dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment | 20260513 | daemon_id 도입 및 1자산 1데몬 자산 배정 모델 정립 | [Discovery](./dcy-006_20260513_daemon-id-and-single-daemon-asset-assignment.md) | confirmed | \ No newline at end of file +| dcy-001_20260519_asset-catalog-and-user-portfolio-model | 20260519 | 자산 관리 구조 재설계 — admin 관리 asset catalog와 user portfolio 분리 모델 도입 | [Discovery](./dcy-001_20260519_asset-catalog-and-user-portfolio-model.md) | draft | +| dcy-002_20260519_tag-ownership-and-applicability-policy | 20260519 | 태그 소유권 및 적용 대상 정책 — admin/user 구분 태그 모델과 asset·portfolio 적용 정책 도입 | [Discovery](./dcy-002_20260519_tag-ownership-and-applicability-policy.md) | confirmed | diff --git a/docs/releases/index.md b/docs/releases/index.md deleted file mode 100644 index 45f3f57..0000000 --- a/docs/releases/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# Release Index - -## 목적 -- 이 문서는 release 단위의 전역 인덱스다. -- 각 release는 하나 이상의 release-candidate batch를 묶고, profile(`docs-only` | `tooling` | `app-service`)에 맞는 배포와 후속 환류를 추적한다. - -## Release Register - -| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | -| --- | --- | --- | --- | --- | --- | -| REL-001 | app-service | released | BAT-001 | dcy-002 / REQ-004~010 | [rel-001_20260502_trex-basic-v1-api-contract](./rel-001_20260502_trex-basic-v1-api-contract.md) | -| REL-002 | tooling | released | BAT-002, BAT-005, BAT-008 | minor-change + dcy-002 / REQ-011, REQ-012, REQ-015 | [rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync](./rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync.md) | -| REL-003 | tooling | released | BAT-003, BAT-004, BAT-007 | dcy-003 + dcy-001 / REQ-013, REQ-014, REQ-001~003 | [rel-003_20260512_cli-user-scoping-and-db-ops-runbook](./rel-003_20260512_cli-user-scoping-and-db-ops-runbook.md) | -| REL-004 | app-service | released | BAT-006 | dcy-004 / REQ-016~019 | [rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch](./rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch.md) | -| REL-005 | docs-only | confirmed | BAT-009 | dcy-005 / REQ-020~022 | [rel-005_20260512_production-operator-runbook-and-credential-matrix](./rel-005_20260512_production-operator-runbook-and-credential-matrix.md) | -| REL-006 | app-service | draft | BAT-010 | dcy-006 / REQ-005~009, REQ-011~012, REQ-023, REQ-028 | [rel-006_20260513_daemon-model-implementation](./rel-006_20260513_daemon-model-implementation.md) | \ No newline at end of file diff --git a/docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md b/docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md deleted file mode 100644 index 15d6bc8..0000000 --- a/docs/releases/rel-001_20260502_trex-basic-v1-api-contract.md +++ /dev/null @@ -1,142 +0,0 @@ -# REL-001: trex-basic v1 API contract - -- Status: released -- Profile: app-service -- Included Batch: BAT-001 - -## Scope - -- Included: - - BAT-001 (REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010) - - GET /health 응답 스키마 확장 (service/version/checked_at 필드) - - POST /auth/service-tokens — service-token 발급 + runtime_id 반환 API - - GET /v1/assets — service-token 소유자 범위의 포트폴리오 목록 조회 (status 필터, strategy_params, owner scope 재확인 반영) - - GET /v1/user_info — 사용자 정보 + broker credentials 복호화 반환 - - POST /v1/assets/{asset_id}/ledger/trades — 체결 기록 (idempotent: 201/200/409/404) - - Fernet 암호화 유틸리티 (app/core/encryption.py) - - startup validation: CREDENTIAL_ENCRYPTION_KEY 없으면 RuntimeError - - DB 스키마 migration: portfolios.market, auth_tokens.runtime_id, broker_credentials, ledger_trades - -- Excluded: - - GET /health status degraded/failed 경로 (이번 반복 OOS) - - broker credential 저장 HTTP API (CLI 전용) - - DB 환경(Docker Compose) 직접 실행 검증 - -## Rollout Plan - -1. 배포 전 `CREDENTIAL_ENCRYPTION_KEY` 환경 변수 설정 확인 (`python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` 로 생성) -2. `alembic upgrade head` 실행 — migration 0014 적용 (portfolios.market, auth_tokens.runtime_id, broker_credentials, ledger_trades) -3. 서비스 재시작 (`uvicorn app.main:app`) -4. GET /health 호출로 기동 확인 -5. v1 smoke 테스트: GET /v1/assets, GET /v1/user_info (service-token 사용) -6. GET /v1/assets smoke 검증 시 service-token 소유자의 portfolio만 반환되고 다른 사용자 소유 portfolio는 포함되지 않는지 확인 - -## Rollback Plan - -- 조건: startup 실패 또는 GET /health 비정상 응답 시 -- 절차: - 1. 이전 버전 코드로 롤백 (`git revert` 또는 이전 컨테이너 이미지 재기동) - 2. `alembic downgrade -1` — migration 0014 롤백 (신규 테이블/컬럼 DROP) - 3. `CREDENTIAL_ENCRYPTION_KEY` 미설정 상태로도 DB disabled 모드에서는 기동 가능하므로, 긴급 시 `PORTFOLIOHUB_DB_ENABLED=false`로 임시 운영 가능 - -## Verification Checklist - -### 배포 전 - -- [x] `CREDENTIAL_ENCRYPTION_KEY` 환경 변수 설정 완료 -- [x] `alembic upgrade head` 성공 (migration 0014) -- [x] pytest 로컬 PASS 확인 (`python -m pytest -q` → `129 passed in 4.46s`) - -### 배포 후 - -- [x] GET /health → 200, body에 `status`, `service`, `version`, `checked_at` 포함 확인 -- [x] GET /health → `checked_at`에 `+09:00` 포함 확인 -- [~] POST /auth/service-tokens (admin 토큰) → 201, `runtime_id` 포함 확인 — **유보**: admin credential 미보유, 후속 검증 이관 -- [x] GET /v1/assets (service-token) → 200, `assets[]` 반환 확인 -- [~] GET /v1/assets (service-token) → service-token 소유자의 portfolio만 반환되고 다른 사용자 portfolio가 비노출인지 확인 (REQ-006 owner scope 재확인) — **유보**: 현재 total=0이라 비노출 강도 미확인. CONFLICT-1 fix 재배포 후 다중 사용자 데이터 기반 검증 필요 -- [x] GET /v1/user_info (service-token) → 200, `trading_enabled`/`brokers[]` 포함 확인 -- [~] POST /v1/assets/{asset_id}/ledger/trades → 201 (신규), 200 (중복 동일 payload) 확인 — **유보**: trade-write credential 미보유, 후속 검증 이관 -- [x] 서비스 startup 로그에 RuntimeError 없음 확인 — proxy: GET /health → 200 (`status=ok`) 확인됨 - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-05-11 -- Completed At: - - 2026-05-11 -- Environment: - - https://fourblocks2026.com/portfolio-hub/ -- Operator: - - repository-maintainers -- Revision or Artifact: - - 2e1298a (fix: enforce trade-write permission check on v1 ledger trade endpoint) -- Commands or Procedure: - - `CREDENTIAL_ENCRYPTION_KEY` 설정 확인 완료 - - `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` 실행으로 Fernet 키 생성 형식 확인 완료 - - 보안상 실제 환경 변수 값과 생성된 키 원문은 문서에 기록하지 않음 - - `alembic upgrade head` 실행 완료 - - Alembic 출력: `Context impl PostgresqlImpl.` / `Will assume transactional DDL.` - - 추가 revision 실행 로그는 출력되지 않았고, 명시적 오류 메시지는 없었음 - - 로컬 회귀 검증: `python -m pytest -q` → `129 passed in 4.46s` - - 배포 public smoke: `curl https://fourblocks2026.com/portfolio-hub/health` → HTTP 200, `{"status":"ok","service":"portfolio-hub","version":"0.1.0","checked_at":"2026-05-11T21:35:54.686819+09:00"}` - - 배포 public contract 확인: `/portfolio-hub/docs` → HTTP 200, `/portfolio-hub/openapi.json` → HTTP 200 - - 실배포 설정 파일 `temp/real_portfolio-hub_environment`의 service token으로 protected read smoke 실행 - - `GET /v1/assets` → HTTP 200, body 요약: `total=0`, `assets=[]` - - `GET /v1/user_info` → HTTP 200, body 요약: `runtime_id` 존재, `trading_enabled=true`, `permission=read`, `brokers=[]` - - `POST /auth/service-tokens` with same token → HTTP 403 `Insufficient permission` (admin 토큰 미보유) - - `POST /v1/assets/00000000-0000-0000-0000-000000000001/ledger/trades` with same read service token → HTTP 404 `Asset not found ...` - - 위 ledger 결과는 계약상 trade-write 부족이면 403이어야 한다는 기대와 다르므로, write permission enforcement 재확인 필요 - - 현재 보유 credential만으로는 admin 성공 경로와 trade-write 성공 경로를 완료할 수 없음 - -### Profile-Specific Checks - -- app-service: - - Health Endpoint: - - `GET https://fourblocks2026.com/portfolio-hub/health` → HTTP 200 확인. body에 `status=ok`, `service=portfolio-hub`, `version=0.1.0`, `checked_at=2026-05-11T21:35:54.686819+09:00` 포함. - - Startup Log Fields: - - 외부 URL만으로는 startup 로그 접근 불가. 배포 환경 로그 접근 권한 필요. - - Request Log Fields: - - 외부 URL만으로는 request 로그 접근 불가. 배포 환경 로그 접근 권한 필요. - - Smoke or Manual Verification: - - public smoke 완료 (`/health`, `/docs`, `/openapi.json`). - - protected read smoke 완료: 실배포 service token으로 `/v1/assets` 200, `/v1/user_info` 200 확인. - - `/v1/assets` 응답은 빈 목록(`total=0`)이라 owner scope 비노출은 강하게 입증되지 않음 — 유보 처리. - - same token으로 `/auth/service-tokens`는 403 `Insufficient permission` 확인됨 — admin 성공 경로는 유보. - - same token으로 `/v1/assets/{asset_id}/ledger/trades` 호출 시 원래 404 반환됐으나, commit `2e1298a`로 permission 검사 순서 수정 완료 — trade-write 성공 경로는 credential 미보유로 유보. - -### Rollback Outcome - -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - released -- Rationale: - - public health, public contract(/docs, /openapi.json), read-permission smoke(/v1/assets, /v1/user_info) 모두 확인됨. trade-write permission enforcement 코드 수정(commit 2e1298a) 완료. admin 성공 경로(POST /auth/service-tokens)와 trade-write 성공 경로(POST ledger/trades)는 credential 미보유로 유보하며, owner scope 비노출 강검증도 유보함. 유보 항목은 운영 환경 접근 후 후속 검증 이관. -- Recorded By: - - repository-maintainers -- Recorded At: - - 2026-05-12 - -## Feedback Handoff - -- Observation Summary: - - read-permission smoke 성공. trade-write permission 코드 수정(2e1298a) 완료되었으나 재배포 미확인. admin credential 및 trade-write credential 미보유로 주요 성공 경로 미검증 상태로 released 전환. -- Discovery Input: - - admin token 발급 및 관리 운영 절차가 없어 admin 성공 경로 검증 체계 확립 필요 - - trade-write token 발급 및 smoke 절차 운영화 필요 - - owner scope 비노출 검증을 위한 다중 사용자 테스트 데이터 운영 방안 검토 -- REQ Input: - - 없음 -- Change Request Input: - - 없음 diff --git a/docs/releases/rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync.md b/docs/releases/rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync.md deleted file mode 100644 index c816ced..0000000 --- a/docs/releases/rel-002_20260511_operator-cli-tooling-and-baseline-docs-sync.md +++ /dev/null @@ -1,118 +0,0 @@ -# REL-002: operator CLI tooling and trex-basic baseline docs sync - -- Status: released -- Profile: tooling -- Included Batch: BAT-002, BAT-005, BAT-008 - -## Scope - -- Included: - - BAT-002 (REQ-012) — `pfhub service-token rotate [--force]` CLI 명령 추가 - - BAT-005 (REQ-015) — `pfhub credential set/list/remove` CLI 명령 그룹 추가 - - BAT-008 (REQ-011) — `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md` trex-basic 계약 기준 baseline 동기화 - - rotate 운영 메모 — 새 토큰 배포 후 downstream consumer는 새 토큰으로 다시 인증하고 `GET /v1/user_info` 기준 `runtime_id`를 재조회해야 함 - -- Excluded: - - BAT-001 app-service 배포 및 runtime health 확인 - - DB schema migration 실행 - - 신규 HTTP API 엔드포인트 추가 또는 서비스 프로세스 재기동 필수 변경 - -## Rollout Plan - -1. 대상 운영자 환경에 BAT-002, BAT-005, BAT-008이 포함된 현재 revision을 배포하거나 패키지 갱신한다. -2. `CREDENTIAL_ENCRYPTION_KEY` 설정 여부를 확인하고, rotate smoke용 no-expiry service-token과 credential smoke용 테스트 사용자를 준비한다. -3. 비운영 토큰으로 `pfhub service-token rotate --force`를 실행해 one-time token 출력과 DB 갱신을 확인한다. -4. rotate한 토큰을 실제 consumer에 배포할 계획이 있으면, 새 토큰으로 재인증 후 `GET /v1/user_info`를 재호출해 새 `runtime_id`를 재인식하는 절차를 운영 메모에 반영한다. -5. `pfhub credential set`, `pfhub credential list`, `pfhub credential remove --force` smoke를 실행해 broker credential 관리 동작을 확인한다. -6. 배포 산출물 또는 저장소에서 baseline 문서 3종이 최신 내용으로 노출되는지 확인한다. - -## Rollback Plan - -- 조건: CLI 명령 실행 실패, 패키지 회귀, 운영자 가이드 누락으로 release를 유지할 수 없을 때 -- 절차: - 1. 이전 revision 또는 패키지 버전으로 롤백한다. - 2. rotate smoke가 이미 토큰 값을 변경했으면, 해당 비운영 토큰을 다시 rotate하거나 새로 발급해 테스트 상태를 복구한다. - 3. credential smoke가 테스트 레코드를 남겼으면 `pfhub credential remove --force` 또는 기존 값 재설정으로 정리한다. - 4. 문서 게시 산출물이 있다면 이전 문서 버전으로 되돌린다. - -## Verification Checklist - -### 배포 전 - -- [x] BAT-002 verification `verified`, BAT-005 verification `passed`, BAT-008 verification `passed` 확인 -- [x] REQ-011, REQ-012, REQ-015가 `Implemented` 상태로 전환되었는지 확인 -- [x] `CREDENTIAL_ENCRYPTION_KEY` 설정 및 비운영 smoke 대상(token, user) 준비 확인 — tooling profile, test evidence로 대체 -- [x] DB schema migration이 이번 release 범위에 포함되지 않음을 운영자에게 재확인 — BAT-002/005/008 모두 tooling/docs 배치, 해당 없음 - -### 배포 후 - -- [x] `pfhub service-token rotate --force` smoke 성공 및 one-time token 안내 메시지 확인 — BAT-002 test evidence로 대체: `test_cli_service_token_rotate_no_expiry_succeeds` 외 4개 PASS -- [x] rotate 대상 consumer의 `runtime_id` 재조회 필요성 전달 또는 runbook 반영 확인 — REQ-012 CHG-20260511-01에 lifecycle 및 consumer 재조회 필요성 명시 완료 -- [x] `pfhub credential set/list/remove` smoke 성공 및 `encrypted_payload` 비노출 확인 — BAT-005 test evidence로 대체: `test_cli_credential_set_creates_record` 외 4개 PASS -- [x] `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md` 최신 반영본 확인 — BAT-008 verification REQ-011 AC-1~3 PASS - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-05-11 -- Completed At: - - 2026-05-12 -- Environment: - - local workspace (tooling profile — 서버 배포 불필요) -- Operator: - - repository-maintainers -- Revision or Artifact: - - 2e1298a (fix: enforce trade-write permission check on v1 ledger trade endpoint) -- Commands or Procedure: - - tooling profile — 별도 패키지 설치 단계 없음 - - CLI regression 확인: `python -m pytest tests/test_cli.py -q` → `49 passed in 1.49s` (BAT-005 verification 기준) - - rotate CLI evidence: BAT-002 verification — `test_cli_service_token_rotate_*` 5개 PASS - - credential CLI evidence: BAT-005 verification — `test_cli_credential_*` 5개 PASS - - baseline 문서 sync: BAT-008 verification — REQ-011 AC-1~3 PASS - -### Profile-Specific Checks - -- tooling: - - Install or Upgrade Smoke: - - Source checkout 기준 tooling release로 별도 패키지 설치 단계는 없음. 현재 workspace revision에서 CLI regression evidence 확인: BAT-005 verification 기준 `python -m pytest tests/test_cli.py -q` → `49 passed in 1.49s`. - - CLI or Script Execution: - - BAT-002 verification 기준 rotate CLI acceptance evidence 확보: `test_cli_service_token_rotate_no_expiry_succeeds`, `test_cli_service_token_rotate_unknown_name_exits_error`, `test_cli_service_token_rotate_revoked_token_exits_error`, `test_cli_service_token_rotate_with_expiry_exits_error`, `test_cli_service_token_rotate_can_abort` 모두 PASS. - - BAT-005 verification 기준 credential CLI acceptance evidence 확보: `test_cli_credential_set_creates_record`, `test_cli_credential_list_hides_payload`, `test_cli_credential_set_with_unknown_user_exits_error`, `test_cli_credential_remove_deletes_record`, `test_cli_credential_set_requires_encryption_key` 모두 PASS. - - BAT-008 verification 기준 baseline 문서 sync evidence 확보: REQ-011 AC-1~3 PASS, Ready for Release true. - - Compatibility Note: - - rotate는 `runtime_id`를 새 값으로 갱신하므로 downstream consumer는 새 토큰 배포 후 `GET /v1/user_info` 기준 `runtime_id`를 재조회해야 함 - -### Rollback Outcome - -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - released -- Rationale: - - BAT-002/005/008 모두 release-candidate. REQ-011/012/015 모두 Implemented. tooling profile이므로 서버 재기동 불필요. rotate/credential 실환경 smoke는 각 batch test evidence(BAT-002 5개, BAT-005 5개 PASS)로 대체. baseline 문서 3종 BAT-008 완료로 확인. rotate 후 runtime_id lifecycle은 REQ-012 change log에 명시 완료. -- Recorded By: - - repository-maintainers -- Recorded At: - - 2026-05-12 - -## Feedback Handoff - -- Observation Summary: - - tooling profile release 전 항목 모두 test evidence로 확인 완료. 실환경 CLI smoke는 수행하지 않고 batch verification evidence로 대체함. -- Discovery Input: - - rotate 후 downstream consumer(trex-basic) runtime_id 재인식 절차를 운영 runbook에 반영하는 것을 검토할 수 있음 -- REQ Input: - - 없음 -- Change Request Input: - - 없음 \ No newline at end of file diff --git a/docs/releases/rel-003_20260512_cli-user-scoping-and-db-ops-runbook.md b/docs/releases/rel-003_20260512_cli-user-scoping-and-db-ops-runbook.md deleted file mode 100644 index 7cedce1..0000000 --- a/docs/releases/rel-003_20260512_cli-user-scoping-and-db-ops-runbook.md +++ /dev/null @@ -1,127 +0,0 @@ -# REL-003: CLI user scoping and local DB ops runbook - -- Status: released -- Profile: tooling -- Included Batch: BAT-003, BAT-004, BAT-007 - -## Scope - -- Included: - - BAT-003 (REQ-013) — `pfhub portfolio list --user ` 필수화; owner_user_id 기반 포트폴리오 필터링 - - BAT-004 (REQ-014) — `pfhub portfolio create --user ` 필수화; 생성 시 owner_user_id 설정 - - BAT-007 (REQ-001, REQ-002, REQ-003) — 로컬 DB 운영 runbook 및 baseline 문서 갱신 - - README.md "Local DB Validation Runbook" 섹션: init-db/migrate-db 절차, Scenario A/B, rollback 기준, release gate 기준 - - docs/runtime-flows.md: "Flow: Local DB Initialization and Validation", "Flow: Alembic vs ensure_schema() Schema Ownership Boundary" - - docs/project-structure.md: "Schema Ownership Boundary" 섹션 - -- Excluded: - - BAT-006 (REQ-016~019) — portfolios UUID migration 및 HTTP API/CLI UUID 전환 (app-service, 별도 릴리즈) - - HTTP API 변경 없음 (서버 재기동 불필요) - - DB 스키마 migration 없음 - -## Breaking Changes - -- `pfhub portfolio list`에서 `--user ` 이 필수가 됨. `--user` 없이 실행 시 오류 메시지와 exit code 1 반환. -- `pfhub portfolio create`에서 `--user ` 이 필수가 됨. `--user` 없이 실행 시 오류 메시지와 exit code 1 반환. -- 기존 스크립트 또는 자동화에서 `portfolio list` / `portfolio create`를 `--user` 없이 호출하고 있다면 수정 필요. - -## Rollout Plan - -1. 패키지 또는 소스를 최신 커밋으로 업데이트한다. -2. `pfhub --help` 실행 → `portfolio` 커맨드 그룹이 정상 로드되는지 확인한다. -3. `pfhub portfolio list` (--user 없이) 실행 → exit code 1과 오류 메시지가 출력되는지 확인한다. -4. `pfhub portfolio list --user ` 실행 → 해당 사용자의 포트폴리오만 출력되는지 확인한다. -5. `pfhub portfolio create --symbol X --force` (--user 없이) 실행 → exit code 1과 오류 메시지가 출력되는지 확인한다. -6. README.md "Local DB Validation Runbook" 섹션이 정상 렌더링되는지 확인한다. -7. 서버 재기동 불필요. - -## Rollback Plan - -- 이 릴리즈는 CLI 동작 변경과 문서 추가만 포함한다. DB 상태 변경 없음. -- 이전 버전으로 rollback 시: 이전 소스/패키지로 되돌리면 `--user` 없이 `portfolio list` / `portfolio create` 실행 가능한 이전 동작으로 복원된다. -- 문서 rollback은 git revert로 처리한다. - -## Verification Checklist - -### 배포 전 확인 - -- [x] BAT-003 verification.md Status: verified, Blocking Issues: 없음 확인 -- [x] BAT-004 verification.md Status: verified, Blocking Issues: 없음 확인 -- [x] BAT-007 verification.md Status: passed, Blocking Issues: 없음 확인 -- [x] `pytest tests/test_cli.py -v` 전체 통과 확인 — 49 passed in 1.44s -- [x] README.md "Local DB Validation Runbook" 섹션 존재 확인 -- [x] docs/runtime-flows.md "Flow: Local DB Initialization and Validation" 섹션 존재 확인 -- [x] docs/project-structure.md "Schema Ownership Boundary" 섹션 존재 확인 - -### 배포 후 확인 - -- [x] `pfhub portfolio list` 실행 → `--user` 미입력 시 exit code 1 반환 확인 — test evidence: `test_cli_portfolio_list_without_user_exits_error` PASS -- [x] `pfhub portfolio list --user ` 실행 → 해당 사용자 포트폴리오만 반환 확인 — test evidence: `test_cli_portfolio_list_with_valid_user_filters_by_owner` PASS -- [x] `pfhub portfolio create --symbol X --force` 실행 → `--user` 미입력 시 exit code 1 반환 확인 — test evidence: `test_cli_portfolio_create_without_user_exits_error` PASS -- [x] `pfhub portfolio create --symbol X --user --force` 실행 → 정상 생성 확인 — test evidence: `test_cli_portfolio_create_with_valid_user_sets_owner` PASS - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-05-12 -- Completed At: - - 2026-05-12 -- Environment: - - local workspace (tooling profile — 서버 배포 불필요) -- Operator: - - repository-maintainers -- Revision or Artifact: - - 2e1298a978d789bae8bc6b31d9e94027fc63dc66 -- Commands or Procedure: - - tooling profile — 별도 패키지 설치 단계 없음 - - CLI regression 확인: `.venv/bin/pytest tests/test_cli.py -v` → `49 passed in 1.44s` - - portfolio list CLI evidence: BAT-003 verification — `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_filters_owner` 모두 PASS - - portfolio create CLI evidence: BAT-004 verification — `test_cli_portfolio_create_without_user_exits_error`, `test_cli_portfolio_create_with_valid_user_sets_owner`, `test_cli_portfolio_create_with_unknown_user_exits_error` 모두 PASS - - DB runbook 문서 확인: BAT-007 verification — REQ-001/002/003 AC 전체 PASS - -### Profile-Specific Checks - -- tooling: - - Install or Upgrade Smoke: - - Source checkout 기준 tooling release로 별도 패키지 설치 단계는 없음. 현재 workspace revision에서 CLI regression evidence 확인: `.venv/bin/pytest tests/test_cli.py -v` → `49 passed in 1.44s`. - - CLI or Script Execution: - - BAT-003 verification 기준 portfolio list acceptance evidence 확보: `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_filters_owner` 모두 PASS. - - BAT-004 verification 기준 portfolio create acceptance evidence 확보: `test_cli_portfolio_create_without_user_exits_error`, `test_cli_portfolio_create_with_valid_user_sets_owner`, `test_cli_portfolio_create_with_unknown_user_exits_error` 모두 PASS. - - BAT-007 verification 기준 DB runbook 문서 evidence 확보: REQ-001 AC-1~3, REQ-002 AC-1~2, REQ-003 AC-1~3 전체 PASS. - - Compatibility Note: - - Breaking change: `portfolio list`, `portfolio create` 명령에 `--user` 필수. 기존 스크립트 점검 필요. - -### Rollback Outcome - -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - released -- Rationale: - - BAT-003/004/007 모두 release-candidate. REQ-001/002/003/013/014 모두 Implemented. tooling profile이므로 서버 재기동 불필요. portfolio list/create CLI smoke는 각 batch test evidence(BAT-003 4개, BAT-004 3개 PASS)로 대체. DB runbook 문서 3종 BAT-007 완료로 확인. `pytest tests/test_cli.py -v` → 49 passed in 1.44s 전체 회귀 통과. -- Recorded By: - - repository-maintainers -- Recorded At: - - 2026-05-12 - -## Feedback Handoff - -- Observation Summary: - - tooling profile release 전 항목 모두 test evidence로 확인 완료. 실환경 CLI smoke는 수행하지 않고 batch verification evidence로 대체함. -- Discovery Input: - - none -- REQ Input: - - none -- Change Request Input: - - none diff --git a/docs/releases/rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch.md b/docs/releases/rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch.md deleted file mode 100644 index aaf60ad..0000000 --- a/docs/releases/rel-004_20260512_portfolio-uuid-migration-and-api-uuid-switch.md +++ /dev/null @@ -1,134 +0,0 @@ -# REL-004: portfolio UUID migration and API UUID switch - -- Status: released -- Profile: app-service -- Included Batch: BAT-006 - -## Scope - -- Included: - - BAT-006 (REQ-016, REQ-017, REQ-018, REQ-019) — portfolios UUID 전환 + CLI name+user 식별 전환 + HTTP API UUID 전환 + baseline 문서 갱신 - - REQ-016: portfolios 테이블 portfolio_id를 UUID PK로 전환, (owner_user_id, name) UNIQUE 제약 추가, Alembic migration 0015 적용 - - REQ-017: CLI portfolio 명령 전체를 name+user 조합 식별 방식으로 전환 (portfolio_id 직접 사용 제거) - - REQ-018: GET /v1/assets 응답 asset_id UUID 형식 반환, POST /v1/assets/{asset_id}/ledger/trades UUID 경로 처리 - - REQ-019: docs/project-structure.md, docs/runtime-flows.md, docs/data-model.md, docs/interface-contract.md baseline 갱신 - -- Excluded: - - trex-basic 측 코드 변경 (별도 저장소, 동시 배포 조정만 해당) - - 신규 HTTP API 엔드포인트 추가 없음 - -## Breaking Changes - -- `GET /v1/assets` 응답 `assets[].asset_id`가 이전 TEXT 형식에서 UUID 형식으로 변경됨. - - trex-basic 등 downstream consumer는 asset_id를 UUID로 파싱해야 함. -- `POST /v1/assets/{asset_id}/ledger/trades` path parameter가 UUID를 기대함. -- CLI portfolio 명령(create, buy, sell, status, delete, update 등)에서 portfolio_id 직접 지정 방식이 제거되고 ` --user ` 방식으로 전환됨. -- Alembic migration 0015 실행 필수: portfolios 테이블 기존 레코드 UUID 전환, FK 4개 테이블(portfolio_ledger, portfolio_comments, portfolio_tags, ledger_trades) 업데이트. - -## Rollout Plan - -1. trex-basic 팀과 배포 시각을 조율한다. asset_id UUID 형식 변경으로 trex-basic이 새 형식을 파싱할 준비가 됐는지 확인한다. -2. `alembic upgrade head`를 실행한다 — migration 0015 적용 (portfolio_id UUID 전환, name 컬럼 추가, FK UPDATE, UNIQUE 제약 추가, orphan check). -3. 서비스를 재시작한다 (`uvicorn app.main:app`). -4. `GET /health` 호출로 기동 확인한다. -5. service-token으로 `GET /v1/assets`를 호출하여 응답 `assets[].asset_id`가 UUID 형식인지 확인한다. -6. trex-basic을 동시에 배포하거나, 새 asset_id UUID 파싱 가능한 버전으로 업데이트한다. -7. `POST /v1/assets/{asset_id}/ledger/trades` (UUID asset_id) smoke 확인한다. -8. CLI: `pfhub portfolio create --user `, `pfhub portfolio status --user ` smoke 확인한다. - -## Rollback Plan - -- 조건: migration 0015 실패, 서비스 startup 오류, GET /health 비정상, trex-basic 호환 문제 발생 시 -- 절차: - 1. 서비스를 이전 버전 코드로 롤백한다. - 2. `alembic downgrade -1` — migration 0015 롤백 (UUID 전환 되돌리기, portfolio_id 구 형식 복원). 단, 롤백 시 기존 UUID로 생성된 레코드는 구 형식으로 변환이 어려울 수 있으므로 migration 이전 DB 스냅샷 백업을 확인한다. - 3. trex-basic을 이전 버전으로 롤백한다. - -## Verification Checklist - -### 배포 전 확인 - -- [x] BAT-006 verification.md Status: passed, Blocking Issues: 없음 확인 -- [x] `pytest` 전체 통과 확인 — `.venv/bin/pytest -q` → 130 passed in 4.31s (tests/test_portfolio_uuid.py 14/14, tests/test_cli.py 49/49, tests/test_portfolio_ownership.py 4/4 포함) -- [x] trex-basic 동시 배포 준비 상태 확인 (asset_id UUID 형식 파싱 지원 여부) — 외부 팀 조율 필요 -- [x] DB 스냅샷 또는 백업 존재 확인 (migration 0015 롤백 대비) — 배포 환경 접근 필요 -- [x] `CREDENTIAL_ENCRYPTION_KEY` 환경 변수 설정 확인 — local workspace 기준 set 확인; 배포 대상 환경 별도 확인 필요 - -### 배포 후 확인 - -- [x] `alembic upgrade head` 성공 (migration 0015) 및 오류 없음 확인 — REL-001 배포(revision 2e1298a) 시 적용 완료; production asset_id UUID 형식으로 확인됨 -- [x] `GET /health` → 200, `status: ok` 확인 — `{"status":"ok","service":"portfolio-hub","version":"0.1.0","checked_at":"2026-05-12T14:18:30.613518+09:00"}` -- [x] `GET /v1/assets` (service-token) → 응답 `assets[].asset_id`가 UUID 형식인지 확인 — `435b8e89-afc1-4a97-a658-f0e69f46d247` UUID 형식 확인 -- [~] `GET /v1/assets` → service-token 소유자의 portfolio만 반환되고 타 사용자 portfolio 비노출 확인 — **유보**: total=1, 타 사용자 포트폴리오 없어 비노출 강검증 불가 -- [x] `POST /v1/assets/{uuid}/ledger/trades` → UUID asset_id로 정상 처리 또는 404 응답 확인 — 기존 UUID 201(신규)/200(중복) 확인; 없는 UUID `00000000-0000-0000-0000-000000000099` → 404 확인 -- [x] CLI `pfhub portfolio create --user ` → UUID PK portfolio 생성 확인 — test evidence: `test_create_portfolio_assigns_uuid_id` PASS -- [x] CLI `pfhub portfolio status --user ` → name+user 조합으로 정상 조회 확인 — test evidence: `test_status_resolves_by_name_and_user` PASS -- [ ] trex-basic 측 asset_id UUID 파싱 정상 동작 확인 — 외부 팀 조율 필요 - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-05-12 -- Completed At: - - 2026-05-12 -- Environment: - - https://fourblocks2026.com/portfolio-hub/ -- Operator: - - repository-maintainers -- Revision or Artifact: - - 2e1298a978d789bae8bc6b31d9e94027fc63dc66 -- Commands or Procedure: - - migration 0015는 REL-001 배포(revision 2e1298a) 시 `alembic upgrade head`로 함께 적용 완료. 별도 재배포 절차 없음. - - production smoke: `GET /health` → 200, `GET /v1/assets` → UUID asset_id 확인, `POST /v1/assets/{uuid}/ledger/trades` → 201/200/404 확인 - - local CLI regression: `.venv/bin/pytest -q` → 130 passed in 4.31s - -### Profile-Specific Checks - -- app-service: - - Health Endpoint: - - `GET https://fourblocks2026.com/portfolio-hub/health` → HTTP 200. body: `{"status":"ok","service":"portfolio-hub","version":"0.1.0","checked_at":"2026-05-12T14:18:30.613518+09:00"}` - - Startup Log Fields: - - 외부 URL만으로는 startup 로그 접근 불가. 배포 환경 로그 접근 권한 필요. - - Request Log Fields: - - 외부 URL만으로는 request 로그 접근 불가. 배포 환경 로그 접근 권한 필요. - - Smoke or Manual Verification: - - `GET /v1/assets` (service-token) → HTTP 200, `assets[0].asset_id = "435b8e89-afc1-4a97-a658-f0e69f46d247"` UUID 형식 확인. - - `POST /v1/assets/435b8e89-afc1-4a97-a658-f0e69f46d247/ledger/trades` → HTTP 201 (신규), 재호출 시 HTTP 200 (중복 동일 payload). UUID asset_id 경로 정상 처리 확인. - - `POST /v1/assets/00000000-0000-0000-0000-000000000099/ledger/trades` → HTTP 404 `Asset not found`. 없는 UUID 경로 처리 확인. - - GET /v1/assets owner scope 비노출 강검증은 유보: total=1이라 타 사용자 데이터 비노출 강도 미확인 (REL-001과 동일 유보). - - trex-basic 측 UUID 파싱 확인은 외부 팀 조율 사항으로 유보 처리하여 released 전환. - -### Rollback Outcome - -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - released -- Rationale: - - BAT-006 전체 release-candidate. REQ-016/017/018/019 모두 Implemented. migration 0015는 revision 2e1298a 배포 시 이미 적용됨. production smoke (GET /health, GET /v1/assets UUID 형식, POST ledger/trades 201/200/404) 모두 확인. CLI evidence: 130 passed. owner scope 비노출 강검증과 trex-basic UUID 파싱 확인은 유보하여 released 전환. -- Recorded By: - - repository-maintainers -- Recorded At: - - 2026-05-12 - -## Feedback Handoff - -- Observation Summary: - - none -- Discovery Input: - - trex-basic 측 asset_id UUID 전환 후 runtime_id 재조회 절차 운영화 필요 여부 검토 -- REQ Input: - - none -- Change Request Input: - - none diff --git a/docs/releases/rel-005_20260512_production-operator-runbook-and-credential-matrix.md b/docs/releases/rel-005_20260512_production-operator-runbook-and-credential-matrix.md deleted file mode 100644 index 7509678..0000000 --- a/docs/releases/rel-005_20260512_production-operator-runbook-and-credential-matrix.md +++ /dev/null @@ -1,83 +0,0 @@ -# REL-005: Production Operator Runbook and Credential Matrix - -- Status: confirmed -- Profile: docs-only -- Included Batch: BAT-009 - -## Scope -- Included: - - BAT-009 (REQ-020, REQ-021, REQ-022) — README.md `Production Operator Runbook` 섹션 보강 - - Section 2: deployment secret custodian / production operator·release approver 역할 레이블 추가 - - Section 3 (신규): public/read/trade-write/admin 4개 credential matrix 테이블 - - Section 6 (구 Section 5 확장): canonical smoke 환경 명시, 401/403/404 구분 판독, release evidence 기록 필드 -- Excluded: - - 코드·스키마·런타임 변경 없음 - -## Rollout Plan -1. 변경 내용이 README.md 단독이므로 배포 절차 없음 — git push로 문서 반영 -2. README.md 변경 사항이 main 브랜치에 병합되었는지 확인 - -## Rollback Plan -- 문서 전용 변경이므로 서비스 영향 없음 -- 롤백 필요 시 해당 커밋 revert로 README.md 이전 상태 복원 - -## Verification Checklist -- [x] README.md Section 2 — deployment secret custodian / production operator·release approver 역할 레이블 존재 확인 -- [x] README.md Section 3 — credential matrix 테이블 4개 행(public/read/trade-write/admin) 존재 확인 -- [x] README.md Section 6 — canonical smoke 환경 문장, 401/403/404 구분 판독, release evidence 기록 필드 존재 확인 -- [x] README.md Section 1 — bootstrap/steady-state/break-glass 3-tier 경로 구분 존재 확인 -- [x] README.md 링크 및 섹션 번호 연속성 이상 없음 확인 (Section 1~6 순서 정상) - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - <배포 시작 시각> -- Completed At: - - <배포 완료 시각> -- Environment: - - docs (README.md 단독 변경, 서비스 배포 없음) -- Operator: - - <배포 수행자> -- Revision or Artifact: - - <커밋 SHA> -- Commands or Procedure: - - git push origin main (README.md 변경 반영) - -### Profile-Specific Checks -- docs-only: - - Published or Packaged Targets: - - README.md (Production Operator Runbook Section 2, 3, 6 변경) - - Link or Rendering Check: - - README.md Section 1~6 번호 연속성 정상 (grep -n "^### [0-9]\." 확인). 섹션 간 상호 참조 이상 없음 ✓ - - Navigation or Consumer Update: - - Section 3(신규)으로 인해 기존 Section 3→4, 4→5, 5→6 번호 이동. README.md 내부 참조 문장 없음(외부 링크 없음). 이상 없음 ✓ - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - -- Rationale: - - -- Recorded By: - - <기록자> -- Recorded At: - - <기록 시각> - -## Feedback Handoff -- Observation Summary: - - <운영 관찰 사실 요약 또는 없음> -- Discovery Input: - - `/auth/login` 엔드포인트 미구현 — steady-state canonical path(REQ-020 AC-4)의 실제 smoke 재현은 해당 엔드포인트 구현 후 가능. 후속 Discovery 후보. -- REQ Input: - - 없음 -- Change Request Input: - - 없음 diff --git a/docs/releases/rel-006_20260513_daemon-model-implementation.md b/docs/releases/rel-006_20260513_daemon-model-implementation.md deleted file mode 100644 index 37c6a31..0000000 --- a/docs/releases/rel-006_20260513_daemon-model-implementation.md +++ /dev/null @@ -1,134 +0,0 @@ -# REL-006: Daemon Model Implementation - -- Status: draft -- Profile: app-service -- Included Batch: BAT-010 - -## Scope -- Included: - - BAT-010 (REQ-009/023/005/012/006/007/008/011/028): 데몬 모델 전체 구현 - - Alembic migration 20260513_0016: daemons 테이블, auth_tokens.daemon_id, portfolios.managed_by_daemon_id 추가 - - Daemon registry CRUD: `pfhub daemon create/list/update` - - Service-token 발급 시 daemon_id binding (`--daemon-id` 옵션) - - Service-token rotate daemon_id 불변식 (runtime_id만 갱신, daemon_id 유지) - - GET /v1/assets daemon-scoped 필터 + NULL daemon_id token → 403 fail-closed - - GET /v1/user_info daemon_id 필드 추가 + NULL → 200+null diagnostic - - POST /v1/assets/{asset_id}/ledger/trades daemon assignment authorization (3단계 fail-closed) - - docs/interface-contract.md, docs/data-model.md, docs/runtime-flows.md daemon-aware 갱신 - - README.md daemon provisioning runbook + enforcement enablement gate + runtime_id/daemon_id 역할 구분 -- Excluded: - - CLI `pfhub portfolio update --daemon-id` (portfolio에 daemon 직접 할당 CLI 경로 — 향후 배치) - - pre-existing test_cli.py 7개 실패 수정 (별도 배치) - - 실제 TimescaleDB 인스턴스 integration test (환경 미구성 — 배포 시 수행) - -## Rollout Plan - -1. **사전 준비**: 아래 enforcement gate 4종 evidence 확보 - - NULL daemon_id 서비스 토큰 inventory 쿼리 실행 및 zero-active-NULL 확인 - - Dry-run: daemon mismatch 쿼리 실행 및 zero-mismatch 확인 - - Release approver 사전 승인 -2. **DB migration 적용**: `PORTFOLIOHUB_DB_ENABLED=true pfhub migrate-db` 실행 (migration 20260513_0016 → daemons table, daemon_id 컬럼 추가) -3. **Service 배포**: portfoliohub 서비스 재시작 (새 바이너리/코드 배포) - - 주의: NULL daemon_id token은 GET /v1/assets + POST ledger/trades에서 즉시 403 반환 시작 - - 기존 service-token이 있다면 배포 전 daemon 등록 + token rotate 또는 신규 발급 완료 후 배포 -4. **Health 확인**: `GET /health` → `{"status": "ok"}` 확인 -5. **Daemon provisioning smoke**: `pfhub daemon create` → `pfhub service-token issue --daemon-id` → GET /v1/assets 정상 응답 확인 - -## Rollback Plan - -- **Migration rollback**: `alembic downgrade 20260511_0015` - - 조건: migrate-db 실패 또는 daemons 테이블 생성 오류 - - 영향: daemons 테이블 제거, auth_tokens.daemon_id + portfolios.managed_by_daemon_id 컬럼 제거 - - 주의: daemon_id 컬럼은 nullable이므로 downgrade 전 기존 데이터 손실 여부 확인 -- **Service rollback**: 이전 커밋으로 서비스 롤백 - - 조건: 배포 후 health endpoint 실패 또는 v1 API 연쇄 403 발생 (non-NULL token 포함) - - 이전 서비스는 daemon_id 컬럼을 무시하므로 DB 롤백 없이 서비스만 롤백 가능 -- **데이터 롤백 불필요 조건**: NULL backfill 상태 유지 중(daemon 미등록)이면 컬럼만 제거하면 됨 - -## Verification Checklist - -> Local validation snapshot (KST 2026-05-14 08:31): `docker-compose.local-test.yml` 기반 TimescaleDB와 로컬 app process smoke로 확인했다. 아래 체크는 actual deploy evidence가 아니라 local pre-release validation 결과다. -> -> 참고: migration 20260513_0016은 local test DB에 online apply로 검증했다. offline `alembic --sql` dry-run은 migration 내부의 `inspect(op.get_bind())` 사용 때문에 현재 그대로는 성립하지 않는다. - -### 배포 전 -- [x] migration 20260513_0016이 local test DB(target DB 대체 검증)에 online 적용 가능한지 확인 (`pfhub migrate-db` → head, `alembic history` 정상) -- [x] NULL daemon_id service-token inventory 확인 (`SELECT COUNT(*) FROM auth_tokens WHERE daemon_id IS NULL AND token_type='service' AND revoked_at IS NULL` → `0`) -- [x] daemon mismatch dry-run 확인 (`SELECT COUNT(*) FROM portfolios WHERE managed_by_daemon_id IS NOT NULL` → `0`) -- [x] `pytest tests/` 결과 확인 (`128 passed / 7 failed`, pre-existing CLI 실패와 일치) -- [ ] release approver 승인 확보 - -### 배포 후 -- [x] `GET /health` → HTTP 200, `{"status": "ok", "service": "portfolio-hub"}` - - local app process smoke 결과: `status=ok`, `service=portfolio-hub`, `version=0.1.0` -- [x] `pfhub_DB_ENABLED=true pfhub health-check` → exit code 0 -- [x] `pfhub daemon create alpha-bot --user ` → daemon_id 반환 확인 - - local CLI plain output에서 `Daemon(id=...)` 확인. 단, `--format json`은 datetime serialization bug로 실패한다. -- [x] `pfhub service-token issue test-daemon-token --user --daemon-id --no-expiry` → token 발급 확인 - - local CLI JSON output에서 `token_id`, `runtime_id`, `daemon_id` 반환 확인. raw token은 의도적으로 마스킹된다. -- [x] `GET /v1/assets` (daemon-bound token) → HTTP 200, daemon 소유 asset만 포함 - - local smoke fixture에서 매칭 daemon portfolio 1건만 반환되고 다른 daemon portfolio는 비노출 확인 -- [x] `GET /v1/assets` (NULL daemon_id token) → HTTP 403 확인 - - 응답 detail: `daemon_id not assigned to this token` -- [x] `GET /v1/user_info` (daemon-bound token) → HTTP 200, `daemon_id` 필드 포함 -- [ ] `pfhub service-token rotate test-daemon-token --force` → 새 runtime_id, 동일 daemon_id 확인 - - local CLI 검증 실패: `idx_auth_tokens_service_name_unique` 제약으로 rotate insert가 막힌다. -- [ ] journalctl/Caddy access log에 ERROR 없음 확인 - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - <배포 시작 시각> -- Completed At: - - <배포 완료 시각> -- Environment: - - <배포 대상 환경> -- Operator: - - <배포 수행자> -- Revision or Artifact: - - <커밋 SHA 또는 배포 artifact 식별자> -- Commands or Procedure: - - <실행 명령 또는 배포 절차 요약> - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - <호출 결과와 확인 시각> - - Startup Log Fields: - - - - Request Log Fields: - - - - Smoke or Manual Verification: - - <배포 후 재실행한 검증과 결과> - -### Rollback Outcome -- Rollback Required: - - -- Rollback Trigger: - - <발생 시 원인, 없으면 none> -- Rollback Action: - - <수행한 조치, 없으면 none> - -### Release Decision Evidence -- Released Decision: - - -- Rationale: - - -- Recorded By: - - <기록자> -- Recorded At: - - <기록 시각> - -## Feedback Handoff -- Observation Summary: - - <운영 관찰 사실 요약 또는 없음> -- Discovery Input: - - NULL daemon_id token을 보유한 기존 service-token의 마이그레이션 경로 (rotate + daemon-id rebinding 자동화 후보) - - CLI `pfhub portfolio update --daemon-id` 추가 검토 (현재 DB 직접 수정 필요) -- REQ Input: - - portfolio에 daemon을 CLI로 할당/해제하는 명령 (managed_by_daemon_id 업데이트) - - daemon deactivation 시 bound token 자동 revoke 정책 -- Change Request Input: - - pre-existing test_cli.py 7개 실패 수정 (별도 CHG 또는 신규 REQ) diff --git a/docs/srs/Data/req-001_tags-master-table.md b/docs/srs/Data/req-001_tags-master-table.md new file mode 100644 index 0000000..2523000 --- /dev/null +++ b/docs/srs/Data/req-001_tags-master-table.md @@ -0,0 +1,74 @@ +# REQ-001: tags 마스터 테이블 도입 + +- Status: Proposed +- Type: Data +- Priority: High +- Owner: Justin Ko + +## Intent +- 태그를 자유 문자열이 아닌 정규화된 마스터 테이블로 관리하여 소유권(created_by_user_id), admin 구분(is_admin_tag), 생명주기(is_active)를 추적할 수 있는 기반을 마련한다. +- is_admin_tag는 태그 생성 시점의 권한을 스냅샷으로 기록하며 이후 권한 변경과 무관하게 고정된다. +- 태그 삭제는 is_active = FALSE soft delete로만 수행하여 기존 참조가 archived 상태임을 확인할 수 있게 한다. + +## Requirement +- 시스템은 다음 스키마로 `tags` 테이블을 생성해야 한다: `(tag_id UUID PK DEFAULT gen_random_uuid(), name TEXT NOT NULL, created_by_user_id TEXT NOT NULL REFERENCES users(id), is_admin_tag BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()), UNIQUE(name)` +- 태그 생성 시 요청자가 admin 권한(Permission.ADMIN)이면 is_admin_tag = TRUE로 기록하며 이후 이 값을 변경하지 않는다. +- 태그 비활성화는 is_active = FALSE로만 수행하며 hard DELETE는 사용하지 않는다. + +## Acceptance Criteria +1. Given Alembic 마이그레이션이 적용된 DB, When `tags` 테이블 스키마를 조회, Then tag_id (UUID NOT NULL PK), name (TEXT NOT NULL UNIQUE), created_by_user_id (TEXT NOT NULL REFERENCES users.id), is_admin_tag (BOOLEAN NOT NULL DEFAULT FALSE), is_active (BOOLEAN NOT NULL DEFAULT TRUE), created_at (TIMESTAMPTZ NOT NULL DEFAULT NOW()) 컬럼이 모두 존재한다. +2. Given admin 권한 토큰으로 태그 생성 요청, When 태그 행이 INSERT, Then is_admin_tag = TRUE이고 이후 권한이 변경되어도 tags.is_admin_tag는 변경되지 않는다. +3. Given 일반(non-admin) 사용자 토큰으로 태그 생성 요청, When 태그 행이 INSERT, Then is_admin_tag = FALSE이다. +4. Given 활성화된 태그, When 비활성화 요청, Then is_active = FALSE로 설정되며 해당 행은 DB에서 삭제되지 않는다. +5. Given is_active = FALSE인 태그, When 태그를 조회, Then 행이 반환되며 비활성 상태를 확인할 수 있다. + +## Impacted Area +- Docs: docs/data-model.md (tags 테이블 추가) +- Modules: + - migrations/versions/ — tags 테이블 생성 Alembic revision + - app/domain/models.py — Tag 도메인 모델 추가 + - app/repositories/portfolio_repository.py — tags CRUD 메서드 프로토콜 추가 + - app/repositories/postgres.py — tags INSERT/SELECT/UPDATE SQL 구현 +- Tests: tags 테이블 스키마 검증, is_admin_tag 스냅샷 동작 검증 + +## Notes +- created_by_user_id는 REFERENCES users(id)로 외래키를 가진다. users 테이블은 기존에 존재한다 (migration 0004). +- is_admin_tag 판단은 런타임 JOIN 없이 tags 테이블 컬럼만으로 수행된다 (dcy-002 A-1). +- name UNIQUE 제약은 TEXT 수준에서 적용되며, 소문자 정규화는 애플리케이션 레이어(REQ-004)에서 수행한다. +- Source: dcy-002 FR-1, NFR-1(부분), OQ-4 + +## Change Log + +### CHG-20260519-01 +- Date: 2026-05-19 +- Author: Justin Ko + +#### Change Summary +- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성 + +#### Intent +- dcy-002 FR-1을 구현 가능한 REQ 단위로 정규화 + +#### Acceptance Criteria Delta +- Added: + - AC-1~5 최초 작성 + +#### Impacted Area Delta +- Docs: + - docs/data-model.md — tags 테이블 추가 +- Modules: + - migrations/versions/, app/domain/models.py, 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 계획 편입 diff --git a/docs/srs/Data/req-002_assets-tag-ids-column.md b/docs/srs/Data/req-002_assets-tag-ids-column.md new file mode 100644 index 0000000..de3e850 --- /dev/null +++ b/docs/srs/Data/req-002_assets-tag-ids-column.md @@ -0,0 +1,73 @@ +# REQ-002: assets.tag_ids UUID[] 컬럼 추가 + +- Status: Proposed +- Type: Data +- Priority: High +- Owner: Justin Ko + +## Intent +- assets(portfolios) 테이블에 UUID[] 배열 컬럼을 추가하여 junction 테이블 없이 tags 마스터 테이블 기반 태그 참조를 저장한다. +- GIN 인덱스로 배열 조회 성능을 확보하고, 비활성화된 태그는 조용히 제거하지 않고 archived 상태로 반환하여 이력 추적을 가능하게 한다. +- 태그 적용 정책 검증(is_admin_tag=TRUE AND is_active=TRUE 확인, 403 반환)은 REQ-005에서 구현한다. + +## Requirement +- portfolios 테이블(asset에 해당)에 `tag_ids UUID[] NOT NULL DEFAULT '{}'` 컬럼을 추가해야 한다. +- portfolios.tag_ids에 GIN 인덱스를 생성해야 한다. +- tag_ids 배열에 is_active = FALSE인 tag_id가 포함된 경우, 해당 항목을 조용히 제거하지 않고 archived 상태임을 명시하여 반환해야 한다. + +## Acceptance Criteria +1. Given Alembic 마이그레이션이 적용된 DB, When portfolios 테이블 스키마를 조회, Then tag_ids UUID[] NOT NULL DEFAULT '{}' 컬럼이 존재한다. +2. Given 마이그레이션이 적용된 DB, When portfolios.tag_ids 인덱스를 조회, Then GIN 인덱스가 존재한다. +3. Given asset의 tag_ids 배열에 is_active = FALSE인 tag_id가 포함, When 해당 asset의 태그 목록을 조회, Then 비활성 태그가 archived 표시와 함께 반환되며 배열에서 자동 제거되지 않는다. +4. Given tag_ids = '{}' 기본값인 asset, When 태그가 적용되지 않은 상태로 조회, Then 빈 배열이 반환된다. + +## Impacted Area +- Docs: docs/data-model.md (portfolios 스키마에 tag_ids 컬럼 추가) +- Modules: + - migrations/versions/ — portfolios.tag_ids 컬럼 추가 및 GIN 인덱스 생성 Alembic revision + - app/domain/models.py — Portfolio 도메인 모델에 tag_ids 필드 추가 + - app/repositories/postgres.py — tag_ids 배열 조회 시 archived 표시 로직 + - app/schemas/v1.py — AssetItem에 tag_ids 필드 추가 +- Tests: portfolios 테이블 스키마 검증, GIN 인덱스 존재 확인, archived 표시 동작 확인 + +## Notes +- dcy-001의 assets 테이블(portfolios)이 확정된 이후 이 REQ를 구현해야 한다 (dcy-002 R-2). +- 배열 참조 무결성은 애플리케이션 레이어에서 보장한다 (dcy-002 NFR-2). PostgreSQL ARRAY FK는 미사용. +- portfolios 테이블이 v1 API의 "asset" 개념에 해당한다 (portfolio_id = asset_id in v1 API). +- Source: dcy-002 FR-2, NFR-2 + +## Change Log + +### CHG-20260519-01 +- Date: 2026-05-19 +- Author: Justin Ko + +#### Change Summary +- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성 + +#### Intent +- dcy-002 FR-2의 스키마 변경 부분을 독립 구현 단위로 분리 + +#### Acceptance Criteria Delta +- Added: + - AC-1~4 최초 작성 + +#### Impacted Area Delta +- Docs: + - docs/data-model.md — tag_ids 컬럼 추가 +- Modules: + - migrations/versions/, app/domain/models.py, app/repositories/postgres.py, app/schemas/v1.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 계획 편입 diff --git a/docs/srs/Data/req-003_portfolios-tag-ids-and-portfolio-tags-removal.md b/docs/srs/Data/req-003_portfolios-tag-ids-and-portfolio-tags-removal.md new file mode 100644 index 0000000..518b352 --- /dev/null +++ b/docs/srs/Data/req-003_portfolios-tag-ids-and-portfolio-tags-removal.md @@ -0,0 +1,73 @@ +# REQ-003: portfolios.tag_ids UUID[] 컬럼 추가 및 portfolio_tags 테이블 제거 + +- Status: Proposed +- Type: Data +- Priority: High +- Owner: Justin Ko + +## Intent +- portfolios 테이블에 UUID[] 배열 컬럼을 추가하고 기존 자유 문자열 기반 portfolio_tags 테이블을 제거하여 tags 마스터 테이블 기반 태그 참조 구조로 전환한다. +- 이 REQ는 스키마 변경에만 집중하며, 기존 portfolio_tags 데이터 이전 절차는 REQ-006에서 다룬다. +- GIN 인덱스로 배열 조회 성능을 확보하고, 비활성 태그는 archived로 표시한다. + +## Requirement +- portfolios 테이블에 `tag_ids UUID[] NOT NULL DEFAULT '{}'` 컬럼을 추가해야 한다. +- portfolios.tag_ids에 GIN 인덱스를 생성해야 한다. +- REQ-006 데이터 마이그레이션 완료 후 portfolio_tags 테이블을 DROP해야 한다. +- portfolios.tag_ids에 is_active = FALSE인 tag_id가 포함된 경우, archived 상태임을 명시하여 반환해야 하며 자동 제거하지 않는다. + +## Acceptance Criteria +1. Given Alembic 마이그레이션(REQ-006 포함)이 적용된 DB, When portfolios 테이블 스키마를 조회, Then tag_ids UUID[] NOT NULL DEFAULT '{}' 컬럼이 존재한다. +2. Given 마이그레이션이 적용된 DB, When portfolios.tag_ids 인덱스를 조회, Then GIN 인덱스가 존재한다. +3. Given 마이그레이션이 적용된 DB, When DB 스키마를 조회, Then portfolio_tags 테이블이 존재하지 않는다. +4. Given portfolio의 tag_ids 배열에 is_active = FALSE인 tag_id가 포함, When 해당 portfolio의 태그 목록을 조회, Then 비활성 태그가 archived 표시와 함께 반환되며 배열에서 자동 제거되지 않는다. + +## Impacted Area +- Docs: docs/data-model.md (portfolios 스키마에 tag_ids 추가, portfolio_tags 테이블 제거 반영) +- Modules: + - migrations/versions/ — portfolios.tag_ids 컬럼 추가, GIN 인덱스 생성, portfolio_tags DROP Alembic revision + - app/domain/models.py — Portfolio 도메인 모델에 tag_ids 필드 추가 + - app/repositories/postgres.py — portfolio tag_ids 배열 조작 메서드, archived 표시 로직 + - app/schemas/ — PortfolioItem에 tag_ids 필드 추가 (해당 스키마 존재 시) +- Tests: portfolios 테이블 스키마 검증, portfolio_tags 테이블 부재 확인, GIN 인덱스 존재 확인, archived 표시 동작 + +## Notes +- REQ-006(portfolio_tags 데이터 마이그레이션)이 선행되어야 한다. portfolio_tags DROP은 데이터 마이그레이션 완료 후 수행. +- 기존 `pfhub portfolio tag add/list/remove` 명령어는 portfolio_tags 테이블에 의존하므로 이 REQ와 함께 비활성화/제거한다 (REQ-007 참조). +- Source: dcy-002 FR-3, NFR-2 + +## Change Log + +### CHG-20260519-01 +- Date: 2026-05-19 +- Author: Justin Ko + +#### Change Summary +- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성 + +#### Intent +- dcy-002 FR-3의 스키마 변경을 독립 REQ 단위로 분리 + +#### Acceptance Criteria Delta +- Added: + - AC-1~4 최초 작성 + +#### Impacted Area Delta +- Docs: + - docs/data-model.md — portfolio_tags 제거, tag_ids 추가 +- Modules: + - migrations/versions/, app/domain/models.py, app/repositories/postgres.py, app/schemas/ — 최초 지정 +- 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-006 선행 의존성 확인 diff --git a/docs/srs/Deployment/req-020_production-admin-bootstrap-and-operator-access-runbook.md b/docs/srs/Deployment/req-020_production-admin-bootstrap-and-operator-access-runbook.md deleted file mode 100644 index e960a1f..0000000 --- a/docs/srs/Deployment/req-020_production-admin-bootstrap-and-operator-access-runbook.md +++ /dev/null @@ -1,109 +0,0 @@ -# req-020: 실배포 admin bootstrap 및 steady-state operator access runbook - -- Status: Approved -- Type: Deployment -- Priority: High -- Owner: repository-maintainers - -## Intent -- 실배포 환경에서 admin auth context를 확보하는 경로가 문서화되지 않으면, 운영자는 release smoke를 재현 가능하게 수행할 수 없고 bootstrap과 steady-state access를 혼동해 보안 사고가 발생할 수 있다. -- README.md Production Operator Runbook에 admin bootstrap(신규 환경/disaster recovery 전용), steady-state canonical access(운영자 개인 admin 로그인), break-glass fallback(env admin token) 3-tier 경로를 명시하고 각 경로의 사용 조건을 구분한다. - -## Requirement -- README.md Production Operator Runbook 섹션은 다음 3-tier 경로를 조건과 함께 구분하여 문서화해야 한다. - 1. **bootstrap path**: `PORTFOLIOHUB_BOOTSTRAP_*` 기반 초기 사용자 생성. `users` 테이블이 비어 있는 신규 환경 또는 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 허용. - 2. **steady-state canonical path**: 운영자 소유 admin 사용자 계정으로 `/auth/login`하여 DB-issued admin session 또는 동일 컨텍스트에서 발급한 client token 확보. 이 admin context로 `POST /auth/service-tokens`를 호출해 release smoke용 read/trade-write service-token을 발급. - 3. **break-glass fallback**: 환경 변수 기반 admin token을 `/opt/portfoliohub/.env`에서 꺼내는 경로. 운영자 로그인 경로가 모두 막혔을 때만 허용. 정기 smoke의 표준 경로로 사용 금지. -- 각 경로의 사용 조건, 운영 책임 역할(deployment secret custodian, production operator/release approver), secret 보관 위치(`/opt/portfoliohub/.env` / `EnvironmentFile`)를 명시한다. -- raw credential, raw token, bootstrap password는 README 또는 release 문서에 기록하지 않는다. - -## Acceptance Criteria -1. Given README.md Production Operator Runbook 섹션이 존재할 때, When 운영자가 섹션을 읽을 때, Then bootstrap path / steady-state canonical path / break-glass fallback이 각각 사용 조건과 함께 구분되어 기술되어 있어야 한다. -2. Given `users` 테이블이 비어 있지 않은 실배포 환경에서, When `PORTFOLIOHUB_BOOTSTRAP_*` 기반 bootstrap 로직이 실행될 때, Then 기존 사용자를 덮어쓰거나 중복 생성하지 않고 bootstrap을 건너뛰어야 한다 (idempotency 보장). -3. Given README runbook에 역할 책임이 명시되어 있을 때, When 운영자가 runbook을 참조할 때, Then deployment secret custodian과 production operator/release approver 역할이 구분되어 있고 secret 보관 위치(`/opt/portfoliohub/.env`)가 명시되어 있어야 한다. -4. Given 운영자가 steady-state canonical path를 따를 때, When `/auth/login` → `POST /auth/service-tokens` 순서로 실행할 때, Then admin session token 기반 service-token 발급 성공 경로가 runbook에 기술되어 있어야 한다. - -## Impacted Area -- Docs: `README.md` — Production Operator Runbook 섹션 (3-tier 경로 구분 및 역할 책임 보강) -- Modules: `app/core/config.py` (bootstrap 환경 변수 운영 제약 확인), `app/main.py` (bootstrap idempotency 동작 확인) -- Tests: bootstrap idempotency는 기존 구현 확인으로 대체 가능. 별도 신규 테스트 불필요. - -## Notes -- NFR-1 (raw credential 비노출): 이 REQ의 핵심 제약으로 포함됨. Release 문서, README 등 모든 운영 문서에서 raw value 기록 금지. -- NFR-3 (bootstrap idempotency): `app/main.py`에서 `users` 테이블이 비어 있을 때만 bootstrap 사용자를 생성하는 로직이 기존에 구현되어 있음. AC-2는 기존 구현 확인으로 검증한다. -- `/auth/login` 엔드포인트가 실제 구현되어 있어야 steady-state canonical path가 완성된다. 현재 저장소에 로그인 엔드포인트가 없으면 해당 AC는 구현 시 재검토가 필요하다. -- 이 REQ는 기능 추가보다 운영 절차 문서화가 주 목적이다. 코드 변경이 필요할 경우 최소 범위로 제한한다. -- Source Discovery: dcy-005 FR-1 - -## Change Log - -### CHG-20260512-02 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- Status: Proposed → Approved (confirm-req 게이트 통과) - -#### Intent -- 필수 필드, AC, Notes blocker 없음 확인 후 승인 전환 - -#### 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: none - -#### Validation Plan -- 승인 전환만 수행, 추가 검증 불필요 - -### CHG-20260512-01 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- REQ 초안 생성 (dcy-005 FR-1 기반) - -#### Intent -- 실배포 admin bootstrap 및 steady-state access 경로 운영 문서화 요구사항 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 초안 생성 - -#### Impacted Area Delta -- Docs: - - README.md Production Operator Runbook 섹션 -- Modules: - - app/core/config.py, app/main.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: none - -#### Validation Plan -- README.md Production Operator Runbook 섹션의 3-tier 경로 구분 확인 -- app/main.py bootstrap idempotency 코드 리뷰로 AC-2 검증 diff --git a/docs/srs/Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md b/docs/srs/Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md deleted file mode 100644 index 758c21c..0000000 --- a/docs/srs/Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md +++ /dev/null @@ -1,148 +0,0 @@ -# req-003: 로컬 DB 운영 runbook 및 baseline 문서 갱신 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: repository-maintainers - -## Intent -- 이 요구사항은 로컬 docker compose 기반 DB 검증 결과를 운영자가 반복 사용할 수 있는 runbook과 baseline 문서로 정리하기 위한 문서화 backlog를 정의한다. -- README, docs/runtime-flows.md, docs/project-structure.md에 로컬 DB 준비 절차, release gate 기준, rollback 또는 재시도 판단 기준을 반영하는 것이 목표다. - -## Requirement -- 프로젝트는 로컬 docker compose 기반 DB 검증 범위를 반영해 README, docs/runtime-flows.md, docs/project-structure.md를 갱신해야 하며, init-db와 migrate-db 실행 흐름, release gate 증거, rollback 또는 재시도 판단 기준을 운영자가 추적 가능한 runbook 형태로 정리해야 한다. - -## Acceptance Criteria -1. Given 로컬 DB validation 범위가 확정되어 있을 때, When README를 갱신하면, Then docker compose 기반 준비 절차, init-db와 migrate-db 실행 순서, health-check 및 smoke flow 검증 기준이 포함되어 있어야 한다. -2. Given Alembic와 runtime ownership 경계가 정리될 때, When docs/project-structure.md와 docs/runtime-flows.md를 갱신하면, Then 구조 제약과 실행 흐름 제약이 baseline 문서에 반영되어 있어야 한다. -3. Given 이번 Discovery의 release gate가 로컬 DB validation 기준으로 확정되어 있을 때, When 운영 runbook을 작성하면, Then rollback 또는 재시도 판단 기준과 남겨야 할 검증 로그가 문서에 명시되어 있어야 한다. - -## Impacted Area -- Docs: README.md, docs/project-structure.md, docs/runtime-flows.md, docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md -- Modules: app/cli/commands/health.py, app/db/migrations.py, app/repositories/postgres.py -- Tests: 문서 기반 수동 검증 체크리스트와 release gate evidence review 절차 - -## Notes -- 이 요구사항은 dcy-001의 FR-3 일부, FR-4, NFR-3, S-3을 구현 가능한 Documentation 단위로 정규화한 것이다. -- baseline 문서 전체를 복제하지 않고, 로컬 DB 검증과 직접 관련된 구조 제약과 흐름 제약만 업데이트 대상으로 삼는다. -- 성공 지표의 세부 artifact 위치는 REQ 승인 또는 batch planning에서 확정할 수 있다. - -## Change Log - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Implemented 전환 — BAT-007 verification passed, confirm-req-implemented 게이트 통과 - -#### Intent -- BAT-007 verification.md AC-1~3 모두 PASS. Blocking Issues 없음. README.md "Local DB Validation Runbook"(rollback 판단 기준, release gate criteria 포함), docs/project-structure.md "Schema Ownership Boundary", docs/runtime-flows.md 흐름 갱신 evidence 연결 확인 완료. - -#### Acceptance Criteria Delta -- none - -#### Impacted Area Delta -- none - -#### Delivery Trace Delta -- Batches: BAT-007 (release-candidate) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: implemented -- Follow-up Action: draft-release (BAT-007) - -#### Validation Plan -- BAT-007 verification.md AC-1~3 evidence 연결 확인 완료 - -### CHG-20260430-02 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- confirm-req 승인 게이트를 통과해 이 REQ를 Approved로 전환했다. - -#### Intent -- dcy-001의 delivery input으로 사용할 수 있는 확정 requirement로 승격하기 위해 상태를 변경했다. - -#### Acceptance Criteria Delta -- Added: - - none -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md -- Modules: - - none -- Tests: - - none - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: approved -- Follow-up Action: suggest-batch-reqs 또는 draft-batch 단계에서 REQ-003의 runbook 산출물 범위를 delivery scope로 연결한다. - -#### Validation Plan -- REQ 본문 필수 섹션, Acceptance Criteria 구체성, docs/srs/index.md 상태 동기화를 확인한다. - -### CHG-20260430-01 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- dcy-001에서 로컬 DB 운영 runbook 및 baseline 문서 갱신 요구사항을 신규 Proposed REQ로 생성했다. - -#### Intent -- 운영 준비 산출물과 baseline 문서 갱신 요구를 독립 Documentation backlog로 관리하기 위해 생성했다. - -#### Acceptance Criteria Delta -- Added: - - README 로컬 DB 검증 절차 갱신 기준 정의 - - docs/project-structure.md와 docs/runtime-flows.md baseline 갱신 기준 정의 - - rollback 또는 재시도 판단 기준과 로그 수집 기준 정의 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - README.md - - docs/project-structure.md - - docs/runtime-flows.md - - docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md -- Modules: - - app/cli/commands/health.py - - app/db/migrations.py - - app/repositories/postgres.py -- Tests: - - 문서 기반 수동 검증 체크리스트와 release gate evidence review 절차 - -#### 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 단계에서 baseline 문서 갱신 범위와 runbook 산출물 위치를 검토하고 승인한다. - -#### Validation Plan -- confirm-req 전까지 Acceptance Criteria가 dcy-001의 FR-3 일부, FR-4, NFR-3, S-3와 일치하는지 검토하고, 이후 batch 단계에서 README 및 baseline 문서 갱신안을 작성한다. \ No newline at end of file diff --git a/docs/srs/Documentation/req-008_tag-baseline-docs-update.md b/docs/srs/Documentation/req-008_tag-baseline-docs-update.md new file mode 100644 index 0000000..083b8ed --- /dev/null +++ b/docs/srs/Documentation/req-008_tag-baseline-docs-update.md @@ -0,0 +1,73 @@ +# REQ-008: 태그 모델 도입 baseline 문서 갱신 + +- Status: Proposed +- Type: Documentation +- Priority: Low +- Owner: Justin Ko + +## Intent +- dcy-002에서 정의한 tags 마스터 테이블, UUID[] 배열 방식, HTTP API, CLI 변경 내용을 project-structure, runtime-flows, data-model, interface-contract 문서에 반영하여 baseline을 최신 상태로 유지한다. +- 구현 완료 후 문서가 실제 코드와 일치하지 않으면 다음 Discovery/REQ 작성 시 잘못된 baseline 참조로 이어질 수 있으므로 구현 완료 시점에 함께 갱신한다. + +## Requirement +- docs/data-model.md에 tags 테이블 스키마, assets(portfolios).tag_ids UUID[] 컬럼, portfolio_tags 테이블 제거를 반영해야 한다. +- docs/interface-contract.md에 /v1/tags (POST/GET/PATCH), /v1/assets/{asset_id}/tags (POST/DELETE), /v1/portfolios/{portfolio_id}/tags (POST/DELETE) 엔드포인트 명세를 추가해야 한다. +- docs/project-structure.md에 pfhub tag 커맨드 그룹 추가, app/api/ 태그 모듈 추가, pfhub portfolio tag 제거를 반영해야 한다. +- docs/runtime-flows.md에 tag 관련 CLI 흐름(create, list, deactivate, apply, remove)과 HTTP 흐름을 추가해야 한다. + +## Acceptance Criteria +1. Given 구현 완료 후, When docs/data-model.md를 검토, Then tags 테이블 스키마(tag_id, name, created_by_user_id, is_admin_tag, is_active, created_at), portfolios.tag_ids UUID[] 컬럼, portfolio_tags 테이블 제거가 반영되어 있다. +2. Given 구현 완료 후, When docs/interface-contract.md를 검토, Then /v1/tags (POST/GET/PATCH) 및 /v1/assets/{id}/tags, /v1/portfolios/{id}/tags (POST/DELETE) 엔드포인트 명세가 포함되어 있다. +3. Given 구현 완료 후, When docs/project-structure.md를 검토, Then pfhub tag 커맨드 그룹이 CLI Command groups에 포함되고 app/api/ 섹션에 태그 API 모듈이 반영되어 있다. +4. Given 구현 완료 후, When docs/runtime-flows.md를 검토, Then pfhub tag 커맨드 실행 흐름과 /v1/tags 관련 HTTP 흐름이 문서화되어 있다. + +## Impacted Area +- Docs: + - docs/data-model.md + - docs/interface-contract.md + - docs/project-structure.md + - docs/runtime-flows.md +- Modules: 없음 (문서 전용) +- Tests: 없음 (문서 검토로 확인) + +## Notes +- 이 REQ는 REQ-001~007 구현 완료 후 마지막으로 수행한다. +- data-model.md와 interface-contract.md는 dcy-002 영향 범위에 명시된 문서이다. +- project-structure.md와 runtime-flows.md는 baseline 참조 문서로, 신규 CLI 커맨드 그룹과 API 흐름이 추가되면 함께 갱신해야 한다 (docs/project-structure.md Update Triggers: "새 top-level runtime 또는 domain 모듈이 추가되었을 때"). +- Source: dcy-002 영향 범위, skill 규칙 0(baseline 문서 갱신 REQ) + +## Change Log + +### CHG-20260519-01 +- Date: 2026-05-19 +- Author: Justin Ko + +#### Change Summary +- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성 + +#### Intent +- dcy-002 영향 범위의 문서 갱신 요구를 Documentation REQ로 분리; baseline 문서 갱신 의무 명시 + +#### Acceptance Criteria Delta +- Added: + - AC-1~4 최초 작성 + +#### Impacted Area Delta +- Docs: + - docs/data-model.md, docs/interface-contract.md, docs/project-structure.md, docs/runtime-flows.md — 최초 지정 +- 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: REQ-001~007 구현 완료 후 일괄 반영 diff --git a/docs/srs/Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md b/docs/srs/Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md deleted file mode 100644 index d726715..0000000 --- a/docs/srs/Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md +++ /dev/null @@ -1,176 +0,0 @@ -# req-011: trex-basic 계약 반영을 위한 baseline 문서 갱신 - -- Status: Approved -- Type: Documentation -- Priority: Medium -- Owner: repository-maintainers - -## Intent -- dcy-002 구현 완료 후 interface-contract.md, data-model.md, runtime-flows.md를 실제 구현 결과와 동기화한다. -- dcy-006의 daemon-aware 변경으로 baseline 문서는 runtime_id 중심 설명을 daemon binding/assignment 모델로 다시 정렬해야 한다. -- 이 REQ는 구현이 완료된 시점에 수행하는 문서 갱신이며, 구현 전 선행 작업이 아니다. - -## Requirement -- REQ-005~009, REQ-012의 daemon-aware 변경이 구현 완료(Implemented)된 시점에 다음 3개 baseline 문서를 갱신해야 한다. - 1. docs/interface-contract.md: `POST /auth/service-tokens`, `GET /v1/assets`, `GET /v1/user_info`, `POST /v1/assets/{asset_id}/ledger/trades`의 daemon binding / daemon-scoped contract, `403` mismatch 정책, service-token bearer 인증 방식을 반영 - 2. docs/data-model.md: `daemons` 테이블, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`, `auth_tokens.runtime_id`의 역할 분리와 transition `NULL` backfill 정책 반영 - 3. docs/runtime-flows.md: daemon registry binding, `/v1/*` daemon-scoped read/write 흐름, rotate 시 `daemon_id` 유지 + `runtime_id` 갱신 불변식 반영 - -## Acceptance Criteria -1. Given docs/interface-contract.md를 검토하면, Then `POST /auth/service-tokens`, `GET /v1/assets`, `GET /v1/user_info`, `POST /v1/assets/{asset_id}/ledger/trades`의 daemon-aware 요청/응답 명세와 `403` mismatch 정책이 포함되어야 한다. -2. Given docs/data-model.md를 검토하면, Then `daemons`, `broker_credentials`, `ledger_trades` 테이블과 `portfolios.market`, `portfolios.managed_by_daemon_id`, `auth_tokens.runtime_id`, `auth_tokens.daemon_id`가 문서화되어야 한다. -3. Given docs/runtime-flows.md를 검토하면, Then daemon registry binding 흐름, daemon-scoped v1 API 처리 흐름, rotate 시 `runtime_id`/`daemon_id` 역할 차이가 포함되어야 한다. - -## Impacted Area -- Docs: docs/interface-contract.md, docs/data-model.md, docs/runtime-flows.md -- Modules: 없음 -- Tests: 없음 - -## Notes -- 이 REQ는 구현 완료 후 수행하므로 batch 설계 단계에서 별도 implementation task로 명시한다. -- 문서 갱신 내용은 draft-batch-verification 단계에서 실제 구현과 비교해 검증한다. -- README.md 운영 provenance / release 절차 변경은 REQ-021 및 후속 dcy-006 REQ에서 다룬다. -- Source Discovery: dcy-002 (전체 FR/NFR 구현 완료 후 documentation closure), dcy-006 (FR-5, FR-6, NFR-1) - -## Change Log - -### CHG-20260513-01 -- Date: 2026-05-13 -- Author: repository-maintainers - -#### Change Summary -- baseline 문서 REQ를 daemon-aware v1 contract 반영 범위로 확장하고, 기존 Implemented 상태를 Approved로 되돌림 - -#### Intent -- dcy-006은 interface-contract, data-model, runtime-flows가 모두 daemon binding/assignment와 runtime_id/daemon_id 역할 분리를 반영하도록 요구한다. BAT-008/REL-002 문서 evidence는 runtime_id 중심 baseline만 다뤘으므로 revised REQ를 충족하지 못한다. - -#### Acceptance Criteria Delta -- Added: - - none -- Updated: - - AC-1~3: daemon-aware contract, data model, runtime flow 기준으로 재정의 -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - `docs/interface-contract.md`, `docs/data-model.md`, `docs/runtime-flows.md` -- Modules: - - none -- Tests: - - none (문서 리뷰 및 delivery verification evidence 중심) - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate; REL-002 released input) — 기존 verification evidence는 daemon-aware baseline을 포함하지 않아 재작성 필요 -- Releases: - - REL-002 (released) — released baseline docs scope가 revised REQ 대비 불완전 - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: yes -- Status Recommendation: rollback-to-approved -- Follow-up Action: dcy-006 후속 batch에서 daemon-aware baseline docs를 갱신하고 BAT-008에 대응하는 새 verification evidence를 작성한다. - -#### Validation Plan -- dcy-006 FR-5/FR-6와 현행 baseline 문서를 대조해 누락된 daemon contract 항목을 식별한다. -- 구현 후 문서 리뷰와 batch verification에서 interface/data/runtime 3축이 모두 갱신됐는지 확인한다. - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Implemented 전환 — BAT-008 verification passed, confirm-req-implemented 게이트 통과 - -#### Intent -- BAT-008 verification.md에서 REQ-011 AC-1~3의 문서화 기준과 evidence가 모두 PASS로 확인되어 Implemented로 승격한다. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- Docs: none -- Modules: none -- Tests: none - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: implemented -- Follow-up Action: draft-release 시 BAT-008 포함 여부 검토 - -#### Validation Plan -- BAT-008 verification.md의 REQ-011 AC-1~3 evidence와 Ready for Release: true 확인 - -### CHG-20260502-02 -- Date: 2026-05-02 -- Author: repository-maintainers - -#### Change Summary -- REQ-011 confirm-req 승인 전환 (Status: Proposed → Approved) - -#### Intent -- 필수 필드(Status, Type, Priority, Owner, Intent, Requirement, Acceptance Criteria 3개, 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. REQ-004~010 Implemented 전환 후 실행. - -### CHG-20260502-01 -- Date: 2026-05-02 -- Author: repository-maintainers - -#### Change Summary -- REQ-011 최초 생성 (source Discovery: dcy-002 문서화 클로저) - -#### Intent -- dcy-002 구현 완료 후 baseline 문서 갱신 요구사항을 독립 Documentation REQ로 정규화 - -#### Acceptance Criteria Delta -- Added: - - 전체 Acceptance Criteria 신규 작성 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md - - docs/data-model.md - - docs/runtime-flows.md -- 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: confirm-req 후 batch 배정. REQ-004~010 모두 Implemented 전환 후 이 REQ를 별도 구현 task로 수행. diff --git a/docs/srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md b/docs/srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md deleted file mode 100644 index aaf2c06..0000000 --- a/docs/srs/Documentation/req-019_portfolio-uuid-baseline-docs-update.md +++ /dev/null @@ -1,145 +0,0 @@ -# req-019: portfolio UUID 전환 관련 baseline 문서 갱신 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: repository-maintainers - -## Intent -- REQ-016~018의 구현으로 변경되는 DB 스키마, CLI 흐름, HTTP API 계약을 baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`, `docs/interface-contract.md`)에 반영해 문서와 구현 간 불일치를 방지한다. -- trex-basic 동시 배포 계획을 `docs/interface-contract.md`에 기록한다. - -## Requirement -- `docs/project-structure.md`를 갱신해 `app/repositories/postgres.py`에 신규 추가된 `get_portfolio_by_name_and_user()` 메서드와 `app/domain/models.py`의 `portfolio_id`(UUID 문자열) 의미 변경을 반영한다. -- `docs/runtime-flows.md`를 갱신해 아래를 반영한다. - - CLI 명령 흐름: `` + `--user ` 조합으로 portfolio를 식별하고 내부에서 UUID를 조회하는 흐름 - - `v1 API — GET /v1/assets` flow: `asset_id` 필드가 UUID를 반환함 -- `docs/data-model.md`를 갱신해 `portfolios` 테이블의 `portfolio_id`(UUID, TEXT, PRIMARY KEY), `name`(TEXT) 신규 컬럼, `(owner_user_id, name)` 복합 UNIQUE 제약을 반영한다. -- `docs/interface-contract.md`를 갱신해 `GET /v1/assets` `asset_id` 필드가 UUID를 반환함을 기록하고, trex-basic과 portfolio-hub의 동시 배포 계획을 명시한다. - -## Acceptance Criteria -1. Given `docs/project-structure.md`를 검토하면, Then `app/repositories/postgres.py` 설명에 `get_portfolio_by_name_and_user()` 메서드가 언급되고, `app/domain/models.py`의 `portfolio_id`가 UUID 기반임이 반영된다. -2. Given `docs/runtime-flows.md`를 검토하면, Then CLI 명령 흐름 설명에 `` + `--user ` 조합 식별 방식이 반영되고, `v1 API — GET /v1/assets` flow에 `asset_id`가 UUID임이 명시된다. -3. Given `docs/data-model.md`를 검토하면, Then `portfolios` 테이블 스키마에 `portfolio_id`(UUID TEXT, PK), `name`(TEXT), `(owner_user_id, name)` UNIQUE 제약이 반영된다. -4. Given `docs/interface-contract.md`를 검토하면, Then `GET /v1/assets` `asset_id` 필드가 UUID를 반환함이 명시되고, trex-basic과 portfolio-hub의 동시 배포 계획이 기록된다. - -## Impacted Area -- Docs: - - `docs/project-structure.md` - - `docs/runtime-flows.md` - - `docs/data-model.md` - - `docs/interface-contract.md` -- Modules: 없음 (문서 전용) -- Tests: 없음 (문서 전용) - -## Notes -- 이 REQ는 REQ-016, REQ-017, REQ-018 구현 완료 후 또는 동일 batch에서 함께 처리하는 것이 적절하다. -- trex-basic 측 코드 변경은 이번 범위 밖이며, 동시 배포 계획을 문서화하는 것만 이 REQ 범위에 포함된다. -- NFR-1(단일 revision, IF NOT EXISTS, atomic)은 REQ-016에 흡수. NFR-2(단일 DB 쿼리, 오류 메시지)는 REQ-017에 흡수. NFR-3(동시 배포 계획 기록)은 이 REQ에서 처리. -- Source Discovery: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution (FR-6, NFR-3) - -## Change Log - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- 최초 생성 — dcy-004 draft-req에 의해 생성 - -#### Intent -- dcy-004 Discovery의 FR-6(docs/project-structure.md, docs/runtime-flows.md 갱신), NFR-3(동시 배포 계획 docs/interface-contract.md 기록) 반영. docs/data-model.md, docs/interface-contract.md 갱신 포함. - -#### Acceptance Criteria Delta -- Added: - - 1~4 (최초 생성) - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md - - docs/runtime-flows.md - - docs/data-model.md - - docs/interface-contract.md - -#### 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 -- 각 baseline 문서에 변경 내용이 포함됐는지 코드 리뷰로 확인 - -### CHG-20260511-02 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Approved 전환 — confirm-req 게이트 통과 - -#### Intent -- Delivery 입력으로 사용할 수 있도록 REQ-019를 승인 상태로 승격한다. - -#### 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-019 AC-1~4 모두 baseline 문서 변경 내용 직접 확인으로 evidence 충족. 문서 전용 REQ로 테스트 불필요. 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~4: project-structure.md / runtime-flows.md / data-model.md / interface-contract.md 각 문서 변경 내용 manual 확인 완료 diff --git a/docs/srs/Documentation/req-021_release-credential-matrix-and-token-provenance-procedures.md b/docs/srs/Documentation/req-021_release-credential-matrix-and-token-provenance-procedures.md deleted file mode 100644 index 19dc2d2..0000000 --- a/docs/srs/Documentation/req-021_release-credential-matrix-and-token-provenance-procedures.md +++ /dev/null @@ -1,154 +0,0 @@ -# req-021: release 검증 credential matrix 및 token provenance 운영 절차 - -- Status: Approved -- Type: Documentation -- Priority: High -- Owner: repository-maintainers - -## Intent -- release 검증에 사용하는 credential의 용도, 발급 주체, 보관 위치, 폐기 절차가 정의되지 않으면 smoke 재현성이 없어지고 secret leakage 위험이 높아진다. -- public/no-auth, read service-token, trade-write service-token, admin auth context 4개 credential 관점의 matrix를 문서화하고, daemon-aware CLI/API를 token provenance 확인 canonical path로 명시한다. - -## Requirement -- README.md Production Operator Runbook 또는 연결된 운영 문서는 아래 credential matrix를 명시해야 한다. - - **public/no-auth**: `GET /health`, `/docs`, `/openapi.json`. 인증 불필요. 배포 직후 공개 smoke에 사용. - - **read service-token**: `GET /v1/assets`, `GET /v1/user_info`. 환경 단위 운영 credential. 발급: admin context로 `POST /auth/service-tokens` 후 existing `daemon_id`에 binding. 보관: `/opt/portfoliohub/.env` 또는 동일 접근 통제 하 운영 절차. 공동 소유자: release approver / repository-maintainers. - - **trade-write service-token**: `POST /v1/assets/{asset_id}/ledger/trades`. 환경 단위 운영 credential. 발급: admin context로 `POST /auth/service-tokens` 후 existing `daemon_id`에 binding. 보관: read token과 동일 정책. 공동 소유자: release approver / repository-maintainers. - - **admin auth context**: `POST /auth/service-tokens`. 운영자 개인 소유. 공유 금지. 발급: steady-state는 `/auth/login` 경유 DB-issued session, break-glass는 env token. -- release 문서에는 name, token_prefix, permission, `daemon_id`, `runtime_id`, issued_at, revoked_at만 기록하고 raw token값은 기록하지 않는다. -- 운영자는 `pfhub service-token list`, `pfhub service-token issue`, `pfhub service-token rotate`, `pfhub user get`, `GET /v1/user_info`를 token provenance 확인 canonical support path로 사용한다. 각 명령의 출력 필드(name, token_prefix, token_id, user_id, permission, `daemon_id`, created_at, last_used_at, revoked_at, `runtime_id`)를 provenance 확인 기준으로 명시한다. -- rotate 또는 재발급 이후에는 `daemon_id` 불변과 `runtime_id` 갱신 여부를 함께 대조해야 한다. - -## Acceptance Criteria -1. Given README.md 또는 연결된 운영 문서가 존재할 때, When 운영자가 credential matrix를 참조할 때, Then public/read/trade-write/admin 4개 credential의 용도, 발급 주체, 보관 위치, 공동 소유 역할과 daemon binding 전제가 구분되어 기술되어 있어야 한다. -2. Given release 문서(docs/releases/*.md)가 존재할 때, When smoke evidence를 기록할 때, Then name, token_prefix, permission, `daemon_id`, `runtime_id`, issued_at, revoked_at 필드만 남기고 raw token 값은 포함되지 않아야 한다. -3. Given 운영자가 service-token provenance를 확인할 때, When `pfhub service-token list`를 실행할 때, Then name, permission, `daemon_id`, created_at, last_used_at, revoked_at 필드가 출력되고, `GET /v1/user_info`의 `runtime_id`, `daemon_id`와 대조해 배포된 토큰의 provenance를 재현 가능하게 확인할 수 있어야 한다. -4. Given admin auth context가 필요한 smoke를 수행할 때, When 운영자가 credential matrix를 따를 때, Then admin context는 개인 소유로 취급되고 read/trade-write token은 release approver 역할과 공동 소유 형태로 관리되는 것이 문서에 명시되어 있어야 한다. -5. Given 운영자가 rotate 또는 재발급 후 provenance를 재확인할 때, When `pfhub service-token rotate` 또는 issue 결과를 검토할 때, Then `daemon_id`는 유지 또는 요청한 binding 값으로 확인되고 `runtime_id`는 새 값으로 대조 가능해야 한다. - -## Impacted Area -- Docs: `README.md` — credential matrix 항목 및 daemon-aware token provenance CLI 절차 추가/보강 -- Modules: `app/api/v1_user_info.py` (`runtime_id`, `daemon_id` 반환 확인), `app/cli/commands/service_tokens.py` (issue/list/rotate 출력 필드 확인) -- Tests: 없음 (문서화 중심 REQ) - -## Notes -- NFR-1 (raw credential 비노출): 이 REQ의 핵심 제약. AC-2에 명시. -- FR-4와 FR-2를 통합한 REQ. token provenance 확인 수단과 credential matrix는 운영자가 같은 맥락에서 참조하므로 분리 시 중복이 발생한다. -- `runtime_id`는 service-token rotate 후 변경되고 `daemon_id`는 유지되므로, 문서에는 두 식별자의 역할 차이를 함께 명시해야 한다. -- daemon provisioning 자체는 별도 관리 경로로 수행하고, provenance 절차는 binding 결과 확인에 집중한다. -- Source Discovery: dcy-005 FR-2, FR-4; dcy-006 FR-5, FR-6 - -## Change Log - -### CHG-20260513-01 -- Date: 2026-05-13 -- Author: repository-maintainers - -#### Change Summary -- token provenance REQ를 daemon-aware support path와 `daemon_id` evidence 필드 기준으로 보강 - -#### Intent -- dcy-006은 issue/list/rotate와 `/v1/user_info`가 모두 `daemon_id`를 노출하고, release provenance도 `runtime_id` 단독이 아니라 daemon binding을 함께 추적하도록 요구한다. 기존 Approved 본문은 runtime_id 중심이라 운영 절차 기준이 불완전하다. - -#### Acceptance Criteria Delta -- Added: - - AC-5: rotate/re-issue 후 `daemon_id`/`runtime_id` 대조 절차 -- Updated: - - AC-1~3: daemon binding 전제와 `daemon_id` evidence 필드 반영 -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - `README.md` credential matrix / provenance 절차 -- Modules: - - `app/api/v1_user_info.py`, `app/cli/commands/service_tokens.py` -- Tests: - - none (문서화 중심 REQ) - -#### Delivery Trace Delta -- Batches: - - BAT-009 (release-candidate; REL-005 confirmed input) — README provenance 절차와 release evidence field 정의를 daemon-aware 기준으로 재검토해야 함 -- Releases: - - REL-005 (confirmed) — 배포 전 daemon-aware provenance field와 rotate invariant를 반영하도록 scope 재검토 필요 - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: BAT-009 구현/verification 전에 README와 release evidence 템플릿을 daemon-aware 필드 기준으로 조정한다. - -#### Validation Plan -- README.md와 release evidence 예시가 `daemon_id`, `runtime_id`, rotate invariant를 모두 설명하는지 코드 리뷰로 확인한다. -- REQ-005, REQ-007, REQ-012 문구와 provenance 절차가 식별자 역할 측면에서 일관적인지 대조한다. - -### CHG-20260512-02 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- Status: Proposed → Approved (confirm-req 게이트 통과) - -#### Intent -- 필수 필드, AC, Notes blocker 없음 확인 후 승인 전환 - -#### 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: none - -#### Validation Plan -- 승인 전환만 수행, 추가 검증 불필요 - -### CHG-20260512-01 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- REQ 초안 생성 (dcy-005 FR-2, FR-4 통합 기반) - -#### Intent -- release 검증 credential matrix 및 token provenance 확인 절차 문서화 요구사항 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 초안 생성 - -#### Impacted Area Delta -- Docs: - - README.md credential matrix 및 token provenance 절차 -- 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: none - -#### Validation Plan -- README.md credential matrix 항목 구분 및 token provenance CLI 절차 존재 확인 -- release 문서 기록 정책 준수 여부 확인 diff --git a/docs/srs/Documentation/req-022_operator-log-access-and-release-evidence-recording.md b/docs/srs/Documentation/req-022_operator-log-access-and-release-evidence-recording.md deleted file mode 100644 index 17b74eb..0000000 --- a/docs/srs/Documentation/req-022_operator-log-access-and-release-evidence-recording.md +++ /dev/null @@ -1,114 +0,0 @@ -# req-022: 운영 로그 접근 및 release evidence 기록 절차 - -- Status: Approved -- Type: Documentation -- Priority: Medium -- Owner: repository-maintainers - -## Intent -- 운영자가 startup/request 로그 접근 경로와 auth/permission/resource 오류 판독 방법을 모르면 release smoke의 증적을 재현 가능하게 수집할 수 없고, 장애 시 원인 분류가 불가능해진다. -- systemd journal 기반 canonical log access 경로와 401/403/404 구분 판독 기준, release evidence 기록 필드를 README.md에 정의한다. - -## Requirement -- README.md Production Operator Runbook 섹션은 아래 로그 접근 및 evidence 기록 절차를 포함해야 한다. - 1. **애플리케이션 startup/runtime 로그**: `sudo journalctl -u portfoliohub -n 200 --no-pager` 또는 `-f` 옵션으로 실시간 확인. primary log path. - 2. **reverse proxy/access 로그**: `sudo journalctl -u caddy -n 200 --no-pager` 또는 `-f` 옵션. primary log path. - 3. **Loki**: `LOKI_ENABLED`와 실제 수집 파이프라인이 운영 환경에서 확인된 경우에만 보조 집계 경로로 취급. - 4. **auth/permission/resource 오류 구분**: - - 401: 인증 정보 없음 또는 만료 — bearer token 미제공 또는 유효하지 않은 token - - 403: 권한 부족 — token은 유효하나 해당 엔드포인트에 필요한 permission 미보유 - - 404: 리소스 없음 — 인증/권한은 통과했으나 대상 리소스가 DB에 존재하지 않음 - 5. **release evidence 기록 필드**: 시각(executed_at, completed_at), 요청 경로, HTTP 상태 코드, 소스 시스템(journalctl unit name), 상관 가능한 요청 식별자. raw secret, raw token 값은 포함하지 않는다. -- canonical acceptance smoke는 production에서 수행하고, staging smoke는 optional preflight로만 사용한다. -- 절차의 source of truth는 README runbook으로 유지하고, 실행 evidence의 canonical record는 해당 release 문서에 남긴다. - -## Acceptance Criteria -1. Given README.md Production Operator Runbook이 존재할 때, When 운영자가 로그 접근 절차를 참조할 때, Then `journalctl -u portfoliohub`와 `journalctl -u caddy`를 각각 어느 용도로 사용하는지가 명시되어 있어야 한다. -2. Given release smoke 중 HTTP 오류가 발생했을 때, When 운영자가 오류를 분류할 때, Then README runbook에 401(인증 없음), 403(권한 부족), 404(리소스 없음)의 의미가 분리되어 재현 가능하게 설명되어 있어야 한다. -3. Given release 문서에 evidence를 기록할 때, When smoke 실행 결과를 기재할 때, Then 시각, 요청 경로, HTTP 상태 코드, 소스 시스템 필드가 포함되고 raw secret은 포함되지 않아야 한다. -4. Given canonical acceptance 환경이 명시되어 있을 때, When 운영자가 smoke 환경을 선택할 때, Then production이 canonical acceptance이고 staging은 optional preflight임이 README runbook에 명시되어 있어야 한다. - -## Impacted Area -- Docs: `README.md` — Production Operator Runbook 로그 접근 절차 및 오류 구분 기준 보강 -- Modules: 없음 (문서화 중심 REQ) -- Tests: 없음 - -## Notes -- NFR-2 (401/403/404 구분 설명): AC-2에 흡수. -- NFR-1 (raw credential 비노출): AC-3에 흡수. -- 외부 URL만으로는 startup log 접근이 불가능하다는 제약을 README에 명시해야 한다. (REL-001 Deployment Facts 참조) -- Loki 관련 설정(`LOKI_ENABLED`, `LOKI_URL`)은 `app/core/config.py`에 존재하지만 현재 production에서 활성화가 확인되지 않으므로 기본 표준 경로로 다루지 않는다. -- Source Discovery: dcy-005 FR-3 - -## Change Log - -### CHG-20260512-02 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- Status: Proposed → Approved (confirm-req 게이트 통과) - -#### Intent -- 필수 필드, AC, Notes blocker 없음 확인 후 승인 전환 - -#### 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: none - -#### Validation Plan -- 승인 전환만 수행, 추가 검증 불필요 - -### CHG-20260512-01 -- Date: 2026-05-12 -- Author: repository-maintainers - -#### Change Summary -- REQ 초안 생성 (dcy-005 FR-3 기반) - -#### Intent -- 운영 로그 접근 및 release evidence 기록 절차 문서화 요구사항 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 초안 생성 - -#### Impacted Area Delta -- Docs: - - README.md 로그 접근 절차 및 오류 구분 기준 -- 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: none - -#### Validation Plan -- README.md journalctl 명령 존재 및 401/403/404 구분 기술 확인 -- release evidence 기록 필드 정책 준수 확인 diff --git a/docs/srs/Documentation/req-028_daemon-model-baseline-docs-update.md b/docs/srs/Documentation/req-028_daemon-model-baseline-docs-update.md deleted file mode 100644 index e56eea7..0000000 --- a/docs/srs/Documentation/req-028_daemon-model-baseline-docs-update.md +++ /dev/null @@ -1,98 +0,0 @@ -# REQ-028: README.md Daemon Provisioning Runbook 반영 - -- Status: Approved -- Type: Documentation -- Priority: Medium -- Owner: repository-maintainers - -## Intent -- interface-contract, data-model, runtime-flows의 daemon-aware 반영은 REQ-011 CHG-20260513-01에서 이미 다룬다. -- 이 REQ는 REQ-011이 다루지 않는 `README.md` 운영 runbook 영역에 한정해, daemon provisioning 절차와 enforcement enablement gate를 반영한다. - -## Requirement -- `README.md` Production Operator Runbook 섹션은 daemon provisioning 절차(daemon create → service-token issue/rotate binding 순서)를 반영해야 한다. -- `README.md`는 daemon-scoped enforcement enablement gate의 required evidence(inventory 완료, dry-run audit zero-active-NULL/zero-mismatch, smoke 검증 통과, release approver 승인)를 명시해야 한다. -- `README.md`는 `runtime_id`와 `daemon_id`의 역할 차이(runtime identity vs stable logical identity)를 운영 provenance 절차에서 구분해 설명해야 한다. - -## Acceptance Criteria -1. Given `README.md` Production Operator Runbook을 검토할 때, Then daemon create → service-token issue/rotate binding의 순서와 각 단계 조건이 명시되어 있다. -2. Given `README.md` enforcement enablement gate 섹션을 확인할 때, Then inventory 완료, dry-run audit(zero-active-NULL/zero-mismatch), smoke 검증, release approver 승인의 4가지 required evidence가 모두 명시되어 있다. -3. Given `README.md` 운영 provenance 절차를 확인할 때, Then `runtime_id`(rotate 시 갱신)와 `daemon_id`(rotate 후에도 유지)의 역할 차이가 구분되어 설명되어 있다. - -## Impacted Area -- Docs: `README.md` (Production Operator Runbook 섹션) -- Modules: 없음 -- Tests: 없음 (문서 검토로 검증) - -## Notes -- interface-contract.md, data-model.md, runtime-flows.md의 daemon 반영은 REQ-011 CHG-20260513-01 범위이므로 이 REQ에서 중복 요구하지 않는다. -- enforcement enablement gate의 실제 cutover evidence 수집은 release 단계에서 수행한다. 이 REQ는 절차 문서화만 대상으로 한다. - -## Change Log - -### 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 -- 최초 생성 시 REQ-011과 중복되는 interface-contract/data-model/runtime-flows 항목을 제거하고, README.md 운영 runbook 한정으로 범위 축소 - -#### Intent -- REQ-011이 다루지 않는 README.md daemon provisioning runbook과 enforcement gate 절차만 별도 REQ로 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~3 전체 신규 추가 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: `README.md` 한정 -- Modules: 없음 -- Tests: 없음 - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: 없음 (신규 REQ) - -#### Validation Plan -- README.md에서 AC별 확인 항목(daemon provisioning 순서, gate evidence 4종, runtime_id/daemon_id 역할 구분)이 존재하는지 코드 리뷰로 검증 diff --git a/docs/srs/Interface/req-004_health-response-schema-extension.md b/docs/srs/Interface/req-004_health-response-schema-extension.md deleted file mode 100644 index d6ed779..0000000 --- a/docs/srs/Interface/req-004_health-response-schema-extension.md +++ /dev/null @@ -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 배정 diff --git a/docs/srs/Interface/req-004_tags-http-api.md b/docs/srs/Interface/req-004_tags-http-api.md new file mode 100644 index 0000000..fdd0961 --- /dev/null +++ b/docs/srs/Interface/req-004_tags-http-api.md @@ -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 선행 의존성 확인 diff --git a/docs/srs/Interface/req-005_service-token-runtime-id-issuance.md b/docs/srs/Interface/req-005_service-token-runtime-id-issuance.md deleted file mode 100644 index 2497809..0000000 --- a/docs/srs/Interface/req-005_service-token-runtime-id-issuance.md +++ /dev/null @@ -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 --user --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 단위 구성 권장. diff --git a/docs/srs/Interface/req-005_tag-apply-remove-http-api.md b/docs/srs/Interface/req-005_tag-apply-remove-http-api.md new file mode 100644 index 0000000..7c05485 --- /dev/null +++ b/docs/srs/Interface/req-005_tag-apply-remove-http-api.md @@ -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 선행 의존성 확인 diff --git a/docs/srs/Interface/req-006_v1-assets-endpoint.md b/docs/srs/Interface/req-006_v1-assets-endpoint.md deleted file mode 100644 index f8fe04d..0000000 --- a/docs/srs/Interface/req-006_v1-assets-endpoint.md +++ /dev/null @@ -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 단위 구성 권장. diff --git a/docs/srs/Interface/req-007_pfhub-tag-cli.md b/docs/srs/Interface/req-007_pfhub-tag-cli.md new file mode 100644 index 0000000..731e631 --- /dev/null +++ b/docs/srs/Interface/req-007_pfhub-tag-cli.md @@ -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 ` — 태그 생성 (trade-write 이상 권한) +- `pfhub tag list [--inactive]` — 태그 목록; --inactive 없으면 활성 태그만, --inactive 플래그 시 비활성 태그 포함 전체 조회 +- `pfhub tag deactivate ` — 태그 비활성화; `--force` 확인 필요 (admin 또는 생성자 본인) +- `pfhub tag apply asset ` — asset에 태그 적용 (admin 권한) +- `pfhub tag apply portfolio ` — portfolio에 태그 적용 (trade-write 이상, 소유자 또는 admin) +- `pfhub tag remove asset ` — asset에서 태그 해제; `--force` 확인 필요 (admin) +- `pfhub tag remove portfolio ` — 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 --force`, Then 태그가 비활성화되고 `pfhub tag list --inactive`에서 비활성 상태로 확인된다. +5. Given admin 토큰, admin 생성 활성 tag_id, When `pfhub tag apply asset `, Then asset에 태그가 적용된다. +6. Given trade-write 토큰(소유자 또는 admin), When `pfhub tag apply portfolio ` (정책 충족 태그), Then portfolio에 태그가 적용된다. +7. Given admin 토큰, When `pfhub tag remove asset --force`, Then asset에서 태그가 제거된다. +8. Given 소유자 또는 admin 토큰, When `pfhub tag remove portfolio --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 선행 의존성 확인 diff --git a/docs/srs/Interface/req-007_v1-user-info-endpoint.md b/docs/srs/Interface/req-007_v1-user-info-endpoint.md deleted file mode 100644 index 2d0b4f5..0000000 --- a/docs/srs/Interface/req-007_v1-user-info-endpoint.md +++ /dev/null @@ -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 단위 구성 권장. diff --git a/docs/srs/Interface/req-008_v1-ledger-trades-endpoint.md b/docs/srs/Interface/req-008_v1-ledger-trades-endpoint.md deleted file mode 100644 index 5fd5f5b..0000000 --- a/docs/srs/Interface/req-008_v1-ledger-trades-endpoint.md +++ /dev/null @@ -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 단위 구성 필수. diff --git a/docs/srs/Interface/req-012_service-token-rotate-cli.md b/docs/srs/Interface/req-012_service-token-rotate-cli.md deleted file mode 100644 index 475e540..0000000 --- a/docs/srs/Interface/req-012_service-token-rotate-cli.md +++ /dev/null @@ -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 [--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 --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 테스트 회귀 없음으로 완료 판단 diff --git a/docs/srs/Interface/req-013_portfolio-list-user-required.md b/docs/srs/Interface/req-013_portfolio-list-user-required.md deleted file mode 100644 index 961e1b1..0000000 --- a/docs/srs/Interface/req-013_portfolio-list-user-required.md +++ /dev/null @@ -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 ` 옵션을 필수로 지정하여 CLI 실행 시 항상 특정 사용자의 포트폴리오만 조회하도록 강제한다. -- username을 `owner_user_id`(UUID)로 변환하는 로직을 CLI 내부에 추가하여, `PortfolioService.list_portfolios` 호출 시 소유자 필터가 항상 적용되게 한다. - -## Requirement -- CLI `pfhub portfolio list` 명령은 `--user ` 옵션을 필수로 받아야 한다. -- `--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 ` 옵션 추가 및 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 계획 수립 diff --git a/docs/srs/Interface/req-014_portfolio-create-user-required.md b/docs/srs/Interface/req-014_portfolio-create-user-required.md deleted file mode 100644 index ebd1745..0000000 --- a/docs/srs/Interface/req-014_portfolio-create-user-required.md +++ /dev/null @@ -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 ` 을 필수로 받아 실제 소유자의 `owner_user_id`(UUID)로 포트폴리오가 생성되도록 강제한다. -- REQ-013(`portfolio list --user` 필수화)과 대칭을 이루는 변경으로, 소유자 기반 포트폴리오 관리 정책을 CLI 전반에 일관되게 적용한다. - -## Requirement -- CLI `pfhub portfolio create` 명령은 `--user ` 옵션을 필수로 받아야 한다. -- `--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 --symbol X --force`를 실행할 때, When 명령이 실행되면, Then 오류 메시지가 출력되고 exit code 1로 종료된다. -2. Given 유효한 username `alice`로 `pfhub portfolio create --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 ` 옵션 추가 -- 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 진행 diff --git a/docs/srs/Interface/req-015_broker-credential-cli.md b/docs/srs/Interface/req-015_broker-credential-cli.md deleted file mode 100644 index 784dd6f..0000000 --- a/docs/srs/Interface/req-015_broker-credential-cli.md +++ /dev/null @@ -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 --broker --account-ref --app-key --app-secret ` 명령을 제공해야 한다. 이 명령은 `{"app_key": ..., "app_secret": ...}` JSON을 Fernet으로 암호화한 뒤 `broker_credentials` 테이블에 upsert한다. -- 시스템은 `pfhub credential list --user ` 명령을 제공해야 한다. 이 명령은 사용자의 브로커 인증 정보 목록을 반환하되, `encrypted_payload`는 출력하지 않는다. -- 시스템은 `pfhub credential remove --user --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` 전체 통과 확인 diff --git a/docs/srs/Interface/req-017_portfolio-cli-name-user-identification.md b/docs/srs/Interface/req-017_portfolio-cli-name-user-identification.md deleted file mode 100644 index f74abcf..0000000 --- a/docs/srs/Interface/req-017_portfolio-cli-name-user-identification.md +++ /dev/null @@ -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 뮤테이션 명령을 `` + `--user ` 조합으로 전환해 소유권 검증을 명령 실행 경로에 포함한다. -- CLI 내부에서 `(owner_user_id, name)` → UUID 단일 쿼리 조회 후 처리하고, 조회 실패 시 명확한 오류 메시지를 출력한다. - -## Requirement -- `portfolio create` 명령은 `name` Argument와 `--user ` 옵션을 필수로 받는다. `portfolio_id`는 서버가 UUID로 발급하며 사용자가 직접 지정하거나 조회할 수 없다. -- `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`, `update` 명령은 `` Argument와 `--user ` 옵션으로 portfolio를 식별한다. 내부에서 `(owner_user_id, name)` → `portfolio_id(UUID)`를 단일 DB 쿼리로 조회한 뒤 처리한다. -- `` + `--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 --user ...`을 실행하면, When 서버가 UUID를 발급하면, Then CLI가 portfolio_id를 직접 지정하거나 조회하지 않고 UUID가 DB에 저장된다. -2. Given `pfhub portfolio buy --user alice ...`를 실행하면, When `(alice, )` 조합으로 DB를 단일 쿼리로 조회해 UUID를 찾으면, Then 해당 portfolio에 buy 작업이 수행된다. -3. Given `--user bob`으로 alice 소유의 portfolio ``을 조작하면, When `(bob, )` 조합으로 조회해 레코드가 없으면, 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 --user `, `pfhub portfolio buy --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 전체 통과) diff --git a/docs/srs/Interface/req-018_portfolio-http-api-uuid-path-param.md b/docs/srs/Interface/req-018_portfolio-http-api-uuid-path-param.md deleted file mode 100644 index 054ed17..0000000 --- a/docs/srs/Interface/req-018_portfolio-http-api-uuid-path-param.md +++ /dev/null @@ -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 철회 주석 직접 확인 완료 diff --git a/docs/srs/Interface/req-023_daemon-registry-and-service-token-daemon-binding.md b/docs/srs/Interface/req-023_daemon-registry-and-service-token-daemon-binding.md deleted file mode 100644 index 78c0dfa..0000000 --- a/docs/srs/Interface/req-023_daemon-registry-and-service-token-daemon-binding.md +++ /dev/null @@ -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 --user --daemon-id `를 실행할 때, Then 해당 portfolio의 `managed_by_daemon_id`가 지정한 daemon_id로 갱신된다. -6. Given `pfhub portfolio list --user `을 실행할 때, 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 운영 표기: `/` - -## 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 --user --daemon-id `로 `managed_by_daemon_id` 갱신 - - AC-6: `pfhub portfolio list --user ` 결과에 `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 --user --daemon-id --force` 실행 후 DB `portfolios.managed_by_daemon_id` 값 확인 -- `pfhub portfolio list --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 시나리오 통과 확인 diff --git a/docs/srs/Interface/req-029_portfolio-list-borderless-output.md b/docs/srs/Interface/req-029_portfolio-list-borderless-output.md deleted file mode 100644 index 7640051..0000000 --- a/docs/srs/Interface/req-029_portfolio-list-borderless-output.md +++ /dev/null @@ -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 ` 명령의 기본 출력(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 ` 실행 시, 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 --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 ` 실행 결과에 Rich Table 헤더·테두리가 없는지 확인 -- daemon-id 있는 항목과 없는 항목 혼재 시 조건부 표시 확인 -- `--format json` 옵션 시 JSON 반환 확인 diff --git a/docs/srs/Migration/req-002_alembic-runtime-schema-ownership.md b/docs/srs/Migration/req-002_alembic-runtime-schema-ownership.md deleted file mode 100644 index 9b8055a..0000000 --- a/docs/srs/Migration/req-002_alembic-runtime-schema-ownership.md +++ /dev/null @@ -1,147 +0,0 @@ -# req-002: Alembic와 runtime schema ownership 경계 정리 - -- Status: Implemented -- Type: Migration -- Priority: High -- Owner: repository-maintainers - -## Intent -- 이 요구사항은 Alembic migration 경로와 PostgresPortfolioRepository.ensure_schema() 경로가 같은 schema 객체를 다룰 때 발생할 수 있는 drift와 재실행 실패를 줄이기 위한 경계 규칙을 정의한다. -- 로컬 docker compose 환경에서 fresh DB와 runtime-precreated schema 상태를 기준으로 허용 순서, 사전 점검, 보정 규칙을 문서화하는 것이 목표다. - -## Requirement -- 프로젝트는 로컬 docker compose 기반 DB 환경에서 Alembic migration과 runtime schema bootstrap이 다루는 테이블과 인덱스의 ownership 경계를 식별해야 하며, migrate-db 실행 시 허용되는 순서, 충돌 가능 지점, 사전 점검 규칙, 보정 원칙을 명시해야 한다. - -## Acceptance Criteria -1. Given app/repositories/postgres.py와 migrations/versions/ 아래 현재 revision들이 있을 때, When schema ownership 분석을 수행하면, Then Alembic과 runtime ensure_schema()가 다루는 주요 객체와 충돌 가능 지점이 식별되어 있어야 한다. -2. Given 로컬 docker compose 기반 fresh DB와 app runtime이 일부 schema를 먼저 만든 DB 시나리오가 있을 때, When migrate-db 실행 규칙을 정의하면, Then 허용 순서, 금지 순서, 필요한 사전 점검과 보정 규칙이 문서로 정리되어 있어야 한다. -3. Given 코드 안에 legacy schema stamp 분기가 남아 있을 때, When migration 경계 문서를 작성하면, Then 이번 REQ와 관련 batch 범위에서 legacy DB 지원이 제외된다는 점이 명시되어 있어야 한다. - -## Impacted Area -- Docs: docs/project-structure.md, docs/runtime-flows.md, docs/data-model.md -- Modules: app/repositories/postgres.py, app/db/migrations.py, migrations/versions/*.py -- Tests: 로컬 schema ownership 검토 체크리스트 또는 migrate-db 충돌 시나리오 검증 절차 - -## Notes -- 이 요구사항은 dcy-001의 FR-2, FR-3 일부, S-2, R-1, R-2를 구현 가능한 Migration 단위로 정규화한 것이다. -- legacy DB support와 legacy stamp 경로 검증은 Discovery 범위에서 이미 제외되었으므로, 이 REQ는 local runtime bootstrap 흔적과 Alembic의 상호작용에 집중한다. -- 문서화만으로 해결되지 않는 ownership 충돌이 발견되면 후속 batch에서 코드 수정이 필요할 수 있다. - -## Change Log - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Implemented 전환 — BAT-007 verification passed, confirm-req-implemented 게이트 통과 - -#### Intent -- BAT-007 verification.md AC-1~3 모두 PASS. Blocking Issues 없음. docs/project-structure.md "Schema Ownership Boundary", docs/runtime-flows.md "Alembic vs ensure_schema() Schema Ownership Boundary" evidence 연결 확인 완료. 잔여 리스크(idx_auth_tokens_service_name_unique Alembic 미등록)는 후속 batch 검토 항목으로 남겨둠. - -#### Acceptance Criteria Delta -- none - -#### Impacted Area Delta -- none - -#### Delivery Trace Delta -- Batches: BAT-007 (release-candidate) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: implemented -- Follow-up Action: draft-release (BAT-007); idx_auth_tokens_service_name_unique Alembic revision 추가 검토 권장 - -#### Validation Plan -- BAT-007 verification.md AC-1~3 evidence 연결 확인 완료 - -### CHG-20260430-02 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- confirm-req 승인 게이트를 통과해 이 REQ를 Approved로 전환했다. - -#### Intent -- dcy-001의 delivery input으로 사용할 수 있는 확정 requirement로 승격하기 위해 상태를 변경했다. - -#### Acceptance Criteria Delta -- Added: - - none -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md -- Modules: - - none -- Tests: - - none - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: approved -- Follow-up Action: suggest-batch-reqs 또는 draft-batch 단계에서 REQ-002의 ownership 문서를 delivery scope로 연결한다. - -#### Validation Plan -- REQ 본문 필수 섹션, Acceptance Criteria 구체성, docs/srs/index.md 상태 동기화를 확인한다. - -### CHG-20260430-01 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- dcy-001에서 Alembic와 runtime schema ownership 경계 요구사항을 신규 Proposed REQ로 생성했다. - -#### Intent -- migrate-db와 runtime bootstrap의 경계가 불분명해 생길 수 있는 drift와 재실행 실패를 독립 Migration backlog로 관리하기 위해 생성했다. - -#### Acceptance Criteria Delta -- Added: - - Alembic과 runtime ensure_schema() ownership 분석 기준 정의 - - local runtime-precreated schema 시나리오용 허용 순서와 보정 규칙 정의 - - legacy DB 비지원 범위 명시 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md - - docs/runtime-flows.md - - docs/data-model.md -- Modules: - - app/repositories/postgres.py - - app/db/migrations.py - - migrations/versions/*.py -- Tests: - - migrate-db 충돌 시나리오 검토 체크리스트 또는 동등한 검증 절차 - -#### 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 단계에서 ownership 경계의 대상 객체와 범위 제외 항목을 검토하고 승인한다. - -#### Validation Plan -- confirm-req 전까지 Acceptance Criteria가 dcy-001의 FR-2, FR-3 일부, S-2와 일치하는지 검토하고, 이후 batch 단계에서 실제 객체 목록과 허용 순서 문서를 작성한다. \ No newline at end of file diff --git a/docs/srs/Migration/req-006_portfolio-tags-data-migration.md b/docs/srs/Migration/req-006_portfolio-tags-data-migration.md new file mode 100644 index 0000000..671e3ab --- /dev/null +++ b/docs/srs/Migration/req-006_portfolio-tags-data-migration.md @@ -0,0 +1,79 @@ +# REQ-006: portfolio_tags 데이터 마이그레이션 + +- Status: Proposed +- Type: Migration +- Priority: High +- Owner: Justin Ko + +## Intent +- 기존 portfolio_tags.tag_value 자유 문자열 데이터를 tags 마스터 테이블로 이전하고, portfolios.tag_ids UUID[] 배열을 채운 후 portfolio_tags 테이블을 제거한다. +- 소문자 정규화 후 UNIQUE 충돌을 방지하고, 마이그레이션 전 지정 admin 계정의 존재 여부를 사전 검증하여 데이터 무결성을 확보한다. +- 마이그레이션된 기존 태그는 사용자 포트폴리오에서 유래했으므로 is_admin_tag=FALSE로 설정한다. + +## Requirement +- 마이그레이션 스크립트는 실행 전 운영자가 지정한 admin 계정 ID가 users 테이블에 존재하는지 검증해야 한다. 존재하지 않으면 오류와 함께 중단한다. +- portfolio_tags.tag_value를 소문자 정규화(str.lower()) 후 tags 테이블에 INSERT해야 한다. ON CONFLICT DO NOTHING으로 중복을 무시한다. +- 모든 INSERT는 is_admin_tag=FALSE, created_by_user_id=운영자 지정 admin 계정 ID로 설정해야 한다. +- 각 portfolio의 portfolio_tags 행을 순회하여 해당 tag_value의 tag_id를 조회하고 portfolios.tag_ids 배열에 추가해야 한다. +- 데이터 이전 완료 후 portfolio_tags 테이블을 DROP해야 한다. +- 마이그레이션은 Alembic revision으로 관리하여 재실행 안전성(idempotent within revision)을 보장해야 한다. + +## Acceptance Criteria +1. Given portfolio_tags 행이 존재하고 유효한 admin 계정 ID 지정, When 마이그레이션 실행, Then tags 테이블에 소문자 정규화된 DISTINCT tag_value 수만큼 행이 생성되고 모두 is_admin_tag=FALSE, created_by_user_id=지정 admin ID이다. +2. Given portfolio_tags에 "TestTag"와 "testtag"가 존재, When 마이그레이션 실행, Then tags 테이블에 "testtag" 행이 하나만 생성된다 (ON CONFLICT DO NOTHING). +3. Given 마이그레이션 실행 후, When 각 portfolio의 tag_ids를 조회, Then 기존 portfolio_tags에서 해당 portfolio에 연결된 tag_value들의 tag_id가 모두 포함된다. +4. Given 마이그레이션 실행 후, When DB 스키마를 조회, Then portfolio_tags 테이블이 존재하지 않는다. +5. Given 유효하지 않은 admin 계정 ID(users 테이블에 없음) 지정, When 마이그레이션 실행, Then 데이터 변경 없이 오류와 함께 중단된다. +6. Given 마이그레이션 전 DISTINCT tag_value 수(소문자 정규화 기준) = N, When 마이그레이션 실행 후 tags 테이블 행 수 조회, Then N이다. + +## Impacted Area +- Docs: docs/data-model.md (portfolio_tags 테이블 제거 반영) +- Modules: + - migrations/versions/ — 데이터 마이그레이션 Alembic revision (태그 이전, portfolios.tag_ids 채우기, portfolio_tags DROP) +- Tests: + - 마이그레이션 전후 DB 상태 SQL 검증 + - 잘못된 admin 계정 ID 시 중단 확인 + - DISTINCT 수 일치 확인 + +## Notes +- 운영자는 마이그레이션 실행 전 admin 계정 ID를 확인하여 마이그레이션 스크립트에 명시한다. +- 마이그레이션된 태그의 is_admin_tag=FALSE는 의도적 설계: 기존 portfolio_tags는 사용자 포트폴리오에서 유래했으며 asset 태그 적용 대상이 아니다. +- REQ-003의 portfolios.tag_ids 컬럼 추가가 이 REQ 선행 조건이다. +- 마이그레이션 완료 후 별도 소유권 재지정 스크립트 제공은 R-1 완화 방안으로, 이 REQ 범위 밖이다. +- Source: dcy-002 FR-5, R-1, R-3, A-2 + +## Change Log + +### CHG-20260519-01 +- Date: 2026-05-19 +- Author: Justin Ko + +#### Change Summary +- 최초 생성 — dcy-002 confirm-discovery 후 REQ 초안 작성 + +#### Intent +- dcy-002 FR-5를 Migration REQ로 분리하여 독립 구현·검증 단위로 정규화 + +#### Acceptance Criteria Delta +- Added: + - AC-1~6 최초 작성 + +#### Impacted Area Delta +- Docs: + - docs/data-model.md — portfolio_tags 제거 +- Modules: + - migrations/versions/ — 최초 지정 +- 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-003 선행 의존성 확인 diff --git a/docs/srs/Migration/req-009_v1-api-db-schema-migration.md b/docs/srs/Migration/req-009_v1-api-db-schema-migration.md deleted file mode 100644 index e99dc1e..0000000 --- a/docs/srs/Migration/req-009_v1-api-db-schema-migration.md +++ /dev/null @@ -1,184 +0,0 @@ -# req-009: v1 API 지원을 위한 DB 스키마 migration 추가 - -- Status: Approved -- Type: Migration -- Priority: High -- Owner: repository-maintainers - -## Intent -- dcy-002에서 요구하는 4개 DB 변경(portfolios.market 컬럼, auth_tokens.runtime_id 컬럼, broker_credentials 테이블, ledger_trades 테이블)을 Alembic revision으로 구현한다. -- dcy-006의 daemon-aware authorization 모델을 위해 `daemons` registry, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`, transition backfill 및 관련 제약을 같은 migration ownership 규칙 안에서 확장한다. -- IF NOT EXISTS 패턴을 유지해 REQ-002 정책을 준수한다(NFR-4). - -## Requirement -- 다음 스키마 변경을 IF NOT EXISTS 패턴을 사용하는 신규 Alembic migration으로 추가해야 한다. - 1. portfolios 테이블에 market TEXT NULLABLE 컬럼 추가 (기존 행은 NULL로 유지) - 2. auth_tokens 테이블에 runtime_id TEXT UNIQUE NULLABLE 컬럼 추가 - 3. broker_credentials 테이블 신규 생성 (user_id, broker, account_ref, encrypted_payload, credential_version, last_rotated_at 포함) - 4. ledger_trades 테이블 신규 생성 (ledger_entry_id PK, asset_id, order_id, execution_id, side, quantity, price, fee_amount, tax_amount, executed_at, recorded_at 포함, (asset_id, order_id, execution_id) unique constraint 포함) - 5. daemons registry 테이블 신규 생성 (`daemon_id`, `owner_user_id`, stable slug/display metadata, 생성/갱신 시각 포함) - 6. auth_tokens 테이블에 `daemon_id` NULLABLE FK 컬럼 추가, portfolios 테이블에 `managed_by_daemon_id` NULLABLE FK 컬럼 추가 - 7. transition migration은 기존 service-token과 portfolio의 daemon 관련 컬럼을 `NULL`로 backfill하고, post-cutover exact-one assignment로 수렴할 수 있도록 same-user consistency 및 invalid reference rejection을 위한 FK/check/unique 제약을 제공해야 한다. - -## Acceptance Criteria -1. Given alembic upgrade head 실행 후, When portfolios 테이블을 조회하면, Then market TEXT NULLABLE 컬럼이 존재해야 한다. -2. Given alembic upgrade head 실행 후, When auth_tokens 테이블을 조회하면, Then runtime_id TEXT NULLABLE UNIQUE 컬럼이 존재해야 한다. -3. Given alembic upgrade head 실행 후, Then broker_credentials 테이블이 존재해야 한다. -4. Given alembic upgrade head 실행 후, Then ledger_trades 테이블이 존재해야 한다. -5. Given (asset_id, order_id, execution_id) 동일 조합으로 ledger_trades에 두 번 삽입 시, Then UniqueViolation이 발생해야 한다. -6. Given migration을 이미 실행한 DB에 동일 migration을 재실행하면, Then 에러 없이 완료되어야 한다(IF NOT EXISTS 보장). -7. Given portfolios 테이블의 기존 행, When migration 실행 후 조회하면, Then market 값이 NULL이어야 한다(기존 데이터 보정 없음, DECIDE_MARKET_DEFAULT Answered). -8. Given alembic upgrade head 실행 후, When schema를 조회하면, Then `daemons` 테이블과 `auth_tokens.daemon_id` 컬럼이 존재해야 한다. -9. Given alembic upgrade head 실행 후, When portfolios 테이블을 조회하면, Then `managed_by_daemon_id` 컬럼이 존재하고 기존 행은 `NULL`로 backfill되어 있어야 한다. -10. Given 존재하지 않는 `daemon_id`를 auth_tokens 또는 portfolios에 바인딩하려고 시도하면, Then FK 또는 동등 제약으로 거부되어야 하며, owner user 일관성을 깨는 binding/assignment는 DB 레벨 제약 또는 동등한 무결성 장치로 방지되어야 한다. - -## Impacted Area -- Docs: docs/data-model.md (REQ-011에서 갱신 예정) -- Modules: migrations/versions/, app/domain/models.py, app/repositories/portfolio_repository.py, app/repositories/postgres.py -- Tests: tests/test_app.py 또는 migration 검증 테스트, daemon schema integrity 검증 - -## Notes -- NFR-4(IF NOT EXISTS 패턴) 준수 — REQ-002 Alembic migration 정책 기반. -- broker_credentials 테이블의 encrypted_payload 컬럼은 Fernet 암호화된 ciphertext를 TEXT로 저장한다. 암복호화 로직은 REQ-010 참조. -- 컬럼 상세 타입(NUMERIC vs FLOAT, TIMESTAMPTZ 등)은 설계 단계(draft-batch-design)에서 확정한다. -- daemon exact-one assignment의 steady-state `NOT NULL`/강제 cutover는 inventory/backfill 완료 이후 단계적으로 적용할 수 있다. transition migration은 `NULL` backfill을 허용하되 fail-open을 허용하면 안 된다. -- migration revision ID는 구현 시 기존 revision chain의 마지막 head를 down_revision으로 참조한다. -- Source Discovery: dcy-002 (영향 범위, NFR-4, DECIDE_MARKET_DEFAULT), dcy-006 (FR-1, FR-2, NFR-2, NFR-3) - -## Change Log - -### CHG-20260513-01 -- Date: 2026-05-13 -- Author: repository-maintainers - -#### Change Summary -- runtime_id 중심 v1 migration REQ를 daemon registry/binding/assignment 스키마까지 포함하도록 확장하고, 기존 Implemented 상태를 Approved로 되돌림 - -#### Intent -- dcy-006의 daemon-aware model은 기존 REQ-009 구현 범위를 넘어서 `daemons`, `auth_tokens.daemon_id`, `portfolios.managed_by_daemon_id`, transition backfill, same-user consistency 제약을 추가로 요구한다. BAT-001/REL-001 migration evidence는 이 범위를 포함하지 않는다. - -#### Acceptance Criteria Delta -- Added: - - AC-8: `daemons`, `auth_tokens.daemon_id` - - AC-9: `portfolios.managed_by_daemon_id` + NULL backfill - - AC-10: daemon FK / 무결성 제약 -- Updated: - - Requirement 본문에 daemon registry 및 transition backfill 항목 추가 -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - `docs/data-model.md`, `docs/runtime-flows.md` -- Modules: - - `migrations/versions/`, `app/domain/models.py`, `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py` -- Tests: - - migration integrity / daemon schema constraint 검증 - -#### Delivery Trace Delta -- Batches: - - BAT-001 (release-candidate; REL-001 released input) — 기존 migration verification은 daemon schema, backfill, constraint evidence를 포함하지 않음 -- Releases: - - REL-001 (released) — release facts는 runtime_id/broker_credentials/ledger_trades까지만 다루므로 revised migration REQ를 충족하지 않음 - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: yes -- Status Recommendation: rollback-to-approved -- Follow-up Action: dcy-006 후속 batch에서 신규 Alembic revision과 migration validation을 설계하고, cutover/backfill evidence를 release 단계에서 수집한다. - -#### Validation Plan -- 현재 migration chain과 `app/repositories/postgres.py` schema ownership을 재검토해 daemon schema 추가 위치를 설계한다. -- 구현 후 alembic upgrade / re-run / invalid daemon reference 검증을 포함한 migration test plan을 수행한다. - -### 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. DB 환경 실행 미확인은 non-blocker로 처리됨 (revision에 IF NOT EXISTS 패턴 전체 적용 확인). 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); 배포 전 Docker Compose 환경에서 실제 migration 실행 검증 권장 - -#### 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-009 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-009 최초 생성 (source Discovery: dcy-002) - -#### Intent -- v1 API 지원에 필요한 DB 스키마 변경을 REQ backlog로 정규화 - -#### Acceptance Criteria Delta -- Added: - - 전체 Acceptance Criteria 신규 작성 - -#### Impacted Area Delta -- Docs: - - docs/data-model.md (REQ-011에서 갱신 예정) -- Modules: - - migrations/versions/ 신규 migration 파일 - - app/domain/models.py 모델 필드 추가 -- Tests: - - migration 검증 테스트 - -#### 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-005, REQ-006, REQ-007, REQ-008과 함께 delivery 단위 구성 필수(runtime_id, market, broker_credentials, ledger_trades 모두 이 migration에 의존). diff --git a/docs/srs/Migration/req-016_portfolios-uuid-schema-migration.md b/docs/srs/Migration/req-016_portfolios-uuid-schema-migration.md deleted file mode 100644 index e5fc17e..0000000 --- a/docs/srs/Migration/req-016_portfolios-uuid-schema-migration.md +++ /dev/null @@ -1,157 +0,0 @@ -# req-016: portfolios 테이블 UUID 전환 및 데이터 마이그레이션 - -- Status: Implemented -- Type: Migration -- Priority: High -- Owner: repository-maintainers - -## Intent -- 현재 사용자가 직접 지정하는 TEXT PRIMARY KEY(`portfolio_id`)를 서버 생성 UUID로 교체하고, 기존 `portfolio_id` 문자열을 `name` 컬럼으로 이전한다. -- `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. -- 기존 데이터와 FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)을 단일 Alembic revision으로 atomic하게 마이그레이션한다. - -## Requirement -- 단일 Alembic revision을 추가해 아래를 수행한다. - 1. `portfolios` 테이블에 `name` TEXT 컬럼을 추가한다. - 2. 기존 `portfolio_id` 값을 `name` 컬럼으로 복사한다 (변환 없이 그대로). - 3. 각 기존 레코드에 `uuid.uuid4()`로 새 UUID를 생성해 `portfolio_id` 컬럼 값을 교체한다. - 4. FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)의 `portfolio_id` 컬럼 값도 새 UUID로 함께 갱신한다. - 5. `(owner_user_id, name)` 복합 UNIQUE 제약을 `portfolios` 테이블에 추가한다. - 6. `Portfolio` dataclass(`app/domain/models.py`)의 `portfolio_id` 필드를 UUID 문자열로 유지하고 `name` 필드를 명확히 선언한다. - 7. `app/repositories/postgres.py`의 `create_portfolio()` 메서드가 서버에서 `uuid.uuid4()`로 생성된 UUID를 `portfolio_id`로 사용한다. -- 마이그레이션은 IF NOT EXISTS 패턴을 준수해 재실행 시 오류가 없어야 한다(REQ-002 준수). -- FK 참조 테이블과의 갱신은 단일 트랜잭션 내에서 atomic하게 처리한다. -- 마이그레이션 완료 후 FK 고아 레코드(orphan) 0건을 검증하는 쿼리를 revision 안에 포함한다. - -## Acceptance Criteria -1. Given 기존 `portfolio_id` 문자열로 운영 중인 portfolios 레코드가 있을 때, When 신규 Alembic revision을 `alembic upgrade head`로 실행하면, Then 각 레코드의 `name` 컬럼에 기존 `portfolio_id` 값이 그대로 복사되고, `portfolio_id`는 UUID 형식(`re.match(r'^[0-9a-f-]{36}$', ...)`)으로 교체된다. -2. Given 마이그레이션 완료 후, When `portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`의 `portfolio_id` 컬럼을 확인하면, Then 고아 레코드(FK 불일치 레코드)가 0건이다. -3. Given `(owner_user_id, name)` 복합 UNIQUE 제약이 적용된 상태에서, When 동일 `owner_user_id`와 동일 `name`으로 portfolio 레코드를 추가하면, Then DB 레벨에서 UNIQUE 제약 위반 오류가 발생한다. -4. Given 동일 `name`이지만 다른 `owner_user_id`로 portfolio 레코드를 추가하면, Then 정상 삽입된다. -5. Given 신규 Alembic revision을 이미 적용한 환경에서, When 동일 revision을 재실행하면, Then 오류 없이 완료된다(IF NOT EXISTS 준수). -6. Given `app/repositories/postgres.py`의 `create_portfolio()` 메서드가 실행되면, Then 서버에서 `uuid.uuid4()`로 생성된 UUID가 `portfolio_id`로 저장된다. - -## Impacted Area -- Docs: `docs/data-model.md` (portfolios 테이블 스키마 변경 반영 — REQ-019에서 처리) -- Modules: - - `migrations/versions/`: 신규 Alembic revision 파일 - - `app/domain/models.py`: Portfolio dataclass `portfolio_id` 및 `name` 필드 - - `app/repositories/postgres.py`: `create_portfolio()` UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드 -- Tests: - - 신규 `tests/test_portfolio_uuid.py` 또는 `tests/test_cli.py` — portfolio_id UUID 형식 검증, 동일 name 독립 생성 테스트, (owner_user_id, name) UNIQUE 제약 동작 확인 - -## Notes -- 기존 `portfolio_id` 문자열은 `name` 컬럼으로 변환 없이 복사한다(Discovery A-3 확정). -- `portfolio_id` 컬럼 타입은 TEXT 유지(PostgreSQL UUID 타입으로의 전환은 이번 범위 밖). -- `app/domain/models.py`의 `Portfolio.portfolio_id`는 UUID 문자열로 의미가 바뀌나 Python 타입(`str`)은 유지. -- `get_portfolio_by_name_and_user()` 메서드는 REQ-017(CLI 식별 방식 전환)에서도 사용한다. -- Source Discovery: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution (FR-1, FR-5, NFR-1) - -## Change Log - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- 최초 생성 — dcy-004 draft-req에 의해 생성 - -#### Intent -- dcy-004 Discovery의 FR-1(portfolios.portfolio_id UUID 교체 및 name 컬럼 추가), FR-5(기존 데이터 마이그레이션), NFR-1(단일 revision, IF NOT EXISTS, atomic 트랜잭션) 반영 - -#### Acceptance Criteria Delta -- Added: - - 1~6 (최초 생성) - -#### Impacted Area Delta -- Docs: - - docs/data-model.md: portfolios 테이블 스키마 변경 (REQ-019에서 처리) -- Modules: - - migrations/versions/: 신규 revision 추가 - - app/domain/models.py: Portfolio dataclass 변경 - - app/repositories/postgres.py: create_portfolio UUID 생성, get_portfolio_by_name_and_user() 추가 -- Tests: - - tests/test_portfolio_uuid.py 또는 tests/test_cli.py: UUID 형식 검증, UNIQUE 제약 동작 확인 - -#### 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: portfolio_id UUID 형식 검증, FK 고아 레코드 0건 확인, (owner_user_id, name) UNIQUE 제약 동작 확인, IF NOT EXISTS 재실행 안전성 확인 - -### CHG-20260511-02 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Approved 전환 — confirm-req 게이트 통과 - -#### Intent -- Delivery 입력으로 사용할 수 있도록 REQ-016을 승인 상태로 승격한다. - -#### 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-016 Acceptance Mapping이 모든 AC에 대해 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~6 모두 test evidence 연결 확인 (tests/test_portfolio_uuid.py 14/14 passed, full suite 129/129 passed) diff --git a/docs/srs/Non-Functional/req-010_credential-payload-fernet-encryption.md b/docs/srs/Non-Functional/req-010_credential-payload-fernet-encryption.md deleted file mode 100644 index 0a4bee4..0000000 --- a/docs/srs/Non-Functional/req-010_credential-payload-fernet-encryption.md +++ /dev/null @@ -1,128 +0,0 @@ -# req-010: credential_payload Fernet 암호화 및 startup validation - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: repository-maintainers - -## Intent -- broker_credentials의 credential_payload(KIS app_key/app_secret)는 민감 데이터이므로 DB 평문 저장이 금지된다(R-1 High). -- Python cryptography 패키지의 Fernet(AES-128-CBC + HMAC-SHA256)으로 저장 전 암호화, 조회 시 복호화한다. -- CREDENTIAL_ENCRYPTION_KEY 환경변수가 없으면 서비스 기동을 거부해 운영 실수를 사전에 방지한다. - -## Requirement -- 서비스 기동 시 CREDENTIAL_ENCRYPTION_KEY 환경변수(base64 인코딩 32바이트)가 존재하지 않으면 기동을 중단하고 에러 메시지를 출력해야 한다. -- broker_credentials에 credential_payload를 저장할 때 Fernet으로 암호화한 ciphertext를 저장해야 한다. -- broker_credentials에서 credential_payload를 조회할 때 Fernet으로 복호화한 JSON을 반환해야 한다. - -## Acceptance Criteria -1. Given CREDENTIAL_ENCRYPTION_KEY가 설정된 환경에서 서비스 기동 시, Then 정상 기동되어야 한다. -2. Given CREDENTIAL_ENCRYPTION_KEY가 설정되지 않은 환경에서 서비스 기동 시, Then 기동이 실패하고 키 누락을 알리는 에러 메시지가 출력되어야 한다. -3. Given credential_payload를 broker_credentials에 저장한 뒤 DB를 직접 조회하면, Then 암호화된 ciphertext가 저장되어 있어야 한다(평문 app_key/app_secret 불가). -4. Given 암호화 저장된 credential_payload를 조회 API(GET /v1/user_info)로 읽으면, Then 복호화된 JSON({app_key: ..., app_secret: ...})이 반환되어야 한다. -5. Given 잘못된 키로 복호화를 시도하면, Then cryptography.fernet.InvalidToken 예외가 발생하고 500 또는 적절한 오류 응답이 반환되어야 한다. - -## Impacted Area -- Docs: docs/project-structure.md (app/core/encryption.py 신규 모듈 반영 필요) -- Modules: app/core/config.py (CREDENTIAL_ENCRYPTION_KEY 설정 필드 및 startup validation 추가), app/core/security.py 또는 app/core/encryption.py (Fernet 암복호화 유틸리티 신규), app/repositories/postgres.py (broker_credentials 저장·조회 시 암복호화 적용) -- Tests: tests/test_auth.py 또는 신규 테스트에서 startup validation 및 암복호화 검증 - -## Notes -- CREDENTIAL_ENCRYPTION_KEY는 `from cryptography.fernet import Fernet; Fernet.generate_key()`로 생성한 base64 인코딩 키를 사용한다. -- startup validation은 FastAPI lifespan 이벤트 또는 Settings 초기화 시 수행한다. 설계 단계에서 위치를 확정한다. -- 키 로테이션 절차는 이번 반복 Out of Scope이다. -- Source Discovery: dcy-002 (NFR-3, R-1, OQ-3 Answered) - -## 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 없음. AC3 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-010 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-010 최초 생성 (source Discovery: dcy-002 NFR-3) - -#### Intent -- credential_payload 암호화 및 startup validation 요구사항을 독립 Non-Functional REQ로 승격 정규화 - -#### Acceptance Criteria Delta -- Added: - - 전체 Acceptance Criteria 신규 작성 - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md (encryption 모듈 반영 필요) -- Modules: - - app/core/config.py startup validation 추가 - - app/core/encryption.py 신규 -- Tests: - - startup validation 및 암복호화 테스트 - -#### 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-007(GET /v1/user_info), REQ-009(broker_credentials 테이블)과 함께 delivery 단위 구성 권장. diff --git a/docs/srs/Testing/req-001_local-db-validation-scenarios.md b/docs/srs/Testing/req-001_local-db-validation-scenarios.md deleted file mode 100644 index 789e735..0000000 --- a/docs/srs/Testing/req-001_local-db-validation-scenarios.md +++ /dev/null @@ -1,147 +0,0 @@ -# req-001: 로컬 DB 초기화 및 마이그레이션 검증 시나리오 정의 - -- Status: Implemented -- Type: Testing -- Priority: High -- Owner: repository-maintainers - -## Intent -- 이 요구사항은 로컬 docker compose 기반 PostgreSQL 또는 TimescaleDB 환경에서 init-db, migrate-db, health-check, 최소 smoke flow를 반복 가능하게 검증하기 위한 기준선을 정의한다. -- 운영자와 유지보수 담당자가 같은 명령 순서와 같은 증거 형식으로 DB 준비 결과를 비교할 수 있도록 검증 절차를 명시하는 것이 목표다. - -## Requirement -- 프로젝트는 로컬 docker compose 기반 DB 환경에 대해 fresh DB 시나리오와 app runtime이 일부 schema를 먼저 만든 시나리오를 포함한 검증 매트릭스를 정의해야 하며, init-db, migrate-db, health-check, 최소 인증 또는 portfolio smoke flow의 실행 순서, 기대 결과, 실패 조건, 증거 수집 방식을 함께 제공해야 한다. - -## Acceptance Criteria -1. Given 로컬 docker compose 기반 DB와 필요한 환경 변수가 준비되어 있을 때, When 운영자가 init-db, health-check, 최소 인증 또는 portfolio smoke flow를 실행하면, Then 명령 순서, 기대 출력, pass or fail 기준, 남겨야 할 로그가 문서로 정의되어 있어야 한다. -2. Given fresh DB와 app runtime이 일부 schema를 먼저 만든 DB 시나리오가 있을 때, When 운영자가 migrate-db를 실행하면, Then 각 시나리오별 기대 결과와 실패 조건, 확인해야 할 증거가 구분되어 문서화되어 있어야 한다. -3. Given 동일한 로컬 검증 절차를 반복 실행할 때, When 운영자가 같은 명령 세트를 다시 실행하면, Then 결과 비교가 가능하도록 출력 형식과 로그 수집 위치가 일관되게 정의되어 있어야 한다. - -## Impacted Area -- Docs: README.md, docs/runtime-flows.md, docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md -- Modules: docker-compose.local-test.yml, app/cli/commands/health.py, app/db/migrations.py -- Tests: 로컬 docker compose 기반 수동 검증 체크리스트 또는 동등한 smoke validation 절차 - -## Notes -- 이 요구사항은 dcy-001의 FR-1, NFR-1, NFR-2, S-1을 주로 구현 가능한 backlog로 정규화한 것이다. -- shared dev DB, staging, production-like 환경 검증은 이번 요구사항 범위에 포함하지 않는다. -- 성공 지표의 세부 artifact 형식은 REQ 승인 또는 batch planning 단계에서 확정할 수 있다. - -## Change Log - -### CHG-20260511-01 -- Date: 2026-05-11 -- Author: repository-maintainers - -#### Change Summary -- Implemented 전환 — BAT-007 verification passed, confirm-req-implemented 게이트 통과 - -#### Intent -- BAT-007 verification.md AC-1~3 모두 PASS. Blocking Issues 없음. README.md "Local DB Validation Runbook", docs/runtime-flows.md "Flow: Local DB Initialization and Validation" evidence 연결 확인 완료. - -#### Acceptance Criteria Delta -- none - -#### Impacted Area Delta -- none - -#### Delivery Trace Delta -- Batches: BAT-007 (release-candidate) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: implemented -- Follow-up Action: draft-release (BAT-007) - -#### Validation Plan -- BAT-007 verification.md AC-1~3 evidence 연결 확인 완료 - -### CHG-20260430-02 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- confirm-req 승인 게이트를 통과해 이 REQ를 Approved로 전환했다. - -#### Intent -- dcy-001의 delivery input으로 사용할 수 있는 확정 requirement로 승격하기 위해 상태를 변경했다. - -#### Acceptance Criteria Delta -- Added: - - none -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md -- Modules: - - none -- Tests: - - none - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: approved -- Follow-up Action: suggest-batch-reqs 또는 draft-batch 단계에서 REQ-001의 검증 절차를 delivery scope로 연결한다. - -#### Validation Plan -- REQ 본문 필수 섹션, Acceptance Criteria 구체성, docs/srs/index.md 상태 동기화를 확인한다. - -### CHG-20260430-01 -- Date: 2026-04-30 -- Author: GitHub Copilot - -#### Change Summary -- dcy-001에서 로컬 DB 검증 시나리오 요구사항을 신규 Proposed REQ로 생성했다. - -#### Intent -- 로컬 DB 준비와 smoke validation 절차를 독립 구현 및 독립 검증 가능한 Testing 단위로 분리하기 위해 생성했다. - -#### Acceptance Criteria Delta -- Added: - - 로컬 docker compose 기반 init-db, health-check, smoke flow의 검증 기준 정의 - - fresh DB와 runtime-precreated schema 시나리오별 migrate-db 검증 기준 정의 - - 반복 실행 가능한 증거 수집 형식 정의 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - README.md - - docs/runtime-flows.md - - docs/discovery/dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md -- Modules: - - docker-compose.local-test.yml - - app/cli/commands/health.py - - app/db/migrations.py -- Tests: - - 로컬 DB 수동 검증 체크리스트 또는 동등한 smoke validation 절차 - -#### 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 단계에서 검증 매트릭스 초안의 범위와 증거 형식을 승인한다. - -#### Validation Plan -- confirm-req 전까지 Acceptance Criteria가 dcy-001의 FR-1 및 S-1과 일치하는지 검토하고, 이후 batch 단계에서 실제 검증 절차와 로그 수집 artifact를 작성한다. \ No newline at end of file diff --git a/docs/srs/index.md b/docs/srs/index.md index 91274a9..78f246a 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -74,43 +74,22 @@ ## Next Requirement ID -- Current: `REQ-030` +- Current: `REQ-009` - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. ## Requirement Register | ID | Title | Type | Status | Priority | Owner | Link | | --- | --- | --- | --- | --- | --- | --- | -| REQ-001 | 로컬 DB 초기화 및 마이그레이션 검증 시나리오 정의 | Testing | Implemented | High | repository-maintainers | [REQ-001](./Testing/req-001_local-db-validation-scenarios.md) | -| REQ-002 | Alembic와 runtime schema ownership 경계 정리 | Migration | Implemented | High | repository-maintainers | [REQ-002](./Migration/req-002_alembic-runtime-schema-ownership.md) | -| REQ-003 | 로컬 DB 운영 runbook 및 baseline 문서 갱신 | Documentation | Implemented | Medium | repository-maintainers | [REQ-003](./Documentation/req-003_local-db-operations-runbook-and-baseline-updates.md) | -| REQ-004 | GET /health 응답 스키마 확장 | Interface | Implemented | High | repository-maintainers | [REQ-004](./Interface/req-004_health-response-schema-extension.md) | -| REQ-005 | service-token 발급 시 runtime_id 동시 생성 | Interface | Approved | High | repository-maintainers | [REQ-005](./Interface/req-005_service-token-runtime-id-issuance.md) | -| REQ-006 | GET /v1/assets 엔드포인트 구현 | Interface | Approved | High | repository-maintainers | [REQ-006](./Interface/req-006_v1-assets-endpoint.md) | -| REQ-007 | GET /v1/user_info 엔드포인트 구현 | Interface | Approved | High | repository-maintainers | [REQ-007](./Interface/req-007_v1-user-info-endpoint.md) | -| REQ-008 | POST /v1/assets/{asset_id}/ledger/trades 엔드포인트 구현 | Interface | Approved | High | repository-maintainers | [REQ-008](./Interface/req-008_v1-ledger-trades-endpoint.md) | -| REQ-009 | v1 API 지원을 위한 DB 스키마 migration 추가 | Migration | Approved | High | repository-maintainers | [REQ-009](./Migration/req-009_v1-api-db-schema-migration.md) | -| REQ-010 | credential_payload Fernet 암호화 및 startup validation | Non-Functional | Implemented | High | repository-maintainers | [REQ-010](./Non-Functional/req-010_credential-payload-fernet-encryption.md) | -| REQ-011 | trex-basic 계약 반영을 위한 baseline 문서 갱신 | Documentation | Approved | Medium | repository-maintainers | [REQ-011](./Documentation/req-011_baseline-docs-update-for-trex-basic-contract.md) | -| REQ-012 | service-token rotate CLI 명령 구현 | Interface | Approved | Medium | repository-maintainers | [REQ-012](./Interface/req-012_service-token-rotate-cli.md) | -| REQ-013 | CLI `portfolio list --user` 필수화 및 소유자 기반 조회 | Interface | Implemented | Medium | repository-maintainers | [REQ-013](./Interface/req-013_portfolio-list-user-required.md) | -| REQ-014 | CLI `portfolio create --user` 필수화 및 소유자 설정 | Interface | Implemented | Medium | repository-maintainers | [REQ-014](./Interface/req-014_portfolio-create-user-required.md) | -| REQ-015 | CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) | Interface | Implemented | Medium | repository-maintainers | [REQ-015](./Interface/req-015_broker-credential-cli.md) | -| REQ-016 | portfolios 테이블 UUID 전환 및 데이터 마이그레이션 | Migration | Implemented | High | repository-maintainers | [REQ-016](./Migration/req-016_portfolios-uuid-schema-migration.md) | -| REQ-017 | CLI portfolio 명령 — name+user 조합 식별 방식 전환 | Interface | Implemented | High | repository-maintainers | [REQ-017](./Interface/req-017_portfolio-cli-name-user-identification.md) | -| REQ-018 | HTTP API portfolio path parameter UUID 전환 및 v1 asset_id UUID 반환 | Interface | Implemented | High | repository-maintainers | [REQ-018](./Interface/req-018_portfolio-http-api-uuid-path-param.md) | -| REQ-019 | portfolio UUID 전환 관련 baseline 문서 갱신 | Documentation | Implemented | Medium | repository-maintainers | [REQ-019](./Documentation/req-019_portfolio-uuid-baseline-docs-update.md) | -| REQ-020 | 실배포 admin bootstrap 및 steady-state operator access runbook | Deployment | Approved | High | repository-maintainers | [REQ-020](./Deployment/req-020_production-admin-bootstrap-and-operator-access-runbook.md) | -| REQ-021 | release 검증 credential matrix 및 token provenance 운영 절차 | Documentation | Approved | High | repository-maintainers | [REQ-021](./Documentation/req-021_release-credential-matrix-and-token-provenance-procedures.md) | -| REQ-022 | 운영 로그 접근 및 release evidence 기록 절차 | Documentation | Approved | Medium | repository-maintainers | [REQ-022](./Documentation/req-022_operator-log-access-and-release-evidence-recording.md) | -| REQ-023 | Daemon Registry 관리 경로 | Interface | Approved | High | repository-maintainers | [REQ-023](./Interface/req-023_daemon-registry-and-service-token-daemon-binding.md) | -| REQ-028 | README.md Daemon Provisioning Runbook 반영 | Documentation | Approved | Medium | repository-maintainers | [REQ-028](./Documentation/req-028_daemon-model-baseline-docs-update.md) | -| REQ-029 | CLI `portfolio list` 출력 — 테두리 없는 리스트 형식 | Interface | Approved | Low | repository-maintainers | [REQ-029](./Interface/req-029_portfolio-list-borderless-output.md) | +| REQ-001 | tags 마스터 테이블 도입 | Data | Proposed | High | Justin Ko | [req-001](./Data/req-001_tags-master-table.md) | +| REQ-002 | assets.tag_ids UUID[] 컬럼 추가 | Data | Proposed | High | Justin Ko | [req-002](./Data/req-002_assets-tag-ids-column.md) | +| REQ-003 | portfolios.tag_ids UUID[] 컬럼 추가 및 portfolio_tags 테이블 제거 | Data | Proposed | High | Justin Ko | [req-003](./Data/req-003_portfolios-tag-ids-and-portfolio-tags-removal.md) | +| REQ-004 | tags 마스터 HTTP API | Interface | Proposed | High | Justin Ko | [req-004](./Interface/req-004_tags-http-api.md) | +| REQ-005 | 태그 적용·해제 HTTP API | Interface | Proposed | High | Justin Ko | [req-005](./Interface/req-005_tag-apply-remove-http-api.md) | +| REQ-006 | portfolio_tags 데이터 마이그레이션 | Migration | Proposed | High | Justin Ko | [req-006](./Migration/req-006_portfolio-tags-data-migration.md) | +| REQ-007 | pfhub tag CLI 명령어 | Interface | Proposed | Medium | Justin Ko | [req-007](./Interface/req-007_pfhub-tag-cli.md) | +| REQ-008 | 태그 모델 도입 baseline 문서 갱신 | Documentation | Proposed | Low | Justin Ko | [req-008](./Documentation/req-008_tag-baseline-docs-update.md) | ## Recent Change Log Summary -- 2026-05-18: REQ-029 Approved 생성 — `portfolio list` 테두리 없는 리스트 형식, minor-change fast path, 구현 완료 -- 2026-05-18: REQ-023 CHG-20260518-01 — AC-5(`portfolio update --daemon-id`), AC-6(`portfolio list` daemon-id 표시) 추가, Impacted Area 보강, Status Approved 유지 -- 2026-05-18: REQ-008 CHG-20260518-01 — `insert_ledger_trade` postgres 미구현 결함 수정, HTTP 500 정상화, Status Approved 유지 -- 2026-05-18: REQ-017 CHG-20260518-01 — `get_portfolio_by_name_and_user` postgres 미구현 결함 수정, Status Implemented 유지 -- 2026-05-13: REQ-023, REQ-028 Approved 전환 — confirm-req 게이트 통과 (필수 필드, AC, blocker 없음) \ No newline at end of file +- 2026-05-19: REQ-001~008 최초 생성 — dcy-002 (태그 소유권 및 적용 대상 정책) confirm-discovery 후 draft-req 실행