Skip to content

Pre-commit gate

proxxx ships with an eight-stage gate that runs against the local source tree AND a real Proxmox cluster. It is the single source of truth for "ready to commit". There are no skip flags.

Installing the gate

sh
git config core.hooksPath .githooks
chmod +x scripts/gate.sh .githooks/pre-commit .githooks/pre-push
cargo install cargo-audit --locked
cargo install cargo-deny --locked

Both hooks call scripts/gate.sh. Defense in depth — even if you git commit --no-verify, the push hook re-runs the gate before anything reaches the remote.

The eight stages

─── stage 0/8: secret regression scan ───
─── stage 1/8: cargo fmt --check ───
─── stage 2/8: cargo clippy --release --all-targets ───
─── stage 3/8: cargo audit --deny warnings ───
─── stage 4/8: cargo deny check ───
─── stage 5/8: cargo test --release --all-targets ───
─── stage 6/8: tests/live/test_run.sh ───
─── stage 7/8: tests/live/test_mutation.sh ───
✓ GATE PASSED in ~340–480s
#StageTimeCatches
0secret regression scan<1 sinline secrets / tokens / private keys in staged files
1cargo fmt --all -- --check~3 sformatting drift
2cargo clippy --release --all-targets10–60 slint policy: unwrap_used, expect_used, panic, todo, await_holding_lock are deny
3cargo audit --deny warnings3–5 snew CVEs in Cargo.lock, against a documented advisory ignore policy in .cargo/audit.toml
4cargo deny check2–4 slicense whitelist, banned crates (openssl, native-tls, etc.), crates.io-only sources, wildcard ban — policy in deny.toml
5cargo test --release --all-targets10–90 sfull unit + integration + wiremock + TUI snapshot suites (646 lib tests + 447 integration tests + ~25 proptest properties × 256 cases each)
6tests/live/test_run.sh~30 s67 read-only probes against a live cluster
7tests/live/test_mutation.sh~60 s34 mutation probes covering full lifecycle: LXC 9999 (create → start → snapshot → stop → delete), cluster-level CRUD across all 8 state families (pools / ACL / storage-defs / backup-jobs / firewall-cluster / notifications / HA rules / HA resources), QEMU 9998 from alpine ISO, opt-in QGA round-trips via PROXXX_E2E_QGA_VMID=<vmid>

Stages 0–5 mirror CI exactly. Stages 6–7 require pve1, pve2, pve3 to be reachable — they are skipped on machines without that environment, but they are not skipped on the developer's primary workstation where the cluster lives.

Why integration tests in the gate

A previous gate ran cargo test --release --lib only. That covered the lib tests but missed the 447 tests in tests/*.rs that CI catches. A developer could land a stale-mock regression locally, push, and CI would fail in remote — wasting a round trip.

The gate now runs --all-targets so the local checkpoint is exactly what CI sees. Stage 5 is intentionally identical to the .github/workflows/ci.yml#test step.

The audit policy

.cargo/audit.toml documents every advisory we accept, with:

  1. The crate + version
  2. The dependency path that pulls it in
  3. The reason we accept it (with threat model justification)
  4. The planned remediation

Entries without remediation are debt, not policy — that is the declared rule. Today the file ignores three advisories, all in the russh / ratatui transitive surface, all with planned upstream-bumps tracked.

What "live" means

Stage 6 (read-only) runs every read CLI against the cluster:

[probe] ls nodes      [OK] exit=0
[probe] ls guests     [OK] exit=0
[probe] ls storage    [OK] exit=0
[probe] ha preview    [OK] exit=0
[probe] hw conflicts  [OK] exit=0
... 67 probes total, 0 fail ...

Stage 7 (mutation) runs the full lifecycle on the live cluster: LXC 9999 (create → start → snapshot → stop → delete), cluster-level CRUD across all 8 state families (pool / ACL grant / storage-defs / backup-job / firewall-cluster alias+group+ipset / notifications matcher / HA rules node-affinity+resource-affinity / HA resources), QEMU 9998 booted from an alpine ISO, and — when PROXXX_E2E_QGA_VMID=<vmid> is set — QGA agent-required round-trips (file read / write / network probe / QMP sendkey). Every artifact uses a proxxx-mut- prefix and a trap EXIT cleanup sweeps even after a panic mid-test.

Bypassing

The only way out is git commit --no-verify (or git push --no-verify). The committer owns the consequence — that is the explicit policy in scripts/gate.sh. CI will re-run all eight stages and reject the push if anything is broken.

See Bypass policy for when bypass is acceptable.

Trade-offs

  • Latency. A commit-grade run is ~340–480 s end-to-end on the primary workstation (with the cluster reachable). Stages 0–5 alone are ~30–160 s. This is the price of "no surprises in CI".
  • Cluster requirement. Stages 6–7 assume the test cluster is up. If it is not, the hook fails — by design. proxxx does not pretend the cluster is fine when it cannot reach it.
  • Strict mode. There is no --lite mode. If you need to commit in a hurry, use --no-verify and accept the responsibility — the gate refuses to be partially-applied.

Released under the MIT License.