Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/no-std.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: no-std

# The crates that build without std, built where there is none, and et-ws-server as Raspberry Pi Pico 2 W firmware.
# One Linux job: a bare-metal target compiles the same on every host, so another platform would only repeat it. The
# board is not here, so the firmware is built and linted, not run; nothing is uploaded or published.
#
# Not path-gated: what breaks a no_std build is a dependency or a feature gaining std somewhere in the tree, which a
# path list would have to name every crate to catch.
"on":
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

defaults:
run:
shell: bash --noprofile --norc -euo pipefail {0}

env:
MISE_ENV: dart,dotnet,java,js,kotlin,python,r,rust,zig

jobs:
no-std:
runs-on: ubuntu-latest
# Read-only pull of the maintainer-published ghcr.io mise-tools store (install-mise-tools restore step).
# Never write: the stores are published only from a maintainer's machine via `mise run push-mise-tools`.
permissions:
contents: read
packages: read
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false

- name: Show MISE_ENV
run: echo "MISE_ENV=$MISE_ENV"

- name: Install mise + tools
uses: $/.github/actions/install-mise-tools
timeout-minutes: 60
with:
github-token: ${{ github.token }}

- name: Build the no_std crates for a bare-metal target
timeout-minutes: 20
run: mise run build-no-std

- name: Test the no_std crates built without std
timeout-minutes: 20
run: mise run test-no-std

- name: Fetch the Pico 2 W radio's firmware blobs
timeout-minutes: 5
run: mise run fetch-cyw43-firmware-rclone

# The firmware bakes in the network it joins, so CI builds it for one that does not exist.
- name: Build and lint the Pico 2 W firmware
timeout-minutes: 30
env:
CYW43_FIRMWARE_DIR: ${{ github.workspace }}/target/cyw43-firmware
WIFI_NETWORK: edge-toolkit-ci
WIFI_PASSWORD: not-a-real-passphrase
run: |
mise run build-firmware-pico2w
mise run cargo-clippy-check-pico2w
69 changes: 69 additions & 0 deletions .mise/config.rust.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,65 @@
# The rest of the Rust toolchain + tasks (the `rust` tool itself, cargo-*, the universal repo-wide checks,
# ws-server, the e2e tests) still live in the default config; moving those out is a later pass.

# et-ws-server as Raspberry Pi Pico 2 W firmware: an ELF that picotool or probe-rs loads onto the board.
# Its build reads the radio's blobs from CYW43_FIRMWARE_DIR, and the network to join from WIFI_NETWORK and
# WIFI_PASSWORD; its name stays outside `build-modules`'s `build-ws-*` glob, since that has none of them.
[tasks.build-firmware-pico2w]
description = "Build et-ws-server as Raspberry Pi Pico 2 W firmware"
run = "cargo build --release -p et-int-ws-server-pico2w --target thumbv8m.main-none-eabihf"

# The firmware's body compiles only for the board, so the workspace's host clippy never reaches it; this does.
# It needs what build-firmware-pico2w needs.
[tasks.cargo-clippy-check-pico2w]
description = "Lint the Raspberry Pi Pico 2 W firmware for its own target"
run = "cargo clippy --release -p et-int-ws-server-pico2w --target thumbv8m.main-none-eabihf"

# The Pico 2 W radio's firmware, CLM and NVRAM, from embassy's cyw43-firmware directory at the pinned commit.
# They land in the gitignored target/cyw43-firmware, for build-firmware-pico2w to read as CYW43_FIRMWARE_DIR, and
# each is checked against the SHA256 config/upstream-cache/data.toml records for it.
[tasks.fetch-cyw43-firmware-rclone]
description = "Fetch the Pico 2 W radio's firmware blobs into target/cyw43-firmware"
run = """
set -- 43439A0.bin '{{ vars.cyw43_firmware_asset }}' 43439A0_clm.bin '{{ vars.cyw43_clm_asset }}'
set -- "$@" nvram_rp2040.bin '{{ vars.cyw43_nvram_asset }}'
while [ "$#" -gt 0 ]; do
file=$1
export asset=$2
shift 2
dst="{{ vars.cyw43_dir }}/$file"
# shellcheck disable=SC2086
rclone copyto "{{ vars.cyw43_repo }}/$file" "$dst" {{ vars.gh_http }}
sha=$(mise exec -- yq -p toml -oy '.asset[strenv(asset)].sha256' config/upstream-cache/data.toml)
actual=$(mise exec -- coreutils sha256sum -b "$dst" | mise exec -- coreutils cut -c1-64)
[ "$actual" = "$sha" ] || {
echo "cyw43 checksum mismatch for $file: expected=$sha actual=$actual" >&2
exit 1
}
done
"""
shell = "{{ vars.task_shell }}"

# The crates that build without std, built for the Pico 2 W's bare-metal target, which has no std to fall back on.
# A host build of the same features can still link std through a dependency without anyone noticing; this cannot.
[tasks.build-no-std]
description = "Build the no_std crates for a bare-metal target"
run = """
features=et-ws-service/picoserve-hub,et-ws-server/picoserve-server
set -- -p et-org -p edge-toolkit -p et-ws-service -p et-ws-server
cargo build --lib --target thumbv8m.main-none-eabihf --no-default-features --features "$features" "$@"
"""
shell = "{{ vars.task_shell }}"

# The tests of the no_std crates as they build without std, which runs only the test files that need none.
[tasks.test-no-std]
description = "Test the no_std crates built without std"
run = """
features=et-ws-service/picoserve-hub,et-ws-server/picoserve-server
set -- -p edge-toolkit -p et-ws-service -p et-ws-server
cargo nextest run --config-file config/nextest.toml --no-default-features --features "$features" "$@"
"""
shell = "{{ vars.task_shell }}"

# Browser wasm-pack builds are coverage-instrumented when ET_TEST_COVERAGE is set.
# The web_cov_wrapper RUSTC_WORKSPACE_WRAPPER (built by build-wasm-cov-wrapper) instruments only workspace
# crates -- never dependency cdylibs such as wasm-streams that don't link minicov's
Expand Down Expand Up @@ -155,3 +214,13 @@ coreutils cp "target/wasm32-wasip2/release/$wasm" "services/ws-modules/wasi-comm
{{ vars.et_cli }} module-package-json --module-dir services/ws-modules/wasi-comm1
"""
shell = "{{ vars.task_shell }}"

[vars]
# The Pico 2 W radio's blobs, as fetch-cyw43-firmware-rclone fetches them.
# Each asset name carries embassy's short commit; config/upstream-cache/data.toml records the matching SHA256s, and
# checksums.rego cross-checks both sides.
cyw43_clm_asset = "cyw43-75f40b3-43439A0_clm.bin"
cyw43_dir = "target/cyw43-firmware"
cyw43_firmware_asset = "cyw43-75f40b3-43439A0.bin"
cyw43_nvram_asset = "cyw43-75f40b3-nvram_rp2040.bin"
cyw43_repo = ":http:embassy-rs/embassy/raw/75f40b383a14/cyw43-firmware"
2 changes: 1 addition & 1 deletion .mise/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ zizmor = "latest"
components = "clippy,rust-analyzer"
os = ["linux", "macos"]
profile = "minimal"
targets = "wasm32-unknown-unknown,wasm32-wasip2"
targets = "thumbv8m.main-none-eabihf,wasm32-unknown-unknown,wasm32-wasip2"
# Use `stable`, not `latest`, so every lane resolves through the same rustup channel.
# mise's `latest` picks the max of rust-lang/rust's GitHub Releases, which are published by separate
# automation that lags the `stable` rustup channel the Windows [[tools.rust]] entries resolve through. That
Expand Down
2 changes: 1 addition & 1 deletion .mise/config.windows.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ url = "https://github.com/edge-toolkit/core/releases/download/gnupg-w32-v1/2.5.2
[[tools.rust]]
components = "clippy,rust-analyzer"
profile = "minimal"
targets = "wasm32-unknown-unknown,wasm32-wasip2"
targets = "thumbv8m.main-none-eabihf,wasm32-unknown-unknown,wasm32-wasip2"
version = "stable-{{ vars.win_rust_host }}"

[[tools.rust]]
Expand Down
2 changes: 1 addition & 1 deletion .mise/mise.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ Changelog 1.1.0.

### Added

- `edge-toolkit` builds as `no_std` + `alloc` with `default-features = false`, keeping the WebSocket protocol types
in `ws`, `ports`, the `BasicAuth` struct and the in-memory agent registry of `ws_server`. The new default `std`
feature restores everything else, the registry file included. `et-org` is `no_std` unconditionally.
- `et-ws-service` builds as a `no_std` + `alloc` hub with `default-features = false`: `hub::Link` routes each agent's
frames through the registry and hands back what to send where, leaving the transport, ids and clock to the
embedder. Its features `std` (the actix-web hub), `nats` (the backplane) and `otel` (spans and metrics) are
separate. Without `nats`, `WsConfig` has no `backplane`.
- `et-ws-service`'s `picoserve-hub` feature, on by default, adds `picoserve_hub::Hub`: a `no_std` hub serving
`GET /ws` over HTTP and WebSocket through picoserve on any socket the embedder accepts, such as an `embassy-net`
`TcpSocket`. It is a local hub, with no backplane, admin API, idle timeout or registry file. A crate awaiting
`Hub::serve` needs `#![recursion_limit = "256"]`.
- `et-ws-server` builds as `no_std` + `alloc` without its new `std` feature. Its `picoserve-server` feature, on by
default, adds `picoserve_server::serve`: the hub's `/ws` with `/health` and `/favicon.ico` beside it, over picoserve.
- The `et-ws-server-pico2w` firmware runs et-ws-server on a Raspberry Pi Pico 2 W, over the board's Wi-Fi.
`MISE_ENV=rust mise run build-firmware-pico2w` builds it from `WIFI_NETWORK`, `WIFI_PASSWORD` and a
`CYW43_FIRMWARE_DIR` holding the radio's firmware blobs.
- An opt-in `et-recovery-supervisor` service observes application progress and coordinates bounded Kubernetes recovery.
- The Python SDK provides recovery lifecycle messages and integrity-checked checkpoint storage helpers.
- A scenario can list `machines:` to span several hosts. The first is the server, running the hub and the collector,
Expand Down Expand Up @@ -62,6 +78,10 @@ Changelog 1.1.0.

### Changed

- **Breaking:** `et-ws-server`'s actix server, its binary and its environment config are behind the new default `std`
feature. A build with `default-features = false` and none of the features that imply `std` -- `modules`, `otlp`,
`qr`, `storage`, `tls`, `websockify` -- now gets the `no_std` library and no binary; add `std` to keep the server.

- The workspace requires actix-web 4.17.
- **Breaking:** the minimum supported Rust version is 1.99.0, up from 1.91.1, for every published crate.
- **Breaking:** an agent's IP address is always known. `AgentSummary::last_known_ip` and
Expand Down
21 changes: 14 additions & 7 deletions CHECKS.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Checks

- **Generated:** 2026-10-28
- **Checks:** 78
- **Generated:** 2026-10-30
- **Checks:** 79
- **Tools:** 47
- **Shell-script checks:** 10
- **Checks with a repair pass:** 21
- **Rules:** 359 custom local rules, or non-default strict linter settings
- **Rules:** 360 custom local rules, or non-default strict linter settings
- **Meta rules:** 20 of those rules are rules about rules

Every check is listed across every `MISE_ENV`, filed under the tool it runs. Each entry names the env whose config
Expand Down Expand Up @@ -155,12 +155,17 @@ Rules, from `config/ast-grep/rules`:
`et-ws-runner-common::config`) so every variable is declared in one typed place. Only these cargo-provided constants,
which have no serde-env equivalent, are exempt: - `CARGO_BIN_EXE_<name>` -- the built binary's path, injected by cargo
for an integration test. - `CARGO_PKG_VERSION` / `CARGO_PKG_NAME` -- crate identity baked into the binary. -
`ET_WIT_DIR` -- the WIT directory injected at build time for wit-bindgen. `CARGO_MANIFEST_DIR` is deliberately NOT
exempt -- use `edge_toolkit::config::get_project_root()` at runtime (see the `no-cargo-manifest-dir` rule).
`ET_WIT_DIR` -- the WIT directory injected at build time for wit-bindgen. - `OUT_DIR` -- the build script's output
directory, for `include!`-ing what it generated. Configuration a bare-metal binary bakes in is read there, by its
`build.rs`, and generated as typed constants. `CARGO_MANIFEST_DIR` is deliberately NOT exempt -- use
`edge_toolkit::config::get_project_root()` at runtime (see the `no-cargo-manifest-dir` rule).
- `no-env-macro-in-attribute` -- `env!` / `option_env!` inside an attribute are banned exactly as they are anywhere
else, with the same allowlist of cargo-provided constants (`CARGO_BIN_EXE_<name>`, `CARGO_PKG_VERSION`,
`CARGO_PKG_NAME`, `ET_WIT_DIR`). Read configuration through a `serde-env` struct instead, and locate repo files from
the project root rather than from `CARGO_MANIFEST_DIR`.
`CARGO_PKG_NAME`, `ET_WIT_DIR`, `OUT_DIR`). Read configuration through a `serde-env` struct instead, and locate repo
files from the project root rather than from `CARGO_MANIFEST_DIR`.
- `no-env-var-literal` -- `env::var` takes a named constant, never a string literal: a mistyped literal is a lookup that
silently fails, while a mistyped constant does not compile. Declare `const NAME: &str = "NAME";` beside the read. It
is the local twin of DeepSource RS-W1015.
- `no-eq-bool-literal` -- `==`/`!=` with a `true` or `false` literal as either operand is a boolean written the long
way. Use the boolean itself -- `x`, `!x` -- and when the value is not a `bool` (a parsed `serde_yaml::Value` field,
say), convert it and keep the operator: `value == true` becomes `value.as_bool() == Some(true)`, and `value != false`
Expand Down Expand Up @@ -327,6 +332,8 @@ Rules, from `config/ast-grep/rules`:

- `cargo-clippy-check` (`default` env; run by `check:rust`). Repaired by `cargo-clippy-fix`. Config: `Cargo.toml`,
`config/clippy.toml`.
- `cargo-clippy-check-pico2w` (`rust` env; not run by `mise run check`) -- Lint the Raspberry Pi Pico 2 W firmware for
its own target. Config: `Cargo.toml`, `config/clippy.toml`.
- `cargo-clippy-check-pkg` (`default` env; not run by `mise run check`) -- cargo clippy on one or more workspace
packages (narrow inner-loop check). Config: `Cargo.toml`, `config/clippy.toml`.

Expand Down
Loading
Loading