docs(req): draft manual ledger API requirement
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
- 대체됨: 없음
|
||||
- 교차 발원: 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 참조: 없음(REQ draft 진행 예정)
|
||||
- 생성된 REQ 참조: `docs/srs/Interface/req-056_v1-portfolios-manual-ledger-api.md`
|
||||
|
||||
# 1. 계획 상태 요약
|
||||
- 연계 문서 현황:
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# REQ-056: owner-scoped 수동 장부 입력 HTTP API
|
||||
|
||||
- Status: Proposed
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
|
||||
- trex-web이 user/session token으로 자기 소유 portfolio에 수동 장부 entry를 append할 수 있는 portfolio-hub v1 API 계약을 신설한다.
|
||||
- 기존 `POST /v1/portfolios/{portfolio_id}/ledger/trades`는 trex-core/daemon/service-token broker 체결 보고 전용 계약으로 유지하고, 사용자 수동 입력은 신규 `POST /v1/portfolios/{portfolio_id}/ledger/manual`로 분리한다.
|
||||
- manual write는 portfolio snapshot(`cash_balance`, `asset_quantity`, `profit_buffer`)과 `portfolio_ledger` append를 단일 트랜잭션으로 처리하고, trex-web이 장부 이력/후속 상태/수익 실현액을 표시할 수 있는 응답 shape을 제공한다.
|
||||
|
||||
## Requirement
|
||||
|
||||
- 신규 라우트 `POST /v1/portfolios/{portfolio_id}/ledger/manual`을 제공한다.
|
||||
- Bearer user/session token을 요구한다.
|
||||
- 요청자는 대상 portfolio의 `owner_user_id`와 일치해야 한다. owner mismatch는 HTTP 403 fail-closed로 거부한다.
|
||||
- service-token/daemon-token은 본 endpoint에서 HTTP 403 fail-closed로 거부한다. service-token broker trade write는 기존 `/ledger/trades`만 사용한다.
|
||||
- `status='disabled'` portfolio write는 HTTP 409로 거부한다.
|
||||
- request body는 단일 action-dispatch shape을 사용하며 `action` enum을 포함한다.
|
||||
|
||||
| action | 필수 입력 | 선택 입력 | snapshot effect |
|
||||
|---|---|---|---|
|
||||
| `INVEST` | `amount` | `note` | `profit_buffer += amount` |
|
||||
| `BUFFER_OUT` | `amount` | `note` | `profit_buffer -= amount`, `cash_balance += amount` |
|
||||
| `BUFFER_IN` | `amount` | `note` | `cash_balance -= amount`, `profit_buffer += amount` |
|
||||
| `BUY` | `quantity`, `price` | `fee`, `tax`, `note` | `cash_balance -= quantity * price + fee + tax`, `asset_quantity += quantity` |
|
||||
| `SELL` | `quantity`, `price` | `fee`, `tax`, `note` | `asset_quantity -= quantity`, `cash_balance += quantity * price - fee - tax` |
|
||||
| `WITHDRAW` | `amount` | `note` | `profit_buffer -= amount`; 순입금액 계산에서 `sum(INVEST) - sum(WITHDRAW)`의 차감 항목 |
|
||||
| `PROFIT` | `amount` | `note` | `profit_buffer -= amount`; 순수 수익 실현 인출이며 순입금액 계산에는 미포함 |
|
||||
| `REVERSAL` | `target_ledger_entry_id` | `note` | target entry의 반대 effect를 서버가 계산해 append |
|
||||
|
||||
- `QUANTITY_ADJUSTMENT`는 1차 범위에서 제외한다. manual endpoint enum/schema/test는 해당 action을 허용하지 않아야 한다.
|
||||
- 입력 값 검증:
|
||||
- `amount`, `quantity`, `price`는 0보다 커야 한다.
|
||||
- `fee`, `tax`는 생략 시 0으로 취급하며 음수를 허용하지 않는다.
|
||||
- action에 필요하지 않은 핵심 수치 필드가 포함되면 422로 거부하거나 schema에서 명시적으로 무시하지 말고 계약상 실패로 처리한다.
|
||||
- manual write는 새 음수 상태를 만드는 operation을 1차 범위에서 거부한다.
|
||||
- `BUY`: `quantity * price + fee + tax > cash_balance`이면 HTTP 409.
|
||||
- `SELL`: `quantity > asset_quantity`이면 HTTP 409.
|
||||
- `BUFFER_IN`: `amount > cash_balance`이면 HTTP 409.
|
||||
- `BUFFER_OUT`, `WITHDRAW`, `PROFIT`: `amount > profit_buffer`이면 HTTP 409.
|
||||
- `INVEST`: `amount > 0`이면 잔고 부족 검증 없이 허용한다.
|
||||
- daemon-managed portfolio(`managed_by_daemon_id IS NOT NULL`)에는 API-level explicit confirmation을 강제한다.
|
||||
- confirmation 없는 manual write는 HTTP 409 + error code `MANUAL_WRITE_REQUIRES_CONFIRMATION`으로 거부한다.
|
||||
- 사용자가 경고를 확인한 뒤 request body에 confirmation flag/evidence를 포함하면 write를 허용한다.
|
||||
- 허용된 write는 actor user, `managed_by_daemon_id`, confirmation evidence를 ledger/audit metadata 또는 동등한 추적 가능한 저장 위치에 남긴다.
|
||||
- `REVERSAL` 정책:
|
||||
- `target_ledger_entry_id`는 필수이며 같은 portfolio 소속 ledger entry여야 한다.
|
||||
- target entry를 직접 수정/삭제하지 않고 반대 effect의 신규 `REVERSAL` entry를 append한다.
|
||||
- target을 찾을 수 없거나 owner scope를 벗어나면 fail-closed(404 또는 403)한다.
|
||||
- 동일 target entry의 중복 reversal은 HTTP 409로 거부한다.
|
||||
- manual write 성공 응답은 HTTP 201을 기본으로 하며 다음 필드를 포함한다.
|
||||
- `ledger_entry_id`, `portfolio_id`, `action`, 입력 수치(`amount` 또는 `quantity`/`price`/`fee`/`tax`), `note`, `recorded_at`
|
||||
- `cash_balance_after`, `asset_quantity_after`, `profit_buffer_after`, `total_value_after`
|
||||
- `target_ledger_entry_id`(REVERSAL인 경우), `source="manual"` 또는 동등한 source 구분 필드
|
||||
- daemon-managed confirmation이 적용된 경우 confirmation metadata 또는 audit reference
|
||||
- manual ledger history/read 계약은 trex-web 표시를 위해 portfolio별 최근 ledger entries를 반환해야 한다.
|
||||
- 기존 history endpoint를 확장하거나 신규 `GET /v1/portfolios/{portfolio_id}/ledger/manual`/동등 endpoint를 설계할 수 있으나, 응답 item에는 `action`, `amount`, `quantity`, `price`, `fee`, `tax`, `note`, `recorded_at`, `cash_balance_after`, `asset_quantity_after`, `profit_buffer_after`, `total_value_after`가 포함되어야 한다.
|
||||
- `total_value_after`는 ledger entry 기록 시점의 portfolio-hub snapshot 값이며 실시간 mark-to-market 값으로 해석하지 않는다.
|
||||
- 수익률/요약 응답은 `cumulative_profit_withdrawn`을 별도 지표로 노출해야 한다.
|
||||
- 값은 `PROFIT` action 누적액이다.
|
||||
- 순입금액은 `sum(INVEST) - sum(WITHDRAW)`로 계산하며 `PROFIT`은 이 계산에 포함하지 않는다.
|
||||
- ledger append와 portfolio snapshot update는 같은 DB 트랜잭션에서 수행하고, 같은 portfolio의 동시 manual write는 row lock 또는 동등한 concurrency control로 lost update 없이 직렬화한다.
|
||||
- 기존 `/ledger/trades` service-token broker write, `GET /v1/portfolios`, CLI portfolio invest/transfer/buy/sell 흐름은 본 endpoint 추가로 회귀하지 않아야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Given owner user/session token + active portfolio, When `POST /v1/portfolios/{id}/ledger/manual` with `action="INVEST"`, `amount=100000`, Then HTTP 201 + `profit_buffer_after`가 100000 증가하고 `cash_balance_after`/`asset_quantity_after`는 기존 값이며 `portfolio_ledger` row가 append된다.
|
||||
2. Given owner token + active portfolio(`profit_buffer=100000`, `cash_balance=0`), When `BUFFER_OUT amount=40000`, Then HTTP 201 + `profit_buffer_after=60000`, `cash_balance_after=40000`.
|
||||
3. Given owner token + active portfolio(`cash_balance=40000`, `profit_buffer=60000`), When `BUFFER_IN amount=10000`, Then HTTP 201 + `cash_balance_after=30000`, `profit_buffer_after=70000`.
|
||||
4. Given owner token + active portfolio(`cash_balance=1000000`, `asset_quantity=0`), When `BUY quantity=10 price=80000 fee=100 tax=0`, Then HTTP 201 + `cash_balance_after=199900`, `asset_quantity_after=10`.
|
||||
5. Given owner token + active portfolio(`cash_balance=199900`, `asset_quantity=10`), When `SELL quantity=3 price=90000 fee=100 tax=0`, Then HTTP 201 + `asset_quantity_after=7`, `cash_balance_after=469800`.
|
||||
6. Given owner token + active portfolio(`profit_buffer=70000`), When `WITHDRAW amount=20000`, Then HTTP 201 + `profit_buffer_after=50000` and the portfolio return/net-contribution calculation treats the amount as `sum(INVEST) - sum(WITHDRAW)` reduction.
|
||||
7. Given owner token + active portfolio(`profit_buffer=50000`), When `PROFIT amount=10000`, Then HTTP 201 + `profit_buffer_after=40000`, `cumulative_profit_withdrawn` increases by 10000, and net contributed capital is unchanged.
|
||||
8. Given owner token + existing ledger entry in the same portfolio, When `REVERSAL target_ledger_entry_id=<target>` is posted, Then HTTP 201 + a new `REVERSAL` row is appended with the target's opposite snapshot effect and the original target row remains unchanged.
|
||||
9. Given a target ledger entry was already reversed, When the same `target_ledger_entry_id` is reversed again, Then HTTP 409 and no additional ledger row/snapshot update occurs.
|
||||
10. Given non-owner user/session token, When manual write or manual history/read is requested for another user's portfolio, Then HTTP 403 fail-closed and no ledger/snapshot mutation occurs.
|
||||
11. Given service-token/daemon-token, When `POST /v1/portfolios/{id}/ledger/manual` is called, Then HTTP 403 fail-closed; When existing `POST /v1/portfolios/{id}/ledger/trades` is called with valid daemon assignment, Then existing broker trade contract remains unchanged.
|
||||
12. Given disabled portfolio, When owner token posts any manual action, Then HTTP 409 and no ledger/snapshot mutation occurs.
|
||||
13. Given daemon-managed portfolio and no confirmation flag/evidence, When owner token posts any manual action, Then HTTP 409 with error code `MANUAL_WRITE_REQUIRES_CONFIRMATION`; Given confirmation evidence is included, Then the write succeeds and actor/daemon/confirmation audit metadata is stored.
|
||||
14. Given insufficient balances, When `BUY` exceeds cash, `SELL` exceeds quantity, `BUFFER_IN` exceeds cash, or `BUFFER_OUT`/`WITHDRAW`/`PROFIT` exceeds `profit_buffer`, Then HTTP 409 and ledger/snapshot remain unchanged for each case.
|
||||
15. Given invalid action `QUANTITY_ADJUSTMENT` or missing/zero/negative required numeric fields, When manual write is posted, Then HTTP 422 and no ledger/snapshot mutation occurs.
|
||||
16. Given successful manual writes, When manual ledger history/read is requested, Then each returned entry includes `action`, numeric inputs, `note`, `recorded_at`, `cash_balance_after`, `asset_quantity_after`, `profit_buffer_after`, `total_value_after`.
|
||||
17. Given `PROFIT` entries exist, When return/summary response is requested, Then `cumulative_profit_withdrawn` is returned separately and net contribution uses `INVEST - WITHDRAW` without `PROFIT`.
|
||||
18. Given two concurrent manual writes for the same portfolio, When both complete, Then row lock or equivalent control serializes updates without lost update; final snapshot equals sequential application of both accepted entries.
|
||||
19. Given existing regression tests for `/ledger/trades`, `GET /v1/portfolios`, and CLI portfolio invest/transfer/buy/sell, When this REQ is implemented, Then those contracts still pass without semantic regression.
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — 신규 manual ledger write/read endpoint, request/response schema, error codes, `cumulative_profit_withdrawn` 지표 명세.
|
||||
- `docs/runtime-flows.md` — owner-scoped manual write flow, daemon-managed confirmation, transaction/row lock, reversal flow 추가.
|
||||
- `docs/data-model.md` — `portfolio_ledger` manual source/action metadata, reversal target reference, after-snapshot/cumulative profit semantics 보강.
|
||||
- Modules:
|
||||
- `app/api/v1_portfolios.py` 또는 신규 v1 router — `POST /v1/portfolios/{portfolio_id}/ledger/manual` 및 manual ledger history/read endpoint.
|
||||
- `app/schemas/v1.py` — manual request/response schemas, action enum, error response shape.
|
||||
- `app/domain/services.py` — manual action/effect helper, negative-state validation, reversal calculation, `PROFIT` vs `WITHDRAW` classification.
|
||||
- `app/repositories/portfolio_repository.py`, `app/repositories/postgres.py` — transaction/row lock, ledger append, reversal target lookup/dedup, history/read, `cumulative_profit_withdrawn` aggregate.
|
||||
- auth dependencies — user/session owner-scope write guard and service-token fail-closed guard.
|
||||
- Tests:
|
||||
- 신규/갱신 API tests for AC-1~17.
|
||||
- repository/concurrency tests for AC-18.
|
||||
- regression tests for existing `/ledger/trades`, portfolio list, CLI/domain flow compatibility (AC-19).
|
||||
|
||||
## Notes
|
||||
|
||||
- Source: `docs/discovery/dcy-012_20260714_manual-ledger-write-api-owner-scoped.md` confirmed by Justin Ko, Ready for REQ Drafting true.
|
||||
- Project #7 item: `PVTI_lADOBCBKFM4BbUVHzgyvA9A` — Status Requirements.
|
||||
- 본 REQ는 Interface 중심이다. Data 변경(예: reversal target reference 저장 위치, confirmation metadata 저장 위치)이 필요할 수 있으나, 현재는 API 계약과 수락 기준에서 추적하고 batch design에서 기존 `portfolio_ledger` metadata로 충분한지 또는 migration REQ 분리가 필요한지 판정한다.
|
||||
- 기존 `/ledger/trades`는 daemon/service-token broker write 전용으로 보존한다. user/session token manual BUY/SELL은 `/ledger/trades`로 우회 허용하지 않는다.
|
||||
- `PROFIT`과 `WITHDRAW`는 snapshot effect가 모두 `profit_buffer` 감소일 수 있으나 성과 계산 의미가 다르다. `WITHDRAW`는 원금/입금액 회수로 순입금액을 감소시키고, `PROFIT`은 순수 수익 실현 인출로 `cumulative_profit_withdrawn`에만 누적한다.
|
||||
- `total_value_after`는 ledger 기록 시점의 snapshot 값이며, 실시간 가격 재평가 결과가 아니다.
|
||||
- `QUANTITY_ADJUSTMENT`는 후속 Discovery/REQ 후보이며 본 REQ 구현 범위가 아니다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260714-01
|
||||
- Date: 2026-07-14
|
||||
- Author: portfolio-hub-dev (Hermes)
|
||||
|
||||
#### Change Summary
|
||||
- 신규 생성 (Proposed) — dcy-012 confirmed Discovery 근거로 draft-req 실행. owner-scoped manual ledger write/read API 계약을 정의하고 기존 daemon/service-token `/ledger/trades` 계약 보존을 명시한다.
|
||||
|
||||
#### Intent
|
||||
- trex-web manual ledger UI가 소비할 portfolio-hub API surface를 `POST /v1/portfolios/{portfolio_id}/ledger/manual` + action enum으로 고정하고, action/effect/권한/409 validation/daemon-managed confirmation/history response/PROFIT 계산 의미를 수락 가능 요구사항으로 정규화한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- AC-1~19 최초 작성
|
||||
- Updated: 없음
|
||||
- Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/data-model.md` 갱신 예고
|
||||
- Modules: v1 portfolio router, v1 schemas, domain services, portfolio repository/postgres repository, auth dependency 확장
|
||||
- Tests: manual action/effect, owner/non-owner/service-token/disabled/daemon-confirmation, insufficient balance, reversal, history/read, cumulative profit, concurrency, existing ledger trade/CLI regression
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (confirm-req 이후 suggest-batch-reqs)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: confirm-req(팀장 승인) → suggest-batch-reqs. Design 단계에서 reversal/confirmation metadata 저장 위치가 migration 필요 여부를 확정한다.
|
||||
|
||||
#### Validation Plan
|
||||
- pytest TestClient/API tests로 AC-1~17 검증. repository/integration 또는 concurrency harness로 AC-18 검증. 기존 `/ledger/trades`, `GET /v1/portfolios`, CLI/domain 흐름 regression으로 AC-19 검증.
|
||||
+3
-2
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-056`
|
||||
- Current: `REQ-057`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -136,11 +136,12 @@
|
||||
| REQ-053 | admin 데몬 CRUD HTTP API | Interface | Implemented | High | Justin Ko | [req-053](./Interface/req-053_admin-daemon-crud-api.md) |
|
||||
| REQ-054 | 인증정보 관리 보안 격리 — write-only·secret 미반환·로그 미노출 | Non-Functional | Implemented | High | Justin Ko | [req-054](./Non-Functional/req-054_credential-management-security-write-only.md) |
|
||||
| REQ-055 | GET /v1/portfolios 응답에 전략·시장 메타데이터 노출 (additive) | Interface | Implemented | High | Justin Ko | [req-055](./Interface/req-055_v1-portfolios-strategy-market-meta-exposure.md) |
|
||||
| REQ-056 | owner-scoped 수동 장부 입력 HTTP API | Interface | Proposed | High | Justin Ko | [req-056](./Interface/req-056_v1-portfolios-manual-ledger-api.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-07-14: REQ-056 신규 생성 `Proposed` (draft-req) — dcy-012 confirmed Discovery 근거. 신규 `POST /v1/portfolios/{portfolio_id}/ledger/manual` + action enum(`INVEST`, `BUFFER_OUT`, `BUFFER_IN`, `BUY`, `SELL`, `WITHDRAW`, `PROFIT`, `REVERSAL`) owner-scoped user/session API 계약. 기존 `/ledger/trades`는 daemon/service-token broker write 전용으로 보존. 부족 cash/quantity/buffer 409, daemon-managed confirmation 409 `MANUAL_WRITE_REQUIRES_CONFIRMATION`, after-snapshot/history fields, `cumulative_profit_withdrawn` 및 `PROFIT` 순입금액 미포함 의미 포함. confirm-req(팀장 승인) 대기.
|
||||
- 2026-07-04: REQ-055 신규 생성 `Implemented` (draft-req) — trex-tvr-v1 dcy-001 confirmed Discovery 근거. `GET /v1/portfolios` 응답에 `active_strategy_id`·`strategy_name`·`strategy_params`·`markets` additive 노출. DB 마이그레이션 불필요(기존 컬럼·테이블 활용), N+1 금지·파싱 에러 격리(500 금지) 포함. confirm-req(팀장 승인) 대기.
|
||||
- 2026-06-28: REQ-052/053/054 `Approved` → `Implemented` (confirm-req-implemented) — BAT-022 PR #19 mergeCommit 1b78c18, QC PASS-WITH-NOTES(opus, blocker 0). 565 passed(+leak 테스트), AC 전건·secret 미반환·소유 스코프·REQ-051 회귀 PASS. Major 보안 #3(owner 재할당 credential leak) 원자화 수정(83e6dde) 반영 — 단일 트랜잭션+선삭제+leak 방지 테스트 3종. REQ-053 owner 재할당 원자화 수정 명시.
|
||||
- 2026-06-28: REQ-052/053/054 `Approved` → `Approved` (confirm-req 팀장 승인) — dcy-011 추적·AC testable·blocker 0. 소유 스코프 음성(REQ-052 AC-6/9/10)·owner 재할당+인증정보 무효화(REQ-053 AC-4/5)·secret 미반환·로그 미노출·REQ-051 회귀(REQ-054) 확인. admin도 인증정보는 소유 스코프 전용(self-service 결정 정합). 엔드포인트 경로(OQ-1/2) design 위임. 다음=suggest-batch-reqs.
|
||||
- 2026-06-28: REQ-047 `Approved` → `Implemented` (confirm-req-implemented) — BAT-021 PR #18 머지(7164f9e), 실 dev PG 통합검증 AC-1~7 PASS(Step1→backfill→Step2→downgrade), QC PASS-WITH-NOTES(blocker 0). **Step2(PK 전환) 실 운영 DB 적용은 별도 release 결재 게이트**(런북 `docs/runbooks/bat-021-req047-step2-migration-runbook.md` 엄수, 배포순서 Step1→backfill→Step2→코드배포, alembic stamp 사전확인). 운영 적용 시 AC-3/4/5 실운영 evidence 재확보.
|
||||
- 2026-06-28: REQ-046/048/049/050/051 `Approved` → `Implemented` (confirm-req-implemented) — BAT-020 PR #17 머지(d4acfd4), QC PASS-WITH-NOTES(blocker 0), 테스트 통과. REQ-047은 실 운영 DB 적용·실 PostgreSQL 통합검증이 비가역 사장님 결재 게이트 미완 → Approved 유지. AC-3/4/5(Step2 적용 후 schema) real-DB evidence 대기. 결재·적용 시 TODO: ensure_schema()·upsert ON CONFLICT를 (daemon_id,broker)로 갱신.
|
||||
|
||||
Reference in New Issue
Block a user