# REL-004: safety-sell 쿨다운 + 운영 버그픽스/하드닝 + 운영자 CLI + 슬롯 가시화·현금 write-back 통합 릴리즈 - Status: feedback-captured - Profile: app-service - Included Batch: BAT-014, BAT-015, BAT-016, BAT-017, BAT-018, BAT-019, BAT-020, BAT-021, BAT-022, BAT-023 ## Scope - Included: - BAT-014 (dcy-010 / REQ-052~054): safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고 - BAT-015 (dcy-011 / REQ-007·018·021 change-req + REQ-055): buy intent portfolio_id 필수화 + symbol 서버 resolve + ledger skip 가시화 + closed↔outbox anti-join 점검 - BAT-016 (dcy-012 / REQ-002·003·006·007·013·015·036 change-req): 매도 가격 버그 정정 — 슬롯 `entry_price`→`limit_price` rename(+마이그레이션 006) + dispatch 단일 필드 + sell optional 가격 - BAT-017 (dcy-013 / REQ-012·013·014·015·018·031 change-req): KIS 외부 취소 감지(cncl_yn) + 부분 체결 회계 + pending stale 워치독(Alert-only) - BAT-018 (dcy-014 / REQ-005·009·048 change-req): trading_date 설정 타임존(KST) 분리 + 일자 경계 일일 preallocation - BAT-019 (dcy-015 / REQ-017·018·019·020 change-req): ledger outbox 영속 오류 분류 + 지수 백오프/dead-letter(+마이그레이션 007) - BAT-020 (dcy-016 / REQ-021·022·026·032·034 change-req): 운영자 CLI 확장(universe/slots/enter/exit, 데몬 RPC 통일) + 워치독 억제 읽기 - BAT-021 (dcy-017 / REQ-021·032·034·037·038·039·042 change-req): 워치독 소유권 가시화(snapshot 워치독 상태 + safety-sell 발동 로깅) - BAT-022 (dcy-016 후속 / REQ-028·032 change-req): `trex-core slots` 가 universe `assets[]` 대신 실제 슬롯 상태를 표시 — snapshot envelope 에 read-only `slots[]`(slot_id/status/symbol/qty/limit_price/avg_price/portfolio_id) additive 추가, `universe`/`slots` 표면 분리 - BAT-023 (dcy-018 / REQ-017·019·029·030 change-req, REQ-030 Approved→Implemented): ledger write 응답 cash/position write-back(option D 활성화) — portfolio-hub#8 응답 `cash_balance_after`/`asset_quantity_after` 파싱 후 `SnapshotStore` write-back(best-effort), 체결~write-back 공백은 추정 현금 오버레이(`cash_estimated`)로 메움. "수량 live·현금 stale" 비대칭 제거 - Excluded: - BAT-001~013 (REL-001/002/003 에서 이미 released) - dcy-018 이후 후속 주제, Redis projection·push 알림(dcy-017 OQ-1 미채택), dead-letter 재처리 트리거(dcy-015 후속), 슬롯 오버레이 제거(dcy-018 OQ-1 B 로 유지 확정) ## Rollout Plan 1. 배포 전: 대상 인스턴스 데몬 정지(또는 거래 세션 외 시간), 현재 리비전 SHA 기록. 2. DB 마이그레이션 적용(멱등): `006_rename_entry_price_to_limit_price.sql`(daily_slot 컬럼 rename) + `007_ledger_outbox_delivery_failed_and_backoff.sql`(status enum/next_retry_at). 데몬 기동 시 `apply_migrations` 가 자동 적용하나, 운영 `trex_core` DB 에 최초 적용되므로 사전 백업 권장. 3. 신 리비전(`6d7bca1` 또는 그 이후 main HEAD) 배포 + 데몬 재기동(`--config .toml`). 기동 로그에서 RUNNING/비-degraded 확인. 4. 스냅샷 RPC degraded=false·available_slots·asset_count 확인. snapshot envelope 에 `slots[]`(per-slot 상태)·`watchdog[]` 노출 확인(BAT-022). trading_date 가 KST 당일과 일치하는지 확인. 5. 운영자 CLI 스모크: `trex-core universe`(=`assets[]`)/`slots`(=실제 슬롯 상태·status·워치독 표시, BAT-022) — paper 또는 비거래 시간. 6. (가능 시) paper buy/sell 1회 후 `trex-core universe` 에서 `cash_balance`·`asset_quantity` 가 **함께** 갱신(write-back) 또는 `cash_estimated` 추정 라벨 노출 확인(BAT-023). 7. 거래 세션 재개. ## Rollback Plan - 조건: 기동 실패, 마이그레이션 오류, buy/sell admission·집행 회귀, ledger 전송 비정상, safety-sell 오작동. - 절차: 신 리비전 → 직전 released 리비전(REL-003 시점)으로 롤백 + 데몬 재기동. - 주의: 마이그레이션 006(컬럼 rename)은 비가역적 rename 이다 — 롤백 시 구 코드는 `entry_price` 컬럼을 기대하므로, 코드 롤백과 함께 역마이그레이션(`limit_price`→`entry_price` rename) 또는 컬럼 alias 가 필요하다. 007 은 additive(컬럼/enum 추가)라 구 코드와 공존 가능. 배포 전 역마이그레이션 스크립트를 준비한다. - 데이터: 마이그레이션은 순수 rename/additive 라 행 데이터 보존. ledger_outbox `delivery_failed` 행은 롤백 후 구 코드가 모르는 status 이므로 list_pending 에서 자연 제외(무해). ## Verification Checklist - 배포 전: - 전체 인프라 게이트 테스트 green (PG `trex_core_test` + RabbitMQ): BAT-023 verification 시점(main HEAD `2bfecd5`, BAT-022 `63da87c` 포함) **`385 passed`**(pending_cash_delta SQL 포함) — 확장 범위 반영 완료. (참고: 최초 confirm 시점 BAT-014~021 범위는 `369 passed`) - `ruff check trex_core tests` All passed (BAT-022/023 변경 파일 포함). - 마이그레이션 006/007 idempotent 재적용 확인(컬럼 계약 가드 green). BAT-022/023 은 신규 마이그레이션 없음(snapshot 필드 additive + 코드-only write-back). - 역마이그레이션(006 rename 되돌리기) 스크립트 준비. - 배포 후: - 데몬 RUNNING + 비-degraded, 8개 백그라운드 스레드 기동(`trex-preallocation` 포함). - trading_date == KST 당일, 슬롯 preallocation 정상. - 스냅샷 RPC envelope(assets/available_slots/degraded) 정상 + `watchdog`·`slots[]` 필드 노출(BAT-022). - `trex-core universe`(assets[])/`slots`(실제 슬롯 상태·status·워치독 소유 표시) 응답 정상. - (가능 시) paper buy/sell 1회 e2e: portfolio_id 기반 admission → 체결 → ledger 기록 → `cash_balance`·`asset_quantity` write-back(또는 `cash_estimated` 추정 라벨) 확인(BAT-023). ## Pre-Release Run Evidence (operator-facing smoke) confirm-release 전 "운영자가 쓰는 프로그램이 실제로 동작한다"는 run-the-app 증거. 테스트 통과(369 passed)와 별개로 실 데몬 기동 + `trex-core` CLI 구동을 검증했다. > 주: 아래 base run 증거는 BAT-014~021 범위(REL-004 최초 confirm) 시점의 것이다. BAT-022·BAT-023 편입 범위의 run/test 증거는 바로 아래 "확장 범위 증거(BAT-022/023)"에 추가했다. ### 확장 범위 증거 (BAT-022 / BAT-023, 재-confirm) - **BAT-022 (슬롯 상태 가시화) — 실 인프라 운영 재현 PASS**: 신규 코드 데몬 재기동(`trex-core-daemon --config config/test.toml`, PID 2965049) 후 `trex-core slots` → `instance=test status=all count=10` 으로 **10개 슬롯 각각 status(idle)·slot_id·qty·limit·avg·pid 표시**(이전 갭: universe 1개·status 없음 해소). `slots --status holding` → `count=0`(필터 동작). `trex-core universe` → `assets=1`(universe 유지, slots↔universe 분리 실증). DB `daily_slot (2026-06-08,'idle',10)` 와 정확히 일치. 요청은 `actor=operator`/`touch_watchdog=false`(비침습). 단위 회귀 `338 passed`. - **BAT-023 (현금/포지션 write-back, option D) — 인프라 게이트 PASS**: PG `trex_core_test` + RabbitMQ 인프라 게이트 **`385 passed`**(option D write-back 11 + adapter `*_after` 파싱 + `pending_cash_delta` SQL 포함), 단위 `350 passed / 35 skipped`, `ruff` clean. write-back(new/duplicate 멱등)·`*_after` 누락 skip·best-effort 무차단·추정 현금 오버레이(`cash_estimated`) 수렴 전부 green. - **한계(운영 배포 검증으로 이월)**: BAT-023 의 실 portfolio-hub#8 full-stack option D e2e(실 `*_after` 응답 → 스냅샷 cash/quantity 갱신)는 paper/단위로 커버했고, LIVE full-stack 은 아래 Release Execution Evidence(실 배포)에서 확인한다. BAT-022 의 pytest 인프라 게이트 미실행은 verification Human Approval Memo 로 수용된 잔여(실 인프라 운영 재현이 동등 이상 경로 커버). - 환경: paper 브로커 + 테스트 PostgreSQL(`trex_core_test`) + 테스트 RabbitMQ, portfolio-hub stub(universe 2종), `--config `(instance_key=`rel004-smoke`), revision = main HEAD(수정 후). - 데몬 기동: `trex-core-daemon --config ...` → 마이그레이션 적용(006/007 포함), RabbitMQ 연결, snapshot scheduler boot(stub universe swap), preallocation, 8 스레드 기동, RUNNING. 기동 후 에러 로그 없음. - 운영자 CLI(데몬 RPC 통일) 구동 결과: - `trex-core universe` → snapshot RPC, assets 출력, degraded=false (BAT-020). - `trex-core slots` / `slots --status held --json` → 보유 슬롯 + `watchdog` 필드(slot_id/expired/last_seen) 노출 (BAT-021/dcy-016). - `trex-core enter pf-005930 --capital ... --yes` → `status=admitted slot_id=...` → paper 체결 → holding (BAT-015/018). - `trex-core enter ... --dry-run` → 미제출 미리보기(안전장치), `--json`+enter 는 `--yes` 강제 (BAT-020). - `trex-core exit --yes` → `status=admitted` → 시장가 매도 체결 → closed(held count 0) (BAT-016). - `ledger_outbox`: buy/sell 모두 `status=completed`(outbox durability + writer drain, BAT-019 경로 정상). - **검출·수정한 결함(이 run 이 아니었으면 release 에 포함됐을 버그)**: `Daemon._resolve_symbol` 이 `SnapshotAsset.symbol`(존재하지 않음)을 접근해 데몬 buy admission 이 매 요청 `AttributeError` → intent RPC 타임아웃. **실 데몬 buy 가 전면 불능**이었으나 단위 테스트는 stub resolver 를 주입해 미검출(369 passed). `asset.item.symbol` 로 수정 + 회귀 테스트(`tests/runtime/test_daemon_resolve_symbol.py`) 추가 → 재기동 후 buy/sell e2e 정상(REQ-007 CHG-20260608-03). 수정 후 단위 `337 passed`. - 한계: 실계좌(LIVE KIS) 주문은 미검증(paper 한정) — LIVE full-stack 은 운영자 배포 evidence(아래)에서 확인. ## Release Execution Evidence - Release Status Transition Target: released ### Deployment Facts - Executed At: - 2026-06-09 13:31:33 KST (+0900) - Completed At: - 2026-06-09 13:31:33 KST (+0900) - Environment: - 운영 trex_core DB + instance_key `trex-admin-daily-trading` - Operator: - 진환 고 - Revision or Artifact: - `fec9411d330d0722c5b7970ad33e651ee479fd30` (`fec9411`) - Commands or Procedure: - 배포 후 데몬 재기동 및 운영자 CLI 스모크 검증(`trex-core universe`, `trex-core slots`, buy/sell e2e, ledger outbox/portfolio-hub 반영 확인) ### Profile-Specific Checks - app-service: - Health Endpoint: - 2026-06-09 13:31:33 KST 기준 snapshot RPC `degraded=False`, `assets=45` 확인 - Startup Log Fields: - 데몬 기동 상태 양호; `trex-core-daemon --config temp/config_trex-admin-daily-trading.toml` 정상 실행 중, `watchdog_owned` 슬롯 존재 및 CLI 응답 정상 - Request Log Fields: - buy/sell intent RPC 및 safety-sell 처리 로그에서 correlation/transition 확인 가능 - Smoke or Manual Verification: - `trex-core universe --config temp/config_trex-admin-daily-trading.toml` 정상 응답 - `trex-core slots --config temp/config_trex-admin-daily-trading.toml` 정상 응답 - buy/sell e2e 확인: `holding -> pending_sell -> closed` via `take_profit` 및 `forced_close_at` 트리거 모두 확인 ### Rollback Outcome - Rollback Required: - no - Rollback Trigger: - none - Rollback Action: - none ### Release Decision Evidence - Released Decision: - released - Rationale: - REL-004 범위의 safety-sell 쿨다운/forced_close_at/take_profit/stop_loss, operator CLI, ledger write-back, slot 가시화가 실 운영 검증에서 동작함을 확인했고, 회귀를 유발하는 배포 장애나 롤백 트리거는 관측되지 않았다. - Recorded By: - 진환 고 - Recorded At: - 2026-06-09 13:31:33 KST (+0900) ### Post-release Verification Evidence (2026-06-09) - Trading flow evidence: - buy intent admitted for portfolio `1c53b936-d69d-41c4-9ce2-4a2b5baa4301` - sell intent admitted for the same slot - final slot state: `closed` - slot id: `73e7cfae-1a92-498a-bf3d-bfe347f7b5e4` - symbol: `316140` - qty: `1.0` - avg price: `28950.0` - take-profit verification: slot `3a95d0cb-0f4c-49da-88d5-5a709f671ccc` transitioned `holding -> pending_sell -> closed` with `reason=take_profit` and daemon log `safety-sell fired: phase=take_profit` - Ledger/outbox evidence: - daemon log recorded `POST http://localhost:8002/v1/portfolios/1c53b936-d69d-41c4-9ce2-4a2b5baa4301/ledger/trades` returning `HTTP/1.1 201 Created` for both buy and sell writes - `ledger_outbox` rows for buy/sell were both `status=completed` and `last_outcome=completed` - portfolio-hub visibility evidence: - `GET /v1/portfolios` returned the portfolio row for `1c53b936-d69d-41c4-9ce2-4a2b5baa4301` - visible state in portfolio-hub list: `symbol=316140`, `cash_balance=-208.65499999999884`, `asset_quantity=0.0`, `price=28750.0`, `updated_at=2026-06-09T00:11:29.252209Z` - the openapi contract exposes `POST /v1/portfolios/{portfolio_id}/ledger/trades`; no read-only GET ledger endpoint was found, so visibility was confirmed through the portfolio summary row and outbox completion logs ## Feedback Handoff - Observation Summary: - 2026-06-09 실 운영 배포(instance `trex-admin-daily-trading`, rev `fec9411`)에서 REL-004 범위가 정상 동작 확인: buy/sell e2e(`holding→pending_sell→closed`, slot `73e7cfae…` symbol 316140), `take_profit` 트리거(slot `3a95d0cb…`, daemon log `safety-sell fired: phase=take_profit`)·`forced_close_at` 트리거 발동, ledger `POST …/ledger/trades 201 Created`(buy/sell)·`ledger_outbox status=completed`, snapshot RPC `degraded=False assets=45`, portfolio-hub `GET /v1/portfolios` 가시성. 롤백 트리거 없음(Rollback Required=no). - 관찰 포인트: 한 바퀴(buy→sell) 후 portfolio-hub 상태가 `cash_balance=-208.65…`(음수)·`asset_quantity=0.0`. 수수료/세금 반영의 정상 순값인지 vs 초기 cash seed 부재/현금 회계 갭인지 확인 가치 있음(option D write-back 경로는 동작). - 별개 운영 환류: issue #16(00:00 KST 일자 경계 자동 preallocation 누락)이 동일 인스턴스에서 관측됨 — 데몬은 RPC 응답(살아 있음)하나 당일 슬롯 미생성. REL-004 BAT-018(일자 경계 일일 preallocation) 범위와 직접 관련. - Discovery Input: - issue #16: 00:00 KST 일자 경계 자동 preallocation 누락 — `trex-preallocation` 스레드 생존/일자 경계 발화 검증 + 견고화(boot run_once 예외 흡수 등). BAT-018 후속 운영 환류(High 우선). - issue #18 → **dcy-019**(slot `entry_count` + closed 슬롯 재할당 정책) 이미 confirmed — REQ 초안 대기. - (관찰) portfolio-hub 음수 `cash_balance` 정합성 — 필요 시 별도 Discovery 후보. - REQ Input: - issue #17 → **BAT-024**(operator `enter --forced-close-at`, REQ-032 CHG-20260609-01/02) 이미 release-candidate. **REL-004 미포함** → 차기 release(REL-005 또는 통합)로 별도 배포 필요. - 신규 REQ 특이 후보 없음(이번 주기 변경은 대부분 change-req 로 처리됨). - Change Request Input: - `daily_symbol_entries`(심볼 재진입 카운터) 제거 — production 소비자 없음 확인 + 심볼 재진입 제한 미사용 결정(dcy-019). **REQ-002 change-req 트랙**(별도 batch-lite).