feat(reconcile): safely seed canonical cost basis

This commit is contained in:
Hermes Agent
2026-07-17 09:57:08 +09:00
parent 18bd8d9f0b
commit 81a853a314
25 changed files with 1689 additions and 66 deletions
@@ -6,7 +6,7 @@
- 작성 시각(KST): 2026-07-14 21:52:01 KST
- 마지막 갱신 시각(KST): 2026-07-14 22:24:41 KST (draft-req)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 후속 Discovery 참조: `dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding` — portfolio-hub cost-basis canonical response parsing 및 안전한 sync avg_price seed P1 follow-up
- 생성된 REQ 참조: REQ-119, REQ-120, REQ-121, REQ-122
# 1. 계획 상태 요약
@@ -0,0 +1,227 @@
# 0. 문서 상태
- 상태: confirmed
- 문서 ID: dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding
- 이슈명: P1 — portfolio-hub cost-basis response contract 정합 및 안전한 sync 평균가 seed
- 작성 시각(KST): 2026-07-16 22:37:30 KST
- 마지막 갱신 시각(KST): 2026-07-16 22:44:20 KST (confirm-discovery)
- 대체됨: 없음
- 선행 Discovery: dcy-034, dcy-037
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: `docs/srs/Integration/req-123_cost-basis-canonical-parse-and-safe-sync-seeding.md`
# 1. 계획 상태 요약
- 연계 문서 현황:
- 선행 `dcy-034`/REQ-105는 broker-held position을 inactive slot에 onboarding하는 `sync`와 portfolio-hub cost-basis seed를 도입했다.
- 선행 `dcy-037`/BAT-057/REL-011은 fetched credential broker truth 및 managed non-holding divergence를 정정했다. 운영 preflight는 해당 credential/config 가설을 정상으로 확인했다.
- portfolio-hub는 `GET /v1/portfolios/{portfolio_id}/holdings/cost-basis`에서 `HoldingsCostBasisResponse.cost_basis_per_unit`을 canonical WAC field로 반환한다.
- 해석:
- trex-core의 realized-PnL adapter는 이미 `cost_basis_per_unit`을 읽지만, sync용 `PortfolioHubReadAdapter.get_cost_basis()`는 legacy `avg_cost`, `wac`, `cost_basis`만 탐색한다.
- 따라서 정상 HTTP 200 response의 `cost_basis_per_unit=124580`도 `None`으로 해석된다. reconcile은 이를 graceful degrade로 취급해 qty/status만 seed하고 `avg_price`를 누락할 수 있다.
# 2. 요구사항 판정 결과
- 판정: 신규 P1 hotfix Discovery
- 근거:
- REQ-105 FR-7은 cost-basis seed 목적을 정의했지만, portfolio-hub canonical response field를 adapter contract와 regression test로 고정하지 못했다.
- dcy-037의 credential source/broker truth 결함과 달리, 이번 결함은 정상 인증·정상 HTTP 200 response를 잘못 parse하는 consumer contract defect다.
- `avg_price`는 slot의 전략/손익 판단에 영향을 주는 값이므로, 양수 broker holding에 avg_price 없이 live sync하는 안전 정책을 별도 명시해야 한다.
# 3. 문제점의 요약
## 현재 상태
- portfolio-hub schema: `HoldingsCostBasisResponse.cost_basis_per_unit: float | None`.
- portfolio-hub endpoint: 보유 수량이 양수면 WAC를 `cost_basis_per_unit`에 반환한다.
- trex-core `PortfolioHubReadAdapter.get_cost_basis()`:
```python
val = body.get("avg_cost") or body.get("wac") or body.get("cost_basis")
```
- trex-core reconcile `sync_slot_to_broker()`:
- adapter가 `None`을 반환하면 avg_price seed를 건너뛰고, dry-run은 `avg_price: null`, `seeded_avg_price: false`를 반환한다.
- live path는 현재 inactive slot을 `holding`으로 전이하고 broker qty를 쓸 수 있다.
- 운영 재현(비밀 없는 response shape):
```text
HTTP 200: {"current_quantity": 5.0, "cost_basis_per_unit": 124580.0}
current adapter result: None
sync --dry-run: holding / qty=5 / avg_price=null / seeded_avg_price=false
```
## 기대 상태
- trex-core가 portfolio-hub의 canonical `cost_basis_per_unit`을 우선 읽고, 정상 양수 WAC를 slot `avg_price` seed에 사용한다.
- legacy field는 호환 필요성이 확인된 경우에만 lower-priority fallback으로 남긴다.
- cost-basis가 실제로 unavailable/malformed/invalid인 경우에는 정상 canonical 응답 parsing failure와 구분 가능한 operator reason을 낸다.
- 양수 broker holding을 inactive slot으로 live onboarding할 때 WAC가 없으면, 승인된 안전 정책에 따라 불완전한 slot write를 방지한다.
## 이해관계자 및 제약
- 사장님: P1 범위 및 live sync fail-closed 정책의 승인자.
- 팀장: Discovery/REQ/Batch/release gate 관리.
- trex-core-dev: adapter, reconcile, CLI/operator output, tests 및 baseline 문서 delivery 담당.
- qc-reviewer: canonical response contract, no-write barrier, regression의 독립 검증 담당.
- portfolio-hub maintainers: existing response contract의 source owner; 이 Discovery에서는 API/model 변경을 수행하지 않는다.
- 제약:
- `sync --live`, DB 직접 수정, token/config 변경은 본 Discovery/REQ/Batch/verification 완료 및 별도 release approval 전 금지한다.
- raw credential, account number, token을 fixtures/logs/문서에 기록하지 않는다.
- portfolio-hub endpoint/schema 및 service-token authorization policy를 변경하지 않는다.
## 주요 사용자 시나리오
1. 운영자가 inactive slot과 broker qty>0을 대상으로 `sync --dry-run`을 실행한다. canonical WAC가 있으면 결과는 `avg_price=<WAC>`, `seeded_avg_price=true`로 표시되며 DB write는 없다.
2. portfolio-hub가 WAC를 `null`로 반환하거나 response가 malformed이면 dry-run은 왜 seed가 불가능한지 안전하게 표시한다. live sync는 확정된 no-write policy를 위반하지 않는다.
3. QC는 portfolio-hub canonical payload와 legacy fallback payload를 각각 주입해 adapter→reconcile 경로를 검증한다. 정상 canonical payload가 `None`으로 degrade하지 않음을 증명한다.
# 4. 이번에 정의할 변경
- 변경 1 — canonical response parsing: `get_cost_basis()`가 `cost_basis_per_unit`을 canonical source로 읽고 수치 검증을 수행한다.
- 변경 2 — compatibility policy: legacy `avg_cost`/`wac`/`cost_basis`의 유지 필요성·우선순위·허용 값(0/negative/null)을 REQ에서 명시한다. canonical field가 존재하지만 invalid한 경우 legacy fallback으로 조용히 마스킹하지 않는다.
- 변경 3 — safe sync behavior: broker qty>0 + avg_price unavailable의 dry-run diagnostics와 live write barrier를 명시한다. 기존 `cost_basis_port` 미연결/네트워크 실패의 generic graceful degrade와 canonical parsing defect를 구분한다.
- 변경 4 — evidence and baseline alignment: HTTP response contract, sync output semantics, `avg_price` source/absence policy를 relevant interface/runtime/operations baseline 문서와 동기화한다.
## 영향 범위
- 코드 영향:
- `trex_core/adapters/portfolio/hub_read.py`: canonical parser 및 typed/result error semantics.
- `trex_core/application/reconciliation.py`: avg_price unavailable 조건의 dry-run/live behavior 및 output.
- `tests/adapters/portfolio/test_hub_read.py`, `tests/application/test_sync.py`: response-contract 및 no-write regression.
- 문서 영향:
- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/operations.md`, 신규 REQ/Batch/verification/release 문서.
- 서비스 영향:
- portfolio-hub는 existing GET response contract의 producer로 참조만 한다. API, schema, DB migration, auth policy 변경은 없다.
# 5. 요구사항 목록
## 기능 요구사항
- FR-1: trex-core cost-basis adapter는 successful response에서 `cost_basis_per_unit`을 canonical WAC field로 parse한다.
- FR-2: canonical response가 valid numeric WAC를 제공하면 `sync --dry-run`과 approved live sync candidate는 동일한 avg_price seed value를 산출한다.
- FR-3: canonical field missing/null/non-numeric/invalid, non-2xx, response-shape failure, transport failure는 서로 구분 가능한 sanitized reason으로 operator surface에 나타난다.
- FR-4: broker qty>0의 inactive slot onboarding에서 avg_price unavailable일 때 dry-run은 no-write preview와 reason을 제공하고, live path는 확정된 fail-closed/explicit-confirmation policy를 적용한다.
- FR-5: legacy aliases가 유지되면 canonical field보다 낮은 우선순위로만 사용하며, canonical invalid value를 legacy value로 은폐하지 않는다.
- FR-6: response contract와 reconcile behavior는 deterministic test fixtures로 고정하고 independent QC가 adapter, application, CLI/output 경로를 재실행한다.
## 비기능 요구사항
- NFR-1 (안전성): cost-basis unavailable 상태에서 broker qty/status만 write해 avg_price 없는 holding slot을 만드는 경로는 승인된 정책 없이 허용되지 않는다.
- NFR-2 (회귀): 정상 `cost_basis_per_unit`, legacy fallback(채택 시), no cost-basis port, port transport failure, current holding update, dry-run no-write가 기존 의도에 맞게 분리 검증된다.
- NFR-3 (관측성): sync output/log는 `seeded_avg_price`, source/absence reason, dry-run/live 여부를 비밀 없이 판별 가능하게 한다.
- NFR-4 (계약 안정성): portfolio-hub HTTP response producer를 변경하지 않으며, trex-core AMQP wire contract와 broker credential source behavior를 변경하지 않는다.
## 범위 경계
- In Scope:
- trex-core consumer의 canonical `cost_basis_per_unit` parsing, legacy compatibility policy, safe sync behavior, regression tests, baseline/runbook 정합.
- Out of Scope:
- portfolio-hub endpoint/schema/authorization/ledger WAC calculation 변경.
- fetched credential/token lifecycle 수정, broker balance/reconcile classification 수정(dcy-037 범위).
- live sync 실행, database direct edit, bulk onboarding, automatic trading/automatic remediation.
- avg_price 재계산 알고리즘 또는 broker WAC를 새 authoritative source로 도입하는 설계 변경.
# 6. 리스크/가정 목록
## 리스크
- R-1 (High): canonical parser만 고치고 WAC absence live-write policy를 두지 않으면, 다른 unavailable failure에서 avg_price 없는 holding write가 계속 가능하다.
- 완화: OQ-1을 Discovery confirmation 전에 결정하고, live no-write assertion을 tests/QC/release gate에 둔다.
- R-2 (Medium): `or`-chain parsing은 `0`/negative/invalid values 및 canonical-invalid+legacy-present response의 의미를 흐릴 수 있다.
- 완화: explicit presence/type/range validation 및 deterministic precedence tests를 REQ에 명시한다.
- R-3 (Medium): producer schema 변경 없이 consumer만 수정하므로 실제 cross-service drift가 재발할 수 있다.
- 완화: canonical fixture는 portfolio-hub schema/endpoint implementation에 근거하고, producer-owned contract reference와 consumer regression을 함께 기록한다.
- R-4 (High): P1 수정 전 live sync를 실행하면 avg_price null slot이 실제 DB에 기록될 수 있다.
- 완화: release 문서에 dry-run → QC → explicit release approval → single-target live execution 순서를 강제한다.
## 가정
- A-1: portfolio-hub response의 `cost_basis_per_unit`은 `HoldingsCostBasisResponse` 및 endpoint implementation의 현재 canonical public field다.
- A-2: broker qty>0 onboarding에서 avg_price는 downstream 전략/손익 판단에 필요한 safety-relevant slot data다.
- A-3: current `sync --dry-run`은 DB write를 하지 않으며, actual live execution은 별도 release gate에서만 허용된다.
## 오픈 질문
- OQ-1: `broker_qty > 0`이고 avg_price/WAC가 unavailable일 때 live sync 정책은 무엇인가?
- 상태: Answered (2026-07-16, Justin Ko)
- 결정: **(a) fail-closed** — status/qty/avg_price를 모두 write하지 않고 non-zero failure로 중단한다. dry-run은 reason과 no-write preview를 제공하되, 평균가 없는 inactive→holding 전이는 차단한다.
- 종료 근거: Justin Ko가 팀장 권고인 fail-closed 정책을 명시 승인했다.
- OQ-2: legacy `avg_cost`/`wac`/`cost_basis` aliases를 지원할 실제 producer가 현재 존재하는가?
- 상태: Answered (2026-07-16, in REQ-123)
- 결정: producer/fixture의 canonical은 `cost_basis_per_unit` 기반이며, 호환성 유지를 위해 `avg_cost` → `wac` → `cost_basis` fallback은 유지한다. 단, canonical key가 유효하지 않으면 legacy fallback을 허용하지 않는다.
- OQ-3: response unavailability reason을 API adapter result type으로 확장할지, existing Optional port를 유지하고 reconcile-local diagnostics를 추가할지?
- 상태: Answered (2026-07-16, in REQ-123)
- 결정: `Optional[float]` port를 유지하고, unavailable는 reconcile-local diagnostics(`seeded_avg_price`, source, reason)로 분리 표시한다.
# 7. 초기 성공 기준
- S-1: canonical fixture `{current_quantity: 5.0, cost_basis_per_unit: 124580.0}`가 adapter에서 `124580.0`으로 parse된다.
- S-2: inactive slot + broker qty=5 + canonical WAC=124580 dry-run이 `after.status=holding`, `after.qty=5`, `after.avg_price=124580`, `seeded_avg_price=true`를 반환하며 slot/warning write가 없다.
- S-3: unavailable/malformed WAC fixture가 source reason을 남기고, OQ-1에서 확정한 live no-write/override behavior를 만족한다.
- S-4: canonical field가 invalid일 때 legacy alias가 있더라도 silent masking하지 않는 precedence regression이 통과한다.
- S-5: existing no-port/transport-failure graceful-degrade behavior, holding update, dry-run no-write and relevant core suite pass evidence가 있다.
## 측정 방식 및 데이터 출처
- S-1: `tests/adapters/portfolio/test_hub_read.py`에서 HTTP response fixture → adapter return assertion.
- S-2/S-3: `tests/application/test_sync.py`에서 in-memory slot/warning store, broker qty, fake cost-basis port를 이용한 dry-run/live no-write assertions.
- S-4: adapter contract test에서 canonical/legacy presence matrix를 명시한다.
- S-5: targeted adapter/application suite 및 affected CLI/output tests, independent QC rerun.
# 8. REQ로 넘기기 전 확인 체크
- [x] producer canonical field 및 consumer mismatch의 source evidence 확인
- 근거: portfolio-hub `HoldingsCostBasisResponse.cost_basis_per_unit`; trex-core `get_cost_basis()` legacy-only parser; synthetic canonical response 재현.
- [x] P1 scope 및 non-goals 초안화
- 근거: producer API 변경, credential/token scope, live execution, DB direct edit를 명시적으로 제외했다.
- [x] 성공 기준의 test seam과 evidence source 정의
- 근거: adapter response fixture, sync application fixture, independent QC rerun을 S-1~S-5에 연결했다.
- [x] OQ-1 live sync WAC-unavailable policy 확정
- 근거: Justin Ko가 2026-07-16 fail-closed 정책을 승인했다. dry-run은 no-write preview, live는 non-zero failure 및 status/qty/avg_price 무기록이다.
- [x] OQ-2/OQ-3: producer canonical 우선순위와 compatibility strategy, unavailability diagnostics는 REQ-123에서 확정했다.
- [x] Freeze 확정 담당자 지정
- 근거: 승인자 Justin Ko가 2026-07-16 Discovery scope와 OQ-1 fail-closed 정책을 confirm했다.
# 9. 파일 처리 결과
- 처리 결과: 생성
- 생성 경로: `docs/discovery/dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md`
- 참조 문서:
- `docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md`
- `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md`
- `trex_core/adapters/portfolio/hub_read.py:get_cost_basis`
- `trex_core/application/reconciliation.py:sync_slot_to_broker`
- `tests/adapters/portfolio/test_hub_read.py`, `tests/application/test_sync.py`
- portfolio-hub `app/schemas/v1.py:HoldingsCostBasisResponse`, `app/api/v1_portfolios.py:get_holdings_cost_basis`
# 10. 사용자 결정 필요 항목 요약
## DECIDE
- OQ-1: WAC가 unavailable인 양수 broker holding의 live sync는 **fail-closed**로 차단한다.
- 승인자: **Justin Ko**, 2026-07-16 KST.
## CONFIRM
- 본 P1을 trex-core consumer hotfix로 채택한다. portfolio-hub producer API는 변경하지 않는다.
- 수정·independent QC·dry-run 성공 증적·별도 release approval 전까지 live sync는 실행하지 않는다.
## DATA
- 실제 운영 대상의 portfolio/slot/account/credential 값은 Discovery/REQ/test fixture에 기록하지 않는다. release gate에서 필요한 대상은 별도 안전한 운영 증적으로만 취급한다.
# 11. Discovery Freeze
## 섹션 참조
- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록)
- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록)
- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준)
- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크)
- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약)
## Freeze 플래그
- Handoff Decision: 승인 — REQ 인계
- Handoff Rationale: canonical field mismatch와 P1 scope를 evidence로 확정했고, Justin Ko가 avg_price unavailable 상태의 live sync를 fail-closed로 차단하는 안전 정책을 승인했다. 이 승인은 REQ drafting을 허용하지만 live sync 실행·배포·DB 변경 승인을 의미하지 않는다.
- Ready for REQ Drafting: true
- Confirmed By: Justin Ko
- Confirmed At (KST): 2026-07-16 22:44:20 KST
+1
View File
@@ -42,3 +42,4 @@
| dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart | 20260625 | 슬롯↔실계좌 차단형 reconcile + sync/verify 명령 — 중장기 cold-start(기존 보유 onboarding). B/C 불일치→슬롯 차단(durable warning) + pause∨warning 게이트 + 운영자 sync/verify (M3) | [Discovery](./dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md) | confirmed |
| dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load | 20260628 | 그날-시작 KIS 인증정보 fetch(portfolio-hub) + fail-safe 거래 로드 — 진실원천 부재 시 신규 거래 미개시, 장중 무중단 (교차 발원 portfolio-hub dcy-010) | [Discovery](./dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md) | confirmed |
| dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix | 20260714 | HOTFIX — fetched credential 운영자 broker truth 정합 및 managed non-holding divergence 분리 (#39, #40, #41) | [Discovery](./dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md) | confirmed |
| dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding | 20260716 | P1 — portfolio-hub cost-basis response contract 정합 및 안전한 sync 평균가 seed | [Discovery](./dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md) | confirmed |