feat(scripts): M1 end-to-end smoke (Deribit → ingestion → engine → API → WS) - #69
Merged
Merged
Conversation
scripts/e2e-smoke.sh boots every M1 service in dependency order (compose storage → ingestion → engine → API), polls for two engine snapshots so the assertions land while the engine is warm, then runs five hard checks covering both the storage layer and the API surface: 1. options_ticks has ≥ 1 fresh row in the last 60 s 2. index_ticks has ≥ 1 fresh row in the last 120 s 3. GET /v1/index/bvol/latest returns 200, value > 0, age < 90 s 4. GET /v1/index/bvol/history?interval=5m&limit=12 returns 200, bars ≥ 1 5. ws:///v1/stream delivers one tick per channel inside 75 s scripts/e2e-ws-client.py is the WS verifier — strict frame-shape check against the PRD §6 contract, exits non-zero with a diagnostic when a channel goes silent. Live verified end-to-end on the dev box: 198 s total, all five assertions pass. ClickHouse fresh-data query uses curl -G (URL-param) rather than the form-encoded body, and compose-down --volumes runs at the top of the script so a previous session's index_ticks rows can not satisfy "fresh row" assertions before the new pipeline writes anything. Rust + Go binaries are built once up front and re-used (no cargo-run cold-compile during the wait_until window). Engine wait is poll-based so slow ingestion warmup never flakes the run. EXIT trap reaps the Rust release binaries by name (cargo-run does not always reap its fork-exec child) and tears compose down. Bash-3.2 compatible (parallel indexed arrays for the stage-timing summary, no declare -A) so it runs on a stock macOS shell. README "End-to-end smoke" section documents the script + dependencies. M1 close gate per #66.
The new smoke script builds the Go API to ./api/api-bin and runs it in-place. Without this rule a clean `git add` after the smoke would stage the binary.
- HIGH-1: bump ENGINE_WAIT_S default 135 → 240. Cold Deribit instrument enumeration pushes the first snapshot to t=60-90, the second to t=120-150, leaving no margin under the old deadline. The new value fits two cycles plus ~90 s of cold-start slack and can still be tuned down on a warm cache via the env override. - HIGH-2: relax /latest age threshold 90 → 150 s. The poll exits the moment two distinct snapshots exist; the second can be ~60 s old by the time the REST assertion runs, and a brief engine stall pushes it past 90 s deterministically. 150 s = two full cycles + slack. - MED-3: introduce PRESERVE_VOLUMES env guard. Default 0 keeps the destructive `compose down -v` behaviour (clean-slate smoke) but setting it to 1 skips the wipe so a dev iterating on the script doesn't lose accumulated local ClickHouse data. README assertion-table thresholds + interval phrasing updated to match the script. Re-verified end-to-end after the changes: 206 s total, all 5 assertions pass.
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
M1 close gate per issue #66.
scripts/e2e-smoke.shboots every M1 service in dependency order, polls for two engine snapshots so the pipeline is warm, then runs five hard assertions covering both the storage layer and the API surface.After this lands, the published claim "the local pipeline works end-to-end" is enforceable by a single command.
What the script does
Assertions
options_ticksfresh rowsindex_ticksfresh rowsGET /v1/index/bvol/latestGET /v1/index/bvol/history?interval=5m&limit=12ws:///v1/streamsubscribescripts/e2e-ws-client.pyis the WS verifier — strict frame-shape check against the PRD §6 contract (type:"tick", lowercasechannel, integertsms, floatvalue/confidence), exits non-zero with a diagnostic when a channel goes silent. Reused by the script and runnable standalone against an already-up pipeline.Live verification (dev box)
WS frames received in PRD wire shape, exact:
Robustness fixes baked in
Each of these surfaced during dev-box verification:
declare -A: invalid optionon stock macOSvolx-normalizerSyntax error: failed at position 1 ('query')from ClickHouse--data-urlencodePOSTs the form-encoded body; needs-Gto send as URL param. Helper now uses-Gindex_ticksfrom a previous session satisfied "fresh row" assertions--volumesat script start guarantees a clean slate/latest age (107s) ≥ 90sfailure on cold runscargo runcold-compile blew the 60 s api-ready timeoutcargo runfork-exec child not reaped on teardowntarget/release/volx-ingestion, etc.) plus the parent PIDFile map
scripts/e2e-smoke.shscripts/e2e-ws-client.pyREADME.md"End-to-end smoke" section.gitignoreapi/api-binproduced by the build phaseDependencies
docker(compose v2)cargo(Rust ≥ 1.85)go(1.25)curlpython3withwebsocketsConfiguration
ENGINE_WAIT_S135WS_TIMEOUT_S75CLICKHOUSE_DBvolxPYTHON_BINresearch/.venv/bin/python3Out of scope
WHERE venue IN (...)checks once OKX + Bybit ingest landTest plan
bash -n scripts/e2e-smoke.shclean on macOS bash 3.2python3 -m py_compile scripts/e2e-ws-client.pycleandown --volumes+ binary-name pkill)enginemid-run on a separate execution to confirm the/latestage assertion fails with the expected messageFixes #66