Contributions are welcome! Whether it's a bug fix, new feature, documentation improvement, or test — we appreciate the help. By participating you agree to our Code of Conduct.
(CLAUDE.md at the repository root is context for AI coding agents working in this repo — human contributors can ignore it.)
Prerequisites:
- Rust 1.98+ (Orion uses the 2024 edition; MSRV is 1.98) — and the MSRV now tracks Wasmtime's (stable minus two), which the plugin sandbox depends on; a Wasmtime upgrade may move it
- SQLite (bundled — no separate install needed)
Clone and build:
git clone https://github.com/GoPlasmatic/Orion.git
cd Orion
cargo build
cargo testIf all tests pass, you're ready to go.
cargo build # Build (all capabilities compiled in — no feature flags)
cargo build --release # Release build
cargo test # Run the server suite (default-members)
cargo test --workspace # Server + CLI suites — what CI runs
cargo test <test_name> # Run a single test by name
cargo test --test integration # Run the consolidated integration test binary
cargo clippy --workspace --all-targets # Lint (matches CI)
cargo fmt --all # Format codeRun cargo clippy and cargo fmt before committing — both must pass cleanly.
The repo is a cargo workspace of five crates: crates/orion-server (the
runtime), crates/orion-cli (the CLI — see its own CLAUDE.md), two shared
library crates — crates/orion-api (the wire contract) and
crates/orion-client (the HTTP transport) — and crates/orion-plugin-sdk
(the guest crate a plugin author links against; it depends on nothing else
here). The end-to-end suite that drives the server with the CLI lives at the
repo root (tests/e2e/). Bare cargo commands target the server via
default-members.
crates/orion-server/
src/
main.rs # Binary entrypoint (thin wrapper over cli.rs)
lib.rs # Public module declarations
bootstrap.rs # Startup sequence: config -> pools -> repos -> engine -> server
cli.rs # CLI subcommands (migrate, lint, dry-run, test, preflight, ...)
preflight.rs # `orion-server preflight`: scan stored entities for 1.0 breaks
package_cli.rs # `orion-server package`: export/lint/plan/apply/diff promotion CLI
channel/ # Channel registry, config, routing, deduplication, auth guards
cluster/ # Cluster mode: config-epoch watcher, background-job leases
cron/ # Scheduled channels: reconciler, workers, occurrence ledger
config/ # Configuration loading (TOML + ORION_SECTION__KEY env overrides)
connector/ # Connector types, registry, circuit breakers, pool caching
definitions/ # Definition sets: the JSON front end, analysis, fmt, clippy, compile
engine/ # Dataflow engine & custom function handlers (functions/)
errors.rs # OrionError enum -> HTTP response mapping
jwt/ # Shared JWT core: verify, sign, JWKS cache
kafka/ # Kafka producer & consumer
metrics.rs # Prometheus metrics collection
plugin/ # WebAssembly plugins: Wasmtime sandbox, manifest, handler, limits
query/ # Portable data dialect (data_query / data_write -> SQL/Mongo/ES)
queue/ # Async trace processing, DLQ retry
runtime/ # The published generation (engine + channel estate), reload, task supervisor
server/ # Axum routes (routes/), middleware, AppState
storage/ # Database abstraction, models, repositories (repositories/)
validation/ # Input validation, SSRF protection
tests/
integration/ # Consolidated integration test binary (main.rs + *_test.rs modules)
integration/common/ # Test helpers (test_app, json_request, body_json)
cluster/ # Multi-node cluster suite (container-gated, #[ignore])
schema_parity.rs # Cross-backend schema comparison (container-gated)
storage_postgres.rs # PostgreSQL repository suite (container-gated)
storage_mysql.rs # MySQL repository suite (container-gated)
metrics_exposition.rs # Rendered /metrics assertions (own process — the recorder is global)
benchmark/ # bench.sh + fixtures (hey-based performance scenarios)
migrations/ # SQLite / Postgres / MySQL migrations (embedded at compile time)
crates/orion-cli/
src/commands/ # clap subcommands (one file per command group)
crates/orion-api/ # Shared wire contract (DTOs, enums, error envelope)
crates/orion-client/ # Shared HTTP transport (OrionClient, endpoint paths)
crates/orion-plugin-sdk/ # Guest SDK for plugin authors (WIT bindings, export macro)
tests/e2e/ # Shell end-to-end suites: orion-cli against orion-server (`just e2e`)
examples/ # Deployable packages, plugin sources, offline workflow tests, e2e use cases
deploy/ # Helm chart (helm/orion), HA compose drill (ha/)
docs/ # mdBook documentation (published to docs.goplasmatic.io)
Issues labelled good first issue
are scoped to be finishable without knowing the whole codebase, and
help wanted
marks work we'd love a hand with. Not sure where something lives? Ask in
Discussions and a
maintainer will point you at the right module. Documentation fixes are real
contributions — the book under docs/src/ ships with the same review bar as
code.
- Fork the repository and create a branch from
main - Make your changes — keep commits focused and atomic
- Write tests for new functionality
- Run the checks. With
justinstalled, one command runs the full CI-equivalent gate — fmt, clippy with-D warnings, tests, doc-tests and rustdoc:Withoutjust check
just, the equivalent is the four commands it wraps. Note the--workspaceflags: barecargo clippy/cargo testonly cover the server, becausedefault-memberspoints at it, so they silently skiporion-cli— which CI does not.Thecargo fmt --all --check cargo clippy --workspace --all-targets -- -D warnings cargo test --workspace cargo test --doc
justfileat the repo root also carriesjust test-containers,just openapi,just docsandjust e2e. If you changed the HTTP API (routes or request/response schemas), regenerate the checked-in OpenAPI spec — a test fails if it's stale:cargo run -- dump-openapi > docs/openapi.json - Submit a pull request with a clear description of what changed and why
The shipped migration files are checksum-frozen once released (sqlx records
a checksum per applied migration), so never edit an existing NNN_*.sql — add
a new numbered file to each of
crates/orion-server/migrations/{sqlite,postgres,mysql}/.
To bootstrap the migration set for a new backend, copy the newest existing
backend's 001_initial.sql and adapt the dialect by hand (types, triggers,
view syntax) — that is how the shipped sets were produced; there is no
generator.
The three sequences are independent. Each backend has its own directory and
its own numbering, and a change that applies to only two of them advances only
those two — so the same number means different things per backend (004 is
cluster_coordination on SQLite, bigint_columns on PostgreSQL,
active_immutability on MySQL). Never assume the numbers line up, and name
migrations rather than number them in commit messages, runbooks and docs.
orion-server migrate --dry-run prints backend, number and name together for
exactly this reason. crates/orion-server/tests/schema_parity.rs is what catches a change applied
to two backends out of three.
Write migrations expand/contract style: during a rolling deploy, old and
new binaries briefly share one database, so a release may only add schema
(columns, tables, indexes) alongside code that tolerates both shapes; drop or
rename the old shape in a later release, once no running replica depends on
it. Cluster deployments run orion-server migrate as a deploy step
(storage.auto_migrate = false), and replicas refuse to boot on a pending
migration.
The full map of the test estate — every suite, what it covers, what it
needs, and which CI job runs it — is TESTING.md. The layers
you will touch most often:
Integration tests use an in-memory SQLite database and the full Axum router — no running server needed. New test files go in tests/integration/ and are declared as modules in tests/integration/main.rs. The test helpers in tests/integration/common/mod.rs provide:
test_app()— creates a ready-to-useRouterwith in-memory DB, repos, and enginejson_request(method, uri, body)— builds an HTTPRequest<Body>with JSON content-typebody_json(response)— extracts and parses the response body asserde_json::Value
Example pattern:
#[tokio::test]
async fn test_my_feature() {
let app = common::test_app().await;
let req = json_request("POST", "/api/v1/admin/workflows", Some(json!({
"workflow_id": "test-workflow",
"name": "Test Workflow",
"condition": true,
"tasks": []
})));
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
// Responses are wrapped in a `data` envelope; new workflows start as drafts.
let body = body_json(response).await;
assert_eq!(body["data"]["name"], "Test Workflow");
assert_eq!(body["data"]["status"], "draft");
}Add unit tests inline in the relevant module using #[cfg(test)] blocks. See src/config/mod.rs or src/errors.rs for examples.
Tests that need a real backend (PostgreSQL, MySQL, MongoDB, Elasticsearch,
Kafka, Redis, or a multi-node cluster) are #[ignore]d, so cargo test skips
them locally. CI runs every one of them; run them yourself with Docker up
(tests/README.md explains the six-binary layout, why the
backend is pinned per process, and how the CI filters are kept drift-free):
cargo test --test storage_postgres -- --ignored # testcontainers spin up the DB
cargo test --test cluster -- --ignored # the 14-contract multi-node suite
cargo test --test integration -- --ignored <filter> # container-gated integration modulesThe #[ignore] → CI-filter mapping is self-enforcing
(tests/integration/ci_filter_drift_test.rs): a container-gated module missing
from CI's name filters fails the build, in both directions.
tests/e2e/ at the repo root drives a real orion-server binary over HTTP
with the orion-cli binary — both built from your tree — through 12 shell
suites. The last suite is data-driven: scenario cases in
examples/use-cases/ deploy the shipped example packages, and
runtime-behaviour cases live in tests/e2e/cases/. It needs jq and
curl; CI runs it on every PR (the cli-e2e job):
just e2e # or: ./tests/e2e/run.sh
./tests/e2e/run.sh 07 08 # a subset, by suite-number prefixCI also runs cargo test --doc and RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --lib — a documented example that stops compiling, or a broken
intra-doc link, fails a PR even though a plain cargo test --all-targets
never executes either.
cargo test test_my_feature # By test name
cargo test --test integration # Run the whole integration binary- Rust 2024 edition — the codebase uses let-chains (
if let Some(x) = a && let Some(y) = b) cargo fmt— all code must be formattedcargo clippy— all warnings must be resolved- Error handling — use
OrionErrorvariants fromsrc/errors.rsfor new error cases - Async — all repository traits use
async_trait; keep I/O operations async
Around 900 comments across src/ open with a short code — N10, R13, F35,
W8, S15. These are items from the pre-1.0 audits, and the code is the reason
the comment exists rather than a note about it. Resolve one with:
git log --grep=N10 # the commit that closed it, with the full rationaleThe IDs are a historical record — safe to ignore entirely when reading or
writing code. Chasing one needs the full history: on a shallow clone run
git fetch --unshallow first.
Every commit that closed an item names it in the message body, so the history is
the index. The proposal.md trackers that held open items are retired — both
audits closed out before the 1.0 tag, and the last four carried items live where
they are actioned: P12 and C13 in RELEASING.md's release procedure, P13's
revisit criterion in dist-workspace.toml, B6 an external listing process. An
ID's absence from the tree means the work is done (or externally tracked), not
that the reference is stale.
Two rules keep this readable. Each comment must stand on its own without the ID: the prefix points at the history for a reader who wants the argument, and is never the only place the reason is written. And when a comment's claim stops being true, correct the comment — the ID records why the code was written, not a promise that it still behaves that way.
New comments do not need an ID. The scheme is a record of how 1.0 was reached, not a convention to keep feeding.
Releases are tag-driven. Pushing a version tag runs three workflows —
release.yml (binaries, installers, Homebrew), docker-release.yml
(multi-arch image, Helm chart, signing/attestation), and
crates-publish.yml (crates.io; skips prerelease tags) — and all three
gate on a successful CI run for the tagged commit (ci-gate), so a tag can
never outrun a red build. Note that crates.io carries orion-server and the
rider crates only — orion-cli ships through the installers, the Homebrew
tap and GHCR, because its crates.io name belongs to an unrelated crate. The
secrets they need and the full procedure are in RELEASING.md.
- Version bump. All five crates share
workspace.package.versionin the rootCargo.toml;cargo release <level>rewrites it and the two[workspace.dependencies]requirements together, then tags. A barev-prefixed tag (v1.0.0) releases every package at that version, and lockstep is what keeps the rider crates (orion-api,orion-client,orion-plugin-sdk) publishable — their version always moves with a release, so the skip-if-present rider publish can never leaveorion-serverresolving older crates.io content. The Helm chart needs no manual bump —docker-release.ymlstamps both the chart version andappVersionfrom the tag at publish time; the in-treeChart.yamlvalue is a development placeholder. - CHANGELOG cut. Fold
## [Unreleased]into a dated## [X.Y.Z] - YYYY-MM-DDheading (merge per category), leave an empty[Unreleased]on top, and check the compare links at the foot of the file name the new tag. - Tag and push. Push the tag by its full
refs/tags/name. Releases are cut frommainand no release branch exists today, but the form is unambiguous whatever a branch is called — and a branch named after its version, which is how thev1.0.0branch worked, makes a baregit push origin vX.Y.Zfail as an ambiguous refspec.git tag vX.Y.Z && git push origin refs/tags/vX.Y.Z - Watch the release land.
release.ymlbuilds each dist target (macOS/Windows are pre-proven per PR bycross-os-build.yml, but the release build is the real one) and publishes the GitHub Release;docker-release.ymlbuilds per-platform images, merges the manifest, then signs and attests the merged manifest digest and pushes the chart tooci://ghcr.io/goplasmatic/charts. - Verify what shipped. The merge job's final step already runs
cosign verifyandgh attestation verifyagainst the published tag and fails the release if nothing verifiable landed — confirm it ran green rather than assuming.dry_rundispatches skip the merge job entirely, so a scratch tag is the only true rehearsal of this half.
Expand/contract migration discipline across releases is described under Database migrations — a release may only add schema alongside code that tolerates both shapes.
By contributing, you agree that your contributions will be licensed under the Apache-2.0 License.