Contributing¶
This page is the operational half of the project: the gates a change has to pass, how to run each one, and what to do when one of them reddens. It assumes you have a clone; Install covers getting one and what toolchain it needs.
Everything here is runnable locally. There is no gate that only exists in CI.
The house rules, in six lines¶
- No new unreviewed
unsafe. Workspace crates forbid it exceptatune_py, which setsunsafe_code = "deny"and documents its four hand-written binding operations at the operation sites. Any widening needs a soundness argument and focused tests. - No panics on library paths.
Resultandthiserror;unwrap/expectonly in tests or provably-infallible spots that carry a comment saying why. atune_corestays browser-clean. No clock, no filesystem, no threads in what compiles forwasm32; everything that needs an operating system sits behind thesystemfeature.- Determinism is a feature. Every source of randomness is seeded and derived, every iteration order is stable.
- Public items are documented, and doc examples compile.
- No gate is weakened to make a change pass. Not with an
#[allow], not with an#[ignore], not with a wildcard that hides a difference. A gate that cannot go red is a gate nobody reads.
Dependency changes¶
Every direct dependency needs either a compiled source use or a written reason in its manifest covering a build script, procedural macro, generated surface, target-specific path, example, test, or optional feature. Before adding or removing an edge, inspect the package in its native, WASM, no-default, and relevant solo-feature configurations; workspace feature unification is not proof that the edge is necessary or unnecessary.
Pinned cargo-machete 0.9.2 is the supported inventory and a protected Rust
gate. A reported edge is a review prompt, not automatic deletion: verify the
cases above, make the smallest manifest change, update Cargo.lock, then rerun
the affected isolated checks and cargo machete. Put a narrowly explained
package.metadata.cargo-machete.ignored entry beside a proven false positive;
never silence a whole class. Do not make cargo udeps a required gate until the
project has an explicit false-positive policy for build scripts, proc macros,
examples, tests, target dependencies, and optional features. Transitive version
duplicates are upstream graph facts unless a first-party edge can remove them
without changing behavior.
CI actions use reviewed full commit SHAs; tool and interpreter inputs use exact
versions. Automated update proposals still require review of the upstream diff
and the affected workflow before the pin moves. Python documentation inputs are
declared in docs/requirements.in; the universal, hash-checked
docs/requirements.txt and its freshness check own the resolved graph.
The gates¶
Run all of these before proposing a change. They are ordinary commands, in the order that fails fastest.
| # | What it checks | Command |
|---|---|---|
| 1 | Formatting | cargo fmt --all --check |
| 2 | Lints, warnings as errors | cargo clippy --workspace --all-targets --all-features -- -D warnings |
| 3 | Tests, excluding doctests | cargo test --workspace --all-targets |
| 4 | Doctests | cargo test --workspace --doc |
| 5 | The core without its default features | cargo test -p atune_core --all-targets --no-default-features |
| 6 | The storage conformance guide's own examples | cargo test -p atune_core --features conformance --doc |
| 7 | The browser target | cargo check -p atune_core --target wasm32-unknown-unknown --no-default-features |
| 8 | Rustdoc, both feature sets | RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps, then the same with --all-features |
| 9 | Generated reference pages are current | cargo dev generate-all --mode check |
| 10 | Every Rust example runs | cargo dev run-examples |
| 11 | Every Python example runs | python -m pytest examples/python, after maturin develop --release -m crates/atune_py/Cargo.toml |
| 12 | Every CLI documentation block runs | cargo test -p atune_cli --test cli_docs |
| 13 | Rust and Python agree, bit for bit | cargo dev check-parity |
| 14 | The site builds with no warning | uv run --with-requirements docs/requirements.txt mkdocs build --strict |
| 15 | The docs.rs build succeeds |
CARGO_TARGET_DIR=target/docsrs RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc -p <crate> --all-features --no-deps, once per published crate (atune_core, atune_derive, atune, atune_cli) — one invocation each, not one over four: docs.rs builds crates alone, and a unified build is strictly weaker than the thing this gate predicts |
Four traps in that list, each of which has cost this project a bug:
--all-targetsexcludes doctests. Gates 3 and 4 are two commands because one of them cannot do both jobs.--no-default-featuresmust be per package. Workspace-wide, feature unification through another member turns the flag into a no-op.- A doctest inside a feature-gated module is invisible without the feature, which is why gate 6 exists as its own line.
- All-on and all-off do not cover the single-feature combinations. A bug can live in exactly one feature set; CI runs the combinations individually — ten solo-feature test runs today — and so should you if you touched feature-gated code.
The fast tier is slightly wider than the table: CI's per-push jobs also run
cargo dev check-api-links (after a fresh cargo doc), cargo dev
check-lints, cargo dev check-guide-links, cargo deny check advisories bans
licenses sources (pinned cargo-deny), the facade's no-default leg, and the
GUI's wasm32 check. Every one is an ordinary local command; they sit outside
the table only because the fifteen above are the ones every change owes, while
these fire on the subsystems they guard.
One more layer exists beside the gates: pre-commit hooks
(.pre-commit-config.yaml is the authority; pre-commit install turns them
on). They are optional automation, not additional obligations — most mirror
gates 1–4 and 8 — but a few checks live only there: the Miri target,
codespell, and mdformat over the non-docs/ markdown. The Miri hook runs
the 20-test atune_core snapshot integration target with
--no-default-features and strict provenance on pinned
nightly-2026-07-26; install it with
rustup component add --toolchain nightly-2026-07-26 miri. It does not claim
workspace, OS-backed Journal, faer, or PyO3 coverage; the ordinary backend
suite, targeted sanitizer, and free-threaded-runtime evidence own those
surfaces. Nothing in this table silently depends on the optional hooks.
Hook revisions in .pre-commit-config.yaml are frozen to full upstream commit
IDs; the comments retain the approved release labels. To refresh one, select
the approved release tag and inspect its first-party ref before editing:
git ls-remote --tags <repository-url> refs/tags/<tag> 'refs/tags/<tag>^{}'
Use the tag line for a lightweight tag or the ^{} line for an annotated tag,
then corroborate the resulting commit on the repository's release or commit
page. Update only that rev and its release comment, and run
pre-commit validate-config plus the affected hooks on disposable fixture
files, using a read-only/check mode where the hook supports one. Do not run
pre-commit autoupdate, choose a newer release, or change hook entries as part
of a pin refresh; a version or behavior change needs separate review.
Gate 14 has a precondition: the Python API reference is produced by importing the module, so the build needs an activated virtual environment with the wheel in it. Without one it fails on that page rather than skipping it.
The installed-wheel Python regression gate covers active-run ownership and canonical constrained analysis. After building and installing the candidate wheel into the current environment, run from the repository root with that same interpreter:
PYTHONPATH= python -m pytest crates/atune_py/tests/test_installed_wheel.py crates/atune_py/tests/test_canonical_analysis.py -q
python -m pytest keeps collection and the ownership tests' child processes on
the wheel interpreter. PYTHONPATH= removes explicit path injection, but is
not by itself a complete shadowing check: the child processes use isolated
-I mode from temporary directories outside the checkout, and an explicit
package-location assertion rejects a source-tree import. The canonical-analysis
tests copy their immutable journal fixtures before opening them, so the source
fixtures remain unchanged.
Python ownership safety runtimes¶
These checks complement the installed-wheel gate; they do not replace it or
add a supported release wheel. Record the source identity, interpreter and
toolchain versions, exact command, test count and diagnostics. A successful
--no-run build is preparation, not safety execution evidence. Missing
prerequisites and open gaps belong in the repository's
MASTER_STRATEGIC_PLAN.md (not published on this site).
For Linux x86_64 AddressSanitizer, use pinned nightly-2026-07-26 with
rust-src, and a CPython interpreter with its development library and the
declared NumPy dependency. Replace the interpreter placeholder with that
environment's lexical executable path:
rtk proxy env -u LD_PRELOAD \
CARGO_TARGET_DIR=/tmp/atune-python-asan CARGO_BUILD_JOBS=2 \
PYO3_PYTHON=/absolute/path/to/python RUSTFLAGS=-Zsanitizer=address \
cargo +nightly-2026-07-26 test --locked -Zbuild-std \
--target x86_64-unknown-linux-gnu -p atune_py --lib -- --test-threads=1
Keep extension-module off: this builds a Rust test executable that embeds
Python and links Rust's matching sanitizer runtime. The explicit target keeps
host build scripts and procedural macros separate; -Zbuild-std instruments
the Rust standard library. CPython, NumPy and other native dependencies are
not instrumented by this command, so it is not whole-process memory-safety
proof. Preserve sanitizer reports; do not silently suppress a failure or turn
off leak detection to obtain a green result. See the
Rust sanitizer guide.
The embedded test executable above is complementary evidence, not the release acceptance test for installed Python shutdown. The installed-wheel safety gate must run fixed-work create/run/drop cycles, release the complete study between cycles, and verify Python-object release and bounded retained-memory growth after warmup. Cover callback and objective failures, stop, abandoned trials, queue ownership, parallel workers, and NumPy exports. Retained trial history and peak RSS alone do not establish a leak. Keep normal-allocator coverage as well as a separate sanitizer-compatible run.
For that separate run, use an isolated pinned CPython build with ASan and
--without-pymalloc, instrument the Rust extension, and verify that both use
one compatible sanitizer runtime. Do not replace the host interpreter.
Preserve raw unsuppressed CPython-only, NumPy-only, and installed-atune reports
with exact source, compiler, options, dependency and artifact identities. An
accepted teardown signature needs independently reproduced, reviewed evidence
and explicit allocation/byte bounds; new signatures, growth, address errors,
truncated reports, or changed identities fail. Never subtract aggregate leak
totals or suppress a whole library. Deliberate native leak, invalid-access,
and Python retained-object controls must fail through the same acceptance
path. A clean build or a clean uninstrumented execution cannot replace them.
See the CPython sanitizer build guidance
and LeakSanitizer documentation.
The fixed-work probe is crates/atune_py/tests/safety/lifecycle.py. For the
pinned ordinary CPython 3.10 wheel environment, run it from a disposable working
directory, with an absolute path to the probe and evidence outside the checkout:
rtk proxy env -u LD_PRELOAD -u PYTHONHOME -u PYTHONPATH -u PYTHONMALLOC \
OPENBLAS_NUM_THREADS=1 timeout 120 /absolute/path/to/wheel-venv/bin/python -I \
/absolute/path/to/atune/crates/atune_py/tests/safety/lifecycle.py \
--allocator pymalloc --cycles 64 --warmup 8 \
--output /absolute/path/to/evidence/lifecycle.json
Its short unit-test runs check the probe interface, not a long-lived-use
certificate. Keep both --negative-control and --negative-growth-control
results: each must exit nonzero for its specific retained-object or memory-growth
reason. Python weak references cannot observe every native extension handle;
the instrumented native gate remains independently required.
Repeat the traced run with PYTHONMALLOC=malloc, --allocator malloc, and
-s instead of -I: isolated Python ignores PYTHONMALLOC. Continue clearing
PYTHONHOME/PYTHONPATH and verify installed-package origins in the report.
For the separately instrumented no-pymalloc interpreter, use
--allocator sanitizer --memory-observer none. That mode requires actual ASan
configuration and mapped-runtime evidence, executes the same ownership cases,
and reports traced growth as unavailable; both traced normal/malloc runs remain
required. CPython's allocation profiler itself produces shutdown leaks in the
selected diagnostic runtime, so native and traced-growth observations are kept
separate. Historical profiler reports remain evidence, not suppressed passes.
.github/release/build_python_safety_runtime.sh prepares the isolated diagnostic
interpreter. It pins the CPython source archive, checks the selected Clang major
for both C and C++ compilers, limits parallelism, records build/runtime identities
and exercises native controls. Select an explicitly installed matching Clang with
--cc and --cxx;
if that private compiler requires shared libraries, select its one absolute
directory through --compiler-library-path, not ambient LD_LIBRARY_PATH.
use --output only with an absent or empty disposable directory. Its successful
exit proves preparation and controls, not the installed-atune lifecycle. Build
and package-install bootstrap may disable leak checking, but acceptance runs
must enable it. Do not set LSAN_OPTIONS=exitcode=0: with the selected runtime
that also makes an address error exit zero.
For allocation-origin investigation only, --disable-float-freelist adds the
fixed upstream CPython setting -DPyFloat_MAXFREELIST=0 to this disposable
runtime's compiler flags. The default remains unchanged. Metadata records the
choice; separately verify actual free-list statistics after allocating and
releasing dynamic floats. This prevents float-object reuse from obscuring the
original malloc call site, but neither proves a leak harmless nor replaces
normal-allocator growth checks. A new runtime needs fresh identity-bound
captures and independent negative controls; never reuse an old leak baseline.
Classify preserved captures with .github/release/check_python_safety.py:
rtk proxy python .github/release/check_python_safety.py \
--log /absolute/path/to/evidence/raw.stderr --exit-status 1 \
--identity /absolute/path/to/evidence/identity.json \
--baseline /absolute/path/to/evidence/reviewed-baseline.json \
--completion /absolute/path/to/evidence/completion.json \
--stdout /absolute/path/to/evidence/raw.stdout
Use the actual captured status, never a substituted workload exit code. A
clean report requires status zero. A complete leak-only report may retain the
selected runtime's status one only with a reviewed exact-stack baseline and
separate completed-workload evidence. Address errors, other statuses and missing
or mismatched evidence fail. Identity schema atune-python-safety-identity/v2
binds source archive, compiler, ASan runtime/options/build flags, wheel, NumPy
and workload attestation; baseline/completion schemas are version 1. Completion
binds the identity, raw stderr/stdout digests, status and workload evidence.
Control references require immutable digests and explicit independent review.
The offline checker does not fetch or authenticate external review artifacts:
their independence and scientific adequacy remain the reviewer's responsibility.
It never generates an accepted baseline; an empty baseline accepts no leaks.
The pinned acceptance options are
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1:abort_on_error=0:fast_unwind_on_malloc=0, with
LSAN_OPTIONS unset. Record those canonical environment keys in
runtime.options; use JSON null for the unset value. Other sanitizer options
require a separate reviewed policy change, not a baseline-file workaround.
Slow allocation unwinding is required because the default frame-pointer
unwinder produced unresolved frames through the pinned NumPy binary. Preserve
every reported frame, module offset and build identity; unresolved signatures
remain ineligible for a baseline. Retain effective-option evidence and rerun
clean, deliberate-leak and invalid-access controls with the selected settings.
The runtime builder's standalone preparation controls use their own minimal
detection settings; they are not captures eligible for this acceptance policy.
For the separate Linux free-threaded safety runtime, build a local wheel with the installed Maturin and an explicitly selected free-threaded CPython interpreter:
rtk proxy env -u LD_PRELOAD CARGO_BUILD_JOBS=2 maturin build --locked \
--manifest-path crates/atune_py/Cargo.toml \
--interpreter /absolute/path/to/python3.14t --compatibility linux \
--target-dir /tmp/atune-python-free-threaded \
--out /absolute/path/to/empty-wheel-output
Inspect the wheel's WHEEL metadata: the safety artifact must be
cp314-cp314t-*, not cp310-abi3-*. PyO3 ignores the limited-API setting for
this interpreter and builds a version-specific extension; this does not change
atune's release matrix. See the
PyO3 free-threading guide.
Install that exact artifact with --no-deps into a disposable free-threaded
environment outside the checkout. Verify its interpreter version,
sysconfig.get_config_var("Py_GIL_DISABLED") == 1, installed package path,
and not sys._is_gil_enabled() after importing atune.
CPython documents the runtime GIL check.
An existing pytest environment may orchestrate the ownership scenarios even
when pytest is unavailable for the free-threaded interpreter:
rtk proxy env -u LD_PRELOAD PYTHONPATH= \
ATUNE_TEST_PYTHON=/absolute/path/to/free-threaded-venv/bin/python \
/absolute/path/to/pytest-venv/bin/python -m pytest \
crates/atune_py/tests/test_installed_wheel.py -q
Each scenario runs in a fresh isolated child using ATUNE_TEST_PYTHON and
checks package origin and that the free-threaded GIL stays disabled on import.
This suite exercises queue/session ownership, exceptions and object release;
it does not exercise NumPy. Missing free-threaded NumPy blocks its own runtime
coverage, not these dependency-independent ownership scenarios.
Three tiers, and which one you owe¶
The fifteen gates above are the fast tier: everything a change is expected to pass before it is proposed, on any machine, with no special hardware. Two more tiers exist, and confusing them is how a suite ends up either untrustworthy or unrunnable.
| Tier | What is in it | Who runs it, and when | What a failure means |
|---|---|---|---|
| fast | the fifteen gates above, plus the CI extras listed after the traps | every change, locally and in the release_gate and wasm CI jobs |
a regression; fix before proposing |
| slow | the #[ignore]d GP sweep and the heavy benchmark oracles — kept out of the fast loop by their cost, not by their prerequisites |
the slow CI job, on every push to main and on demand; locally with cargo test -p atune_bench --release -- --ignored |
a real regression: nothing here depends on a machine outside the runner |
| live | the environment-gated suites — the journal's real shared filesystem case (ATUNE_NFS_DIR) and the four real-Oniro cases (ONIRO_CLI) |
the live CI job, dispatched manually onto a runner that actually has both; never on a schedule |
either a real regression or a changed external contract — read the retained logs before deciding which |
The live tier is manual on purpose. An ordinary CI runner does not necessarily
have a shared filesystem or an Oniro binary, so a scheduled run could fail for the wrong
reason every time and teach everyone to ignore it. The job refuses to start
without both prerequisites rather than reporting a pass it did not earn. The
self-hosted runner supplies ATUNE_NFS_DIR, ONIRO_CLI, ONIRO_REPO, and
GYMNASIUM_RS_REPO; the dispatch supplies the full expected Oniro and
gymnasium_rs commits. The preflight requires a Linux nfs or nfs4 mount and
clean sibling trees at those commits, then retains the environment manifest,
explicit exit codes, logs, and Oniro run roots for ninety days. An
environment-gated claim is only worth the evidence that survives the machine
that produced it.
The exact live commands are ATUNE_NFS_DIR=/absolute/nfs/scratch cargo test
--locked -p atune_core --test journal_nfs -- --ignored --nocapture and
ONIRO_CLI=/absolute/oniro cargo test --locked -p atune_oniro --test
oniro_spike -- --ignored --nocapture. Local substitutes do not close these
evidence rows.
Evidence outside local gates¶
Local green output does not prove these surfaces:
| Surface | Prerequisite | Required evidence |
|---|---|---|
| Real NFS | Linux; readable /proc/self/mountinfo; an absolute, canonicalizable, writable ATUNE_NFS_DIR whose filesystem type is exactly nfs or nfs4 |
Mount identity, commit, six-worker × 25-trial ignored-test log, explicit exit code |
| Real Oniro | Unix; executable ONIRO_CLI; clean Oniro and gymnasium_rs trees at explicitly expected commits |
Binary SHA-256, sibling commits/status, supported schema-1/schema-2 four-test log, explicit exit code, retained roots |
| Self-hosted platform matrix | Linux x86_64/aarch64, macOS x86_64/aarch64, and Windows x86_64 native self-hosted runners | Full gate logs, five native wheel installs, and production manylinux_2_17 proof |
| Hosted CI/Pages | Maintainer-enabled protected runs; final repository/site identity; Pages source set to GitHub Actions; github-pages environment allows the authorized default branch or release tag |
Run IDs, immutable complete-version artifact, deployment commit, and public URL checks |
| Fresh advisories | An online isolated advisory index | Exact cargo-deny version, index revision, lockfile, and full standard-fetch report; protected rerun remains a release gate |
| Crate/wheel/sdist artifacts | Locally buildable artifacts | Isolated consumer installs/imports and a deterministic manifest |
| Publication/signing/rollback | Explicit maintainer authorization | Registry receipts, signatures or provenance, and a staging rollback rehearsal |
All CI execution requires explicit self-hosted routing. Native jobs retain
their OS/architecture requirements; missing runners are unavailable evidence,
not permission to fall back to GitHub-hosted machines or Linux-only checks.
Cross-compilation proves a target build, not native wheel imports or platform
permissions. Linux manylinux wheel jobs additionally require docker-build
and a successful Docker preflight. Fork pull requests cannot execute on this
persistent fleet. Canonical Pages deployment still requires an eligible runner
and workflow in the canonical repository; private SpaceRL CI does not supply
cross-repository Pages identity or OIDC authority.
Gate 15 needs a nightly toolchain at or above the workspace's Rust version,
and runs without -D warnings deliberately: docs.rs
reports warnings and publishes anyway, so the gate asserts what docs.rs will
actually do — the build succeeds — and adding the flag would couple it to
nightly's evolving lint set. Lint enforcement lives in gate 8, whose stable
rustdoc runs -D warnings on both feature sets.
The parity gate¶
Gate 13 is unusual enough to need its own page: the parity gate — what bit-for-bit equality asserts, how to run both halves, the five causes of a red in the order to check them, and what you may not do to make it quiet.
Generated pages are not edited¶
Seven pages under reference/, the three Python type stubs, and docs/api_links.yml
are written by cargo dev generate-all, from the code's own registries: the clap command
tree, the [features] tables, the error enum, the sampler and scheduler
catalogues. They carry begin/end pragmas, and gate 9 fails when a checked-in file
has drifted.
So: never hand-edit a generated page. Change the code, then run
cargo dev generate-all --mode write and commit both. Adding a new generated
page is one GeneratedDoc implementation plus one line in the driver — no
documentation edit is needed for a new sampler, flag or feature, which is the
point.
Documentation rules¶
The guide is held to five rules, and they are what keep it from rotting:
- No page over 200 lines of hand-written source. A page that outgrows it splits, or delegates to a reference page.
- No code block in a page. Every fenced block is a macro call that pulls a
real file out of
examples/, so a page cannot contain code that CI does not run. Short literals —cargo add atune, a manifest line — go in a code span instead. - Adding a page is one file plus one
nav:line inmkdocs.yml. A page that is not in the nav fails the build, and so does a nav entry with no page. - Adding an example is one file per language, plus its
parity.tomlentry. docs/requirements.txtnames four direct dependencies, and that is a cap. Every addition is a maintenance liability and a supply-chain edge.
Build the site with uv run --with-requirements docs/requirements.txt mkdocs serve from an environment that has the wheel installed. The internal design
corpus under docs/design/ is excluded from the site on purpose: it is written
for implementers, and no published doc comment or crate front page may cite it,
because a reader of docs.rs or PyPI cannot open it.
A crate's README.md and its //! header are different documents on purpose.
The README is the crate's shop window on crates.io; the //! header is the entry
point to its API reference. The one part that would drift — the quickstart — is a
shared snippet included by both.
Commits, and reporting things¶
Commit messages follow the repository's existing convention: a type and scope,
then what changed — docs(guide): …, feat(core): …, fix(cli): ….
Report a bug as a GitHub issue with the smallest reproduction you can manage, including the version or commit, the platform, and whether it reproduces with a single thread. Do not report a suspected vulnerability that way; the security policy has the private route.
Where to go next¶
| If you want to | Go to |
|---|---|
| What a version number promises | Versioning and stability |
| What changed | Changelog |
| To write a sampler, scheduler or storage backend | Extending |
| To publish your own as a crate | Publishing a plugin crate |
| The determinism contract the parity gate asserts | Determinism |