Deliver BAT-002: slot-core daemon + HTTP intent API + PostgreSQL
Implement REQ-005~009 (dcy-002 slice 1) as an onion stack on top of the
BAT-001 pure domain:
- application: IntentService (admission + single-writer transition +
intent_id idempotency via durable intent_log)
- adapters/persistence: PostgreSQLSlotRepository + PostgreSQLIntentStore +
trex-core-owned migrations (daily_slot per Appendix A, daily_symbol_entries,
slot_transition_log, intent_log) + in-memory intent store
- adapters/api: FastAPI /v1 (healthz, intents/buy|sell|{id}, slots, capacity)
- runtime: trex-core-daemon entrypoint with lifespan migrate+preallocate
Verification (against local PostgreSQL 18): 74 passed, ruff clean, domain
import sweep shows zero web/db deps (NFR-1), 20-thread concurrency admits
exactly the idle count (NFR-2), idempotent resubmit causes no double
transition (NFR-3), EXPECTED_SLOT_FIELDS<->information_schema drift test
(realizes dcy-001 OQ-2's deferred DB-side check), and a daemon E2E smoke.
Align all four baseline docs to the new daemon/API/persistence layers.
BAT-002 -> release-candidate; REQ-005~009 -> Implemented. Full async outbox,
broker execution, safety floor, and strategy daemons remain deferred to a
follow-up Discovery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# BAT-002 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-002
|
||||
|
||||
## Architecture Summary
|
||||
- onion 3-레이어로 구성한다. **domain**(`trex_core.domain.slot`, BAT-001, 무의존) ← **application**(intent admission/슬롯 전이 유스케이스, SlotService 호출) ← **adapters**(FastAPI HTTP 입구 + psycopg PostgreSQL SlotRepository) + **runtime**(데몬 엔트리포인트/uvicorn/lifecycle). 코어 데몬이 `daily_slot` 단일 writer + intent_log 소유. 전략 데몬은 HTTP 클라이언트.
|
||||
|
||||
## Changed Areas
|
||||
- Docs: docs/interface-contract.md(/v1 API), docs/runtime-flows.md(데몬·요청 흐름), docs/project-structure.md(레이어), docs/data-model.md(intent_log + 영속 테이블) — REQ-009
|
||||
- Modules:
|
||||
- runtime: 데몬 엔트리포인트(console_scripts), uvicorn 기동, lifecycle(startup: preallocate / shutdown: graceful), `/v1/healthz`
|
||||
- adapters/api: FastAPI 앱 + `/v1` 라우터(intents buy/sell/{id}, slots, capacity) + pydantic 요청/응답 스키마
|
||||
- adapters/persistence: psycopg `SlotRepository` 구현 + 마이그레이션 SQL
|
||||
- application: intent 유스케이스(admission → SlotService.transit_slot + intent_log 기록, intent_id 멱등)
|
||||
- Interfaces: `/v1` HTTP 6종(dcy-002 OQ-3)
|
||||
|
||||
## Key Decisions
|
||||
1. 동기 admission+커밋: HTTP 핸들러가 단일 트랜잭션(advisory lock)에서 admission→슬롯 전이→intent_log 기록을 커밋하고 admit/reject 동기 응답(dcy-002 OQ-1). 완전 비동기 outbox 는 브로커 Discovery 로 Deferred.
|
||||
2. 멱등: `intent_log.intent_id` UNIQUE + 수락 전 dedup 조회. 재전송 시 최초 decision 재응답. `GET /v1/intents/{id}` 로 재조회.
|
||||
3. 단일 writer(불변식 A): `daily_slot` 쓰기는 application 의 SlotService 경로뿐. read API(REQ-008)는 쓰기 없음. 동시 intent 는 `pg_advisory_xact_lock(SLOT_ADVISORY_LOCK_ID)` 로 직렬화.
|
||||
4. 스키마(dcy-002 OQ-4): 부록 A 단일 기준 trex-core 소유 마이그레이션. 테이블 = daily_slot/daily_symbol_entries/slot_transition_log/intent_log. archive/prune 제외. orbit-v2 DDL 은 참조.
|
||||
5. 도메인 순수성(NFR-1): FastAPI/psycopg 의존은 adapters/runtime 에만. `trex_core.domain.slot` 은 import 스윕 0 유지(BAT-001 계약 테스트 패턴 확장).
|
||||
|
||||
## Edge Cases
|
||||
- 동일 intent_id 재전송(네트워크 재시도) → 슬롯 중복 전이 없이 최초 decision 반환.
|
||||
- 동시 buy intent 가 같은 idle 슬롯/마지막 슬롯 경합 → advisory lock 직렬화, 한쪽은 admit 한쪽은 reject(슬롯 소진).
|
||||
- sell intent 의 slot_id 가 이미 holding 이 아님(이미 전이됨) → reject + 사유.
|
||||
- PostgreSQL 미가용(검증 환경) → 통합테스트 evidence blocker 로 분리(코드/단위는 별도 통과).
|
||||
- preallocate 재기동 → 멱등(이미 있으면 0).
|
||||
|
||||
## Architecture Impact
|
||||
- both (project-structure + runtime-flows). interface-contract/data-model 도 신규 계약/테이블로 갱신.
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: domain/application/adapters/runtime 레이어와 신규 모듈 트리 반영(REQ-009).
|
||||
- runtime-flows.md: 데몬 lifecycle 흐름 + intent 요청 흐름(admission→전이→intent_log) 추가(REQ-009).
|
||||
- interface-contract.md: `/v1` 6종 입력/출력/에러/멱등 계약 추가(REQ-009).
|
||||
- data-model.md: intent_log 엔티티 + 영속 테이블 계약 반영(REQ-009).
|
||||
@@ -0,0 +1,38 @@
|
||||
# BAT-002 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-002
|
||||
|
||||
## Plan Summary
|
||||
- REQ-005 런타임 → REQ-006 영속화 → REQ-007 intent API → REQ-008 조회 API → REQ-009 baseline 정합을 순차 구현. onion 4-레이어(domain/application/adapters/runtime). 도메인(BAT-001)은 무수정 재사용.
|
||||
|
||||
## Changed Files
|
||||
- pyproject.toml: fastapi/uvicorn/psycopg 의존성 + dev httpx + console_scripts `trex-core-daemon`.
|
||||
- application: trex_core/application/{ports.py(IntentStore), intents.py(IntentService/BuyIntent/SellIntent/IntentDecision)}
|
||||
- adapters/persistence: postgresql.py(PostgreSQLSlotRepository + PostgreSQLIntentStore), in_memory_intent_store.py, migrate.py, sql/001_create_slot_core_tables.sql
|
||||
- adapters/api: schemas.py(pydantic), app.py(create_app, /v1 라우터)
|
||||
- runtime: config.py(DaemonConfig), daemon.py(build_app + lifespan migrate/preallocate + uvicorn main)
|
||||
- tests: tests/adapters/api/test_intent_api.py, tests/application/test_intent_concurrency.py, tests/adapters/persistence/test_postgresql_repository.py, tests/adapters/persistence/test_migration_and_drift.py, tests/conftest.py
|
||||
- docs(REQ-009): interface-contract.md, runtime-flows.md, project-structure.md, data-model.md
|
||||
|
||||
## Execution Log
|
||||
1. SQL 마이그레이션(daily_slot[부록 A 16컬럼]/daily_symbol_entries/slot_transition_log/intent_log) + migrate 러너.
|
||||
2. PostgreSQL SlotRepository(연결-당-호출, pg_advisory_xact_lock) + PostgreSQLIntentStore(intent_id UNIQUE dedup gate).
|
||||
3. IntentService: dedup(try_insert) → admission(find idle / fetch holding) → SlotService.transit_slot(advisory lock) → finalize intent_log. last-idle 경합은 SlotStateMismatchError 재시도.
|
||||
4. FastAPI create_app(/v1 healthz·intents/buy·sell·{id}·slots·capacity), DI 로 in-memory/PG 양쪽 구동.
|
||||
5. 데몬 엔트리포인트: lifespan 에서 migrate + 거래일 preallocate(공개 엔드포인트 아님).
|
||||
6. 테스트 작성 + 로컬 PostgreSQL 18 기동 DB(trex_core_test)로 통합 실행.
|
||||
|
||||
## Validation
|
||||
- `pytest`: **74 passed** (PostgreSQL DSN 설정 시) / **66 passed + 8 skipped** (DSN 미설정 시 PG 통합 graceful skip).
|
||||
- `ruff check .`: **All checks passed**.
|
||||
- 도메인 순수성(NFR-1): `trex_core/domain/` 에 fastapi/uvicorn/psycopg/starlette import **0**(grep 확인).
|
||||
- 멱등(NFR-3): API/PG 양쪽에서 동일 intent_id 재전송 시 동일 decision·슬롯 중복 전이 없음(테스트 + 데몬 스모크).
|
||||
- 단일 writer(NFR-2): 20-thread 동시 buy → 정확히 idle 수(5)만 admit, 슬롯 중복 사용 0.
|
||||
- 드리프트(REQ-006): `EXPECTED_SLOT_FIELDS` ↔ `daily_slot` information_schema 일치 + 음성 케이스 검출.
|
||||
- 데몬 E2E 스모크: lifespan migrate+preallocate(5 idle) → buy admit→pending_buy → 멱등 resubmit 동일 slot_id → capacity 갱신 → intent lookup, PostgreSQL 영속 확인.
|
||||
|
||||
## Remaining Risks
|
||||
- intent 비원자성 미세 창: dedup insert 와 transit 가 별 트랜잭션이라, 둘 사이 크래시 시 intent_log 가 'processing' 으로 남을 수 있음(슬롯 중복 전이는 UNIQUE gate 로 차단). 완전 원자/outbox 는 브로커 Discovery 인계.
|
||||
- admission 은 슬라이스 1 기준 슬롯 가용성 중심(orbit REQ-028 다중 게이트는 범위 밖).
|
||||
- 연결-당-호출 repo 는 고부하 시 풀링 필요(후속 최적화).
|
||||
@@ -0,0 +1,32 @@
|
||||
# BAT-002: trex-core 슬롯 코어 데몬화 + HTTP intent API (슬라이스 1)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-002
|
||||
- Included REQ: REQ-005, REQ-006, REQ-007, REQ-008, REQ-009
|
||||
- Excluded REQ: 없음
|
||||
|
||||
## Summary
|
||||
- dcy-002 의 슬라이스 1(데몬 + HTTP API + PostgreSQL 영속화 + 멱등 intent)을 하나의 delivery 단위로 묶는다. BAT-001 의 순수 도메인을 안쪽 코어로 두고 그 바깥에 런타임(REQ-005)·영속화(REQ-006)·API(REQ-007/008)·문서정합(REQ-009)을 얹는다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- REQ-005: 데몬 런타임 + lifecycle(기동/정상종료/`GET /v1/healthz`) + 거래일 preallocate(lifecycle 내부) + FastAPI/uvicorn/psycopg 의존성·entrypoint.
|
||||
- REQ-006: PostgreSQL `SlotRepository` + 신규 마이그레이션(daily_slot/daily_symbol_entries/slot_transition_log/intent_log) + 드리프트 테스트.
|
||||
- REQ-007: `POST /v1/intents/buy|sell` + 동기 admission/전이 + intent_id 멱등 + `intent_log` + `GET /v1/intents/{id}`.
|
||||
- REQ-008: `GET /v1/slots?status=holding` + `GET /v1/capacity` (read-only).
|
||||
- REQ-009: interface-contract/runtime-flows/project-structure/data-model 정합.
|
||||
- Out of Scope (후속 Discovery):
|
||||
- 브로커 단일 세션/주문 집행(불변식 B), safety floor(forced_close/EOD/손절 backstop), 완전 비동기 outbox, 매수·매도 전략 데몬, 인증/인가 고도화·수평 확장.
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- Source Discovery 는 dcy-002 단일. 모든 REQ 가 dcy-002 FR-1~FR-6 에서 분해됨.
|
||||
- 확정 설계(dcy-002 OQ): 동기 커밋+intent_log(OQ-1) / FastAPI+uvicorn(OQ-2) / `/v1` 엔드포인트 6종(OQ-3) / 부록 A 기준 신규 스키마(OQ-4).
|
||||
- 핵심 리스크: 실제 PostgreSQL·HTTP·신규 의존 도입(검증 환경 필요), 도메인 순수성 유지(어댑터 격리), 단일 writer 불변식.
|
||||
- 검증 환경: PostgreSQL 인스턴스 필요(통합테스트). 미가용 시 일부 evidence 가 blocker 로 남을 수 있음(verification 에서 분리 판단).
|
||||
@@ -0,0 +1,47 @@
|
||||
# BAT-002 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-002
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-005 (Installation): 슬롯 코어 데몬 런타임 부트스트랩
|
||||
- REQ-006 (Data): PostgreSQL SlotRepository 영속화 + 스키마
|
||||
- REQ-007 (Interface): intent 제출 HTTP API + 멱등
|
||||
- REQ-008 (Interface): 슬롯 read-only 조회 HTTP API
|
||||
- REQ-009 (Documentation): 데몬/API/영속화 baseline 정합
|
||||
|
||||
## Delivery Plan
|
||||
- 의존 순서대로 구현한다(기반 → API → 문서).
|
||||
1. REQ-005: pyproject 에 FastAPI/uvicorn/psycopg + console_scripts; 데몬 lifecycle(기동/shutdown/`/v1/healthz`); preallocate 를 lifecycle 내부로.
|
||||
2. REQ-006: psycopg 기반 `SlotRepository` 구현 + trex-core 소유 마이그레이션(daily_slot[부록 A 16컬럼]/daily_symbol_entries/slot_transition_log/intent_log) + `pg_advisory_xact_lock` + 드리프트 테스트.
|
||||
3. REQ-007: FastAPI 라우터 `POST /v1/intents/buy|sell` → 애플리케이션 서비스가 `SlotService` 로 admission/전이(advisory lock 직렬화) + intent_log 동기 기록 + intent_id 멱등 + `GET /v1/intents/{id}`.
|
||||
4. REQ-008: `GET /v1/slots?status=holding` + `GET /v1/capacity` read-only.
|
||||
5. REQ-009: baseline 4문서 정합.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- REQ-006·REQ-007·REQ-008 은 REQ-005(런타임 기반) 선행.
|
||||
- REQ-007·REQ-008 은 REQ-006(영속화, 특히 intent_log) 선행.
|
||||
- REQ-009 는 REQ-005~008 구현 완료 후.
|
||||
- 안쪽 코어는 BAT-001 의 `trex_core.domain.slot`(완성) 재사용.
|
||||
- External:
|
||||
- PostgreSQL 인스턴스(통합테스트/런타임). psycopg, FastAPI, uvicorn(dev/runtime 의존).
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: 신규 3개 레이어(데몬 런타임 + FastAPI API + psycopg 영속화)와 onion 경계(domain/application/adapters), 단일 writer 강제 지점, intent_log/멱등 트랜잭션 경계를 구현 전에 고정해야 한다. 구조/인터페이스/런타임 영향이 모두 있어 design.md 를 같은 턴에 작성한다.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-005: 데몬 기동 + `/v1/healthz` 200 + graceful shutdown.
|
||||
2. M2 — REQ-006: 마이그레이션 적용 + PostgreSQL repo 로 도메인 테스트 통과 + 드리프트 테스트 green.
|
||||
3. M3 — REQ-007: buy/sell intent 전이 + 멱등 재전송 + 동시성 단일 writer 통합테스트.
|
||||
4. M4 — REQ-008: holding 스냅샷 + capacity 조회.
|
||||
5. M5 — REQ-009: baseline 4문서 정합.
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium-High): 실제 PostgreSQL/HTTP/신규 의존 도입 — 검증 환경(PG 인스턴스) 필요. 대응: 통합테스트용 PG 준비; 미가용 시 해당 evidence 를 verification 의 blocker/missing-evidence 로 분리(코드/단위는 별도 통과).
|
||||
- R-2 (Medium): 도메인 순수성 오염(웹/psycopg). 대응: domain/application/adapters 레이어 분리 + import 스윕 가드(NFR-1) — `trex_core.domain.slot` import 0 유지.
|
||||
- R-3 (Medium): 단일 writer 불변식 붕괴(API 핸들러 경유 다중 writer). 대응: 모든 쓰기를 `SlotService`+advisory lock 단일 경로로 강제, 동시성 통합테스트로 검증(NFR-2).
|
||||
- R-4 (Medium): intent 멱등/내구성 결함. 대응: intent_id dedup + intent_log 동기 커밋 + 재전송/재조회 테스트(NFR-3).
|
||||
- R-5 (Low): 컬럼 계약 드리프트. 대응: `EXPECTED_SLOT_FIELDS`↔information_schema 드리프트 테스트(REQ-006).
|
||||
@@ -0,0 +1,53 @@
|
||||
# BAT-002 Verification
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-002
|
||||
|
||||
## Acceptance Mapping
|
||||
- REQ-005 (daemon runtime):
|
||||
- AC1 기동/`/v1/healthz` 200 → 데몬 E2E 스모크(TestClient lifespan) healthz `{status:ok}`. **PASS**
|
||||
- AC2 graceful shutdown → lifespan 컨텍스트 정상 종료(연결-당-호출, 풀 없음). **PASS**
|
||||
- AC3 lifecycle preallocate(멱등, 비공개) → lifespan 에서 preallocate 5 idle 확인, 재기동 멱등. **PASS**
|
||||
- AC4 deps/entrypoint → pyproject 에 fastapi/uvicorn/psycopg + console_scripts `trex-core-daemon` 설치 확인. **PASS**
|
||||
- REQ-006 (PostgreSQL persistence):
|
||||
- AC1 마이그레이션·컬럼 부록 A 일치 → `test_migrations_create_expected_tables` + `test_daily_slot_columns_match_expected_contract`. **PASS**
|
||||
- AC2 PG repo 로 도메인 테스트 통과 → `test_postgresql_repository.py`(preallocate/전이/재진입+1). **PASS**
|
||||
- AC3 드리프트 테스트 → EXPECTED_SLOT_FIELDS↔information_schema 일치 + 음성 케이스(`test_drift_detected_when_contract_extended`). **PASS**
|
||||
- AC4 advisory lock → `pg_advisory_xact_lock` 구현 + 동시성 테스트로 직렬화 확인. **PASS**
|
||||
- REQ-007 (intent API + 멱등):
|
||||
- AC1 buy 전이 → `test_buy_intent_admits_and_transitions` + PG `test_buy_intent_persists_transition`. **PASS**
|
||||
- AC2 sell 전이/거부 → `test_sell_intent_admits_from_holding` / `test_sell_intent_requires_holding`. **PASS**
|
||||
- AC3 멱등 재전송(NFR-3) → `test_buy_intent_idempotent_resubmit`(API) + `test_buy_intent_idempotent_on_pg`(PG) + 데몬 스모크(동일 slot_id). **PASS**
|
||||
- AC4 동시성 단일 writer(NFR-2) → `test_concurrent_buys_admit_exactly_idle_count`(20스레드→정확히 5 admit) + `test_duplicate_intent_id_admits_once_under_concurrency`. **PASS**
|
||||
- AC5 intent 재조회 → `test_get_intent_roundtrip`(`GET /v1/intents/{id}`). **PASS**
|
||||
- REQ-008 (read-only API):
|
||||
- AC1/2 holding 스냅샷·필터 → `test_list_slots_and_capacity`(status/trading_date 필터). **PASS**
|
||||
- AC3 capacity → 동 테스트(idle/holding/pending + scarcity_ratio). **PASS**
|
||||
- REQ-009 (baseline 정합):
|
||||
- AC1 interface-contract /v1 계약 → docs/interface-contract.md 에 6 엔드포인트 + 불변식 추가. **PASS**
|
||||
- AC2 runtime-flows/project-structure 레이어 → 데몬/intent 흐름 + application/adapters/runtime 레이어 반영. **PASS**
|
||||
- AC3 data-model intent_log → docs/data-model.md 에 intent_log 엔티티 + PostgreSQL 백엔드 반영. **PASS**
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- `pytest`: **74 passed** (TREX_POSTGRESQL_TEST_DSN=trex_core_test) / **66 passed + 8 skipped** (DSN 미설정 시 PG graceful skip).
|
||||
- 동시성: 20-thread buy → 5 admit / 15 reject, pending=5·idle=0.
|
||||
- 드리프트: EXPECTED_SLOT_FIELDS == daily_slot 컬럼; 음성 케이스 검출.
|
||||
- Manual:
|
||||
- 도메인 import 스윕: `trex_core/domain/` 에 fastapi/uvicorn/psycopg/starlette import 0(NFR-1).
|
||||
- 데몬 E2E 스모크(PostgreSQL 18): healthz→capacity(5 idle)→buy admit(pending_buy)→멱등 resubmit 동일 slot_id→capacity(4 idle/1 pending)→intent lookup; `daily_slot`·`intent_log` 영속 확인.
|
||||
- `ruff check .`: All checks passed.
|
||||
- Logs:
|
||||
- implementation.md Validation 절 + 본 batch 작업 로그.
|
||||
|
||||
## Baseline Sync Evidence (Architecture Impact = both)
|
||||
- interface-contract.md: HTTP `/v1` 6 엔드포인트 계약 + 멱등/단일writer 불변식 추가(REQ-009).
|
||||
- runtime-flows.md: 데몬 lifecycle + 매수/매도 intent 흐름 추가, Primary Runtime=daemon+library.
|
||||
- project-structure.md: application/adapters/runtime 레이어 + entrypoint 반영.
|
||||
- data-model.md: PostgreSQL 백엔드 + intent_log 엔티티 반영.
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- 없음. 모든 포함 REQ AC 가 evidence 와 연결됨(로컬 PostgreSQL 18 로 통합 evidence 확보).
|
||||
- 비차단 잔여: intent dedup/transit 비원자성 미세 창(슬롯 중복 전이는 UNIQUE gate 로 차단), 연결 풀링 미도입 — 후속 인계.
|
||||
@@ -9,3 +9,4 @@
|
||||
| BAT ID | Profile | Status | Included REQ | Discovery | Folder |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| BAT-001 | standard | release-candidate | REQ-001, REQ-002, REQ-003, REQ-004 | dcy-001 | [bat-001](./bat-001_20260603_trex-core-slot-domain-seed/) |
|
||||
| BAT-002 | standard | release-candidate | REQ-005, REQ-006, REQ-007, REQ-008, REQ-009 | dcy-002 | [bat-002](./bat-002_20260603_slot-core-daemon-http-api/) |
|
||||
|
||||
Reference in New Issue
Block a user