Files
portfolio-hub/docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md
T

28 KiB

0. 문서 상태

이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.

  • 상태: draft
  • 문서 ID: dcy-012_20260714_manual-ledger-write-api-owner-scoped
  • 이슈명: 사용자 수동 장부 입력 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 (문서상 PROFIT 포함 결정 반영; Project title은 후속 정리 가능)
  • 생성된 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 조정을 호출한다.
    • 사용자 정정(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를 함께 갱신한다.
    • 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 기반으로 분리해야 한다.
    • 사용자 결정(2026-07-14): 기존 POST /v1/portfolios/{portfolio_id}/ledger/trades는 trex-core/daemon/service-token broker trade write 전용으로 유지한다. trex-web manual BUY/SELL은 이 endpoint를 재사용하지 않고 신규 manual endpoint를 사용한다. 단, 내부 ledger append + snapshot update 회계 helper는 가능한 한 공유한다.

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, PROFIT, 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)--> 외부자금. 이 금액은 수익률 계산의 순입금액(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, 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를 정의한다.
  • 변경 5 — 기존 domain/CLI와 v1 API 정합화: 현행 apply_invest, apply_transfer, apply_buy, apply_sell 흐름을 재사용/분리할지 결정하고, broker/service-token endpoint와 충돌하지 않도록 source를 분리한다.
  • 변경 6 — 기존 broker trade endpoint 보존: POST /v1/portfolios/{portfolio_id}/ledger/trades는 기존 trex-core/daemon 체결 보고 계약으로 유지하고, manual endpoint와 auth/source/idempotency 의미를 분리한다.

영향 범위

  • 코드 영향:
    • 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-1a: 기존 POST /v1/portfolios/{portfolio_id}/ledger/trades는 daemon/service-token broker trade write 전용으로 유지한다. manual user write를 이 endpoint에 추가 허용하지 않는다.
  • FR-1b: 신규 manual endpoint를 추가한다. 기본 방향은 POST /v1/portfolios/{portfolio_id}/ledger/manual 단일 action-dispatch endpoint이며, request body에 action enum을 포함한다.
  • 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 변화 없음. 수익률 계산의 순입금액은 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. total_value_after는 ledger entry 기록 시점의 portfolio-hub snapshot 값이며 실시간 mark-to-market 값으로 해석하지 않는다.
  • FR-11: manual ledger history 조회 API는 portfolio별 최근 ledger entries를 반환해야 한다. action, amount/quantity/price/fee/tax, note, recorded_at, after-snapshot(cash_balance_after, asset_quantity_after, profit_buffer_after, total_value_after)을 trex-web이 표시할 수 있어야 한다.
  • FR-11a: 수익률 응답/표시를 위해 cumulative_profit_withdrawn을 별도 지표로 노출한다. 이 값은 PROFIT으로 인출한 순수 수익 실현 누적액이며, 순입금액(sum(INVEST) - sum(WITHDRAW)) 계산에는 포함하지 않는다.
  • 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/PROFIT는 amount > profit_buffer이면 HTTP 409, BUFFER_IN은 amount > cash_balance이면 HTTP 409로 거부한다. INVEST는 amount > 0이면 허용한다.

비기능 요구사항

  • 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-4a: HTTP endpoint는 분리하되, ledger append + snapshot update의 action/effect 계산은 shared domain helper 또는 동일 test vectors로 중복/divergence를 방지해야 한다.
  • 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, PROFIT, 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의 의미 변경
    • 기존 POST /v1/portfolios/{portfolio_id}/ledger/trades에 user/session-token manual write 권한을 추가하는 것

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 (Mitigated): manual write와 daemon write가 같은 portfolio에서 혼재되어 운영자가 상태를 오해.
    • 완화: source 분리, daemon-managed API-level explicit confirmation, history 표시. confirmation 없는 요청은 HTTP 409로 거부한다.
  • R-4 (Medium): 기존 CLI/domain 흐름과 신규 API 회계 공식이 divergence.
    • 완화: shared domain helper 추출 또는 동일 test vectors로 검증. manual API는 1차에서 부족 cash/quantity/buffer를 거부하므로 기존 CLI/domain validation 정책과 의도적 차이가 있다면 REQ에서 명시한다.
  • 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에서 혼동.
    • 완화: 사용자 정정에 따라 PROFIT은 legacy alias가 아니라 신규 user-facing API의 필수 action으로 포함한다. WITHDRAW와 snapshot effect는 유사하지만 성과/수익률 계산 classification이 다름을 문서화한다.

가정

  • 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인가?
    • 상태: Answered
    • 결정: 기존 POST /v1/portfolios/{portfolio_id}/ledger/trades는 유지하고, 신규 manual endpoint를 추가한다. 기본 shape은 POST /v1/portfolios/{portfolio_id}/ledger/manual + action enum 단일 action-dispatch endpoint로 한다.
    • 근거: /ledger/trades는 trex-core/daemon/service-token broker 체결 보고이며, manual write는 user/session-token owner-scoped operation이다. auth/source/idempotency/audit 의미가 달라 HTTP endpoint는 분리한다. 단, 내부 회계 helper는 공유한다.
    • 종료 조건: 완료
  • OQ-2: REVERSAL은 target ledger entry id를 필수로 둘 것인가?
    • 상태: Answered
    • 결정: target_ledger_entry_id 필수. REVERSAL은 기존 ledger row를 직접 수정/삭제하지 않고, 지정된 target entry의 반대 effect를 새 entry로 append한다.
    • 근거: 감사 추적, 중복 reversal 방지, 서버 측 정확한 반대 effect 계산을 위해 target reference가 필요하다.
    • REQ 세부화 항목: target이 같은 portfolio 소속인지 검증, 이미 reversed 된 entry의 재 reversal 금지 여부, REVERSAL 자체 reversal 허용 여부, target reference 저장 위치(정식 컬럼 vs metadata)를 확정한다.
    • 종료 조건: 완료
  • 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/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 허용은 우회 보정 기능이 될 수 있다.
    • 종료 조건: 완료
  • OQ-4: daemon-managed portfolio write 시 API가 warning_required/confirmation_token 같은 기계적 확인 필드를 요구할 것인가, 아니면 trex-web UI 경고만으로 충분한가?
    • 상태: Answered
    • 결정: API-level explicit confirmation을 강제한다. daemon-managed portfolio(managed_by_daemon_id IS NOT NULL)에 confirmation 없는 manual write가 들어오면 HTTP 409 MANUAL_WRITE_REQUIRES_CONFIRMATION을 반환한다. 사용자가 trex-web 경고를 확인한 뒤 confirmation flag를 포함해 재요청하면 허용한다.
    • audit: 허용된 write는 actor_user_id, managed_by_daemon_id, confirmation reason/evidence를 ledger/audit metadata에 남긴다.
    • 근거: UI 경고만으로는 API 직접 호출/우회/버그를 막을 수 없고, 자동/daemon 흐름과 수동 보정이 섞일 때 서버 측 감사 근거가 필요하다.
    • REQ 세부화 항목: confirmation flag 이름, 409 error body, confirmation evidence 저장 위치(metadata JSONB vs note/context)를 확정한다.
    • 종료 조건: 완료
  • OQ-5: history response에 total_value_after를 포함할 것인가?
    • 상태: Answered
    • 결정: history response와 manual write after-snapshot에 total_value_after를 포함한다. total_value_after는 ledger entry 기록 시점의 portfolio-hub snapshot 값이며 실시간 mark-to-market 값으로 해석하지 않는다.
    • 추가 결정: 수익률 응답/표시에서 cumulative_profit_withdrawn을 별도 지표로 노출한다. cumulative_profit_withdrawn은 PROFIT action 누적액이며 순입금액(sum(INVEST) - sum(WITHDRAW)) 계산에는 포함하지 않는다.
    • 근거: 장부 이력에서 entry 이후 portfolio 상태를 한 번에 이해할 수 있고, PROFIT은 수익률 분모와 분리된 실현 수익 지표로 보여야 한다.
    • 종료 조건: 완료
  • OQ-6: legacy PROFIT action은 신규 API에서 노출하지 않고 BUFFER_IN/WITHDRAW/INVEST로 대체할 것인가?
    • 상태: 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되며 순입금액 계산에서 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가 검증된다.
  • 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 including total_value_after)가 반환된다.
  • S-10: Given return/summary response, When PROFIT entries exist, Then cumulative_profit_withdrawn이 별도 지표로 반환되고 순입금액(INVEST - WITHDRAW) 계산에는 포함되지 않는다.

측정 방식 및 데이터 출처

  • 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) — 기존 /ledger/trades 유지 + 신규 POST /v1/portfolios/{portfolio_id}/ledger/manual 추가
  • REVERSAL target/reference policy 확정(OQ-2) — target_ledger_entry_id 필수
  • 부족 잔고/수량 validation policy 확정(OQ-3) — manual write가 새 음수 상태를 만드는 것은 1차 거부
  • daemon-managed confirmation contract 확정(OQ-4) — API-level explicit confirmation 강제
  • history response fields 확정(OQ-5) — total_value_after 포함, cumulative_profit_withdrawn 별도 지표
  • 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) 지정

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 → 기존 /ledger/trades 유지, 신규 POST /v1/portfolios/{portfolio_id}/ledger/manual + action enum 추가
  • REVERSAL target ledger id/reference 필수 여부 → target_ledger_entry_id 필수
  • 부족 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 포함 여부 → 포함. cumulative_profit_withdrawn도 별도 지표로 노출
  • legacy PROFIT action 노출/매핑 정책 → PROFIT은 신규 manual API 필수 action, 순입금액(INVEST - WITHDRAW) 계산에는 미포함

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를 담을 별도 컬럼이 필요한지 여부 — OQ-2는 target id 필수로 결정, 저장 위치는 REQ에서 세부화
  • 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는 확인했고, 기존 /ledger/trades 유지 + 신규 /ledger/manual 추가 방향은 확정했다. 주요 Open Question은 모두 해소되었고, 남은 것은 scope owner/freeze 승인이다.
  • Ready for REQ Drafting: false — 주요 OQ는 해소되었으나 confirm-discovery 승인 전
  • Confirmed By: 미정
  • Confirmed At (KST): 미정