From 175570d4a46e7b53166a98c8dd57547b8b097167 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Tue, 14 Jul 2026 14:04:11 +0900 Subject: [PATCH] docs(discovery): start manual ledger API discovery --- ...14_manual-ledger-write-api-owner-scoped.md | 243 ++++++++++++++++++ docs/discovery/index.md | 1 + 2 files changed, 244 insertions(+) create mode 100644 docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md diff --git a/docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md b/docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md new file mode 100644 index 0000000..a0159fc --- /dev/null +++ b/docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md @@ -0,0 +1,243 @@ +# 0. 문서 상태 +이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. + +- 상태: draft +- 문서 ID: dcy-012_20260714_manual-ledger-write-api-owner-scoped +- 이슈명: 사용자 수동 장부 입력 API — INVEST/BUFFER/BUY/SELL/WITHDRAW 계약 및 owner-scoped ledger write +- 작성 시각(KST): 2026-07-14 14:01 +- 마지막 갱신 시각(KST): 2026-07-14 14:01 KST (new-discovery draft) +- 대체됨: 없음 +- 교차 발원: trex-web `docs/discovery/dcy-011_20260714_portfolio-manual-ledger-balance-quantity.md` confirmed by Justin Ko +- Project #7 item: `PVTI_lADOBCBKFM4BbUVHzgyvA9A` — `portfolio-hub: 수동 장부 입력 API 계약 — INVEST/BUFFER/BUY/SELL/WITHDRAW` +- 생성된 REQ 참조: 없음(draft) + +# 1. 계획 상태 요약 +- 연계 문서 현황: + - trex-web `dcy-011_20260714_portfolio-manual-ledger-balance-quantity`가 `approved-backend-first`로 confirmed 됨. + - trex-web은 수동 장부 입력/이력화 UI를 원하지만, source of truth인 `portfolio-hub`의 user-token owner-scoped write API 계약이 먼저 필요하다. + - portfolio-hub baseline `docs/data-model.md`는 “Balance-changing commands must update snapshot fields and append a ledger row together” 원칙을 명시한다. + - `dcy-006_20260605_ledger-trade-write-portfolio-state-return` 이후 portfolio_ledger는 broker/service-token trade write의 단일 원장으로 정리되었고, cash/quantity after-snapshot 응답 정책이 확립되었다. +- 현행 구조 확인: + - `app/domain/services.py`: `_INVEST_ACTION_MAP`은 `deposit -> INVEST`, `profit -> PROFIT`, `withdraw -> WITHDRAW`로 buffer 조정을 호출한다. + - `app/repositories/postgres.py:adjust_buffer()`는 `INVEST` 시 `profit_buffer += amount`, `WITHDRAW/PROFIT` 계열은 `profit_buffer -= amount`로 처리하고 ledger row를 남긴다. + - `app/repositories/postgres.py:transfer_buffer()`는 `to-buffer`를 `BUFFER_IN`으로 기록하며 `cash_balance -= amount`, `profit_buffer += amount`; `from-buffer`를 `BUFFER_OUT`으로 기록하며 `profit_buffer -= amount`, `cash_balance += amount`로 처리한다. + - `app/domain/services.py:apply_buy()`/`apply_sell()`은 `BUY`/`SELL` ledger row를 남기며 `cash_balance`와 `asset_quantity`를 함께 갱신한다. + - `README.md`에는 CLI 흐름이 이미 존재한다: `pfhub portfolio invest`, `pfhub portfolio transfer`, `pfhub portfolio buy`, `pfhub portfolio sell`. +- 해석: + - portfolio-hub에는 수동 장부에 필요한 도메인 서비스/CLI 흐름이 상당 부분 존재하지만, trex-web이 사용할 **HTTP v1 user-token owner-scoped API 계약**은 아직 정식 제품 계약으로 정리되어 있지 않다. + - 현재 v1 service-token endpoint(`POST /v1/portfolios/{portfolio_id}/ledger/trades`)는 daemon/trade-write 체결 보고용이다. 사용자 수동 장부 입력은 owner-scoped user/session token 기반으로 분리해야 한다. + +# 2. 요구사항 판정 결과 +- 판정: 신규 +- 근거: + - 기존 dcy-006/REQ-037은 daemon/service-token broker trade write의 회계 책임과 응답 정합성을 다뤘다. + - 기존 레거시/CLI `portfolio invest|transfer|buy|sell` 흐름은 사용자 self-service 웹 API 계약이 아니다. + - trex-web dcy-011에서 확정된 UI/제품 요구는 `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `REVERSAL`을 owner-scoped manual ledger API로 노출할 필요가 있다. + +# 3. 문제점의 요약 +- 현재 상태: + - portfolio snapshot fields는 `cash_balance`, `asset_quantity`, `profit_buffer`, `price`로 구성된다. + - portfolio_ledger는 after-snapshot 필드(`cash_balance_after`, `asset_quantity_after`, `total_value_after`, `profit_buffer_after`)를 가진다. + - CLI/domain 경로는 수동 장부성 작업을 수행할 수 있지만, trex-web이 직접 호출할 안정적인 v1 API 계약/권한/응답 shape이 없다. + - daemon-managed portfolio에 대한 수동 보정은 운영상 필요하지만, 자동/daemon 체결 기록과 충돌할 수 있다. +- 기대 상태: + - trex-web은 user/session token으로 자기 포트폴리오에 수동 장부 entry를 추가할 수 있다. + - 모든 manual ledger write는 portfolio row lock/transaction 안에서 snapshot update와 ledger append를 함께 수행한다. + - 응답은 변경 후 `cash_balance_after`, `asset_quantity_after`, `profit_buffer_after`, 필요 시 `total_value_after`를 포함한다. + - action/effect는 trex-web dcy-011에서 확정된 portfolio-hub 구조를 따른다. + - ledger history 조회는 trex-web이 최근 장부 이력과 변경 후 상태를 표시할 수 있을 만큼 충분하다. +- 이해관계자: + - portfolio-hub-dev — API/data model/권한/트랜잭션 구현 책임 + - trex-web/service-dev — API client, 장부 입력 UI, 이력 UI 소비자 + - qc-reviewer — action/effect, owner-scope, transaction, regression 검증 + - lead — StagePilot 게이트와 교차서비스 정합 관리 + - Justin Ko — 제품/운영 정책 승인 +- 제약사항: + - 기존 daemon/service-token ledger trade endpoint의 semantics와 idempotency를 깨지 않아야 한다. + - disabled portfolio write는 현행 정책대로 HTTP 409 또는 동등한 차단을 유지해야 한다. + - owner mismatch는 fail-closed 해야 한다. + - `QUANTITY_ADJUSTMENT`는 1차 범위에서 제외하고 후속 후보로만 남긴다. + +## 주요 사용자 시나리오 +- 사용자가 trex-web에서 투자금을 포트폴리오에 투입한다: 외부자금 --(`INVEST`)--> `profit_buffer`. +- 사용자가 전략 수행 기본자금으로 배정한다: `profit_buffer` --(`BUFFER_OUT`)--> `cash_balance`. +- 사용자가 상승 후 리밸런싱/보관한다: `cash_balance` --(`BUFFER_IN`)--> `profit_buffer`. +- 사용자가 수동 매수 기록을 입력한다: `cash_balance` --(`BUY`)--> `asset_quantity`. +- 사용자가 수동 매도 기록을 입력한다: `asset_quantity` --(`SELL`)--> `cash_balance`. +- 사용자가 실제 수익/자금을 회수한다: `profit_buffer` --(`WITHDRAW`)--> 외부자금. +- 사용자가 오입력 정정을 수행한다: 기존 entry를 수정/삭제하지 않고 반대 효과의 `REVERSAL` entry를 append한다. + +# 4. 이번에 정의할 변경 +- 변경 1 — **manual ledger write API 계약**: user/session token으로 owner-scoped portfolio에 `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `REVERSAL`을 기록하는 HTTP v1 계약을 정의한다. +- 변경 2 — **action/effect 회계 규칙**: 각 action의 snapshot 변화(`cash_balance`, `asset_quantity`, `profit_buffer`)와 수수료/세금/단가/수량 입력 필드를 정의한다. +- 변경 3 — **ledger history/read API 계약**: trex-web이 최근 장부 이력을 표시할 수 있는 response shape, pagination/limit, action filter 필요 여부를 정의한다. +- 변경 4 — **권한/운영 안전 정책**: owner-scoped authorization, disabled portfolio, daemon-managed portfolio warning/confirmation metadata, negative input policy, reversal policy를 정의한다. +- 변경 5 — **기존 domain/CLI와 v1 API 정합화**: 현행 `apply_invest`, `apply_transfer`, `apply_buy`, `apply_sell` 흐름을 재사용/분리할지 결정하고, broker/service-token endpoint와 충돌하지 않도록 source를 분리한다. + +## 영향 범위 +- 코드 영향: + - `app/api/v1_portfolios.py` 또는 신규 v1 router — manual ledger write/read endpoint + - `app/schemas/v1.py` — request/response schemas + - `app/domain/services.py` — manual ledger accounting helpers reuse/extract + - `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py` — transaction, row lock, ledger append, history read 확장 + - `app/auth/**` 또는 auth dependency — owner-scoped user/session permission guard + - tests — API, repository, auth negative, transaction, regression +- 문서 영향: + - `docs/interface-contract.md` + - `docs/runtime-flows.md` + - `docs/data-model.md` + - 후속 SRS/Batch 문서 +- 운영 영향: + - 사용자가 수동으로 portfolio source-of-truth를 변경할 수 있으므로 audit/권한/정정 정책이 중요하다. + - daemon-managed portfolio는 자동 체결과 수동 보정이 혼재할 수 있어 warning/confirmation evidence가 필요하다. + +# 5. 요구사항 목록 +## 기능 요구사항 +- FR-1: user/session token 사용자는 자기 소유 portfolio에 manual ledger entry를 append할 수 있어야 한다. 타 사용자 portfolio는 HTTP 403 또는 동등한 fail-closed 응답이어야 한다. +- FR-2: `INVEST` action은 외부자금에서 `profit_buffer`로 유입된 것으로 기록한다. 효과: `profit_buffer += amount`, `cash_balance`/`asset_quantity` 변화 없음. +- FR-3: `BUFFER_OUT` action은 `profit_buffer -> cash_balance` 이동을 기록한다. 효과: `profit_buffer -= amount`, `cash_balance += amount`. +- FR-4: `BUFFER_IN` action은 `cash_balance -> profit_buffer` 이동을 기록한다. 효과: `cash_balance -= amount`, `profit_buffer += amount`. +- FR-5: `BUY` action은 `cash_balance -> asset_quantity` 이동을 기록한다. 효과: `cash_balance -= quantity*price + fee + tax`, `asset_quantity += quantity`, `profit_buffer` 변화 없음. +- FR-6: `SELL` action은 `asset_quantity -> cash_balance` 이동을 기록한다. 효과: `asset_quantity -= quantity`, `cash_balance += quantity*price - fee - tax`, `profit_buffer` 변화 없음. +- FR-7: `WITHDRAW` action은 `profit_buffer -> 외부자금` 이동을 기록한다. 효과: `profit_buffer -= amount`, `cash_balance`/`asset_quantity` 변화 없음. +- FR-8: `REVERSAL`은 기존 ledger entry를 직접 수정/삭제하지 않고 target entry의 반대 effect를 새 entry로 append한다. target reference 필드/notes 정책은 REQ에서 확정한다. +- FR-9: `QUANTITY_ADJUSTMENT`는 1차 scope에서 제외한다. split/오입력 수량 보정은 후속 Discovery/REQ 후보로 남긴다. +- FR-10: manual ledger write 응답은 변경 후 snapshot을 포함해야 한다: 최소 `cash_balance_after`, `asset_quantity_after`, `profit_buffer_after`; `total_value_after` 포함 여부는 REQ에서 확정한다. +- FR-11: manual ledger history 조회 API는 portfolio별 최근 ledger entries를 반환해야 한다. action, amount/quantity/price/fee/tax, note, recorded_at, after-snapshot을 trex-web이 표시할 수 있어야 한다. +- FR-12: disabled portfolio write는 현행 state rule과 일관되게 거부한다. +- FR-13: daemon-managed portfolio에 manual ledger write 시 완전 차단하지 않는다. 대신 response/request contract에 trex-web이 강한 경고와 명시 확인을 표현할 수 있는 정책을 포함한다. + +## 비기능 요구사항 +- NFR-1: ledger append와 portfolio snapshot update는 단일 DB 트랜잭션으로 처리되어야 한다. +- NFR-2: 같은 portfolio에 대한 동시 manual ledger write는 lost update가 없어야 한다(row lock 또는 동등한 concurrency control). +- NFR-3: action/effect 계산은 기존 portfolio-hub domain/CLI 흐름과 일관되어야 한다. +- NFR-4: manual user-token write와 daemon/service-token broker write는 source/permission/path 의미가 분리되어야 한다. +- NFR-5: 모든 write request는 audit 가능한 note/source/requester 정보를 남길 수 있어야 한다. +- NFR-6: 기존 `GET /v1/portfolios`, daemon ledger trade write, CLI portfolio flows에 회귀가 없어야 한다. + +## 범위 경계 +- In Scope: + - owner-scoped manual ledger write API 계약 + - manual ledger history/read API 계약 + - action/effect: `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `REVERSAL` + - after-snapshot response + - owner/disabled/daemon-managed policy + - baseline docs and SRS follow-up +- Out of Scope: + - trex-web UI 구현 + - 실계좌 reconcile + - 브로커 주문 실행 + - strategy_sets 설정 UI + - 복잡한 세금/회계 모델 + - `QUANTITY_ADJUSTMENT` 1차 구현 + - 기존 daemon/service-token broker ledger endpoint의 의미 변경 + +# 6. 리스크/가정 목록 +## 리스크 +- R-1 (High): owner-scope 누락으로 타 사용자 portfolio 장부 변경 가능. + - 완화: API guard와 negative tests를 acceptance criteria에 포함한다. +- R-2 (High): ledger row와 snapshot update가 분리되어 장부/상태 불일치 발생. + - 완화: 단일 transaction + row lock + rollback tests. +- R-3 (Medium): manual write와 daemon write가 같은 portfolio에서 혼재되어 운영자가 상태를 오해. + - 완화: source 분리, daemon-managed warning/explicit confirmation policy, history 표시. +- R-4 (Medium): 기존 CLI/domain 흐름과 신규 API 회계 공식이 divergence. + - 완화: shared domain helper 추출 또는 동일 test vectors로 검증. +- R-5 (Medium): `REVERSAL`이 target entry 참조 없이 notes 기반으로만 남아 audit성이 약함. + - 완화: target ledger id/reference 필드 필요 여부를 REQ에서 결정. +- R-6 (Low): `PROFIT` legacy action과 `BUFFER_IN`/`WITHDRAW` 의미가 사용자 UI에서 혼동. + - 완화: 신규 user-facing API는 trex-web dcy-011 action taxonomy를 기준으로 명명하고, legacy CLI alias와 매핑을 문서화한다. + +## 가정 +- A-1: `profit_buffer`는 투자 대기/보관 buffer 역할로 유지한다. +- A-2: `cash_balance`는 전략 수행/매수 가능 현금 역할이다. +- A-3: trex-web은 user/session token으로 owner portfolio만 수정한다. +- A-4: `QUANTITY_ADJUSTMENT`는 1차에서 제외한다. +- A-5: portfolio-hub current domain functions can be reused or refactored without changing existing CLI behavior. + +## 오픈 질문 +- OQ-1: 신규 manual write endpoint shape은 action-dispatch 단일 endpoint인가, action별 endpoint인가? + - 후보 A: `POST /v1/portfolios/{portfolio_id}/ledger/manual` with `action` enum + - 후보 B: `POST /v1/portfolios/{portfolio_id}/ledger/invest|transfer|buy|sell|withdraw|reversal` + - 상태: Open + - 종료 조건: REQ draft 전 API path/shape 확정 +- OQ-2: `REVERSAL`은 target ledger entry id를 필수로 둘 것인가? + - 상태: Open + - 종료 조건: audit/reference policy 확정 +- OQ-3: manual `BUY`/`SELL`에서 잔고/수량 부족을 거부할 것인가, 표시 음수 허용 정책과 맞춰 결과 음수를 허용할 것인가? + - 사용자 정책: trex-web UI 입력 음수 금지, 기존/결과 표시 음수 허용. + - 상태: Open + - 종료 조건: portfolio-hub validation policy 확정 +- OQ-4: daemon-managed portfolio write 시 API가 `warning_required`/`confirmation_token` 같은 기계적 확인 필드를 요구할 것인가, 아니면 trex-web UI 경고만으로 충분한가? + - 상태: Open + - 종료 조건: API-level safety contract 확정 +- OQ-5: history response에 `total_value_after`를 포함할 것인가? + - 상태: Open + - 종료 조건: trex-web display needs and existing schema 정합성 검토 +- OQ-6: legacy `PROFIT` action은 신규 API에서 노출하지 않고 `BUFFER_IN`/`WITHDRAW`/`INVEST`로 대체할 것인가? + - 상태: Open + - 종료 조건: backwards compatibility and CLI mapping policy 확정 + +# 7. 초기 성공 기준 +- S-1: Given owner user token, When `INVEST` manual write, Then `profit_buffer_after`가 amount만큼 증가하고 ledger row가 append된다. +- S-2: Given owner user token, When `BUFFER_OUT`/`BUFFER_IN`, Then `profit_buffer`와 `cash_balance`가 기대값으로 이동하고 ledger row가 append된다. +- S-3: Given owner user token, When `BUY`/`SELL`, Then `cash_balance`와 `asset_quantity`가 기대값으로 갱신되고 ledger row가 append된다. +- S-4: Given owner user token, When `WITHDRAW`, Then `profit_buffer_after`가 감소하고 ledger row가 append된다. +- S-5: Given non-owner user token, When manual write/read, Then 403 또는 동등한 fail-closed 결과. +- S-6: Given disabled portfolio, When manual write, Then 409 또는 현행 state rule과 일관된 거부. +- S-7: Given daemon-managed portfolio, When manual write without/with required confirmation policy, Then REQ에서 정한 경고/확인 contract가 검증된다. +- S-8: Given existing daemon/service-token ledger trade write tests, When full test suite, Then regression 없음. +- S-9: Given ledger history request, When entries exist, Then trex-web 표시 필드(action, amount/quantity/price/fee/tax, note, recorded_at, after-snapshot)가 반환된다. + +## 측정 방식 및 데이터 출처 +- portfolio-hub API tests: owner/non-owner/disabled/daemon-managed/action effect/history +- repository tests: transaction, row lock/lost update, after-snapshot consistency +- contract tests or snapshots for trex-web consumption shape +- existing test suite: `uv run pytest` or project-standard test command after REQ/batch +- baseline doc diff: interface-contract/runtime-flows/data-model updates + +# 8. REQ로 넘기기 전 확인 체크 +- [ ] API endpoint shape 확정(OQ-1) +- [ ] REVERSAL target/reference policy 확정(OQ-2) +- [ ] 부족 잔고/수량 validation policy 확정(OQ-3) +- [ ] daemon-managed confirmation contract 확정(OQ-4) +- [ ] history response fields 확정(OQ-5) +- [ ] legacy `PROFIT` mapping policy 확정(OQ-6) +- [ ] Scope owner: `portfolio-hub-dev` primary, `trex-web/service-dev` consumer, `qc-reviewer` verification +- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 + +# 9. 파일 처리 결과 +- 처리 결과: 생성 +- 생성 경로: `docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md` +- 참조 문서: + - trex-web `docs/discovery/dcy-011_20260714_portfolio-manual-ledger-balance-quantity.md` + - portfolio-hub `docs/discovery/dcy-006_20260605_ledger-trade-write-portfolio-state-return.md` + - portfolio-hub `docs/data-model.md` + - portfolio-hub `app/domain/services.py` + - portfolio-hub `app/repositories/postgres.py` + - portfolio-hub `app/cli/commands/portfolio.py` + - portfolio-hub `README.md` + +# 10. 사용자 결정 필요 항목 요약 +## DECIDE +- API shape: 단일 action-dispatch endpoint vs action별 endpoint +- REVERSAL target ledger id/reference 필수 여부 +- 부족 cash/quantity 시 validation 거부 vs 결과 음수 허용 +- daemon-managed portfolio warning/confirmation을 API-level로 강제할지 UI-level로 둘지 +- history response에 `total_value_after` 포함 여부 +- legacy `PROFIT` action 노출/매핑 정책 + +## CONFIRM +- 이 Discovery는 trex-web UI 구현이 아니라 portfolio-hub API 계약/권한/트랜잭션 Discovery임을 확인 +- `QUANTITY_ADJUSTMENT`는 1차 제외하고 후속으로 분리한다는 trex-web dcy-011 결정을 유지 + +## DATA +- 현행 auth dependencies 중 session/user-token owner-scoped write에 가장 적합한 guard +- portfolio_ledger에 reversal target reference를 담을 별도 컬럼이 필요한지 여부 +- existing tests coverage for CLI invest/transfer/buy/sell flows + +# 11. Discovery Freeze +- Handoff Decision: 보류(draft) +- Handoff Rationale: trex-web dcy-011의 backend-first 후속 Discovery로 생성했다. 현행 portfolio-hub 구조와 action/effect는 확인했으나 API shape, reversal reference, validation, daemon-managed confirmation contract 결정이 필요하다. +- Ready for REQ Drafting: false +- Confirmed By: 미정 +- Confirmed At (KST): 미정 diff --git a/docs/discovery/index.md b/docs/discovery/index.md index 9a18e56..57bdf6c 100644 --- a/docs/discovery/index.md +++ b/docs/discovery/index.md @@ -17,3 +17,4 @@ | dcy-009_20260627_user-read-asset-catalog-and-daemons | 20260627 | 사용자용 read 2종 — GET /v1/assets require_read 완화 + 사용자 가용 데몬 목록 신규(교차 발원 trex-web 포트폴리오 관리 dcy-007) | [Discovery](./dcy-009_20260627_user-read-asset-catalog-and-daemons.md) | confirmed | | dcy-010_20260627_daemon-scoped-broker-credential-provisioning | 20260627 | daemon-scoped 브로커(KIS) 인증정보 소유·전달 — 다중 인스턴스 trex-core가 daemon별 다른 계좌로 거래 (교차 발원 trex-core 중장기 멀티 인스턴스) | [Discovery](./dcy-010_20260627_daemon-scoped-broker-credential-provisioning.md) | confirmed | | dcy-011_20260628_daemon-crud-and-self-service-credential-api | 20260628 | 데몬 admin CRUD API + 사용자 self-service 브로커 인증정보 관리 API — 소유 스코프 write/rotate/메타데이터(복호화 secret 미반환), 데몬 생성·할당·(비)활성화는 admin (교차 발원 trex-web dcy-009) | [Discovery](./dcy-011_20260628_daemon-crud-and-self-service-credential-api.md) | confirmed | +| dcy-012_20260714_manual-ledger-write-api-owner-scoped | 20260714 | 사용자 수동 장부 입력 API — INVEST/BUFFER/BUY/SELL/WITHDRAW 계약 및 owner-scoped ledger write | [Discovery](./dcy-012_20260714_manual-ledger-write-api-owner-scoped.md) | draft |