This document describes the justfiles/ox-check/ tree that ox-check writes into a repo, how the
recipes are organized, and how local invocations differ from CI invocations (spoiler: they
don't — that's the design).
See also:
- design.md for the overall principles.
- checks.md for the catalog the recipes implement.
- updates.md for how these files are tracked / regenerated.
repo/
├── Justfile managed-region: ox-check-imports
│ # >>> ox-check-managed: ox-check-imports
│ # checksum: sha256:… rendered-by: cargo-ox-check 0.4.1
│ import 'justfiles/ox-check/checks.just'
│ import 'justfiles/ox-check/groups.just'
│ import 'justfiles/ox-check/tiers.just'
│ import 'justfiles/ox-check/tools.just'
│ alias ox-check := ox-check-pr
│ # <<< ox-check-managed: ox-check-imports
│ …user content…
│
└── justfiles/ox-check/ owned (one checksum per file)
├── checks.just per-check recipes (ox-check-fmt, ox-check-clippy, ox-check-llvm-cov, …)
├── groups.just group recipes (ox-check-pr-fast, ox-check-pr-test, ox-check-nightly-test, …)
├── tiers.just tier aggregators (ox-check-pr, ox-check-nightly, ox-check-full)
└── tools.just ox-check-tools-check + ox-check-tools-install + helpers
The Justfile region is the only file ox-check adds to that the user co-owns. The four files
under justfiles/ox-check/ are tool-owned (full file checksums). If the user wants to add
project-specific recipes, they add them to the top-level Justfile outside the managed
region, or to their own additional imported .just files.
justfiles/ox-check/ is structured to make all three levels (check, group, tier) addressable
from the command line.
One recipe per individual check, each named ox-check-<check>. Recipes are usually a single
cargo … line; a handful (license-headers, ensure-no-cyclic-deps,
ensure-no-default-features, pr-title, the bench smoke loop) are short [script] blocks.
Every check recipe is prefixed with a quick version-gate dependency:
ox-check-clippy: (_ox-check-require "cargo-clippy")
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings_ox-check-require is a private recipe in tools.just that calls cargo install --list to
verify the tool meets the catalog's declared minimum version; missing or below-minimum
tools fail with a one-line cargo install hint. The cost is one cheap cargo install --list invocation per check, well under a second on a warm cache.
One recipe per group, named ox-check-<tier>-<group>. The check-recipe and group-recipe
namespaces are kept disjoint by naming choice: no check is named <tier>-<group> for
any tier × group combination (e.g. the coverage-instrumented test check is named
llvm-cov, not test, so that ox-check-pr-test unambiguously refers to the PR-tier
test group). The pr-mutants group runs the diff-scoped recipe; nightly-exhaustive
runs the full-workspace recipe:
ox-check-pr-fast: ox-check-fmt ox-check-clippy ox-check-cargo-sort ox-check-license-headers \
ox-check-ensure-no-cyclic-deps ox-check-ensure-no-default-features \
ox-check-doc-build ox-check-readme-check ox-check-spellcheck ox-check-pr-title \
ox-check-deny ox-check-audit ox-check-udeps ox-check-semver-check \
ox-check-external-types ox-check-aprz
ox-check-pr-test: ox-check-llvm-cov ox-check-doc-test ox-check-examples
ox-check-pr-mutants: ox-check-mutants-diff
ox-check-nightly-test: ox-check-llvm-cov ox-check-doc-test ox-check-examples
ox-check-nightly-advisories: ox-check-deny ox-check-audit ox-check-aprz ox-check-clippy ox-check-udeps
ox-check-nightly-runtime: ox-check-miri ox-check-careful
ox-check-nightly-exhaustive: ox-check-mutants-full ox-check-cargo-hack ox-check-bench-onlyThree tier aggregators. Each tier is a recipe that depends on the appropriate set of groups in a deterministic order:
ox-check-pr: ox-check-tools-check ox-check-pr-fast ox-check-pr-test ox-check-pr-mutants
ox-check-nightly: ox-check-tools-check ox-check-nightly-test ox-check-nightly-advisories \
ox-check-nightly-runtime ox-check-nightly-exhaustive
ox-check-full: ox-check-pr ox-check-nightlyox-check-tools-check— print a status table of every tool's installed version vs. minimum.ox-check-tools-install— install every catalog tool at the minimum version (or skip if already satisfied). Used as a one-shot in CI setup and locally on first use.ox-check-tools-install-missing— install only the tools that are missing or below minimum._ox-check-require <tool>— internal helper called by each check.
The full tool-version policy these recipes implement is detailed in §3 below.
The tool never pins exact versions for the user. The catalog records, for each tool, a
minimum required version (e.g. cargo-nextest >= 0.9.122). Users are free to install
newer versions, use mise/asdf, install via package manager, etc.
_ox-check-require <tool> (a private just recipe in tools.just) uses
cargo install --list to enumerate currently-installed cargo subcommands and their
versions, then compares against the catalog minimum. This avoids the problem of tools
without a stable --version flag, is fast, and works uniformly for everything the tool
cares about (all the cargo-* checks). For the small number of non-cargo dependencies
(just itself and pwsh), the recipe falls back to tool --version and a known parser.
ox-check-tools-install and ox-check-tools-install-missing are plain just recipes that loop
over the catalog and run cargo install --locked <tool> --version >=<min>. They are the
only mechanism the tool uses to install cargo-managed tools — there is no separate code
path for CI. CI setup just calls the recipes. Locally, the user runs the recipes once
when ox-check-tools-check complains.
Two prerequisites are not cargo-installable and must be present before the recipes can run:
justitself — bootstrap withcargo install just --lockedonce, or use a system package. Every backend's setup composite/template installs it via cargo as a one-shot.pwsh(PowerShell Core) — used by thepr-title[script]recipe (regex match on$PR_TITLE; no equivalent cargo subcommand andjustlacks a boolean-regex primitive). The other historically-scripted checks (license-headers,ensure-no-cyclic-deps,ensure-no-default-features) are now plain cargo subcommands from the ox-tools family and don't need pwsh.pwshis preinstalled on every relevant CI runner (GH-hosted Linux/Windows/macOS, Microsoft-hosted ADO agents). On a developer machine without pwsh,_ox-check-require pwshfails with a per-OS install hint pointing at https://github.com/PowerShell/PowerShell. The dependency is kept (rather than dropped to remove the one script) so future additions that don't fit cleanly as cargo subcommands have an established escape hatch.
Trade-off acknowledged: cargo install --locked is slow on a cold cache (several minutes
for the full catalog). It is also the most reliable mechanism in restricted networks.
Caching (via the GH cache action and the ADO pipeline workspace cache) is configured by the
setup action/template to key on Cargo.lock, the toolchain channel, and the binary's
catalog hash. See github.md and ado.md.
Every check recipe depends on _ox-check-require <its-tool> so even ad-hoc invocations like
just ox-check-miri warn loudly if the installed tool predates the catalog minimum. The full
tier invocations additionally print a one-line tools summary at the top.
rust-toolchain.toml is read but never written, and ox-check never installs a Rust toolchain
itself. Per-backend rationale lives in github.md and
ado.md; short version: msrustup owns it on ADO/1ESPT, the runner
image owns it on GH, the user owns it locally.
_ox-check-require validates the installed rustc against the catalog's minimum at recipe
time; missing or below-minimum rustc produces a clean failure message naming the version
mismatch. Per-check toolchain requirements (e.g. miri, careful, udeps need nightly) are
also enforced by _ox-check-require, which suggests the user-environment-appropriate install
command in the failure message (rustup install nightly or "ask your team's pipeline
owner to add nightly to msrustup").
Every check recipe whose work is per-crate accepts three optional pass-through env vars
and forwards them verbatim as --workspace-compatible exclude flags. Empty (the local
default) means full workspace; CI populates them from the ox-check-impact building block:
ox-check-clippy: (_ox-check-require "cargo-clippy")
cargo clippy --workspace ${OX_CHECK_EXCLUDE_NOT_MODIFIED:-} --all-targets --all-features --locked -- -D warnings
ox-check-llvm-cov: (_ox-check-require "cargo-llvm-cov") (_ox-check-require "cargo-nextest")
cargo llvm-cov nextest --workspace ${OX_CHECK_EXCLUDE_NOT_AFFECTED:-} --all-features --locked --lcov --output-path target/coverage/lcov.infoThe mapping from check to env var is fixed in the catalog (see
checks.md §5). Checks with no
per-crate scope (pr-title, aprz, deny, audit, spellcheck, fmt, cargo-sort,
license-headers, ensure-no-cyclic-deps, ensure-no-default-features) ignore the
vars. Group recipes do not interpolate the vars themselves — each underlying check
recipe reads what it needs, so a group recipe is just a dependency list and nothing
changes when scoping is disabled.
A fourth env var, OX_CHECK_IMPACT_SKIP, is set to "true" by the CI wiring when
cargo-delta reports that no workspace member is in any impact tier (typically a
docs-only PR or a PR touching only files cargo-delta's file_exclude_patterns ignore).
It is advisory, not a kill switch:
- The CI wiring never uses it to skip whole jobs. Every group runs on every PR.
- Recipes that scope to workspace members may check it and early-return — for
example,
ox-check-clippyskips the cargo invocation whenOX_CHECK_IMPACT_SKIP=true, saving the cargo-delta-computed exclude list from being parsed and the workspace from being touched. - Recipes that don't scope to workspace members ignore it.
fmtstill runs (the source tree may have non-Rust files affected by the PR),deny/audit/aprzstill run (their outcome doesn't depend on what was changed),pr-titlestill runs.
This separation is what makes the wiring layer durably structural. "Which checks can no-op when nothing in the workspace is affected?" is a per-check property and lives in the catalog/recipe, not in the wiring layer. Moving a check between groups never requires touching the stages template / reusable workflow.
A typical skip-aware recipe looks like:
ox-check-clippy: (_ox-check-require "cargo-clippy")
@[ "${OX_CHECK_IMPACT_SKIP:-false}" = "true" ] && echo 'no affected crates; skipping clippy' && exit 0; \
cargo clippy --workspace ${OX_CHECK_EXCLUDE_NOT_MODIFIED:-} --all-targets --all-features --locked -- -D warnings(On Windows, with set shell := ["pwsh", "-NoProfile", "-Command"], the equivalent
short-circuit uses if ($env:OX_CHECK_IMPACT_SKIP -eq 'true') { exit 0 }.)
Not the default. To preview what CI would skip, run cargo-delta manually and export the env vars:
git stash; git checkout origin/main
cargo delta -c .delta.toml snapshot > /tmp/base.json
git stash pop
cargo delta -c .delta.toml snapshot > /tmp/head.json
export OX_CHECK_EXCLUDE_NOT_AFFECTED="$(cargo delta -c .delta.toml impact \
--baseline /tmp/base.json --current /tmp/head.json -f cargo-excludes --affected)"
just ox-check-pr-testA wrapper recipe (ox-check-impact-set base=origin/main) is left to v2: it has subtle
git-state interactions and the manual flow is good enough for the rare case a developer
actually wants to reproduce CI scoping locally.
$ just ox-check
[just] running ox-check-tools-check
[just] running ox-check-pr-fast
[just] running ox-check-pr-test
[just] running ox-check-pr-mutants
ox-check OK
ox-check is an alias for ox-check-pr (set in the managed Justfile region). All three tiers
(ox-check-pr, ox-check-nightly, ox-check-full) are first-class — locally reproducible with
exactly the same arguments CI uses, because CI invokes the same just recipes.
A user with only cargo (no just, no cargo-ox-check) can still run the basics:
cargo test --workspace --all-targets --all-features --locked
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo fmt --checkThe same commands appear as the body of the corresponding just recipes in
justfiles/ox-check/checks.just, so they are discoverable by reading that file. The fallback
covers core hygiene only — coverage, miri, mutants, etc. still require their respective
tools.
Per the four customization tiers in design.md §7:
- Add your own recipes to the top-level
Justfileoutside the managed region. The Justfile's managed region only containsimportlines and an alias — your recipes never collide with it. - Add your own
.justfiles andimportthem after the managed region's closing sentinel. - Override a single ox-check recipe: the
justimport-and-override rules make this awkward (just doesn't have a "the most specific definition wins" rule). The recommended way is to copy the recipe you want to change into your top-level Justfile with a different name (e.g.my-clippy) and reference that from your own group/tier recipes. Don't fight the ox-check-* names; just compose around them. - Disable a recipe wholesale: opt out of the managed
Justfileregion per updates.md §opt-out. This stops the imports from happening at all, so allox-check-*recipes vanish. Use this only when ox-check is no longer the right tool for your repo.
Customizing the contents of justfiles/ox-check/*.just is supported — they're owned files,
so editing them flips them to "dirty" and the next update writes a .ox-check-proposed
sibling instead of overwriting. See updates.md for the lifecycle.