1010-- envelope source = "hb_runtime".
1111--
1212-- NO TTL: replay-critical strategy history, retained indefinitely.
13+ --
14+ -- Delivery contract: the producer is AT-MOST-ONCE over a bounded in-memory
15+ -- queue. It drops the oldest row when the queue is full and discards a batch
16+ -- after two failed POST attempts, so rows can be lost silently. To make loss
17+ -- distinguishable from "the event never happened", every row carries `seq`: a
18+ -- monotonic counter starting at 1, scoped to one (controller_id, run_id) and
19+ -- shared across ALL tables in this database. Gap detection is therefore a UNION
20+ -- of these tables filtered by run_id, looking for holes in `seq`; the producer's
21+ -- heartbeat carries dropped_rows/failed_batches as the aggregate counterpart.
22+ -- A hole proves an allocated row was lost. The converse does not hold: paths
23+ -- that skip emission entirely (e.g. blocked Layer12 ticks bypassing the archive
24+ -- tap) never allocate a `seq`, so their absence leaves no hole.
25+ --
26+ -- Adding a column here is a POISON PILL if it ships after the producer. Inserts
27+ -- use JSONEachRow, so a row carrying a column ClickHouse does not have fails the
28+ -- whole per-table batch; the forwarder returns non-2xx and the producer drops it.
29+ -- Deploy the forwarder (which applies this file at startup) BEFORE the producer.
1330
1431CREATE DATABASE IF NOT EXISTS strategy_data;
1532
@@ -28,6 +45,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.policy_evaluation_events
2845 trading_pair LowCardinality(String),
2946 market_id String,
3047 run_id String,
48+ -- Per-run gap-detection counter; see the delivery contract at the top.
49+ seq UInt64,
3150
3251 policy_epoch String,
3352 fidelity LowCardinality(String),
@@ -61,6 +80,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.strategy_policy_snapshots
6180 trading_pair LowCardinality(String),
6281 market_id String,
6382 run_id String,
83+ -- Per-run gap-detection counter; see the delivery contract at the top.
84+ seq UInt64,
6485
6586 snapshot_reason LowCardinality(String),
6687 policy_epoch String,
@@ -88,6 +109,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.market_identity
88109 trading_pair LowCardinality(String),
89110 market_id String,
90111 run_id String,
112+ -- Per-run gap-detection counter; see the delivery contract at the top.
113+ seq UInt64,
91114
92115 snapshot_reason LowCardinality(String),
93116 source_hash String,
@@ -115,6 +138,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.symbol_mapping
115138 trading_pair LowCardinality(String),
116139 market_id String,
117140 run_id String,
141+ -- Per-run gap-detection counter; see the delivery contract at the top.
142+ seq UInt64,
118143
119144 snapshot_reason LowCardinality(String),
120145 source_hash String,
@@ -140,6 +165,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.inventory_settlement_events
140165 trading_pair LowCardinality(String),
141166 market_id String,
142167 run_id String,
168+ -- Per-run gap-detection counter; see the delivery contract at the top.
169+ seq UInt64,
143170
144171 event_kind LowCardinality(String),
145172 token LowCardinality(String),
@@ -151,3 +178,20 @@ CREATE TABLE IF NOT EXISTS strategy_data.inventory_settlement_events
151178ENGINE = MergeTree
152179PARTITION BY toYYYYMM(fromUnixTimestamp64Milli(event_time_ms))
153180ORDER BY (controller_id, trading_pair, event_time_ms);
181+
182+ -- Backfill the gap-detection counter onto already-created tables. Existing rows
183+ -- keep seq = 0; only runs that start after the producer ships allocate real values.
184+ ALTER TABLE strategy_data .policy_evaluation_events
185+ ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
186+
187+ ALTER TABLE strategy_data .strategy_policy_snapshots
188+ ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
189+
190+ ALTER TABLE strategy_data .market_identity
191+ ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
192+
193+ ALTER TABLE strategy_data .symbol_mapping
194+ ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
195+
196+ ALTER TABLE strategy_data .inventory_settlement_events
197+ ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
0 commit comments