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,74 @@
|
||||
-- BAT-002 / REQ-006: trex-core-owned slot-core schema.
|
||||
--
|
||||
-- Authored fresh for trex-core, keyed to the Slot column contract in
|
||||
-- dcy-001 Appendix A (orbit-v2 DDL used only as reference). Scope: slice 1.
|
||||
-- Excludes daily_slot_archive / retention prune (out of scope, dcy-002).
|
||||
|
||||
-- daily_slot — current lifecycle state (single-writer source of truth).
|
||||
-- Columns mirror dcy-001 Appendix A (16 columns) and trex_core EXPECTED_SLOT_FIELDS.
|
||||
CREATE TABLE IF NOT EXISTS daily_slot (
|
||||
slot_id UUID PRIMARY KEY,
|
||||
trading_date DATE NOT NULL,
|
||||
status TEXT NOT NULL CHECK (
|
||||
status IN ('idle','pending_buy','holding','pending_sell','closed','error')
|
||||
),
|
||||
symbol TEXT,
|
||||
qty NUMERIC(18, 4),
|
||||
avg_price NUMERIC(18, 4),
|
||||
entry_price NUMERIC(18, 4),
|
||||
allocated_capital NUMERIC(18, 2),
|
||||
order_id TEXT,
|
||||
strategy_id TEXT,
|
||||
broker_order_id TEXT,
|
||||
portfolio_id TEXT,
|
||||
sell_strategy_id TEXT,
|
||||
forced_close_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_daily_slot_trading_date_status
|
||||
ON daily_slot (trading_date, status);
|
||||
|
||||
-- daily_symbol_entries — per-day re-entry counter (decision input).
|
||||
CREATE TABLE IF NOT EXISTS daily_symbol_entries (
|
||||
trading_date DATE NOT NULL,
|
||||
symbol TEXT NOT NULL,
|
||||
entry_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_entry_at TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'),
|
||||
PRIMARY KEY (trading_date, symbol)
|
||||
);
|
||||
|
||||
-- slot_transition_log — append-only audit of every transition.
|
||||
CREATE TABLE IF NOT EXISTS slot_transition_log (
|
||||
log_id BIGSERIAL PRIMARY KEY,
|
||||
slot_id UUID NOT NULL,
|
||||
from_state TEXT NOT NULL,
|
||||
to_state TEXT NOT NULL,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'),
|
||||
reason TEXT NOT NULL,
|
||||
actor TEXT NOT NULL,
|
||||
extra JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_slot_transition_log_slot_id_ts
|
||||
ON slot_transition_log (slot_id, ts);
|
||||
|
||||
-- intent_log — durable idempotency store + audit for accepted intents
|
||||
-- (dcy-002 OQ-1). intent_id UNIQUE is the dedup gate that prevents double
|
||||
-- slot transitions on at-least-once retransmission. Precursor to a full
|
||||
-- outbox (status column reserved); async consumer loop deferred to the
|
||||
-- broker-execution Discovery.
|
||||
CREATE TABLE IF NOT EXISTS intent_log (
|
||||
intent_id TEXT PRIMARY KEY,
|
||||
intent_type TEXT NOT NULL CHECK (intent_type IN ('buy','sell')),
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'processing'
|
||||
CHECK (status IN ('processing','admitted','rejected')),
|
||||
slot_id UUID,
|
||||
reason TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_intent_log_status ON intent_log (status);
|
||||
Reference in New Issue
Block a user