Skip to contents

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 v1

The 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 command

The 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:

  1. 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 abstract MsEngine, resolved from the string sugar via engine_class()), fit_fn (the implementation function, called by fit_engine() as fit_fn(engine, ...)), packages (the R dependencies reported by required_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).
  2. 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 at match_ms_engine() resolution (class msuiter_warning_uncertified_engine). The documentation must state the trust boundary: registered engine code executes with your privileges.
  3. The fit_engine / required_pkgs semantics: fit_engine() dispatches to the registered fit_fn; required_pkgs() reports the R package dependencies before the engine runs (for ms_sitrep() / the installation hints).
  4. 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.R contract suite; a third-party package must ship its own: a registration round-trip test (visible via ms_engines() after .onLoad(), resolvable via match_ms_engine()), the behaviour tests of its own engine, and the idempotence test of the non-certified warning.
  5. The documentation obligations: the third-party documentation declares engine_version / contract_version / certified and the trust boundary; the msuiter-side vignettes/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.
  6. 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):

  1. Read through the header of src/rust/deny.toml and the [advisories] comments (the upgrade re-review hook is exactly this flow);
  2. The full cargo deny check (all four sections, not just the bans fast gate);
  3. 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;
  4. 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;
  5. The MSRV re-proof: cargo +1.71.0 build/test --workspace passes (the lock pins must not move, see §6); Cargo.lock stays 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.md exemplar: 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 (currently 0x4D53_5552_4E47_5631 = “MSURNGV1”, see src/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, and tools/docs-sync.R plus 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 --check constants; 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.lock stays version 3. The MSRV is not relaxed – raising the rustc floor requires an ADR.
  • The two crates engine/catalog carry #![forbid(unsafe_code)]; extendr-api may appear only in the FFI shell manifest; the direction ffi→catalog→engine never reverses (asserted by tools/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. See git log for 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 through lifecycle::deprecate_warn; NEWS.md is the changelog; the error classes follow the msuiter_error_<topic> convention, a bare stop() is forbidden.