Files
portfolio-hub/.proofline/lines/line-0009/dcy-0009.md
T
genichin 996127f215 Preserve ledger venue provenance (#39)
* feat: preserve ledger venue provenance

* fix: satisfy ledger test lint ratchet

* fix: advance image migration inventory
2026-09-19 19:30:32 +09:00

7.5 KiB

id, status
id status
dcy-0009 confirmed

Broker ledger venue provenance preservation

Problem

trex-core는 broker가 확인한 실제 체결 거래소를 LedgerTrade.venue에, 최초 주문 라우팅 거래소를 LedgerTrade.order_venue에 구분하여 durable ledger outbox까지 보존한다. 그러나 PortfolioHubLedger._payload()가 두 값을 HTTP payload에서 누락하고, portfolio-hub의 broker ledger request·저장소·응답 모델에도 대응 필드가 없다. 이 때문에 core 내부에서 보존된 venue provenance가 portfolio-hub 전달 경계에서 소실된다.

기대 상태는 core의 nullable actual execution venue와 original order venue를 추정·변환 없이 portfolio-hub에 전달하고, 기존 broker ledger 행·manual ledger·corporate action·구버전 client의 동작을 깨뜨리지 않으면서 저장·조회하는 것이다.

Evidence

  • E-001 GitHub Issue #32 https://github.com/fourblocks2020/portfolio-hub/issues/32는 LedgerTrade.venue -> execution_venue, LedgerTrade.order_venue -> order_venue mapping, nullable historical compatibility, duplicate metadata 보강, Hub-first rollout과 venue-only 범위를 구현 계획으로 정한다.
  • E-002 trex-core revision bbdacb121c25a7539f6f9f7fe0559fc03d4542eb의 trex_core/application/ledger_port.py는 LedgerTrade.venue: Optional[str]와 LedgerTrade.order_venue: str를 별도 필드로 정의한다.
  • E-003 같은 revision의 trex_core/adapters/persistence/postgresql.py와 migration 024_add_ledger_order_venue.sql은 durable outbox에 venue와 order_venue를 별도 저장·복원하지만, trex_core/adapters/ledger/portfolio_hub.py::PortfolioHubLedger._payload()는 두 값을 HTTP payload에 포함하지 않는다.
  • E-004 portfolio-hub revision 567d662913335a90d0de51b8e0e961b6b166d543의 app/schemas/v1.py::LedgerTradeRequest·LedgerTradeResponse, app/domain/models.py::LedgerEntry, app/repositories/portfolio_repository.py와 app/repositories/postgres.py에는 execution/order venue 계약이 없다.
  • E-005 현재 Alembic head의 portfolio_ledger에는 두 venue column이 없으며 app/db/migrations.py와 Alembic migration이 빈 DB와 legacy DB bootstrap을 함께 소유한다.
  • E-006 portfolio-hub active AC-0032·AC-0034·AC-0035·AC-0037은 unified ledger read, nullable source-specific facts, authorization·manual compatibility와 joined ledger 안정성을 소유하지만 broker venue의 POST validation·persistence·duplicate 보강을 명시하지 않는다. 따라서 새 observable contract는 create 대상이고 기존 AC는 의미를 바꾸지 않는 satisfy 대상이다.
  • E-007 trex-core active AC-0019는 original order_venue와 nullable actual execution venue의 durable 분리, (order_venue=SOR, venue=NULL) 보존, 무추론 원칙과 기존 LedgerWriter 전달 경로를 이미 소유한다. Core 변경은 기존 AC의 의미를 바꾸지 않는 satisfy 구현이다.

Scope

  • S-001 portfolio-hub에 새 active AC를 create하여 broker ledger request가 nullable execution_venue (KRX, NXT)와 nullable order_venue (KRX, NXT, SOR)를 수용하고 다른 값과 execution_venue=SOR를 거부하는 계약을 소유한다.
  • S-002 portfolio_ledger에 nullable venue column과 allowlist constraint를 additive migration으로 추가하고 historical row를 NULL로 유지한다. 빈 DB·legacy DB migration apply/replay와 실제 PostgreSQL round-trip을 검증한다.
  • S-003 새 active AC를 create하여 broker ledger insert, duplicate lookup, domain mapping, POST 응답과 unified GET 응답이 두 venue를 추정·변환 없이 저장·반환하는 계약을 소유한다.
  • S-004 동일 (portfolio_id, order_id, execution_id) duplicate는 portfolio row lock으로 기존 동시성 경계를 유지하면서 기존 ledger row도 FOR UPDATE로 선택한다. 같은 transaction에서 기존 economic payload 비교를 먼저 수행해 불일치면 아무 metadata도 갱신하지 않고 409로 중단한다. Economic payload가 같을 때만 모든 venue의 non-null 충돌을 먼저 검사하고, 충돌이 없으면 NULL column만 incoming non-null 값으로 한 번에 보강한다. Incoming NULL은 기존 값을 지우지 않으며 이 보강은 ledger row를 재생성하거나 cash·quantity·snapshot 회계를 다시 적용하지 않는다.
  • S-005 기존 active AC-0032·AC-0034·AC-0035·AC-0037을 satisfy하여 source-neutral ordering/filter/cursor, nullable source-specific representation, owner-only authorization, manual/corporate-action 및 WAC/PnL compatibility를 유지한다.
  • S-006 trex-core는 active AC-0019 아래 PortfolioHubLedger._payload()가 기존 LedgerTrade.venue를 execution_venue, 기존 LedgerTrade.order_venue를 order_venue로 전송하도록 변경한다. producer, LedgerTrade, outbox schema와 broker routing은 변경하지 않는다.
  • S-007 배포 호환 경계는 portfolio-hub 선배포 후 trex-core adapter 배포이며, 구버전 core payload와 historical/manual/corporate-action row는 venue가 NULL인 상태로 계속 유효하다.
  • S-008 검증은 portfolio-hub schema/API/repository/PostgreSQL migration과 concurrency/idempotency, trex-core exact adapter payload·outbox reload/retry, 양 저장소 교차 contract 201 new -> 200 duplicate -> GET /ledger를 포함한다.

Out of Scope

  • OS-001 broker_ordered_at, core_observed_filled_at 또는 다른 timestamp provenance.
  • OS-002 trex-core LedgerTrade, producer, broker execution adapter, durable outbox schema 또는 주문 routing 변경. PortfolioHubLedger HTTP ledger transport adapter의 두 기존 field 직렬화는 S-006 범위다.
  • OS-003 historical venue 추정·backfill, 특히 order_venue나 시간대로 actual execution venue를 추론하는 동작.
  • OS-004 ledger detail endpoint, idempotency key, broker 주문 실행·라우팅 정책 변경.
  • OS-005 production migration·deployment·runtime restart·실제 broker 주문.

Risks and Unknowns

Risks

  • R-001 mixed-version rollout에서 새 core가 구 Hub에 먼저 배포되면 추가 payload field가 거부되거나 유실될 수 있다. Hub를 먼저 배포하고 기존 payload omission을 허용한다.
  • R-002 duplicate metadata 보강이 기존 회계 transaction path를 재실행하면 잔액·수량·snapshot이 중복 반영될 수 있다. row lock과 단일 transaction에서 provenance-only update를 분리하고 회계 불변 회귀를 실행한다.
  • R-003 repository query의 positional mapping과 joined corporate-action projection에 column을 추가할 때 기존 mapping이 어긋날 수 있다. 명시 projection·mapping 검토와 실제 PostgreSQL mixed-row 회귀로 방지한다.
  • R-004 concurrent duplicates가 서로 다른 non-null venue를 보강하면 last-write-wins로 provenance가 변조될 수 있다. 동일 row를 잠그고 NULL-only update와 non-null conflict를 transaction 안에서 판정한다.

Assumptions

  • A-001 Issue #32에 적힌 venue-only 구현 계획과 사용자의 현재 구현 지시가 제품 범위의 근거이며, 추가 timestamp나 broker lifecycle 변경은 승인되지 않았다.
  • A-002 trex-core active AC-0019의 기존 producer/outbox 계약은 변경 없이 유지되고, 이번 core 변경은 transport 누락을 복구하는 satisfy 작업이다.

Open Questions

없음. Issue #32와 현재 source에서 API naming, 값 allowlist, NULL semantics, duplicate policy, migration policy, rollout 순서와 제외 범위가 모두 결정되어 있다.