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 index 9f27760..a1c2425 100644 --- 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 @@ -3,12 +3,12 @@ - 상태: draft - 문서 ID: dcy-012_20260714_manual-ledger-write-api-owner-scoped -- 이슈명: 사용자 수동 장부 입력 API — INVEST/BUFFER/BUY/SELL/WITHDRAW 계약 및 owner-scoped ledger write +- 이슈명: 사용자 수동 장부 입력 API — INVEST/BUFFER/BUY/SELL/WITHDRAW/PROFIT 계약 및 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` +- Project #7 item: `PVTI_lADOBCBKFM4BbUVHzgyvA9A` — `portfolio-hub: 수동 장부 입력 API 계약 — INVEST/BUFFER/BUY/SELL/WITHDRAW` (문서상 PROFIT 포함 결정 반영; Project title은 후속 정리 가능) - 생성된 REQ 참조: 없음(draft) # 1. 계획 상태 요약 @@ -19,6 +19,7 @@ - `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 조정을 호출한다. + - **사용자 정정(2026-07-14)**: `PROFIT`과 `WITHDRAW`는 snapshot effect(`profit_buffer -= amount`)는 유사하지만, 수익률 계산 의미가 완전히 다르다. `WITHDRAW`는 투자 원금/입금액 회수로 `INVEST - WITHDRAW` 순입금액 계산에 반영되지만, `PROFIT`은 순수 수익 실현 인출로 순입금액 계산에 포함하지 않는다. 따라서 신규 manual API에도 `PROFIT`은 별도 action으로 반드시 필요하다. - `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`를 함께 갱신한다. @@ -33,7 +34,7 @@ - 근거: - 기존 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로 노출할 필요가 있다. + - trex-web dcy-011에서 확정된 UI/제품 요구와 후속 사용자 정정은 `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `PROFIT`, `REVERSAL`을 owner-scoped manual ledger API로 노출할 필요가 있다. # 3. 문제점의 요약 - 현재 상태: @@ -65,11 +66,12 @@ - 사용자가 상승 후 리밸런싱/보관한다: `cash_balance` --(`BUFFER_IN`)--> `profit_buffer`. - 사용자가 수동 매수 기록을 입력한다: `cash_balance` --(`BUY`)--> `asset_quantity`. - 사용자가 수동 매도 기록을 입력한다: `asset_quantity` --(`SELL`)--> `cash_balance`. -- 사용자가 실제 수익/자금을 회수한다: `profit_buffer` --(`WITHDRAW`)--> 외부자금. +- 사용자가 투자 원금/입금액을 회수한다: `profit_buffer` --(`WITHDRAW`)--> 외부자금. 이 금액은 수익률 계산의 순입금액(`INVEST - WITHDRAW`)에 반영된다. +- 사용자가 순수 수익을 실현해 꺼낸다: `profit_buffer` --(`PROFIT`)--> 외부자금. snapshot effect는 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 계약을 정의한다. +- 변경 1 — **manual ledger write API 계약**: user/session token으로 owner-scoped portfolio에 `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `PROFIT`, `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를 정의한다. @@ -103,14 +105,15 @@ - 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-7: `WITHDRAW` action은 투자 원금/입금액 회수 성격의 `profit_buffer -> 외부자금` 이동을 기록한다. 효과: `profit_buffer -= amount`, `cash_balance`/`asset_quantity` 변화 없음. 수익률 계산의 순입금액은 `INVEST - WITHDRAW` 기준이므로 `WITHDRAW`는 순입금액 차감에 반영된다. +- FR-7a: `PROFIT` action은 순수 수익 실현 성격의 `profit_buffer -> 외부자금` 이동을 기록한다. 효과는 `profit_buffer -= amount`, `cash_balance`/`asset_quantity` 변화 없음으로 `WITHDRAW`와 유사하지만, 수익률 계산의 순입금액(`INVEST - WITHDRAW`)에는 포함하지 않는다. - 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 시 완전 차단하지 않는다. 대신 API-level explicit confirmation을 요구한다. `managed_by_daemon_id IS NOT NULL`이고 confirmation flag가 없으면 HTTP 409 `MANUAL_WRITE_REQUIRES_CONFIRMATION`을 반환한다. trex-web은 강한 경고를 표시한 뒤 사용자 명시 확인 시 confirmation flag를 포함해 재요청한다. 허용된 write는 actor, managed_by_daemon_id, confirmation evidence를 ledger/audit metadata에 남긴다. -- FR-14: manual endpoint는 1차 범위에서 새 음수 상태를 만드는 write를 거부한다. `BUY`는 `quantity * price + fee + tax > cash_balance`이면 HTTP 409, `SELL`은 `quantity > asset_quantity`이면 HTTP 409, `BUFFER_OUT`/`WITHDRAW`는 `amount > profit_buffer`이면 HTTP 409, `BUFFER_IN`은 `amount > cash_balance`이면 HTTP 409로 거부한다. `INVEST`는 `amount > 0`이면 허용한다. +- FR-14: manual endpoint는 1차 범위에서 새 음수 상태를 만드는 write를 거부한다. `BUY`는 `quantity * price + fee + tax > cash_balance`이면 HTTP 409, `SELL`은 `quantity > asset_quantity`이면 HTTP 409, `BUFFER_OUT`/`WITHDRAW`/`PROFIT`는 `amount > profit_buffer`이면 HTTP 409, `BUFFER_IN`은 `amount > cash_balance`이면 HTTP 409로 거부한다. `INVEST`는 `amount > 0`이면 허용한다. ## 비기능 요구사항 - NFR-1: ledger append와 portfolio snapshot update는 단일 DB 트랜잭션으로 처리되어야 한다. @@ -125,7 +128,7 @@ - In Scope: - owner-scoped manual ledger write API 계약 - manual ledger history/read API 계약 - - action/effect: `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `REVERSAL` + - action/effect: `INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `PROFIT`, `REVERSAL` - after-snapshot response - owner/disabled/daemon-managed policy - baseline docs and SRS follow-up @@ -152,7 +155,7 @@ - R-5 (Mitigated): `REVERSAL`이 target entry 참조 없이 notes 기반으로만 남아 audit성이 약해질 수 있음. - 완화: 사용자 결정(2026-07-14)에 따라 `target_ledger_entry_id`를 필수로 한다. REQ에서는 컬럼/metadata 저장 위치와 중복 reversal 방지 규칙을 구체화한다. - R-6 (Low): `PROFIT` legacy action과 `BUFFER_IN`/`WITHDRAW` 의미가 사용자 UI에서 혼동. - - 완화: 신규 user-facing API는 trex-web dcy-011 action taxonomy를 기준으로 명명하고, legacy CLI alias와 매핑을 문서화한다. + - 완화: 사용자 정정에 따라 `PROFIT`은 legacy alias가 아니라 신규 user-facing API의 필수 action으로 포함한다. `WITHDRAW`와 snapshot effect는 유사하지만 성과/수익률 계산 classification이 다름을 문서화한다. ## 가정 - A-1: `profit_buffer`는 투자 대기/보관 buffer 역할로 유지한다. @@ -175,7 +178,7 @@ - 종료 조건: 완료 - OQ-3: manual `BUY`/`SELL`에서 잔고/수량 부족을 거부할 것인가, 표시 음수 허용 정책과 맞춰 결과 음수를 허용할 것인가? - 상태: Answered - - 결정: manual endpoint 1차에서는 부족 cash/quantity/buffer를 거부한다. `BUY`는 required cash(`quantity * price + fee + tax`)가 `cash_balance`를 초과하면 HTTP 409, `SELL`은 requested quantity가 `asset_quantity`를 초과하면 HTTP 409, `BUFFER_OUT`/`WITHDRAW`는 amount가 `profit_buffer`를 초과하면 HTTP 409, `BUFFER_IN`은 amount가 `cash_balance`를 초과하면 HTTP 409로 거부한다. `INVEST`는 amount > 0이면 허용한다. + - 결정: manual endpoint 1차에서는 부족 cash/quantity/buffer를 거부한다. `BUY`는 required cash(`quantity * price + fee + tax`)가 `cash_balance`를 초과하면 HTTP 409, `SELL`은 requested quantity가 `asset_quantity`를 초과하면 HTTP 409, `BUFFER_OUT`/`WITHDRAW`/`PROFIT`는 amount가 `profit_buffer`를 초과하면 HTTP 409, `BUFFER_IN`은 amount가 `cash_balance`를 초과하면 HTTP 409로 거부한다. `INVEST`는 amount > 0이면 허용한다. - 사용자 정책 정합: trex-web UI 입력 음수 금지, 기존/결과 표시 음수 허용. 즉 read/display에서는 기존 음수 상태를 숨기지 않지만, manual write가 새 음수 상태를 만드는 것은 1차에서 금지한다. - 근거: 수동 입력은 사전 검증 가능한 operation이고, `QUANTITY_ADJUSTMENT`를 1차 제외했으므로 부족 수량 SELL 허용은 우회 보정 기능이 될 수 있다. - 종료 조건: 완료 @@ -190,14 +193,18 @@ - 상태: 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 확정 + - 상태: Answered + - 결정: `PROFIT`은 legacy alias가 아니라 신규 manual API의 필수 user-facing action으로 포함한다. `WITHDRAW`와 `PROFIT`은 둘 다 `profit_buffer -> 외부자금` 방향으로 buffer를 감소시키지만, 의미와 수익률 계산 반영이 다르다. + - 수익률 계산 의미: 입금 기준 금액은 `sum(INVEST) - sum(WITHDRAW)`로 계산한다. `PROFIT`으로 꺼낸 금액은 순수 수익 실현 인출이므로 이 입금 기준 계산에 포함하지 않는다. + - 근거: portfolio 성과 계산에서 원금 회수(`WITHDRAW`)와 수익 실현(`PROFIT`)은 결과 snapshot effect가 유사해도 완전히 다른 회계/성과 분류다. + - 종료 조건: 완료 # 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-4: Given owner user token, When `WITHDRAW`, Then `profit_buffer_after`가 감소하고 ledger row가 append되며 순입금액 계산에서 `WITHDRAW`가 차감된다. +- S-4a: Given owner user token, When `PROFIT`, 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가 검증된다. @@ -217,7 +224,7 @@ - [x] 부족 잔고/수량 validation policy 확정(OQ-3) — manual write가 새 음수 상태를 만드는 것은 1차 거부 - [x] daemon-managed confirmation contract 확정(OQ-4) — API-level explicit confirmation 강제 - [ ] history response fields 확정(OQ-5) -- [ ] legacy `PROFIT` mapping policy 확정(OQ-6) +- [x] legacy `PROFIT` mapping policy 확정(OQ-6) — `PROFIT`은 신규 manual API 필수 action - [ ] Scope owner: `portfolio-hub-dev` primary, `trex-web/service-dev` consumer, `qc-reviewer` verification - [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 @@ -240,7 +247,7 @@ - ~~부족 cash/quantity 시 validation 거부 vs 결과 음수 허용~~ → manual write는 부족 cash/quantity/buffer 409 거부, read/display 음수 표시는 허용 - ~~daemon-managed portfolio warning/confirmation을 API-level로 강제할지 UI-level로 둘지~~ → API-level explicit confirmation 강제 - history response에 `total_value_after` 포함 여부 -- legacy `PROFIT` action 노출/매핑 정책 +- ~~legacy `PROFIT` action 노출/매핑 정책~~ → `PROFIT`은 신규 manual API 필수 action, 순입금액(`INVEST - WITHDRAW`) 계산에는 미포함 ## CONFIRM - 이 Discovery는 trex-web UI 구현이 아니라 portfolio-hub API 계약/권한/트랜잭션 Discovery임을 확인 @@ -253,7 +260,7 @@ # 11. Discovery Freeze - Handoff Decision: 보류(draft) -- Handoff Rationale: trex-web dcy-011의 backend-first 후속 Discovery로 생성했다. 현행 portfolio-hub 구조와 action/effect는 확인했고, 기존 `/ledger/trades` 유지 + 신규 `/ledger/manual` 추가 방향은 확정했다. 남은 결정은 history fields, legacy PROFIT mapping이다. +- Handoff Rationale: trex-web dcy-011의 backend-first 후속 Discovery로 생성했다. 현행 portfolio-hub 구조와 action/effect는 확인했고, 기존 `/ledger/trades` 유지 + 신규 `/ledger/manual` 추가 방향은 확정했다. 남은 결정은 history fields이다. - Ready for REQ Drafting: false - Confirmed By: 미정 - Confirmed At (KST): 미정