Files
portfolio-hub/docs/batches/bat-016_20260605_ledger-accounting-consolidation/verification.md
T
genichinandClaude Opus 4.8 2a19b7f058 fix: ledger write idempotency for TimescaleDB hypertable (BAT-016 rework)
BAT-016's initial implementation passed only against mocked repositories and
broke on a real database. Two defects fixed:

1. insert_ledger_trade used `ON CONFLICT (portfolio_id, order_id, execution_id)
   WHERE order_id IS NOT NULL` but portfolio_ledger is a TimescaleDB hypertable,
   which cannot have that partial unique index (must include the partitioning
   column recorded_at). The statement errored against Postgres. Replaced with
   application-level idempotency: SELECT-before-insert inside the existing
   `SELECT ... FOR UPDATE` portfolio row lock, which serializes concurrent
   same-portfolio writes (race-safe). Duplicate returns current authoritative
   portfolio state (dcy-006 OQ-4 b).

2. `with self._connect() as conn:` closes the shared psycopg3 connection on exit,
   leaving the reused repository connection unusable. Switched to
   `with conn.transaction():` (commits without closing).

Added tests/test_ledger_consolidation_integration.py — real-Postgres integration
tests (new / duplicate / negative-balance) that close the mock-only verification
gap. Full suite: 376 passed.

SDLC: REQ-036/037 CHG-20260605-03 reconcile OQ-7 (ii) (DB partial unique →
application-level idempotency) since the hypertable makes the DB constraint
infeasible. REQ-036/037/038 → Implemented; BAT-016 → release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:11:17 +09:00

3.5 KiB

BAT-016 Verification

  • Status: confirmed
  • Batch: BAT-016

재검증 (2026-06-05): 초기 검증은 mock-only 테스트만 근거로 "passed" 표기했으나, verify 단계에서 신 path가 실 DB에서 깨지는 결함 2건이 발견됨(ON CONFLICT on hypertable 불가, 공유 connection close). 재구현 + 실 DB 통합 테스트 후 아래 재검증.

Acceptance Mapping

  • REQ-036:
    • AC-1: \d portfolio_ledger에 ledger_entry_id(Identity, 비-PK — CHG-20260605-03), order_id/execution_id(NULL), side/executed_at(NOT NULL) 존재. 마이그레이션 20260605_0025 + ensure_schema() 일치.
    • AC-2: 수치 8컬럼 NUMERIC(20,8) — 실 DB \d portfolio_ledger로 확인.
    • AC-3 (CHG-20260605-03 재정의): partial unique index/PK는 존재하지 않음(hypertable 제약). idempotency는 application layer + tests/test_ledger_consolidation_integration.py::test_duplicate_broker_write_is_idempotent(실 DB)로 검증 — duplicate 시 row 1건 유지 확인.
    • AC-4: action 컬럼 제거 + side 대체 — 확인.
    • AC-5: ledger_trades 테이블 부재 — 실 DB 확인.
    • AC-6~7: ensure_schema/마이그레이션 backfill 정합 + 전체 회귀 통과.
  • REQ-037:
    • AC-1: test_new_trade_updates_portfolio_and_returns_state(실 DB) — buy 10@80,000 fee 100 → cash_balance_after=199,900, asset_quantity_after=10, portfolios 동일 갱신.
    • AC-2 (OQ-4 b): test_duplicate_broker_write_is_idempotent(실 DB) — 재호출 시 row 재생성 없이 현재 권위 상태 반환.
    • AC-3 (OQ-3 음수 허용): test_negative_balance_allowed(실 DB) — oversell → asset_quantity_after=-999, 거부 없음.
    • AC-4: 응답에 기존 필드 + cash_balance_after/asset_quantity_after 추가 — schema + 실 DB 응답 확인.
    • AC-5: daemon 3단계 인가 보존 — tests/test_daemon_assignment.py 회귀.
    • AC-6: LedgerTrade 참조 0건 — grep 확인, LedgerEntry 단일.
    • AC-7: row lock(SELECT ... FOR UPDATE) + conn.transaction()로 원자성/직렬화.
  • REQ-038:
    • AC-1~2: 레거시 buy/sell deprecated=True + Deprecation: true 헤더 — app/api/portfolios.py 확인.
    • AC-3~4: tests/test_auth.py::test_legacy_buy_sell_routes_emit_deprecation_header 통과.

Evidence

  • Test:
    • 실 DB 통합: .venv/bin/python -m pytest -q tests/test_ledger_consolidation_integration.py → 3 passed (실 Postgres — new/duplicate/음수)
    • mock 회귀: tests/test_v1_portfolios_ledger.py tests/test_daemon_assignment.py tests/test_domain_services.py → 24 passed
    • 전체: .venv/bin/python -m pytest -q → 376 passed, 3 warnings (기존 373 + 통합 3)
  • Manual / 실 DB 직접 검증:
    • 수정 전: ON CONFLICT (portfolio_id, order_id, execution_id) WHERE order_id IS NOT NULL → 실 DB에서 there is no unique or exclusion constraint matching the ON CONFLICT specification 에러 재현.
    • hypertable 제약 확인: CREATE UNIQUE INDEX ... WHERE order_id IS NOT NULL → cannot create a unique index without the column "recorded_at".
    • 수정 후: 통합 테스트로 new/duplicate/음수 정상 동작.
  • Logs:
    • pytest 출력 + docker psql 검증 출력 (터미널).

Result

  • Ready for Release: true (실 DB 통합 검증 기준)
  • Blocking Issues: none
  • Deferred (prod staging): alembic 20260605_0025의 fresh-DB 적용 + DOUBLE→NUMERIC 정밀도/action 분포는 release staging에서 검증(workspace_purpose 메모리). ensure_schema가 이미 적용된 개발 DB에서는 alembic 경로 충돌 가능 — 기존 ensure_schema vs alembic 이중 경로 패턴.