Files

5.4 KiB

id, status
id status
dcy-0007 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에서 추가 제품·정책 결정을 요구하는 항목은 발견되지 않았다.