feat(reconcile): safely seed canonical cost basis
This commit is contained in:
@@ -24,9 +24,8 @@
|
||||
|
||||
| 모듈 | 변경/소유권 | 구현 경계 |
|
||||
| --- | --- | --- |
|
||||
| `trex_core/application/broker_provider.py` (신규) | `BrokerProvider` protocol, source metadata, credential resolution, broker reuse/invalidation policy의 application ownership | portfolio-hub read port와 KIS/paper construction을 조립하되 CLI 출력/DB write를 소유하지 않는다. |
|
||||
| `trex_core/runtime/broker_factory.py` (신규) 또는 `runtime/daemon.py`의 명시적 factory | `DaemonConfig` → provider assembly. 기존 `_build_broker` 직접 사용자를 provider로 이관 | runtime composition만 담당하며 credential 값을 로그/JSON으로 만들지 않는다. 구현 시 순환 import가 생기면 factory를 별도 runtime module로 둔다. |
|
||||
| `trex_core/runtime/daemon.py` | daemon은 process-lifetime provider에서 broker를 획득하고 scheduler에 같은 provider/reload callback을 주입 | daemon이 별도 env broker를 만들거나 scheduler와 다른 credential source를 해석하지 않는다. |
|
||||
| `trex_core/runtime/broker_provider.py` | `BrokerProvider` protocol, source metadata, credential resolution, broker reuse/invalidation policy의 runtime composition ownership | portfolio-hub read port와 KIS/paper construction을 조립하되 CLI 출력/DB write를 소유하지 않는다. 현재 구현의 `ConfigBrokerProvider`가 daemon과 CLI의 공통 경계다. |
|
||||
| `trex_core/runtime/daemon.py` | `DaemonConfig` → provider assembly 및 process-lifetime provider 주입 | runtime composition만 담당하며 credential 값을 로그/JSON으로 만들지 않는다. legacy `_build_broker`는 env-only compatibility helper로 남아 있으므로 새 broker-read 경로에서 직접 사용하지 않는다. |
|
||||
| `trex_core/application/snapshot_scheduler.py` | day-start fetched credential 성공/실패 및 broker reload를 provider contract로 호출 | scheduler의 entry gate 역할은 유지하되, fetched failure 뒤 env credential으로 trading/broker-read를 계속하는 현재 fallback은 제거한다. |
|
||||
| `trex_core/runtime/cli.py` | `--config`가 있으면 daemon과 동일한 `DaemonConfig.from_config(toml_path, env_file=...)`로 instance TOML을 로드하고, `_cmd_sync`/`_cmd_verify`가 그 config의 같은 provider를 호출 | broker read가 성공하기 전 slot/warning write, `broker_qty_zero`, verify success를 만들지 않으며 CLI가 TOML `credential_fetch_enabled`를 env-only default로 덮어쓰지 않는다. |
|
||||
| `trex_core/adapters/broker/kis.py` | typed sanitized `BrokerBalanceError`, strict `get_balance()`, token/error mapping | credential/token/header/raw account/payload를 error message, log, CLI JSON에 싣지 않는다. |
|
||||
@@ -275,3 +274,54 @@ REQ-119~121 implementation and independent QC evidence가 확정된 뒤에만 RE
|
||||
- [ ] 4-case reconciliation matrix가 existing A/B/C behavior와 additive divergence effect를 구분한다.
|
||||
- [ ] REQ-119~122 AC별 test/document evidence가 §9에 연결되었다.
|
||||
- [ ] live service, credentials, production DB, `sync --live`를 사용하지 않았고 implementation/baseline update authorization과 혼동하지 않았다.
|
||||
|
||||
## 12. Evidence boundary and unresolved gates
|
||||
|
||||
### Source evidence inspected
|
||||
|
||||
The following repository evidence is the basis for this design (paths are relative to
|
||||
this trex-core root):
|
||||
|
||||
- `trex_core/runtime/broker_provider.py`: `ConfigBrokerProvider` owns one broker per
|
||||
process/command, distinguishes `env`/`fetched`/`break_glass`, and converts fetched
|
||||
credential read failures to an allow-listed `BrokerCredentialUnavailable` code.
|
||||
- `trex_core/adapters/broker/kis.py` and
|
||||
`trex_core/application/broker_port.py`: balance reads validate `rt_cd` and
|
||||
`output1`, return `[]` only for a validated empty response, and raise the
|
||||
allow-listed `BrokerBalanceError` codes for failure classes.
|
||||
- `trex_core/domain/slot/repository.py`,
|
||||
`trex_core/domain/slot/in_memory_repository.py`, and
|
||||
`trex_core/adapters/persistence/postgresql.py`: the instance-and-symbol query is
|
||||
read-only, deterministic, and implemented with the same ordering rule in both
|
||||
adapters.
|
||||
- `trex_core/application/reconciliation.py`: the query is called only for an
|
||||
unmatched positive broker holding; the result selects the additive decision code
|
||||
without a persistence mutation.
|
||||
- `tests/runtime/test_broker_provider.py`,
|
||||
`tests/runtime/test_cli_broker_failure.py`, and
|
||||
`tests/adapters/broker/test_kis.py`: fetched fallback prevention, CLI failure
|
||||
boundary, sanitized balance failure, and validated-empty fixtures exist. The
|
||||
repository parity evidence remains fake-driver based, not a live PostgreSQL
|
||||
engine run.
|
||||
|
||||
### Unresolved blockers and explicit non-decisions
|
||||
|
||||
1. **Cross-service contract gate — unresolved for implementation/release:** this
|
||||
repository contains only the consumer-side `PortfolioHubReadPort`. A live
|
||||
portfolio-hub contract/integration fixture proving the credential-version and
|
||||
`trading_enabled` semantics is not available here. Implementation must not
|
||||
change the portfolio-hub API; any required wire change stops at a lead-owned
|
||||
cross-service approval gate.
|
||||
2. **Security gate — unresolved for release:** source-level sanitization and
|
||||
deny-list tests are present, but production log sink, tracing, and portfolio-hub
|
||||
observability configuration are outside this repository and have not been
|
||||
independently reviewed. No credential or raw account evidence may be collected
|
||||
until that review is complete.
|
||||
3. **Migration gate — currently no schema migration is designed:** the lookup is
|
||||
read-only over existing `daily_slot` columns and does not authorize an index or
|
||||
schema change. If production query-plan evidence later requires an index, that
|
||||
is a separate migration-approved batch, not an implementation assumption here.
|
||||
4. **Scope/approval gate — explicit hold:** baseline document edits, production
|
||||
configuration changes, deployment, and `sync --live` remain outside this design
|
||||
gate. The next authorized step is `run-batch-implementation`; it must not be
|
||||
started from this task without the lead's gate transition.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# BAT-058 Design
|
||||
|
||||
- Status: in-progress
|
||||
- Batch: BAT-058
|
||||
|
||||
## 핵심 설계 결정
|
||||
|
||||
### 1) cost-basis 응답 파싱 규칙
|
||||
- `portfolio-hub` 응답에서 `cost_basis_per_unit`(canonical)을 최우선으로 사용
|
||||
- canonical key가 존재하면 `avg_cost/wac/cost_basis`로 fallback 금지
|
||||
- canonical이 없을 때만 legacy chain(`avg_cost`, `wac`, `cost_basis`)를 순차적으로 시도
|
||||
- 유효성: `None` 금지, 숫자 변환 가능, 0보다 큰 값만 허용
|
||||
|
||||
### 2) 진단값 분리
|
||||
- adapter 수준에서 기존 `get_cost_basis()`는 값만 반환
|
||||
- 새 진단 API `get_cost_basis_with_diagnostics()` 추가:
|
||||
- `(value, source, reason)`
|
||||
- public `cost_basis_source`는 `canonical` / `legacy` / `none`만 허용한다. adapter의 `avg_cost` / `wac` / `cost_basis` alias는 `legacy`로 정규화하며, 진단 port의 미승인/비문자 source는 `none`으로 정규화한다.
|
||||
- public `cost_basis_reason`는 `missing`, `invalid`, `http_status_<3-digit>`, `transport`, `invalid_json`, `invalid_payload`, `not_configured`, `diagnostic_exception`, `diagnostic_source_invalid` 또는 유효한 값이 seed되었을 때의 `null`만 허용한다. `http_status_<3-digit>`만 동적 형식이며 provider field name, parser detail, 임의 오류 텍스트는 공개 값으로 허용하지 않는다.
|
||||
- adapter parser의 field-level detail은 private implementation detail이다. sync 결과와 `sync --dry-run/--live` 출력에는 위 source/reason taxonomy만 노출한다.
|
||||
|
||||
### 3) sync fail-closed 정책
|
||||
- `slot.status == INACTIVE`, `broker_qty > 0`, 동기화 결과 `avg_price` 미확보 시:
|
||||
- `dry_run=True`: write 없음 preview 반환 (`after`는 before와 동일), `skipped=True`, `skip_reason=cost_basis_unavailable`
|
||||
- `dry_run=False`: no-write fail-closed. `error='cost_basis_unavailable'`, `skipped=True`, `after=before`
|
||||
- 기존 `HOLDING` 및 `PENDING_*`, `ERROR` 경로는 기존 규칙 유지
|
||||
|
||||
### 4) 로그/출력
|
||||
- sync 결과 객체에 아래 필드 추가: `seeded_avg_price`, `cost_basis_source`, `cost_basis_reason`
|
||||
- CLI 출력에서 위 값 표시
|
||||
- 실패 시 reason을 노출해 운영자가 어떤 fallback/오류가 발생했는지 즉시 확인 가능
|
||||
|
||||
## 비(非)설계 대상
|
||||
- `portfolio-hub` API 스키마 변경/인증 변경
|
||||
- 실제 live sync 수행이나 운영 배포 실행
|
||||
|
||||
## 검증 전략
|
||||
- adapter 단위 테스트: canonical 우선/legacy fallback/invalid canonical non-fallback/이유 분리
|
||||
- sync application 테스트: inactive fail-closed와 dry-run preview, holding 기본 동기화 동작 회귀 보존
|
||||
- 최소 통합: 기존 테스트셋에서 관련 케이스가 모두 통과되는지 확인
|
||||
@@ -0,0 +1,33 @@
|
||||
# BAT-058 Implementation
|
||||
|
||||
- Status: in-progress
|
||||
- Batch: BAT-058
|
||||
|
||||
## 변경 대상
|
||||
|
||||
1. `trex_core/adapters/portfolio/hub_read.py`
|
||||
- `get_cost_basis_with_diagnostics()` 새 메서드 추가
|
||||
- canonical(`cost_basis_per_unit`) 우선 파싱 + legacy fallback 체인 적용
|
||||
- invalid/parse 오류 reason 문자열 반환
|
||||
|
||||
2. `trex_core/application/reconciliation.py`
|
||||
- `sync_slot_to_broker()`에서 cost-basis 진단 메서드 사용
|
||||
- inactive onboarding & no cost basis 시 dry-run/no-write + live fail-closed 구현
|
||||
- sync 결과에 `seeded_avg_price`, `cost_basis_source`, `cost_basis_reason` 추가
|
||||
|
||||
3. `trex_core/runtime/cli.py`
|
||||
- sync 결과 출력에 `cost_basis.seeded`, `cost_basis.source`, `cost_basis.reason`, `error` 표시
|
||||
|
||||
4. `tests/adapters/portfolio/test_hub_read.py`
|
||||
- get_cost_basis canonical/legacy/invalid/오류 reason 검증 추가
|
||||
|
||||
5. `tests/application/test_sync.py`
|
||||
- inactive no-cost_basis live/dry-run 동작
|
||||
- hold/warning scenarios 회귀 보존
|
||||
|
||||
## 상태
|
||||
|
||||
- planning: completed
|
||||
- design: completed
|
||||
- implementation: completed
|
||||
- draft-batch-verification: completed
|
||||
@@ -0,0 +1,29 @@
|
||||
# BAT-058: cost-basis canonical parse 및 안전 sync seed hotfix
|
||||
|
||||
- Status: release-candidate (lead `confirm-batch-verification`, 2026-07-17; Release gate/approval 미실행)
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding
|
||||
- Source REQ: REQ-123
|
||||
- Included REQ: REQ-123
|
||||
- Excluded REQ: 없음
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- REQ-123(제안된 cost-basis canonical parse 및 WAC unavailable fail-closed seed)
|
||||
- portfolio-hub 응답 `cost_basis_per_unit` canonical 우선 파싱 고정
|
||||
- legacy alias fallback/invalid 처리 규칙 구현
|
||||
- dry-run 진단 및 live no-write fail-closed semantics
|
||||
- adapter/application regression test 확장 및 baseline 문서 정합
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- portfolio-hub API/schema/인증/토큰/설정 변경
|
||||
- DB migration / 데이터 삭제 / 실서버 직접 수정
|
||||
- `sync --live`, 배포/운영 재시작 실행은 별도 release 승인 단계
|
||||
|
||||
## Notes
|
||||
|
||||
- 이 batch는 `suggest-batch-reqs` 다음 단계로 생성한 lead-승인 배치입니다.
|
||||
- implementation 전환 직전까지는 설계 산출물을 대체하지 않고, 해당 scope만 구현/검증합니다.
|
||||
@@ -0,0 +1,43 @@
|
||||
# BAT-058 Planning
|
||||
|
||||
- Status: in-progress
|
||||
- Batch: BAT-058
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-123 (Approved): cost-basis canonical 우선 파싱 + safe sync seeding
|
||||
|
||||
## Delivery Plan
|
||||
1. `trex_core.adapters.portfolio.hub_read`에서 `cost-basis` 응답 파싱 순서 정립
|
||||
- 우선순위: `cost_basis_per_unit`(canonical) -> `avg_cost` -> `wac` -> `cost_basis`
|
||||
- canonical가 present이면 legacy fallback를 금지하고 이유를 기록
|
||||
- 유효성 검사(형변환/양수) 실패 시 source/reason 분리
|
||||
2. `sync_slot_to_broker`에 WAC 사용 경로를 cost-basis 진단 결과와 함께 통합
|
||||
- INACTIVE + broker_qty>0 + 가격 미확보 시 dry-run은 preview만
|
||||
- live에서는 no-write fail-closed + exit fail
|
||||
3. `tests/adapters/portfolio/test_hub_read.py`와 `tests/application/test_sync.py`를 AC-기반으로 확장
|
||||
- canonical 우선 규칙, invalid canonical non-fallback, unavailable reason 보존
|
||||
- inactive onboarding no-write dry-run / live fail-closed
|
||||
4. `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/operations.md` baseline 동기화
|
||||
5. `docs/batches/bat-058...` 산출물(implementation + verification) 작성 후 리드 검토 제출
|
||||
|
||||
## Dependencies
|
||||
- modules: `trex_core/adapters/portfolio/hub_read.py`, `trex_core/application/reconciliation.py`, `trex_core/runtime/cli.py`
|
||||
- tests: `tests/adapters/portfolio/test_hub_read.py`, `tests/application/test_sync.py`
|
||||
- docs: `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/operations.md`
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: 계약 precedence 및 fail-closed semantics이 배포 안전성에 직결되어, 미리 수용 기준(재기록/no-write/오류 로그)을 고정해야 함.
|
||||
|
||||
## Milestones
|
||||
1. planning 확정 (현재)
|
||||
2. design 작성
|
||||
3. implementation 완료
|
||||
4. draft-batch-verification 작성 및 테스트 실행
|
||||
5. QC handoff
|
||||
|
||||
## Risks
|
||||
- R1 High: canonical 값 parse 실패 시 legacy fallback를 허용하면 REQ-123 위반 가능성(의도치 않은 source 사용)
|
||||
- R2 High: inactive onboarding에서 가격 미확보일 때 live write가 진행될 경우 거래원장·slot 상태 오염
|
||||
- R3 Medium: 비용/시간 과다로 기존 `cost_basis` 회귀 테스트 간과 시 interface contract drift
|
||||
@@ -0,0 +1,74 @@
|
||||
# BAT-058 Verification
|
||||
|
||||
- Status: release-candidate (lead `confirm-batch-verification`, 2026-07-17)
|
||||
- Batch: BAT-058
|
||||
- Verified By: delivery-runner (implementation evidence) + independent `dev-qc` QC-7
|
||||
- Confirmed By: trex-dev-lead (QC-7 `PASS-WITH-NOTES` 수용; 사용자 지시: “QC-7 PASS-WITH-NOTES로 batch verification을 진행해”)
|
||||
|
||||
## Verification 범위
|
||||
|
||||
- Scope: REQ-123 및 `dcy-038` 기반 hotfix 범위
|
||||
- `trex_core/adapters/portfolio/hub_read.py`
|
||||
- `trex_core/application/reconciliation.py`
|
||||
- `trex_core/runtime/cli.py`
|
||||
- `tests/adapters/portfolio/test_hub_read.py`
|
||||
- `tests/application/test_sync.py`
|
||||
|
||||
## AC 점검 결과
|
||||
|
||||
| AC | 내용 | 결과 | 근거 |
|
||||
|---|---|---|---|
|
||||
| AC-1 | canonical `cost_basis_per_unit` 우선 파싱 + 유효성 검증 | ✅ PASS | `test_get_cost_basis_with_diagnostics_prefers_canonical_when_valid`, `test_get_cost_basis_with_diagnostics_canonical_null_blocks_legacy`, `test_get_cost_basis_with_diagnostics_canonical_non_numeric_blocks_legacy`, `test_get_cost_basis_with_diagnostics_canonical_negative_blocks_legacy` |
|
||||
| AC-2 | canonical invalid 시 legacy masking 금지, canonical 부재 시 legacy fallback 순차 적용 | ✅ PASS | `test_get_cost_basis_with_diagnostics_canonical_invalid_blocks_legacy`, `test_get_cost_basis_with_diagnostics_legacy_chain_fallback_order`, `test_get_cost_basis_with_diagnostics_legacy_skips_invalid_values` |
|
||||
| AC-3 | 성공/실패 이유 분류(reason) | ✅ PASS | `test_get_cost_basis_with_diagnostics_canonical_null_blocks_legacy`, `test_get_cost_basis_with_diagnostics_canonical_non_numeric_blocks_legacy`, `test_get_cost_basis_with_diagnostics_canonical_negative_blocks_legacy`, `test_get_cost_basis_with_diagnostics_missing_field_returns_missing_reason`, `test_get_cost_basis_with_diagnostics_all_legacy_invalid_returns_last_reason` |
|
||||
| AC-4 | non-2xx/transport 실패 사유 분류 | ✅ PASS | `test_get_cost_basis_with_diagnostics_non_2xx_or_transport_keeps_diagnostic` |
|
||||
| AC-5 | dry-run no-write preview (seeded=true/false 반영) | ✅ PASS | `test_sync_dry_run_no_write`, `test_sync_dry_run_inactive_valid_canonical_seed_previews_without_writes` |
|
||||
| AC-6 | dry-run `inactive + broker_qty>0 + avg_price unavailable` no-write preview | ✅ PASS | `test_sync_dry_run_no_write` |
|
||||
| AC-7 | live fail-closed (status/qty/avg_price 모두 미반영, non-zero 종료) | ✅ PASS | independent QC-7이 `test_sync_fail_closed_skipped_error_exits_nonzero_without_writes` 및 legacy-only `0.0`/negative/NaN/+Inf no-write 회귀를 재실행·검토함; CLI rc=1, mutation SQL 0, warning clear 0 확인 |
|
||||
| AC-8 | HOLDING 정상 동기화 회귀 및 seed 적용 유지 | ✅ PASS | `test_sync_holding_qty_mismatch_updated`, `test_sync_seeds_avg_price_from_cost_basis_port`, `test_sync_seeds_avg_price_holding_with_cost_basis`, `test_sync_degrades_gracefully_cost_basis_returns_none` |
|
||||
| AC-9 | response-contract + regression 테스트 전체 통과 | ✅ PASS | `tests/runtime/test_cli_operator.py`, `tests/adapters/portfolio/test_hub_read.py`, `tests/application/test_sync.py` (independent QC-7 affected suite: 113 passed; full suite: 833 passed, 47 skipped) |
|
||||
| AC-10 | public diagnostic source/reason contract normalization (raw field/detail non-leak) 및 release gate | ✅ PASS (release 실행은 별도 gate) | independent QC-7이 `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/operations.md`와 bounded diagnostic/no-leak 구현을 대조함. REQ가 요구하는 actual release artifact·approval·live evidence는 Release 단계에서만 생성/판정하며, 본 batch verification은 이를 승인하거나 대체하지 않음. |
|
||||
|
||||
## 독립 QC-7 및 Lead `confirm-batch-verification`
|
||||
|
||||
- QC-7 실행: 2026-07-17 00:30:22 UTC 시작, supervised child exit `0`.
|
||||
- QC verdict: `PASS-WITH-NOTES`.
|
||||
- QC-7 독립 재실행: legacy-invalid focused `8 passed, 19 deselected`; affected suite `113 passed`; full suite `833 passed, 47 skipped`; Ruff `All checks passed`; compileall exit `0`; `git diff --check` exit `0`.
|
||||
- Lead 판정: 사용자 지시에 따라 QC-7 `PASS-WITH-NOTES`를 수용하여 BAT-058의 `confirm-batch-verification`을 완료하고 `release-candidate`로 승격한다.
|
||||
- Note 해소: REQ-123 AC-10의 release artifact/explicit approval/dry-run evidence/live execution은 **Release 단계의 별도 gate**다. 이 batch confirmation은 release approval, `sync --live`, 배포, credential 접근을 승인하지 않는다.
|
||||
- Ledger note: 이전 QC 반복 횟수 표기는 과거 handoff 사이에 불일치가 있어 이 verification의 판정 근거로 사용하지 않는다. 이번 confirmation의 근거는 QC-7의 독립 verdict와 위 재실행 증적이다.
|
||||
|
||||
## 실행 커맨드 및 실제 재실행 결과
|
||||
|
||||
아래는 delivery worktree에서 실행한다. 공유 가상환경은 worktree의 상위 repository root에 있으므로 `git rev-parse`로 worktree root를 해석해 사용한다.
|
||||
|
||||
```bash
|
||||
WORKTREE="$(git rev-parse --show-toplevel)"
|
||||
PYTHON="${TREX_CORE_PYTHON:-$(cd "$WORKTREE/../.." && pwd)/.venv312/bin/python}"
|
||||
cd "$WORKTREE"
|
||||
|
||||
"$PYTHON" -m pytest tests/runtime/test_cli_operator.py tests/adapters/portfolio/test_hub_read.py tests/application/test_sync.py -q
|
||||
"$PYTHON" -m pytest -q
|
||||
"$PYTHON" -m ruff check trex_core/runtime/cli.py trex_core/adapters/portfolio/hub_read.py trex_core/application/reconciliation.py tests/runtime/test_cli_operator.py tests/adapters/portfolio/test_hub_read.py tests/application/test_sync.py
|
||||
"$PYTHON" -m compileall -q trex_core/runtime/cli.py trex_core/adapters/portfolio/hub_read.py trex_core/application/reconciliation.py tests/runtime/test_cli_operator.py tests/adapters/portfolio/test_hub_read.py tests/application/test_sync.py
|
||||
```
|
||||
|
||||
- focused pytest (2026-07-17, QC-5 재실행): `105 passed in 0.28s`
|
||||
- full pytest (2026-07-17, QC-5 재실행): `825 passed, 47 skipped in 1.71s`
|
||||
- ruff (2026-07-17, QC-5 재실행): `All checks passed!`
|
||||
- compileall (2026-07-17, QC-5 재실행): `compileall: PASS` (exit 0; quiet mode)
|
||||
- `git diff --check` (2026-07-17, QC-5 재실행): `PASS` (exit 0; no output)
|
||||
- QC-6 targeted pytest (2026-07-17): `tests/application/test_sync.py -q -k legacy_only_invalid_wac` → `8 passed, 19 deselected in 0.05s`.
|
||||
- QC-6 focused pytest (2026-07-17): `tests/runtime/test_cli_operator.py tests/adapters/portfolio/test_hub_read.py tests/application/test_sync.py -q` → `113 passed in 0.29s`.
|
||||
- QC-6 full pytest (2026-07-17): `pytest -q` → `833 passed, 47 skipped in 1.72s`.
|
||||
- QC-6 ruff (2026-07-17): 대상 6개 source/test 파일 → `All checks passed!` (exit 0).
|
||||
- QC-6 compileall (2026-07-17): 대상 6개 source/test 파일 → exit 0 (quiet mode; no output).
|
||||
|
||||
## 미해결/미실행 항목
|
||||
|
||||
- 실운영 배포, `sync --live` 실행, DB 직접 수정은 실행하지 않음.
|
||||
- 본 문서는 implementation self-approval가 아니다. 독립 QC-7 `PASS-WITH-NOTES`와 lead `confirm-batch-verification`까지 반영한 batch verification 기록이며, release approval은 포함하지 않는다.
|
||||
|
||||
## 위험/차단 요인
|
||||
|
||||
- 없음. 단, Release 단계에서 REQ-123 AC-10의 explicit release approval, 단일 대상 live 실행 승인, rollback 기준, post-release monitoring plan을 별도 `draft-release` 산출물로 점검해야 한다.
|
||||
@@ -63,3 +63,4 @@
|
||||
| BAT-053 | standard | release-candidate | REQ-112, REQ-113, REQ-114, REQ-115 | dcy-036 | [bat-053](./bat-053_20260628_daystart-credential-fetch-failsafe/) |
|
||||
| BAT-054 | standard | in-progress | REQ-113 (AC-2/AC-4 보강), REQ-115 (AC-1) | dcy-036 | [bat-054](./bat-054_20260628_daystart-credential-kis-inject/) |
|
||||
| BAT-057 | standard | release-candidate | REQ-119, REQ-120, REQ-121, REQ-122 | dcy-037 | [bat-057](./bat-057_20260714_operator-broker-truth-hotfix/) |
|
||||
| BAT-058 | standard | release-candidate | REQ-123 | dcy-038 | [bat-058](./bat-058_20260716_cost-basis-safe-sync-seed/) |
|
||||
|
||||
+1
-1
@@ -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. 계획 상태 요약
|
||||
|
||||
+227
@@ -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
|
||||
@@ -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 |
|
||||
|
||||
@@ -620,3 +620,17 @@ trex-core가 `source="trex-core"`, `severity="critical"`로 발행하는 초기
|
||||
- 2026-06-10 :: BAT-031 (dcy-021 포지션 사이징) — 매수 intent 가 **holding 슬롯 대상이면 증액**(qty+price 추가 매수; M 캡 미적용); 매도 intent 에 **`qty`(부분 매도, 선택)** 추가 — 지정 시 `min(qty, 브로커잔고)` 부분 매도(잔량 holding), 미지정 시 전량(→inactive). 상태머신 13 전이(holding→pending_buy). avg_price 증액 가중평균·부분 매도 cost basis 유지. (REQ-066/057/061/062)
|
||||
- 2026-07-04 :: BAT-055 (dcy-001/002 v3 FR-8, REQ-116) — `trex.slots.req` additive 확장: 요청에 선택적 `strategy: str` 필터 추가(exact-match, `filter` AND 조합, `available_slots` 불변 — REQ-073 AC-6); 응답 슬롯 행에 `strategy_name`(null)/`strategy_params`({})/`markets`([]) 3필드 추가(PortfolioItem/hub 정본, safe default). 기존 소비자(orb·tsl) backward compatible(unknown 필드 무시). REQ-074 AC-5 additive 재도입(dcy-025 OQ-4 후속 확정). PortfolioItem에 `strategy_name`/`markets` 필드 추가 + hub_read.py 파싱 확장.
|
||||
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — `OrderRequest.venue: str = "KRX"` additive 추가(기존 위치 인수 순서 불변 — AMQP wire 계약 영향 없음, 브로커 어댑터 계층 DTO까지만). KIS 어댑터: submit_order/cancel_order body에 `EXCG_ID_DVSN_CD` 항상 명시 전송(기존 미전송 → KRX 명시화 견고성 향상), cancel_order/get_current_price 시그니처 venue/market 파라미터 추가(기본값 KRX/"J" — 기존 호출자 무영향). poll_fill TR 유지(NXT 체결 포함 여부 미확인 open item). paper broker 시그니처 정합(venue/market 무시). 실측 근거: 2026-07-06 NXT 주문/취소 rt_cd=0 확인.
|
||||
|
||||
|
||||
## REQ-123 Contract Addendum: cost-basis canonical parsing & sync diagnostics
|
||||
|
||||
`trex-core` REQ-123 적용 시, portfolio-hub cost-basis contract는 다음을 준수한다.
|
||||
|
||||
- `GET /v1/portfolios/{portfolio_id}/holdings/cost-basis` 응답의 `cost_basis_per_unit`(canonical)을 먼저 확인한다.
|
||||
- canonical 값이 유효한 양수 수치면 이를 `avg_price` seed 후보로 사용한다.
|
||||
- canonical key가 존재하지만 null / non-numeric / non-positive / 범위 외이면 legacy fallback(`avg_cost` → `wac` → `cost_basis`)은 시도하지 않는다.
|
||||
- canonical이 부재한 경우에만 approved legacy 순서로 fallback을 검증한다.
|
||||
- canonical/legacy parsing 실패는 실패 원인을 reason 코드(`cost_basis_reason`)로 분리해 운영 진단으로 노출한다.
|
||||
- `sync`/`sync --dry-run` 결과에는 `seeded_avg_price`(bool), `cost_basis_source`(`canonical`/`legacy`/`none`), `cost_basis_reason` 필드가 들어간다. `cost_basis_reason`의 공개 값은 `missing`, `invalid`, `http_status_<3-digit>`, `transport`, `invalid_json`, `invalid_payload`, `not_configured`, `diagnostic_exception`, `diagnostic_source_invalid` 또는 유효한 값이 seed되었을 때의 `null`로 한정한다.
|
||||
- `http_status_<3-digit>`만 동적 형식이다. provider field name, parser detail, 임의 오류 텍스트는 공개 `cost_basis_reason` 계약 값이 아니다.
|
||||
- 비밀(credential/token/계좌정보)은 로그/출력에 노출하지 않는다.
|
||||
|
||||
@@ -383,3 +383,17 @@ pause 후 동작:
|
||||
- `docs/interface-contract.md` — RabbitMQ 토폴로지 + 메시지 payload + reason/actor 어휘
|
||||
- `docs/runtime-flows.md` — 흐름 다이어그램(스냅샷/intent RPC/이벤트/집행 루프/로깅)
|
||||
- `.claude/skills/run-trex-core-daemon/SKILL.md` — AI 에이전트용 실행 레시피(이 런북의 사실 자료 출처)
|
||||
|
||||
|
||||
### `sync` 출력 진단 포맷 (REQ-123)
|
||||
|
||||
`trex-core sync [--dry-run|--live]` 실행 시 `--json` 또는 일반 출력에서 다음 진단 필드로 no-write/fail-closed를 확인한다.
|
||||
|
||||
- `seeded_avg_price`: true면 seed된 avg_price가 반영 대상인지 표시
|
||||
- `cost_basis_source`: `canonical` / `legacy` / `none`
|
||||
- `cost_basis_reason`: `missing`, `invalid`, `http_status_<3-digit>`, `transport`, `invalid_json`, `invalid_payload`, `not_configured`, `diagnostic_exception`, `diagnostic_source_invalid` 또는 유효한 값이 seed되었을 때의 `null`. `http_status_<3-digit>`만 동적 형식이며 provider field name, parser detail, 임의 오류 텍스트는 출력 계약 값이 아니다.
|
||||
- `skip_reason`: `cost_basis_unavailable`, `broker_qty_zero`, `status_in_flight:*` 등
|
||||
- `error`: live fail-closed (`cost_basis_unavailable`) 등 write 중단 사유
|
||||
- `before`/`after`는 write/미리보기 비교에 사용하며, dry-run은 `warning_cleared=False`로 고정
|
||||
|
||||
`sync --dry-run`은 실제 DB 쓰기·warning clear를 하지 않으므로 운영자가 미리보기를 확인한 뒤 직접 승인된 live 실행으로 전환한다.
|
||||
|
||||
@@ -19,3 +19,4 @@
|
||||
| REL-009 | app-service | feedback-captured | BAT-047, BAT-048, BAT-049 | dcy-031/OPS-005 (이벤트 발행 신뢰성 — idle keepalive·재시도·DLQ 경보) + dcy-032 (체결가 0/None 백필 가드·WARN 로깅) + dcy-033 (알람 emitter + trex.alerts exchange 1단계) + dcy-017 후속(무데이터/죽은 종목 위생 런북 docs-only) — M2 통합 릴리즈. 환류: OPS-005 해소 확인, slot_filled NO_ROUTE/DLQ 신규 발견(M3 Discovery 입력) | [rel-009](rel-009_20260625_event-reliability-fill-guard-alarm-runbook.md) |
|
||||
| REL-010 | app-service | released | BAT-050, BAT-051, BAT-052, BAT-053, BAT-054 | dcy-034 (차단형 reconcile + sync/verify cold-start) + dcy-035 (WAC 실현손익 운영리포트) + dcy-036 (KIS 인증정보 휴면 배포 — credential_fetch_enabled=OFF) — M3 통합 릴리즈. 배포 revision `314047b`(2026-06-30). verify ImportError 핫픽스 `51cc3cc` 포함. | [rel-010](rel-010_20260629_reconcile-coldstart-wac-report-credential-dormant.md) |
|
||||
| REL-011 | app-service | draft | BAT-057 | dcy-037 (operator broker truth / managed non-holding divergence hotfix) — **M5 trex-core P0 release**. Source pin `6d90f89`; production daemon restart와 read/dry-run smoke만 대상이며 `sync --live`, config activation, DB migration은 별도 gate. | [rel-011](rel-011_20260716_m5_operator-broker-truth-hotfix.md) |
|
||||
| REL-012 | app-service | draft / release-pending | BAT-058 | dcy-038 / REQ-123 (cost-basis canonical parse + unavailable WAC safe sync seed) — **긴급 consumer hotfix**. Immutable source pin은 commit/push 후 확정하며, daemon restart와 read/dry-run smoke만 release scope; `sync --live`, config activation, DB migration은 별도 gate. | [rel-012](rel-012_20260717_cost-basis-safe-sync-seed-hotfix.md) |
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# REL-012: cost-basis canonical parse 및 안전 sync seed 긴급 핫픽스
|
||||
|
||||
- Status: **Draft / Release-Pending**
|
||||
- Profile: `app-service`
|
||||
- Priority: **urgent**
|
||||
- Owner: 팀장 / 운영팀
|
||||
- Included batch: **BAT-058** (`REQ-123`)
|
||||
- Source discovery: [`dcy-038`](../discovery/dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md)
|
||||
- Batch verification: [`BAT-058 verification`](../batches/bat-058_20260716_cost-basis-safe-sync-seed/verification.md) — lead `confirm-batch-verification`, QC-7 `PASS-WITH-NOTES`
|
||||
- Operations handoff: [`REL-012 deployment request`](../runbooks/rel-012-cost-basis-safe-sync-seed-ops-handoff.md)
|
||||
|
||||
## Release intent
|
||||
|
||||
portfolio-hub의 기존 cost-basis 응답에서 canonical `cost_basis_per_unit`을 우선 해석하고, broker quantity가 양수인 inactive-slot onboarding에서 WAC/avg_price를 얻지 못하면 live sync가 slot status/qty/avg_price/warning을 쓰지 않은 채 non-zero로 fail-closed하도록 한다. dry-run은 sanitized source/reason과 no-write preview를 제공한다.
|
||||
|
||||
## Source publication boundary
|
||||
|
||||
| Item | Current evidence | Release constraint |
|
||||
| --- | --- | --- |
|
||||
| Remote `origin/main` | `18bd8d9f0b7bb76b08303bbf8743e6ddf916de89` (2026-07-17 fetch) | BAT-058 source is **not included** in this remote revision. |
|
||||
| BAT-058 candidate | delivery worktree `sp/trex-bat-058-lead-kickoff-delivery`, currently uncommitted | A local worktree is not a deployable source pin. |
|
||||
| Immutable deploy target | **PENDING_COMMIT_AND_PUSH** | Git-ops must publish an exact reviewed commit SHA and verify that the remote tree contains this REL/runbook before ops execution. |
|
||||
|
||||
This document is a release draft and a preflight request only. It does **not** authorize deployment, daemon restart, broker/portfolio access, `sync --live`, database writes, configuration activation, or credential access.
|
||||
|
||||
## Included scope
|
||||
|
||||
1. `cost_basis_per_unit` canonical priority parsing and approved legacy compatibility chain (`avg_cost` → `wac` → `cost_basis`) only when canonical is absent.
|
||||
2. Canonical invalid/null/non-numeric/zero/negative values are not silently masked by a legacy value; diagnostic reasons remain sanitized.
|
||||
3. Inactive slot + positive broker quantity + unavailable WAC: dry-run no-write preview; live non-zero fail-closed with no slot/warning mutation.
|
||||
4. Existing HOLDING reconciliation remains compatible; sanitized CLI/operator diagnostics preserve `seeded_avg_price`, source, and absence reason.
|
||||
|
||||
## Explicit exclusions / no-go
|
||||
|
||||
- portfolio-hub API/schema/auth changes, token/config changes, dependency upgrade, DB migration, direct DB edit/delete, AMQP wire/topology change.
|
||||
- Any `sync --live`, `--yes` live write, automatic remediation, order action (`enter`/`exit`/`cancel`), or target portfolio selection.
|
||||
- Changing `credential_fetch_enabled`, break-glass mode, instance configuration, endpoint, or secrets.
|
||||
- Strategy daemon deployment.
|
||||
|
||||
A single-target `sync --live --yes` remains a separately approved post-release action even after this source rollout. It must not be used as a deployment smoke shortcut.
|
||||
|
||||
## Draft verification evidence
|
||||
|
||||
| Check | Result | Evidence / limitation |
|
||||
| --- | --- | --- |
|
||||
| Independent QC-7 | **PASS-WITH-NOTES** | Legacy-invalid focused `8 passed, 19 deselected`; affected suite `113 passed`; full suite `833 passed, 47 skipped`; Ruff/compileall/diff hygiene pass. |
|
||||
| Release-prep full regression | **PASS** | 2026-07-17 delivery worktree: `833 passed, 47 skipped in 1.88s`. |
|
||||
| Focused Ruff | **PASS** | Changed adapter/reconcile/CLI and relevant tests: `All checks passed!`. |
|
||||
| Focused compile check | **PASS** | Six changed source/test paths, `compileall -q`, exit `0`. |
|
||||
| Live broker, live PostgreSQL, production deploy | **NOT EXECUTED** | Outside batch verification and this draft; no claim is made. |
|
||||
| Source publication | **PENDING** | No immutable remote candidate SHA exists yet. |
|
||||
|
||||
## Release approval gates
|
||||
|
||||
All items must be satisfied before operations executes the runbook:
|
||||
|
||||
- [ ] Git-ops publishes the reviewed source and these release documents; lead records the exact remote immutable target SHA.
|
||||
- [ ] Operations verifies that exact target SHA contains REL-012 and its runbook, and captures the actual deployed revision as `ROLLBACK_REVISION`.
|
||||
- [ ] User/approver explicitly authorizes production execution and the approved off-market window is recorded.
|
||||
- [ ] Operations confirms the source checkout is clean; existing runtime configuration and credential/break-glass modes will not change.
|
||||
- [ ] Operations confirms no dependency or migration delta exists between `ROLLBACK_REVISION` and the exact target SHA.
|
||||
- [ ] Operations records the approved supervisor method, instance key (redacted as necessary), and post-deploy observation owner.
|
||||
|
||||
## Rollout and rollback policy
|
||||
|
||||
1. Use the pinned immutable source only; use the host’s existing approved supervisor to restart the same daemon with unchanged approved configuration.
|
||||
2. After startup acceptance, run only the read/dry-run commands in the REL-012 runbook.
|
||||
3. Stop and roll back on startup failure, traceback/error regression, unexpected credential/source-mode behavior, unexpected mutation indication, or smoke output that violates the no-write/fail-closed contract.
|
||||
4. Roll back by restoring the actual captured `ROLLBACK_REVISION` and restarting with unchanged configuration. Do not run database rollback commands, direct DB edits, or live sync.
|
||||
|
||||
## Evidence required for `confirm-release`
|
||||
|
||||
Operations returns a secret-free record containing: approver/window/operator; `ROLLBACK_REVISION` and target SHA; clean-checkout/preflight result; restart/startup result; dry-run smoke and `verify` exit/status summaries; observed no-write/fail-closed classifications; and rollback disposition. The lead then reviews that evidence before changing REL-012 from Draft / Release-Pending.
|
||||
@@ -0,0 +1,115 @@
|
||||
# REL-012 cost-basis safe sync seed hotfix — deployment runbook
|
||||
|
||||
- Release: `REL-012`
|
||||
- Target source: **PENDING_COMMIT_AND_PUSH** (replace only with the approved immutable remote SHA)
|
||||
- Execution owner: 운영팀
|
||||
- Approval required: team lead review + user production execution approval
|
||||
- Safety classification: daemon restart; no DB migration; read/dry-run smoke only
|
||||
|
||||
> Do not record credentials, tokens, account numbers, DSNs, `.env` content, raw broker/portfolio responses, or command lines containing secrets. Use `[REDACTED]`.
|
||||
|
||||
## 1. Hard boundaries
|
||||
|
||||
Allowed only after every REL-012 approval gate is checked:
|
||||
|
||||
- Check out the approved immutable source and restart the existing trex-core daemon through the host’s approved supervisor.
|
||||
- Inspect startup status/logs and execute `sync --all --dry-run --json` plus `verify --json` using the unchanged approved instance configuration.
|
||||
|
||||
Not allowed:
|
||||
|
||||
- `sync --live`, `--yes` with a live write, automatic remediation, `enter`, `exit`, `cancel`, target portfolio selection, or any DB write.
|
||||
- Changes to credential/break-glass mode, instance config, endpoints, secrets, dependency versions, DB schema, AMQP topology, or strategy daemons.
|
||||
|
||||
If a prohibited action appears necessary, stop and return to the approver.
|
||||
|
||||
## 2. Preflight
|
||||
|
||||
1. Confirm the exact approved target SHA, approval, and off-market execution window. If the target remains `PENDING_COMMIT_AND_PUSH`, **stop**.
|
||||
2. Capture only redacted operating-checkout metadata:
|
||||
|
||||
```bash
|
||||
pwd
|
||||
git status --short
|
||||
git rev-parse HEAD
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
Record the current SHA as `ROLLBACK_REVISION`. A dirty checkout is a stop condition.
|
||||
3. Identify the active instance configuration and existing supervisor method without exposing configuration values. Confirm credential/break-glass state will remain unchanged.
|
||||
4. From a clean source checkout, verify the candidate and migration/dependency delta:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git show -s --format='%H %s' <APPROVED_TARGET_SHA>
|
||||
git diff --name-only "$ROLLBACK_REVISION" <APPROVED_TARGET_SHA> -- pyproject.toml trex_core/adapters/persistence/migrations
|
||||
```
|
||||
|
||||
Any output on a dependency/migration path or any uncertainty about the target SHA is a stop condition requiring lead review.
|
||||
|
||||
## 3. Source deployment and restart
|
||||
|
||||
Use the approved host supervisor; do not infer a service-manager command from this document.
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout --detach <APPROVED_TARGET_SHA>
|
||||
# Stop the existing daemon through the approved host supervisor.
|
||||
# Start exactly one daemon with the unchanged approved instance configuration.
|
||||
.venv/bin/trex-core-daemon --config <approved-instance-config>
|
||||
```
|
||||
|
||||
Record redacted proof of target SHA, old-process stop, new-process supervisor/PID status, selected instance key, and startup log interval.
|
||||
|
||||
## 4. Startup acceptance and read-only smoke
|
||||
|
||||
Before CLI smoke, confirm startup completed, RabbitMQ topology declaration completed, expected workers are running, and the post-restart window has no `ERROR`, `CRITICAL`, or traceback.
|
||||
|
||||
Run only:
|
||||
|
||||
```bash
|
||||
.venv/bin/trex-core sync --all --dry-run --config <approved-instance-config> --json
|
||||
.venv/bin/trex-core verify --config <approved-instance-config> --json
|
||||
```
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Output is sanitized; no credential/account/raw payload is collected.
|
||||
- For available WAC, dry-run may show `seeded_avg_price=true` with the preview only; no slot/warning write occurs.
|
||||
- For unavailable WAC on a positive-broker inactive slot, dry-run must show `seeded_avg_price=false`, sanitized reason, and no-write preview.
|
||||
- No invocation adds `--live` or `--yes`; no remediation occurs.
|
||||
- Any unexpected write indication, unsafe success/empty-balance interpretation, or error inconsistent with fail-closed behavior is a rollback trigger.
|
||||
|
||||
## 5. Rollback
|
||||
|
||||
For any stop condition, stop the new daemon through the approved supervisor, restore the actual captured `ROLLBACK_REVISION`, and restart with the unchanged configuration:
|
||||
|
||||
```bash
|
||||
# Stop the new daemon through the approved host supervisor.
|
||||
git checkout --detach "$ROLLBACK_REVISION"
|
||||
# Restart with the same approved configuration through the supervisor.
|
||||
```
|
||||
|
||||
Do not run DB rollback, direct DB changes, or `sync --live` as a diagnostic shortcut.
|
||||
|
||||
## 6. Evidence template
|
||||
|
||||
```text
|
||||
Release: REL-012
|
||||
Approved by / timestamp: [REDACTED AS NEEDED]
|
||||
Window: [REDACTED]
|
||||
Host / instance key: [REDACTED]
|
||||
Pre-deploy revision (ROLLBACK_REVISION): <sha>
|
||||
Target revision: <approved immutable sha>
|
||||
Configuration/credential mode unchanged: yes/no (values not recorded)
|
||||
Checkout clean: pass/fail
|
||||
Dependency/migration path delta: none / stop
|
||||
Daemon stop/start evidence: <redacted supervisor status or PID>
|
||||
Startup acceptance: pass/fail
|
||||
sync --dry-run result: pass/fail + sanitized exit/status summary
|
||||
verify result: pass/fail + sanitized exit/status summary
|
||||
Unexpected write indication: none / details and stop action
|
||||
Rollback performed: no / yes (reason and restored SHA)
|
||||
Operator: [REDACTED]
|
||||
```
|
||||
|
||||
REL-012 can move to `released` only after this record is returned and the lead completes `confirm-release`. `sync --live` remains a separate explicit approval gate.
|
||||
@@ -0,0 +1,50 @@
|
||||
# TREX REL-012 운영팀 배포 요청 근거
|
||||
|
||||
- 대상: 운영팀 (`trex-op`)
|
||||
- 작성일: 2026-07-17
|
||||
- 상태: **긴급 배포 사전 근거 및 실행 요청 준비 — source publication / production approval 대기**
|
||||
|
||||
> 이 문서는 운영팀용 요약이다. 실제 실행은 immutable remote source에 존재하는 REL-012 및 deployment runbook만 따른다. 이 문서는 deployment approval 또는 execution completion이 아니다.
|
||||
|
||||
## 1. 결론
|
||||
|
||||
BAT-058은 independent QC-7 `PASS-WITH-NOTES`를 수용해 batch verification을 완료했고, REL-012 긴급 릴리즈 초안을 준비했다. 코드가 현재 delivery worktree에만 존재하여 **배포 가능한 immutable target SHA는 아직 없다**. 따라서 운영팀은 지금 배포를 시작하지 말고, 아래 source-publication gate가 해소된 뒤에만 preflight를 시작한다.
|
||||
|
||||
## 2. 배포 전 근거와 원본
|
||||
|
||||
| 순서 | Release | 배포 대상 source/artifact | 비운영 근거 | 실행 전 남은 gate | immutable 원본 링크 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | REL-012 / BAT-058 | `PENDING_COMMIT_AND_PUSH` | QC-7: focused `113 passed`, full `833 passed, 47 skipped`; release-prep full regression `833 passed, 47 skipped`; focused Ruff/compileall PASS | immutable SHA publish·remote tree 확인·release execution approval·window·host preflight | **PENDING** — git-ops 검증 후 commit-URL만 삽입 |
|
||||
|
||||
원본 문서(현재 release candidate worktree):
|
||||
|
||||
- REL draft: [`REL-012`](../releases/rel-012_20260717_cost-basis-safe-sync-seed-hotfix.md)
|
||||
- 운영 runbook: [`REL-012 deployment runbook`](rel-012-cost-basis-safe-sync-seed-deployment.md)
|
||||
- batch verification: [`BAT-058 verification`](../batches/bat-058_20260716_cost-basis-safe-sync-seed/verification.md)
|
||||
|
||||
## 3. 승인 후 실행 순서
|
||||
|
||||
1. **Git-ops / lead** — reviewed source와 REL/runbook을 commit·push하고 exact remote SHA 및 immutable commit URLs를 검증·전달한다.
|
||||
2. **운영팀** — 승인된 장외 window에 `ROLLBACK_REVISION`, clean checkout, unchanged config/credential mode, dependency/migration delta를 preflight에 기록한다.
|
||||
3. **운영팀** — approved supervisor로 동일 config의 trex-core daemon을 target SHA로 재기동한다.
|
||||
4. **운영팀** — startup acceptance 후 `sync --all --dry-run --json` 및 `verify --json`만 수행하고 runbook evidence template을 반환한다.
|
||||
5. **팀장** — evidence를 검토해 `confirm-release` 여부를 판정한다.
|
||||
|
||||
어느 단계에서든 stop condition이면 즉시 rollout을 멈추고 captured `ROLLBACK_REVISION`으로 되돌린다. `sync --live`는 이 배포 요청의 범위가 아니다.
|
||||
|
||||
## 4. 핵심 주의사항 / 중단 조건
|
||||
|
||||
- 허용 범위: daemon restart, startup/log inspection, `sync --dry-run`, `verify`.
|
||||
- 금지 범위: `sync --live`, `--yes` live write, 자동 remediation, 주문 명령, DB/스키마 변경, config/credential/break-glass 변경, AMQP topology 변경.
|
||||
- target SHA가 정확하지 않거나 dirty checkout, dependency/migration delta, startup error/traceback, unexpected write indication, fail-closed 위반, unsafe empty-balance 해석이 있으면 **중단 및 rollback**.
|
||||
- token, secret, account number, DSN, raw broker/portfolio payload를 전달서 또는 증적에 기록하지 않는다.
|
||||
|
||||
## 5. 운영팀 반환 증적
|
||||
|
||||
runbook의 evidence template을 작성해 아래만 전달한다.
|
||||
|
||||
- approver/window/operator 및 redacted host/instance identifier
|
||||
- target SHA와 `ROLLBACK_REVISION`
|
||||
- clean-checkout·dependency/migration preflight 결과
|
||||
- daemon restart/startup·dry-run·verify의 secret-free 결과
|
||||
- no-write/fail-closed 관찰 결과, rollback 여부와 사유/복구 SHA
|
||||
@@ -452,3 +452,17 @@ trex-notifier (단일 소비자) → 텔레그램 등 전달
|
||||
| 소비자 | 전략 모듈(strategy_id 필터) | trex-notifier 단독 |
|
||||
| 발행 재시도 | 재연결 후 1회 retry | **없음** (best-effort) |
|
||||
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — 브로커 집행 venue 전달 경로 명시: `OrderRequest.venue`(기본 KRX) → KIS 어댑터 `EXCG_ID_DVSN_CD` 항상 명시 전송(submit_order/cancel_order). `get_current_price` market 파라미터 추가(기본 J). poll_fill TR 유지(NXT 체결 open item). 기존 orb/tsl 경로 무영향.
|
||||
|
||||
|
||||
## Flow: `operator sync` (REQ-105/REQ-123, dcy-034 / BAT-051)
|
||||
|
||||
- Operator CLI `trex-core sync`는 브로커 잔고를 1회 조회해 슬롯 병합을 수행한다.
|
||||
- 대상 슬롯은 상태별로 다음 규칙을 따른다:
|
||||
- `PENDING_*`: 쓰기 스킵(수정 없음, warning clear 없음)
|
||||
- `broker_qty == 0`: 스킵(안전 가드)
|
||||
- `HOLDING`: qty(및 avg_price) 갱신
|
||||
- `INACTIVE`: qty onboarding 시 `cost_basis`를 `seeded_avg_price`로 seed 시도
|
||||
- `INACTIVE` 상태에서 `broker_qty > 0`이나 seedable avg_price가 없으면:
|
||||
- `--dry-run`은 `skipped=True`, `seeded_avg_price=False`, `cost_basis_reason=<reason>`만 노출하고 status/qty/avg_price 변경을 수행하지 않는다.
|
||||
- live는 `cost_basis_unavailable` 에러로 fail-closed 하여 status/qty/avg_price 쓰기를 모두 거부한다.
|
||||
- 결과 객체에는 `before/after`, `warning_cleared`, `seeded_avg_price`, `cost_basis_source`, `cost_basis_reason`, `error`가 포함되어 운영자가 no-write/fail-closed 여부를 판별할 수 있다.
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
# req-123: cost-basis canonical parse 및 WAC unavailable 안전 sync seed
|
||||
|
||||
- Status: Approved
|
||||
- Type: Integration
|
||||
- Priority: High
|
||||
- Owner: trex-core-dev
|
||||
|
||||
## Intent
|
||||
- portfolio-hub의 기존 `GET /v1/portfolios/{portfolio_id}/holdings/cost-basis` 응답에서 canonical field인 `cost_basis_per_unit`을 trex-core `PortfolioHubReadAdapter.get_cost_basis()`가 우선 해석하도록 하여 정상 HTTP 200 응답이 `None`으로 degrade되는 consumer contract defect를 수정한다.
|
||||
- legacy alias는 실제 호환 필요성이 확인되는 경우에만 낮은 우선순위 fallback으로 유지하고, canonical field의 invalid 값을 legacy 값으로 조용히 은폐하지 않는다.
|
||||
- broker 수량이 양수인데 WAC/avg_price가 unavailable한 inactive-slot onboarding에서 불완전한 live write를 방지한다. Discovery 승인 결정에 따라 live path는 fail-closed이며, dry-run은 진단과 no-write preview를 제공한다.
|
||||
- 본 REQ는 dcy-038의 consumer hotfix 요구사항만 다루며 portfolio-hub producer API 변경, 인증·토큰·설정 변경, DB migration/direct edit, `sync --live` 실행, 배포를 포함하지 않는다.
|
||||
|
||||
## Requirement
|
||||
1. `PortfolioHubReadAdapter.get_cost_basis(portfolio_id)`는 successful JSON object response에서 `cost_basis_per_unit`을 canonical WAC source로 먼저 검사하고, 유효한 양의 수치이면 `float` 값으로 반환해야 한다.
|
||||
2. canonical key가 없을 때에만 승인된 legacy alias를 정해진 순서로 검사할 수 있다. 현재 지원 alias는 `avg_cost` → `wac` → `cost_basis`이며 순차 적용한다. canonical key가 response에 존재하지만 null, non-numeric, 0, negative 또는 허용 범위 밖이면 legacy 값으로 fallback masking하지 않는다.
|
||||
3. cost-basis 결과는 최소한 다음 상태를 구분해 reconcile/operator surface가 sanitized reason을 표시할 수 있어야 한다: valid canonical/legacy value, field missing/null, malformed/non-numeric/invalid value, non-2xx response, transport/timeout failure, unexpected response shape. 기존 호출부 호환성이 필요한 경우 `Optional[float]` port를 유지하되, unavailable reason은 reconcile-local result/diagnostic으로 보존한다.
|
||||
4. canonical WAC가 유효하면 `sync --dry-run`의 `after.avg_price`와 `seeded_avg_price=true`가 해당 값과 일치해야 하며, 실제 DB slot/warning write는 발생하지 않아야 한다. 승인된 live candidate도 동일한 source value를 사용해야 한다.
|
||||
5. `broker_qty > 0`인 inactive slot onboarding에서 WAC/avg_price가 unavailable이면 dry-run은 `after.avg_price=null`, `seeded_avg_price=false`, sanitized source/absence reason 및 no-write preview를 제공해야 한다. 이 경우 inactive→holding 전이와 qty/status/avg_price write를 dry-run에서 수행하지 않아야 한다.
|
||||
6. 동일 조건의 live sync path는 Discovery에서 승인된 fail-closed 정책을 적용해야 한다. 즉 status, qty, avg_price를 모두 기록하지 않고 non-zero failure로 중단하며, 평균가 없는 inactive→holding 전이를 허용하지 않는다. 기존 double-ack/live gate와 별도로 이 no-write barrier를 유지한다.
|
||||
7. cost-basis port가 연결되지 않았거나 transport failure인 기존 graceful-degrade 경로와, canonical response parsing/validation failure 경로를 operator reason 및 테스트에서 구분해야 한다. unavailable을 정상적인 0 또는 빈 잔고로 해석하지 않아야 한다.
|
||||
8. sync diagnostics/logging은 비밀 없이 `portfolio_id`/`slot_id`/`symbol`의 허용된 식별자, `dry_run` 또는 `live` mode, `seeded_avg_price`, cost-basis source(`canonical`/legacy/none), sanitized absence reason, no-write/fail-closed 결과를 판별 가능하게 해야 한다. token, credential payload, account number 등은 출력·로그·fixture에 포함하지 않는다.
|
||||
9. response-contract 및 regression test는 adapter→reconcile→CLI/output 경로를 고정해야 한다. canonical payload, legacy payload(채택 시), canonical-invalid+legacy-present, missing/null, malformed, non-2xx/transport failure, current holding update, positive broker qty unavailable WAC의 dry-run no-write와 live no-write를 각각 검증해야 한다.
|
||||
10. 관련 baseline 문서(`docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/operations.md`)와 REQ/Batch/verification/release 문서는 portfolio-hub existing response contract, WAC absence semantics, dry-run diagnostics, fail-closed barrier, observability, rollback 및 release gate를 구현 결과와 일치하게 기술해야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given `{\"current_quantity\": 5.0, \"cost_basis_per_unit\": 124580.0}`인 HTTP 200 response, When `get_cost_basis()`를 호출하면, Then `124580.0`을 반환하고 요청 경로가 `/v1/portfolios/{portfolio_id}/holdings/cost-basis`임을 contract test가 고정한다.
|
||||
2. Given canonical field가 없고 승인된 legacy fixture가 있는 response, When adapter가 parsing하면, Then 명시된 legacy 우선순위에 따라 값이 반환된다. Given canonical field가 invalid하고 legacy 값이 함께 있으면, When parsing하면, Then legacy로 masking하지 않고 invalid/unavailable reason을 반환한다.
|
||||
3. Given canonical field가 null/missing/non-numeric/0/negative 또는 response shape가 object가 아닌 경우, When adapter/reconcile가 처리하면, Then 정상 WAC seed로 취급하지 않고 케이스별 sanitized reason을 기록·반환한다.
|
||||
4. Given cost-basis endpoint가 non-2xx 또는 transport/timeout failure를 반환하는 경우, When sync 경로가 처리하면, Then parsing failure와 구분되는 unavailable reason을 내고 credential/token/raw response 비밀을 노출하지 않는다.
|
||||
5. Given inactive slot과 `broker_qty=5`, canonical WAC `124580.0`인 경우, When `sync --dry-run`을 실행하면, Then preview의 `after.status=holding`, `after.qty=5`, `after.avg_price=124580.0`, `seeded_avg_price=true`를 표시하되 slot/warning write count는 0이다.
|
||||
6. Given inactive slot과 `broker_qty>0`, WAC가 unavailable인 경우, When `sync --dry-run`을 실행하면, Then inactive→holding 전이 및 qty/status/avg_price write를 하지 않고 `avg_price=null`, `seeded_avg_price=false`, reason 및 no-write preview를 표시한다.
|
||||
7. Given 동일한 positive broker holding/WAC unavailable 조건과 live path, When reconcile/sync가 실행되면, Then non-zero failure로 종료하고 status, qty, avg_price, warning clear를 포함한 write가 모두 0이며 inactive→holding 전이가 일어나지 않는다.
|
||||
8. Given 이미 HOLDING인 slot의 broker 수량 정합 업데이트, When sync/reconcile가 처리하면, Then 기존 의도된 current-holding update와 warning semantics가 유지되고 canonical WAC가 있으면 avg_price seed 값이 일관되게 적용된다.
|
||||
9. Given adapter/application/CLI regression suite를 실행하면, Then canonical contract, alias precedence, unavailable reason, dry-run no-write, live fail-closed, current holding regression, output/log redaction 및 `seeded_avg_price` 관측성 검증이 모두 통과한다.
|
||||
10. Given release candidate를 운영에 반영하려는 경우, When release gate를 점검하면, Then targeted regression 및 independent QC PASS, dry-run evidence, explicit release approval, 단일 대상 live 실행 승인, rollback 명령/기준과 post-release monitoring plan이 없이는 배포 또는 `sync --live`를 시작하지 않는다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — portfolio-hub existing cost-basis response의 canonical field와 legacy compatibility 경계
|
||||
- `docs/runtime-flows.md` — sync/reconcile WAC seed 및 unavailable fail-closed/no-write 흐름
|
||||
- `docs/operations.md` — dry-run diagnostics, operator reason, observability, rollback/release gate
|
||||
- `docs/discovery/dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md`
|
||||
- Modules:
|
||||
- `trex_core/adapters/portfolio/hub_read.py` — `get_cost_basis()` canonical/legacy parse와 unavailable 분류
|
||||
- `trex_core/application/reconciliation.py` — WAC unavailable no-write barrier, dry-run result 및 failure semantics
|
||||
- `trex_core/runtime/cli.py` 또는 현재 sync output 경계 — sanitized diagnostics와 non-zero failure
|
||||
- 기존 warning/observability 호출부 — fail-closed 시 warning clear/write 차단 및 식별 가능한 로그
|
||||
- Tests:
|
||||
- `tests/adapters/portfolio/test_hub_read.py` — HTTP response contract와 precedence/validation regression
|
||||
- `tests/application/test_sync.py` 및 관련 reconcile/CLI test — dry-run no-write, live fail-closed, diagnostics/output regression
|
||||
|
||||
## Notes
|
||||
- Source Discovery: `docs/discovery/dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md`.
|
||||
- Producer source of truth: portfolio-hub existing `HoldingsCostBasisResponse.cost_basis_per_unit`; 이 REQ에서는 producer endpoint/schema/auth를 수정하지 않는다.
|
||||
- Discovery confirmation: Justin Ko, 2026-07-16 22:44:20 KST. 승인 내용은 REQ drafting을 허용하며 `sync --live`, DB 변경, token/config 변경 또는 배포 승인을 의미하지 않는다.
|
||||
- OQ-2 Answered(2026-07-16): Producer 확인 결과, 현재 producer/fixture는 canonical `HoldingsCostBasisResponse.cost_basis_per_unit`을 기준으로 하고, 호환성 사유로 `avg_cost` → `wac` → `cost_basis` fallback을 유지한다. 단, canonical key가 유효하지 않으면 legacy fallback을 허용하지 않는다.
|
||||
- OQ-3 Answered(2026-07-16): 기존 `Optional[float]` port를 유지한다. unavailable reason은 reconcile-local 진단 구조에서 `seeded_avg_price`, source(`canonical`/`legacy`/`none`), reason을 분리해 노출한다.
|
||||
- Rollback은 consumer hotfix의 코드/문서 revert와 release 이전 candidate 철회를 의미한다. 이미 운영에 기록된 slot을 자동으로 직접 수정하거나 DB migration으로 복구하는 범위는 포함하지 않으며, 별도 승인된 운영 절차로만 다룬다.
|
||||
- Dependencies: existing REQ-105 sync contract, dcy-034의 WAC seed 범위, dcy-037의 broker truth/credential 변경은 재설계하지 않고 현재 baseline과 회귀 확인만 한다.
|
||||
- No live action: 구현·QC·release approval 전 `sync --live`, production sync, deployment, credential/token/config access는 금지한다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260716-01
|
||||
- Date: 2026-07-16
|
||||
- Author: delivery-runner
|
||||
|
||||
#### Change Summary
|
||||
- dcy-038 confirmed Discovery의 consumer hotfix 범위를 단일 Integration REQ 초안으로 분해했다.
|
||||
- canonical `cost_basis_per_unit` 우선 parsing, legacy alias 정책, WAC unavailable fail-closed sync, dry-run diagnostics, response-contract/regression test, observability 및 rollback/release gate를 요구사항과 AC로 고정했다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added: AC-1~10 전부 신규.
|
||||
- Updated: 없음.
|
||||
- Removed: 없음.
|
||||
|
||||
#### Impacted Area Delta
|
||||
- `hub_read.py`, `reconciliation.py`, sync CLI/output, 관련 adapter/application tests 및 runtime/interface/operations baseline 문서를 영향 범위로 명시했다.
|
||||
- portfolio-hub API/schema/auth, token/config, DB migration/direct edit, live sync, deployment는 명시적으로 제외했다.
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: none (draft only; suggest-batch-reqs 이후 편성).
|
||||
- Releases: none (별도 release approval gate 대상).
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: yes — confirm-req 전 legacy producer 증거, diagnostic result seam, source-level no-write 경계를 재검토하고 구현/QC에서 AC별 재검증한다.
|
||||
- Existing Implementation Invalidated: no — 현재 구현의 canonical-field parsing gap과 WAC unavailable live-write risk를 후속 hotfix requirement로 명시한다.
|
||||
- Status Recommendation: Proposed
|
||||
- Follow-up Action: OQ-2/OQ-3이 이 문서에서 Answered로 전환되었으나, implementation에서 reason enum/출력 형식 고정은 설계 단계에서 재확인한다.
|
||||
|
||||
#### Validation Plan
|
||||
- adapter HTTP fake/fixture로 canonical field와 alias precedence를 검증하고, canonical-invalid에서 legacy fallback 금지(차단) 및 canonical-absent에서 legacy fallback 동작을 함께 검증한다.
|
||||
- application/CLI recording double로 dry-run/live write count, fail-closed no-write, `seeded_avg_price`, source, reason 분리 표시를 검증한다.
|
||||
- targeted suite와 independent QC rerun 결과를 Batch verification에 AC별로 기록한다.
|
||||
|
||||
### CHG-20260716-02
|
||||
- Date: 2026-07-16
|
||||
- Author: delivery-runner
|
||||
|
||||
#### Change Summary
|
||||
- OQ-2/OQ-3을 본 REQ에서 답변 상태로 확정한다.
|
||||
- OQ-2: canonical-absent fallback alias 순서 및 canonical-invalid/no-valid canonical 값 처리 우선순위를 `Requirement`/`AC`/`Notes`에 고정한다.
|
||||
- OQ-3: `Optional[float]` 시그니처 유지 + unavailable reason을 reconcile-local 진단으로 분리 표기( `seeded_avg_price`, source, reason )하도록 변경한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added: AC-2/AC-3의 canonical-invalid/legacy precedence 경로 재정의(유효성 규칙 및 canonical masking 금지).
|
||||
- Updated: 없음.
|
||||
- Removed: 없음.
|
||||
|
||||
#### Impacted Area Delta
|
||||
- `trex_core/adapters/portfolio/hub_read.py`, `trex_core/application/reconciliation.py`, `tests/adapters/portfolio/test_hub_read.py`, `tests/application/test_sync.py`, `docs/interface-contract.md` 문안에 OQ 결정 반영.
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: none (draft/confirm 단계).
|
||||
- Releases: none (별도 release approval gate 대상).
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: yes — batch design에서 reason enum/표시 고정값, AC-별 regression test matrix 보강 및 independent QC rerun을 통해 재검증한다.
|
||||
- Existing Implementation Invalidated: no.
|
||||
- Status Recommendation: Proposed.
|
||||
- Follow-up Action: 확인된 OQ 반영 후 confirm-req 재평가에서 pass 되면 `suggest-batch-reqs` 진행.
|
||||
|
||||
### CHG-20260716-03
|
||||
- Date: 2026-07-16
|
||||
- Author: trex-dev-lead
|
||||
|
||||
#### Change Summary
|
||||
- confirm-req 게이트에서 dcy-038 승인 근거(제한범위/구조/결정사항)를 기준으로 `REQ-123`을 `Approved`로 전환하고, 다음 배치 인계만 선행한다.
|
||||
- 구현/검증/배포는 이 상태 전이 이후 `trex-core-dev`가 `suggest-batch-reqs`에서 생성되는 `BAT-058`로 진행한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added: StagePilot 상태 transition 근거(확정 OQ 0개, OQ-1/BLA no-write 정책 승인, scope non-goals 고정)을 confirm 증빙으로 고정.
|
||||
- Updated: none.
|
||||
- Removed: none.
|
||||
|
||||
#### Impacted Area Delta
|
||||
- StagePilot state: docs/srs/index REQ-123 status 및 문서 연계 준비.
|
||||
- Implementation trace: batch planning 경로로 이동.
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Next: `BAT-058` (dcy-038, dcy-038-based hotfix) 생성 후 implementation 단계로 이관.
|
||||
- Scope: implementation은 REQ-123 AC-1~10 + no-write 리스크 관리 포함.
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: yes (batch implementation/verification 단계).
|
||||
- Existing Implementation Invalidated: no.
|
||||
- Status Recommendation: Approved.
|
||||
- Follow-up Action: suggest-batch-reqs 완료 후 implementation/handoff.
|
||||
+4
-1
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-123`
|
||||
- Current: `REQ-124`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -203,9 +203,12 @@
|
||||
| REQ-120 | KIS balance 인증 실패 fail-loud 및 broker/token lifecycle 정책 | Exception | Implemented | High | trex-core-dev | [req-120](./Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md) |
|
||||
| REQ-121 | managed non-holding divergence 분류와 slot 존재 조회 계약 | Data | Implemented | High | trex-core-dev | [req-121](./Data/req-121_managed-non-holding-divergence-classification-query.md) |
|
||||
| REQ-122 | operator broker truth hotfix baseline 문서 정합 | Documentation | Implemented | High | trex-core-dev | [req-122](./Documentation/req-122_operator-broker-truth-hotfix-baseline-alignment.md) |
|
||||
| REQ-123 | cost-basis canonical parse 및 WAC unavailable 안전 sync seed | Integration | Approved | High | trex-core-dev | [req-123](./Integration/req-123_cost-basis-canonical-parse-and-safe-sync-seeding.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-07-16 :: REQ-123 **confirm-req 승인(Approved, lead confirm)**: dcy-038 OQ-1~3 완료 기준으로 제약(scope 제외 항목: API/schema 변경/credential/config 변경/live sync/배포), OQ-2/OQ-3 결정 반영, fail-closed WAC unavail no-write 정책, canonical parsing 우선순위/legacy fallback 규칙 및 dry-run diagnostics가 일치함을 확인해 `REQ-123`을 Approved 전환.
|
||||
- 2026-07-16 :: REQ-123 **신규 생성(Proposed, draft-req, dcy-038)**: portfolio-hub canonical `cost_basis_per_unit` consumer parse, legacy alias precedence, WAC unavailable fail-closed sync/no-write, dry-run diagnostics, response-contract/regression tests, observability 및 rollback/release gate. portfolio-hub API/schema/auth·token/config·DB migration/direct edit·live sync·배포 제외.
|
||||
- 2026-07-15 :: REQ-119~122 **`Approved` → `Implemented`** (`confirm-req-implemented`, dcy-037/BAT-057): PR #42 merged to `main` (94e1489). Independent QC PASS-WITH-NOTES (acceptance blocker 0); 806 passed/47 skipped/1 deselected. `sync --live`, deployment, and production remediation remain separate release-approval work.
|
||||
- 2026-07-14 :: REQ-119~122 **승인(Approved, confirm-req, dcy-037)**: lead 검토로 각 REQ의 owner·priority·scope·검증 가능 AC와 Notes의 non-blocking 설계 제약을 확인했다. #39/#40/#41은 다음 `suggest-batch-reqs`에서 단일 emergency batch 후보이며, `sync --live`는 여전히 별도 release approval gate다.
|
||||
- 2026-07-14 :: REQ-119~122 **신규 생성(Proposed, draft-req, dcy-037)**: #39 fetched credential daemon/operator broker truth parity, #40 token/auth fail-loud empty-balance 분리, #41 managed non-holding divergence classification, runtime/interface/operations baseline 정합. #39/#40/#41 단일 emergency batch 및 `sync --live` 별도 release approval gate를 유지한다.
|
||||
|
||||
Reference in New Issue
Block a user