rivet-cli 0.23.1

Rivet: PostgreSQL/MySQL/SQL Server/MongoDB → Parquet/CSV (local, S3, GCS, Azure). Crate name rivet-cli; binary rivet.
Documentation
# 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"