Stop Persisting Non-Signal Option Prints in ClickHouse
+ + +Summary
+Implemented a signal-gated persistence path for option prints in ingest-options. With the new default configuration, prints that fail the initial signal gate (signal_pass=false) are no longer inserted into ClickHouse, while JetStream publish behavior remains unchanged.
Changes Made
+-
+
- Added
OPTIONS_PERSIST_SIGNAL_ONLYenv support (defaulttrue) inservices/ingest-options/src/index.ts.
+ - Refactored option-trade side effects into a new helper module:
services/ingest-options/src/trade-pipeline.ts.
+ - Updated ingest trade handling to gate ClickHouse inserts by
signal_passwhen signal-only mode is enabled.
+ - Kept publish behavior as-is: always publish to
options.prints, publish tooptions.signal_printsonly whensignal_pass=true.
+ - Added targeted tests in
services/ingest-options/tests/trade-pipeline.test.ts.
+ - Added
OPTIONS_PERSIST_SIGNAL_ONLY=trueto both env example files.
+ - Documented one-time cleanup SQL and mutation verification steps in
docs/clickhouse-reset-runbook.md.
+
Context
+The options pipeline enriches and classifies prints before persistence and fanout. Previously, all enriched prints were inserted into ClickHouse regardless of signal eligibility, which retained low-value noise in durable history. The intended direction is to keep durable history aligned with the signal gate while preserving stream fanout compatibility.
+ +Important Implementation Details
+-
+
OPTIONS_PERSIST_SIGNAL_ONLYparses standard boolean env strings (true/false,1/0,yes/no,on/off) and defaults totrue.
+ - Persistence decision logic is centralized in
shouldPersistOptionPrint()for easy testing and future reuse.
+ - A startup log line now reports the active persistence mode for quick operator visibility. +
- Cleanup SQL is intentionally documented as a manual one-off operational step, not automatic startup behavior. +
- Expected semantic effect: new “raw” ClickHouse history for options will be signal-only when default mode is used; replay paths sourced from ClickHouse option prints will reflect the same dataset. +
Relevant Diff Snippets
+Unified diffs below are formatted to be compatible with diffs.com rendering conventions.
+diff --git a/services/ingest-options/src/index.ts b/services/ingest-options/src/index.ts
+@@
++ OPTIONS_PERSIST_SIGNAL_ONLY: z.preprocess(..., z.boolean()).default(true),
+@@
++ logger.info("option print clickhouse persistence mode", { signal_only: env.OPTIONS_PERSIST_SIGNAL_ONLY });
+@@
+- await insertOptionPrint(clickhouse, print);
+- await publishJson(js, SUBJECT_OPTION_PRINTS, print);
+- if (print.signal_pass) {
+- await publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, print);
+- }
++ await processOptionTrade(print, {
++ persistSignalOnly: env.OPTIONS_PERSIST_SIGNAL_ONLY,
++ persist: async (value) => insertOptionPrint(clickhouse, value),
++ publishRaw: async (value) => publishJson(js, SUBJECT_OPTION_PRINTS, value),
++ publishSignal: async (value) => publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, value)
++ });
+
+diff --git a/services/ingest-options/src/trade-pipeline.ts b/services/ingest-options/src/trade-pipeline.ts
+@@
++export const shouldPersistOptionPrint = (print, persistSignalOnly) => !persistSignalOnly || print.signal_pass === true;
++
++export const processOptionTrade = async (print, deps) => {
++ if (shouldPersistOptionPrint(print, deps.persistSignalOnly)) {
++ await deps.persist(print);
++ }
++ await deps.publishRaw(print);
++ if (print.signal_pass) {
++ await deps.publishSignal(print);
++ }
++};
+
+diff --git a/docs/clickhouse-reset-runbook.md b/docs/clickhouse-reset-runbook.md
+@@
++## One-Time Cleanup: Remove Non-Signal Option Prints
++docker compose exec clickhouse clickhouse-client --query "ALTER TABLE option_prints DELETE WHERE signal_pass = 0"
++...monitor with system.mutations and verify remaining_non_signal count...
+
+ Expected Impact for End-Users
+Options history and replay streams backed by ClickHouse contain less noise and better reflect actionable signal flow. This improves signal-to-noise in historical tape usage without changing event schemas or API contract shapes.
+ +Validation
+-
+
- Ran focused tests:
bun test services/ingest-options/tests/trade-pipeline.test.ts(pass).
+ - Attempted broader ingest-options test run:
bun test services/ingest-options/tests(failed in this worktree due to missing module resolution for@islandflow/typesin existing tests unrelated to this change).
+ - Manual review confirmed no API schema/type signature changes were introduced. +
Issues, Limitations, and Mitigations
+-
+
- Cleanup of historical non-signal rows is manual; it is not auto-executed by services to avoid accidental destructive behavior. +
- ClickHouse delete mutations are asynchronous. Mitigation: documented mutation-status query and post-delete verification query. +
- The full local test suite for
services/ingest-options/testswas not fully runnable in this worktree due to module-resolution setup, so validation relied on targeted tests plus static review.
+
Follow-up Work
+-
+
- Run the one-time cleanup mutation in each environment that should drop historical non-signal rows. +
- After cleanup completion, verify zero remaining rows where
signal_pass = 0.
+ - Optionally add an operational metric for dropped non-signal persistence decisions if observability of suppression volume is needed. +