name: ci
on:
workflow_dispatch:
push:
branches:
- main
tags:
- "v*"
pull_request:
permissions:
actions: read
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.92.0
- uses: Swatinem/rust-cache@v2
- name: cargo check --no-default-features (msrv)
run: cargo check --no-default-features
- name: cargo check --features default (msrv)
run: cargo check --features default
- name: cargo test --lib --features default (msrv)
run: cargo test --lib --features default
check:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable, nightly]
profile:
- { name: no-default-features, args: "--no-default-features" }
- { name: default-features, args: "--features default" }
- { name: all-features, args: "--all-features" }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: cargo check (${{ matrix.profile.name }})
run: cargo check ${{ matrix.profile.args }}
- name: cargo test --lib (${{ matrix.profile.name }})
run: cargo test --lib ${{ matrix.profile.args }}
- name: cargo build --example suite (stable ubuntu)
if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
run: |
cargo build --example pg_to_stdout --features postgres
cargo build --example postgres_to_otel --features postgres,metrics
cargo build --example sqlserver_to_otel --features sqlserver,metrics
cargo build --example regex_filter_transform
cargo build --example uppercase_transform
quality:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable, nightly]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: cargo fmt --check
run: cargo fmt --check
- name: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --all-targets --all-features -- -D warnings
- name: cargo doc --all-features --no-deps
run: cargo doc --all-features --no-deps
env:
RUSTDOCFLAGS: "-D warnings"
transport-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: cargo test --lib --features postgres,tls
run: cargo test --lib --features postgres,tls
- name: cargo test --lib --features mysql,tls
run: cargo test --lib --features mysql,tls
- name: cargo test --lib --features sqlserver,tls
run: cargo test --lib --features sqlserver,tls
policy-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install policy gate dependencies
run: |
sudo apt-get update
sudo apt-get install -y ripgrep jq
- name: policy gates (schema + deprecated + workflow drift)
run: bash scripts/ci-policy-gate.sh
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: install cargo-deny
run: cargo install cargo-deny --locked
- name: cargo deny check (advisories + licenses + bans + sources)
run: cargo deny check
correctness-smoke:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull postgres image (public ecr mirror)
run: bash scripts/ci-pull-relational-images.sh 16-alpine
- name: pre-pull mysql/mariadb images
run: bash scripts/ci-pull-relational-images.sh --relational-smoke
- name: contract compatibility smoke
run: cargo test --test compatibility_contract --all-features
- name: postgres checkpoint durability smoke
run: cargo test --test checkpoint_file_integration --features postgres
- name: postgres process-crash replay smoke
run: cargo test --test runtime_postgres_process_crash_integration --features postgres --bins
- name: reliability data-loss smoke
run: cargo test --test data_loss_detection --features postgres,test-harnesses
- name: mysql connection integration smoke
run: cargo test --test mysql_connection_integration --features mysql
- name: mariadb connection integration smoke
run: cargo test --test mariadb_connection_integration --features mariadb
- name: sqlserver connection integration smoke
run: cargo test --test sqlserver_connection_integration --features sqlserver
- name: mysql process-crash replay smoke
run: cargo test --test runtime_mysql_process_crash_integration --features mysql --bins
- name: mariadb process-crash replay smoke
run: cargo test --test runtime_mariadb_process_crash_integration --features mariadb --bins
- name: sqlserver process-crash replay smoke
run: cargo test --test runtime_sqlserver_process_crash_integration --features sqlserver --bins
reliability-core:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
suite:
- deterministic_replay_failure_fixtures
- deterministic_replay_golden_fixtures
- runtime_health_states
- wasm_conformance_contract
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: reliability core suite (${{ matrix.suite }})
run: cargo test --test ${{ matrix.suite }} --features postgres,wasm,test-harnesses
latency-core:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
LATENCY_GATE_DEFAULT_P95_MS: "500"
LATENCY_GATE_DEFAULT_P99_MS: "1000"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull postgres image (public ecr mirror)
run: bash scripts/ci-pull-relational-images.sh 16-alpine
- name: connector-backed latency evidence gate (p95/p99)
run: bash scripts/ci-latency-gate.sh
integration-postgres:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
strategy:
fail-fast: false
matrix:
suite:
- runtime_postgres
- postgres_version_matrix
- postgres_snapshot_integration
- postgres_stream_integration
- postgres_handoff_integration
- checkpoint_file_integration
- runtime_postgres_process_crash_integration
- parallel_snapshot_stress_integration
- otel_metrics_integration
- otel_tracing_integration
- snapshot_resumable_integration
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull postgres images (public ecr mirror)
run: |
tags="16-alpine"
if [[ "${{ matrix.suite }}" == "postgres_version_matrix" ]]; then
tags="12-alpine 14-alpine 15-alpine 16-alpine"
fi
bash scripts/ci-pull-relational-images.sh ${tags}
- name: cargo test --test ${{ matrix.suite }}
if: matrix.suite != 'runtime_postgres_process_crash_integration' && matrix.suite != 'otel_metrics_integration' && matrix.suite != 'otel_tracing_integration'
run: cargo test --test ${{ matrix.suite }} --features postgres
- name: cargo test --test ${{ matrix.suite }} (metrics)
if: matrix.suite == 'otel_metrics_integration' || matrix.suite == 'otel_tracing_integration'
run: cargo test --test ${{ matrix.suite }} --features postgres,metrics
- name: cargo test --test runtime_postgres_process_crash_integration (with bins)
if: matrix.suite == 'runtime_postgres_process_crash_integration'
run: cargo test --test runtime_postgres_process_crash_integration --features postgres --bins
integration-postgres-encryption:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull postgres images (public ecr mirror)
run: bash scripts/ci-pull-relational-images.sh 16-alpine
- name: cargo test --test runtime_postgres_process_crash_integration --features postgres,encryption --bins
run: cargo test --test runtime_postgres_process_crash_integration --features postgres,encryption --bins
integration-reliability:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
suite:
- data_loss_detection
- deterministic_replay_failure_fixtures
- deterministic_replay_golden_fixtures
- runtime_health_states
- fault_injection_soak_matrix
- wasm_runtime_integration
- wasm_conformance_contract
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: cargo test --test ${{ matrix.suite }} --features postgres,wasm,test-harnesses
run: cargo test --test ${{ matrix.suite }} --features postgres,wasm,test-harnesses
integration-mysql:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
strategy:
fail-fast: false
matrix:
suite:
- mysql_version_matrix
- mysql_connection_integration
- mysql_snapshot_integration
- mysql_stream_integration
- mysql_handoff_integration
- runtime_mysql_process_crash_integration
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull mysql/mariadb images
run: bash scripts/ci-pull-relational-images.sh --relational-smoke
- name: cargo test --test ${{ matrix.suite }}
if: matrix.suite != 'runtime_mysql_process_crash_integration'
run: cargo test --test ${{ matrix.suite }} --features mysql
- name: cargo test --test runtime_mysql_process_crash_integration (with bins)
if: matrix.suite == 'runtime_mysql_process_crash_integration'
run: cargo test --test runtime_mysql_process_crash_integration --features mysql --bins
integration-mysql-encryption:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull mysql/mariadb images
run: bash scripts/ci-pull-relational-images.sh --relational-smoke
- name: cargo test --test runtime_mysql_process_crash_integration --features mysql,encryption --bins
run: cargo test --test runtime_mysql_process_crash_integration --features mysql,encryption --bins
integration-mariadb:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
strategy:
fail-fast: false
matrix:
suite:
- mariadb_connection_integration
- mariadb_e2e_integration
- runtime_mariadb_process_crash_integration
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull mysql/mariadb images
run: bash scripts/ci-pull-relational-images.sh --relational-smoke
- name: cargo test --test ${{ matrix.suite }}
if: matrix.suite != 'runtime_mariadb_process_crash_integration'
run: cargo test --test ${{ matrix.suite }} --features mariadb
- name: cargo test --test runtime_mariadb_process_crash_integration (with bins)
if: matrix.suite == 'runtime_mariadb_process_crash_integration'
run: cargo test --test runtime_mariadb_process_crash_integration --features mariadb --bins
integration-mariadb-encryption:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull mysql/mariadb images
run: bash scripts/ci-pull-relational-images.sh --relational-smoke
- name: cargo test --test runtime_mariadb_process_crash_integration --features mariadb,encryption --bins
run: cargo test --test runtime_mariadb_process_crash_integration --features mariadb,encryption --bins
integration-sqlserver:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
strategy:
fail-fast: false
matrix:
suite:
- sqlserver_version_matrix
- sqlserver_snapshot_integration
- sqlserver_stream_integration
- sqlserver_handoff_integration
- runtime_sqlserver_process_crash_integration
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull SQL Server image (soft — MCR downtime or retired tag skips tests, not CI)
run: bash scripts/ci-pull-relational-images.sh --sqlserver-soft 2022-latest
- name: cargo test --test ${{ matrix.suite }}
if: matrix.suite != 'runtime_sqlserver_process_crash_integration'
run: cargo test --test ${{ matrix.suite }} --features sqlserver
- name: cargo test --test runtime_sqlserver_process_crash_integration (with bins)
if: matrix.suite == 'runtime_sqlserver_process_crash_integration'
run: cargo test --test runtime_sqlserver_process_crash_integration --features sqlserver --bins
integration-sqlserver-encryption:
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: pre-pull SQL Server image (soft — MCR downtime or retired tag skips tests, not CI)
run: bash scripts/ci-pull-relational-images.sh --sqlserver-soft 2022-latest
- name: cargo test --test runtime_sqlserver_process_crash_integration --features sqlserver,encryption --bins
run: cargo test --test runtime_sqlserver_process_crash_integration --features sqlserver,encryption --bins
release-evidence:
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
needs:
- msrv
- check
- quality
- transport-security
- policy-gate
- correctness-smoke
- reliability-core
- latency-core
- integration-postgres
- integration-postgres-encryption
- integration-reliability
- integration-mysql
- integration-mysql-encryption
- integration-mariadb
- integration-mariadb-encryption
- integration-sqlserver
- integration-sqlserver-encryption
runs-on: ubuntu-latest
env:
CDC_RS_RUN_DOCKER_TESTS: "1"
BENCHMARK_STRICT: "1"
BENCHMARK_REQUIRE_HISTORICAL_BASELINE: "1"
BENCHMARK_ENFORCE_RELEASE_POLICY: "1"
CRITERION_BASELINE: ci-baseline
BENCHMARK_CRITICAL_GROUP_PREFIXES: "quality_gates wasm_transform"
LATENCY_GATE_DEFAULT_P95_MS: "500"
LATENCY_GATE_DEFAULT_P99_MS: "1000"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install release evidence dependencies
run: |
sudo apt-get update
sudo apt-get install -y ripgrep jq
cargo install cargo-deny --locked
- name: derive benchmark baseline inputs
env:
CONFIGURED_BASELINE_COMMIT: ${{ vars.BENCHMARK_BASELINE_COMMIT }}
CONFIGURED_BASELINE_ARTIFACT: ${{ vars.BENCHMARK_BASELINE_ARTIFACT }}
run: |
set -euo pipefail
baseline_commit="${CONFIGURED_BASELINE_COMMIT}"
baseline_artifact="${CONFIGURED_BASELINE_ARTIFACT}"
if [ -z "$baseline_commit" ]; then
# Auto-detect baseline from git history:
# 1. Try the most recent prior release tag.
# 2. Fall back to HEAD~1 (parent commit).
# 3. If this is the initial commit with no parent, use HEAD itself
# and disable the historical-baseline requirement for this run.
prior_tag_commit="$(git log --tags --no-walk --pretty=format:'%H' 2>/dev/null | head -1 || true)"
if [ -n "$prior_tag_commit" ] && [ "$prior_tag_commit" != "$(git rev-parse HEAD)" ]; then
baseline_commit="$prior_tag_commit"
echo "::notice::BENCHMARK_BASELINE_COMMIT not configured — auto-detected from most recent release tag: ${baseline_commit:0:12}. Set the 'BENCHMARK_BASELINE_COMMIT' repository variable to a specific SHA for reproducible release evidence."
elif git rev-parse HEAD~1 >/dev/null 2>&1; then
baseline_commit="$(git rev-parse HEAD~1)"
echo "::notice::BENCHMARK_BASELINE_COMMIT not configured — falling back to HEAD~1: ${baseline_commit:0:12}. Set the 'BENCHMARK_BASELINE_COMMIT' repository variable to a known-good commit SHA for reproducible release evidence."
else
# Initial commit: no ancestor exists — bootstrap mode.
baseline_commit="$(git rev-parse HEAD)"
echo "BENCHMARK_REQUIRE_HISTORICAL_BASELINE=0" >> "$GITHUB_ENV"
echo "BENCHMARK_ENFORCE_RELEASE_POLICY=0" >> "$GITHUB_ENV"
echo "::notice::BENCHMARK_BASELINE_COMMIT not configured and no prior commit found — running in bootstrap mode (no regression comparison, release policy not enforced)."
fi
fi
if [ -z "$baseline_artifact" ]; then
baseline_artifact="commit:${baseline_commit}"
fi
echo "BENCHMARK_BASELINE_COMMIT=$baseline_commit" >> "$GITHUB_ENV"
echo "BENCHMARK_BASELINE_ARTIFACT=$baseline_artifact" >> "$GITHUB_ENV"
- name: policy gate
run: bash scripts/ci-policy-gate.sh
- name: cargo deny check (advisories + licenses + bans + sources)
run: cargo deny check
- name: clear stale criterion baseline cache
run: |
# Remove any Criterion baseline cached from a prior crate version
# (Swatinem/rust-cache persists target/ across Cargo.lock-stable runs).
# Clearing here forces ensure_named_baseline to bootstrap against the
# current commit so the gate compares like-for-like.
rm -rf target/criterion
- name: benchmark policy gate
run: bash scripts/ci-benchmark-gate.sh
- name: full integration matrix evidence
run: bash scripts/run_full_integration_matrix_evidence.sh
- name: upload release-gate evidence artifacts
uses: actions/upload-artifact@v4
with:
name: release-gate-evidence
path: |
target/integration-full-matrix-evidence.txt
target/latency-evidence.txt
target/latency-gate.txt
target/postgres-latency-evidence.json
target/postgres-latency-evidence.md
target/mysql-latency-evidence.json
target/mysql-latency-evidence.md
target/sqlserver-latency-evidence.json
target/sqlserver-latency-evidence.md
target/benchmark-ci-gate*.txt
target/benchmark-ci-env.txt
target/criterion
BENCHMARK_REPORT.md
release-evidence-verify:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: install verification dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: verify tag matches Cargo.toml version
run: |
TAG="${GITHUB_REF_NAME#v}"
CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "rustcdc") | .version')
if [ "$TAG" != "$CARGO_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match Cargo.toml version $CARGO_VERSION"
exit 1
fi
- name: verify successful release-evidence run exists for this commit
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const workflowId = '.github/workflows/ci.yml';
const headSha = context.sha;
const runs = await github.paginate(github.rest.actions.listWorkflowRuns, {
owner,
repo,
workflow_id: workflowId,
per_page: 100,
head_sha: headSha,
});
const candidates = runs.filter((run) =>
run.id !== context.runId &&
!run.head_branch?.startsWith('v') &&
run.event !== 'workflow_dispatch' &&
run.conclusion === 'success'
);
if (candidates.length === 0) {
core.setFailed(`No successful non-tag CI run found for commit ${headSha}.`);
return;
}
let evidenceFound = false;
for (const run of candidates) {
const jobsResp = await github.rest.actions.listJobsForWorkflowRun({
owner,
repo,
run_id: run.id,
per_page: 100,
});
const releaseEvidenceJob = jobsResp.data.jobs.find((job) => job.name === 'release-evidence');
if (!releaseEvidenceJob || releaseEvidenceJob.conclusion !== 'success') {
continue;
}
const artifactsResp = await github.rest.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: run.id,
per_page: 100,
});
const hasEvidenceArtifact = artifactsResp.data.artifacts.some((artifact) => artifact.name === 'release-gate-evidence');
if (hasEvidenceArtifact) {
core.info(`Using validated evidence from run ${run.id}.`);
evidenceFound = true;
break;
}
}
if (!evidenceFound) {
core.setFailed('No successful release-evidence job with release-gate-evidence artifact found for this commit.');
}
publish:
if: startsWith(github.ref, 'refs/tags/v')
needs: release-evidence-verify
runs-on: ubuntu-latest
environment: crates-io
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo publish
run: cargo publish --no-verify
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}