Files
portfolio-hub/docs/srs/Interface/req-056_v1-portfolios-manual-ledger-api.md
T
Hermes Agent 7b9c23a377 docs(req): approve ledger metadata migration requirement
Confirm REQ-057 for BAT-025 while preserving REQ-056/BAT-024 implementation status. Operational DB execution remains separately approved.
2026-07-15 10:50:25 +09:00

18 KiB

REQ-056: owner-scoped 수동 장부 입력 HTTP API

  • Status: Implemented
  • 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 검증.

CHG-20260714-02

  • Date: 2026-07-14
  • Author: TREX lead (Hermes)

Change Summary

  • Status: Proposed → Approved (confirm-req). dcy-012 confirmed Discovery와 AC-1~19가 test 가능한 구현 기준으로 충분하다고 판정한다.

Intent

  • owner-scoped manual ledger API를 batch delivery 기준으로 확정한다.

Acceptance Criteria Delta

  • Added: 없음
  • Updated: 없음
  • Removed: 없음

Impacted Area Delta

  • 변경 없음. 구현 단계에서 docs/interface-contract.md, docs/runtime-flows.md, docs/data-model.md, API/schema/domain/repository/auth/tests를 갱신한다.

Delivery Trace Delta

  • Batches: BAT-024 후보로 제안
  • Releases: 미정

Revalidation Impact

  • Reverification Needed: no
  • Existing Implementation Invalidated: no
  • Status Recommendation: proceed-to-batch
  • Follow-up Action: suggest-batch-reqs → draft-batch/planning/design/implementation/verification.

Validation Plan

  • AC-1~19를 batch verification에서 API/repository/concurrency/regression evidence로 검증한다.

CHG-20260715-03

  • Date: 2026-07-15
  • Author: TREX lead (Hermes)

Change Summary

  • 운영 적용 방식 보완을 REQ-057로 분리 추적한다. REQ-056의 기존 API 계약, AC-1~19, Implemented status 및 BAT-024 release-candidate 상태는 변경하거나 되돌리지 않는다.

Intent

  • REQ-056 구현에서 사용된 additive portfolio_ledger 컬럼의 운영 DB 적용을 runtime ensure_schema() 자동 DDL에만 의존하지 않도록 명시 Alembic migration과 운영 runbook 요구사항으로 후속 관리한다.

Acceptance Criteria Delta

  • Added: 없음 (REQ-056 AC 변경 없음)
  • Updated: 없음
  • Removed: 없음
  • Follow-on: REQ-057 AC-1~11이 migration, schema/Alembic revision 정합, 운영 preflight/runbook, forward-only recovery, release 결재 게이트를 별도로 정의한다.

Impacted Area Delta

  • REQ-056 구현 영향 범위는 변경 없음.
  • 후속 REQ-057 영향: migrations/versions/, runtime ensure_schema() 호환성, migration/runbook 및 persistence baseline 문서. 실제 변경은 BAT-025 standard에서 수행한다.

Delivery Trace Delta

  • Batches: BAT-024는 REQ-056 구현/검증 release-candidate 상태로 유지. BAT-025 standard가 REQ-057 migration implementation/test/QC/runbook을 담당한다.
  • Releases: 운영 DB upgrade, 서비스 재시작 및 신규 manual ledger API 활성화는 별도 명시 release 결재 전 실행하지 않는다.

Revalidation Impact

  • Reverification Needed: no — REQ-056의 기존 implementation/QC evidence는 무효화하지 않는다.
  • Existing Implementation Invalidated: no
  • Status Recommendation: keep-Implemented
  • Follow-up Action: REQ-057 Proposed 검토·승인 후 BAT-025에서 migration compatibility/data preservation/runbook evidence를 확보한다.

Validation Plan

  • BAT-025에서 fresh DB와 runtime-precreated-schema 환경의 Alembic upgrade, 기존 row/source 보존, alembic current 정합, broker/manual ledger regression, runbook preflight 및 service smoke를 검증한다. 운영 DB 실행은 별도 release 결재 범위다.

Implementation Confirmation

  • Confirmed: 2026-07-14 KST
  • Implementing Batch: BAT-024 — docs/batches/bat-024_20260714_manual-ledger-api/
  • Implementation commit: b3447f6 feat(api): add owner-scoped manual ledger endpoint
  • Developer verification: 594 passed, 12 warnings in 37.24s
  • Independent QC: PASS (deleg_f2337a39) — 594 passed, 12 warnings in 37.29s; focused remediation tests 4 passed.
  • Lead gate: confirm-req-implemented accepted.
  • Release caveat: production DB rollout needs release-gate handling for additive portfolio_ledger columns (source, target_ledger_entry_id, confirmation_evidence, actor_user_id), preferably Alembic migration/runbook instead of relying only on ensure_schema().