Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions docs/pre-commit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Canonical pre-commit configuration

Rivet ships a 21-hook `.pre-commit-config.yaml` that is the reference for
PulseEngine Rust repositories. The canonical, copy-pasteable version lives
at [`templates/pre-commit/.pre-commit-config.yaml`](../templates/pre-commit/.pre-commit-config.yaml).

This document explains why each hook exists, which standard clause it helps
satisfy, and how an adopter repository picks the right tier.

> **Hook security model (REQ-051).** Pre-commit hooks are convenience for
> local development; `git commit --no-verify` trivially bypasses all of
> them. CI must independently run the same checks as required status
> checks for any traceability or safety claim to hold.

## Tiers (advisory)

The hook set is split into three advisory tiers. Repositories pick the
lowest tier that matches their assurance posture and may freely add hooks
from higher tiers. The tier is a recommendation; the only hard requirement
is that whatever ships in CI matches the safety claims the repository
makes in its `rivet.yaml`.

| Tier | Adds | When to use |
|---|---|---|
| **T1 — baseline** | File hygiene, yamllint, `cargo fmt`/`clippy`/`test`, `rivet validate`, `rivet commit-msg-check` | Every PulseEngine Rust repository |
| **T2 — safety-critical** | + `cargo audit`, `cargo deny`, `cargo bench --no-run` | Anything claiming an ASIL / DAL level or shipping signed binaries |
| **T3 — verification-heavy** | + `cargo mutants` (pre-push) | Repositories whose tests must meet a mutation-score target (rivet today; see #185) |

Each hook in the canonical template carries a `# T1 / T2 / T3` annotation
in a trailing comment so an adopter can grep-trim the file to their tier.

> The exact T1 / T2 / T3 partition is open for review — see issue
> [#186](https://github.com/pulseengine/rivet/issues/186) for the
> discussion. The annotations in the template reflect the proposal as
> filed and may be tightened during review.

## Per-hook rationale

| Hook | Tier | Standard mapping | Why it exists |
|---|---|---|---|
| `trailing-whitespace` | T1 | hygiene | Eliminates noisy diffs |
| `end-of-file-fixer` | T1 | hygiene | POSIX text-file convention |
| `check-yaml` | T1 | hygiene | YAML parses; required because rivet artifacts are YAML |
| `check-toml` | T1 | hygiene | `Cargo.toml` / `deny.toml` parse |
| `check-json` | T1 | hygiene | Settings + lockfile parse |
| `check-added-large-files` | T1 | hygiene | Prevents accidental binary check-in (>500 KB) |
| `check-merge-conflict` | T1 | hygiene | Catches stray `<<<<<<<` markers |
| `detect-private-key` | T1 | security hygiene | Catches PEM/SSH keys before they hit the remote |
| `check-case-conflict` | T1 | hygiene | Avoids cross-platform breakage |
| `check-symlinks` | T1 | hygiene | Avoids dangling symlinks |
| `mixed-line-ending` | T1 | hygiene | Forces LF |
| `yamllint` | T1 | hygiene | Enforces YAML style consistent with `.yamllint.yaml` |
| `cargo-fmt` | T1 | ISO 26262-6 §5.4.7 (style) | Style consistency, deterministic diffs |
| `cargo-clippy` | T1 | IEC 61508-3 §7.4.4 (defensive programming) | Lint-as-spec; `-D warnings` makes lints blocking |
| `cargo-test` | T1 | DO-178C §6.4 (verification) | Functional unit/integration tests |
| `rivet-validate` | T1 | DO-178C §A-7 (traceability), ISO 26262-8 §6 | Schema + link-graph integrity for the traceability artifacts |
| `rivet-commit-msg` | T1 | DO-178C §A-7 (traceability) | Commit-message trailers (`Implements:`, `Verifies:`, ...) — see CLAUDE.md "Commit Traceability" |
| `cargo-audit` | T2 | EU CRA Art. 13 (vulnerability handling) | Blocks known-CVE dependencies (RustSec advisory DB) |
| `cargo-deny` | T2 | EU CRA Art. 13, IEC 61508-3 §7.4.2.13 | License + dependency policy enforcement |
| `cargo-bench-check` | T2 | regression hygiene | Bench harness still compiles (full run is too slow for pre-push) |
| `cargo-mutants` | T3 | IEC 61508-3 Annex C.5.12, ISO 26262-6 Tab. 13 | Test-suite adequacy via mutation testing (the strongest answer to the open MC/DC-for-Rust question) |

## Adoption recipe

1. Copy `templates/pre-commit/.pre-commit-config.yaml` into the adopter
repository root.
2. Trim hooks to the chosen tier; comment out (don't delete) lower-tier
hooks you defer for later so the diff back to the canonical template
stays readable.
3. Resolve every `CUSTOMIZE:` marker in the file:
- `cargo +stable` — pin to whatever channel `rust-toolchain.toml`
declares.
- `rivet validate` `files:` glob — match the adopter's artifact /
schema directory layout.
- `cargo-mutants -p YOUR_CRATE` — pick the most safety-critical crate
(full-workspace mutation runs are too slow for pre-push; CI shards
are the right home for full coverage).
4. Run `pre-commit install` and `pre-commit install --hook-type pre-push
--hook-type commit-msg` so all the configured stages are active.
5. Mirror the same set of hooks into CI as required status checks.
Pre-commit hooks alone do not satisfy any traceability claim — see
"Hook security model" at the top of this document.

## Installing `rivet` for hooks

Two tested install paths:

- **Cargo:** `cargo install --git https://github.com/pulseengine/rivet
rivet-cli` (pin a tag/sha for reproducibility).
- **Pre-commit `additional_dependencies`** (preferred for adopters): once a
binary release exists, the `rivet-validate` and `rivet-commit-msg`
entries can be wrapped in a pre-commit `local` repo with
`additional_dependencies: [rivet@<version>]`. Tracking issue:
[#187](https://github.com/pulseengine/rivet/issues/187).

## Drift policy

Because adopter repositories will lag the canonical template, run a
quarterly diff:

```sh
diff -u templates/pre-commit/.pre-commit-config.yaml \
../<adopter>/.pre-commit-config.yaml
```

Justified divergences (a hook genuinely doesn't apply) belong in the
adopter's `docs/pre-commit.md` as an explicit opt-out with rationale.

## See also

- [`templates/pre-commit/.pre-commit-config.yaml`](../templates/pre-commit/.pre-commit-config.yaml) — the template itself
- [Issue #186](https://github.com/pulseengine/rivet/issues/186) — canonical-template tracking issue
- [Issue #187](https://github.com/pulseengine/rivet/issues/187) — `rivet-validate` enforcement across repos
- [Issue #185](https://github.com/pulseengine/rivet/issues/185) — `cargo-mutants` adoption (T3)
- `CLAUDE.md` — "Commit Traceability" and "Hook Security Model" sections
122 changes: 122 additions & 0 deletions templates/pre-commit/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Canonical pre-commit configuration for PulseEngine Rust repositories.
#
# Source of truth: pulseengine/rivet:templates/pre-commit/.pre-commit-config.yaml
# Documented in: pulseengine/rivet:docs/pre-commit.md
#
# Tier annotations (T1 / T2 / T3) below match the tier system documented in
# docs/pre-commit.md. Adopter repositories pick a tier and trim hooks to match.
# The tier groupings are advisory; safety-critical work should target T2 or T3.
#
# CUSTOMIZE markers indicate places adopter repositories must adjust:
# - rust-toolchain pinning (cargo +<channel> ...)
# - per-project paths in `files:` regex patterns
# - rivet artifact directory layout

repos:
# ── T1 — Generic file hygiene ─────────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace # T1
- id: end-of-file-fixer # T1
- id: check-yaml # T1
args: ['--allow-multiple-documents']
- id: check-toml # T1
- id: check-json # T1
- id: check-added-large-files # T1
args: ['--maxkb=500']
# CUSTOMIZE: exclude vendored/minified assets per project.
# exclude: '^path/to/vendored/asset$'
- id: check-merge-conflict # T1
- id: detect-private-key # T1
- id: check-case-conflict # T1
- id: check-symlinks # T1
- id: mixed-line-ending # T1
args: ['--fix=lf']

# ── T1 — YAML lint ────────────────────────────────────────────────────
- repo: https://github.com/adrienverge/yamllint
rev: v1.37.0
hooks:
- id: yamllint # T1
# CUSTOMIZE: drop the `args` line if your repo has no .yamllint.yaml.
args: ['-c', '.yamllint.yaml']

# ── T1 — Rust core (formatting, lint, tests) ──────────────────────────
- repo: local
hooks:
- id: cargo-fmt # T1
name: cargo fmt
entry: cargo fmt --all -- --check
language: system
types: [rust]
pass_filenames: false

- id: cargo-clippy # T1
name: cargo clippy -D warnings
# CUSTOMIZE: pin the Rust channel to match rust-toolchain.toml.
entry: cargo +stable clippy --all-targets -- -D warnings
language: system
types: [rust]
pass_filenames: false

- id: cargo-test # T1
name: cargo test
entry: cargo test --all
language: system
types: [rust]
pass_filenames: false

# ── T1 — Rivet traceability (dogfood) ───────────────────────────
- id: rivet-validate # T1
name: rivet validate (dogfood)
# CUSTOMIZE: invoke `rivet` from your install of choice (see
# docs/pre-commit.md "Installing rivet for hooks").
entry: rivet validate
language: system
pass_filenames: false
# CUSTOMIZE: file globs must cover your artifact + schema layout.
files: '(artifacts/.*\.yaml|schemas/.*\.yaml|safety/.*\.yaml|rivet\.yaml)$'

- id: rivet-commit-msg # T1
name: rivet commit-msg check
entry: rivet commit-msg-check
language: system
stages: [commit-msg]
always_run: true

# ── T2 — Supply-chain & advisory checks (pre-push) ──────────────
- id: cargo-audit # T2
name: cargo audit
entry: cargo audit
language: system
pass_filenames: false
files: '(Cargo\.toml|Cargo\.lock)$'
stages: [pre-push]

- id: cargo-deny # T2
name: cargo deny check
entry: cargo deny check
language: system
pass_filenames: false
files: '(Cargo\.toml|Cargo\.lock|deny\.toml)$'
stages: [pre-push]

- id: cargo-bench-check # T2
name: cargo bench --no-run
entry: cargo bench --no-run
language: system
types: [rust]
pass_filenames: false
stages: [pre-push]

# ── T3 — Mutation testing (verification-heavy, slow) ────────────
- id: cargo-mutants # T3
name: cargo mutants (smoke)
# CUSTOMIZE: scope `-p <crate>` to the most safety-critical crate.
# See docs/mutation-testing.md (issue #185) for batching guidance.
entry: bash -c 'cargo mutants --timeout 60 --jobs 4 -p YOUR_CRATE -- --lib 2>&1 | tail -5'
language: system
pass_filenames: false
stages: [pre-push]
verbose: true
Loading