49 lines
5.4 KiB
Markdown
49 lines
5.4 KiB
Markdown
---
|
|
id: "dcy-0007"
|
|
status: confirmed
|
|
---
|
|
|
|
# 기업행동 원장이 포함된 보유 원가·전체 원장 조회 복구 (#19)
|
|
|
|
## Problem
|
|
|
|
기업행동 원장이 존재하는 포트폴리오에서 `GET /v1/portfolios/{portfolio_id}/holdings/cost-basis`와 `GET /v1/portfolios/{portfolio_id}/ledger?limit=50`가 PostgreSQL ambiguous-column 오류로 HTTP 500을 반환한다. 기업행동 metadata를 읽기 위해 `portfolio_ledger`와 `portfolio_corporate_actions`를 JOIN한 뒤 base ledger 컬럼 일부를 table alias 없이 참조한 것이 원인이다. 그 결과 보유 원가 replay와 전체 원장 조회라는 서로 다른 read path가 함께 중단되고, downstream인 trex-web 포트폴리오 상세에서 보유 정보와 원장 정보를 표시할 수 없다.
|
|
|
|
## Evidence
|
|
|
|
- `E-001` Source issue `https://github.com/fourblocks2020/trex-web/issues/19`는 두 API의 HTTP 500과 `psycopg.errors.AmbiguousColumn: column reference "ledger_entry_id" is ambiguous` 운영 관찰을 기록한다. 확인 시각의 issue body SHA-256은 `8e382d87dd3358ac516cdd8e5c683f4fa673253c6eb7fc4d0699ed572378ad5b`, `updated_at`은 `2026-09-13T11:28:56Z`다.
|
|
- `E-002` 기준 source는 `portfolio-hub` `c7d8235bdebfa8c2459b0b3300d8ed40a9f465e5`다. `app/repositories/postgres.py`의 `get_all_trades_for_pnl()`은 `portfolio_ledger AS ledger`와 `portfolio_corporate_actions ca`를 JOIN하지만 첫 18개 base projection의 `ledger_entry_id`, `portfolio_id`, `recorded_at` 등을 alias 없이 선택한다.
|
|
- `E-003` 같은 파일의 `list_ledger_entries()`는 두 table을 JOIN하면서 base projection, 동적 `WHERE`의 `portfolio_id`·`source`·cursor tuple, `ORDER BY recorded_at, ledger_entry_id`를 alias 없이 참조한다. 현재 source에서 기업행동 table을 JOIN하는 repository query는 이 두 곳뿐이다.
|
|
- `E-004` `.proofline/lines/line-0006/req-0006.md`는 approved 상태이며 unified ledger read를 소유한다. Active `AC-0032`는 source-neutral latest-first 조회, `AC-0034`는 source별 canonical representation, `AC-0036`은 PostgreSQL query 회귀를 요구한다. 보유 원가/PnL replay read는 현재 active AC가 직접 소유하지 않는다.
|
|
- `E-005` `tests/test_ledger_consolidation_integration.py`는 실제 PostgreSQL repository path를 실행하지만 기업행동 행과 일반 거래 행을 함께 만든 뒤 `get_all_trades_for_pnl()`과 `list_ledger_entries()`를 호출하는 회귀는 없다. Mock 기반 테스트만으로는 PostgreSQL column resolution 오류를 검출할 수 없다.
|
|
|
|
## Scope
|
|
|
|
- `S-001` `get_all_trades_for_pnl()`의 모든 base ledger projection을 `ledger.<column>`으로 한정하여 기업행동 원장이 포함된 포트폴리오의 보유 원가 replay가 정상 동작하게 한다. 이 동작은 기존 active AC가 직접 소유하지 않으므로 지속적인 독립 검증이 가능한 AC를 `create`한다.
|
|
- `S-002` `list_ledger_entries()`의 base ledger projection, dynamic predicate, keyset cursor tuple과 ordering을 `ledger.<column>`으로 한정하고 `portfolio_ledger AS ledger`를 일관되게 사용한다. Unified ledger의 응답 구조·정렬·cursor·권한 의미를 변경하지 않고 기존 `AC-0032`, `AC-0034`, `AC-0036`을 `satisfy`한다.
|
|
- `S-003` 실제 PostgreSQL에서 기업행동 원장과 일반 거래 원장이 공존하는 fixture로 두 repository read를 실행한다. Holdings cost-basis API와 unified ledger API가 HTTP 200을 반환하고 기업행동 metadata 및 일반 거래 행을 기존 schema대로 보존하는 focused API/repository 회귀를 둔다.
|
|
- `S-004` 변경은 ambiguous-column을 제거하는 query qualification과 직접 회귀에 한정한다. 회계 수치, WAC·replay 규칙, source/action 의미, authorization, pagination 또는 API schema를 바꾸지 않는다.
|
|
|
|
## Out of Scope
|
|
|
|
- `OS-001` 기업행동 원장의 생성·수정·삭제, reversal 또는 recovery 동작 변경.
|
|
- `OS-002` broker 주문·취소·체결 처리, producer/outbox 변경 또는 기존 원장 row mutation/backfill.
|
|
- `OS-003` holdings cost basis, PnL, corporate-action 회계 산식의 변경이나 임의 보정.
|
|
- `OS-004` trex-web UI 변경, 배포·release·production mutation 및 특정 production portfolio 데이터 수정.
|
|
|
|
## Risks and Unknowns
|
|
|
|
### Risks
|
|
|
|
- `R-001` SELECT column 순서가 `_row_to_ledger_entry()`와 corporate-action metadata positional mapping에 결속돼 있다. Qualification 과정에서 순서를 바꾸면 값 mapping이 깨질 수 있으므로 기존 순서를 보존하고 반환 객체를 검증한다.
|
|
- `R-002` mock cursor 테스트는 PostgreSQL의 ambiguous-column 판정을 재현하지 못한다. 실제 PostgreSQL 회귀가 없으면 같은 결함이 다시 유입될 수 있으므로 disposable/local integration을 필수 검증 경계로 둔다.
|
|
- `R-003` unified ledger의 dynamic keyset predicate qualification이 누락되면 첫 page는 통과해도 source filter 또는 다음 page가 실패할 수 있다. 기본 조회, source filter와 cursor 경계를 함께 검증한다.
|
|
|
|
### Assumptions
|
|
|
|
- `A-001` Issue #19에 기록된 운영 오류와 현재 source의 unqualified JOIN query가 같은 결함이라는 판단은 SQL text와 PostgreSQL column-name 충돌에 근거한다. 실제 PostgreSQL pre-fix 회귀로 이를 확인한다.
|
|
|
|
### Open Questions
|
|
|
|
없음. Source issue가 query qualification, 유지할 계약, 회귀 범위와 제외 범위를 구체적으로 정했고 현재 source에서 추가 제품·정책 결정을 요구하는 항목은 발견되지 않았다.
|