rill-ml 1.3.0

RillML adaptive intelligence core library — lightweight, serializable online machine learning for native and edge applications.
Documentation
# Contributing to RillML

Thank you for your interest in RillML. This document describes how to
contribute effectively while preserving the project's core principles.

RillML (Rill) is a lightweight adaptive intelligence runtime for native and
edge applications; `rill-ml` is its adaptive intelligence core library (online,
single-pass, bounded-memory). The bar for additions is **reliability and
verifiability**, not algorithm count. A small set of well-tested modules is
preferred over a large set of loosely verified ones.

## 1. Before you start

Please read:

- `README.md` (or `README.en.md`) — project scope, status, and disclaimers.
- `ROADMAP.md` — long-term direction and per-version goals.
- `CHANGELOG.md` — what has already shipped.

If your contribution is substantial (new module, API change, or new
dependency), please open an issue first to discuss the design. Small fixes
(docs, typos, test improvements) can go straight to a pull request.

## 2. Project principles (non-negotiable)

Every contribution must respect:

1. **Online, single-pass, bounded memory.** No storing the full history. `O(1)`
   for non-rolling statistics, `O(d)` for linear models, `O(window)` for
   rolling statistics.
2. **`predict` is side-effect free.** State updates happen only in `learn` or
   `update`. Progressive evaluation follows `predict → metric.update → learn`.
3. **No panics in public APIs.** Return `Result<_, RillError>`. Validate inputs
   at the boundary.
4. **`f64` only.** Dense `&[f64]` slices. No `HashMap<String, f64>`.
5. **Concrete types for optimizers/losses.** No trait objects for these. Use
   enums so state remains serializable.
6. **Transformers never see the target `y`.** No label leakage in the
   progressive-evaluation sense.
7. **No domain-specific types.** RillML must not contain `Battery`, `Mouse`,
   `ChargingState`, `PollingRate`, `RGB`, HID, Tauri, or plugin-protocol
   types. These belong in the application layer.
8. **Optional `serde` only.** The default build must not require `serde`.
9. **No `no_std` claim.** Do not add a fake `std` feature.
10. **No CLI named `rill`.** Future inspect tools, if any, will be named
    `rillml-inspect` or similar.

## 3. Development setup

Requirements:

- Rust 1.94.0 or newer (MSRV is pinned to 1.94 for the `rill-ml` core crate).
  Ecosystem crates under `crates/` (`rill-ml-polars`, `rill-ml-python`,
  `rill-ml-arrow`) require the stable toolchain due to transitive dependencies.
- `cargo`, `rustfmt`, `clippy` from the stable toolchain.

Common commands:

```bash
cargo fmt --check
cargo check
cargo check --features serde
cargo test
cargo test --features serde
cargo clippy --all-targets --features serde -- -D warnings
cargo doc --features serde --no-deps
RUSTDOCFLAGS="-D warnings" cargo doc --features serde --no-deps
cargo package
cargo package --features serde
```

Before pushing, please run the full release checklist locally (see
`.github/workflows/pipeline.yml`). CI will run the same set on Linux, macOS, and
Windows.

## 4. Adding a new module

A new module (statistic, model, metric, transformer, etc.) must come with:

- A clear mathematical definition in the module-level rustdoc, including the
  update rule and any numerical-stability considerations.
- Time and space complexity stated in the rustdoc.
- Boundary conditions: what happens with zero samples, one sample, non-finite
  inputs, dimension mismatches.
- Unit tests covering the happy path and the boundaries.
- A random comparison test against a reliable reference (offline batch
  formula, or a well-known implementation). Use `proptest` or a fixed-seed
  `ChaCha8Rng`.
- Serialization round-trip test under `tests/serialization.rs` when the type
  holds state.
- At least one example in the rustdoc or under `examples/`.

If any of these is missing, the PR will be held until it is addressed.

## 5. API stability and breaking changes

The Stable crates (`rill-ml`, `rill-runtime`, `rill-runtime-protocol`,
`rill-handler-api`) are under the 1.x compatibility freeze documented in
[`STABILITY.md`](STABILITY.md). The Preview crates
(`rill-ml-python`, `rill-ml-wasm`, `rill-ml-tokio`, `rill-ml-arrow`,
`rill-ml-polars`, `rillml-inspect`) remain at `0.x`.

### 5.1 1.x breaking-change policy

Breaking changes to any Stable artifact require a new major version (2.0).
Stable artifacts include:

- Rust public API of the Stable crates.
- serde model state schema (a new `format_version` is not breaking if a
  migration path exists, but removing support for an old version is).
- IPC v1/v2 wire schema.
- WIT ABI v1.
- Model/handler pack format.
- Runtime CLI subcommands and argument names.

Additive changes are allowed within 1.x: new Stable APIs, new error codes,
new `*Config` fields via `#[non_exhaustive]`, new enum variants via
`#[non_exhaustive]`. See `STABILITY.md` for the full policy.

### 5.2 Deprecation-before-removal

A deprecated Stable API remains available for at least one minor 1.x
release before removal. Removals happen only in a new minor release,
never a patch. Deprecations are recorded in `CHANGELOG.md` under a
"Deprecated" section.

### 5.3 API baseline and SemVer CI

The public Rust API of each Stable crate is recorded under
`api-baseline/` and enforced by `cargo-semver-checks` in CI. A PR that
changes the public API of a Stable crate must update the corresponding
baseline file in the same PR. CI will fail on unapproved baseline
changes. Additive changes are accepted; breaking changes are rejected
unless explicitly approved as a 2.0 preparation.

### 5.4 State schema bumps

`Snapshot<T>` carries an outer `format_version` (currently `1`). Each
serializable model type additionally implements `ValidateState` to
enforce type-specific invariants. A schema bump requires:

1. A migration note in `CHANGELOG.md`.
2. A new fixture set under `tests/fixtures/state/v<N>/`.
3. Continued loading of all prior 1.x fixtures in CI.
4. A `format_version` increment in `Snapshot<T>`.

Removing support for an old `format_version` is a breaking change and
requires 2.0.

### 5.5 IPC and WIT evolution

IPC v1/v2 wire schemas and WIT ABI v1 are permanently frozen. New fields
or new semantics use a new versioned type (e.g. `RuntimeResponseV3`,
`rill:handler@2.x`). The 1.x runtime continues to support v1 and v2
throughout the 1.x cycle. Removing an old protocol version or WIT ABI
version requires 2.0 or the end of the stated support period.

### 5.6 MSRV bumps

The Minimum Supported Rust Version for Stable crates is **1.94.0**,
enforced in CI. An MSRV bump is a minor breaking change and requires:

1. A `CHANGELOG.md` note.
2. A CI matrix update (the `msrv` job's `toolchain` field and the
   `Rust 1.94+` badge in `README.md` / `README.en.md`).
3. A `STABILITY.md` update if the stated MSRV value changes.

### 5.7 Public enum and config design norms

- Non-versioned, extensible enums carry `#[non_exhaustive]` so future
  variants can be added without a breaking change. Versioned wire-schema
  types (e.g. `RuntimeRequest`, `RuntimeResponse`, `RuntimeResponseV2`)
  are NOT marked `#[non_exhaustive]`; they evolve by introducing a new
  versioned type.
- Config structs that are expected to grow carry `#[non_exhaustive]` and
  a `Default` implementation. Examples and docs use `Default::default()`
  or the provided constructors rather than full struct literals where
  the field set may expand.
- Public APIs do not panic on ordinary user input. `Mutex::lock()` calls
  in public paths return `Result` rather than `.expect()`. Internal
  invariant panics must not be reachable from external input. Rustdoc
  `# Panics` sections document any remaining condition.

## 6. Code style

- Run `cargo fmt` before committing. `rustfmt.toml` is checked in.
- `cargo clippy --all-targets --features serde -- -D warnings` must pass.
- Public items have rustdoc with `///`. Include `# Errors` and `# Panics`
  sections where applicable (public APIs should not panic).
- Avoid unexplained abbreviations.
- No `println!`/`eprintln!` in library code. Examples and benches may print.
- No global mutable state. No implicit threads. No async inside the library.
- Floating-point comparisons in tests use tolerances (`approx` or explicit
  `abs()` checks), never `==` on computed values.
- Random examples and tests use fixed seeds so output is reproducible.

## 7. Tests

- Unit tests live in `#[cfg(test)] mod tests` inside each source file.
- Integration tests live under `tests/` and exercise cross-module behavior
  (pipeline, progressive order, serialization round-trip, learning
  convergence).
- Property-based tests use `proptest` with fixed seeds.
- Reference tests compare online results against batch formulas.
- Do not introduce tests that depend on wall-clock time, network access, or
  the filesystem.

## 8. Commit messages and pull requests

- Use the imperative mood: "Add RollingMSE", not "Added RollingMSE".
- Reference issues in the PR description, not in every commit.
- Keep PRs focused. A PR that mixes a new module with a refactor of unrelated
  code will be split.
- Fill in the pull request template (`.github/pull_request_template.md`).
- Make sure CI is green before requesting review.

## 9. Releases

Releases are created automatically from `main`; do not create or push version
tags by hand. To prepare a release:

1. Update the root and workspace package versions, all local path dependency
   requirements, the example model manifest, and the Python project version
   per the `release-plan.toml` Stable/Preview split. Stable crates inherit
   the version from `[workspace.package].version`; Preview crates pin
   locally and stay at `0.x` during the 1.x cycle.
2. Move the release notes out of `[Unreleased]` into a dated version section
   in `CHANGELOG.md`.
3. Push the completed change to `main`.

After the `CI / Release` CI jobs succeed, `Auto Release` validates that every
version source agrees, creates the missing annotated `vX.Y.Z` tag at the
verified `main` commit, and dispatches `CI / Release` with the tag input.
Existing tags are never moved. A successful or active release is treated as an
idempotent no-op, while a failed release can be retried automatically with the
current workflow while still checking out the original immutable version tag.

### 9.1 Release channels

There are two release channels, documented in `STABILITY.md`:

- **stable**`stable-index.json` published to the `local-ai-stable`
  pointer release. Updated only by a final stable version
  (e.g. `1.2.0`, `1.1.0`).
- **candidate**`candidate-index.json` published to the
  `local-ai-candidate` pointer release. Updated only by a prerelease
  version (e.g. `1.2.0-rc.1`, `1.2.0-rc.2`).

The release workflow detects prerelease versions (the version string
contains `-` after the patch number) and routes the signed index to the
appropriate channel. A candidate release never updates the stable
pointer; the stable pointer remained at `0.13.0` throughout the 1.0 RC
cycle.

### 9.2 Prerelease versions

Prerelease versions follow SemVer `1.2.0-rc.N`. The version tooling
(`scripts/sync_version.py`, `scripts/release_version.py`,
`scripts/release_version_compare.py`) accepts prerelease identifiers via
strict SemVer 2.0 regex patterns. `scripts/build-release-index.py` routes
the signed index to the candidate channel via `--channel candidate`. The
release workflow passes `--prerelease` to `gh release create`. RC assets
and tags are immutable: a failed RC is fixed by shipping `rc.N+1`, never
by moving the existing tag.

## 10. Licensing

By contributing, you agree that your contributions will be licensed under
the MIT license, the same as the rest of RillML. Do not include
code that is incompatible with this license.

If you adapt code from another project, attribute it in
`THIRD_PARTY_NOTICES.md` and ensure the source license permits inclusion under
the MIT license.

## 11. What we do not want

Please do not open PRs for:

- A "dynamic, arbitrarily-composed pipeline" with trait objects and runtime
  composition. This is explicitly out of scope.
- `no_std` support. It is a future possibility, not a current goal.
- Python bindings, WASM bindings, or PyO3 wrappers in the **core** `rill-ml`
  crate. They now live in separate workspace crates — `rill-ml-python`
  (PyO3/Maturin) and `rill-ml-wasm` (wasm-bindgen) — where contributions
  should go.
- Domain-specific features (battery, mouse, HID, Tauri, plugin protocols).
- Copy-paste ports of River modules without independent verification.
- A CLI named `rill`.

These will be politely closed.

## 12. Getting help

Open a GitHub issue with the `question` label, or start a discussion in the
relevant issue thread. Be specific: include the Rust version, the RillML
version, a minimal reproducible example, and the expected vs. actual behavior.