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>
63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
"""Slot-core daemon entrypoint (REQ-005).
|
|
|
|
Builds the FastAPI app wired to PostgreSQL adapters, applies migrations and
|
|
preallocates the trading day's slots on startup (lifecycle, not a public
|
|
endpoint), then serves via uvicorn. Console script: `trex-core-daemon`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from contextlib import asynccontextmanager
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from trex_core.adapters.api.app import create_app
|
|
from trex_core.adapters.persistence.migrate import apply_migrations
|
|
from trex_core.adapters.persistence.in_memory_intent_store import InMemoryIntentStore # noqa: F401
|
|
from trex_core.adapters.persistence.postgresql import (
|
|
PostgreSQLIntentStore,
|
|
PostgreSQLSlotRepository,
|
|
)
|
|
from trex_core.application.intents import IntentService
|
|
from trex_core.domain.slot import SlotService
|
|
from trex_core.runtime.config import DaemonConfig
|
|
|
|
|
|
def build_app(config: DaemonConfig) -> FastAPI:
|
|
"""Construct the daemon app with PostgreSQL adapters + startup lifecycle."""
|
|
slot_repo = PostgreSQLSlotRepository(config.dsn)
|
|
intent_store = PostgreSQLIntentStore(config.dsn)
|
|
slot_service = SlotService(slot_repo)
|
|
intent_service = IntentService(slot_service, slot_repo, intent_store)
|
|
|
|
base = create_app(
|
|
slot_service=slot_service,
|
|
slot_repo=slot_repo,
|
|
intent_service=intent_service,
|
|
default_total_slots=config.total_slots,
|
|
)
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI):
|
|
# Startup: migrate schema, preallocate today's slots (idempotent).
|
|
apply_migrations(config.dsn)
|
|
today = datetime.now(tz=timezone.utc).date()
|
|
slot_service.preallocate_daily_slots(today, config.total_slots)
|
|
yield
|
|
# Shutdown: connection-per-call adapters hold no pooled state to close.
|
|
|
|
base.router.lifespan_context = lifespan
|
|
return base
|
|
|
|
|
|
def main() -> None: # pragma: no cover - process entrypoint
|
|
import uvicorn
|
|
|
|
config = DaemonConfig.from_env()
|
|
app = build_app(config)
|
|
uvicorn.run(app, host=config.host, port=config.port)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|