This document describes the steps required to cut a tagged release of RIVR. Follow every section in order; do not skip steps on "minor" releases.
# All C test suites (acceptance, recovery, replay, dutycycle, policy, OTA)
make -C tests test
# AddressSanitizer / UBSanitizer pass
make -C tests asan
# Rust unit tests
cargo test --workspaceAll passes must be 0 FAIL. Any failure blocks the release.
make -C tests size_checkThe command compares the sizes of all test binaries against the stored
baselines in tests/baselines/host_sizes.txt. Any binary that grew by more than
512 bytes fails the gate. To update baselines after an intentional change:
make -C tests update_baselines
git add tests/baselines/host_sizes.txt
git commit -m "chore: update size baselines for vX.Y.Z"make -C tests determinism_checkRebuilds each test binary twice and verifies that the SHA-256 hashes are identical. Any mismatch indicates a non-deterministic build input (timestamp macro, random padding, etc.) and must be resolved before releasing.
make -C tests variant_checkVerifies that every board variant in variants/ compiles cleanly. This
catches config-header-only breakage that the main build might miss.
Run the coverage-guided fuzz targets for at least 30 minutes each before a major release:
# Rust fuzz targets
cargo +nightly fuzz run dsl_parse -- -max_total_time=1800
cargo +nightly fuzz run compiler -- -max_total_time=1800
cargo +nightly fuzz run ffi_entrypoints -- -max_total_time=1800
# C protocol + routing harness (AFL++)
make -C tests fuzz_harness
afl-fuzz -i tests/fuzz_seeds -o /tmp/fuzz_out -- tests/fuzz_ffi_harness @@Any crash that is not already in the known-corpus must be fixed before the release tag is created.
Update the version in each location below (search for RIVR_VERSION or
the hard-coded string):
| File | Symbol / key | Notes |
|---|---|---|
firmware_core/main.c |
RIVR_VERSION_STR |
Shown on OLED boot screen |
rivr_core/Cargo.toml |
version = "..." |
Rust crate version |
rivr_host/Cargo.toml |
version = "..." |
Host tools version |
Cargo.toml (workspace) |
version = "..." if present |
Workspace root |
tools/vscode-rivr/package.json |
"version": "..." |
VS Code extension |
Add an entry at the top of CHANGELOG.md (create if absent):
## vX.Y.Z — YYYY-MM-DD
### Added
- …
### Changed
- …
### Fixed
- …git add -u
git commit -m "chore(release): bump version to vX.Y.Z"Verify the active signing keys in firmware_core/rivr_pubkey.h:
#define RIVR_OTA_KEY_COUNT 2u
static const uint8_t RIVR_OTA_KEYS[RIVR_OTA_KEY_COUNT][32] = { … };Ensure at least one key remains valid after any planned rotation.
The firmware verifies key_id < RIVR_OTA_KEY_COUNT, so adding a new key
before retiring the old one is safe.
# Build a program payload:
# sig[64] | key_id[1] | seq[4] | program_text
#
# Use the rivr_sign tool (or any Ed25519 implementation):
# rivr_sign --key key0.pem --key-id 0 --seq <NEXT_SEQ> program.rivr > payload.bin
# Verify the payload before pushing:
# ./tools/rivr_decode payload.bin | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['ota']['key_id']==0"The sequence number (seq) must be strictly greater than the last
accepted sequence stored in device NVS. There is no recovery for a
skipped sequence — only a firmware reflash can reset the counter.
After the new program pushes successfully, the device sets ota_pending=1.
Ensure the newly-running RIVR program calls (or causes the host app to call)
rivr_ota_confirm() within the first boot. If the device reboots before
confirmation, the previous program is restored from NVS.
Build all four production environments and capture the .bin artefacts:
~/.platformio/penv/bin/pio run -e client_esp32devkit_e22_900
~/.platformio/penv/bin/pio run -e repeater_esp32devkit_e22_900
~/.platformio/penv/bin/pio run -e client_lilygo_lora32_v21
~/.platformio/penv/bin/pio run -e repeater_lilygo_lora32_v21Confirm build outputs are present and sizes are within expected bounds:
| Environment | Binary | Max size |
|---|---|---|
client_esp32devkit_e22_900 |
.pio/build/.../firmware.bin |
1 536 KB |
repeater_esp32devkit_e22_900 |
.pio/build/.../firmware.bin |
1 536 KB |
client_lilygo_lora32_v21 |
.pio/build/.../firmware.bin |
1 536 KB |
repeater_lilygo_lora32_v21 |
.pio/build/.../firmware.bin |
1 536 KB |
# release build
cargo build --release -p rivr_host
# verify the compiler CLI works
cargo run --release -p rivr_host --bin rivrc -- --help
# verify the decoder tool builds
make -C tools
./tools/rivr_decode --helpgit tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin main --tagsThe CI pipeline (.github/workflows/ci.yml) will run all gates again on
the tag commit. The release is considered stable only when all CI jobs pass
on the tag.
- Draft a new Release on GitHub, targeting the new tag.
- Copy the
CHANGELOG.mdentry for this version into the release body. - Attach the four firmware
.binartefacts. - Attach the
rivrchost tool binary (Linux x86_64 static build if available). - Publish.
- Update
docs/en/overview.mdversion badge / "latest release" line. - Announce on the project forum / mailing list.
- Open a new milestone for the next release cycle.
- Rotate signing keys if this release introduced new hardware keys (append new key first, deploy, then remove old key in the next release).