---
title: "Contributing: build, test and change the broker"
description: "Build the broker and the clients, run the test suites and the tutorial programs, and the rules a change follows: docs, changelog and the checks CI runs."
---

> Queen MQ documentation, for AI agents
> Complete self-contained summary of Queen MQ: https://queenmq.com/llms-brief.txt
> Fetch that first when the question is about the product rather than about this page.
> Index of all pages: https://queenmq.com/llms.txt

# Contributing: build, test and change the broker

Queen is one repository with one Rust binary at its center: the broker, which links the proxy and
the Kafka facade as libraries and embeds the dashboard. It keeps its state in a data directory, so
building it, testing it and running it needs no database and no other service. A clean clone and a
Rust toolchain are enough to build a node and run it, and that is a property we work to keep.

Pull requests target `master`. Run the suite of the component you touched, and read the rules
below before you change the engine: each one is there because breaking it is silent until it is
expensive.

**Why:** a broker is trusted with data people cannot afford to lose. Rules that a machine checks (a lint, a golden file, a docs gate) are the ones that still hold a year later.

Every commit carries a `Signed-off-by:` line from its author, which `git commit -s` adds. It
certifies the [Developer Certificate of Origin](https://developercertificate.org): the author wrote
the change, or has the right to submit it under the Apache 2.0 license. `.github/workflows/dco.yml`
refuses a pull request with a commit that lacks the line, because the project can license only code
its contributors had the right to give. How the project is run and who runs it are in
`GOVERNANCE.md` and `MAINTAINERS.md`.

## The repository

| Path | What it is |
|---|---|
| `server/` | The broker: crate `queen-engine`, binary `queen`. `src/rsm/` is the engine, `src/handlers/` the HTTP edge. |
| `clients/` | The SDKs (`client-js`, `client-py`, `client-go`, `client-rust`, `client-php`, `client-cpp`) and `client-cli` (`queenctl`). |
| `protocols/queen-kafka/` | The Kafka wire facade, linked by the `kafka` feature. |
| `proxy/` | The multi-tenant proxy, linked by the `server` feature; `proxy/console/` is its operator console. |
| `app/` | The dashboard (Vue and Vite). It builds into `server/webapp/dist`, which the binary embeds. |
| `shared/` | JavaScript time-range presets and conversions used by the dashboard and proxy console. |
| `crates/queen-protocol/` | Shared wire types for the Rust client and the embedded library API. |
| `test/` | `run.sh` (the Docker test matrix), `jepsen/`, `recovery/` (failure rehearsals), `raft/` (the determinism lint and harnesses). |
| `benchmark-queen/` | Archived benchmark sessions: harness, raw output, results. |
| `webdoc/` | This site. |
| `book/` | The little book of QueenMQ, in LaTeX. |
| `figures/` | The drawings of the book and the site, one module each. |
| `examples/tutorials/` | The programs behind every piece of code in the book and on the site, in six languages. |

## Build and run

The committed `server/webapp/dist` and `proxy/console/dist` let a clean clone compile without
Node. When you change `app/`, rebuild the dashboard and then the broker, because the assets are
embedded at compile time:

```sh
npm --prefix app ci && npm --prefix app run build
cargo build --release --manifest-path server/Cargo.toml
```

When changing `shared/timeRanges.mjs`, rebuild both frontends before the broker:
run `npm --prefix proxy/console ci && npm --prefix proxy/console run build` alongside
the dashboard build above. The common hourly presets feed every picker; extended
periods, system metrics, lag thresholds and console usage add their choices in the
same module. The Dockerfile copies `shared/` into both frontend build stages.

Run one node on a throwaway data directory and check it:

```sh
QUEEN_RAFT_DIR=./queen-data ./server/target/release/queen
curl -s localhost:6632/health
```

`/health` answers `503` with `"status":"settling"` until the node knows a leader that hears from a
majority and has caught up,
then `200`. Delete the directory to start empty. The version comes from `server/server.json`
(`server/build.rs` embeds it), the minimum Rust version is 1.88 (`rust-version` in
`server/Cargo.toml`), and `./build.sh` builds the product image from the root `Dockerfile`.

## Test

| Command | What it runs |
|---|---|
| `cargo test --manifest-path server/Cargo.toml --locked` | The broker's in-process tests: engine, crash, recovery and cluster tests under `server/src/rsm/tests/`. |
| `test/run.sh` | Every client suite against a fresh single node, with the tenancy header off and on (`single`, `tenanted`), in Docker. |
| `test/run.sh --suite js --topo single,ha` | The same suite against a three-node openraft cluster as well. |
| `test/run.sh --suite http` | Every KV and timer route at the raw wire, with no SDK in between. |
| `test/raft/lint/deny-bites.sh` | Proves the determinism lint still refuses a clock, an environment read and a random number. |

`test/run.sh` builds the broker image as `queen:test`, gives every suite and topology its own
Compose project with fresh volumes, and prints a pass/fail matrix. The tenancy parity gate fails
the run when a suite's `single` and `tenanted` results differ, because the tenancy header must
change nothing for a client that does not send it. CI runs the same matrix
(`.github/workflows/tests.yml`).

The Jepsen suite is in `test/jepsen/` (Clojure and Leiningen, five nodes over SSH): `run-matrix.sh`
runs one test per line of a matrix file from the control node. The failure rehearsals behind the
recovery runbook are in `test/recovery/`. Neither runs in CI.

## Rules for engine changes

**Apply is pure.** `rsm/apply.rs`, `rsm/state/` and `rsm/store/` may not read a clock, the
environment or a random number: `server/clippy.toml` lists those calls, and the three modules deny
them. Time and ids come from the planner, inside the entry. This is the rule every replica's
correctness rests on, so after you change the list, run `test/raft/lint/deny-bites.sh`, which
plants one call of each kind in `rsm/apply.rs` and requires clippy to refuse every one.

**Bytes on disk are versioned.** Effects and outcomes share one catalogue version. A new field is a
new version decoded beside the old one, never an edit of an existing encoder, and the golden
fixtures in `server/src/rsm/tests/golden/` fail when an encoding changes. A cluster raises its
version only once every member can read it, which is what lets a rolling upgrade run nodes of two
releases side by side.

**Nothing blocks the async runtime.** Every I/O and RPC has a deadline, store and file calls run on
blocking threads, and no `std::sync::Mutex` is held across an `.await`. A blocking call on a
runtime thread stalls every request scheduled behind it.

## Rules for the docs

**`sourceOfTruth` is a debt list.** Each page names the files that govern it, and
`check-sourceoftruth.mjs` fails a page whose last commit is older than the last commit of any of
them. Update the page in the same change as the code.

**The site follows the book.** The little book of QueenMQ (`book/`, LaTeX, built by `book/build.sh`) and these pages tell the same story in the same order. Change one, change the other. `book/build.sh --publish` puts a new edition on the site: the PDF, its cover and `edition.json` in `webdoc/public/book/`, which [the book page](/book/) reads.

**Code on a page ran.** A page shows code with `<SdkCode id="push" />`: the `guide:start(push)` region of each language's programs under `examples/tutorials/` (`00-five-minutes`, `book-client-guide`, `book-use-cases`), cut by `webdoc/scripts/lib/guide-regions.mjs`, the same cutter the book, the homepage and the README check use. `examples/tutorials/run.sh` runs every program against a broker, and each program checks its own results. A region JavaScript lacks fails the build; a region another language cannot express is listed, with its reason, in `webdoc/src/lib/book-code.ts`. Keep region lines to 92 characters, so they fit the book's page.

**Drawings have one source.** Each figure is a module in `figures/src/<chapter>/<id>.mjs`; `node figures/build.mjs` writes a light and a dark SVG, the book prints them as PDFs, and the site shows them with `<Fig id="..." />`. Every figure is 1000 units wide, so they all print at the same type size.

**Older snippets.** The reference pages still include regions of the client test suites (`docs:start(<id>)`), turned into partials by `webdoc/scripts/gen-snippets.mjs`.

**Generated partials are not edited by hand.** The route tables, the environment variables and the
metric families are read out of the Rust by `webdoc/scripts/gen-*.mjs`; run `pnpm --dir webdoc gen`.
Where a generator mirrors Rust logic in JavaScript (the access levels, the proxy's route classes),
it guards the mirror with a fingerprint of the Rust function and refuses to run when the function
changes, until someone re-reads it.

**One gate.** `pnpm --dir webdoc verify` runs the generator drift check, the build, the markdown
and brief checks, the linter, the prose check, the `sourceOfTruth` check, the search-surface check
and the README check.

## Rules for benchmarks

**A number is published only with an archived artifact under `benchmark-queen/`**: the harness, the
raw output and the conditions (hardware, node count, shape, duration). The site's benchmark figures
are rendered from those artifacts by `webdoc/scripts/charts.py`, and the drift check fails when a
figure no longer matches its data. A number without an artifact is a claim nobody can check, so it
stays out.

Source: https://queenmq.com/architecture/contributing/index.mdx
