Files
trex-core/docs/discovery/dcy-028_20260615_slot-strategy-state-store.md
T
genichinandClaude Opus 4.8 e0d4e71304 feat(slot,intent): slot strategy-state 저장소 (dcy-028) + sell retryable 플래그 (issue #27)
BAT-042 (dcy-028 / issue #26 C-1/C-2/C-2b/C-3, REQ-079~082 + REQ-073/074 CHG):
- slot_strategy_state 별도 테이블 + StrategyStateStore(in-memory+PG) + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap (REQ-079)
- trex.slots.strategy_state.set write RPC — reject reason 집합, 멱등 (REQ-080)
- Slot.occupancy_seq 필드 + inactive→pending_buy bump + →INACTIVE close 삭제 훅 → 점유 전환 ABA 구조적 차단 (REQ-081)
- slots.req 응답 행에 strategy_state 맵(batched 조립)·occupancy_seq additive (REQ-073 AC-7/REQ-074 AC-6)
- 마이그레이션 014(occupancy_seq)/015(slot_strategy_state), baseline 문서 정합 (REQ-082)

BAT-043 (issue #27 / REQ-007 CHG):
- rejected SellIntent 응답에 additive retryable: bool (core 소유 reason→retryable 매핑, 미인식 기본 true, bad_request* false) — 손절매 재시도 안전 경로 decoupling

검증: 487 passed, 47 skipped (신규 41: store CAS 6+ABA, occupancy bump/close 4경로, set RPC 9, slots read 5, retryable 8, PG 게이트), ruff clean.
REQ-079·080·081·082·073·074·007 → Implemented. issue #27 close. PG 경로 실 DB 검증은 release 위임.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:27:57 +09:00

28 KiB
Raw Blame History

0. 문서 상태

이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.

  • 상태: confirmed
  • 문서 ID: dcy-028_20260615_slot-strategy-state-store
  • 이슈명: Slot Strategy-State 저장소 (per-strategy opaque state store on trex-core slots, issue #26 C-1/C-2/C-3)
  • 작성 시각(KST): 2026-06-15 16:26
  • 마지막 갱신 시각(KST): 2026-06-15 18:05
  • 대체됨: 없음
  • 후속 Discovery 참조: 없음
  • 생성된 REQ 참조: REQ-079, REQ-080, REQ-081, REQ-082 (+ change-req: REQ-073, REQ-074)

1. 계획 상태 요약

현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.

  • 연계 문서 현황:
    • 관련 점수 파일: 미생성
    • 관련 TODO/메모: GitHub issue #26 (trex-tsl strategy-daemon 팀 계약 제안), C-4 확인 코멘트·OQ 결정 코멘트(게시 완료), trex-tsl 팀 occupancy race 피드백(#4705990067)
  • 해석:
    • trex-tsl(매도 전략 데몬)이 TSL trailing stop line 계산에 필요한 **시간 누적 상태(예: entry 이후 peak price)**를 슬롯 수명에 묶어 server-side로 보관할 generic state store를 요청했다(C-1/C-2/C-3).
    • 이번 Discovery는 "trex-core가 해석하지 않는 opaque state를 (slot_id, strategy_id) 키로 저장/조회/수명관리"하는 범위를 정의한다. OQ-1~4 전부 Answered. trex-tsl 팀이 점유 전환 ABA 누수(High)를 지적해 occupancy_seq 경량 가드(C-2b)를 v1 범위로 추가, full 3계층 분할만 후속 Deferred. C-4(SellIntent/watchdog)는 기존 계약 확인 항목으로 답변 완료.
    • trex-tsl-v1 운영 모드 = intraday, safety_sell_enabled=true(core SL/TP watchdog backup + market_close EOD backstop 유지, 데몬이 primary 매도). 중장기 프로파일과 무관 — core가 backup으로 남으며 당일 재진입이 잦아 occupancy 가드 필요성이 큼.

2. 요구사항 판정 결과

이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.

  • 판정: 신규
  • 근거
    • docs/discovery/ 전체에서 strategy_state/per-strategy opaque store를 다루는 기존 Discovery가 없다(grep 결과 0건). 가장 가까운 dcy-025(strategy read surface — slots.req 노출면)는 슬롯 메타 read 확장이지 strategy 소유 상태 저장/CAS write/수명 삭제 계약이 아니다.
    • C-2(version CAS write RPC)·C-3(close 시 삭제 불변식)는 신규 RPC와 신규 도메인 저장 개념을 도입하므로 기존 REQ/batch 산출물의 미세 조정 범위를 넘어선다.

3. 문제점의 요약

문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다.

  • 현재 상태
    • 슬롯 스냅샷 RPC(trex.slots.req)는 trex_core/application/messaging_handlers.py의 build_slots_handler + slot_to_dict(trex_core/application/portfolio.py:250)로 슬롯 행을 구성하며, strategy 소유 임의 상태를 실어 나르는 필드가 없다.
    • 슬롯 도메인(trex_core/domain/slot/models.py Slot, state.py)에는 position/occupancy 필드만 있고 strategy opaque blob 저장소가 없다. 전략은 evaluate() 순수성 때문에 상태를 스스로 영속화할 수 없다.
    • 슬롯 close는 영구 슬롯 모델(dcy-020/REQ-057, BAT-030)에서 행 삭제가 아니라 → INACTIVE 전이다(trex_core/domain/slot/service.py:174 — position/occupancy 필드 일괄 clear, asset 정체성 보존). 현재 이 전이에서 정리할 strategy_state 자체가 없다.
    • 결과적으로 전략 데몬이 peak 등 누적 상태를 데몬 메모리에 두면 재시작/장애/failover 시 유실되어 stop line이 느슨해지는 실손실 위험이 있고, 별도 외부 스토어를 쓰면 orphan-cleanup 책임이 발생한다.
  • 기대 상태
    • (slot_id, strategy_id)로 네임스페이스된 opaque state를 trex-core가 저장하고, 슬롯 스냅샷 응답에 round-trip(추가 왕복 없이 read), occupancy_seq + version CAS로 idempotent write, 슬롯 close(→INACTIVE) 시 자동 삭제한다.
    • 전략/데몬은 cleanup 책임이 없고, 슬롯이 source of truth이자 GC가 된다. 재사용된 슬롯은 새 occupancy_seq + 빈 상태(version 없음)로 시작한다.
  • 이해관계자
    • trex-tsl 매도 전략 데몬 팀 (제안자/주 소비자)
    • trex-core 유지보수자 (계약·구현·검증 책임)
    • 운영 담당자 (멀티데이 인스턴스 운영, dcy-027 long-term 프로파일 연계)
  • 제약사항
    • trex-core는 data를 절대 파싱/검증/해석하지 않는다(opaque). schema는 전략 소유.
    • 순수 AMQP 계약 통합만 — 데몬은 trex_core를 직접 import 하지 않는다.
    • 단일 writer 전제(해당 slot·strategy를 운용하는 데몬 1개). version CAS는 재시도/중복 안전용이지 다중 writer 동시성 제어가 아니다.
    • 영구 슬롯 모델 정합: 삭제는 행 삭제가 아니라 →INACTIVE 전이 훅에서 수행한다(dcy-020).

주요 사용자 시나리오

사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.

  • TSL 데몬이 매 decision-cycle에 trex.slots.req로 슬롯과 함께 strategy_state["tsl_v1"]를 읽어 evaluate()의 read-only 입력으로 쓰고, 갱신된 peak 상태를 trex.slots.strategy_state.set(expected_version CAS)로 write 한다.
  • 데몬이 재시작/장애로 메모리 상태를 잃어도 다음 사이클에 슬롯 스냅샷에서 peak를 복원해 stop line이 느슨해지지 않는다.
  • 슬롯이 매도 체결로 close(→INACTIVE)되면 trex-core가 해당 슬롯의 모든 strategy_state를 삭제하고, 같은 슬롯이 재사용되면(occupancy_seq++) 전략은 빈 상태에서 다시 시작한다.

4. 이번에 정의할 변경

이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다.

  • C-1: 슬롯 스냅샷 RPC(trex.slots.req) 응답 각 슬롯 행에 optional strategy_state 맵을 추가(additive, 하위호환). 미보유 시 omit 또는 {}.
  • C-2: 신규 RPC trex.slots.strategy_state.set — (slot_id, strategy_id) opaque value를 version CAS로 upsert. 기존 계약 패턴(direct reply-to RPC + JSON + queue_name({base}.{instance_key})) 준수.
  • C-2b (occupancy_seq 경량 가드): 슬롯 행에 monotonic occupancy_seq(새 점유 시작 시 ++)를 추가하고 snapshot 행에 노출한다. strategy_state.set은 expected_occupancy_seq를 받아 occupancy_seq 일치 + version 일치를 함께 CAS 검증, 점유가 바뀌었으면 occupancy_changed reject. 점유 전환(read v0→close→reopen→write v0)의 ABA 누수를 구조적으로 차단(intraday 재진입 안전). full 3계층(occupancy 테이블 분할)은 후속 Deferred — 본 가드는 컬럼 1개로 정합성 임계부만 선취.
  • C-3: 슬롯 close 시 strategy_state 삭제 불변식 — 영구 슬롯 모델 정합상 →INACTIVE 전이(service.py:174)에서 해당 slot_id의 모든 strategy_state 제거.
  • (baseline) docs/runtime-flows.md에 strategy-state read/write/삭제 흐름 추가, docs/project-structure.md에 저장소 구조·계약 키 반영.

영향 범위

변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.

  • 코드 영향:
    • trex_core/application/messaging_handlers.py(build_slots_handler 응답에 strategy_state + occupancy_seq 병합, 신규 strategy_state.set 핸들러 + occupancy_seq CAS)
    • trex_core/application/portfolio.py(slot_to_dict에 strategy_state + occupancy_seq round-trip)
    • 신규 저장 포트/리포지토리(예: StrategyStateStore)와 PG/in-memory 구현(trex_core/adapters/persistence/postgresql.py, trex_core/domain/slot/in_memory_repository.py 또는 별도 모듈)
    • trex_core/domain/slot/models.py·service.py(슬롯에 occupancy_seq 필드 추가 + 새 점유 시작(inactive→pending_buy) 시 ++ + →INACTIVE 전이 시 strategy_state 삭제 훅), 스키마 마이그레이션(occupancy_seq 컬럼) 및 데몬 와이어링(trex_core/runtime/daemon.py)
  • 문서 영향:
    • docs/runtime-flows.md(strategy-state 흐름), docs/project-structure.md(저장소/계약 키), docs/srs/index.md(신규 REQ 등록)
  • 운영 영향:
    • 신규 RPC 라우팅 키/큐 추가 → 데몬·인프라 설정 반영. PG 사용 시 신규 테이블/스키마 마이그레이션.

5. 요구사항 목록

실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.

기능 요구사항

  • FR-1 (C-1): trex.slots.req 응답 각 슬롯 행에 optional strategy_state 맵({<strategy_id>: {version:int, data:str, updated_at:str}})과 **occupancy_seq(int)**를 추가한다. 미보유 시 omit/{}. 기존 소비자는 unknown 필드 무시로 하위호환. 맵은 별도 slot_strategy_state 테이블(OQ-4)에서 반환 slot_id들로 batched 조회 후 group by 해 조립한다(슬롯 본체 스키마 불변; occupancy_seq는 슬롯 행 컬럼).
  • FR-2 (C-2): 신규 RPC trex.slots.strategy_state.set로 (slot_id, strategy_id) opaque data를 occupancy_seq + version CAS upsert 한다. expected_occupancy_seq가 슬롯 현재 occupancy_seq와 일치하고 expected_version이 stored version과 일치 → commit 후 version+1. 점유 불일치 → {ok:false, reason:"occupancy_changed", occupancy_seq:<cur>} (저장 안 함; 데몬 re-read 후 fresh restart). version 불일치 → {ok:false, reason:"version_conflict", version:<cur>}. slot inactive/missing → {ok:false, reason:"slot_inactive"} (저장 안 함). 최초(없음 + expected_version=0, occupancy_seq 일치) → version=1 생성. 동일 correlation_id 재전송은 부작용 없음. data는 opaque UTF-8 문자열로 받아 (1) 유효 UTF-8 + (2) 크기 상한(NFR-2/OQ-1)만 검증하고 그대로 저장하며, 초과 시 {ok:false, reason:"state_too_large"}로 reject 한다(저장 안 함). malformed 페이로드는 {ok:false, reason:"bad_request:<detail>"}. reject reason 전체 집합 = {occupancy_changed, version_conflict, slot_inactive, state_too_large, bad_request}, 데몬은 미인식 reason에 fail-closed.
  • FR-2b (C-2b, occupancy_seq 가드): 슬롯에 monotonic occupancy_seq 필드를 두고 새 점유 시작(inactive→pending_buy) 시 ++, snapshot 행(FR-1)에 노출한다. read v0→close→reopen→write v0 의 ABA 누수를 FR-2 occupancy CAS로 구조적으로 차단한다. (full occupancy 테이블 분할은 Out of Scope — 컬럼 1개로 정합성 임계부만 선취.)
  • FR-3 (C-3): 슬롯이 어떤 경로로든 close(→INACTIVE)되면 해당 slot_id의 모든 strategy_state를 삭제한다. 재사용 슬롯은 새 occupancy_seq + 빈 상태(version 없음)로 시작한다. 삭제 즉시성 통지는 신규 이벤트를 만들지 않고(OQ-3 확정), 데몬은 다음 slots.req 스냅샷의 state 부재(또는 occupancy_seq 변화)로 감지한다; 즉시성이 필요하면 이미 발행되는 slot_filled_sell(status=inactive) transition 이벤트를 재사용한다(필요시 strategy_state_cleared additive 플래그).
  • FR-4 (baseline): docs/runtime-flows.md에 strategy-state read/write/삭제 흐름·occupancy_seq 가드·영구 슬롯 close 훅 정합을, docs/project-structure.md에 저장소 구조·계약 키를 반영한다.

비기능 요구사항

성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다.

  • NFR-1 (불간섭/opaque): trex-core는 data를 파싱/검증/해석하지 않는다. 충돌/만료/해석은 전적으로 trex-core 밖이다.
  • NFR-2 (인코딩/크기 한계, OQ-1 확정): data는 core가 해석하지 않는 opaque UTF-8 문자열이다 — JSON/base64를 계약 레벨에서 강제하지 않으며(base64 기본 강제는 33% 팽창·무이득), core 검증은 (1) 유효 UTF-8 + (2) 크기 상한(byte 기준) 두 가지뿐이다. 상한은 2단 — TSL 등 알려진 용례 기본 **12KB**, hard ceiling 4KB. 초과 시 명시적 reject(state_too_large). C-1 read piggyback상 스냅샷 응답 크기 ≈ max_active_slots × cap 이므로 상한은 슬롯당 저장이 아니라 폴링 응답 크기 관점에서 정한다.
  • NFR-3 (수명 정합): strategy_state는 슬롯 수명 동안만 존재하고 장기 영속/감사 대상이 아니다. read는 기존 스냅샷 RPC에 round-trip 되어 추가 왕복이 없고 watchdog 갱신을 깨지 않는다.

범위 경계

이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.

  • In Scope
    • C-1 read 확장(strategy_state 맵 + occupancy_seq), C-2 write RPC, C-2b occupancy_seq 경량 가드, C-3 close 삭제 불변식, baseline 문서 정합
    • occupancy_seq + version CAS 의미(occupancy_changed/version_conflict/slot_inactive/state_too_large/bad_request/first-create/idempotent)
  • Out of Scope
    • 다중 writer 동시성 제어(단일 writer 전제)
    • strategy_state 장기 보존/감사/이력 저장(별도 논의)
    • C-4(SellIntent/watchdog) 신규 구현 — 기존 계약 확인 항목으로 issue #26 코멘트에서 답변 완료
    • full 3계층 슬롯 구조 분할(별도 asset_slot/slot_occupancy 테이블, strategy_state를 occupancy_id 키로 전환) — 후속 "영구 슬롯 구조 정합" Discovery로 Deferred. daily_slot 네이밍 정합·daily_symbol_entries 잔재 제거도 함께. (정합성 임계부인 occupancy_seq 가드는 본 batch 포함 — C-2b. 중장기 영구 슬롯엔 점유 회전이 드물어 full 분할은 과함; 가드 컬럼 1개로 충분.)

6. 리스크/가정 목록

불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다.

리스크

  • R-1 (High→완화): 점유 전환 ABA 누수 — read v0→close→reopen→write v0 시 X가 계산한 상태가 새 점유 Y에 바인딩(잘못된 stop line, 안전 관련). version CAS(0 vs 0)·slot_inactive(write 시 Y로 이미 active)로는 못 잡음. intraday Mode A(당일 재진입)에서 실재(trex-tsl 팀 #26 지적).
    • 완화 방안: occupancy_seq 경량 가드(C-2b/FR-2b) — 새 점유마다 occupancy_seq ++, write가 expected_occupancy_seq로 점유 일치를 server-side CAS 검증 → 다른 점유면 occupancy_changed reject. ABA를 구조적으로 차단. + →INACTIVE 단일 전이 지점 삭제 훅(모든 close 경로 커버)·테스트로 보강.
  • R-2 (Medium): write RPC race/중복 전달로 상태 덮어쓰기.
    • 완화 방안: occupancy_seq + version CAS + correlation_id("{slot_id}:{strategy_id}:{expected_version}") idempotency. 단일 writer 전제 문서화.
  • R-3 (Low): PG 스키마 추가/마이그레이션 운영 부담(별도 slot_strategy_state 테이블 + 슬롯 occupancy_seq 컬럼).
    • 완화 방안: opaque blob 별도 테이블 + occupancy_seq 컬럼 1개의 최소 스키마(JSONB-on-row는 OQ-4에서 기각). in-memory 구현 병행으로 paper/테스트 무중단.

가정

아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.

  • A-1 (OQ-4로 확정): strategy_state는 별도 slot_strategy_state 테이블 + 맵(복합 PK (slot_id, strategy_id)) + slot_id 키. 슬롯 본체(daily_slot, 레거시 명 — 실제는 영구 자산 슬롯)에는 정합성 가드용 occupancy_seq 컬럼 1개만 추가(full 테이블 분할 없음).
  • A-2 (OQ-2로 확정): read는 기존 trex.slots.req에 piggyback, write는 별도 신규 RPC trex.slots.strategy_state.set. (가정 → 확정)
  • A-3: 영구 슬롯 모델(dcy-020) 정합상 삭제는 행 삭제가 아니라 →INACTIVE 전이 훅이다(issue #26 코멘트에서 데몬 팀에 이미 고지).

오픈 질문

답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.

  • OQ-1 (OQ-A): data 인코딩을 JSON 문자열로 할지 base64 바이트로 할지, 수용 가능한 크기 상한(제안 4KB)은 얼마인지.
    • 상태: Answered (2026-06-15, Justin Ko)
    • 결정: opaque UTF-8 문자열 + 2단 상한. JSON/base64를 계약 레벨에서 강제하지 않고 core는 유효 UTF-8 + byte 크기 상한만 검증(NFR-1 정합). 상한은 TSL 등 알려진 용례 기본 1~2KB, hard ceiling 4KB(byte 기준). 초과 시 state_too_large 명시적 reject. C-1 스냅샷 응답 크기(max_active_slots × cap) 관점에서 상한을 정한다.
    • 반영 위치: NFR-2(인코딩/크기 한계), FR-2(write 검증·reject 사유).
  • OQ-2 (OQ-B): 신규 RPC(trex.slots.strategy_state.set) vs 기존 메시지(heartbeat/intent) piggyback 중 어느 write 방식을 채택할지.
    • 상태: Answered (2026-06-15, Justin Ko)
    • 결정: write = 별도 RPC trex.slots.strategy_state.set(C-2), read = trex.slots.req piggyback(C-1) 유지. piggyback 기각 근거 — (1) write(거의 매 사이클)와 sell intent(드묾)의 빈도 불일치, (2) trex-core에 dedicated heartbeat 부재 + watchdog는 slots.req가 모든 holding 슬롯에 매 사이클 갱신해야 해 조건부 write와 합치면 커버리지 붕괴, (3) read RPC에 write를 얹으면 멱등성·spy 의미 붕괴, (4) CAS 응답/재시도 격리, (5) trex-core가 이미 concern별 개별 RPC 패턴이라 추가 비용 낮음. read는 어차피 폴링하는 slots.req에 공짜로 얹히므로 비대칭(read piggyback / write 분리)이 의도된 설계.
    • 반영 위치: FR-1(C-1 read piggyback), FR-2(C-2 별도 write RPC). A-2 가정 → 확정.
    • 비고: 사이클당 다중 슬롯 write 시 N개 set RPC 발생 — optional batch 변형은 v1 범위 밖 후속 최적화.
  • OQ-3 (OQ-C): close 시 strategy_state 삭제를 slot-transition 이벤트(trex.events.strategy.*)로 노출할지(데몬 즉시 반응 가능).
    • 상태: Answered (2026-06-15, Justin Ko)
    • 결정: 신규 전용 이벤트 도입하지 않음. 정합성은 (1) 스냅샷 부재(slots.req에서 strategy_state 없음 → 데몬 expected_version=0 first-create) + (2) C-2 CAS rule(닫힌 슬롯 write는 slot_inactive reject로 부활 차단)가 보장한다. 즉시성이 필요하면 이미 발행되는 slot_filled_sell(status=inactive) transition 이벤트를 재사용(모든 매도 close가 execution loop pending_sell→INACTIVE 경로로 이 이벤트를 emit; best-effort). 정 필요하면 그 payload에 strategy_state_cleared: true additive 필드만 추가 — 새 event_type/route 아님. 전용 state-cleared 이벤트는 구체적 필요 발생 시 후속 반복으로 Deferred.
    • 근거: 정합성이 best-effort 이벤트에 의존하지 않도록(source of truth = 스냅샷), 신규 event_type의 라우팅/바인딩/DLQ 비용 회피, transition 이벤트·스냅샷 부재와의 중복 제거.
    • 반영 위치: FR-3(삭제 불변식 — 이벤트 노출은 옵션, 기존 transition 이벤트 재사용 명시), NFR-3(수명 정합).
  • OQ-4 (OQ-D): strategy_state를 슬롯당 다중 strategy 맵으로 둘지 단일 객체로 둘지 + 저장 모델(별도 테이블 vs 슬롯 행 JSONB).
    • 상태: Answered (2026-06-15, Justin Ko)
    • 결정: 별도 slot_strategy_state 테이블 + 맵 + slot_id 키. ① shape = 맵(strategy_id 네임스페이스, 계약 키 (slot_id, strategy_id)로 강제) — 한 슬롯이 strategy_id(매수)·sell_strategy_id(매도)를 동시 보유 가능하므로 단일 객체는 충돌. ② 저장 = 별도 테이블(JSONB-on-row 기각): CAS가 UPDATE ... WHERE version=? rowcount / INSERT ON CONFLICT로 race-safe, hot 슬롯 행(daily_slot) lean 유지, write 경합 분리, C-3 삭제 DELETE WHERE slot_id=? 한 문장. 테이블 PK = 복합 (slot_id, strategy_id), strategy당 1행. C-1 read는 반환 slot_id들로 batched 조회 후 group by 해 맵 조립. in-memory repo는 복합키 dict로 미러. 참조 방향은 strategy_state → slot(테이블이 slot_id를 컬럼으로 보유), slot 본체 스키마 불변.
    • 키 선택(slot_id vs occupancy_id): strategy_state 테이블은 slot_id 키 유지 + occupancy_seq 경량 가드(C-2b) 추가. (2026-06-15 갱신, trex-tsl #4705990067 지적 반영) 당초 "occupancy 식별자 전면 deferred"는 점유 전환 ABA 누수(R-1)를 못 막음이 확인되어 수정 — 정합성 임계부인 occupancy 식별만 슬롯 행 occupancy_seq 컬럼 1개로 v1에 선취하고, write CAS가 expected_occupancy_seq로 점유 일치를 검증한다. full 3계층(별도 occupancy 테이블 + strategy_state를 occupancy_id 키로 전환)만 Deferred — 중장기 영구 슬롯은 점유 회전이 드물어 full 분할이 과하고, intraday(trex-tsl)가 요구하는 정합성은 가드 컬럼으로 충족. strategy_state 격리 테이블이라 추후 full 분할 시 seq→id 흡수도 contained.
    • 반영 위치: FR-1(read 맵 조립), FR-2(CAS 저장), FR-2b/C-2b(occupancy_seq 가드, In Scope), FR-3(close 삭제), §4 영향 범위(별도 StrategyStateStore/테이블), 범위 경계(full 3계층만 Out of Scope), A-1 확정.

오픈 질문 처리 기준

  • Open: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.
  • Answered: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다.
  • Deferred: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다.
  • Dropped: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다.
  • 처리 원칙 1: 질문만 남기지 말고 반드시 상태, 처리 방안, 종료 조건을 함께 적는다.
  • 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다.
  • 처리 원칙 3: High 영향 질문은 Deferred 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다.

7. 초기 성공 기준

이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다.

  • S-1: 데몬이 trex.slots.req로 슬롯과 함께 strategy_state·occupancy_seq를 read하고, trex.slots.strategy_state.set으로 write한 뒤, 같은 슬롯의 다음 스냅샷에서 version+1과 갱신된 data가 보인다.
  • S-2: CAS 의미(occupancy match+version match commit / version_conflict / occupancy_changed / slot_inactive / state_too_large / first-create)와 idempotent 재전송 무부작용이 테스트로 검증된다.
  • S-3: 슬롯이 모든 close 경로(전략 매도 체결/safety-sell/forced-close)로 →INACTIVE 전이 시 strategy_state가 삭제되고, 재사용 슬롯이 새 occupancy_seq + 빈 상태(version 없음)로 시작한다.
  • S-4: 점유 전환 ABA 시나리오(read v0 → close → reopen(occupancy_seq++) → 옛 점유 기준 write) 가 occupancy_changed로 reject 되어 새 점유에 stale 상태가 바인딩되지 않음이 테스트로 검증된다.

측정 방식 및 데이터 출처

각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다.

  • S-1 측정 방식: read/write/재read end-to-end 통합 테스트 / 데이터 출처: messaging_handlers 핸들러 테스트, slots.req 응답 페이로드
  • S-2 측정 방식: strategy_state.set 핸들러 단위 테스트(6 케이스: occupancy+version commit/version_conflict/occupancy_changed/slot_inactive/state_too_large/first-create + 중복 재전송) / 데이터 출처: 신규 핸들러/스토어 테스트
  • S-3 측정 방식: 각 close 경로 후 strategy_state 부재 + occupancy_seq 증가 검증 테스트 / 데이터 출처: slot service →INACTIVE/inactive→pending_buy 전이 테스트, 스토어 상태
  • S-4 측정 방식: 점유 전환 race 단위 테스트(occupancy_seq mismatch → occupancy_changed) / 데이터 출처: strategy_state.set 핸들러/스토어 테스트

8. REQ로 넘기기 전 확인 체크

REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.

  • 범위(In Scope/Out of Scope) 확정
    • 기준: C-1/C-2/C-2b/C-3 + baseline 포함, C-4·다중writer·장기보존·full 3계층 분할 제외 합의 — confirm-discovery 확정.
  • 성공 지표의 측정 방식/데이터 출처 확정
    • 기준: S-1~S-4 테스트 가능 형태로 확정 (§7 측정 방식 참조)
  • High 영향 리스크 우선순위 및 해소 책임자 지정
    • 기준: R-1(점유 전환 ABA, High) → occupancy_seq 가드(C-2b/FR-2b)로 완화안 채택 확정. R-2/R-3 Medium/Low.
  • 모든 오픈 질문(OQ) Open 상태 없음 확인
    • 기준: OQ 목록에 Open 상태 항목이 0개이거나, 잔여 항목 전부 Deferred 처리 완료
    • 결과: OQ-1~4 전부 Answered (2026-06-15). full 3계층 분할만 후속 Discovery로 Deferred(occupancy_seq 가드는 v1 포함).
  • Freeze 확정 담당자(CONFIRMED_BY) 지정
    • 기준: confirm-discovery 시 승인자 지정 — Justin Ko (2026-06-15)

9. 파일 처리 결과

이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다.

  • 처리 결과: 생성
  • 생성 경로: docs/discovery/dcy-028_20260615_slot-strategy-state-store.md
  • 참조 문서:
    • GitHub issue #26 (Slot Strategy-State 계약 제안) + C-4 확인 코멘트 + OQ 결정 코멘트 + occupancy race 피드백/답변(#4705990067, #4706137866)
    • docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (영구 슬롯/→INACTIVE 모델)
    • docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md (slots.req read surface)
    • docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md (영구 슬롯/long-term 프로파일 연계)
    • stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md

10. 사용자 결정 필요 항목 요약

사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.

DECIDE

  • data 인코딩 + 크기 상한 — OQ-A → 해결됨(2026-06-15): opaque UTF-8 문자열 + 2단 상한(기본 1~2KB / hard ceiling 4KB), 초과 시 state_too_large reject. (OQ-1 참조)
  • write 경로(별도 RPC vs piggyback) — OQ-B → 해결됨(2026-06-15): write = 별도 RPC(C-2), read = slots.req piggyback(C-1) 유지. (OQ-2 참조)
  • 슬롯당 다중 strategy 맵 vs 단일 객체 + 저장 모델(테이블 vs JSONB) — OQ-D → 해결됨(2026-06-15): 별도 slot_strategy_state 테이블 + 맵(복합 PK (slot_id, strategy_id)) + slot_id 키, occupancy_id 후속 Deferred. (OQ-4 참조)

CONFIRM

  • close 시 strategy_state 삭제를 slot-transition 이벤트로 노출할지 — OQ-C → 해결됨(2026-06-15): 신규 이벤트 없음, 스냅샷 부재 + CAS 의존, 기존 slot_filled_sell 옵션 재사용. (OQ-3 참조)
  • 삭제 훅을 영구 슬롯 →INACTIVE 전이로 두는 설계(행 삭제 아님) — A-3·FR-3로 확정, 데몬 팀 고지 완료. confirm-discovery 승인 시 함께 확정.

DATA

  • PG 컬럼/메시지 크기 기준 실수용 가능한 data 상한 값 → 확정(OQ-1): 기본 1~2KB / hard ceiling 4KB(byte 기준)

11. Discovery Freeze

REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.

섹션 참조

Freeze 플래그

  • Handoff Decision: 승인
  • Handoff Rationale: OQ-1~4 전부 Answered(2026-06-15) — 인코딩(opaque UTF-8+2단 상한)/write 경로(별도 RPC)/삭제 이벤트(신규 없음)/저장 구조(별도 테이블+맵+slot_id 키 + occupancy_seq 경량 가드) 확정. trex-tsl 팀 점유 ABA 지적 반영(C-2b). full 3계층 분할만 후속 Discovery로 Deferred. 범위·성공기준·저장소 사실관계 확인 완료, C유형 placeholder·Open OQ 0개 → REQ 초안 작성 진행 가능.
  • Ready for REQ Drafting: true
  • Confirmed By: Justin Ko
  • Confirmed At (KST): 2026-06-15 17:52