Contributing¶
Development environment, checks and the steps to send a change. All contributors follow the Code of Conduct.
Workflow¶
- Open an issue to discuss a significant change first.
- Fork and branch from
main. - Implement with tests, see Tests.
- Open a pull request against
main.
Licensing¶
Gradient is AGPL-3.0-only. Contributions are released under the same license. All files carry the SPDX header below.
// SPDX-FileCopyrightText: 2026 Wavelens GmbH <info@wavelens.io>
//
// SPDX-License-Identifier: AGPL-3.0-only
Development Setup¶
Requirements: Nix with flakes.
- An interactive NixOS test driver.
run_tests()will boot a VM with a packaged server, PostgreSQL 18 forwarded to the host port, a usertest(passwordpassword) and a project.cargo runinbackend/can then work against that database.
backend/.cargo/config.toml will cap parallel rustc jobs at 1 ([build] jobs = 1) to bound peak memory. cargo build -j N can override the cap.
Checks¶
| Check | Command |
|---|---|
| Backend tests | nix build .#checks.x86_64-linux.unittest -L |
| CLI tests | nix build .#checks.x86_64-linux.cli-unittest -L |
| Clippy | nix build .#checks.x86_64-linux.clippy -L, ...cli-clippy -L, ...cli-static-clippy -L (the CLI without Nix support) |
| VM tests | nix build .#checks.x86_64-linux.gradient-<name> -L for api, cluster, deploy, e2e, eval, local-worker, scheduler, standalone, standalone-docker |
| Format | cargo fmt --all --check, in backend/ and cli/ |
| Licenses and advisories | cargo deny check, in backend/ and cli/. GPL-family dependencies are banned |
CI (.github/workflows/rust.yml) will check fmt, the #[allow] grep check and cargo-deny over both workspaces. Clippy is part of the flake checks.
Rust¶
cargo fmtbefore committing. Toolchain versions come from the devShell (flake.lock), mirrored inrust-toolchain.tomlfor rustup users.- The
style_edition = "2024"setting is part of therustfmt.tomlfile. - Workspaces each carry their own
deny.toml,clippy.tomlandrustfmt.toml, kept in step. - The
tokio::spawnand rawStatementbans are part of the backendclippy.tomlfile. - No
unwrap()in production paths (clippy::unwrap_used = "deny"). The alternatives are?, an explicit error branch, or.expect("<the invariant>")where the call cannot fail by construction. - Shared state must use
gradient_util::sync::Mutexinstead ofstd::sync::Mutex. Poisoning is ignored, and one panicking critical section cannot break every laterlock()call. - Logging must go through
tracing(info,debug,warn,error) and never throughprintln!macros. #[instrument]on significant async functions.
| Change | Also update |
|---|---|
New endpoint in backend/gradient-web/src/endpoints/ |
#[utoipa::path] on the handler and its route in backend/gradient-web/src/routes.rs. Handlers extract parameters, check authorization, query and respond |
| New table | A migration in backend/gradient-migration/src/, an entity in backend/gradient-entity/src/, see Migrations |
| Configuration option | A field in backend/gradient-types/src/cli/, or in backend/gradient-worker/src/config/options.rs for the worker, then the generated files below |
The handler annotations generate docs/gradient-api.yaml and the frontend types. Regenerate both files with these commands.
cargo run --manifest-path backend/Cargo.toml --features api-spec --bin gradient-api-spec > docs/gradient-api.yaml
cargo run --manifest-path backend/Cargo.toml --features api-spec --bin gradient-api-spec -- typescript > frontend/src/app/core/models/api.generated.ts
Flag, environment variable, NixOS option and the row in the Configuration reference derive from the field and its doc comment. Three commands refresh the generated files.
cargo run --manifest-path backend/Cargo.toml -p gradient-types --example options > backend/gradient-types/options.json
cargo run --manifest-path backend/Cargo.toml -p gradient-worker --example options > backend/gradient-worker/options.json
nix eval --json --file docs/tools/options.nix | python3 docs/tools/options.py > docs/generated/configuration-options.md
#[allow] Policy¶
#[allow(unused_imports)],#[allow(unused)]and#[allow(dead_code)]are forbidden (CI grep check). Contributors fix the warning instead.- Every other
#[allow(...)]must carryreason = "..."(clippy::allow_attributes_without_reason). - Uses of
#[allow(clippy::too_many_arguments)]are temporary, tracked in #503. allow-unwrap-in-testscan only reach code inside a#[test]function.- Integration tests and
gradient-test-supportuse a crate-level#![expect(clippy::unwrap_used, reason = "...")]attribute. - The
expectattribute will warn once the lastunwrap()call is gone.
Angular and TypeScript¶
- Standalone components with signals (
signal(),computed()), feature folders underfrontend/src/app/features/in the app. - UI components from
gr-ui(@gradient/ui/ui, on@angular/cdk), charts through<gr-metric-chart>(Apache ECharts), colours and spacing from the@gradient/ui/styles/variablesmodule. See the Frontend Style Guide. - No UI or chart dependency with a field-of-use restriction. The bundle is distributed under AGPL-3.0. A dependency licensed beyond MIT, BSD or Apache-2.0 cannot be passed on.
- A refreshed
pnpm-lock.yamlwill change thepnpmDepshash innix/packages/gradient-frontend.nix. The new hash is obtainable by settinglib.fakeHash, runningnix build .#gradient-frontend.pnpmDepsand taking the reported hash. minimumReleaseAgeinpnpm-workspace.yamlwill refuse packages younger than 24 hours. A localpnpm updatewill then resolve the same versions the Nix build would accept.
Nix¶
- Packages and modules live in
nix/: server options innix/modules/gradient.nix, worker options innix/modules/gradient-worker.nix, declarative state innix/modules/gradient-state.nix. - New modules need a NixOS VM test under
nix/tests/gradient/.