# Release-gate coverage matrix — every gate CHECK × engine × VERSION, plus the
# backend-INFRASTRUCTURE dimensions the go/no-go gate does (or does not yet) exercise.
#
# This is the SINGLE source of truth for what `dev/release-oracle` actually gates, so
# the gate's coverage is reviewable at a glance and its gaps are counted, not silent.
# Same cell vocabulary + shrink-only ratchet discipline as the sibling docs/*-matrix.yaml
# ledgers; drift-guarded against the gate's real config by
# tests/release_gate_matrix_guard.rs:
# - every `grid.<engine>.gated` version must appear in dev/release-oracle/matrix.yaml
# - every scenario/preflight function in dev/release-oracle/lib/scenarios.sh must have
# a row here (and vice-versa) — a new gate check with no cell fails the guard
# - `gap` cells are counted and ratcheted (no NEW gaps; fill one by wiring the check
# and flipping gap -> test)
#
# Cell vocabulary:
# test — the gate RUNS this check for this engine (across its gridded versions) and it is green.
# na — not applicable to this engine by design (e.g. SQL keyset on Mongo).
# gap — SHOULD be gated, isn't yet (ratcheted; shrink-only).
# ── The engine × VERSION grid the gate brings up (mirrors dev/release-oracle/matrix.yaml) ──
# `gated` = versions the gate spins up, seeds, and runs every applicable scenario against.
# `gaps` = versions that EXIST (a compose service / a devbox stand / a public image) but
# are not yet gated — each is a real coverage hole, ratcheted.
grid:
postgres:
gated: [13, 14, 16, 18] # 14/18 verified green on the devbox against the blessed goldens
gaps: [12, 15, 17] # 12/15 are compose services; 17 is a public image
mysql:
gated: ["8.0", "8.4"]
gaps: ["5.7", "mariadb-11"] # 5.7 = huge install base, needs a CTE-free seed; MariaDB needs a compose service + driver check
mssql:
gated: ["2022"]
gaps: ["2019"] # 2019 ships mssql-tools (not tools18) — needs an sqlcmd-path fallback in bring_up/seed
mongo:
gated: ["4.4", "5", "6", "7", "8"] # 5/6/8 added; 4.4 is arm64-flaky on the colima devbox (documented, not a rivet bug)
gaps: []
# ── Per-(engine, version) DATA / BEHAVIOUR scenarios (run once per gridded version) ──
# Cell is per-ENGINE (uniform across that engine's gridded versions); a version-specific
# divergence would split into its own note.
scenarios:
- id: verdicts
what: "init -> the strategy per table matches the golden (keyset/full/range/name-trap), zero phantom heavy-chunk."
postgres: test
mysql: test
mssql: test
mongo: test
- id: integrity_types
what: "users 150K loss/dup 0 (source count+distinct == DuckDB read) + per-engine DuckDB type/CSV golden (parquet binary path + CSV all-varchar text path)."
postgres: test
mysql: test
mssql: test
mongo: { na: "Mongo is a JSON-blob model (full-scan only) — no per-engine type matrix; the loss/dup arm still runs under integrity_types" }
- id: keyset_parallel
what: "parallel:4 keyset fan-out: loss/dup 0 (DuckDB oracle) AND >=2 distinct pk_w{ridx} workers (no silent degrade to sequential)."
postgres: test
mysql: test
mssql: test
mongo: { na: "Mongo parallel:N is the separate _id-range mongo_parallel reader, not SQL keyset" }
- id: load
what: "manifested parts -> each object store {s3/minio, gcs/fake-gcs, azure/azurite}, independent readback count == summed manifest row_count + id-set round-trips."
postgres: test
mysql: test
mssql: test
mongo: test
- id: gc_survival
what: "concurrent-extract bucket-erasure guard: a Running manifest spares an in-flight part; none deletes the orphan; Success parts always kept."
postgres: test
mysql: { na: "store-level property, source-agnostic — one engine's parts exercise it (runs on postgres)" }
mssql: { na: "store-level property (see postgres)" }
mongo: { na: "store-level property (see postgres)" }
# ── Source-agnostic PREFLIGHTS (run ONCE before the engine loop, not per engine/version) ──
preflights:
- id: release_build_path
what: "the RELEASE build/publish path itself, not `cargo build` (the stricter tooling the TAG runs, which failed AFTER publish for 0.16.0/0.16.1): Cargo.lock in sync (`cargo metadata --locked` — publish + Docker cook are `--locked`), the cargo-manifest strict parse (no multi-line inline tables, the cargo_manifest_chef guard) + JSON-schema regen (schema_drift guard), and `cargo chef prepare` (the Dockerfile's actual planner step, cheap — no compile). Full `docker build` opt-in via RIVET_ORACLE_DOCKER=1. Runs FIRST, before any cargo reconciles the working-tree lock."
status: test # verify_release_build_path (lib/release_path.sh)
- id: state_migrations
what: "SQLite state == Postgres state on the state-exercising golden fixtures (parallel-checkpoint, incremental, crash-resume) — the migration arrays are type-parity."
backends: [sqlite, postgres]
status: test # verify_state_migrations (commit e83656a), RED-proven int4 -> NOT RELEASABLE
- id: coverage_matrices
what: "every docs/*-matrix.yaml drift-guard runs (chunking/behaviour/type-fidelity/... + release_gate + perf) so a rotted coverage ledger blocks the release, not just CI."
status: test # verify_coverage_matrices
# pooler_safety REMOVED from the gate: it re-ran tests/live/live_pool_safety.rs, which
# CI already runs on every push (ci.yml `--profile pool` exists specifically for it).
# A CI-owned correctness suite re-run adds no coverage here AND reddened on a missing
# pool backend (require_alive(Mysql) panics without compose `mysql` :3306), drowning the
# go/no-go signal. Pooler safety is CI-scope, not a gate gap — the gate does the
# shipping-artifact + independent-oracle checks CI can't. Not counted as a gap.
- id: replica_read
what: "prod topology — rivet reads a read-REPLICA, not the master (the 'we were handed a product, no master access' case). Drives tests/live/live_cdc_replica.rs: the mysql-replica re-logs the primary's replicated changes into its OWN binlog (log_replica_updates) and rivet captures from that. Needs the `replica` compose profile (mysql-primary :3308 → mysql-replica :3309); SKIP when down."
infra: [mysql-replica]
status: test # verify_replica_read — drives cdc_reads_changes_from_a_replica
- id: release_regression
what: "regression vs the PREVIOUS RELEASE (not a golden, not the gate itself). B-format: the new binary must READ what the prev release WROTE — prev exports a 100K keyset+zstd fixture, cur `validate` re-reads its manifest+parts (PASSED) + DuckDB row-count == source (a format bump that can't read old artifacts silently breaks every existing user on upgrade). B-perf: cur wall <= the DOWNLOADED prev-release binary's wall × tol (default 1.5×; a 3× slowdown / RSS blow-up ships green through every correctness check), benchmarked against the artifact users actually run, never a rebuilt parent. Env: RIVET_PREV_RELEASE_BIN + RIVET_REGRESSION_SOURCE_URL; SKIP-if-absent."
backends: [postgres]
status: test # verify_release_regression (lib/regression.sh)
- id: scale_memory
what: "the FLAT-RSS guarantee at scale — rivet's headline value prop (the 454M-row, no-OOM field win vs Airbyte). rivet STREAMS, so peak RSS is O(chunk), not O(rows); the gate's data scenarios are ~150K rows, so a buffering regression that scales RSS with the table ships green through every count check. Runs PER ENGINE over TWO EXISTING tables (no seeding — a small + a large already on the stand, default users vs events) and asserts peak RSS on the LARGE table stays FLAT vs the SMALL: RSS(large) <= RSS(small) × tol (RIVET_SCALE_RSS_TOL, default 3× — generous, the tables differ in row width; streaming keeps the ratio near the width ratio, buffering blows it up by the 10× row-count ratio). Measured for the current binary (assertion) AND the downloaded prev release (comparison). Env per engine: RIVET_SCALE_<ENGINE>_URL + _SMALL + _LARGE; SKIP-if-absent."
backends: [postgres, mysql, mssql, mongo]
status: test # verify_scale_memory (lib/regression.sh) — per-engine loop
- id: cdc_e2e
what: "CDC end-to-end per engine (the change-data-capture surface the batch scenarios never exercise): anchor a typed table → INSERT/UPDATE/DELETE → capture (mode:cdc, until_current) to the object store → INDEPENDENT DuckDB readback == 5 events (never rivet) → rivet validate PASSED → state-DB metabase populated (run_status all-success) → at-least-once CRASH recovery (cdc_after_flush_before_ack holds the anchor; the re-run re-reads id=4) → large-transaction ATOMICITY (a >rollover 12-row PG tx survives a cdc_after_ack mid-flush crash whole, 12/12 — the committed-boundary invariant). PLUS SQLite-vs-Postgres state PARITY vs golden/cdc_state_snapshot.json (the reference snapshot). Each engine's anchor is its own mechanism (PG slot / MySQL binlog / MSSQL from-LSN / Mongo resume token). RED-proven by mutation vs the release binary: a drop-one-event sink mutant reddens the independent readback on all 4 engines (while rivet validate stays green); a committed:true-every-event PG mutant reddens the large-tx leg."
backends: [sqlite, postgres]
status: test # verify_cdc_e2e (lib/cdc.sh) — env-driven RIVET_CDC_<ENGINE>_URL, SKIP-if-absent
# ── BACKEND-INFRASTRUCTURE dimensions the gate does NOT yet exercise (the audit gaps) ──
# The gate connects DIRECT to every engine; it never routes through the connection-
# mediation layer prod runs (poolers/proxies/replicas/degraded networks) even though the
# compose stack HAS all of it. Each row is a real hole, ratcheted; fill by adding a
# preflight/scenario and flipping status -> test.
infra:
- id: network_faults
infra: toxiproxy
status: gap
note: "retry/reconnect (retry.rs, classify_pg_sqlstate) under injected latency/drop/reset is untested end-to-end in the gate"
- id: state_upgrade
status: test
note: "pg_upgrade_from_v18_lands_keyset_range_as_bigint_and_keeps_data (src/state/mod.rs) — stages a POPULATED v18 Postgres state db, migrates in place to HEAD, asserts keyset_range is BIGINT + the pre-upgrade cursor survives; run by the state_migrations preflight. RED-proven against the int4 v19."
- id: state_concurrency
infra: postgres-state
status: test
note: "pg_shared_state_cross_connection_visibility_and_supersession (src/state/run_status_store.rs) — two StateStore connections = two processes on one shared Postgres state: a run begun on A is active-visible to B (the gc_orphans signal), and a newer run supersedes the older clock-free. Run by the state_migrations preflight; RED-proven against a flipped supersession."
- id: tls_required
status: gap
note: "TLS-required connections + cert validation (the vendored-openssl / tiberius handshake class); the gate runs mssql on accept_invalid_certs"
- id: auth
infra: mongo-auth
status: gap
note: "SCRAM / cert auth paths; the gate uses no-auth Mongo and password-only SQL"
- id: cdc_standby
infra: ["pg-cdc-standby"]
status: gap
note: "the mysql read-REPLICA half of this is now the `replica_read` preflight (verify_replica_read, status:test). The residual: bounded CDC on a PostgreSQL STANDBY must fail LOUD (roast_pg_cdc_bounded_on_a_standby_fails_loud EXISTS but the gate does not drive it — needs the `cdc-standby` compose profile up)."
- id: warehouse_load
status: partial
note: "BigQuery live golden IS gated; Snowflake + ClickHouse are resolver/type-only (no live load-and-readback) — ClickHouse is even a local compose service, cheap to add"