Contribution guide (CONTRIBUTING)
Source:CONTRIBUTING.md
Basis: vignettes/articles/architecture.qmd §2/§3.4/§3.5/§9 (the dependency budget, the engine extension contract, errors and lifecycle, TDD/CI), vignettes/articles/ffi-surface.qmd (the freeze protocol), vignettes/articles/adr-0002-vendor-budget.qmd (the vendor budget and the MSRV pin discipline), vignettes/articles/roadmap.qmd the M0 governance-files entry. Before any PR is submitted, the local acceptance battery of this document must be fully green; the PR template (.github/PULL_REQUEST_TEMPLATE.md) check items must be ticked one by one.
1. The development environment
| The component | The requirement | The notes |
|---|---|---|
| R | ≥ 4.3 | the DESCRIPTION Depends floor (D8) |
| Rust | rustup-managed, MSRV 1.71 | DESCRIPTION SystemRequirements: cargo, rustc (>= 1.71); validation must pass cargo +1.71.0 build/test (ADR 0002 §4) |
| The R toolchain | rextendr (the workflow), devtools, testthat ≥ 3 (edition 3) | the rextendr hard convention: the FFI crate manifest sits at src/rust/Cargo.toml (the root-package + members shape, fixed by the U-M0-01 live measurement) |
| cargo-deny | cargo install cargo-deny |
the four sections: the ban rules / license / advisory / sources |
| Python3 | pure stdlib | needed only by tools/rng-kat.py
|
The everyday development flow (rextendr): devtools::load_all() compiles the Rust workspace and regenerates R/extendr-wrappers.R; the wrapper file is generated by rextendr and is never hand-edited; the vendor artifacts under src/rust/ (vendor/, vendor.tar.xz) do not enter git (ADR 0002 §3.3).
The known local trap (ADR 0002 §4.4): the conda PATH injected by ~/.Renviron shadows the front toolchain – when validating the MSRV on this machine use R_ENVIRON_USER=/tmp/empty-renviron + the 1.71 bin prepended to PATH.
2. The local acceptance battery (fully green before committing)
The Rust side (cd src/rust):
cargo test --workspace # the unit/property/golden tests
cargo clippy --workspace --all-targets -- -D warnings # the lint gate (the devlog M0-01 caliber)
cargo deny check # all four sections green (the fast gate: cargo deny check bans)
sh ../../tools/check-dep-direction.sh # the dependency-direction guard ffi→catalog→engine
sh ../../tools/vendor.sh --check # the vendor byte-count baseline-drift check (a ±0.1% xz tolerance band)
python3 ../../tools/rng-kat.py # the independent KAT of the self-developed PCG64 layout v1The R side (the repository root):
Rscript -e 'devtools::test()' # the full testthat edition-3 suite (incl. the ffi-surface drift guard)
R CMD build . # the artifact *.tar.gz
R CMD check <tar.gz> --no-manual # no degradation: the 0 error / 0 warning baseline
Rscript tools/docs-sync.R # the ARCH/ROADMAP ms_* API ↔ CAPABILITY-MATRIX L-H table sync
Rscript tools/test-heavy.R # the tier-T2 heavy battery: the two check-layout-sim
# provisioning simulations (~41 min together), one commandThe shard manifest carries a tier column (issue #76): tier T0 is the PR-gate battery every push runs, T1 the scheduled deep battery (xval, refdb, ms-qc sentries, benchmark driver), and T2 the two provisioning simulations under tests/heavy – they are not run by the PR CI; the one-command face above is their local gate (the nightly scheduled job runs the same face), so run it before a release-touching change that could move the check layout. The PR-gate T0 weight is bounded by the committed measurement tools/ci/pr-gate-budget.tsv; growing it is a deliberate re-seal, not an accident.
The CI (U-M0-07) runs the same battery on the 3 OS × {release, oldrel, R-4.3(Linux), devel} matrix plus rcmdcheck –as-cran, cargo audit and the valgrind/ASAN scheduled jobs; the local battery is its precondition.
3. The third-party engine onboarding checklist (the ARCHITECTURE §3.4 extension contract)
A third-party package registers its engine by calling msuiter::register_ms_engine() from its own package’s .onLoad() (the registry row is data; the registration itself does not need S7::methods_register(); that is called only when the third party defines its own methods for the msuiter generics). Onboarding must satisfy every item:
-
All nine fields present:
name(lowercase snake_case, the registry key; a duplicate registration errors),mode("extract"or"fit"),engine_class(an S7 class derived from the abstractMsEngine, resolved from the string sugar viaengine_class()),fit_fn(the implementation function, called byfit_engine()asfit_fn(engine, ...)),packages(the R dependencies reported byrequired_pkgs(), may be empty),tags(free labels, may be empty),engine_version(required, a single string: the engine implementation’s version),contract_version(required, a single string: the msuiter registry/extension-contract version it targets),certified(required, the three states, see below). -
The certified three-state semantics and the trust boundary:
"certified"= passed the msuiter maintainer compatibility review (A17);"self-reported"= self-reported by the engine’s author;"unknown"= undeclared. A non-certified engine raises a once-per-session warning atmatch_ms_engine()resolution (classmsuiter_warning_uncertified_engine). The documentation must state the trust boundary: registered engine code executes with your privileges. -
The fit_engine / required_pkgs semantics:
fit_engine()dispatches to the registeredfit_fn;required_pkgs()reports the R package dependencies before the engine runs (forms_sitrep()/ the installation hints). -
The contract-test requirements: the registration validation (the field types / the name syntax / the derivation relations / the duplicate keys) is covered by the msuiter-side
tests/testthat/test-engines-registry.Rcontract suite; a third-party package must ship its own: a registration round-trip test (visible viams_engines()after.onLoad(), resolvable viamatch_ms_engine()), the behaviour tests of its own engine, and the idempotence test of the non-certified warning. -
The documentation obligations: the third-party documentation declares
engine_version/contract_version/certifiedand the trust boundary; the msuiter-sidevignettes/extending.Rmd+ the companion harness (the M2 deliverable) are the canonical reading for extension authors. The maintainer runs the monthly reverse-dependency compatibility matrix job over the certified engines. -
The D9 red line: the external tools (SigProfiler*, MuSiCal, mmsig…) never enter the
ms_*API – they serve only as bench comparators (the bench/adapters containers) and must not be wrapped into the user interface through the registry table.
4. The extendr-api upgrade checklist (the deny.toml [advisories] comment hook)
On every extendr-api upgrade (any minor/patch):
- Read through the header of
src/rust/deny.tomland the[advisories]comments (the upgrade re-review hook is exactly this flow); - The full
cargo deny check(all four sections, not just thebansfast gate); - Re-assess paste 1.0.15 / RUSTSEC-2024-0421 (unmaintained, transitively via extendr-api 0.9):
unmaintained = "workspace"scans only the own crates and so does not fail the build, but an upgrade may bring in new transitive dependencies or lift that exposure – re-read entry by entry and update the deny.toml comment conclusions; - Per the ADR 0002 §2 increment discipline append a row to the ADR table (the date/the unit/the newly added content/the byte increment on the three calibers/the new baseline), and update in lockstep the built-in baseline constants of
tools/vendor.sh --check; - The MSRV re-proof:
cargo +1.71.0 build/test --workspacepasses (the lock pins must not move, see §6);Cargo.lockstays version 3.
5. The fixture / golden refresh protocol (the team’s iron law)
-
Any golden-fixture update (the bit-for-bit semantic goldens, the numerical-protocol goldens, the RNG golden, the channel reference files) must state the upstream reason in the corresponding devlog (
vignettes/articles/devlog-YYYY-MM-DD-<name>.qmd) – the upstream version/commit/protocol change or the implementation-fix basis. A golden change without a reason is refused outright. A precedent: the U-M0-06 RNG post-step correction (the canonical pcg64 output bits) recorded the regeneration rationale and the numpy/pcg-cpp evidence in its devlog. -
The provenance format follows the
tools/channel-reference/README.mdexemplar: the upstream source (the project @ the branch + the commit SHA + the path), the license (attaching the original text or an audit link), the acquisition date, the purpose (which test anchors it). -
An RNG golden regeneration must rotate the
LAYOUT_ID(currently0x4D53_5552_4E47_5631= “MSURNGV1”, seesrc/rust/engine/src/rng.rs): any modification affecting the stream layout / the output-bit convention = a new layout = a new id (domain separation; the v1 stream and the later streams are mutually inconvertible), and the old golden is kept as the cross-version comparison. - The channel-registry single source of truth sits at
data-raw/build_channels.R: changing channel data regenerates through that script, andtools/docs-sync.Rplus the channels sync tests (drift goes red) are the guards.
6. The dependency discipline
-
The budget frozen (ARCHITECTURE §2 / D2/D4):
extendr-api(default-features=false, the ndarray feature only),ndarray,rayon,memmap2,twobit 0.2.2(unlocked by ADR 0001). The rand family is globally denied – the only randomness is the self-developed PCG64 + SplitMix64. -
A new dependency requires an ADR: stating the necessity, the byte-size impact, the MSRV compatibility; and per ADR 0002 §2 appending the vendor baseline row + updating the
tools/vendor.sh --checkconstants; every ~0.1 MB xz increment must be recorded, and a budget overrun triggers the dependency-re-shrinking decision. -
The MSRV pins must not move:
matrixmultiply = 0.3.9(≥0.3.10 needs rustc 1.75),rayon = 1.10.x(≥1.11 needs rustc 1.80);Cargo.lockstays version 3. The MSRV is not relaxed – raising the rustc floor requires an ADR. - The two crates
engine/catalogcarry#![forbid(unsafe_code)];extendr-apimay appear only in the FFI shell manifest; the direction ffi→catalog→engine never reverses (asserted bytools/check-dep-direction.sh).
7. The commit style and the review flow
-
Conventional Commits (
feat/fix/docs/test/refactor/perf/chore), the scope using the milestone/unit number (e.g.feat(m0): U-M0-04 the engine registry table); Chinese commit subjects are permitted by this house convention. Seegit logfor examples. - TDD first: write the test (red) before the implementation (green); the error-path tests assert the condition rather than a crash.
-
The review flow: the PR → ticking the template check items one by one → at least one round of independent audit (the REVISE/PASS verdict of the AI-agent audit round is recorded into the period’s devlog) → the local acceptance battery + CI fully green → squash or per-unit merge. One devlog per work unit (
vignettes/articles/devlog-*.qmd); major architectural adjudications go through ADRs (vignettes/articles/adr-*.qmd). -
lifecycle (ARCHITECTURE §3.5): before v1.0 the API is marked
@experimental; deprecation goes throughlifecycle::deprecate_warn; NEWS.md is the changelog; the error classes follow themsuiter_error_<topic>convention, a barestop()is forbidden.