1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# conproxy CI — PR gate.
#
# Every PR (and push to the default branch) runs six jobs. Five start in
# parallel so we never burn heavy e2e time when a cheaper gate would
# block the merge. `e2e` is gated on the cheap gates so the full
# ignored suite + UAT + e2e-security only run when code is otherwise
# green.
#
# 1. unit — fmt, clippy (all feature surfaces + bins),
# lib tests (default / embed-api / mcp /
# release), mcp_test, workspace build,
# embed linker, release binary smoke +
# install-sim.
# 2. integration — testcontainers matrix (qdrant, ES, OS,
# meili, pgvector, cascade, peer,
# circuit, batch, metrics,
# context_config, singleflight).
# `make test-integration`.
# 3. integration-experimental — pinecone + milvus mocks. Default-on
# (cheap, no real backends). `make
# test-integration-experimental`.
# 4. security — cargo audit + cargo deny + lint-security
# clippy. ~1 min.
# 5. fuzz — cargo fuzz on every target, 60s each,
# best-effort (continue-on-error: true).
# Crash repros uploaded as artifacts.
# 6. e2e — needs: [unit, integration]. Full
# ignored e2e_proxy_suite (compose up →
# load → all phases: smoke/health/query,
# cascade, load, reload, observability,
# efficiency, advanced, security) + UAT
# CLI suite + e2e-security. Heaviest job.
#
# Out of CI scope (kept local / nightly / release.yml):
# - e2e_eval (LLM eval: requires Ollama or `EVAL_PROVIDER=claude` key)
# - rlt load tests (`make e2e-bench`)
# - PGO / DHAT / flamegraph / perf-tuning-full
# - cross-compile (lives in release.yml)
# - container build + Helm publish (lives in release.yml)
#
# Cache strategy:
# - Swatinem/rust-cache@v2 on every cargo job.
# - `shared-key: "ci-<job>-<os>"` so cache keys are stable across
# matrix renames but isolated per job (different feature graphs
# must not share a registry).
# - `cache-on-failure: true` so a failing build still leaves a warm
# cache for the next attempt.
name: ci
on:
pull_request:
push:
branches:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
jobs:
# ---------------------------------------------------------------------------
# Job 1: unit (cheap, fast — every PR sees this fail first)
# ---------------------------------------------------------------------------
unit:
name: unit
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Install protoc + python3-dev
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler python3-dev
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-unit-${{ runner.os }}
cache-on-failure: true
- name: Format
run: cargo fmt -- --check
- name: Clippy (lib, all feature surfaces)
run: |
cargo clippy --lib -- -D warnings
cargo clippy --features embed-api --lib -- -D warnings
cargo clippy --features persistence --lib -- -D warnings
cargo clippy --features mcp --lib -- -D warnings
cargo clippy --features pgvector --lib -- -D warnings
cargo clippy --features linux-sandbox --lib -- -D warnings
cargo clippy --features release --lib -- -D warnings
- name: Clippy (bins)
run: |
cargo clippy --bin perf_summarize -- -D warnings
cargo clippy --features tokio-console --bin conproxy -- -D warnings
- name: Lib tests (default / embed-api / mcp / release)
run: |
cargo test --lib
cargo test --features embed-api --lib
cargo test --features mcp --lib
cargo test --features release --lib
- name: MCP integration test
run: cargo test --features mcp --test mcp_test
- name: Build (workspace)
run: cargo build --workspace
- name: Build (embed linker, hard gate)
run: cargo build --features embed --lib
- name: Release binary smoke
run: |
cargo build --release --features release --bin conproxy
./target/release/conproxy --version
./target/release/conproxy --help 2>&1 | head -40
- name: Install simulation (isolated --root)
run: |
cargo install --locked --path . --features release --root /tmp/conproxy-install
/tmp/conproxy-install/bin/conproxy --version
rm -rf /tmp/conproxy-install
# ---------------------------------------------------------------------------
# Job 2: integration (testcontainers — Docker daemon on runner)
# ---------------------------------------------------------------------------
integration:
name: integration
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install protoc + python3-dev
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler python3-dev
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-integration-${{ runner.os }}
cache-on-failure: true
- name: Core testcontainers matrix
# qdrant, ES, OS, meili, pgvector + cascade/peer/circuit/batch/
# metrics/context_config/singleflight. ~10 min on a standard
# ubuntu-latest runner.
run: make test-integration
# ---------------------------------------------------------------------------
# Job 3: integration-experimental (pinecone + milvus mocks)
# Cheap — no real backends. Default-on so we catch regressions even
# when nobody runs the `run-integration` label.
# ---------------------------------------------------------------------------
integration-experimental:
name: integration-experimental
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install protoc (build.rs uses tonic-prost-build)
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-integration-experimental-${{ runner.os }}
cache-on-failure: true
- name: Pinecone + Milvus mock integration tests
run: make test-integration-experimental
# ---------------------------------------------------------------------------
# Job 4: security (cargo audit + cargo deny + lint-security clippy)
# ---------------------------------------------------------------------------
security:
name: security
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install protoc + cargo-audit + cargo-deny
# protoc needed for the conproxy build.rs (tonic-prost-build)
# in the clippy step below.
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
cargo install cargo-audit --locked || true
cargo install cargo-deny --locked || true
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-security-${{ runner.os }}
cache-on-failure: true
- name: RustSec advisory scan
run: cargo audit
- name: Supply chain + license check
run: cargo deny check
- name: Security-focused clippy lints
# Lint the lib only. `--all-targets` / `--bins` would also
# compile test code and pre-existing `corpus_gen` indexing
# panics, which are tracked in CONTRIBUTING Known Gaps.
# Tier 1 unit job already covers `--bin perf_summarize` and
# `--bin conproxy` with the standard lib clippy surface.
run: |
cargo clippy --lib -- -D warnings \
-W clippy::unwrap_used \
-W clippy::expect_used \
-W clippy::panic \
-W clippy::indexing_slicing \
-W clippy::arithmetic_side_effects
# ---------------------------------------------------------------------------
# Job 5: fuzz (cargo fuzz, every target, 60s each, best effort)
# Best-effort because fuzz is by nature non-deterministic; we still
# gate PRs on the green gates in jobs 1–4, and any crashes found get
# uploaded as artifacts for triage.
# ---------------------------------------------------------------------------
fuzz:
name: fuzz
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
# cargo-fuzz needs the nightly toolchain (profiler-rt).
- name: Install protoc + cargo-fuzz
# protoc needed because fuzz/Cargo.toml links against the
# conproxy crate, whose build.rs uses tonic-prost-build.
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
cargo install cargo-fuzz --locked
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-fuzz-${{ runner.os }}
cache-on-failure: true
- name: Fuzz query request deserializer
run: cargo fuzz run fuzz_query_request -- -max_len=4096 -max_total_time=60 || true
- name: Fuzz config TOML parser
run: cargo fuzz run fuzz_config_parse -- -max_len=8192 -max_total_time=60 || true
- name: Fuzz ES response
run: cargo fuzz run fuzz_es_response -- -max_len=8192 -max_total_time=60 || true
- name: Fuzz Qdrant response
run: cargo fuzz run fuzz_qdrant_response -- -max_len=8192 -max_total_time=60 || true
- name: Fuzz schema fingerprint
run: cargo fuzz run fuzz_schema_fingerprint -- -max_len=4096 -max_total_time=60 || true
- name: Upload fuzz crash artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts
path: |
fuzz/artifacts/
fuzz/corpus/
if-no-files-found: ignore
retention-days: 7
# ---------------------------------------------------------------------------
# Job 6: e2e (needs unit + integration)
# Real ignored e2e_proxy_suite (all phases), plus UAT CLI suite, plus
# the security-focused e2e filter. Uses GHA Docker layer cache
# (setup-buildx) + docker compose pull to keep image pulls fast.
# ---------------------------------------------------------------------------
e2e:
name: e2e
needs:
if: success()
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install protoc + python3-dev
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler python3-dev
- uses: docker/setup-buildx-action@v3
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-e2e-${{ runner.os }}
cache-on-failure: true
- name: Build release binary (conproxy)
run: cargo build --release --features release --bin conproxy
- name: Pull backend images (Docker cache)
run: |
cd tests/e2e
docker compose pull --quiet || true
- name: Bring up backends (docker compose)
run: |
cd tests/e2e
docker compose up -d
- name: Wait for backends
run: |
cargo build --bin test_runner --features default -q
cargo run --bin test_runner --features default -- wait all
- name: Load test data
run: cargo run --bin test_runner --features default -- load-data
- name: Full ignored e2e_proxy_suite (all phases)
# Single-threaded to avoid two phases fighting over the same
# proxy lifecycle; the test internally restarts the proxy
# between phases. PROXY_URL points the test's E2eClient (and the
# conproxy CLI helper) at the test's proxy on 8080/8081.
env:
E2E_SUITE: all
E2E_OUTPUT_DIR: ${{ github.workspace }}/e2e-results
PROXY_URL: http://127.0.0.1:8081
run: |
mkdir -p "$E2E_OUTPUT_DIR"
PROXY_BIN="${{ github.workspace }}/target/release/conproxy" \
cargo test --test e2e_proxy --features e2e \
-- --ignored --nocapture --test-threads=1 e2e_proxy_suite
- name: UAT (CLI lifecycle)
env:
PROXY_BIN: ${{ github.workspace }}/target/release/conproxy
PROXY_URL: http://127.0.0.1:8081
run: |
PROXY_BIN=$PROXY_BIN \
cargo test --test e2e_uat --features e2e -- --ignored --nocapture
- name: Security-focused e2e
env:
PROXY_BIN: ${{ github.workspace }}/target/release/conproxy
E2E_SUITE: security
E2E_FILTER: security
PROXY_URL: http://127.0.0.1:8081
run: |
PROXY_BIN=$PROXY_BIN \
cargo test --test e2e_proxy --features e2e \
-- --ignored --nocapture e2e_proxy_suite security
- name: Upload e2e results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results
path: e2e-results/
if-no-files-found: ignore
retention-days: 7
- name: Tear down backends
if: always()
run: |
cd tests/e2e
docker compose down -v || true