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
27 changes: 17 additions & 10 deletions .github/workflows/no-std.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
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.
# The firmware is linted bare, and built as a generated scenario's, whose modules its host build then serves to a
# test that fetches each. 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 itself 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.
Expand Down Expand Up @@ -58,17 +60,22 @@ jobs:
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
# The lint covers the firmware with no modules in it, and the build the default scenario's, as et-cli generates
# a deployment of it: the page and the wasm agent built in, which the agent's package has to be built for first.
# The same modules are then served from this host by the firmware's host build, and every one it lists fetched.
- name: Lint the Pico 2 W firmware, then build and serve a generated scenario's
timeout-minutes: 45
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
mise run build-ws-wasm-agent
src=verification/local/input/default.yaml
dst=target/firmware-default
fmt=firmware-pico2
cargo run -q -p et-cli -- generate-deployment --input-file "$src" --output-dir "$dst" --output-type "$fmt"
cd "$dst"
MISE_ENV="$fmt" mise run firmware-build
MISE_ENV="$fmt" mise run firmware-host-test
2 changes: 1 addition & 1 deletion .mise/config.coverage.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ version = "0.9.1"
# only be asserted where that path runs. A crate belongs to exactly one list: naming it in both would let a
# gap in one pipeline be answered by the other.
native_cov_libs = """
{{ "actix-web-config edge-toolkit et-otlp path test-helpers " ~
{{ "actix-web-config edge-toolkit et-otlp path sanitise test-helpers " ~
"test-nats test-otlp test-recovery ws-runner-common" -}}
"""
wasm_cov_libs = "wasi-guest web"
Expand Down
9 changes: 7 additions & 2 deletions .mise/config.rust.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@
# 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.
# Its build reads the radio's blobs from CYW43_FIRMWARE_DIR, which it fetches first, 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]
depends = ["fetch-cyw43-firmware-rclone"]
description = "Build et-ws-server as Raspberry Pi Pico 2 W firmware"
env = { CYW43_FIRMWARE_DIR = "{{ config_root }}/{{ vars.cyw43_dir }}" }
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]
depends = ["fetch-cyw43-firmware-rclone"]
description = "Lint the Raspberry Pi Pico 2 W firmware for its own target"
env = { CYW43_FIRMWARE_DIR = "{{ config_root }}/{{ vars.cyw43_dir }}" }
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.
Expand Down
6 changes: 6 additions & 0 deletions .mise/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3220,6 +3220,12 @@ alias = "cargo-nextest"
description = "Run every Rust test via nextest (web-runner excluded on gnullvm)"
run = "cargo nextest run --workspace {{ vars.cargo_ws_excludes }} --config-file config/nextest.toml"

# One package's tests, under the same runner config as the whole suite, for a caller that needs only that package's.
[tasks.cargo-test-pkg]
description = "Run one workspace package's Rust tests via nextest"
run = 'cargo nextest run --config-file config/nextest.toml -p "$usage_package"'
usage = 'arg "<package>" help="Workspace package name (e.g. et-int-ws-server-pico2w)"'

[tasks.test-recovery-supervisor]
description = "Test recovery policy, Kubernetes observations, persistence, and hub lifecycle transport"
run = "cargo test -p et-recovery-supervisor"
Expand Down
22 changes: 19 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@ Changelog 1.1.0.
`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.
default, adds `picoserve_server::serve`: the hub's `/ws` with `/health` and `/favicon.ico` beside it, over picoserve,
and the modules a `picoserve_modules::Modules` builds into the binary under `/modules` and `/`, as the `std` server
serves them from disk. A module listed as served elsewhere is redirected to a base URL instead.
- 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.
`MISE_ENV=rust mise run build-firmware-pico2w` fetches the radio's firmware blobs and builds it for the network
`WIFI_NETWORK` and `WIFI_PASSWORD` name, embedding the modules `MODULES_PATHS` names and serving `MODULES_ROOT` at
`/`.
- `et-cli generate-deployment --output-type firmware-pico2` writes a `mise.firmware-pico2.toml` whose
`firmware-flash` task builds that firmware with the scenario's modules in it and loads it onto a board. The npm
packages the modules use, such as onnxruntime-web, are redirected to jsDelivr rather than built in. A scenario with
a runner, `machines:` or published artifacts is refused, since the board runs one hub and nothing else. Its
`firmware-host-test` task serves the same modules from the host, as the firmware's host build does, and fetches
every module it lists, with no board needed.
- `et-firmware-modules` generates the source a firmware's build script writes to build ws-modules into its image,
from the same `MODULES_*` variables, so every board's firmware embeds modules the same way.
- 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 @@ -82,6 +93,11 @@ Changelog 1.1.0.
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.

- **Breaking:** `et-cli` refuses a scenario whose `module_paths:` entry, or a module directory found under one, has a
name a shell would need quoted or that some filesystem could not hold, such as `my mod`, `mod$(id)` or `con`; rename
the directory. Generated deployments no longer escape these paths. The check is the new `et-sanitise` crate, which
accepts a name only when both sanitize-filename, with its Windows rules, and shlex leave it unchanged.

- 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
2 changes: 1 addition & 1 deletion CHECKS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Checks

- **Generated:** 2026-10-30
- **Generated:** 2026-10-31
- **Checks:** 79
- **Tools:** 47
- **Shell-script checks:** 10
Expand Down
Loading
Loading