This is a filtered collection, not a popularity list.
A proposed entry should answer four questions:
- What recurring engineering decision or failure does it improve?
- Under what conditions is it valid?
- What does it cost or conflict with?
- What, if anything, can be enforced without guessing?
- Copy an existing TOML record.
- Use a stable kebab-case
idand matching filename. - Prefer the narrowest defensible claim.
- Add at least one serious source.
- Link related and conflicting entries by ID.
- Add the record's
include_str!pair toRECORDSinsrc/catalog.rs, in id order. The binary carries the catalog compiled in, andcargo testrefuses a tree where the list andls principles/disagree. - Rebuild the generated index and the compiled review documents, then run everything CI runs:
uv run --no-project scripts/build_reference.py
uv run --no-project uphold_check.py --review --emit
prek run --all-files --hook-stage manual # or: pre-commit run ...Write enforcement.checks as a brief for whoever builds the check, and
enforcement.limits as what that person will not be able to observe. Neither is
a check. No field of a record is emitted by a tool at runtime: prose has no
condition on which to emit it, so it would be emitted always and ignored, or
never. Adding an entry to policy/upheld.toml requires a rule that already
fires.
policy/principles.toml is this repository's own policy and also the worked
example, so it declares facts about this checkout that a fork has to change.
Both are at the top of the file:
owner = "HackingGate" # where this repository's pushes may go
visibility = "public" # whether what it publishes is readable by everyoneowner is not a claim on the project. unowned-push requires it because the
alternative, reading the owner from origin, is circular for the remote most
likely to be wrong: repointing origin elsewhere would also repoint the
allow-list. A fork changes that line to its own owner, and
prevent-public-push refuses the first push until it does. That is the guard
working as intended.
visibility decides whether the private-name guards run at all. A private fork
sets private and they stand down; a public one keeps public.
A workspace holding many repositories repeats both lines many times; one fleet
had 78 owner lines with seven distinct values. owner_from and
visibility_from take a command whose stdout is the value instead, so the fact
is stored once outside the tree. Every way the command can fail to answer is
exit 2. See
REFERENCE.md.
A third fact concerns your machine rather than your fork, and no line in the
policy file carries it. The private-names set reads
$XDG_CONFIG_HOME/principles/private-owners (else
$HOME/.config/principles/private-owners) for the organizations whose names
must not be published. The file is optional: without it, the guard reports on
stderr what is not being checked, and the commit proceeds. If you keep such a
list, put it at that path, or point private_owners_file at it from the top of
the policy file, and the two forms the note names are checked as well. See
REFERENCE.md.
The checks run at three stages, chosen by cost. The commit stage runs what can
answer from the tree alone: cargo fmt --check, the catalog gates, the content
scan, and the guards registered for the stage. Nothing there compiles the test
tree or opens a socket.
The push stage builds and tests the crate:
cargo test --quiet # the engine suite
cargo clippy --quiet --all-targets # the lint profile declared in Cargo.tomlThese used to run at every commit. A full compile and test pass on every commit
costs time even on commits that touch no Rust, and encourages --no-verify,
which also skips the guards. At pre-push they still refuse before anything
leaves the machine. uphold guard --stage pre-push runs there with them.
The manual stage covers the host and the network:
scripts/deps.sh check # rustup, rustc >= the MSRV, python3, the coverage pair
scripts/coverage.sh # line coverage, refused under the floor in the script
uphold guard --stage manual # the guards that ask a remote about a pin or a nameAll three are manual-stage hooks under pre-commit and prek, and named groups
under lefthook (lefthook run preflight, lefthook run coverage,
lefthook run uphold-manual). The coverage floor is defined only in
scripts/coverage.sh; the workflow calls the same script, so CI and local runs
enforce the same number. Raise it in the commit that earns it.
Editing anything under policy/base/ requires regenerating the set lock in the
same commit, because a bundled set ships inside the binary and would otherwise
change with no diff:
cargo run --quiet -- rules --sets --json > policy/base/sets.lock.jsontests/base_set_lock.rs refuses a tree where the two disagree. Review the
lock diff: it is the change consumers will receive.
A rule added to a bundled set needs an entry in tests/base_set_corpus.rs: at
least one sample it must refuse, and the forms it must let through.
every_content_rule_in_every_bundled_set_is_in_the_corpus fails without one.
This is mandatory because a rule that stops matching produces no output at
all: the gate stays green and nothing reports that the check stopped working.
The MSRV is written twice, in Cargo.toml as rust-version and in
toolchain.toml as the rustc want, because cargo and the preflight cannot read
each other's manifest. Bump both together; tests/test_toolchain.py refuses a
tree where they disagree. Nothing builds the crate on that version: every build
runs on the stable that rust-toolchain.toml names, and cargo uses the MSRV
when resolving dependencies.
Every CLI test builds a real repository under <temp>/uphold-tests/<pid>/, and
the first fixture in a run removes every sibling whose process has exited. Use
support::scratch("name") in tests/, crate::fixture::scratch("name") in
src/, and do not use std::env::temp_dir() directly.
The previous approach cleared a fixture on creation and never afterwards, which
freed nothing, because the directory name includes the pid so it cannot collide
with a live run. One working session left 84,992 directories under /tmp,
filled 15 GB of a 16 GB tmpfs, and caused a cargo mutants run to fail with
No space left on device, which it then reported as 158 mutants "unviable".
cargo install cargo-deny
cargo deny checkdeny.toml defines what this crate may depend on and under what terms:
advisories, licences named one at a time, no wildcard versions, and crates.io as
the only source. These are facts about the dependency graph rather than about
the tree's files, so no rule in policy/principles.toml can check them.
It is not wired into a hook. The advisory check uses the network, and network
checks run at pre-push or manual, not at every commit, for the same reason
no-stale-hook-pins does.
Keep the license allow-list to what the tree uses. cargo deny reports an
allowance that matched nothing; remove such entries.
Coverage shows that a line ran, not that a test would notice the line being wrong. The recurring failure mode here is of that kind: a check that could not look reporting a pass.
cargo install cargo-mutants
cargo mutants --file src/check.rs -j 4 # one module, minutes
cargo mutants -j 4 # the crate, hoursRun it one module at a time. Measured here: src/check.rs is 98 mutants and
about eight minutes at -j 4; the crate is 1,573 mutants, which takes hours.
Start with the modules that decide an exit state (check.rs, config.rs,
guard/mod.rs, pins.rs), because those are where an unknown becomes a
verdict.
A surviving mutant says something about the tests, not the code: the code could be wrong here and every test would still pass. Read it before writing anything. Some survivors are equivalent or unreachable; note those in the commit message rather than writing a test to silence them.
cargo install --locked kani-verifier && cargo kani setup
cargo kani -j --output-format=terse # ten harnesses, about two minutes
lefthook run proofs # the same, as the manual groupTwo places in this crate turn an unknown into a number a caller acts on, and
each carries a #[cfg(kani)] mod proofs that states what it must do over every
input rather than over the few a unit test can name.
error::verdict and Exit::of, over every pair of counts and every run: a run
that could not look never exits 0, a violation outranks an unread surface,
clean means everything was read and nothing found, and a run that stopped on an
error exits 2. Changing could_not_look > 0 to could_not_look > 1 passes
every in-crate unit test, including the four that test verdict directly,
since they use 3 and 0 and never 1. Kani refuses it in 15 milliseconds, with the
counterexample.
text::over_kinds, the step every published-text seam except the shim takes
from what each kind of rule answered to the verdict, over every seam and every
combination of answers: clean exactly when every consulted kind looked and
found nothing, exit 2 whenever one could not look, each consulted kind asked
once and no other kind asked, and the same answers giving the same verdict.
text::load_for is proven to return an error for a policy that did not load,
with config::load replaced by a loader that always fails. Swallowing a kind's
error in over_kinds, or skipping one consulted kind, fails three of the five.
The harnesses state what they do not reach: the rule bodies (a regex, a literal search, a command source, a guard reading the repository), the shim's per-rule dispatch, and everything that reads a file or runs a process. Those remain covered by tests.
The proofs run at the manual stage, not pre-push. Two of the harnesses take one
to two minutes of solver time each, and cargo kani setup fetches a toolchain
of about half a gigabyte. CI runs them as a separate job; locally they are
lefthook run proofs, or the kani hook at the manual stage of pre-commit and
prek.
Bump version in Cargo.toml, merge, then:
git tag vX.Y.Z && git push --tagsThe tag is the only trigger. .github/workflows/release.yml is generated by
dist from dist-workspace.toml and builds the archives, the shell installer
and the GitHub Release. The release body holds the install instructions and the
artifact table; the change record is the merged pull requests between the
previous tag and this one. Edit the workflow through dist-workspace.toml and
dist generate, never by hand, because the next dist init rewrites it.
An entry will normally be rejected when it is:
- a context-free slogan;
- a renamed duplicate;
- vendor marketing presented as a general law;
- impossible to distinguish from its opposite in practice;
- framed as universally correct despite known trade-offs;
- coupled to a transient framework API;
- an enforcement proposal that cannot observe the claimed property.
Review the semantic record before the prose. Ask whether the claim, scope, conflicts, and enforcement limits agree. Grammar can be corrected later; an incorrect boundary becomes policy debt.